From be854fe4e809f6bf9ed96265d602d5c073b9249b Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 14 Jul 2026 11:40:51 +0800 Subject: [PATCH 1/8] pass ownerLogin as param to isViolationDismissed #3 --- .../MoneyRequestReportTransactionList.tsx | 4 +- .../useReportPreviewCarousel.tsx | 17 +++++++- .../TransactionPreviewContent.tsx | 5 ++- .../ListItem/ExpenseReportListItem.tsx | 4 +- .../ListItem/TransactionListItem/index.tsx | 2 +- src/libs/ReportUtils.ts | 31 +++++++------ src/libs/SearchUIUtils.ts | 4 +- src/libs/TransactionPreviewUtils.ts | 27 ++++++++---- src/libs/TransactionUtils/index.ts | 27 ++++++------ src/libs/Violations/ViolationsUtils.ts | 2 +- src/libs/actions/Policy/Policy.ts | 16 ++++++- .../workflows/WorkspaceWorkflowsPage.tsx | 5 ++- tests/actions/PolicyTest.ts | 2 + tests/unit/TransactionPreviewUtils.test.ts | 43 ++++++++++--------- tests/unit/TransactionUtilsTest.ts | 26 +++++------ tests/unit/ViolationUtilsTest.ts | 12 +++--- 16 files changed, 139 insertions(+), 88 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index db7c07293d98..a2efb92bd0a4 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -376,12 +376,12 @@ function MoneyRequestReportTransactionList({ const ids = new Set(); for (const transaction of transactions) { const violations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`] ?? []; - if (transactionHasRBR(transaction, violations, login, accountID, report, policy, reportActionsMap, actionErrors)) { + if (transactionHasRBR(transaction, violations, login, accountID, report, ownerLogin, policy, reportActionsMap, actionErrors)) { ids.add(transaction.transactionID); } } return ids; - }, [isDefaultSort, allTransactionViolations, currentUserDetails?.login, currentUserDetails?.accountID, transactions, report, policy, reportActionsMap]); + }, [isDefaultSort, allTransactionViolations, currentUserDetails?.login, currentUserDetails?.accountID, transactions, report, ownerLogin, policy, reportActionsMap]); const sortedTransactions: TransactionWithOptionalHighlight[] = useMemo(() => { return [...transactions].sort((a, b) => { diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/useReportPreviewCarousel.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/useReportPreviewCarousel.tsx index bbdc645b6e8f..de5ca7cf685d 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/useReportPreviewCarousel.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/useReportPreviewCarousel.tsx @@ -3,6 +3,7 @@ import Text from '@components/Text'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useIsFocusedRef from '@hooks/useIsFocusedRef'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import shouldAdjustScroll from '@libs/shouldAdjustScroll'; @@ -10,6 +11,8 @@ import {compareByRBR} from '@libs/TransactionPreviewUtils'; import {getCreated} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import {personalDetailsLoginSelector} from '@src/selectors/PersonalDetails'; import type {Policy, Report, Transaction} from '@src/types/onyx'; import type {FlashListRef, ListRenderItem, ListRenderItemInfo} from '@shopify/flash-list'; @@ -75,6 +78,7 @@ function useReportPreviewCarousel({ const styles = useThemeStyles(); const {translate, localeCompare} = useLocalize(); const currentUserDetails = useCurrentUserPersonalDetails(); + const [ownerLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsLoginSelector(iouReport?.ownerAccountID)}); const isFocusedRef = useIsFocusedRef(); const carouselTransactions = useMemo(() => { @@ -82,7 +86,16 @@ function useReportPreviewCarousel({ return []; } const sorted = [...transactions].sort((a, b) => { - const rbrComparison = compareByRBR(a, b, transactionViolations, currentUserDetails?.login ?? '', currentUserDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID, iouReport, policy); + const rbrComparison = compareByRBR( + a, + b, + transactionViolations, + currentUserDetails?.login ?? '', + currentUserDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID, + iouReport, + ownerLogin, + policy, + ); if (rbrComparison !== 0) { return rbrComparison; } @@ -90,7 +103,7 @@ function useReportPreviewCarousel({ return localeCompare(getCreated(a), getCreated(b)); }); return sorted.slice(0, MAX_PREVIEWS_NUMBER + 1); - }, [shouldShowAccessPlaceHolder, transactions, transactionViolations, currentUserDetails?.login, currentUserDetails?.accountID, iouReport, policy, localeCompare]); + }, [shouldShowAccessPlaceHolder, transactions, transactionViolations, currentUserDetails?.login, currentUserDetails?.accountID, iouReport, ownerLogin, policy, localeCompare]); const prevCarouselTransactionLength = useRef(0); useEffect(() => { diff --git a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx index 307e12275d1c..2c84efa8ec52 100644 --- a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx +++ b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx @@ -24,6 +24,7 @@ import {getDecodedLeafCategoryName} from '@libs/CategoryUtils'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {calculateAmount} from '@libs/IOUUtils'; import Parser from '@libs/Parser'; +import {getLoginByAccountID} from '@libs/PersonalDetailsUtils'; import {getCommaSeparatedTagNameWithSanitizedColons} from '@libs/PolicyUtils'; import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils'; import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; @@ -93,6 +94,7 @@ function TransactionPreviewContent({ const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`, {selector: getStableReportSelector}); const managerID = report?.managerID ?? reportPreviewAction?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID; const ownerAccountID = report?.ownerAccountID ?? reportPreviewAction?.childOwnerAccountID ?? CONST.DEFAULT_NUMBER_ID; + const ownerLogin = getLoginByAccountID(ownerAccountID, personalDetails); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(report?.reportID)}`); const isChatReportArchived = useReportIsArchived(chatReport?.reportID); const currentUserDetails = useCurrentUserPersonalDetails(); @@ -102,6 +104,7 @@ function TransactionPreviewContent({ const transactionPreviewCommonArguments = useMemo( () => ({ iouReport: report, + iouReportOwnerLogin: ownerLogin, policy, transaction, action, @@ -109,7 +112,7 @@ function TransactionPreviewContent({ violations, transactionDetails, }), - [action, report, policy, isBillSplit, transaction, transactionDetails, violations], + [action, report, ownerLogin, policy, isBillSplit, transaction, transactionDetails, violations], ); const conditionals = useMemo( diff --git a/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx b/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx index c011fea116d2..9b6642666d32 100644 --- a/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx +++ b/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx @@ -189,7 +189,7 @@ function ExpenseReportListItemInner({ return reportItem?.transactions?.some((transaction) => { const relevantViolations = (transaction.violations ?? []).filter( (violation) => - !isViolationDismissed(transaction, violation, currentUserDetails.email ?? '', currentUserDetails.accountID, reportForViolations, policyForViolations, submitterLogin) && + !isViolationDismissed(transaction, violation, currentUserDetails.email ?? '', currentUserDetails.accountID, reportForViolations, submitterLogin, policyForViolations) && shouldShowViolation(reportForViolations, policyForViolations, violation.name, currentUserDetails.email ?? '', false, transaction), ); @@ -221,6 +221,7 @@ function ExpenseReportListItemInner({ // (parentPolicy ?? snapshot); violations + transactions come from the report's live Onyx data. const liveHasVisibleViolations = hasVisibleViolations( reportForViolations, + submitterLogin, reportViolations, currentUserDetails.email ?? '', currentUserDetails.accountID, @@ -383,6 +384,7 @@ function ExpenseReportListItemInner({ const fallbackHasVisibleViolations = liveViolationsForSnapshotTransactions ? hasVisibleViolations( reportForViolations, + submitterLogin, liveViolationsForSnapshotTransactions, currentUserDetails.email ?? '', currentUserDetails.accountID, diff --git a/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx b/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx index 24cd10bc0082..61c764ec94a4 100644 --- a/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx @@ -169,7 +169,7 @@ function TransactionListItemInner({ const onyxViolations = (transactionViolationsForRow ?? []).filter( (violation: TransactionViolation) => - !isViolationDismissed(transactionItem, violation, currentUserDetails.email ?? '', currentUserDetails.accountID, reportForViolations, policyForViolations, submitterLogin) && + !isViolationDismissed(transactionItem, violation, currentUserDetails.email ?? '', currentUserDetails.accountID, reportForViolations, submitterLogin, policyForViolations) && shouldShowViolation(reportForViolations, policyForViolations, violation.name, currentUserDetails.email ?? '', false, transactionItem), ); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5d6f210efc27..cd14f02105bf 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -9183,6 +9183,8 @@ function getViolatingReportIDForRBRInLHN(report: OnyxEntry, transactionV true, transactions, potentialReport, + // This path only runs for reports the current user submitted, so the report owner is the current user. + currentUserLogin, policy, ) || hasWarningTypeViolations( @@ -9193,6 +9195,8 @@ function getViolatingReportIDForRBRInLHN(report: OnyxEntry, transactionV true, transactions, potentialReport, + // This path only runs for reports the current user submitted, so the report owner is the current user. + currentUserLogin, policy, ) || hasNoticeTypeViolationsForRBRInLHN( @@ -9224,7 +9228,8 @@ function hasNoticeTypeViolationsForRBRInLHN( return false; } const filteredViolations = excludedViolationNames.length > 0 ? rawViolations.filter((violation) => !excludedViolationNames.includes(violation.name)) : rawViolations; - return hasNoticeTypeViolation(transaction, filteredViolations, currentUserEmailParam, currentUserAccountIDParam, report, policy, true); + // This path only runs for reports the current user submitted, so the report owner is the current user. + return hasNoticeTypeViolation(transaction, filteredViolations, currentUserEmailParam, currentUserAccountIDParam, report, currentUserEmailParam, policy, true); }); } @@ -9239,10 +9244,13 @@ function hasViolations( shouldShowInReview?: boolean, reportTransactions?: Transaction[], report?: OnyxEntry, + reportOwnerLogin?: string, policy?: OnyxEntry, ): boolean { const transactions = reportTransactions ?? getReportTransactions(reportID); - return transactions.some((transaction) => hasViolation(transaction, transactionViolations, currentUserEmailParam ?? '', currentUserAccountIDParam, report, policy, shouldShowInReview)); + return transactions.some((transaction) => + hasViolation(transaction, transactionViolations, currentUserEmailParam ?? '', currentUserAccountIDParam, report, reportOwnerLogin, policy, shouldShowInReview), + ); } function hasVisibleReportFieldViolations(report: OnyxEntry, policy: OnyxEntry, currentUserAccountID: number | undefined): boolean { @@ -9292,11 +9300,12 @@ function hasWarningTypeViolations( shouldShowInReview?: boolean, reportTransactions?: Transaction[], report?: OnyxEntry, + reportOwnerLogin?: string, policy?: OnyxEntry, ): boolean { const transactions = reportTransactions ?? getReportTransactions(reportID); return transactions.some((transaction) => - hasWarningTypeViolation(transaction, transactionViolations, currentUserEmailParam ?? '', currentUserAccountIDParam, report, policy, shouldShowInReview), + hasWarningTypeViolation(transaction, transactionViolations, currentUserEmailParam ?? '', currentUserAccountIDParam, report, reportOwnerLogin, policy, shouldShowInReview), ); } @@ -9320,14 +9329,12 @@ function hasNoticeTypeViolations( transactionViolations: OnyxCollection, currentUserAccountIDParam: number, currentUserEmailParam: string, - shouldShowInReview?: boolean, - reportTransactions?: Transaction[], - report?: OnyxEntry, - policy?: OnyxEntry, + shouldShowInReview: boolean, + reportTransactions: Transaction[] | undefined, ): boolean { const transactions = reportTransactions ?? getReportTransactions(reportID); return transactions.some((transaction) => - hasNoticeTypeViolation(transaction, transactionViolations, currentUserEmailParam ?? '', currentUserAccountIDParam, report, policy, shouldShowInReview), + hasNoticeTypeViolation(transaction, transactionViolations, currentUserEmailParam ?? '', currentUserAccountIDParam, undefined, undefined, undefined, shouldShowInReview), ); } @@ -9340,13 +9347,11 @@ function hasAnyViolations( currentUserAccountIDParam: number, currentUserEmailParam: string, reportTransactions?: Transaction[], - report?: OnyxEntry, - policy?: OnyxEntry, ) { return ( - hasViolations(reportID, transactionViolations, currentUserAccountIDParam, currentUserEmailParam, undefined, reportTransactions, report, policy) || - hasNoticeTypeViolations(reportID, transactionViolations, currentUserAccountIDParam, currentUserEmailParam, true, reportTransactions, report, policy) || - hasWarningTypeViolations(reportID, transactionViolations, currentUserAccountIDParam, currentUserEmailParam ?? '', true, reportTransactions, report, policy) + hasViolations(reportID, transactionViolations, currentUserAccountIDParam, currentUserEmailParam, undefined, reportTransactions) || + hasNoticeTypeViolations(reportID, transactionViolations, currentUserAccountIDParam, currentUserEmailParam, true, reportTransactions) || + hasWarningTypeViolations(reportID, transactionViolations, currentUserAccountIDParam, currentUserEmailParam ?? '', true, reportTransactions) ); } diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 4662b5aae58f..941b982df3fb 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -2009,6 +2009,7 @@ function classifyAndPreprocess(data: OnyxTypes.SearchResults['data']): Omit, + reportOwnerLogin: string | undefined, allViolations: OnyxCollection, currentUserEmail: string, currentUserAccountID: number, @@ -2033,7 +2034,7 @@ function hasVisibleViolations( } for (const violation of tvs) { - if (isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, report, policy)) { + if (isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, report, reportOwnerLogin, policy)) { continue; } @@ -2891,6 +2892,7 @@ function getReportSections({ const hasVisibleViolationsForReport = hasVisibleViolations( reportItem, + getLoginByAccountID(reportItem.ownerAccountID, data.personalDetailsList), allViolations, currentUserEmail, currentAccountID ?? CONST.DEFAULT_NUMBER_ID, diff --git a/src/libs/TransactionPreviewUtils.ts b/src/libs/TransactionPreviewUtils.ts index c3eae8db25fd..2ccea5a90e62 100644 --- a/src/libs/TransactionPreviewUtils.ts +++ b/src/libs/TransactionPreviewUtils.ts @@ -197,6 +197,7 @@ function getUniqueActionErrorsForTransaction(reportActions: OnyxTypes.ReportActi function getTransactionPreviewTextAndTranslationPaths({ iouReport, + iouReportOwnerLogin, policy, transaction, action, @@ -212,6 +213,7 @@ function getTransactionPreviewTextAndTranslationPaths({ convertToDisplayString, }: { iouReport: OnyxEntry; + iouReportOwnerLogin: string | undefined; policy: OnyxEntry; transaction: OnyxEntry; action: OnyxEntry; @@ -239,7 +241,8 @@ function getTransactionPreviewTextAndTranslationPaths({ const hasFieldErrors = hasMissingSmartscanFields(transaction, iouReport); const isGroupPolicy = isGroupPolicyUtil(policy); - const hasViolationsOfTypeNotice = hasNoticeTypeViolation(transaction, violations, currentUserEmail ?? '', currentUserAccountID, iouReport, policy, true) && isGroupPolicy; + const hasViolationsOfTypeNotice = + hasNoticeTypeViolation(transaction, violations, currentUserEmail ?? '', currentUserAccountID, iouReport, iouReportOwnerLogin, policy, true) && isGroupPolicy; const hasActionWithErrors = hasActionWithErrorsForTransaction(iouReport?.reportID, transaction, reportActions); const {amount: requestAmount, currency: requestCurrency} = transactionDetails; @@ -366,6 +369,7 @@ function getTransactionPreviewTextAndTranslationPaths({ function createTransactionPreviewConditionals({ iouReport, + iouReportOwnerLogin, policy, transaction, action, @@ -379,6 +383,7 @@ function createTransactionPreviewConditionals({ reportActions, }: { iouReport: OnyxEntry; + iouReportOwnerLogin: string | undefined; policy: OnyxEntry; transaction: OnyxEntry | undefined; action: OnyxEntry; @@ -401,7 +406,9 @@ function createTransactionPreviewConditionals({ const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const hasViolationsOfTypeNotice = - hasNoticeTypeViolation(transaction, violations, currentUserEmail ?? '', currentUserAccountID, iouReport ?? undefined, policy, true) && iouReport && isGroupPolicyUtil(policy); + hasNoticeTypeViolation(transaction, violations, currentUserEmail ?? '', currentUserAccountID, iouReport ?? undefined, iouReportOwnerLogin, policy, true) && + iouReport && + isGroupPolicyUtil(policy); const hasFieldErrors = hasMissingSmartscanFields(transaction, iouReport); const isFetchingWaypoints = isFetchingWaypointsFromServer(transaction); @@ -419,8 +426,8 @@ function createTransactionPreviewConditionals({ const hasAnyViolations = !!hasViolationsOfTypeNotice || - hasWarningTypeViolation(transaction, violations, currentUserEmail ?? '', currentUserAccountID, iouReport ?? undefined, policy) || - hasViolation(transaction, violations, currentUserEmail ?? '', currentUserAccountID, iouReport ?? undefined, policy, true) || + hasWarningTypeViolation(transaction, violations, currentUserEmail ?? '', currentUserAccountID, iouReport ?? undefined, iouReportOwnerLogin, policy) || + hasViolation(transaction, violations, currentUserEmail ?? '', currentUserAccountID, iouReport ?? undefined, iouReportOwnerLogin, policy, true) || (isDistanceRequest(transaction) && violations?.some( (violation) => violation.name === CONST.VIOLATIONS.MODIFIED_AMOUNT && (violation.type === CONST.VIOLATION_TYPES.VIOLATION || violation.type === CONST.VIOLATION_TYPES.NOTICE), @@ -469,6 +476,7 @@ function transactionHasRBR( currentUserEmail: string, currentUserAccountID: number, iouReport: OnyxEntry, + iouReportOwnerLogin: string | undefined, policy: OnyxEntry, reportActions?: OnyxTypes.ReportActions, // Optional precomputed action-error state. When provided, the per-transaction action-error check is an O(1) @@ -481,14 +489,14 @@ function transactionHasRBR( // Check for non-dismissed violation-type or warning-type violations if ( - hasViolation(transaction, violations, currentUserEmail, currentUserAccountID, iouReport, policy, true) || - hasWarningTypeViolation(transaction, violations, currentUserEmail, currentUserAccountID, iouReport, policy) + hasViolation(transaction, violations, currentUserEmail, currentUserAccountID, iouReport, iouReportOwnerLogin, policy, true) || + hasWarningTypeViolation(transaction, violations, currentUserEmail, currentUserAccountID, iouReport, iouReportOwnerLogin, policy) ) { return true; } // Check for notice-type violations (only on group policies) - if (hasNoticeTypeViolation(transaction, violations, currentUserEmail, currentUserAccountID, iouReport, policy, true) && isGroupPolicyUtil(policy)) { + if (hasNoticeTypeViolation(transaction, violations, currentUserEmail, currentUserAccountID, iouReport, iouReportOwnerLogin, policy, true) && isGroupPolicyUtil(policy)) { return true; } @@ -547,13 +555,14 @@ function compareByRBR( currentUserEmail: string, currentUserAccountID: number, iouReport: OnyxEntry, + iouReportOwnerLogin: string | undefined, policy: OnyxEntry, reportActions?: OnyxTypes.ReportActions, ): number { const aViolations = violations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${a.transactionID}`] ?? []; const bViolations = violations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${b.transactionID}`] ?? []; - const aHasRBR = transactionHasRBR(a, aViolations, currentUserEmail, currentUserAccountID, iouReport, policy, reportActions); - const bHasRBR = transactionHasRBR(b, bViolations, currentUserEmail, currentUserAccountID, iouReport, policy, reportActions); + const aHasRBR = transactionHasRBR(a, aViolations, currentUserEmail, currentUserAccountID, iouReport, iouReportOwnerLogin, policy, reportActions); + const bHasRBR = transactionHasRBR(b, bViolations, currentUserEmail, currentUserAccountID, iouReport, iouReportOwnerLogin, policy, reportActions); if (aHasRBR === bHasRBR) { return 0; } diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index a740216af029..68957527b7c0 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -14,7 +14,6 @@ import {toLocaleDigit} from '@libs/LocaleDigitUtils'; import {translateLocal} from '@libs/Localize'; import Log from '@libs/Log'; import {rand64, roundToTwoDecimalPlaces} from '@libs/NumberUtils'; -import {getLoginsByAccountIDs} from '@libs/PersonalDetailsUtils'; import { getCommaSeparatedTagNameWithSanitizedColons, getDistanceRateCustomUnit, @@ -1644,7 +1643,7 @@ function getTransactionViolations( const violations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transaction.transactionID]?.filter( - (violation) => !isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, iouReport, policy, iouReportOwnerLogin), + (violation) => !isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, iouReport, iouReportOwnerLogin, policy), ) ?? []; return violations; @@ -1767,7 +1766,7 @@ function shouldShowBrokenConnectionViolationForMultipleTransactions( return false; } - if (isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, report, policy, reportOwnerLogin)) { + if (isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, report, reportOwnerLogin, policy)) { return false; } @@ -1818,7 +1817,7 @@ function getVisibleTransactionViolations( return mergeProhibitedViolations( transactionViolations.filter( (violation) => - !isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, iouReport, policy, iouReportOwnerLogin) && + !isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, iouReport, iouReportOwnerLogin, policy) && shouldShowViolation(iouReport, policy, violation.name, currentUserEmail, shouldShowRterForSettledReport, transaction), ), ); @@ -2036,8 +2035,8 @@ function isDuplicate( currentUserEmail, currentUserAccountID, iouReport, - policy, iouReportOwnerLogin, + policy, ); return hasDuplicatedTransactionViolation && !isDuplicatedTransactionViolationDismissed; @@ -2063,8 +2062,8 @@ function isViolationDismissed( currentUserEmail: string, currentUserAccountID: number, iouReport: OnyxEntry, + iouReportOwnerLogin: string | undefined, policy: OnyxEntry, - iouReportOwnerLogin?: string, ): boolean { if (!transaction || !violation) { return false; @@ -2095,11 +2094,8 @@ function isViolationDismissed( const isSubmitter = iouReport.ownerAccountID === currentUserAccountID; const shouldViewAsSubmitter = !isSubmitter && isOpenExpenseReport(iouReport); - if (shouldViewAsSubmitter && iouReport.ownerAccountID) { - const reportOwnerEmail = iouReportOwnerLogin ?? getLoginsByAccountIDs([iouReport.ownerAccountID]).at(0); - if (reportOwnerEmail && dismissedByEmails.includes(reportOwnerEmail)) { - return true; - } + if (shouldViewAsSubmitter && iouReportOwnerLogin && dismissedByEmails.includes(iouReportOwnerLogin)) { + return true; } return false; @@ -2124,6 +2120,7 @@ function hasViolation( currentUserEmail: string, currentUserAccountID: number, iouReport: OnyxEntry, + iouReportOwnerLogin: string | undefined, policy: OnyxEntry, showInReview?: boolean, ): boolean { @@ -2136,7 +2133,7 @@ function hasViolation( (violation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && - !isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, iouReport, policy), + !isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, iouReport, iouReportOwnerLogin, policy), ); } @@ -2176,6 +2173,7 @@ function hasNoticeTypeViolation( currentUserEmail: string, currentUserAccountID: number, iouReport: OnyxEntry, + iouReportOwnerLogin: string | undefined, policy: OnyxEntry, showInReview?: boolean, ): boolean { @@ -2188,7 +2186,7 @@ function hasNoticeTypeViolation( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && - !isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, iouReport, policy), + !isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, iouReport, iouReportOwnerLogin, policy), ); } @@ -2201,6 +2199,7 @@ function hasWarningTypeViolation( currentUserEmail: string, currentUserAccountID: number, iouReport: OnyxEntry, + iouReportOwnerLogin: string | undefined, policy: OnyxEntry, showInReview?: boolean, ): boolean { @@ -2214,7 +2213,7 @@ function hasWarningTypeViolation( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING && (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && - !isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, iouReport, policy), + !isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, iouReport, iouReportOwnerLogin, policy), ) ?? []; return warningTypeViolations.length > 0; diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 18c02b1cf4a7..d5f25ea80c20 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -1142,7 +1142,7 @@ const ViolationsUtils = { // Check if any violation is not dismissed and should be shown based on user role and violation type return transactionViolations.some((violation: TransactionViolation) => { return ( - !isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, report, policy, currentUserEmail) && + !isViolationDismissed(transaction, violation, currentUserEmail, currentUserAccountID, report, currentUserEmail, policy) && shouldShowViolation(report, policy, violation.name, currentUserEmail, true, transaction) ); }); diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index fbcde9e413ba..5128193d5f6e 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -310,6 +310,7 @@ type SetWorkspaceApprovalModeAdditionalData = { reportNextSteps?: OnyxCollection; transactionViolations?: OnyxCollection; betas?: Beta[]; + personalDetailsList?: OnyxEntry; }; let deprecatedAllPersonalDetails: OnyxEntry; @@ -928,7 +929,8 @@ function setWorkspaceApprovalMode( const nextStepOptimisticData: Array> = []; const nextStepFailureData: Array> = []; - const shouldUpdateNextSteps = additionalData?.reportNextSteps != null && additionalData?.transactionViolations != null && additionalData?.betas != null; + const shouldUpdateNextSteps = + additionalData?.reportNextSteps != null && additionalData?.transactionViolations != null && additionalData?.betas != null && additionalData?.personalDetailsList; // We want to toggle off preventSelfApproval when the user turns off Approvals and has preventSelfApproval enabled. const shouldResetPreventSelfApproval = approvalMode === CONST.POLICY.APPROVAL_MODE.OPTIONAL && !!policy?.preventSelfApproval; @@ -951,7 +953,17 @@ function setWorkspaceApprovalMode( const nextStepKey: `${typeof ONYXKEYS.COLLECTION.NEXT_STEP}${string}` = `${ONYXKEYS.COLLECTION.NEXT_STEP}${reportID}`; const currentNextStep: OnyxEntry | null = resolvedReportNextSteps[nextStepKey] ?? null; - const hasViolations = ReportUtils.hasViolations(reportID, resolvedTransactionViolations, currentUserAccountID, currentUserEmail, undefined, undefined, report, updatedPolicy); + const hasViolations = ReportUtils.hasViolations( + reportID, + resolvedTransactionViolations, + currentUserAccountID, + currentUserEmail, + undefined, + undefined, + report, + PersonalDetailsUtils.getLoginByAccountID(report.ownerAccountID, additionalData.personalDetailsList), + updatedPolicy, + ); const optimisticNextStep = buildNextStepNew({ report, policy: updatedPolicy, diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 9fa7e6bb09fc..2fc23cd6e03f 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -249,8 +249,9 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { reportNextSteps: allReportNextSteps, transactionViolations, betas, + personalDetailsList: personalDetails, }); - }, [allReportNextSteps, betas, policy, transactionViolations, currentUserAccountID, currentUserEmail]); + }, [allReportNextSteps, betas, policy, transactionViolations, currentUserAccountID, currentUserEmail, personalDetails]); const navigateToHRSettings = useCallback(() => { Navigation.navigate(ROUTES.WORKSPACE_HR.getRoute(route.params.policyID)); @@ -522,6 +523,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { reportNextSteps: allReportNextSteps, transactionViolations, betas, + personalDetailsList: personalDetails, }); }, subMenuItems: ( @@ -884,6 +886,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { canWritePayments, canWriteWorkflows, currentUserLogin, + personalDetails, withApprovalsReadOnlyFallback, withPaymentsReadOnlyFallback, withWorkflowsReadOnlyFallback, diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index 48ebe6d43851..eab172671506 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -3334,6 +3334,7 @@ describe('actions/Policy', () => { }, transactionViolations: {}, betas: [], + personalDetailsList: {}, }); await waitForBatchedUpdates(); @@ -3403,6 +3404,7 @@ describe('actions/Policy', () => { }, transactionViolations: {}, betas: [], + personalDetailsList: {}, }); await waitForBatchedUpdates(); diff --git a/tests/unit/TransactionPreviewUtils.test.ts b/tests/unit/TransactionPreviewUtils.test.ts index 8be0e1246f59..732cd46d34b8 100644 --- a/tests/unit/TransactionPreviewUtils.test.ts +++ b/tests/unit/TransactionPreviewUtils.test.ts @@ -22,6 +22,7 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; const basicProps = { iouReport: buildOptimisticIOUReport(123, 234, 1000, '1', 'USD'), + iouReportOwnerLogin: undefined, policy: undefined, transaction: buildOptimisticTransaction({ transactionParams: { @@ -829,28 +830,28 @@ describe('TransactionPreviewUtils', () => { const rbrPolicy = basicProps.policy; it('should return false for a clean transaction with no violations', () => { - expect(transactionHasRBR(basicProps.transaction, [], rbrEmail, rbrAccountID, rbrReport, rbrPolicy)).toBe(false); + expect(transactionHasRBR(basicProps.transaction, [], rbrEmail, rbrAccountID, rbrReport, undefined, rbrPolicy)).toBe(false); }); it('should return true for a transaction with violation-type violations', () => { const violations = [{name: CONST.VIOLATIONS.MISSING_CATEGORY, type: CONST.VIOLATION_TYPES.VIOLATION, showInReview: true}]; - expect(transactionHasRBR(basicProps.transaction, violations, rbrEmail, rbrAccountID, rbrReport, rbrPolicy)).toBe(true); + expect(transactionHasRBR(basicProps.transaction, violations, rbrEmail, rbrAccountID, rbrReport, undefined, rbrPolicy)).toBe(true); }); it('should return true for a transaction with warning-type violations', () => { const violations = [{name: CONST.VIOLATIONS.CUSTOM_RULES, type: CONST.VIOLATION_TYPES.WARNING, showInReview: true}]; - expect(transactionHasRBR(basicProps.transaction, violations, rbrEmail, rbrAccountID, rbrReport, rbrPolicy)).toBe(true); + expect(transactionHasRBR(basicProps.transaction, violations, rbrEmail, rbrAccountID, rbrReport, undefined, rbrPolicy)).toBe(true); }); it('should return true for a transaction on hold', () => { const heldTransaction = {...basicProps.transaction, comment: {hold: 'true'}}; - expect(transactionHasRBR(heldTransaction, [], rbrEmail, rbrAccountID, rbrReport, rbrPolicy)).toBe(true); + expect(transactionHasRBR(heldTransaction, [], rbrEmail, rbrAccountID, rbrReport, undefined, rbrPolicy)).toBe(true); }); it('should return true for a transaction with missing merchant on an expense report', () => { const expenseReport = {...basicProps.iouReport, type: CONST.REPORT.TYPE.EXPENSE}; const transactionMissingMerchant = {...basicProps.transaction, merchant: '', modifiedMerchant: '', created: '2024-01-01'}; - expect(transactionHasRBR(transactionMissingMerchant, [], rbrEmail, rbrAccountID, expenseReport, rbrPolicy)).toBe(true); + expect(transactionHasRBR(transactionMissingMerchant, [], rbrEmail, rbrAccountID, expenseReport, undefined, rbrPolicy)).toBe(true); }); it('should return true for a transaction with receipt error', () => { @@ -866,16 +867,16 @@ describe('TransactionPreviewUtils', () => { }, }, }; - expect(transactionHasRBR(transactionWithReceiptError, [], rbrEmail, rbrAccountID, rbrReport, rbrPolicy)).toBe(true); + expect(transactionHasRBR(transactionWithReceiptError, [], rbrEmail, rbrAccountID, rbrReport, undefined, rbrPolicy)).toBe(true); }); it('should return false for undefined transaction', () => { - expect(transactionHasRBR(undefined, [], rbrEmail, rbrAccountID, rbrReport, rbrPolicy)).toBe(false); + expect(transactionHasRBR(undefined, [], rbrEmail, rbrAccountID, rbrReport, undefined, rbrPolicy)).toBe(false); }); it('should return false for notice-type violations only', () => { const violations = [{name: CONST.VIOLATIONS.CUSTOM_RULES, type: CONST.VIOLATION_TYPES.NOTICE, showInReview: true}]; - expect(transactionHasRBR(basicProps.transaction, violations, rbrEmail, rbrAccountID, rbrReport, rbrPolicy)).toBe(false); + expect(transactionHasRBR(basicProps.transaction, violations, rbrEmail, rbrAccountID, rbrReport, undefined, rbrPolicy)).toBe(false); }); it('should return false for dismissed violation-type violations', () => { @@ -890,7 +891,7 @@ describe('TransactionPreviewUtils', () => { }, }, }; - expect(transactionHasRBR(transactionWithDismissal, violations, userEmail, rbrAccountID, rbrReport, rbrPolicy)).toBe(false); + expect(transactionHasRBR(transactionWithDismissal, violations, userEmail, rbrAccountID, rbrReport, undefined, rbrPolicy)).toBe(false); }); it('should return false for held transaction on a fully settled report', async () => { @@ -901,7 +902,7 @@ describe('TransactionPreviewUtils', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${settledReport.reportID}`, settledReport); await waitForBatchedUpdates(); const heldTransaction = {...basicProps.transaction, comment: {hold: 'true'}}; - expect(transactionHasRBR(heldTransaction, [], rbrEmail, rbrAccountID, settledReport, rbrPolicy)).toBe(false); + expect(transactionHasRBR(heldTransaction, [], rbrEmail, rbrAccountID, settledReport, undefined, rbrPolicy)).toBe(false); }); it('should return false for held transaction on a fully approved report', () => { @@ -911,7 +912,7 @@ describe('TransactionPreviewUtils', () => { statusNum: CONST.REPORT.STATUS_NUM.APPROVED, }; const heldTransaction = {...basicProps.transaction, comment: {hold: 'true'}}; - expect(transactionHasRBR(heldTransaction, [], rbrEmail, rbrAccountID, approvedReport, rbrPolicy)).toBe(false); + expect(transactionHasRBR(heldTransaction, [], rbrEmail, rbrAccountID, approvedReport, undefined, rbrPolicy)).toBe(false); }); it('should return true for notice-type violations on a paid group policy', async () => { @@ -927,7 +928,7 @@ describe('TransactionPreviewUtils', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${paidGroupPolicy.id}`, paidGroupPolicy); await waitForBatchedUpdates(); const violations = [{name: CONST.VIOLATIONS.CUSTOM_RULES, type: CONST.VIOLATION_TYPES.NOTICE, showInReview: true}]; - expect(transactionHasRBR(basicProps.transaction, violations, rbrEmail, rbrAccountID, expenseReport, paidGroupPolicy)).toBe(true); + expect(transactionHasRBR(basicProps.transaction, violations, rbrEmail, rbrAccountID, expenseReport, undefined, paidGroupPolicy)).toBe(true); }); it('should return true for a distance request with MODIFIED_AMOUNT violation', () => { @@ -940,7 +941,7 @@ describe('TransactionPreviewUtils', () => { }, }; const violations = [{name: CONST.VIOLATIONS.MODIFIED_AMOUNT, type: CONST.VIOLATION_TYPES.NOTICE, showInReview: true}]; - expect(transactionHasRBR(distanceTransaction, violations, rbrEmail, rbrAccountID, rbrReport, rbrPolicy)).toBe(true); + expect(transactionHasRBR(distanceTransaction, violations, rbrEmail, rbrAccountID, rbrReport, undefined, rbrPolicy)).toBe(true); }); it('should return true when there are report action errors for the transaction', () => { @@ -955,7 +956,7 @@ describe('TransactionPreviewUtils', () => { pendingAction: null, }, } as unknown as ReportActions; - expect(transactionHasRBR(basicProps.transaction, [], rbrEmail, rbrAccountID, rbrReport, rbrPolicy, reportActionsWithErrors)).toBe(true); + expect(transactionHasRBR(basicProps.transaction, [], rbrEmail, rbrAccountID, rbrReport, undefined, rbrPolicy, reportActionsWithErrors)).toBe(true); }); it('should return true when policy has DEW and there is a submit failure', () => { @@ -973,7 +974,7 @@ describe('TransactionPreviewUtils', () => { pendingAction: null, }, } as unknown as ReportActions; - expect(transactionHasRBR(basicProps.transaction, [], rbrEmail, rbrAccountID, rbrReport, dewPolicy, dewReportActions)).toBe(true); + expect(transactionHasRBR(basicProps.transaction, [], rbrEmail, rbrAccountID, rbrReport, undefined, dewPolicy, dewReportActions)).toBe(true); }); it('should return false for a distance request with missing merchant (guarded by hasMissingSmartscanFields)', () => { @@ -988,7 +989,7 @@ describe('TransactionPreviewUtils', () => { }, }; const expenseReport = {...basicProps.iouReport, type: CONST.REPORT.TYPE.EXPENSE}; - expect(transactionHasRBR(distanceTransaction, [], rbrEmail, rbrAccountID, expenseReport, rbrPolicy)).toBe(false); + expect(transactionHasRBR(distanceTransaction, [], rbrEmail, rbrAccountID, expenseReport, undefined, rbrPolicy)).toBe(false); }); it('should return false for a scanning receipt with missing fields (guarded by hasMissingSmartscanFields)', () => { @@ -1000,7 +1001,7 @@ describe('TransactionPreviewUtils', () => { created: '2024-01-01', }; const expenseReport = {...basicProps.iouReport, type: CONST.REPORT.TYPE.EXPENSE}; - expect(transactionHasRBR(scanningTransaction, [], rbrEmail, rbrAccountID, expenseReport, rbrPolicy)).toBe(false); + expect(transactionHasRBR(scanningTransaction, [], rbrEmail, rbrAccountID, expenseReport, undefined, rbrPolicy)).toBe(false); }); }); @@ -1015,20 +1016,20 @@ describe('TransactionPreviewUtils', () => { it('should return 0 when both transactions have RBR', () => { const secondRbrTransaction = {...basicProps.transaction, transactionID: 'rbr_txn_2', comment: {hold: 'true'}}; - expect(compareByRBR(rbrTransaction, secondRbrTransaction, undefined, cbrEmail, cbrAccountID, cbrReport, cbrPolicy)).toBe(0); + expect(compareByRBR(rbrTransaction, secondRbrTransaction, undefined, cbrEmail, cbrAccountID, cbrReport, undefined, cbrPolicy)).toBe(0); }); it('should return 0 when neither transaction has RBR', () => { const secondCleanTransaction = {...basicProps.transaction, transactionID: 'clean_txn_2'}; - expect(compareByRBR(cleanTransaction, secondCleanTransaction, undefined, cbrEmail, cbrAccountID, cbrReport, cbrPolicy)).toBe(0); + expect(compareByRBR(cleanTransaction, secondCleanTransaction, undefined, cbrEmail, cbrAccountID, cbrReport, undefined, cbrPolicy)).toBe(0); }); it('should return -1 when only the first transaction has RBR', () => { - expect(compareByRBR(rbrTransaction, cleanTransaction, undefined, cbrEmail, cbrAccountID, cbrReport, cbrPolicy)).toBe(-1); + expect(compareByRBR(rbrTransaction, cleanTransaction, undefined, cbrEmail, cbrAccountID, cbrReport, undefined, cbrPolicy)).toBe(-1); }); it('should return 1 when only the second transaction has RBR', () => { - expect(compareByRBR(cleanTransaction, rbrTransaction, undefined, cbrEmail, cbrAccountID, cbrReport, cbrPolicy)).toBe(1); + expect(compareByRBR(cleanTransaction, rbrTransaction, undefined, cbrEmail, cbrAccountID, cbrReport, undefined, cbrPolicy)).toBe(1); }); }); }); diff --git a/tests/unit/TransactionUtilsTest.ts b/tests/unit/TransactionUtilsTest.ts index f6ee3ae78ec5..b053f3fd056b 100644 --- a/tests/unit/TransactionUtilsTest.ts +++ b/tests/unit/TransactionUtilsTest.ts @@ -1145,7 +1145,7 @@ describe('TransactionUtils', () => { const violation = {type: CONST.VIOLATION_TYPES.VIOLATION, name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION}; // When checking if violation is dismissed - const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined); + const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined, undefined); // Then it should return true expect(result).toBe(true); @@ -1159,7 +1159,7 @@ describe('TransactionUtils', () => { const violation = {type: CONST.VIOLATION_TYPES.VIOLATION, name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION}; // When checking if violation is dismissed - const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined); + const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined, undefined); // Then it should return false expect(result).toBe(false); @@ -1179,7 +1179,7 @@ describe('TransactionUtils', () => { const violation = {type: CONST.VIOLATION_TYPES.VIOLATION, name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION}; // When checking if violation is dismissed for current user - const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined); + const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined, undefined); // Then it should return false since current user hasn't dismissed it expect(result).toBe(false); @@ -1208,7 +1208,7 @@ describe('TransactionUtils', () => { const violation = {type: CONST.VIOLATION_TYPES.VIOLATION, name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION}; // When current user (admin, not the owner) checks if violation is dismissed - const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, iouReport, undefined); + const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, iouReport, undefined, undefined); // Then it should return true because admin sees owner's perspective on open reports expect(result).toBe(true); @@ -1235,7 +1235,7 @@ describe('TransactionUtils', () => { const violation = {type: CONST.VIOLATION_TYPES.VIOLATION, name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION}; // When current user (admin, not the owner) checks if violation is dismissed - const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, iouReport, undefined); + const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, iouReport, undefined, undefined); // Then it should return false because on processing reports, admin must dismiss separately expect(result).toBe(false); @@ -1262,7 +1262,7 @@ describe('TransactionUtils', () => { const violation = {type: CONST.VIOLATION_TYPES.VIOLATION, name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION}; // When current user (the submitter) checks if violation is dismissed - const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, iouReport, undefined); + const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, iouReport, undefined, undefined); // Then it should return false (condition 2 doesn't apply to submitters) expect(result).toBe(false); @@ -1291,7 +1291,7 @@ describe('TransactionUtils', () => { const violation = {type: CONST.VIOLATION_TYPES.WARNING, name: CONST.VIOLATIONS.RTER}; // When current user checks if violation is dismissed - const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, policy); + const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined, policy); // Then it should return true because on instant submit, anyone's dismissal counts expect(result).toBe(true); @@ -1318,7 +1318,7 @@ describe('TransactionUtils', () => { const violation = {type: CONST.VIOLATION_TYPES.WARNING, name: CONST.VIOLATIONS.RTER}; // When current user checks if violation is dismissed - const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, policy); + const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined, policy); // Then it should return false because on non-instant submit, each person must dismiss separately expect(result).toBe(false); @@ -1345,7 +1345,7 @@ describe('TransactionUtils', () => { const violation = {type: CONST.VIOLATION_TYPES.WARNING, name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION}; // When current user checks if violation is dismissed - const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, policy); + const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined, policy); // Then it should return false because condition 3 only applies to RTER violations expect(result).toBe(false); @@ -1373,7 +1373,7 @@ describe('TransactionUtils', () => { const violation = {type: CONST.VIOLATION_TYPES.WARNING, name: CONST.VIOLATIONS.RTER}; // When current user checks if violation is dismissed - const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, policy); + const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined, policy); // Then it should return true expect(result).toBe(true); @@ -1383,13 +1383,13 @@ describe('TransactionUtils', () => { describe('Edge cases and data validation', () => { it('should return false when transaction is null', () => { const violation = {type: CONST.VIOLATION_TYPES.VIOLATION, name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION}; - const result = TransactionUtils.isViolationDismissed(undefined, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined); + const result = TransactionUtils.isViolationDismissed(undefined, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined, undefined); expect(result).toBe(false); }); it('should return false when violation is null', () => { const transaction = generateTransaction({}); - const result = TransactionUtils.isViolationDismissed(transaction, undefined, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined); + const result = TransactionUtils.isViolationDismissed(transaction, undefined, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined, undefined); expect(result).toBe(false); }); @@ -1404,7 +1404,7 @@ describe('TransactionUtils', () => { }, }); const violation = {type: CONST.VIOLATION_TYPES.VIOLATION, name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION}; - const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined); + const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, undefined, undefined, undefined); expect(result).toBe(false); }); }); diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index 65efaa1cbb33..2f95f822b0aa 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -2896,8 +2896,8 @@ describe('getViolations', () => { await Onyx.multiSet({...transactionCollectionDataSet}); - const isSmartScanDismissed = isViolationDismissed(transaction, smartScanFailedViolation, CARLOS_EMAIL, CARLOS_ACCOUNT_ID, undefined, undefined); - const isDuplicateViolationDismissed = isViolationDismissed(transaction, duplicatedTransactionViolation, CARLOS_EMAIL, CARLOS_ACCOUNT_ID, undefined, undefined); + const isSmartScanDismissed = isViolationDismissed(transaction, smartScanFailedViolation, CARLOS_EMAIL, CARLOS_ACCOUNT_ID, undefined, undefined, undefined); + const isDuplicateViolationDismissed = isViolationDismissed(transaction, duplicatedTransactionViolation, CARLOS_EMAIL, CARLOS_ACCOUNT_ID, undefined, undefined, undefined); expect(isSmartScanDismissed).toBeTruthy(); expect(isDuplicateViolationDismissed).toBeFalsy(); @@ -2935,8 +2935,8 @@ describe('getViolations', () => { await Onyx.multiSet({...transactionCollectionDataSet}); - const isSmartScanDismissed = isViolationDismissed(transaction, smartScanFailedViolation, CARLOS_EMAIL, CARLOS_ACCOUNT_ID, report, policy); - const isDuplicateViolationDismissed = isViolationDismissed(transaction, duplicatedTransactionViolation, CARLOS_EMAIL, CARLOS_ACCOUNT_ID, report, policy); + const isSmartScanDismissed = isViolationDismissed(transaction, smartScanFailedViolation, CARLOS_EMAIL, CARLOS_ACCOUNT_ID, report, undefined, policy); + const isDuplicateViolationDismissed = isViolationDismissed(transaction, duplicatedTransactionViolation, CARLOS_EMAIL, CARLOS_ACCOUNT_ID, report, undefined, policy); expect(isSmartScanDismissed).toBeTruthy(); expect(isDuplicateViolationDismissed).toBeFalsy(); @@ -3017,7 +3017,7 @@ describe('getViolations', () => { }; await Onyx.multiSet({...transactionCollectionDataSet}); - const hasWarningTypeViolationRes = hasWarningTypeViolation(transaction, transactionViolationsCollection, '', CONST.DEFAULT_NUMBER_ID, undefined, undefined); + const hasWarningTypeViolationRes = hasWarningTypeViolation(transaction, transactionViolationsCollection, '', CONST.DEFAULT_NUMBER_ID, undefined, undefined, undefined); expect(hasWarningTypeViolationRes).toBeTruthy(); }); @@ -3057,7 +3057,7 @@ describe('getViolations', () => { }; await Onyx.multiSet({...transactionCollectionDataSet}); - const hasWarningTypeViolationRes = hasWarningTypeViolation(transaction, transactionViolationsCollection, CARLOS_EMAIL, CARLOS_ACCOUNT_ID, report, policy); + const hasWarningTypeViolationRes = hasWarningTypeViolation(transaction, transactionViolationsCollection, CARLOS_EMAIL, CARLOS_ACCOUNT_ID, report, CARLOS_EMAIL, policy); expect(hasWarningTypeViolationRes).toBeTruthy(); }); }); From b386b29e06a15f108d5d0d14d0931e750e19c9bb Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 14 Jul 2026 11:46:00 +0800 Subject: [PATCH 2/8] remove personal details from Onyx.connect from getLoginsByAccountIDs and create a deprected version of getLoginsByAccountIDs --- src/libs/NextStepUtils.ts | 4 ++-- src/libs/PersonalDetailsUtils.ts | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 6b85be2b1e1a..a931ece81181 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -17,7 +17,7 @@ import Onyx from 'react-native-onyx'; import EmailUtils from './EmailUtils'; import {formatPhoneNumber as formatPhoneNumberPhoneUtils} from './LocalePhoneNumber'; import isTrackOnboardingChoice from './OnboardingUtils'; -import {getLoginsByAccountIDs, getPersonalDetailsByIDs} from './PersonalDetailsUtils'; +import {deprecatedGetLoginsByAccountIDs, getPersonalDetailsByIDs} from './PersonalDetailsUtils'; import {getApprovalWorkflow, getCorrectedAutoReportingFrequency, getReimburserAccountID} from './PolicyUtils'; import { getDisplayNameForParticipant, @@ -530,7 +530,7 @@ function buildNextStepNew(params: BuildNextStepNewParams): ReportNextStepDepreca ? (getDisplayNameForParticipant({accountID: bypassNextApproverID, formatPhoneNumber: formatPhoneNumberPhoneUtils}) ?? getPersonalDetailsForAccountID(bypassNextApproverID).login) : getNextApproverDisplayName(report, isUnapprove); const approverAccountID = bypassNextApproverID ?? getNextApproverAccountID(report, isUnapprove); - const approvers = getLoginsByAccountIDs([approverAccountID ?? CONST.DEFAULT_NUMBER_ID]); + const approvers = deprecatedGetLoginsByAccountIDs([approverAccountID ?? CONST.DEFAULT_NUMBER_ID]); const reimburserAccountID = getReimburserAccountID(policy); const type: ReportNextStepDeprecated['type'] = 'neutral'; diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 1fc92e70386b..1d1398b9fd24 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -302,7 +302,7 @@ function getLoginByAccountID(accountID: number | undefined, personalDetails: Ony * @param personalDetailsList Record of user personal details, indexed by user id * @returns Array of logins according to passed accountIDs */ -function getLoginsByAccountIDs(accountIDs: number[] | undefined, personalDetailsList: OnyxEntry = allPersonalDetails): string[] { +function getLoginsByAccountIDs(accountIDs: number[] | undefined, personalDetailsList: OnyxEntry): string[] { return ( accountIDs?.reduce((foundLogins: string[], accountID) => { const currentLogin = getLoginByAccountID(accountID, personalDetailsList); @@ -314,6 +314,13 @@ function getLoginsByAccountIDs(accountIDs: number[] | undefined, personalDetails ); } +/** + * @deprecated + */ +function deprecatedGetLoginsByAccountIDs(accountIDs: number[] | undefined): string[] { + return getLoginsByAccountIDs(accountIDs, allPersonalDetails); +} + /** * Provided a set of invited logins and optimistic accountIDs. Returns the ones which are not known to the user i.e. they do not exist in the personalDetailsList. */ @@ -620,6 +627,7 @@ export { getPersonalDetailByEmail, getKnownAccountIDByLogin, getAccountIDsByLogins, + deprecatedGetLoginsByAccountIDs, getLoginsByAccountIDs, getPersonalDetailsOnyxDataForOptimisticUsers, getCurrentAddress, From 43c3f349b6813e9e493a3734f81a89e415faefa2 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 14 Jul 2026 12:00:56 +0800 Subject: [PATCH 3/8] fix test --- tests/actions/PolicyTest.ts | 1 + tests/unit/TransactionUtilsTest.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index eab172671506..ac9388a07cfe 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -3417,6 +3417,7 @@ describe('actions/Policy', () => { undefined, undefined, expect.anything(), + undefined, expect.anything(), ); diff --git a/tests/unit/TransactionUtilsTest.ts b/tests/unit/TransactionUtilsTest.ts index b053f3fd056b..1b1e2657ab42 100644 --- a/tests/unit/TransactionUtilsTest.ts +++ b/tests/unit/TransactionUtilsTest.ts @@ -1208,7 +1208,7 @@ describe('TransactionUtils', () => { const violation = {type: CONST.VIOLATION_TYPES.VIOLATION, name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION}; // When current user (admin, not the owner) checks if violation is dismissed - const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, iouReport, undefined, undefined); + const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL, CURRENT_USER_ID, iouReport, OTHER_USER_EMAIL, undefined); // Then it should return true because admin sees owner's perspective on open reports expect(result).toBe(true); From 2f284e4ee51524dc52c6ac1b3b83b7445bd534e1 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 14 Jul 2026 12:05:20 +0800 Subject: [PATCH 4/8] create a deprecated getPersonalDetailsByIDs --- src/libs/NextStepUtils.ts | 4 ++-- src/libs/PersonalDetailsUtils.ts | 7 ++++--- src/selectors/PersonalDetails.ts | 4 ++-- tests/unit/libs/PersonalDetailsUtilsTest.ts | 12 ++++++------ 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index a931ece81181..2b01a2dc47cc 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -17,7 +17,7 @@ import Onyx from 'react-native-onyx'; import EmailUtils from './EmailUtils'; import {formatPhoneNumber as formatPhoneNumberPhoneUtils} from './LocalePhoneNumber'; import isTrackOnboardingChoice from './OnboardingUtils'; -import {deprecatedGetLoginsByAccountIDs, getPersonalDetailsByIDs} from './PersonalDetailsUtils'; +import {deprecatedGetLoginsByAccountIDs, deprecatedGetPersonalDetailsByIDs} from './PersonalDetailsUtils'; import {getApprovalWorkflow, getCorrectedAutoReportingFrequency, getReimburserAccountID} from './PolicyUtils'; import { getDisplayNameForParticipant, @@ -512,7 +512,7 @@ function buildNextStepNew(params: BuildNextStepNewParams): ReportNextStepDepreca const autoReportingFrequency = getCorrectedAutoReportingFrequency(policy); const isInstantSubmitEnabled = autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT; const shouldShowFixMessage = hasViolations && isInstantSubmitEnabled && !isASAPSubmitBetaEnabled; - const [policyOwnerPersonalDetails, ownerPersonalDetails] = getPersonalDetailsByIDs({ + const [policyOwnerPersonalDetails, ownerPersonalDetails] = deprecatedGetPersonalDetailsByIDs({ accountIDs: [policy?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID, ownerAccountID], currentUserAccountID: currentUserAccountIDParam ?? CONST.DEFAULT_NUMBER_ID, shouldChangeUserDisplayName: true, diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 1d1398b9fd24..85851c7066cc 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -174,8 +174,9 @@ function getPersonalDetailsByID(accountID: number | undefined, personalDetailsLi * @param currentUserAccountID * @param shouldChangeUserDisplayName - It will replace the current user's personal detail object's displayName with 'You'. * @returns - Array of personal detail objects + * @deprecated */ -function getPersonalDetailsByIDs({ +function deprecatedGetPersonalDetailsByIDs({ accountIDs, currentUserAccountID, shouldChangeUserDisplayName = false, @@ -204,7 +205,7 @@ function getPersonalDetailsByIDs({ return result; } -function newGetPersonalDetailsByIDs(accountIDs: number[] | undefined, personalDetails: OnyxEntry): PersonalDetails[] { +function getPersonalDetailsByIDs(accountIDs: number[] | undefined, personalDetails: OnyxEntry): PersonalDetails[] { if (!accountIDs) { return []; } @@ -619,8 +620,8 @@ function areTravelPersonalDetailsMissing(privatePersonalDetails: OnyxEntry (personalDetailsList: OnyxEntry) => getPersonalDetailsByID(accountID, personalDetailsList); -const multiPersonalDetailsSelector = (accountIDs: number[] | undefined) => (personalDetails: OnyxEntry) => newGetPersonalDetailsByIDs(accountIDs, personalDetails); +const multiPersonalDetailsSelector = (accountIDs: number[] | undefined) => (personalDetails: OnyxEntry) => getPersonalDetailsByIDs(accountIDs, personalDetails); const personalDetailsListSelector = (accountIDs: Array | undefined) => (personalDetailsList: OnyxEntry) => getPersonalDetailsListByIDs(accountIDs, personalDetailsList); diff --git a/tests/unit/libs/PersonalDetailsUtilsTest.ts b/tests/unit/libs/PersonalDetailsUtilsTest.ts index 702550c5050a..20f7febde9c4 100644 --- a/tests/unit/libs/PersonalDetailsUtilsTest.ts +++ b/tests/unit/libs/PersonalDetailsUtilsTest.ts @@ -8,7 +8,7 @@ import { getPersonalDetailByEmail, getPersonalDetailsListByIDs, getPersonalDetailsOnyxDataForOptimisticUsers, - newGetPersonalDetailsByIDs, + getPersonalDetailsByIDs, temporaryGetDisplayNameOrDefault, } from '@libs/PersonalDetailsUtils'; @@ -796,7 +796,7 @@ describe('PersonalDetailsUtils', () => { }); }); - describe('newGetPersonalDetailsByIDs', () => { + describe('getPersonalDetailsByIDs', () => { const accountID1 = 1; const accountID2 = 2; const personalDetails: PersonalDetailsList = { @@ -813,22 +813,22 @@ describe('PersonalDetailsUtils', () => { }; it('should return an empty array if accountIDs is undefined', () => { - const result = newGetPersonalDetailsByIDs(undefined, personalDetails); + const result = getPersonalDetailsByIDs(undefined, personalDetails); expect(result).toEqual([]); }); it('should return an empty array if accountIDs is empty', () => { - const result = newGetPersonalDetailsByIDs([], personalDetails); + const result = getPersonalDetailsByIDs([], personalDetails); expect(result).toEqual([]); }); it('should return personal details for the given accountIDs', () => { - const result = newGetPersonalDetailsByIDs([accountID1, accountID2], personalDetails); + const result = getPersonalDetailsByIDs([accountID1, accountID2], personalDetails); expect(result).toEqual([personalDetails[accountID1], personalDetails[accountID2]]); }); it('should filter out accountIDs that do not have corresponding personal details', () => { - const result = newGetPersonalDetailsByIDs([accountID1, 999], personalDetails); + const result = getPersonalDetailsByIDs([accountID1, 999], personalDetails); expect(result).toEqual([personalDetails[accountID1]]); }); }); From 0c28c025ca7e11218e02ffd8e5fb4d90e7ec6b29 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 14 Jul 2026 12:06:44 +0800 Subject: [PATCH 5/8] add. comment --- src/libs/PersonalDetailsUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 85851c7066cc..3b6e14bccd27 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -174,7 +174,7 @@ function getPersonalDetailsByID(accountID: number | undefined, personalDetailsLi * @param currentUserAccountID * @param shouldChangeUserDisplayName - It will replace the current user's personal detail object's displayName with 'You'. * @returns - Array of personal detail objects - * @deprecated + * @deprecated Don't use this. The only usage left is in deprecated NextStepUtils/buildNextStepNew which will be removed later. */ function deprecatedGetPersonalDetailsByIDs({ accountIDs, @@ -316,7 +316,7 @@ function getLoginsByAccountIDs(accountIDs: number[] | undefined, personalDetails } /** - * @deprecated + * @deprecated Don't use this. The only usage left is in deprecated NextStepUtils/buildNextStepNew which will be removed later. */ function deprecatedGetLoginsByAccountIDs(accountIDs: number[] | undefined): string[] { return getLoginsByAccountIDs(accountIDs, allPersonalDetails); From 1e66ce2de68d85eae58bc9a524ad18f9d42c9e31 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 14 Jul 2026 12:11:33 +0800 Subject: [PATCH 6/8] use the merged personal details --- src/libs/SearchUIUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 941b982df3fb..797174ca67e5 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -2892,7 +2892,7 @@ function getReportSections({ const hasVisibleViolationsForReport = hasVisibleViolations( reportItem, - getLoginByAccountID(reportItem.ownerAccountID, data.personalDetailsList), + getLoginByAccountID(reportItem.ownerAccountID, mergedPersonalDetails), allViolations, currentUserEmail, currentAccountID ?? CONST.DEFAULT_NUMBER_ID, From 9a1477ccd5cd0e0028c67ea8f53194e7e5a6e21a Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 14 Jul 2026 12:13:16 +0800 Subject: [PATCH 7/8] pass missing owner login --- .../actions/OnyxDerived/configs/reportAttributes.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts index c1c8f9d28268..924f5d59ab26 100644 --- a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts +++ b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts @@ -3,6 +3,7 @@ import type {LocalizedTranslate} from '@components/LocaleContextProvider'; import {getReportPreviewAction} from '@libs/actions/IOU/MoneyRequestBuilder'; import {translate as translateForLocale} from '@libs/Localize'; import {getIsOffline} from '@libs/NetworkState'; +import {getLoginByAccountID} from '@libs/PersonalDetailsUtils'; import {getLinkedTransactionID} from '@libs/ReportActionsUtils'; import {computeReportName} from '@libs/ReportNameUtils'; import { @@ -182,6 +183,7 @@ const isActionable = (childReport: OnyxEntry) => isOpenReport(childRepor // transactionViolations so this works even when owner data is absent (e.g. masked Onyx exports). const needsViolationFix = ( childReport: OnyxEntry, + childReportOwnerLogin: string | undefined, policies: OnyxCollection, transactionViolations: OnyxCollection, currentUserAccountID: number, @@ -191,7 +193,7 @@ const needsViolationFix = ( return false; } const childPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${childReport.policyID}`]; - return hasViolations(childReport.reportID, transactionViolations, currentUserAccountID, currentUserEmail, true, undefined, childReport, childPolicy); + return hasViolations(childReport.reportID, transactionViolations, currentUserAccountID, currentUserEmail, true, undefined, childReport, childReportOwnerLogin, childPolicy); }; /** @@ -586,7 +588,14 @@ export default createOnyxDerivedValueConfig({ actionTargetReportActionID = getOldestPreviewActionID(chatReportID, erroredChildReportIDs, reports, isActionable) ?? getOldestPreviewActionID(chatReportID, childReportIDsByChat.get(chatReportID), reports, (childReport) => - needsViolationFix(childReport, policies, transactionViolations, currentUserAccountID, currentUserEmail), + needsViolationFix( + childReport, + getLoginByAccountID(childReport?.ownerAccountID, personalDetails), + policies, + transactionViolations, + currentUserAccountID, + currentUserEmail, + ), ) ?? getOldestPreviewActionID(chatReportID, erroredChildReportIDs, reports) ?? actionTargetReportActionID; From 9bb5e8e5f652f7420021cffacbd9695afc26cadc Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 15 Jul 2026 10:18:21 +0800 Subject: [PATCH 8/8] suppress lint --- src/libs/NextStepUtils.ts | 2 ++ src/libs/PersonalDetailsUtils.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 65783cceee26..55fad48eba42 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -512,6 +512,7 @@ function buildNextStepNew(params: BuildNextStepNewParams): ReportNextStepDepreca const autoReportingFrequency = getCorrectedAutoReportingFrequency(policy); const isInstantSubmitEnabled = autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT; const shouldShowFixMessage = hasViolations && isInstantSubmitEnabled && !isASAPSubmitBetaEnabled; + // eslint-disable-next-line @typescript-eslint/no-deprecated const [policyOwnerPersonalDetails, ownerPersonalDetails] = deprecatedGetPersonalDetailsByIDs({ accountIDs: [policy?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID, ownerAccountID], currentUserAccountID: currentUserAccountIDParam ?? CONST.DEFAULT_NUMBER_ID, @@ -530,6 +531,7 @@ function buildNextStepNew(params: BuildNextStepNewParams): ReportNextStepDepreca ? (getDisplayNameForParticipant({accountID: bypassNextApproverID, formatPhoneNumber: formatPhoneNumberPhoneUtils}) ?? getPersonalDetailsForAccountID(bypassNextApproverID).login) : getNextApproverDisplayName(report, isUnapprove); const approverAccountID = bypassNextApproverID ?? getNextApproverAccountID(report, isUnapprove); + // eslint-disable-next-line @typescript-eslint/no-deprecated const approvers = deprecatedGetLoginsByAccountIDs([approverAccountID ?? CONST.DEFAULT_NUMBER_ID]); const reimburserAccountID = getReimburserAccountID(policy); diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 3b6e14bccd27..bd264ed9d4b8 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -620,6 +620,7 @@ function areTravelPersonalDetailsMissing(privatePersonalDetails: OnyxEntry