From 181f46492d06e2588321f876023af6f5d976dc4b Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Fri, 10 Oct 2025 04:04:29 +0200
Subject: [PATCH 01/17] Block report submission when strict policy rules are
 enabled with violations

---
 src/components/AnimatedSubmitButton/index.tsx | 27 ++++++++----
 src/components/MoneyReportHeader.tsx          | 27 +++++++++++-
 .../MoneyRequestReportPreviewContent.tsx      | 41 +++++++++++++++----
 src/hooks/useStrictPolicyRules.ts             | 37 +++++++++++++++++
 src/languages/en.ts                           |  1 +
 src/languages/es.ts                           |  1 +
 src/libs/ReportPreviewActionUtils.ts          |  9 ++++
 src/libs/ReportUtils.ts                       | 17 ++++++++
 8 files changed, 143 insertions(+), 17 deletions(-)
 create mode 100644 src/hooks/useStrictPolicyRules.ts

diff --git a/src/components/AnimatedSubmitButton/index.tsx b/src/components/AnimatedSubmitButton/index.tsx
index aabe97dca511..0eb0b929f0bf 100644
--- a/src/components/AnimatedSubmitButton/index.tsx
+++ b/src/components/AnimatedSubmitButton/index.tsx
@@ -2,6 +2,7 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
 import Animated, {Keyframe, runOnJS, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
 import Button from '@components/Button';
 import * as Expensicons from '@components/Icon/Expensicons';
+import Tooltip from '@components/Tooltip';
 import useLocalize from '@hooks/useLocalize';
 import useThemeStyles from '@hooks/useThemeStyles';
 import variables from '@styles/variables';
@@ -22,9 +23,15 @@ type AnimatedSubmitButtonProps = {
 
     // Function to call when the animation finishes
     onAnimationFinish: () => void;
+
+    // Whether the button should be disabled
+    isDisabled?: boolean;
+
+    // Tooltip text to show on hover when button is disabled
+    tooltipText?: string;
 };
 
-function AnimatedSubmitButton({success, text, onPress, isSubmittingAnimationRunning, onAnimationFinish}: AnimatedSubmitButtonProps) {
+function AnimatedSubmitButton({success, text, onPress, isSubmittingAnimationRunning, onAnimationFinish, isDisabled, tooltipText}: AnimatedSubmitButtonProps) {
     const styles = useThemeStyles();
     const {translate} = useLocalize();
     const isAnimationRunning = isSubmittingAnimationRunning;
@@ -123,12 +130,18 @@ function AnimatedSubmitButton({success, text, onPress, isSubmittingAnimationRunn
                 </Animated.View>
             )}
             {!isAnimationRunning && (
-                <Button
-                    success={success}
-                    text={text}
-                    onPress={onPress}
-                    icon={icon}
-                />
+                <Tooltip
+                    text={tooltipText ?? ''}
+                    shouldRender={!!tooltipText}
+                >
+                    <Button
+                        success={success}
+                        text={text}
+                        onPress={onPress}
+                        icon={icon}
+                        isDisabled={isDisabled}
+                    />
+                </Tooltip>
             )}
         </Animated.View>
     );
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 95761f8d71be..b88c2a7f5936 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -20,6 +20,7 @@ import usePermissions from '@hooks/usePermissions';
 import useReportIsArchived from '@hooks/useReportIsArchived';
 import useResponsiveLayout from '@hooks/useResponsiveLayout';
 import useSelectedTransactionsActions from '@hooks/useSelectedTransactionsActions';
+import useStrictPolicyRules from '@hooks/useStrictPolicyRules';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useTransactionsAndViolationsForReport from '@hooks/useTransactionsAndViolationsForReport';
@@ -69,6 +70,7 @@ import {
     navigateOnDeleteExpense,
     navigateToDetailsPage,
     rejectMoneyRequestReason,
+    shouldBlockSubmitDueToStrictPolicyRules,
 } from '@libs/ReportUtils';
 import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
 import {
@@ -203,6 +205,7 @@ function MoneyReportHeader({
     const [lastDistanceExpenseType] = useOnyx(ONYXKEYS.NVP_LAST_DISTANCE_EXPENSE_TYPE, {canBeMissing: true});
     const exportTemplates = useMemo(() => getExportTemplates(integrationsExportTemplates ?? [], csvExportLayouts ?? {}, policy), [integrationsExportTemplates, csvExportLayouts, policy]);
     const {isBetaEnabled} = usePermissions();
+    const {isStrictPolicyRulesEnabled} = useStrictPolicyRules();
 
     const requestParentReportAction = useMemo(() => {
         if (!reportActions || !transactionThreadReport?.parentReportActionID) {
@@ -217,6 +220,11 @@ function MoneyReportHeader({
         return Object.values(reportTransactions);
     }, [reportTransactions]);
 
+    // Check if submission should be blocked due to strict policy rules and violations
+    const shouldBlockSubmit = useMemo(() => {
+        return shouldBlockSubmitDueToStrictPolicyRules(moneyRequestReport?.reportID, violations, isStrictPolicyRulesEnabled, transactions);
+    }, [moneyRequestReport?.reportID, violations, isStrictPolicyRulesEnabled, transactions]);
+
     const iouTransactionID = isMoneyRequestAction(requestParentReportAction) ? getOriginalMessage(requestParentReportAction)?.IOUTransactionID : undefined;
     const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(iouTransactionID)}`, {
         canBeMissing: true,
@@ -385,7 +393,20 @@ function MoneyReportHeader({
     // to avoid any flicker during transitions between online/offline states
     const nextApproverAccountID = getNextApproverAccountID(moneyRequestReport);
     const isSubmitterSameAsNextApprover = isReportOwner(moneyRequestReport) && nextApproverAccountID === moneyRequestReport?.ownerAccountID;
-    const optimisticNextStep = isSubmitterSameAsNextApprover && policy?.preventSelfApproval ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() : nextStep;
+    let optimisticNextStep = isSubmitterSameAsNextApprover && policy?.preventSelfApproval ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() : nextStep;
+
+    // When strict policy rules are enabled and there are violations, show a custom next step message
+    if (shouldBlockSubmit && isReportOwner(moneyRequestReport)) {
+        optimisticNextStep = {
+            type: 'alert',
+            icon: CONST.NEXT_STEP.ICONS.HOURGLASS,
+            message: [
+                {
+                    text: translate('iou.waitingForSubmitterToFixViolations'),
+                },
+            ],
+        };
+    }
 
     const shouldShowNextStep = isFromPaidPolicy && !isInvoiceReport && !shouldShowStatusBar;
     const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(moneyRequestReport, shouldShowPayButton);
@@ -740,7 +761,7 @@ function MoneyReportHeader({
                 success
                 text={translate('common.submit')}
                 onPress={() => {
-                    if (!moneyRequestReport) {
+                    if (!moneyRequestReport || shouldBlockSubmit) {
                         return;
                     }
                     startSubmittingAnimation();
@@ -757,6 +778,8 @@ function MoneyReportHeader({
                 }}
                 isSubmittingAnimationRunning={isSubmittingAnimationRunning}
                 onAnimationFinish={stopAnimation}
+                isDisabled={shouldBlockSubmit}
+                tooltipText={shouldBlockSubmit ? translate('iou.waitingForSubmitterToFixViolations') : undefined}
             />
         ),
         [CONST.REPORT.PRIMARY_ACTIONS.APPROVE]: (
diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
index 9434fe158f90..13518cb59f3b 100644
--- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
+++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
@@ -22,6 +22,7 @@ import ExportWithDropdownMenu from '@components/ReportActionItem/ExportWithDropd
 import AnimatedSettlementButton from '@components/SettlementButton/AnimatedSettlementButton';
 import {showContextMenuForReport} from '@components/ShowContextMenuContext';
 import Text from '@components/Text';
+import Tooltip from '@components/Tooltip';
 import useLocalize from '@hooks/useLocalize';
 import useNetwork from '@hooks/useNetwork';
 import useOnyx from '@hooks/useOnyx';
@@ -29,6 +30,7 @@ import useParticipantsInvoiceReport from '@hooks/useParticipantsInvoiceReport';
 import usePaymentAnimations from '@hooks/usePaymentAnimations';
 import useReportIsArchived from '@hooks/useReportIsArchived';
 import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useStrictPolicyRules from '@hooks/useStrictPolicyRules';
 import useStyleUtils from '@hooks/useStyleUtils';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
@@ -66,6 +68,7 @@ import {
     isSettled,
     isTripRoom as isTripRoomReportUtils,
     isWaitingForSubmissionFromCurrentUser as isWaitingForSubmissionFromCurrentUserReportUtils,
+    shouldBlockSubmitDueToStrictPolicyRules,
 } from '@libs/ReportUtils';
 import shouldAdjustScroll from '@libs/shouldAdjustScroll';
 import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
@@ -131,6 +134,7 @@ function MoneyRequestReportPreviewContent({
     const StyleUtils = useStyleUtils();
     const {translate} = useLocalize();
     const {isOffline} = useNetwork();
+    const {isStrictPolicyRulesEnabled} = useStrictPolicyRules();
     const {shouldUseNarrowLayout} = useResponsiveLayout();
 
     const {areAllRequestsBeingSmartScanned, hasNonReimbursableTransactions} = useMemo(
@@ -470,6 +474,10 @@ function MoneyRequestReportPreviewContent({
         Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(iouReportID, undefined, undefined, Navigation.getActiveRoute()));
     }, [iouReportID]);
 
+    const shouldBlockSubmit = useMemo(() => {
+        return shouldBlockSubmitDueToStrictPolicyRules(iouReport?.reportID, violations, isStrictPolicyRulesEnabled, transactions);
+    }, [iouReport?.reportID, violations, isStrictPolicyRulesEnabled, transactions]);
+
     const reportPreviewAction = useMemo(() => {
         return getReportPreviewAction(
             violations,
@@ -480,8 +488,20 @@ function MoneyRequestReportPreviewContent({
             invoiceReceiverPolicy,
             isPaidAnimationRunning,
             isSubmittingAnimationRunning,
+            isStrictPolicyRulesEnabled,
         );
-    }, [isPaidAnimationRunning, isSubmittingAnimationRunning, violations, iouReport, policy, transactions, isIouReportArchived, invoiceReceiverPolicy, isChatReportArchived]);
+    }, [
+        isPaidAnimationRunning,
+        isSubmittingAnimationRunning,
+        violations,
+        iouReport,
+        policy,
+        transactions,
+        isIouReportArchived,
+        invoiceReceiverPolicy,
+        isChatReportArchived,
+        isStrictPolicyRulesEnabled,
+    ]);
 
     const addExpenseDropdownOptions = useMemo(
         () => [
@@ -598,13 +618,18 @@ function MoneyRequestReportPreviewContent({
             />
         ) : null,
         [CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW]: (
-            <Button
-                icon={Expensicons.DotIndicator}
-                iconFill={theme.danger}
-                iconHoverFill={theme.danger}
-                text={translate('common.review')}
-                onPress={() => openReportFromPreview()}
-            />
+            <Tooltip
+                text={shouldBlockSubmit ? translate('iou.waitingForSubmitterToFixViolations') : ''}
+                shouldRender={shouldBlockSubmit}
+            >
+                <Button
+                    icon={Expensicons.DotIndicator}
+                    iconFill={theme.danger}
+                    iconHoverFill={theme.danger}
+                    text={translate('common.review')}
+                    onPress={() => openReportFromPreview()}
+                />
+            </Tooltip>
         ),
         [CONST.REPORT.REPORT_PREVIEW_ACTIONS.VIEW]: (
             <Button
diff --git a/src/hooks/useStrictPolicyRules.ts b/src/hooks/useStrictPolicyRules.ts
new file mode 100644
index 000000000000..6961b8cfa871
--- /dev/null
+++ b/src/hooks/useStrictPolicyRules.ts
@@ -0,0 +1,37 @@
+import {Str} from 'expensify-common';
+import ONYXKEYS from '@src/ONYXKEYS';
+import useOnyx from './useOnyx';
+
+type UseStrictPolicyRulesResult = {
+    /** Whether the user's domain has strict policy rules enabled (strictly enforce workspace rules) */
+    isStrictPolicyRulesEnabled: boolean;
+};
+
+/**
+ * Hook to check if strict policy rules are enabled for the user's domain security group.
+ * When enabled, users cannot submit reports that have policy violations.
+ */
+function useStrictPolicyRules(): UseStrictPolicyRulesResult {
+    const [myDomainSecurityGroups] = useOnyx(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS, {canBeMissing: true});
+    const [securityGroups] = useOnyx(ONYXKEYS.COLLECTION.SECURITY_GROUP, {canBeMissing: true});
+    const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true});
+
+    // Get the user's domain from their email
+    const userDomain = session?.email ? Str.extractEmailDomain(session.email) : undefined;
+
+    // Get the security group ID for the user's domain
+    const securityGroupID = userDomain && myDomainSecurityGroups?.[userDomain];
+
+    // Get the security group details
+    const securityGroupKey = `${ONYXKEYS.COLLECTION.SECURITY_GROUP}${securityGroupID}`;
+    const securityGroup = securityGroupID ? securityGroups?.[securityGroupKey] : null;
+
+    // Check if strict policy rules are enabled
+    const isStrictPolicyRulesEnabled = securityGroup?.enableStrictPolicyRules === true;
+
+    return {
+        isStrictPolicyRulesEnabled,
+    };
+}
+
+export default useStrictPolicyRules;
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 612d87398b22..a6d1e832a3de 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1419,6 +1419,7 @@ const translations = {
         dates: 'Dates',
         rates: 'Rates',
         submitsTo: ({name}: SubmitsToParams) => `Submits to ${name}`,
+        waitingForSubmitterToFixViolations: 'Waiting for you to fix the issue(s). Your admins have restricted submission of expenses that have violations.',
         reject: {
             educationalTitle: 'Should you hold or reject?',
             educationalText: "If you're not ready to approve or pay an expense, you can hold or reject it.",
diff --git a/src/languages/es.ts b/src/languages/es.ts
index ce87097c23f6..dadb199573c6 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1412,6 +1412,7 @@ const translations = {
         dates: 'Fechas',
         rates: 'Tasas',
         submitsTo: ({name}: SubmitsToParams) => `Se envía a ${name}`,
+        waitingForSubmitterToFixViolations: 'Esperando que soluciones los problemas. Tus administradores han restringido el envío de gastos que tienen infracciones.',
 
         reject: {
             educationalTitle: '¿Debes retener o rechazar?',
diff --git a/src/libs/ReportPreviewActionUtils.ts b/src/libs/ReportPreviewActionUtils.ts
index ad9223b3cf9b..a89a619e594b 100644
--- a/src/libs/ReportPreviewActionUtils.ts
+++ b/src/libs/ReportPreviewActionUtils.ts
@@ -26,6 +26,7 @@ import {
     isReportManuallyReimbursed,
     isSettled,
     requiresManualSubmission,
+    shouldBlockSubmitDueToStrictPolicyRules,
 } from './ReportUtils';
 import {getSession} from './SessionUtils';
 import {allHavePendingRTERViolation, isPending, isScanning, shouldShowBrokenConnectionViolationForMultipleTransactions} from './TransactionUtils';
@@ -221,6 +222,7 @@ function getReportPreviewAction(
     invoiceReceiverPolicy?: Policy,
     isPaidAnimationRunning?: boolean,
     isSubmittingAnimationRunning?: boolean,
+    isStrictPolicyRulesEnabled?: boolean,
 ): ValueOf<typeof CONST.REPORT.REPORT_PREVIEW_ACTIONS> {
     if (!report) {
         return CONST.REPORT.REPORT_PREVIEW_ACTIONS.VIEW;
@@ -236,6 +238,13 @@ function getReportPreviewAction(
     if (isAddExpenseAction(report, transactions ?? [], isReportArchived)) {
         return CONST.REPORT.REPORT_PREVIEW_ACTIONS.ADD_EXPENSE;
     }
+
+    // When strict policy rules are enabled and there are violations, show REVIEW button instead of SUBMIT
+    const shouldBlockSubmit = shouldBlockSubmitDueToStrictPolicyRules(report.reportID, violations, isStrictPolicyRulesEnabled ?? false, transactions);
+    if (shouldBlockSubmit && canReview(report, violations, isReportArchived, policy, transactions)) {
+        return CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW;
+    }
+
     if (canSubmit(report, violations, isReportArchived, policy, transactions)) {
         return CONST.REPORT.REPORT_PREVIEW_ACTIONS.SUBMIT;
     }
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 4676cc560998..947f4180e694 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -8518,6 +8518,22 @@ function hasReportViolations(reportID: string | undefined) {
     return Object.values(reportViolations ?? {}).some((violations) => !isEmptyObject(violations));
 }
 
+/**
+ * Checks if submission should be blocked due to strict policy rules being enabled and violations present.
+ * When a user's domain has "strictly enforce workspace rules" enabled, they cannot submit reports with violations.
+ */
+function shouldBlockSubmitDueToStrictPolicyRules(
+    reportID: string | undefined,
+    transactionViolations: OnyxCollection<TransactionViolation[]>,
+    isStrictPolicyRulesEnabled: boolean,
+    reportTransactions?: Transaction[] | SearchTransaction[],
+) {
+    if (!isStrictPolicyRulesEnabled) {
+        return false;
+    }
+    return hasAnyViolations(reportID, transactionViolations, reportTransactions as SearchTransaction[]);
+}
+
 type ReportErrorsAndReportActionThatRequiresAttention = {
     errors: ErrorFields;
     reportAction?: OnyxEntry<ReportAction>;
@@ -12299,6 +12315,7 @@ export {
     excludeParticipantsForDisplay,
     getReportName,
     doesReportContainRequestsFromMultipleUsers,
+    shouldBlockSubmitDueToStrictPolicyRules,
 };
 export type {
     Ancestor,

From 590e3229f34d6efaf8e5298a1cc0d6b27aacac47 Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Sat, 18 Oct 2025 22:34:01 +0200
Subject: [PATCH 02/17] Remove violation tooltip

---
 src/components/AnimatedSubmitButton/index.tsx | 25 ++++++-------------
 src/components/MoneyReportHeader.tsx          |  1 -
 .../MoneyRequestReportPreviewContent.tsx      | 25 ++++++-------------
 src/languages/de.ts                           |  1 +
 src/languages/en.ts                           |  2 +-
 src/languages/es.ts                           |  3 +--
 src/languages/fr.ts                           |  1 +
 src/languages/it.ts                           |  1 +
 src/languages/ja.ts                           |  1 +
 src/languages/nl.ts                           |  1 +
 src/languages/pl.ts                           |  1 +
 src/languages/pt-BR.ts                        |  1 +
 src/languages/zh-hans.ts                      |  1 +
 13 files changed, 25 insertions(+), 39 deletions(-)

diff --git a/src/components/AnimatedSubmitButton/index.tsx b/src/components/AnimatedSubmitButton/index.tsx
index 0eb0b929f0bf..8567f82b7616 100644
--- a/src/components/AnimatedSubmitButton/index.tsx
+++ b/src/components/AnimatedSubmitButton/index.tsx
@@ -2,7 +2,6 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
 import Animated, {Keyframe, runOnJS, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
 import Button from '@components/Button';
 import * as Expensicons from '@components/Icon/Expensicons';
-import Tooltip from '@components/Tooltip';
 import useLocalize from '@hooks/useLocalize';
 import useThemeStyles from '@hooks/useThemeStyles';
 import variables from '@styles/variables';
@@ -26,12 +25,9 @@ type AnimatedSubmitButtonProps = {
 
     // Whether the button should be disabled
     isDisabled?: boolean;
-
-    // Tooltip text to show on hover when button is disabled
-    tooltipText?: string;
 };
 
-function AnimatedSubmitButton({success, text, onPress, isSubmittingAnimationRunning, onAnimationFinish, isDisabled, tooltipText}: AnimatedSubmitButtonProps) {
+function AnimatedSubmitButton({success, text, onPress, isSubmittingAnimationRunning, onAnimationFinish, isDisabled}: AnimatedSubmitButtonProps) {
     const styles = useThemeStyles();
     const {translate} = useLocalize();
     const isAnimationRunning = isSubmittingAnimationRunning;
@@ -130,18 +126,13 @@ function AnimatedSubmitButton({success, text, onPress, isSubmittingAnimationRunn
                 </Animated.View>
             )}
             {!isAnimationRunning && (
-                <Tooltip
-                    text={tooltipText ?? ''}
-                    shouldRender={!!tooltipText}
-                >
-                    <Button
-                        success={success}
-                        text={text}
-                        onPress={onPress}
-                        icon={icon}
-                        isDisabled={isDisabled}
-                    />
-                </Tooltip>
+                <Button
+                    success={success}
+                    text={text}
+                    onPress={onPress}
+                    icon={icon}
+                    isDisabled={isDisabled}
+                />
             )}
         </Animated.View>
     );
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index a11a0095ded3..f8f95a42c034 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -793,7 +793,6 @@ function MoneyReportHeader({
                 isSubmittingAnimationRunning={isSubmittingAnimationRunning}
                 onAnimationFinish={stopAnimation}
                 isDisabled={shouldBlockSubmit}
-                tooltipText={shouldBlockSubmit ? translate('iou.waitingForSubmitterToFixViolations') : undefined}
             />
         ),
         [CONST.REPORT.PRIMARY_ACTIONS.APPROVE]: (
diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
index 8c300a5a6649..318c5f185f17 100644
--- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
+++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
@@ -21,7 +21,6 @@ import ExportWithDropdownMenu from '@components/ReportActionItem/ExportWithDropd
 import AnimatedSettlementButton from '@components/SettlementButton/AnimatedSettlementButton';
 import {showContextMenuForReport} from '@components/ShowContextMenuContext';
 import Text from '@components/Text';
-import Tooltip from '@components/Tooltip';
 import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
 import useLocalize from '@hooks/useLocalize';
 import useNetwork from '@hooks/useNetwork';
@@ -72,7 +71,6 @@ import {
     isSettled,
     isTripRoom as isTripRoomReportUtils,
     isWaitingForSubmissionFromCurrentUser as isWaitingForSubmissionFromCurrentUserReportUtils,
-    shouldBlockSubmitDueToStrictPolicyRules,
 } from '@libs/ReportUtils';
 import shouldAdjustScroll from '@libs/shouldAdjustScroll';
 import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
@@ -474,10 +472,6 @@ function MoneyRequestReportPreviewContent({
         Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(iouReportID, undefined, undefined, Navigation.getActiveRoute()));
     }, [iouReportID]);
 
-    const shouldBlockSubmit = useMemo(() => {
-        return shouldBlockSubmitDueToStrictPolicyRules(iouReport?.reportID, violations, isStrictPolicyRulesEnabled, transactions);
-    }, [iouReport?.reportID, violations, isStrictPolicyRulesEnabled, transactions]);
-
     const reportPreviewAction = useMemo(() => {
         return getReportPreviewAction(
             violations,
@@ -620,18 +614,13 @@ function MoneyRequestReportPreviewContent({
             />
         ) : null,
         [CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW]: (
-            <Tooltip
-                text={shouldBlockSubmit ? translate('iou.waitingForSubmitterToFixViolations') : ''}
-                shouldRender={shouldBlockSubmit}
-            >
-                <Button
-                    icon={Expensicons.DotIndicator}
-                    iconFill={theme.danger}
-                    iconHoverFill={theme.danger}
-                    text={translate('common.review')}
-                    onPress={() => openReportFromPreview()}
-                />
-            </Tooltip>
+            <Button
+                icon={Expensicons.DotIndicator}
+                iconFill={theme.danger}
+                iconHoverFill={theme.danger}
+                text={translate('common.review')}
+                onPress={() => openReportFromPreview()}
+            />
         ),
         [CONST.REPORT.REPORT_PREVIEW_ACTIONS.VIEW]: (
             <Button
diff --git a/src/languages/de.ts b/src/languages/de.ts
index a5b410076fec..f488048e72e2 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -1450,6 +1450,7 @@ const translations = {
         dates: 'Daten',
         rates: 'Preise',
         submitsTo: ({name}: SubmitsToParams) => `Übermittelt an ${name}`,
+        waitingForSubmitterToFixViolations: 'Warten darauf, dass Sie die Probleme beheben. Ihre Administratoren haben die Einreichung von Ausgaben mit Verstößen eingeschränkt.',
         moveExpenses: () => ({one: 'Ausgabe verschieben', other: 'Ausgaben verschieben'}),
         reject: {
             educationalTitle: 'Solltest du halten oder ablehnen?',
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 11da5d8948ea..f033c559f51c 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1421,7 +1421,7 @@ const translations = {
         dates: 'Dates',
         rates: 'Rates',
         submitsTo: ({name}: SubmitsToParams) => `Submits to ${name}`,
-        waitingForSubmitterToFixViolations: 'Waiting for you to fix the issue(s). Your admins have restricted submission of expenses that have violations.',
+        waitingForSubmitterToFixViolations: 'Waiting for you to fix the issue(s). Your admins have restricted submission of expenses with violations.',
         reject: {
             educationalTitle: 'Should you hold or reject?',
             educationalText: "If you're not ready to approve or pay an expense, you can hold or reject it.",
diff --git a/src/languages/es.ts b/src/languages/es.ts
index c6821fac303c..dc3dbd15ebd6 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1414,8 +1414,7 @@ const translations = {
         dates: 'Fechas',
         rates: 'Tasas',
         submitsTo: ({name}: SubmitsToParams) => `Se envía a ${name}`,
-        waitingForSubmitterToFixViolations: 'Esperando que soluciones los problemas. Tus administradores han restringido el envío de gastos que tienen infracciones.',
-
+        waitingForSubmitterToFixViolations: 'Esperando que soluciones los problemas. Tus administradores han restringido el envío de gastos con infracciones.',
         reject: {
             educationalTitle: '¿Debes retener o rechazar?',
             educationalText: 'Si no estás listo para aprobar o pagar un gasto, puedes retenerlo o rechazarlo.',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 1cecdc28f751..9ba7669d6680 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -1448,6 +1448,7 @@ const translations = {
         dates: 'Dates',
         rates: 'Tarifs',
         submitsTo: ({name}: SubmitsToParams) => `Soumet à ${name}`,
+        waitingForSubmitterToFixViolations: 'En attente de votre correction des problèmes. Vos administrateurs ont restreint la soumission de dépenses avec des violations.',
         moveExpenses: () => ({one: 'Déplacer la dépense', other: 'Déplacer les dépenses'}),
         reject: {
             educationalTitle: 'Faut-il mettre en attente ou rejeter ?',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index ba6e2402fcd0..5bb8bf68940f 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -1441,6 +1441,7 @@ const translations = {
         dates: 'Date di calendario',
         rates: 'Tariffe',
         submitsTo: ({name}: SubmitsToParams) => `Invia a ${name}`,
+        waitingForSubmitterToFixViolations: "In attesa che tu risolva i problemi. I tuoi amministratori hanno limitato l'invio di spese con violazioni.",
         moveExpenses: () => ({one: 'Sposta spesa', other: 'Sposta spese'}),
         reject: {
             educationalTitle: 'Devi trattenere o rifiutare?',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 7ac21cd0f917..86ea82b95411 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -1443,6 +1443,7 @@ const translations = {
         dates: '日付',
         rates: '料金',
         submitsTo: ({name}: SubmitsToParams) => `${name}に送信`,
+        waitingForSubmitterToFixViolations: '問題の修正をお待ちしています。管理者が違反のある経費の提出を制限しています。',
         moveExpenses: () => ({one: '経費を移動', other: '経費を移動'}),
         reject: {
             educationalTitle: '保留しますか、それとも却下しますか？',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index fbe5768cea01..60a9673e4100 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -1443,6 +1443,7 @@ const translations = {
         dates: 'Datums',
         rates: 'Tarieven',
         submitsTo: ({name}: SubmitsToParams) => `Dient in bij ${name}`,
+        waitingForSubmitterToFixViolations: 'Wachten tot u de problemen oplost. Uw beheerders hebben het indienen van uitgaven met overtredingen beperkt.',
         moveExpenses: () => ({one: 'Verplaats uitgave', other: 'Verplaats uitgaven'}),
         reject: {
             educationalTitle: 'Moet je vasthouden of afwijzen?',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 7be518d81c30..fdc11cdb76ea 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -1441,6 +1441,7 @@ const translations = {
         dates: 'Daty',
         rates: 'Stawki',
         submitsTo: ({name}: SubmitsToParams) => `Przesyła do ${name}`,
+        waitingForSubmitterToFixViolations: 'Oczekiwanie na naprawę problemów. Twoi administratorzy ograniczyli przesyłanie wydatków z naruszeniami.',
         moveExpenses: () => ({one: 'Przenieś wydatek', other: 'Przenieś wydatki'}),
         reject: {
             educationalTitle: 'Czy powinieneś wstrzymać czy odrzucić?',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 32d38f7600b5..b8bd88d88e44 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -1438,6 +1438,7 @@ const translations = {
         dates: 'Datas',
         rates: 'Taxas',
         submitsTo: ({name}: SubmitsToParams) => `Envia para ${name}`,
+        waitingForSubmitterToFixViolations: 'Aguardando que você corrija os problemas. Seus administradores restringiram o envio de despesas com violações.',
         moveExpenses: () => ({one: 'Mover despesa', other: 'Mover despesas'}),
         reject: {
             educationalTitle: 'Você deve reter ou rejeitar?',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 2df5e8ba6f4e..761091ac440f 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -1427,6 +1427,7 @@ const translations = {
         dates: '日期',
         rates: '费率',
         submitsTo: ({name}: SubmitsToParams) => `提交给${name}`,
+        waitingForSubmitterToFixViolations: '等待您修复问题。您的管理员已限制提交有违规的费用。',
         moveExpenses: () => ({one: '移动费用', other: '移动费用'}),
         reject: {
             educationalTitle: '应该保留还是拒绝？',

From 3a0dee329f4e0ca3f10b1b69d8a3ede75f701c8f Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Sat, 18 Oct 2025 22:36:06 +0200
Subject: [PATCH 03/17] minor edit

---
 src/components/MoneyReportHeader.tsx | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index f8f95a42c034..27448575a0d8 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -224,7 +224,6 @@ function MoneyReportHeader({
         return Object.values(reportTransactions);
     }, [reportTransactions]);
 
-    // Check if submission should be blocked due to strict policy rules and violations
     const shouldBlockSubmit = useMemo(() => {
         return shouldBlockSubmitDueToStrictPolicyRules(moneyRequestReport?.reportID, violations, isStrictPolicyRulesEnabled, transactions);
     }, [moneyRequestReport?.reportID, violations, isStrictPolicyRulesEnabled, transactions]);

From da378142bb5190727f436a2cdcdcade9b96b96c5 Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Sat, 18 Oct 2025 23:42:39 +0200
Subject: [PATCH 04/17] fix ts

---
 src/components/MoneyReportHeader.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 27448575a0d8..558c17857938 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -117,6 +117,7 @@ import ROUTES from '@src/ROUTES';
 import SCREENS from '@src/SCREENS';
 import type * as OnyxTypes from '@src/types/onyx';
 import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
+import type ReportNextStep from '@src/types/onyx/ReportNextStep';
 import type IconAsset from '@src/types/utils/IconAsset';
 import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
 import ActivityIndicator from './ActivityIndicator';
@@ -403,7 +404,6 @@ function MoneyReportHeader({
     const isSubmitterSameAsNextApprover = isReportOwner(moneyRequestReport) && nextApproverAccountID === moneyRequestReport?.ownerAccountID;
     let optimisticNextStep = isSubmitterSameAsNextApprover && policy?.preventSelfApproval ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() : nextStep;
 
-    // When strict policy rules are enabled and there are violations, show a custom next step message
     if (shouldBlockSubmit && isReportOwner(moneyRequestReport)) {
         optimisticNextStep = {
             type: 'alert',
@@ -1295,7 +1295,7 @@ function MoneyReportHeader({
             {shouldShowMoreContent && (
                 <View style={[styles.flexRow, styles.gap2, styles.justifyContentStart, styles.flexNoWrap, styles.ph5, styles.pb3]}>
                     <View style={[styles.flexShrink1, styles.flexGrow1, styles.mnw0, styles.flexWrap, styles.justifyContentCenter]}>
-                        {showNextStepBar && <MoneyReportHeaderStatusBar nextStep={optimisticNextStep} />}
+                        {showNextStepBar && <MoneyReportHeaderStatusBar nextStep={optimisticNextStep as ReportNextStep} />}
                         {showNextStepSkeleton && <MoneyReportHeaderStatusBarSkeleton />}
                         {!!statusBarProps && (
                             <MoneyRequestHeaderStatusBar

From 7f8d6bdbd646a8b0f0259712a6f32ad28865b39f Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Sun, 19 Oct 2025 00:47:40 +0200
Subject: [PATCH 05/17] fixing eslint

---
 src/components/MoneyReportHeader.tsx          | 2 +-
 src/components/MoneyReportHeaderStatusBar.tsx | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 558c17857938..3743ea8b580b 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -1295,7 +1295,7 @@ function MoneyReportHeader({
             {shouldShowMoreContent && (
                 <View style={[styles.flexRow, styles.gap2, styles.justifyContentStart, styles.flexNoWrap, styles.ph5, styles.pb3]}>
                     <View style={[styles.flexShrink1, styles.flexGrow1, styles.mnw0, styles.flexWrap, styles.justifyContentCenter]}>
-                        {showNextStepBar && <MoneyReportHeaderStatusBar nextStep={optimisticNextStep as ReportNextStep} />}
+                        {showNextStepBar && <MoneyReportHeaderStatusBar nextStep={optimisticNextStep} />}
                         {showNextStepSkeleton && <MoneyReportHeaderStatusBarSkeleton />}
                         {!!statusBarProps && (
                             <MoneyRequestHeaderStatusBar
diff --git a/src/components/MoneyReportHeaderStatusBar.tsx b/src/components/MoneyReportHeaderStatusBar.tsx
index 455f9e443610..6c3511a9c8e6 100644
--- a/src/components/MoneyReportHeaderStatusBar.tsx
+++ b/src/components/MoneyReportHeaderStatusBar.tsx
@@ -14,7 +14,7 @@ import RenderHTML from './RenderHTML';
 
 type MoneyReportHeaderStatusBarProps = {
     /** The next step for the report */
-    nextStep: ReportNextStep;
+    nextStep: ReportNextStep | undefined;
 };
 
 type IconName = ValueOf<typeof CONST.NEXT_STEP.ICONS>;
@@ -29,9 +29,9 @@ function MoneyReportHeaderStatusBar({nextStep}: MoneyReportHeaderStatusBarProps)
     const styles = useThemeStyles();
     const theme = useTheme();
     const messageContent = useMemo(() => {
-        const messageArray = nextStep.message;
+        const messageArray = nextStep?.message;
         return NextStepUtils.parseMessage(messageArray);
-    }, [nextStep.message]);
+    }, [nextStep?.message]);
 
     return (
         <View style={[styles.dFlex, styles.flexRow, styles.alignItemsCenter, styles.overflowHidden, styles.w100, styles.headerStatusBarContainer]}>

From 27f2d91dee8148570e0e81ef47a3294877027390 Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Sun, 19 Oct 2025 00:55:21 +0200
Subject: [PATCH 06/17] fixing eslint errs

---
 src/components/MoneyReportHeaderStatusBar.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/MoneyReportHeaderStatusBar.tsx b/src/components/MoneyReportHeaderStatusBar.tsx
index 6c3511a9c8e6..a6fa71b6f902 100644
--- a/src/components/MoneyReportHeaderStatusBar.tsx
+++ b/src/components/MoneyReportHeaderStatusBar.tsx
@@ -3,7 +3,7 @@ import {View} from 'react-native';
 import type {ValueOf} from 'type-fest';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
-import * as NextStepUtils from '@libs/NextStepUtils';
+import {parseMessage} from '@libs/NextStepUtils';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import type ReportNextStep from '@src/types/onyx/ReportNextStep';
@@ -30,7 +30,7 @@ function MoneyReportHeaderStatusBar({nextStep}: MoneyReportHeaderStatusBarProps)
     const theme = useTheme();
     const messageContent = useMemo(() => {
         const messageArray = nextStep?.message;
-        return NextStepUtils.parseMessage(messageArray);
+        return parseMessage(messageArray);
     }, [nextStep?.message]);
 
     return (

From 3b56f6947645365da7c403bdb96330d3d124dc95 Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Sun, 19 Oct 2025 01:02:44 +0200
Subject: [PATCH 07/17] fixing ts err

---
 src/components/MoneyReportHeaderStatusBar.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/MoneyReportHeaderStatusBar.tsx b/src/components/MoneyReportHeaderStatusBar.tsx
index a6fa71b6f902..16b47ee61cb9 100644
--- a/src/components/MoneyReportHeaderStatusBar.tsx
+++ b/src/components/MoneyReportHeaderStatusBar.tsx
@@ -37,7 +37,7 @@ function MoneyReportHeaderStatusBar({nextStep}: MoneyReportHeaderStatusBarProps)
         <View style={[styles.dFlex, styles.flexRow, styles.alignItemsCenter, styles.overflowHidden, styles.w100, styles.headerStatusBarContainer]}>
             <View style={[styles.mr3]}>
                 <Icon
-                    src={iconMap[nextStep.icon] || Expensicons.Hourglass}
+                    src={iconMap[nextStep?.icon] || Expensicons.Hourglass}
                     height={variables.iconSizeSmall}
                     width={variables.iconSizeSmall}
                     fill={theme.icon}

From ceaa89ded9cff7939b73bdf1087bc48b666f9787 Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Sun, 19 Oct 2025 01:13:22 +0200
Subject: [PATCH 08/17] fix eslint

---
 src/components/MoneyReportHeaderStatusBar.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/MoneyReportHeaderStatusBar.tsx b/src/components/MoneyReportHeaderStatusBar.tsx
index 16b47ee61cb9..1cc34aea8483 100644
--- a/src/components/MoneyReportHeaderStatusBar.tsx
+++ b/src/components/MoneyReportHeaderStatusBar.tsx
@@ -37,7 +37,7 @@ function MoneyReportHeaderStatusBar({nextStep}: MoneyReportHeaderStatusBarProps)
         <View style={[styles.dFlex, styles.flexRow, styles.alignItemsCenter, styles.overflowHidden, styles.w100, styles.headerStatusBarContainer]}>
             <View style={[styles.mr3]}>
                 <Icon
-                    src={iconMap[nextStep?.icon] || Expensicons.Hourglass}
+                    src={(nextStep?.icon && iconMap?.[nextStep.icon]) || Expensicons.Hourglass}
                     height={variables.iconSizeSmall}
                     width={variables.iconSizeSmall}
                     fill={theme.icon}

From 902ba2ad467f063512e3942ba9a520363d0347b3 Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Sun, 19 Oct 2025 01:26:14 +0200
Subject: [PATCH 09/17] fixing eslint

---
 src/components/MoneyReportHeader.tsx          | 3 +--
 src/components/MoneyReportHeaderStatusBar.tsx | 2 +-
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 3743ea8b580b..42bd98ffeb3e 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -47,8 +47,8 @@ import Navigation from '@libs/Navigation/Navigation';
 import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
 import type {ReportsSplitNavigatorParamList, SearchFullscreenNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types';
 import {buildOptimisticNextStepForPreventSelfApprovalsEnabled} from '@libs/NextStepUtils';
-import {selectPaymentType} from '@libs/PaymentUtils';
 import type {KYCFlowEvent, TriggerKYCFlow} from '@libs/PaymentUtils';
+import {selectPaymentType} from '@libs/PaymentUtils';
 import Permissions from '@libs/Permissions';
 import {getConnectedIntegration, getValidConnectedIntegration} from '@libs/PolicyUtils';
 import {getIOUActionForReportID, getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils';
@@ -117,7 +117,6 @@ import ROUTES from '@src/ROUTES';
 import SCREENS from '@src/SCREENS';
 import type * as OnyxTypes from '@src/types/onyx';
 import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
-import type ReportNextStep from '@src/types/onyx/ReportNextStep';
 import type IconAsset from '@src/types/utils/IconAsset';
 import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
 import ActivityIndicator from './ActivityIndicator';
diff --git a/src/components/MoneyReportHeaderStatusBar.tsx b/src/components/MoneyReportHeaderStatusBar.tsx
index 1cc34aea8483..f0855a85fd53 100644
--- a/src/components/MoneyReportHeaderStatusBar.tsx
+++ b/src/components/MoneyReportHeaderStatusBar.tsx
@@ -37,7 +37,7 @@ function MoneyReportHeaderStatusBar({nextStep}: MoneyReportHeaderStatusBarProps)
         <View style={[styles.dFlex, styles.flexRow, styles.alignItemsCenter, styles.overflowHidden, styles.w100, styles.headerStatusBarContainer]}>
             <View style={[styles.mr3]}>
                 <Icon
-                    src={(nextStep?.icon && iconMap?.[nextStep.icon]) || Expensicons.Hourglass}
+                    src={(nextStep?.icon && iconMap?.[nextStep.icon]) ?? Expensicons.Hourglass}
                     height={variables.iconSizeSmall}
                     width={variables.iconSizeSmall}
                     fill={theme.icon}

From cad8617fd00467b4d1bcdc02f2212dd9037fa2a2 Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Wed, 22 Oct 2025 01:53:05 +0200
Subject: [PATCH 10/17] Minor refactor

---
 src/components/MoneyReportHeader.tsx                        | 6 +++---
 .../MoneyRequestReportPreviewContent.tsx                    | 6 +++---
 src/hooks/useStrictPolicyRules.ts                           | 6 +++---
 src/libs/ReportPreviewActionUtils.ts                        | 4 ++--
 src/libs/ReportUtils.ts                                     | 4 ++--
 5 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 42bd98ffeb3e..ff820e06fd5e 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -209,7 +209,7 @@ function MoneyReportHeader({
     const expensifyIcons = useMemoizedLazyExpensifyIcons(['Buildings'] as const);
     const [lastDistanceExpenseType] = useOnyx(ONYXKEYS.NVP_LAST_DISTANCE_EXPENSE_TYPE, {canBeMissing: true});
     const exportTemplates = useMemo(() => getExportTemplates(integrationsExportTemplates ?? [], csvExportLayouts ?? {}, policy), [integrationsExportTemplates, csvExportLayouts, policy]);
-    const {isStrictPolicyRulesEnabled} = useStrictPolicyRules();
+    const {areStrictPolicyRulesEnabled} = useStrictPolicyRules();
 
     const requestParentReportAction = useMemo(() => {
         if (!reportActions || !transactionThreadReport?.parentReportActionID) {
@@ -225,8 +225,8 @@ function MoneyReportHeader({
     }, [reportTransactions]);
 
     const shouldBlockSubmit = useMemo(() => {
-        return shouldBlockSubmitDueToStrictPolicyRules(moneyRequestReport?.reportID, violations, isStrictPolicyRulesEnabled, transactions);
-    }, [moneyRequestReport?.reportID, violations, isStrictPolicyRulesEnabled, transactions]);
+        return shouldBlockSubmitDueToStrictPolicyRules(moneyRequestReport?.reportID, violations, areStrictPolicyRulesEnabled, transactions);
+    }, [moneyRequestReport?.reportID, violations, areStrictPolicyRulesEnabled, transactions]);
 
     const iouTransactionID = isMoneyRequestAction(requestParentReportAction) ? getOriginalMessage(requestParentReportAction)?.IOUTransactionID : undefined;
     const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(iouTransactionID)}`, {
diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
index 318c5f185f17..0704ad5f8fc9 100644
--- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
+++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
@@ -137,7 +137,7 @@ function MoneyRequestReportPreviewContent({
     const StyleUtils = useStyleUtils();
     const {translate} = useLocalize();
     const {isOffline} = useNetwork();
-    const {isStrictPolicyRulesEnabled} = useStrictPolicyRules();
+    const {areStrictPolicyRulesEnabled} = useStrictPolicyRules();
     const {shouldUseNarrowLayout} = useResponsiveLayout();
     const currentUserDetails = useCurrentUserPersonalDetails();
 
@@ -483,7 +483,7 @@ function MoneyRequestReportPreviewContent({
             isPaidAnimationRunning,
             isApprovedAnimationRunning,
             isSubmittingAnimationRunning,
-            isStrictPolicyRulesEnabled,
+            areStrictPolicyRulesEnabled,
         );
     }, [
         isPaidAnimationRunning,
@@ -496,7 +496,7 @@ function MoneyRequestReportPreviewContent({
         isIouReportArchived,
         invoiceReceiverPolicy,
         isChatReportArchived,
-        isStrictPolicyRulesEnabled,
+        areStrictPolicyRulesEnabled,
     ]);
 
     const addExpenseDropdownOptions = useMemo(
diff --git a/src/hooks/useStrictPolicyRules.ts b/src/hooks/useStrictPolicyRules.ts
index 6961b8cfa871..fa2062dbd501 100644
--- a/src/hooks/useStrictPolicyRules.ts
+++ b/src/hooks/useStrictPolicyRules.ts
@@ -4,7 +4,7 @@ import useOnyx from './useOnyx';
 
 type UseStrictPolicyRulesResult = {
     /** Whether the user's domain has strict policy rules enabled (strictly enforce workspace rules) */
-    isStrictPolicyRulesEnabled: boolean;
+    areStrictPolicyRulesEnabled: boolean;
 };
 
 /**
@@ -27,10 +27,10 @@ function useStrictPolicyRules(): UseStrictPolicyRulesResult {
     const securityGroup = securityGroupID ? securityGroups?.[securityGroupKey] : null;
 
     // Check if strict policy rules are enabled
-    const isStrictPolicyRulesEnabled = securityGroup?.enableStrictPolicyRules === true;
+    const areStrictPolicyRulesEnabled = securityGroup?.enableStrictPolicyRules === true;
 
     return {
-        isStrictPolicyRulesEnabled,
+        areStrictPolicyRulesEnabled,
     };
 }
 
diff --git a/src/libs/ReportPreviewActionUtils.ts b/src/libs/ReportPreviewActionUtils.ts
index 350db34d4f48..637601f13e2d 100644
--- a/src/libs/ReportPreviewActionUtils.ts
+++ b/src/libs/ReportPreviewActionUtils.ts
@@ -225,7 +225,7 @@ function getReportPreviewAction(
     isPaidAnimationRunning?: boolean,
     isApprovedAnimationRunning?: boolean,
     isSubmittingAnimationRunning?: boolean,
-    isStrictPolicyRulesEnabled?: boolean,
+    areStrictPolicyRulesEnabled?: boolean,
 ): ValueOf<typeof CONST.REPORT.REPORT_PREVIEW_ACTIONS> {
     if (!report) {
         return CONST.REPORT.REPORT_PREVIEW_ACTIONS.VIEW;
@@ -245,7 +245,7 @@ function getReportPreviewAction(
     }
 
     // When strict policy rules are enabled and there are violations, show REVIEW button instead of SUBMIT
-    const shouldBlockSubmit = shouldBlockSubmitDueToStrictPolicyRules(report.reportID, violations, isStrictPolicyRulesEnabled ?? false, transactions);
+    const shouldBlockSubmit = shouldBlockSubmitDueToStrictPolicyRules(report.reportID, violations, areStrictPolicyRulesEnabled ?? false, transactions);
     if (shouldBlockSubmit && canReview(report, violations, isReportArchived, policy, transactions)) {
         return CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW;
     }
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 87e635453a7b..3d2230bf1301 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -8537,10 +8537,10 @@ function hasReportViolations(reportID: string | undefined) {
 function shouldBlockSubmitDueToStrictPolicyRules(
     reportID: string | undefined,
     transactionViolations: OnyxCollection<TransactionViolation[]>,
-    isStrictPolicyRulesEnabled: boolean,
+    areStrictPolicyRulesEnabled: boolean,
     reportTransactions?: Transaction[] | SearchTransaction[],
 ) {
-    if (!isStrictPolicyRulesEnabled) {
+    if (!areStrictPolicyRulesEnabled) {
         return false;
     }
     return hasAnyViolations(reportID, transactionViolations, reportTransactions as SearchTransaction[]);

From b3db34cec5d3437bbfaf2bfa5fcc60617e269979 Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Wed, 22 Oct 2025 11:50:42 +0200
Subject: [PATCH 11/17] Remove parentheses from issue(s) in
 waitingForSubmitterToFixViolations translation

---
 src/languages/en.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/languages/en.ts b/src/languages/en.ts
index f033c559f51c..8e5955abc687 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1421,7 +1421,7 @@ const translations = {
         dates: 'Dates',
         rates: 'Rates',
         submitsTo: ({name}: SubmitsToParams) => `Submits to ${name}`,
-        waitingForSubmitterToFixViolations: 'Waiting for you to fix the issue(s). Your admins have restricted submission of expenses with violations.',
+        waitingForSubmitterToFixViolations: 'Waiting for you to fix the issues. Your admins have restricted submission of expenses with violations.',
         reject: {
             educationalTitle: 'Should you hold or reject?',
             educationalText: "If you're not ready to approve or pay an expense, you can hold or reject it.",

From d48987b1fa78dd78b522d35d052b63e55d7d1fba Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Wed, 22 Oct 2025 14:32:30 +0200
Subject: [PATCH 12/17] Move strict policy next step to NextStepUtils, keep
 English hardcoded msg

---
 src/components/MoneyReportHeader.tsx | 12 ++----------
 src/languages/de.ts                  |  1 -
 src/languages/en.ts                  |  1 -
 src/languages/es.ts                  |  1 -
 src/languages/fr.ts                  |  1 -
 src/languages/it.ts                  |  1 -
 src/languages/ja.ts                  |  1 -
 src/languages/nl.ts                  |  1 -
 src/languages/pl.ts                  |  1 -
 src/languages/pt-BR.ts               |  1 -
 src/languages/zh-hans.ts             |  1 -
 src/libs/NextStepUtils.ts            | 15 +++++++++++++++
 tests/unit/NextStepUtilsTest.ts      | 18 +++++++++++++++++-
 13 files changed, 34 insertions(+), 21 deletions(-)

diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index ff820e06fd5e..01f1e081eef5 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -46,7 +46,7 @@ import {getThreadReportIDsForTransactions, getTotalAmountForIOUReportPreviewButt
 import Navigation from '@libs/Navigation/Navigation';
 import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
 import type {ReportsSplitNavigatorParamList, SearchFullscreenNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types';
-import {buildOptimisticNextStepForPreventSelfApprovalsEnabled} from '@libs/NextStepUtils';
+import {buildOptimisticNextStepForPreventSelfApprovalsEnabled, buildOptimisticNextStepForStrictPolicyRuleViolations} from '@libs/NextStepUtils';
 import type {KYCFlowEvent, TriggerKYCFlow} from '@libs/PaymentUtils';
 import {selectPaymentType} from '@libs/PaymentUtils';
 import Permissions from '@libs/Permissions';
@@ -404,15 +404,7 @@ function MoneyReportHeader({
     let optimisticNextStep = isSubmitterSameAsNextApprover && policy?.preventSelfApproval ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() : nextStep;
 
     if (shouldBlockSubmit && isReportOwner(moneyRequestReport)) {
-        optimisticNextStep = {
-            type: 'alert',
-            icon: CONST.NEXT_STEP.ICONS.HOURGLASS,
-            message: [
-                {
-                    text: translate('iou.waitingForSubmitterToFixViolations'),
-                },
-            ],
-        };
+        optimisticNextStep = buildOptimisticNextStepForStrictPolicyRuleViolations();
     }
 
     const shouldShowNextStep = isFromPaidPolicy && !isInvoiceReport && !shouldShowStatusBar;
diff --git a/src/languages/de.ts b/src/languages/de.ts
index f488048e72e2..a5b410076fec 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -1450,7 +1450,6 @@ const translations = {
         dates: 'Daten',
         rates: 'Preise',
         submitsTo: ({name}: SubmitsToParams) => `Übermittelt an ${name}`,
-        waitingForSubmitterToFixViolations: 'Warten darauf, dass Sie die Probleme beheben. Ihre Administratoren haben die Einreichung von Ausgaben mit Verstößen eingeschränkt.',
         moveExpenses: () => ({one: 'Ausgabe verschieben', other: 'Ausgaben verschieben'}),
         reject: {
             educationalTitle: 'Solltest du halten oder ablehnen?',
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 8e5955abc687..bb17536f13e5 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1421,7 +1421,6 @@ const translations = {
         dates: 'Dates',
         rates: 'Rates',
         submitsTo: ({name}: SubmitsToParams) => `Submits to ${name}`,
-        waitingForSubmitterToFixViolations: 'Waiting for you to fix the issues. Your admins have restricted submission of expenses with violations.',
         reject: {
             educationalTitle: 'Should you hold or reject?',
             educationalText: "If you're not ready to approve or pay an expense, you can hold or reject it.",
diff --git a/src/languages/es.ts b/src/languages/es.ts
index dc3dbd15ebd6..c193832426db 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1414,7 +1414,6 @@ const translations = {
         dates: 'Fechas',
         rates: 'Tasas',
         submitsTo: ({name}: SubmitsToParams) => `Se envía a ${name}`,
-        waitingForSubmitterToFixViolations: 'Esperando que soluciones los problemas. Tus administradores han restringido el envío de gastos con infracciones.',
         reject: {
             educationalTitle: '¿Debes retener o rechazar?',
             educationalText: 'Si no estás listo para aprobar o pagar un gasto, puedes retenerlo o rechazarlo.',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 9ba7669d6680..1cecdc28f751 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -1448,7 +1448,6 @@ const translations = {
         dates: 'Dates',
         rates: 'Tarifs',
         submitsTo: ({name}: SubmitsToParams) => `Soumet à ${name}`,
-        waitingForSubmitterToFixViolations: 'En attente de votre correction des problèmes. Vos administrateurs ont restreint la soumission de dépenses avec des violations.',
         moveExpenses: () => ({one: 'Déplacer la dépense', other: 'Déplacer les dépenses'}),
         reject: {
             educationalTitle: 'Faut-il mettre en attente ou rejeter ?',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 5bb8bf68940f..ba6e2402fcd0 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -1441,7 +1441,6 @@ const translations = {
         dates: 'Date di calendario',
         rates: 'Tariffe',
         submitsTo: ({name}: SubmitsToParams) => `Invia a ${name}`,
-        waitingForSubmitterToFixViolations: "In attesa che tu risolva i problemi. I tuoi amministratori hanno limitato l'invio di spese con violazioni.",
         moveExpenses: () => ({one: 'Sposta spesa', other: 'Sposta spese'}),
         reject: {
             educationalTitle: 'Devi trattenere o rifiutare?',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 86ea82b95411..7ac21cd0f917 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -1443,7 +1443,6 @@ const translations = {
         dates: '日付',
         rates: '料金',
         submitsTo: ({name}: SubmitsToParams) => `${name}に送信`,
-        waitingForSubmitterToFixViolations: '問題の修正をお待ちしています。管理者が違反のある経費の提出を制限しています。',
         moveExpenses: () => ({one: '経費を移動', other: '経費を移動'}),
         reject: {
             educationalTitle: '保留しますか、それとも却下しますか？',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 60a9673e4100..fbe5768cea01 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -1443,7 +1443,6 @@ const translations = {
         dates: 'Datums',
         rates: 'Tarieven',
         submitsTo: ({name}: SubmitsToParams) => `Dient in bij ${name}`,
-        waitingForSubmitterToFixViolations: 'Wachten tot u de problemen oplost. Uw beheerders hebben het indienen van uitgaven met overtredingen beperkt.',
         moveExpenses: () => ({one: 'Verplaats uitgave', other: 'Verplaats uitgaven'}),
         reject: {
             educationalTitle: 'Moet je vasthouden of afwijzen?',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index fdc11cdb76ea..7be518d81c30 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -1441,7 +1441,6 @@ const translations = {
         dates: 'Daty',
         rates: 'Stawki',
         submitsTo: ({name}: SubmitsToParams) => `Przesyła do ${name}`,
-        waitingForSubmitterToFixViolations: 'Oczekiwanie na naprawę problemów. Twoi administratorzy ograniczyli przesyłanie wydatków z naruszeniami.',
         moveExpenses: () => ({one: 'Przenieś wydatek', other: 'Przenieś wydatki'}),
         reject: {
             educationalTitle: 'Czy powinieneś wstrzymać czy odrzucić?',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index b8bd88d88e44..32d38f7600b5 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -1438,7 +1438,6 @@ const translations = {
         dates: 'Datas',
         rates: 'Taxas',
         submitsTo: ({name}: SubmitsToParams) => `Envia para ${name}`,
-        waitingForSubmitterToFixViolations: 'Aguardando que você corrija os problemas. Seus administradores restringiram o envio de despesas com violações.',
         moveExpenses: () => ({one: 'Mover despesa', other: 'Mover despesas'}),
         reject: {
             educationalTitle: 'Você deve reter ou rejeitar?',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 761091ac440f..2df5e8ba6f4e 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -1427,7 +1427,6 @@ const translations = {
         dates: '日期',
         rates: '费率',
         submitsTo: ({name}: SubmitsToParams) => `提交给${name}`,
-        waitingForSubmitterToFixViolations: '等待您修复问题。您的管理员已限制提交有违规的费用。',
         moveExpenses: () => ({one: '移动费用', other: '移动费用'}),
         reject: {
             educationalTitle: '应该保留还是拒绝？',
diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts
index a0e3a7ecbb59..6468318bc2bc 100644
--- a/src/libs/NextStepUtils.ts
+++ b/src/libs/NextStepUtils.ts
@@ -137,6 +137,20 @@ function buildOptimisticNextStepForPreventSelfApprovalsEnabled() {
     return optimisticNextStep;
 }
 
+function buildOptimisticNextStepForStrictPolicyRuleViolations() {
+    const optimisticNextStep: ReportNextStep = {
+        type: 'alert',
+        icon: CONST.NEXT_STEP.ICONS.HOURGLASS,
+        message: [
+            {
+                text: 'Waiting for you to fix the issues. Your admins have restricted submission of expenses with violations.',
+            },
+        ],
+    };
+
+    return optimisticNextStep;
+}
+
 /**
  * Please don't use this function anymore, let's use buildNextStepNew instead
  *
@@ -882,5 +896,6 @@ export {
     // eslint-disable-next-line @typescript-eslint/no-deprecated
     buildNextStep,
     buildOptimisticNextStepForPreventSelfApprovalsEnabled,
+    buildOptimisticNextStepForStrictPolicyRuleViolations,
     buildNextStepNew,
 };
diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts
index c49eb2bdd6ce..5eb649deb945 100644
--- a/tests/unit/NextStepUtilsTest.ts
+++ b/tests/unit/NextStepUtilsTest.ts
@@ -1,7 +1,7 @@
 import Onyx from 'react-native-onyx';
 // TODO: Replace onyx.connect with useOnyx hook (https://github.com/Expensify/App/issues/66365)
 // eslint-disable-next-line @typescript-eslint/no-deprecated
-import {buildNextStep} from '@libs/NextStepUtils';
+import {buildNextStep, buildOptimisticNextStepForStrictPolicyRuleViolations} from '@libs/NextStepUtils';
 import {buildOptimisticEmptyReport, buildOptimisticExpenseReport} from '@libs/ReportUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
@@ -817,4 +817,20 @@ describe('libs/NextStepUtils', () => {
             });
         });
     });
+
+    describe('buildOptimisticNextStepForStrictPolicyRuleViolations', () => {
+        test('returns correct next step message for strict policy rule violations', () => {
+            const result = buildOptimisticNextStepForStrictPolicyRuleViolations();
+
+            expect(result).toEqual({
+                type: 'alert',
+                icon: CONST.NEXT_STEP.ICONS.HOURGLASS,
+                message: [
+                    {
+                        text: 'Waiting for you to fix the issues. Your admins have restricted submission of expenses with violations.',
+                    },
+                ],
+            });
+        });
+    });
 });

From 6f3861517443d705535dc165b138da95241948bc Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Wed, 22 Oct 2025 15:00:40 +0200
Subject: [PATCH 13/17] Add unit tests

---
 tests/actions/ReportPreviewActionUtilsTest.ts |  42 ++++
 tests/unit/ReportUtilsTest.ts                 | 103 +++++++++
 tests/unit/useStrictPolicyRulesTest.ts        | 195 ++++++++++++++++++
 3 files changed, 340 insertions(+)
 create mode 100644 tests/unit/useStrictPolicyRulesTest.ts

diff --git a/tests/actions/ReportPreviewActionUtilsTest.ts b/tests/actions/ReportPreviewActionUtilsTest.ts
index 5c9329cdd423..01006900b206 100644
--- a/tests/actions/ReportPreviewActionUtilsTest.ts
+++ b/tests/actions/ReportPreviewActionUtilsTest.ts
@@ -550,6 +550,48 @@ describe('getReportPreviewAction', () => {
         expect(getReportPreviewAction(violations, false, report, policy, [transaction])).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW);
     });
 
+    it('should return REVIEW when strict policy rules are enabled and report has violations', async () => {
+        const hasAnyViolationsMock = ReportUtils.hasAnyViolations as jest.Mock;
+        hasAnyViolationsMock.mockReturnValue(true);
+
+        const report: Report = {
+            ...createRandomReport(REPORT_ID),
+            statusNum: 0,
+            stateNum: 0,
+            type: CONST.REPORT.TYPE.EXPENSE,
+            ownerAccountID: CURRENT_USER_ACCOUNT_ID,
+            isWaitingOnBankAccount: false,
+        };
+
+        const policy = createRandomPolicy(0);
+        policy.areWorkflowsEnabled = true;
+        policy.type = CONST.POLICY.TYPE.CORPORATE;
+
+        await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
+
+        const violations: OnyxCollection<TransactionViolation[]> = {
+            [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${TRANSACTION_ID}`]: [
+                {
+                    name: CONST.VIOLATIONS.MISSING_CATEGORY,
+                    type: CONST.VIOLATION_TYPES.VIOLATION,
+                } as TransactionViolation,
+            ],
+        };
+
+        const transaction = {
+            transactionID: `${TRANSACTION_ID}`,
+            reportID: `${REPORT_ID}`,
+        } as unknown as Transaction;
+
+        const {result: isReportArchived} = renderHook(() => useReportIsArchived(report?.parentReportID));
+        await waitForBatchedUpdatesWithAct();
+
+        // When strict policy rules are enabled, REVIEW should be shown instead of SUBMIT
+        expect(getReportPreviewAction(violations, isReportArchived.current, report, policy, [transaction], undefined, false, false, false, true)).toBe(
+            CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW,
+        );
+    });
+
     it('canView should return true for reports in which we are waiting for user to add a bank account', async () => {
         const report = {
             ...createRandomReport(REPORT_ID),
diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts
index 6a84e6a294e5..252b99d3cefe 100644
--- a/tests/unit/ReportUtilsTest.ts
+++ b/tests/unit/ReportUtilsTest.ts
@@ -80,6 +80,7 @@ import {
     prepareOnboardingOnyxData,
     requiresAttentionFromCurrentUser,
     requiresManualSubmission,
+    shouldBlockSubmitDueToStrictPolicyRules,
     shouldDisableRename,
     shouldDisableThread,
     shouldEnableNegative,
@@ -8198,4 +8199,106 @@ describe('ReportUtils', () => {
             expect(actorAccountID).toEqual(123);
         });
     });
+
+    describe('shouldBlockSubmitDueToStrictPolicyRules', () => {
+        const reportID = 'report123';
+
+        it('should return false when areStrictPolicyRulesEnabled is false regardless of violations', () => {
+            const transactionViolations = {
+                [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}transaction1`]: [
+                    {
+                        name: CONST.VIOLATIONS.MISSING_CATEGORY,
+                        type: CONST.VIOLATION_TYPES.VIOLATION,
+                        data: {},
+                    },
+                ],
+            };
+
+            const result = shouldBlockSubmitDueToStrictPolicyRules(reportID, transactionViolations, false);
+
+            expect(result).toBe(false);
+        });
+
+        it('should return false when areStrictPolicyRulesEnabled is true but no violations exist', () => {
+            const transactionViolations = {};
+
+            const result = shouldBlockSubmitDueToStrictPolicyRules(reportID, transactionViolations, true);
+
+            expect(result).toBe(false);
+        });
+
+        it('should return false when reportID is undefined', () => {
+            const transactionViolations = {
+                [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}transaction1`]: [
+                    {
+                        name: CONST.VIOLATIONS.MISSING_CATEGORY,
+                        type: CONST.VIOLATION_TYPES.VIOLATION,
+                        data: {},
+                    },
+                ],
+            };
+
+            const result = shouldBlockSubmitDueToStrictPolicyRules(undefined, transactionViolations, true);
+
+            expect(result).toBe(false);
+        });
+
+        it('should return false when areStrictPolicyRulesEnabled is false even with violations and transactions provided', () => {
+            const transaction = buildOptimisticTransaction({
+                transactionParams: {
+                    amount: 100,
+                    currency: CONST.CURRENCY.USD,
+                    reportID,
+                    comment: '',
+                    attendees: [],
+                    created: '2024-01-01',
+                    merchant: CONST.TRANSACTION.DEFAULT_MERCHANT,
+                    category: '',
+                },
+            });
+            const transactions = [transaction];
+            const transactionViolations = {
+                [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`]: [
+                    {
+                        name: CONST.VIOLATIONS.MISSING_CATEGORY,
+                        type: CONST.VIOLATION_TYPES.VIOLATION,
+                        data: {},
+                    },
+                ],
+            };
+
+            const result = shouldBlockSubmitDueToStrictPolicyRules(reportID, transactionViolations, false, transactions);
+
+            expect(result).toBe(false);
+        });
+
+        it('should return true when areStrictPolicyRulesEnabled is true and violations exist', () => {
+            const transaction = buildOptimisticTransaction({
+                transactionParams: {
+                    amount: 100,
+                    currency: CONST.CURRENCY.USD,
+                    reportID,
+                    comment: '',
+                    attendees: [],
+                    created: '2024-01-01',
+                    merchant: CONST.TRANSACTION.DEFAULT_MERCHANT,
+                    category: '',
+                },
+            });
+            const transactions = [transaction];
+            const transactionViolations = {
+                [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`]: [
+                    {
+                        name: CONST.VIOLATIONS.MISSING_CATEGORY,
+                        type: CONST.VIOLATION_TYPES.VIOLATION,
+                        data: {},
+                    },
+                ],
+            };
+
+            const result = shouldBlockSubmitDueToStrictPolicyRules(reportID, transactionViolations, true, transactions);
+
+            expect(result).toBe(true);
+        });
+    });
 });
diff --git a/tests/unit/useStrictPolicyRulesTest.ts b/tests/unit/useStrictPolicyRulesTest.ts
new file mode 100644
index 000000000000..f7459ae2f52d
--- /dev/null
+++ b/tests/unit/useStrictPolicyRulesTest.ts
@@ -0,0 +1,195 @@
+import {renderHook} from '@testing-library/react-native';
+import Onyx from 'react-native-onyx';
+import useStrictPolicyRules from '@hooks/useStrictPolicyRules';
+import ONYXKEYS from '@src/ONYXKEYS';
+import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
+
+describe('useStrictPolicyRules', () => {
+    beforeAll(() => {
+        Onyx.init({
+            keys: ONYXKEYS,
+        });
+        return waitForBatchedUpdates();
+    });
+
+    beforeEach(async () => {
+        await Onyx.clear();
+    });
+
+    it('should return false when no security groups are configured', () => {
+        const {result} = renderHook(() => useStrictPolicyRules());
+
+        expect(result.current.areStrictPolicyRulesEnabled).toBe(false);
+    });
+
+    it('should return false when domain has no security groups', async () => {
+        await Onyx.set(ONYXKEYS.SESSION, {
+            email: 'user@example.com',
+        });
+
+        const {result} = renderHook(() => useStrictPolicyRules());
+
+        expect(result.current.areStrictPolicyRulesEnabled).toBe(false);
+    });
+
+    it('should return true when security group has enableStrictPolicyRules enabled', async () => {
+        const securityGroupID = 'securityGroup123';
+
+        await Onyx.set(ONYXKEYS.SESSION, {
+            email: 'user@example.com',
+        });
+
+        const domainSecurityGroups: Record<string, string> = {};
+        domainSecurityGroups['example.com'] = securityGroupID;
+        await Onyx.set(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS, domainSecurityGroups);
+
+        const securityGroupKey = `${ONYXKEYS.COLLECTION.SECURITY_GROUP}${securityGroupID}` as const;
+        await Onyx.set(securityGroupKey, {
+            enableStrictPolicyRules: true,
+        });
+
+        const {result} = renderHook(() => useStrictPolicyRules());
+
+        expect(result.current.areStrictPolicyRulesEnabled).toBe(true);
+    });
+
+    it('should return false when security group has enableStrictPolicyRules disabled', async () => {
+        const securityGroupID = 'securityGroup123';
+
+        await Onyx.set(ONYXKEYS.SESSION, {
+            email: 'user@example.com',
+        });
+
+        const domainSecurityGroups: Record<string, string> = {};
+        domainSecurityGroups['example.com'] = securityGroupID;
+        await Onyx.set(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS, domainSecurityGroups);
+
+        const securityGroupKey = `${ONYXKEYS.COLLECTION.SECURITY_GROUP}${securityGroupID}` as const;
+        await Onyx.set(securityGroupKey, {
+            enableStrictPolicyRules: false,
+        });
+
+        const {result} = renderHook(() => useStrictPolicyRules());
+
+        expect(result.current.areStrictPolicyRulesEnabled).toBe(false);
+    });
+
+    it('should return false when security group is not found', async () => {
+        const securityGroupID = 'nonExistentGroup';
+
+        await Onyx.set(ONYXKEYS.SESSION, {
+            email: 'user@example.com',
+        });
+
+        const domainSecurityGroups: Record<string, string> = {};
+        domainSecurityGroups['example.com'] = securityGroupID;
+        await Onyx.set(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS, domainSecurityGroups);
+
+        const {result} = renderHook(() => useStrictPolicyRules());
+
+        expect(result.current.areStrictPolicyRulesEnabled).toBe(false);
+    });
+
+    describe('Edge cases', () => {
+        it('should handle null enableStrictPolicyRules', async () => {
+            const securityGroupID = 'securityGroup123';
+
+            await Onyx.set(ONYXKEYS.SESSION, {
+                email: 'user@example.com',
+            });
+
+            const domainSecurityGroups: Record<string, string> = {};
+            domainSecurityGroups['example.com'] = securityGroupID;
+            await Onyx.set(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS, domainSecurityGroups);
+
+            const securityGroupKey = `${ONYXKEYS.COLLECTION.SECURITY_GROUP}${securityGroupID}` as const;
+            await Onyx.set(securityGroupKey, {
+                enableStrictPolicyRules: null,
+            });
+
+            const {result} = renderHook(() => useStrictPolicyRules());
+
+            expect(result.current.areStrictPolicyRulesEnabled).toBe(false);
+        });
+
+        it('should handle undefined enableStrictPolicyRules', async () => {
+            const securityGroupID = 'securityGroup123';
+
+            await Onyx.set(ONYXKEYS.SESSION, {
+                email: 'user@example.com',
+            });
+
+            const domainSecurityGroups: Record<string, string> = {};
+            domainSecurityGroups['example.com'] = securityGroupID;
+            await Onyx.set(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS, domainSecurityGroups);
+
+            const securityGroupKey = `${ONYXKEYS.COLLECTION.SECURITY_GROUP}${securityGroupID}` as const;
+            await Onyx.set(securityGroupKey, {});
+
+            const {result} = renderHook(() => useStrictPolicyRules());
+
+            expect(result.current.areStrictPolicyRulesEnabled).toBe(false);
+        });
+
+        it('should handle missing session email', async () => {
+            const securityGroupID = 'securityGroup123';
+
+            await Onyx.set(ONYXKEYS.SESSION, {});
+
+            const domainSecurityGroups: Record<string, string> = {};
+            domainSecurityGroups['example.com'] = securityGroupID;
+            await Onyx.set(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS, domainSecurityGroups);
+
+            const securityGroupKey = `${ONYXKEYS.COLLECTION.SECURITY_GROUP}${securityGroupID}` as const;
+            await Onyx.set(securityGroupKey, {
+                enableStrictPolicyRules: true,
+            });
+
+            const {result} = renderHook(() => useStrictPolicyRules());
+
+            expect(result.current.areStrictPolicyRulesEnabled).toBe(false);
+        });
+
+        it('should extract domain correctly from email and match security group', async () => {
+            const securityGroupID = 'securityGroup123';
+
+            await Onyx.set(ONYXKEYS.SESSION, {
+                email: 'john.doe@company.org',
+            });
+
+            const domainSecurityGroups: Record<string, string> = {};
+            domainSecurityGroups['company.org'] = securityGroupID;
+            await Onyx.set(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS, domainSecurityGroups);
+
+            const securityGroupKey = `${ONYXKEYS.COLLECTION.SECURITY_GROUP}${securityGroupID}` as const;
+            await Onyx.set(securityGroupKey, {
+                enableStrictPolicyRules: true,
+            });
+
+            const {result} = renderHook(() => useStrictPolicyRules());
+
+            expect(result.current.areStrictPolicyRulesEnabled).toBe(true);
+        });
+
+        it('should return false when domain does not match any security group', async () => {
+            const securityGroupID = 'securityGroup123';
+
+            await Onyx.set(ONYXKEYS.SESSION, {
+                email: 'user@example.com',
+            });
+
+            const domainSecurityGroups: Record<string, string> = {};
+            domainSecurityGroups['different-domain.com'] = securityGroupID;
+            await Onyx.set(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS, domainSecurityGroups);
+
+            const securityGroupKey = `${ONYXKEYS.COLLECTION.SECURITY_GROUP}${securityGroupID}` as const;
+            await Onyx.set(securityGroupKey, {
+                enableStrictPolicyRules: true,
+            });
+
+            const {result} = renderHook(() => useStrictPolicyRules());
+
+            expect(result.current.areStrictPolicyRulesEnabled).toBe(false);
+        });
+    });
+});

From 43de8dd83c4d20f52d3453ab47c5fe2ff7a025a7 Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Wed, 22 Oct 2025 16:11:13 +0200
Subject: [PATCH 14/17] minor edit with test title to follow existing pattern

---
 tests/actions/ReportPreviewActionUtilsTest.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/actions/ReportPreviewActionUtilsTest.ts b/tests/actions/ReportPreviewActionUtilsTest.ts
index 01006900b206..185ed5efb2a8 100644
--- a/tests/actions/ReportPreviewActionUtilsTest.ts
+++ b/tests/actions/ReportPreviewActionUtilsTest.ts
@@ -550,7 +550,7 @@ describe('getReportPreviewAction', () => {
         expect(getReportPreviewAction(violations, false, report, policy, [transaction])).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW);
     });
 
-    it('should return REVIEW when strict policy rules are enabled and report has violations', async () => {
+    it('canReview should return true when strict policy rules are enabled and report has violations', async () => {
         const hasAnyViolationsMock = ReportUtils.hasAnyViolations as jest.Mock;
         hasAnyViolationsMock.mockReturnValue(true);
 

From 6d940f92fabdf4a0639012f085fcead3bd62dc7b Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Wed, 22 Oct 2025 16:25:13 +0200
Subject: [PATCH 15/17] chore: trigger pipeline


From 77e0e1474ab554968fc4d0c3aaa98a419e4bf189 Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Thu, 23 Oct 2025 08:45:07 +0200
Subject: [PATCH 16/17] merge into main

---
 src/libs/ReportPreviewActionUtils.ts          | 2 +-
 tests/actions/ReportPreviewActionUtilsTest.ts | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/libs/ReportPreviewActionUtils.ts b/src/libs/ReportPreviewActionUtils.ts
index ce56adc102ee..f8ab416c6aa6 100644
--- a/src/libs/ReportPreviewActionUtils.ts
+++ b/src/libs/ReportPreviewActionUtils.ts
@@ -247,7 +247,7 @@ function getReportPreviewAction(
 
     // When strict policy rules are enabled and there are violations, show REVIEW button instead of SUBMIT
     const shouldBlockSubmit = shouldBlockSubmitDueToStrictPolicyRules(report.reportID, violations, areStrictPolicyRulesEnabled ?? false, transactions);
-    if (shouldBlockSubmit && canReview(report, violations, isReportArchived, policy, transactions)) {
+    if (shouldBlockSubmit && canReview(report, violations, isReportArchived, currentUserEmail, policy, transactions)) {
         return CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW;
     }
 
diff --git a/tests/actions/ReportPreviewActionUtilsTest.ts b/tests/actions/ReportPreviewActionUtilsTest.ts
index 5c6b3e4e364e..f61fcf0a43d3 100644
--- a/tests/actions/ReportPreviewActionUtilsTest.ts
+++ b/tests/actions/ReportPreviewActionUtilsTest.ts
@@ -595,7 +595,7 @@ describe('getReportPreviewAction', () => {
         await waitForBatchedUpdatesWithAct();
 
         // When strict policy rules are enabled, REVIEW should be shown instead of SUBMIT
-        expect(getReportPreviewAction(violations, isReportArchived.current, report, policy, [transaction], undefined, false, false, false, true)).toBe(
+        expect(getReportPreviewAction(violations, isReportArchived.current, CURRENT_USER_EMAIL, report, policy, [transaction], undefined, false, false, false, true)).toBe(
             CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW,
         );
     });

From 197d9c7408e209fce6da334a3cf34987b03d8c24 Mon Sep 17 00:00:00 2001
From: Abdelrahman Khattab <abzokhattab@gmail.com>
Date: Thu, 23 Oct 2025 09:02:12 +0200
Subject: [PATCH 17/17] fixing eslint

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

diff --git a/src/libs/ReportPreviewActionUtils.ts b/src/libs/ReportPreviewActionUtils.ts
index f8ab416c6aa6..6da0c7401933 100644
--- a/src/libs/ReportPreviewActionUtils.ts
+++ b/src/libs/ReportPreviewActionUtils.ts
@@ -215,6 +215,7 @@ function canReview(report: Report, violations: OnyxCollection<TransactionViolati
     return true;
 }
 
+// eslint-disable-next-line @typescript-eslint/max-params
 function getReportPreviewAction(
     violations: OnyxCollection<TransactionViolation[]>,
     isReportArchived: boolean,
