@@ -96,6 +96,7 @@ import {
9696 getPolicyIDsWithEmptyReportsForAccount,
9797 getPolicyName,
9898 getReasonAndReportActionThatRequiresAttention,
99+ getReportActionWithSmartscanError,
99100 getReportIDFromLink,
100101 getReportName as getReportNameDeprecated,
101102 getReportOrDraftReport,
@@ -112,6 +113,7 @@ import {
112113 hasActionWithErrorsForTransaction,
113114 hasEmptyReportsForPolicy,
114115 hasReceiptError,
116+ hasSmartscanError,
115117 hasVisibleReportFieldViolations,
116118 isAllowedToApproveExpenseReport,
117119 isArchivedNonExpenseReport,
@@ -15898,4 +15900,131 @@ describe('ReportUtils', () => {
1589815900 expect(action.reportActionID).toBeTruthy();
1589915901 });
1590015902 });
15903+
15904+ describe('getReportActionWithSmartscanError', () => {
15905+ const chatReportID = '100';
15906+ const expenseReportID = '200';
15907+ const transactionID = '300';
15908+ const iouReportActionID = '400';
15909+
15910+ const chatReport: Report = {
15911+ ...LHNTestUtils.getFakeReport(),
15912+ reportID: chatReportID,
15913+ type: CONST.REPORT.TYPE.CHAT,
15914+ };
15915+
15916+ const expenseReport: Report = {
15917+ ...LHNTestUtils.getFakeReport(),
15918+ reportID: expenseReportID,
15919+ type: CONST.REPORT.TYPE.EXPENSE,
15920+ parentReportID: chatReportID,
15921+ ownerAccountID: currentUserAccountID,
15922+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
15923+ };
15924+
15925+ // Transaction with $0 amount but valid merchant and created date.
15926+ // On an expense report this should NOT be a smartscan error (amount is
15927+ // irrelevant; only merchant matters). On a chat report the old code path
15928+ // would incorrectly flag getAmount(txn) === 0 as a missing field.
15929+ const transaction: Transaction = {
15930+ ...createRandomTransaction(Number(transactionID)),
15931+ transactionID,
15932+ reportID: expenseReportID,
15933+ amount: 0,
15934+ merchant: 'Coffee Shop',
15935+ modifiedMerchant: '',
15936+ created: testDate,
15937+ };
15938+
15939+ // Money-request (IOU) action that lives inside the expense report's actions
15940+ const iouAction = {
15941+ ...createRandomReportAction(Number(iouReportActionID)),
15942+ reportActionID: iouReportActionID,
15943+ actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
15944+ actorAccountID: currentUserAccountID,
15945+ originalMessage: {
15946+ IOUTransactionID: transactionID,
15947+ IOUReportID: expenseReportID,
15948+ type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
15949+ amount: 0,
15950+ currency: CONST.CURRENCY.USD,
15951+ comment: '',
15952+ participantAccountIDs: [currentUserAccountID],
15953+ },
15954+ };
15955+
15956+ // REPORT_PREVIEW action that sits in the chat report and links to the expense report
15957+ const reportPreviewAction = buildOptimisticReportPreview(chatReport, expenseReport, '', transaction);
15958+
15959+ beforeAll(async () => {
15960+ await Onyx.set(ONYXKEYS.SESSION, {email: currentUserEmail, accountID: currentUserAccountID});
15961+ await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction);
15962+ await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReportID}`, {[iouReportActionID]: iouAction});
15963+ return waitForBatchedUpdates();
15964+ });
15965+
15966+ it('should NOT flag $0 manual expense as smartscan error when expense report is resolved via reports collection', () => {
15967+ // With the reports collection the function can look up the actual
15968+ // expense report (type 'expense') via IOUReportID. For expense
15969+ // reports areRequiredFieldsEmpty checks isMerchantMissing (not
15970+ // amount), so a $0 expense with a valid merchant is fine.
15971+ const reportsCollection = {
15972+ [`${ONYXKEYS.COLLECTION.REPORT}${expenseReportID}`]: expenseReport,
15973+ };
15974+
15975+ const result = getReportActionWithSmartscanError([reportPreviewAction], chatReport, reportsCollection);
15976+ expect(result).toBeUndefined();
15977+ expect(hasSmartscanError([reportPreviewAction], chatReport, reportsCollection)).toBe(false);
15978+ });
15979+
15980+ it('should flag smartscan error when expense report has a missing merchant', async () => {
15981+ // Replace the transaction with one that has an empty merchant
15982+ const transactionMissingMerchant: Transaction = {
15983+ ...transaction,
15984+ merchant: '',
15985+ modifiedMerchant: '',
15986+ };
15987+ await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transactionMissingMerchant);
15988+ await waitForBatchedUpdates();
15989+
15990+ const reportsCollection = {
15991+ [`${ONYXKEYS.COLLECTION.REPORT}${expenseReportID}`]: expenseReport,
15992+ };
15993+
15994+ expect(hasSmartscanError([reportPreviewAction], chatReport, reportsCollection)).toBe(true);
15995+
15996+ // Restore original transaction for subsequent tests
15997+ await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction);
15998+ await waitForBatchedUpdates();
15999+ });
16000+
16001+ it('should NOT flag settled (reimbursed) expense reports even with missing fields', async () => {
16002+ const settledExpenseReport: Report = {
16003+ ...expenseReport,
16004+ statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED,
16005+ };
16006+
16007+ // Even with missing merchant, a settled report should not show error
16008+ const transactionMissingMerchant: Transaction = {
16009+ ...transaction,
16010+ merchant: '',
16011+ modifiedMerchant: '',
16012+ };
16013+ // isSettled reads from the global allReports Onyx store, so we must persist the settled status there
16014+ await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transactionMissingMerchant);
16015+ await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${expenseReportID}`, settledExpenseReport);
16016+ await waitForBatchedUpdates();
16017+
16018+ const reportsCollection = {
16019+ [`${ONYXKEYS.COLLECTION.REPORT}${expenseReportID}`]: settledExpenseReport,
16020+ };
16021+
16022+ expect(hasSmartscanError([reportPreviewAction], chatReport, reportsCollection)).toBe(false);
16023+
16024+ // Restore
16025+ await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction);
16026+ await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${expenseReportID}`, expenseReport);
16027+ await waitForBatchedUpdates();
16028+ });
16029+ });
1590116030});
0 commit comments