diff --git a/src/libs/actions/IOU/SendInvoice.ts b/src/libs/actions/IOU/SendInvoice.ts index dc3f9d285fef..7f4f9c34fc9f 100644 --- a/src/libs/actions/IOU/SendInvoice.ts +++ b/src/libs/actions/IOU/SendInvoice.ts @@ -57,6 +57,7 @@ type SendInvoiceOptions = { currentUserAccountID: number; policyRecentlyUsedCurrencies: string[]; invoiceChatReport?: OnyxEntry; + invoiceChatReportID?: string; receiptFile?: Receipt; policy?: OnyxEntry; policyTagList?: OnyxEntry; @@ -522,6 +523,7 @@ function getSendInvoiceInformation({ currentUserAccountID, policyRecentlyUsedCurrencies, invoiceChatReport, + invoiceChatReportID, receiptFile, policy, policyTagList, @@ -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, @@ -671,6 +674,7 @@ function sendInvoice({ policy, policyTagList, policyCategories, + invoiceChatReportID, companyName, companyWebsite, policyRecentlyUsedCategories, @@ -698,6 +702,7 @@ function sendInvoice({ transaction, currentUserAccountID, policyRecentlyUsedCurrencies, + invoiceChatReportID, invoiceChatReport, receiptFile, policy, diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 9ee529d135ca..3219d5c75a44 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -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, @@ -1149,6 +1151,7 @@ function IOURequestStepConfirmation({ userLocation, submitPerDiemExpense, policyRecentlyUsedCurrencies, + reportID, ], ); diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index 5fab15e2a3dc..284f8ce8fd03 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -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 @@ -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) { diff --git a/tests/actions/IOUTest/SendInvoiceTest.ts b/tests/actions/IOUTest/SendInvoiceTest.ts index 42af684568b7..7bca59f9c2cc 100644 --- a/tests/actions/IOUTest/SendInvoiceTest.ts +++ b/tests/actions/IOUTest/SendInvoiceTest.ts @@ -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, + 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, + currentUserAccountID, + policyRecentlyUsedCurrencies: [], + invoiceChatReport: existingInvoiceChatReport as OnyxEntry, + 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 () => { @@ -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; + + // 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;