Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/libs/actions/IOU/SendInvoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type SendInvoiceOptions = {
currentUserAccountID: number;
policyRecentlyUsedCurrencies: string[];
invoiceChatReport?: OnyxEntry<OnyxTypes.Report>;
invoiceChatReportID?: string;
receiptFile?: Receipt;
policy?: OnyxEntry<OnyxTypes.Policy>;
policyTagList?: OnyxEntry<OnyxTypes.PolicyTagLists>;
Expand Down Expand Up @@ -522,6 +523,7 @@ function getSendInvoiceInformation({
currentUserAccountID,
policyRecentlyUsedCurrencies,
invoiceChatReport,
invoiceChatReportID,
receiptFile,
policy,
policyTagList,
Expand All @@ -548,6 +550,7 @@ function getSendInvoiceInformation({
if (!chatReport) {
isNewChatReport = true;
chatReport = buildOptimisticChatReport({
optimisticReportID: invoiceChatReportID,
participantList: [receiverAccountID, currentUserAccountID],
chatType: CONST.REPORT.CHAT_TYPE.INVOICE,
policyID: senderWorkspaceID,
Expand Down Expand Up @@ -671,6 +674,7 @@ function sendInvoice({
policy,
policyTagList,
policyCategories,
invoiceChatReportID,
companyName,
companyWebsite,
policyRecentlyUsedCategories,
Expand Down Expand Up @@ -698,6 +702,7 @@ function sendInvoice({
transaction,
currentUserAccountID,
policyRecentlyUsedCurrencies,
invoiceChatReportID,
invoiceChatReport,
receiptFile,
policy,
Expand Down
3 changes: 3 additions & 0 deletions src/pages/iou/request/step/IOURequestStepConfirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1020,12 +1020,14 @@ function IOURequestStepConfirmation({
if (iouType === CONST.IOU.TYPE.INVOICE) {
const invoiceChatReport =
!isEmptyObject(report) && report?.reportID && doesReportReceiverMatchParticipant(report, receiverParticipantAccountID) ? report : existingInvoiceReport;
const invoiceChatReportID = invoiceChatReport ? undefined : reportID;

sendInvoice({
currentUserAccountID: currentUserPersonalDetails.accountID,
transaction,
policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [],
invoiceChatReport,
invoiceChatReportID,
receiptFile: currentTransactionReceiptFile,
policy,
policyTagList: policyTags,
Expand Down Expand Up @@ -1149,6 +1151,7 @@ function IOURequestStepConfirmation({
userLocation,
submitPerDiemExpense,
policyRecentlyUsedCurrencies,
reportID,
],
);

Expand Down
10 changes: 7 additions & 3 deletions src/pages/iou/request/step/IOURequestStepParticipants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ function IOURequestStepParticipants({
const firstParticipantReportID = val.at(0)?.reportID;
const isPolicyExpenseChat = !!firstParticipant?.isPolicyExpenseChat;
const policy = isPolicyExpenseChat && firstParticipant?.policyID ? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${firstParticipant.policyID}`] : undefined;
const isInvoice = iouType === CONST.IOU.TYPE.INVOICE && isInvoiceRoomWithID(firstParticipantReportID);
const isInvoice = iouType === CONST.IOU.TYPE.INVOICE;
numberOfParticipants.current = val.length;

// Use transactions array if available, otherwise use initialTransactionID directly
Expand Down Expand Up @@ -284,8 +284,12 @@ function IOURequestStepParticipants({

// When a participant is selected, the reportID needs to be saved because that's the reportID that will be used in the confirmation step.
// We use || to be sure that if the first participant doesn't have a reportID, we generate a new one.
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
selectedReportID.current = firstParticipantReportID || generateReportID();
if (isInvoice) {
selectedReportID.current = firstParticipantReportID && isInvoiceRoomWithID(firstParticipantReportID) ? firstParticipantReportID : generateReportID();
} else {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
selectedReportID.current = firstParticipantReportID || generateReportID();
}

// IOUs are always reported. non-CREATE actions require a report
if (!isPolicyExpenseChat || action !== CONST.IOU.ACTION.CREATE) {
Expand Down
156 changes: 156 additions & 0 deletions tests/actions/IOUTest/SendInvoiceTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,124 @@ describe('actions/SendInvoice', () => {
expect(result.receiver).toBeDefined();
expect(result.onyxData).toBeDefined();
});

it('should use provided invoiceChatReportID when creating new invoice chat', () => {
const preGeneratedReportID = 'pre_generated_invoice_chat_123';
const mockTransaction = {
transactionID: 'transaction_with_report_id',
reportID: 'report_with_id',
amount: 500,
currency: 'USD',
created: '2024-02-01',
merchant: 'Test Merchant',
comment: {
comment: 'Invoice with pre-generated report ID',
},
participants: [
{
accountID: 123,
isSender: true,
policyID: 'workspace_test',
},
{
accountID: 456,
isSender: false,
},
],
};

const currentUserAccountID = 123;

const result = getSendInvoiceInformation({
transaction: mockTransaction as OnyxEntry<Transaction>,
currentUserAccountID,
policyRecentlyUsedCurrencies: [],
invoiceChatReport: undefined,
invoiceChatReportID: preGeneratedReportID,
receiptFile: undefined,
policy: undefined,
policyTagList: undefined,
policyCategories: undefined,
companyName: undefined,
companyWebsite: undefined,
policyRecentlyUsedCategories: [],
});

expect(result.invoiceRoom).toBeDefined();
expect(result.invoiceRoom.reportID).toBe(preGeneratedReportID);
expect(result.invoiceRoom.chatType).toBe(CONST.REPORT.CHAT_TYPE.INVOICE);
});

it('should ignore invoiceChatReportID when existing invoiceChatReport matches receiver', () => {
const preGeneratedReportID = 'should_be_ignored';
const existingReportID = 'existing_invoice_chat';
const receiverAccountID = 456;

const existingInvoiceChatReport = {
reportID: existingReportID,
chatType: CONST.REPORT.CHAT_TYPE.INVOICE,
type: CONST.REPORT.TYPE.CHAT,
participants: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'123': {
accountID: 123,
role: CONST.REPORT.ROLE.MEMBER,
notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
},
// eslint-disable-next-line @typescript-eslint/naming-convention
'456': {
accountID: receiverAccountID,
role: CONST.REPORT.ROLE.MEMBER,
notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
},
},
invoiceReceiver: {
type: CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL,
accountID: receiverAccountID,
},
};

const mockTransaction = {
transactionID: 'transaction_existing_chat',
reportID: 'report_existing',
amount: 300,
currency: 'USD',
created: '2024-02-01',
merchant: 'Existing Chat Test',
participants: [
{
accountID: 123,
isSender: true,
policyID: 'workspace_existing',
},
{
accountID: receiverAccountID,
isSender: false,
},
],
};

const currentUserAccountID = 123;

const result = getSendInvoiceInformation({
transaction: mockTransaction as OnyxEntry<Transaction>,
currentUserAccountID,
policyRecentlyUsedCurrencies: [],
invoiceChatReport: existingInvoiceChatReport as OnyxEntry<Report>,
invoiceChatReportID: preGeneratedReportID,
receiptFile: undefined,
policy: undefined,
policyTagList: undefined,
policyCategories: undefined,
companyName: undefined,
companyWebsite: undefined,
policyRecentlyUsedCategories: [],
});

expect(result.invoiceRoom).toBeDefined();
expect(result.invoiceRoom.reportID).toBe(existingReportID);
expect(result.invoiceRoom.reportID).not.toBe(preGeneratedReportID);
});
});
describe('sendInvoice', () => {
it('creates a new invoice chat when one has been converted from individual to business', async () => {
Expand Down Expand Up @@ -655,6 +773,44 @@ describe('actions/SendInvoice', () => {
expect(newPolicyRecentlyUsedTags[tagName].length).toBe(2);
expect(newPolicyRecentlyUsedTags[tagName].at(0)).toBe(transactionTag);
});

it('should use invoiceChatReportID when creating new invoice chat via sendInvoice', () => {
const preGeneratedReportID = 'pre_generated_report_id_456';
const transaction = {
...createRandomTransaction(1),
participants: [
{
accountID: 123,
isSender: true,
policyID: 'workspace_test',
},
{
accountID: 456,
isSender: false,
},
],
} as unknown as OnyxEntry<Transaction>;

// eslint-disable-next-line rulesdir/no-multiple-api-calls
const writeSpy = jest.spyOn(API, 'write').mockImplementation(jest.fn());

sendInvoice({
currentUserAccountID: 123,
transaction,
policyRecentlyUsedCurrencies: [],
invoiceChatReportID: preGeneratedReportID,
});

expect(writeSpy).toHaveBeenCalledWith(
WRITE_COMMANDS.SEND_INVOICE,
expect.objectContaining({
invoiceRoomReportID: preGeneratedReportID,
}),
expect.anything(),
);

writeSpy.mockRestore();
});
});
describe('Invoice recipient change while offline', () => {
const userAAccountID = 1;
Expand Down
Loading