From d3f6ee39faa91376188fb087541739a68681b530 Mon Sep 17 00:00:00 2001
From: Daniel Gale-Rosen <dgalerosen@expensify.com>
Date: Wed, 9 Jul 2025 15:48:38 -0400
Subject: [PATCH 1/5] first pass

---
 src/libs/ReportPreviewActionUtils.ts | 21 ++++++++-------------
 src/libs/ReportUtils.ts              | 24 +++++++++++++++++++++---
 src/types/onyx/Report.ts             |  9 +++++++++
 3 files changed, 38 insertions(+), 16 deletions(-)

diff --git a/src/libs/ReportPreviewActionUtils.ts b/src/libs/ReportPreviewActionUtils.ts
index b0a8554213c9..4e81f0e957be 100644
--- a/src/libs/ReportPreviewActionUtils.ts
+++ b/src/libs/ReportPreviewActionUtils.ts
@@ -1,7 +1,7 @@
-import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
+import type {OnyxCollection} from 'react-native-onyx';
 import type {ValueOf} from 'type-fest';
 import CONST from '@src/CONST';
-import type {Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx';
+import type {Policy, Report, Transaction, TransactionViolation} from '@src/types/onyx';
 import {getCurrentUserAccountID} from './actions/Report';
 import {
     arePaymentsEnabled,
@@ -17,16 +17,13 @@ import {
     getMoneyRequestSpendBreakdown,
     getParentReport,
     getReportTransactions,
-    hasExportError as hasExportErrorUtil,
     hasMissingSmartscanFields,
     hasNoticeTypeViolations,
-    hasReportBeenReopened,
     hasViolations,
     hasWarningTypeViolations,
     isClosedReport,
     isCurrentUserSubmitter,
     isExpenseReport,
-    isExported as isExportedUtil,
     isInvoiceReport,
     isIOUReport,
     isOpenExpenseReport,
@@ -43,7 +40,6 @@ import {allHavePendingRTERViolation, isPending, isScanning, shouldShowBrokenConn
 function canSubmit(
     report: Report,
     violations: OnyxCollection<TransactionViolation[]>,
-    reportActions?: OnyxEntry<ReportActions> | ReportAction[],
     policy?: Policy,
     transactions?: Transaction[],
     isReportArchived = false,
@@ -57,7 +53,7 @@ function canSubmit(
     const isOpen = isOpenReport(report);
     const isManager = report.managerID === getCurrentUserAccountID();
     const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
-    const hasBeenReopened = hasReportBeenReopened(reportActions);
+    const hasBeenReopened = report.hasReportBeenReopened ?? false;
     const isManualSubmitEnabled = getCorrectedAutoReportingFrequency(policy) === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL;
 
     if (!!transactions && transactions?.length > 0 && transactions.every((transaction) => isPending(transaction))) {
@@ -171,7 +167,7 @@ function canPay(
     return invoiceReceiverPolicy?.role === CONST.POLICY.ROLE.ADMIN && reimbursableSpend > 0;
 }
 
-function canExport(report: Report, violations: OnyxCollection<TransactionViolation[]>, policy?: Policy, reportActions?: OnyxEntry<ReportActions> | ReportAction[]) {
+function canExport(report: Report, violations: OnyxCollection<TransactionViolation[]>, policy?: Policy) {
     const isExpense = isExpenseReport(report);
     const isExporter = policy ? isPreferredExporter(policy) : false;
     const isReimbursed = isSettled(report);
@@ -186,12 +182,12 @@ function canExport(report: Report, violations: OnyxCollection<TransactionViolati
         return false;
     }
 
-    const isExported = isExportedUtil(reportActions);
+    const isExported = report.isExportedToIntegration ?? false;
     if (isExported) {
         return false;
     }
 
-    const hasExportError = hasExportErrorUtil(reportActions);
+    const hasExportError = report.hasExportError ?? false;
     if (syncEnabled && !hasExportError) {
         return false;
     }
@@ -246,7 +242,6 @@ function getReportPreviewAction(
     policy?: Policy,
     transactions?: Transaction[],
     isReportArchived = false,
-    reportActions?: OnyxEntry<ReportActions> | ReportAction[],
     invoiceReceiverPolicy?: Policy,
 ): ValueOf<typeof CONST.REPORT.REPORT_PREVIEW_ACTIONS> {
     if (!report) {
@@ -255,7 +250,7 @@ function getReportPreviewAction(
     if (isAddExpenseAction(report, transactions ?? [], isReportArchived)) {
         return CONST.REPORT.REPORT_PREVIEW_ACTIONS.ADD_EXPENSE;
     }
-    if (canSubmit(report, violations, reportActions, policy, transactions, isReportArchived)) {
+    if (canSubmit(report, violations, policy, transactions, isReportArchived)) {
         return CONST.REPORT.REPORT_PREVIEW_ACTIONS.SUBMIT;
     }
     if (canApprove(report, violations, policy, transactions)) {
@@ -264,7 +259,7 @@ function getReportPreviewAction(
     if (canPay(report, violations, policy, isReportArchived, invoiceReceiverPolicy)) {
         return CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY;
     }
-    if (canExport(report, violations, policy, reportActions)) {
+    if (canExport(report, violations, policy)) {
         return CONST.REPORT.REPORT_PREVIEW_ACTIONS.EXPORT_TO_ACCOUNTING;
     }
     if (canReview(report, violations, policy, transactions, isReportArchived)) {
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 3af5e1e8c4e6..76de16305944 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -10771,7 +10771,13 @@ function getIntegrationNameFromExportMessage(reportActions: OnyxEntry<ReportActi
     }
 }
 
-function isExported(reportActions: OnyxEntry<ReportActions> | ReportAction[]) {
+function isExported(reportActions: OnyxEntry<ReportActions> | ReportAction[], report?: OnyxEntry<Report>): boolean {
+    // If report object is provided and has the property, use it directly
+    if (report?.isExportedToIntegration !== undefined) {
+        return report.isExportedToIntegration;
+    }
+
+    // Fallback to checking actions for backward compatibility
     if (!reportActions) {
         return false;
     }
@@ -10799,7 +10805,13 @@ function isExported(reportActions: OnyxEntry<ReportActions> | ReportAction[]) {
     return exportIntegrationActionsCount > integrationMessageActionsCount;
 }
 
-function hasExportError(reportActions: OnyxEntry<ReportActions> | ReportAction[]) {
+function hasExportError(reportActions: OnyxEntry<ReportActions> | ReportAction[], report?: OnyxEntry<Report>) {
+    // If report object is provided and has the property, use it directly
+    if (report?.hasExportError !== undefined) {
+        return report.hasExportError;
+    }
+
+    // Fallback to checking actions for backward compatibility
     if (!reportActions) {
         return false;
     }
@@ -11076,7 +11088,13 @@ function findReportIDForAction(action?: ReportAction): string | undefined {
         ?.replace(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}`, '');
 }
 
-function hasReportBeenReopened(reportActions: OnyxEntry<ReportActions> | ReportAction[]): boolean {
+function hasReportBeenReopened(reportActions: OnyxEntry<ReportActions> | ReportAction[], report?: OnyxEntry<Report>): boolean {
+    // If report object is provided and has the property, use it directly
+    if (report?.hasReportBeenReopened !== undefined) {
+        return report.hasReportBeenReopened;
+    }
+
+    // Fallback to checking actions for backward compatibility
     if (!reportActions) {
         return false;
     }
diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts
index b6ba318ab743..cea4963592cf 100644
--- a/src/types/onyx/Report.ts
+++ b/src/types/onyx/Report.ts
@@ -194,6 +194,15 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback<
         /** Whether the report is cancelled */
         isCancelledIOU?: boolean;
 
+        /** Whether the report has been reopened */
+        hasReportBeenReopened?: boolean;
+
+        /** Whether the report has been exported to integration */
+        isExportedToIntegration?: boolean;
+
+        /** Whether the report has any export errors */
+        hasExportError?: boolean;
+
         /** The ID of the IOU report */
         iouReportID?: string;
 

From e3ccbc00220baf3c4109b8afa2e836298e24f206 Mon Sep 17 00:00:00 2001
From: Daniel Gale-Rosen <dgalerosen@expensify.com>
Date: Mon, 14 Jul 2025 14:30:33 -0400
Subject: [PATCH 2/5] fix tests

---
 tests/actions/ReportPreviewActionUtilsTest.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/tests/actions/ReportPreviewActionUtilsTest.ts b/tests/actions/ReportPreviewActionUtilsTest.ts
index d598044c46d7..e0b858796b86 100644
--- a/tests/actions/ReportPreviewActionUtilsTest.ts
+++ b/tests/actions/ReportPreviewActionUtilsTest.ts
@@ -287,7 +287,7 @@ describe('getReportPreviewAction', () => {
         } as unknown as Transaction;
 
         const {result: isReportArchived} = renderHook(() => useReportIsArchived(report?.parentReportID));
-        expect(getReportPreviewAction(VIOLATIONS, report, policy, [transaction], isReportArchived.current, undefined, invoiceReceiverPolicy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY);
+        expect(getReportPreviewAction(VIOLATIONS, report, policy, [transaction], isReportArchived.current, invoiceReceiverPolicy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY);
     });
 
     it('getReportPreviewAction should return VIEW action for zero value invoice', async () => {
@@ -332,7 +332,7 @@ describe('getReportPreviewAction', () => {
 
         const {result: isReportArchived} = renderHook(() => useReportIsArchived(report.parentReportID));
 
-        expect(getReportPreviewAction(VIOLATIONS, report, policy, [transaction], isReportArchived.current, undefined, invoiceReceiverPolicy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.VIEW);
+        expect(getReportPreviewAction(VIOLATIONS, report, policy, [transaction], isReportArchived.current, invoiceReceiverPolicy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.VIEW);
     });
 
     it('canPay should return false for archived invoice', async () => {
@@ -364,7 +364,7 @@ describe('getReportPreviewAction', () => {
             reportID: `${REPORT_ID}`,
         } as unknown as Transaction;
         const {result: isReportArchived} = renderHook(() => useReportIsArchived(report?.parentReportID));
-        expect(getReportPreviewAction(VIOLATIONS, report, policy, [transaction], isReportArchived.current, undefined, invoiceReceiverPolicy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY);
+        expect(getReportPreviewAction(VIOLATIONS, report, policy, [transaction], isReportArchived.current, invoiceReceiverPolicy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY);
     });
 
     it('getReportPreviewAction should return VIEW action for invoice when the chat report is archived', async () => {

From 21ca37c6441ceb37a929f7f60df6b746efa1efd5 Mon Sep 17 00:00:00 2001
From: Daniel Gale-Rosen <dgalerosen@expensify.com>
Date: Mon, 14 Jul 2025 20:09:54 -0400
Subject: [PATCH 3/5] lint/typecheck errors

---
 .../MoneyRequestReportPreviewContent.tsx                    | 2 +-
 src/libs/DebugUtils.ts                                      | 6 ++++++
 src/types/utils/whitelistedReportKeys.ts                    | 3 +++
 3 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
index 12035cddd621..5038cb8bb53e 100644
--- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
+++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
@@ -456,7 +456,7 @@ function MoneyRequestReportPreviewContent({
         if (isPaidAnimationRunning) {
             return CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY;
         }
-        return getReportPreviewAction(violations, iouReport, policy, transactions, isIouReportArchived || isChatReportArchived, reportActions, invoiceReceiverPolicy);
+        return getReportPreviewAction(violations, iouReport, policy, transactions, isIouReportArchived || isChatReportArchived, invoiceReceiverPolicy);
     }, [isPaidAnimationRunning, violations, iouReport, policy, transactions, isIouReportArchived, reportActions, invoiceReceiverPolicy, isChatReportArchived]);
 
     const addExpenseDropdownOptions = useMemo(
diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts
index 5b8ba052f549..4ae9004bbf8b 100644
--- a/src/libs/DebugUtils.ts
+++ b/src/libs/DebugUtils.ts
@@ -472,6 +472,9 @@ function validateReportDraftProperty(key: keyof Report | keyof ReportNameValuePa
         case 'isDeletedParentAction':
         case 'isWaitingOnBankAccount':
         case 'isCancelledIOU':
+        case 'hasReportBeenReopened':
+        case 'isExportedToIntegration':
+        case 'hasExportError':
             return validateBoolean(value);
         case 'exportFailedTime':
         case 'lastReadSequenceNumber':
@@ -623,6 +626,9 @@ function validateReportDraftProperty(key: keyof Report | keyof ReportNameValuePa
                 unheldNonReimbursableTotal: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                 isWaitingOnBankAccount: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                 isCancelledIOU: CONST.RED_BRICK_ROAD_PENDING_ACTION,
+                hasReportBeenReopened: CONST.RED_BRICK_ROAD_PENDING_ACTION,
+                isExportedToIntegration: CONST.RED_BRICK_ROAD_PENDING_ACTION,
+                hasExportError: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                 iouReportID: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                 preexistingReportID: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                 nonReimbursableTotal: CONST.RED_BRICK_ROAD_PENDING_ACTION,
diff --git a/src/types/utils/whitelistedReportKeys.ts b/src/types/utils/whitelistedReportKeys.ts
index 847efeb1c7ae..6c4f4771c2d1 100644
--- a/src/types/utils/whitelistedReportKeys.ts
+++ b/src/types/utils/whitelistedReportKeys.ts
@@ -50,6 +50,9 @@ type WhitelistedReport = OnyxCommon.OnyxValueWithOfflineFeedback<
         errors: unknown;
         isWaitingOnBankAccount: unknown;
         isCancelledIOU: unknown;
+        hasReportBeenReopened: unknown;
+        isExportedToIntegration: unknown;
+        hasExportError: unknown;
         iouReportID: unknown;
         preexistingReportID: unknown;
         nonReimbursableTotal: unknown;

From 74e22b4eeb229424e879d8e1f82ba07bbf840295 Mon Sep 17 00:00:00 2001
From: Daniel Gale-Rosen <dgalerosen@expensify.com>
Date: Mon, 14 Jul 2025 22:19:14 -0400
Subject: [PATCH 4/5] prettier

---
 src/libs/ReportPreviewActionUtils.ts | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/src/libs/ReportPreviewActionUtils.ts b/src/libs/ReportPreviewActionUtils.ts
index f9244add02fb..7876368835b9 100644
--- a/src/libs/ReportPreviewActionUtils.ts
+++ b/src/libs/ReportPreviewActionUtils.ts
@@ -37,13 +37,7 @@ import {
 import {getSession} from './SessionUtils';
 import {allHavePendingRTERViolation, isPending, isScanning, shouldShowBrokenConnectionViolationForMultipleTransactions} from './TransactionUtils';
 
-function canSubmit(
-    report: Report,
-    violations: OnyxCollection<TransactionViolation[]>,
-    policy?: Policy,
-    transactions?: Transaction[],
-    isReportArchived = false,
-) {
+function canSubmit(report: Report, violations: OnyxCollection<TransactionViolation[]>, policy?: Policy, transactions?: Transaction[], isReportArchived = false) {
     if (isReportArchived) {
         return false;
     }

From 6225ab293b4d1569f11e404df9e032b6a2d4bc78 Mon Sep 17 00:00:00 2001
From: Daniel Gale-Rosen <dgalerosen@expensify.com>
Date: Wed, 23 Jul 2025 15:07:31 -0400
Subject: [PATCH 5/5] prettier

---
 src/libs/ReportPreviewActionUtils.ts | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/src/libs/ReportPreviewActionUtils.ts b/src/libs/ReportPreviewActionUtils.ts
index 91107a90f621..5e08702429e4 100644
--- a/src/libs/ReportPreviewActionUtils.ts
+++ b/src/libs/ReportPreviewActionUtils.ts
@@ -37,13 +37,7 @@ import {
 import {getSession} from './SessionUtils';
 import {allHavePendingRTERViolation, isPending, isScanning, shouldShowBrokenConnectionViolationForMultipleTransactions} from './TransactionUtils';
 
-function canSubmit(
-    report: Report,
-    violations: OnyxCollection<TransactionViolation[]>,
-    isReportArchived: boolean,
-    policy?: Policy,
-    transactions?: Transaction[],
-) {
+function canSubmit(report: Report, violations: OnyxCollection<TransactionViolation[]>, isReportArchived: boolean, policy?: Policy, transactions?: Transaction[]) {
     if (isReportArchived) {
         return false;
     }
