@@ -94,6 +94,7 @@ import {
9494 getPolicyIDsWithEmptyReportsForAccount,
9595 getPolicyName,
9696 getReasonAndReportActionThatRequiresAttention,
97+ getReportActionWithSmartscanError,
9798 getReportIDFromLink,
9899 getReportName as getReportNameDeprecated,
99100 getReportNotificationPreference,
@@ -110,6 +111,7 @@ import {
110111 hasActionWithErrorsForTransaction,
111112 hasEmptyReportsForPolicy,
112113 hasReceiptError,
114+ hasSmartscanError,
113115 hasVisibleReportFieldViolations,
114116 isAllowedToApproveExpenseReport,
115117 isArchivedNonExpenseReport,
@@ -15400,4 +15402,131 @@ describe('ReportUtils', () => {
1540015402 expect(action.reportActionID).toBeTruthy();
1540115403 });
1540215404 });
15405+
15406+ describe('getReportActionWithSmartscanError', () => {
15407+ const chatReportID = '100';
15408+ const expenseReportID = '200';
15409+ const transactionID = '300';
15410+ const iouReportActionID = '400';
15411+
15412+ const chatReport: Report = {
15413+ ...LHNTestUtils.getFakeReport(),
15414+ reportID: chatReportID,
15415+ type: CONST.REPORT.TYPE.CHAT,
15416+ };
15417+
15418+ const expenseReport: Report = {
15419+ ...LHNTestUtils.getFakeReport(),
15420+ reportID: expenseReportID,
15421+ type: CONST.REPORT.TYPE.EXPENSE,
15422+ parentReportID: chatReportID,
15423+ ownerAccountID: currentUserAccountID,
15424+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
15425+ };
15426+
15427+ // Transaction with $0 amount but valid merchant and created date.
15428+ // On an expense report this should NOT be a smartscan error (amount is
15429+ // irrelevant; only merchant matters). On a chat report the old code path
15430+ // would incorrectly flag getAmount(txn) === 0 as a missing field.
15431+ const transaction: Transaction = {
15432+ ...createRandomTransaction(Number(transactionID)),
15433+ transactionID,
15434+ reportID: expenseReportID,
15435+ amount: 0,
15436+ merchant: 'Coffee Shop',
15437+ modifiedMerchant: '',
15438+ created: testDate,
15439+ };
15440+
15441+ // Money-request (IOU) action that lives inside the expense report's actions
15442+ const iouAction = {
15443+ ...createRandomReportAction(Number(iouReportActionID)),
15444+ reportActionID: iouReportActionID,
15445+ actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
15446+ actorAccountID: currentUserAccountID,
15447+ originalMessage: {
15448+ IOUTransactionID: transactionID,
15449+ IOUReportID: expenseReportID,
15450+ type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
15451+ amount: 0,
15452+ currency: CONST.CURRENCY.USD,
15453+ comment: '',
15454+ participantAccountIDs: [currentUserAccountID],
15455+ },
15456+ };
15457+
15458+ // REPORT_PREVIEW action that sits in the chat report and links to the expense report
15459+ const reportPreviewAction = buildOptimisticReportPreview(chatReport, expenseReport, '', transaction);
15460+
15461+ beforeAll(async () => {
15462+ await Onyx.set(ONYXKEYS.SESSION, {email: currentUserEmail, accountID: currentUserAccountID});
15463+ await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction);
15464+ await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReportID}`, {[iouReportActionID]: iouAction});
15465+ return waitForBatchedUpdates();
15466+ });
15467+
15468+ it('should NOT flag $0 manual expense as smartscan error when expense report is resolved via reports collection', () => {
15469+ // With the reports collection the function can look up the actual
15470+ // expense report (type 'expense') via IOUReportID. For expense
15471+ // reports areRequiredFieldsEmpty checks isMerchantMissing (not
15472+ // amount), so a $0 expense with a valid merchant is fine.
15473+ const reportsCollection = {
15474+ [`${ONYXKEYS.COLLECTION.REPORT}${expenseReportID}`]: expenseReport,
15475+ };
15476+
15477+ const result = getReportActionWithSmartscanError([reportPreviewAction], chatReport, reportsCollection);
15478+ expect(result).toBeUndefined();
15479+ expect(hasSmartscanError([reportPreviewAction], chatReport, reportsCollection)).toBe(false);
15480+ });
15481+
15482+ it('should flag smartscan error when expense report has a missing merchant', async () => {
15483+ // Replace the transaction with one that has an empty merchant
15484+ const txnMissingMerchant: Transaction = {
15485+ ...transaction,
15486+ merchant: '',
15487+ modifiedMerchant: '',
15488+ };
15489+ await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, txnMissingMerchant);
15490+ await waitForBatchedUpdates();
15491+
15492+ const reportsCollection = {
15493+ [`${ONYXKEYS.COLLECTION.REPORT}${expenseReportID}`]: expenseReport,
15494+ };
15495+
15496+ expect(hasSmartscanError([reportPreviewAction], chatReport, reportsCollection)).toBe(true);
15497+
15498+ // Restore original transaction for subsequent tests
15499+ await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction);
15500+ await waitForBatchedUpdates();
15501+ });
15502+
15503+ it('should NOT flag settled (reimbursed) expense reports even with missing fields', async () => {
15504+ const settledExpenseReport: Report = {
15505+ ...expenseReport,
15506+ statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED,
15507+ };
15508+
15509+ // Even with missing merchant, a settled report should not show error
15510+ const txnMissingMerchant: Transaction = {
15511+ ...transaction,
15512+ merchant: '',
15513+ modifiedMerchant: '',
15514+ };
15515+ // isSettled reads from the global allReports Onyx store, so we must persist the settled status there
15516+ await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, txnMissingMerchant);
15517+ await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${expenseReportID}`, settledExpenseReport);
15518+ await waitForBatchedUpdates();
15519+
15520+ const reportsCollection = {
15521+ [`${ONYXKEYS.COLLECTION.REPORT}${expenseReportID}`]: settledExpenseReport,
15522+ };
15523+
15524+ expect(hasSmartscanError([reportPreviewAction], chatReport, reportsCollection)).toBe(false);
15525+
15526+ // Restore
15527+ await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction);
15528+ await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${expenseReportID}`, expenseReport);
15529+ await waitForBatchedUpdates();
15530+ });
15531+ });
1540315532});
0 commit comments