@@ -9437,6 +9437,245 @@ describe('actions/IOU', () => {
94379437 expect(split1).toBeDefined();
94389438 expect(split2).toBeDefined();
94399439 });
9440+
9441+ it('should create hold report actions for split transactions when original transaction is on hold', async () => {
9442+ // Given an expense that is on hold
9443+ const amount = 10000;
9444+ let expenseReport: OnyxEntry<Report>;
9445+ let chatReport: OnyxEntry<Report>;
9446+ let originalTransactionID: string | undefined;
9447+ let transactionThreadReportID: string | undefined;
9448+
9449+ const policyID = generatePolicyID();
9450+ createWorkspace({
9451+ policyOwnerEmail: CARLOS_EMAIL,
9452+ makeMeAdmin: true,
9453+ policyName: "Carlos's Workspace for Hold Test",
9454+ policyID,
9455+ });
9456+
9457+ // Change the approval mode for the policy since default is Submit and Close
9458+ setWorkspaceApprovalMode(policyID, CARLOS_EMAIL, CONST.POLICY.APPROVAL_MODE.BASIC);
9459+ await waitForBatchedUpdates();
9460+
9461+ await getOnyxData({
9462+ key: ONYXKEYS.COLLECTION.REPORT,
9463+ waitForCollectionCallback: true,
9464+ callback: (allReports) => {
9465+ chatReport = Object.values(allReports ?? {}).find((report) => report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT);
9466+ },
9467+ });
9468+
9469+ // Create the initial expense
9470+ requestMoney({
9471+ report: chatReport,
9472+ participantParams: {
9473+ payeeEmail: RORY_EMAIL,
9474+ payeeAccountID: RORY_ACCOUNT_ID,
9475+ participant: {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID, isPolicyExpenseChat: true, reportID: chatReport?.reportID},
9476+ },
9477+ transactionParams: {
9478+ amount,
9479+ attendees: [],
9480+ currency: CONST.CURRENCY.USD,
9481+ created: '',
9482+ merchant: 'Test Merchant',
9483+ comment: 'Original expense',
9484+ },
9485+ shouldGenerateTransactionThreadReport: true,
9486+ isASAPSubmitBetaEnabled: false,
9487+ currentUserAccountIDParam: RORY_ACCOUNT_ID,
9488+ currentUserEmailParam: RORY_EMAIL,
9489+ transactionViolations: {},
9490+ });
9491+ await waitForBatchedUpdates();
9492+
9493+ await getOnyxData({
9494+ key: ONYXKEYS.COLLECTION.REPORT,
9495+ waitForCollectionCallback: true,
9496+ callback: (allReports) => {
9497+ expenseReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.EXPENSE);
9498+ },
9499+ });
9500+
9501+ // Get the original transaction ID and transaction thread report ID
9502+ await getOnyxData({
9503+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport?.reportID}`,
9504+ waitForCollectionCallback: false,
9505+ callback: (allReportsAction) => {
9506+ const iouActions = Object.values(allReportsAction ?? {}).filter((reportAction): reportAction is ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.IOU> =>
9507+ isMoneyRequestAction(reportAction),
9508+ );
9509+ const iouAction = iouActions?.at(0);
9510+ const originalMessage = isMoneyRequestAction(iouAction) ? getOriginalMessage(iouAction) : undefined;
9511+ originalTransactionID = originalMessage?.IOUTransactionID;
9512+ transactionThreadReportID = iouAction?.childReportID;
9513+ },
9514+ });
9515+
9516+ // Put the expense on hold
9517+ if (originalTransactionID && transactionThreadReportID) {
9518+ putOnHold(originalTransactionID, 'Test hold reason', transactionThreadReportID);
9519+ }
9520+ await waitForBatchedUpdates();
9521+
9522+ // Verify the transaction is on hold
9523+ const originalTransaction = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION}${originalTransactionID}`);
9524+ expect(originalTransaction?.comment?.hold).toBeDefined();
9525+
9526+ // Get the first IOU action for the split flow
9527+ let firstIOU: ReportAction | undefined;
9528+ await getOnyxData({
9529+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport?.reportID}`,
9530+ waitForCollectionCallback: false,
9531+ callback: (allReportsAction) => {
9532+ const iouActions = Object.values(allReportsAction ?? {}).filter((reportAction): reportAction is ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.IOU> =>
9533+ isMoneyRequestAction(reportAction),
9534+ );
9535+ firstIOU = iouActions?.at(0);
9536+ },
9537+ });
9538+
9539+ // Create the draft transaction with split expenses
9540+ const draftTransaction: Transaction = {
9541+ reportID: originalTransaction?.reportID ?? '456',
9542+ transactionID: originalTransaction?.transactionID ?? '234',
9543+ amount,
9544+ created: originalTransaction?.created ?? DateUtils.getDBTime(),
9545+ currency: CONST.CURRENCY.USD,
9546+ merchant: originalTransaction?.merchant ?? '',
9547+ comment: {
9548+ originalTransactionID,
9549+ comment: originalTransaction?.comment?.comment ?? '',
9550+ hold: originalTransaction?.comment?.hold,
9551+ splitExpenses: [
9552+ {
9553+ transactionID: 'split-held-tx-1',
9554+ amount: amount / 2,
9555+ description: 'Split 1',
9556+ created: DateUtils.getDBTime(),
9557+ },
9558+ {
9559+ transactionID: 'split-held-tx-2',
9560+ amount: amount / 2,
9561+ description: 'Split 2',
9562+ created: DateUtils.getDBTime(),
9563+ },
9564+ ],
9565+ attendees: [],
9566+ type: CONST.TRANSACTION.TYPE.CUSTOM_UNIT,
9567+ },
9568+ };
9569+
9570+ let allTransactions: OnyxCollection<Transaction>;
9571+ let allReports: OnyxCollection<Report>;
9572+ let allReportNameValuePairs: OnyxCollection<ReportNameValuePairs>;
9573+
9574+ await getOnyxData({
9575+ key: ONYXKEYS.COLLECTION.TRANSACTION,
9576+ waitForCollectionCallback: true,
9577+ callback: (value) => {
9578+ allTransactions = value;
9579+ },
9580+ });
9581+ await getOnyxData({
9582+ key: ONYXKEYS.COLLECTION.REPORT,
9583+ waitForCollectionCallback: true,
9584+ callback: (value) => {
9585+ allReports = value;
9586+ },
9587+ });
9588+ await getOnyxData({
9589+ key: ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS,
9590+ waitForCollectionCallback: true,
9591+ callback: (value) => {
9592+ allReportNameValuePairs = value;
9593+ },
9594+ });
9595+
9596+ // When splitting the held expense
9597+ updateSplitTransactionsFromSplitExpensesFlow({
9598+ allTransactionsList: allTransactions,
9599+ allReportsList: allReports,
9600+ allReportNameValuePairsList: allReportNameValuePairs,
9601+ transactionData: {
9602+ reportID: draftTransaction?.reportID ?? String(CONST.DEFAULT_NUMBER_ID),
9603+ originalTransactionID: draftTransaction?.comment?.originalTransactionID ?? String(CONST.DEFAULT_NUMBER_ID),
9604+ splitExpenses: draftTransaction?.comment?.splitExpenses ?? [],
9605+ splitExpensesTotal: draftTransaction?.comment?.splitExpensesTotal,
9606+ },
9607+ searchContext: {
9608+ currentSearchHash: -2,
9609+ },
9610+ policyCategories: undefined,
9611+ policy: undefined,
9612+ policyRecentlyUsedCategories: [],
9613+ iouReport: expenseReport,
9614+ firstIOU,
9615+ isASAPSubmitBetaEnabled: false,
9616+ currentUserPersonalDetails,
9617+ transactionViolations: {},
9618+ });
9619+
9620+ await waitForBatchedUpdates();
9621+
9622+ // Then verify the split transactions were created
9623+ const split1 = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION}split-held-tx-1`);
9624+ const split2 = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION}split-held-tx-2`);
9625+
9626+ expect(split1).toBeDefined();
9627+ expect(split2).toBeDefined();
9628+
9629+ // Find the transaction thread reports for each split by looking at the IOU actions
9630+ let split1ThreadReportID: string | undefined;
9631+ let split2ThreadReportID: string | undefined;
9632+
9633+ await getOnyxData({
9634+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport?.reportID}`,
9635+ waitForCollectionCallback: false,
9636+ callback: (allReportsAction) => {
9637+ const iouActions = Object.values(allReportsAction ?? {}).filter((reportAction): reportAction is ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.IOU> =>
9638+ isMoneyRequestAction(reportAction),
9639+ );
9640+ for (const action of iouActions) {
9641+ const message = isMoneyRequestAction(action) ? getOriginalMessage(action) : undefined;
9642+ if (message?.IOUTransactionID === 'split-held-tx-1') {
9643+ split1ThreadReportID = action.childReportID;
9644+ } else if (message?.IOUTransactionID === 'split-held-tx-2') {
9645+ split2ThreadReportID = action.childReportID;
9646+ }
9647+ }
9648+ },
9649+ });
9650+
9651+ // Verify that split transaction thread IDs exist
9652+ expect(split1ThreadReportID).toBeDefined();
9653+ expect(split2ThreadReportID).toBeDefined();
9654+
9655+ // Verify each split transaction thread has hold report actions
9656+ // When splitting a held expense, new hold report actions should be created for each split
9657+ if (split1ThreadReportID) {
9658+ const split1ReportActions = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${split1ThreadReportID}`);
9659+ const split1HoldActions = Object.values(split1ReportActions ?? {}).filter((action) => action?.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD);
9660+ const split1CommentActions = Object.values(split1ReportActions ?? {}).filter((action) => action?.actionName === CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT);
9661+
9662+ // Should have at least one HOLD action and one ADD_COMMENT action (the hold comment)
9663+ // The hold actions are created optimistically with pendingAction: ADD, but this
9664+ // may be cleared to null after the API call succeeds
9665+ expect(split1HoldActions.length).toBeGreaterThanOrEqual(1);
9666+ expect(split1CommentActions.length).toBeGreaterThanOrEqual(1);
9667+ }
9668+
9669+ if (split2ThreadReportID) {
9670+ const split2ReportActions = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${split2ThreadReportID}`);
9671+ const split2HoldActions = Object.values(split2ReportActions ?? {}).filter((action) => action?.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD);
9672+ const split2CommentActions = Object.values(split2ReportActions ?? {}).filter((action) => action?.actionName === CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT);
9673+
9674+ // Should have at least one HOLD action and one ADD_COMMENT action (the hold comment)
9675+ expect(split2HoldActions.length).toBeGreaterThanOrEqual(1);
9676+ expect(split2CommentActions.length).toBeGreaterThanOrEqual(1);
9677+ }
9678+ });
94409679 });
94419680 });
94429681
0 commit comments