@@ -3,6 +3,7 @@ import type {OnyxEntry} from 'react-native-onyx';
33import type { CurrencyListActionsContextType } from '@hooks/useCurrencyList' ;
44import CONST from '@src/CONST' ;
55import type { TranslationPaths } from '@src/languages/types' ;
6+ import ONYXKEYS from '@src/ONYXKEYS' ;
67import ROUTES from '@src/ROUTES' ;
78import type * as OnyxTypes from '@src/types/onyx' ;
89import { isEmptyObject } from '@src/types/utils/EmptyObject' ;
@@ -457,6 +458,108 @@ function createTransactionPreviewConditionals({
457458 } ;
458459}
459460
461+ /**
462+ * Lightweight check for whether a transaction has any RBR (Red Brick Road) indicator.
463+ * Evaluates transaction-level signals (violations, hold, missing fields, receipt errors,
464+ * report action errors, and DEW submit failures) with proper context for dismissed
465+ * violations and report settlement/approval status.
466+ *
467+ * This logic mirrors the `shouldShowRBR` computation in `createTransactionPreviewConditionals`.
468+ */
469+ function transactionHasRBR (
470+ transaction : OnyxEntry < OnyxTypes . Transaction > ,
471+ violations : OnyxTypes . TransactionViolations ,
472+ currentUserEmail : string ,
473+ currentUserAccountID : number ,
474+ iouReport : OnyxEntry < OnyxTypes . Report > ,
475+ policy : OnyxEntry < OnyxTypes . Policy > ,
476+ reportActions ?: OnyxTypes . ReportActions ,
477+ ) : boolean {
478+ if ( ! transaction ) {
479+ return false ;
480+ }
481+
482+ // Check for non-dismissed violation-type or warning-type violations
483+ if (
484+ hasViolation ( transaction , violations , currentUserEmail , currentUserAccountID , iouReport , policy , true ) ||
485+ hasWarningTypeViolation ( transaction , violations , currentUserEmail , currentUserAccountID , iouReport , policy )
486+ ) {
487+ return true ;
488+ }
489+
490+ // Check for notice-type violations (only on paid group policies)
491+ if ( hasNoticeTypeViolation ( transaction , violations , currentUserEmail , currentUserAccountID , iouReport , policy , true ) && isPaidGroupPolicyUtil ( iouReport ) ) {
492+ return true ;
493+ }
494+
495+ // Check for distance-request modified-amount violations (type VIOLATION or NOTICE)
496+ if (
497+ isDistanceRequest ( transaction ) &&
498+ violations ?. some (
499+ ( violation ) => violation . name === CONST . VIOLATIONS . MODIFIED_AMOUNT && ( violation . type === CONST . VIOLATION_TYPES . VIOLATION || violation . type === CONST . VIOLATION_TYPES . NOTICE ) ,
500+ )
501+ ) {
502+ return true ;
503+ }
504+
505+ // Check if transaction is on hold — only counts as RBR when the report
506+ // is not fully settled and not fully approved (matching createTransactionPreviewConditionals)
507+ const isSettlementOrApprovalPartial = ! ! iouReport ?. pendingFields ?. partial ;
508+ const isFullySettled = isSettled ( iouReport ?. reportID ) && ! isSettlementOrApprovalPartial ;
509+ const isFullyApproved = isReportApproved ( { report : iouReport } ) && ! isSettlementOrApprovalPartial ;
510+ if ( ! isFullySettled && ! isFullyApproved && isOnHold ( transaction ) ) {
511+ return true ;
512+ }
513+
514+ // Check if transaction has missing required fields (uses hasMissingSmartscanFields
515+ // which guards against distance requests and receipts being scanned)
516+ if ( hasMissingSmartscanFields ( transaction , iouReport ) ) {
517+ return true ;
518+ }
519+
520+ // Check if transaction has receipt error
521+ if ( hasReceiptError ( transaction ) ) {
522+ return true ;
523+ }
524+
525+ // Check for report action errors associated with this transaction
526+ if ( hasActionWithErrorsForTransaction ( iouReport ?. reportID , transaction , reportActions ) ) {
527+ return true ;
528+ }
529+
530+ // Check for DEW submit failures
531+ if ( hasDynamicExternalWorkflow ( policy ) && ! ! getMostRecentActiveDEWSubmitFailedAction ( reportActions ) ) {
532+ return true ;
533+ }
534+
535+ return false ;
536+ }
537+
538+ /**
539+ * Compare two transactions by their RBR (Red Brick Road) status.
540+ * Transactions with RBR indicators are sorted before those without.
541+ * Returns 0 when both transactions have the same RBR status.
542+ */
543+ function compareByRBR (
544+ a : OnyxTypes . Transaction ,
545+ b : OnyxTypes . Transaction ,
546+ violations : Record < string , OnyxTypes . TransactionViolations | undefined > | undefined ,
547+ currentUserEmail : string ,
548+ currentUserAccountID : number ,
549+ iouReport : OnyxEntry < OnyxTypes . Report > ,
550+ policy : OnyxEntry < OnyxTypes . Policy > ,
551+ reportActions ?: OnyxTypes . ReportActions ,
552+ ) : number {
553+ const aViolations = violations ?. [ `${ ONYXKEYS . COLLECTION . TRANSACTION_VIOLATIONS } ${ a . transactionID } ` ] ?? [ ] ;
554+ const bViolations = violations ?. [ `${ ONYXKEYS . COLLECTION . TRANSACTION_VIOLATIONS } ${ b . transactionID } ` ] ?? [ ] ;
555+ const aHasRBR = transactionHasRBR ( a , aViolations , currentUserEmail , currentUserAccountID , iouReport , policy , reportActions ) ;
556+ const bHasRBR = transactionHasRBR ( b , bViolations , currentUserEmail , currentUserAccountID , iouReport , policy , reportActions ) ;
557+ if ( aHasRBR === bHasRBR ) {
558+ return 0 ;
559+ }
560+ return aHasRBR ? - 1 : 1 ;
561+ }
562+
460563export {
461564 getReviewNavigationRoute ,
462565 getIOUPayerAndReceiver ,
@@ -465,5 +568,7 @@ export {
465568 getViolationTranslatePath ,
466569 getUniqueActionErrorsForTransaction ,
467570 formatLastFourPAN ,
571+ transactionHasRBR ,
572+ compareByRBR ,
468573} ;
469574export type { TranslationPathOrText } ;
0 commit comments