From 6ba92f3d04f79e4888713c107f264164f3bf8f36 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 12 Nov 2025 16:47:03 +0700 Subject: [PATCH 01/10] remove connect method --- src/components/MoneyReportHeader.tsx | 10 +-- .../TransactionPreviewContent.tsx | 5 +- src/libs/PersonalDetailsUtils.ts | 2 +- src/libs/ReportPreviewActionUtils.ts | 2 +- src/libs/ReportPrimaryActionUtils.ts | 10 +-- src/libs/ReportSecondaryActionUtils.ts | 4 +- src/libs/ReportUtils.ts | 12 ++-- src/libs/SearchUIUtils.ts | 2 +- src/libs/TransactionPreviewUtils.ts | 12 ++-- src/libs/TransactionUtils/index.ts | 70 ++++++++++--------- src/libs/actions/IOU.ts | 17 ++--- src/libs/actions/Report.ts | 4 +- .../PopoverReportActionContextMenu.tsx | 4 +- tests/unit/IOUUtilsTest.ts | 4 +- tests/unit/ReportPrimaryActionUtilsTest.ts | 6 +- tests/unit/TransactionUtilsTest.ts | 11 --- tests/unit/ViolationUtilsTest.ts | 4 +- 17 files changed, 92 insertions(+), 87 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 6686850793b5..73fb5411c405 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -298,7 +298,7 @@ function MoneyReportHeader({ // Check if there is pending rter violation in all transactionViolations with given transactionIDs. // wrapped in useMemo to avoid unnecessary re-renders and for better performance (array operation inside of function) - const hasAllPendingRTERViolations = useMemo(() => allHavePendingRTERViolation(transactions, violations), [transactions, violations]); + const hasAllPendingRTERViolations = useMemo(() => allHavePendingRTERViolation(transactions, violations, email ?? ''), [transactions, violations]); // Check if user should see broken connection violation warning. const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationForMultipleTransactions(transactionIDs, moneyRequestReport, policy, violations); const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(moneyRequestReport?.reportID); @@ -385,7 +385,7 @@ function MoneyReportHeader({ const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; - const hasDuplicates = hasDuplicateTransactions(moneyRequestReport?.reportID); + const hasDuplicates = hasDuplicateTransactions(email ?? '', moneyRequestReport?.reportID); const shouldShowMarkAsResolved = isMarkAsResolvedAction(moneyRequestReport, transactionViolations); const shouldShowStatusBar = hasAllPendingRTERViolations || @@ -567,7 +567,7 @@ function MoneyReportHeader({ }; const getFirstDuplicateThreadID = (transactionsList: OnyxTypes.Transaction[], allReportActions: OnyxTypes.ReportAction[]) => { - const duplicateTransaction = transactionsList.find((reportTransaction) => isDuplicate(reportTransaction)); + const duplicateTransaction = transactionsList.find((reportTransaction) => isDuplicate(reportTransaction, email ?? '')); if (!duplicateTransaction) { return null; } @@ -850,7 +850,7 @@ function MoneyReportHeader({ onPress={() => { let threadID = transactionThreadReportID ?? getFirstDuplicateThreadID(transactions, reportActions); if (!threadID) { - const duplicateTransaction = transactions.find((reportTransaction) => isDuplicate(reportTransaction)); + const duplicateTransaction = transactions.find((reportTransaction) => isDuplicate(reportTransaction, email ?? '')); const transactionID = duplicateTransaction?.transactionID; const iouAction = getIOUActionForReportID(moneyRequestReport?.reportID, transactionID); const createdTransactionThreadReport = createTransactionThreadReport(moneyRequestReport, iouAction); @@ -1142,7 +1142,7 @@ function MoneyReportHeader({ Navigation.goBack(route.params?.backTo); // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { - deleteAppReport(moneyRequestReport?.reportID); + deleteAppReport(moneyRequestReport?.reportID, email); }); }, }, diff --git a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx index 203e75adc811..387e66fcd32e 100644 --- a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx +++ b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx @@ -10,6 +10,7 @@ import ReportActionItemImages from '@components/ReportActionItem/ReportActionIte import UserInfoCellsWithArrow from '@components/SelectionListWithSections/Search/UserInfoCellsWithArrow'; import Text from '@components/Text'; import TransactionPreviewSkeletonView from '@components/TransactionPreviewSkeletonView'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useReportIsArchived from '@hooks/useReportIsArchived'; @@ -75,7 +76,7 @@ function TransactionPreviewContent({ const isReportAPolicyExpenseChat = isPolicyExpenseChat(chatReport); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(report?.reportID)}`, {canBeMissing: true}); const isChatReportArchived = useReportIsArchived(chatReport?.reportID); - + const currentUserDetails = useCurrentUserPersonalDetails(); const transactionPreviewCommonArguments = useMemo( () => ({ iouReport: report, @@ -94,6 +95,7 @@ function TransactionPreviewContent({ ...transactionPreviewCommonArguments, areThereDuplicates, isReportAPolicyExpenseChat, + currentUserEmail: currentUserDetails.email ?? '', }), [areThereDuplicates, transactionPreviewCommonArguments, isReportAPolicyExpenseChat], ); @@ -112,6 +114,7 @@ function TransactionPreviewContent({ shouldShowRBR, violationMessage, reportActions, + currentUserEmail: currentUserDetails.email ?? '', }), [transactionPreviewCommonArguments, shouldShowRBR, violationMessage, reportActions], ); diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 328ef1798e2c..5119955a0ca3 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -117,7 +117,7 @@ function getPersonalDetailsByIDs({ personalDetailsParam = allPersonalDetails, }: { accountIDs: number[]; - currentUserAccountID: number; + currentUserAccountID?: number; shouldChangeUserDisplayName?: boolean; personalDetailsParam?: Partial; }): PersonalDetails[] { diff --git a/src/libs/ReportPreviewActionUtils.ts b/src/libs/ReportPreviewActionUtils.ts index 93052e34ad9a..ebb2b6f99e27 100644 --- a/src/libs/ReportPreviewActionUtils.ts +++ b/src/libs/ReportPreviewActionUtils.ts @@ -190,7 +190,7 @@ function canReview(report: Report, violations: OnyxCollection transaction.transactionID) ?? []; - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactions, violations); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactions, violations, currentUserEmail); const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationForMultipleTransactions(transactionIDs, report, policy, violations); if (hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!isAdmin || isSubmitter) && !isReportApproved({report}) && !isReportManuallyReimbursed(report))) { diff --git a/src/libs/ReportPrimaryActionUtils.ts b/src/libs/ReportPrimaryActionUtils.ts index 88119fe66bb8..0008825051b5 100644 --- a/src/libs/ReportPrimaryActionUtils.ts +++ b/src/libs/ReportPrimaryActionUtils.ts @@ -245,8 +245,8 @@ function isRemoveHoldAction(report: Report, chatReport: OnyxEntry, repor return isHolder; } -function isReviewDuplicatesAction(report: Report, reportTransactions: Transaction[]) { - const hasDuplicates = reportTransactions.some((transaction) => isDuplicate(transaction)); +function isReviewDuplicatesAction(report: Report, reportTransactions: Transaction[], currentUserEmail: string) { + const hasDuplicates = reportTransactions.some((transaction) => isDuplicate(transaction, currentUserEmail)); if (!hasDuplicates) { return false; @@ -275,7 +275,7 @@ function isMarkAsCashAction(currentUserEmail: string, report: Report, reportTran } const transactionIDs = reportTransactions.map((t) => t.transactionID); - const hasAllPendingRTERViolations = allHavePendingRTERViolation(reportTransactions, violations); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(reportTransactions, violations, currentUserEmail); if (hasAllPendingRTERViolations) { return true; @@ -347,7 +347,7 @@ function getReportPrimaryAction(params: GetReportPrimaryActionParams): ValueOf isDuplicate(transaction)); + const reportHasDuplicatedTransactions = reportTransactions.some((transaction) => isDuplicate(transaction, currentUserLogin)); if (isExpenseReport && isProcessingReport && reportHasDuplicatedTransactions) { return true; @@ -233,7 +233,7 @@ function isApproveAction(currentUserLogin: string, report: Report, reportTransac const transactionIDs = reportTransactions.map((t) => t.transactionID); - const hasAllPendingRTERViolations = allHavePendingRTERViolation(reportTransactions, violations); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(reportTransactions, violations, currentUserLogin); if (hasAllPendingRTERViolations) { return true; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 687e1e5e2b52..9cf9aa716f3a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2675,7 +2675,7 @@ function isMoneyRequestReportEligibleForMerge(reportID: string, isAdmin: boolean return isManager && isExpenseReport(report) && isProcessingReport(report); } -function hasOutstandingChildRequest(chatReport: Report, iouReportOrID: OnyxEntry | string) { +function hasOutstandingChildRequest(chatReport: Report, iouReportOrID: OnyxEntry | string, currentUserEmail: string) { const reportActions = getAllReportActions(chatReport.reportID); // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 // eslint-disable-next-line @typescript-eslint/no-deprecated @@ -2694,7 +2694,9 @@ function hasOutstandingChildRequest(chatReport: Report, iouReportOrID: OnyxEntry const iouReport = typeof iouReportOrID !== 'string' && iouReportOrID?.reportID === iouReportID ? iouReportOrID : getReportOrDraftReport(iouReportID); const transactions = getReportTransactions(iouReportID); return ( - canIOUBePaid(iouReport, chatReport, policy, transactions) || canApproveIOU(iouReport, policy, transactions) || canSubmitReport(iouReport, policy, transactions, undefined, false) + canIOUBePaid(iouReport, chatReport, policy, transactions) || + canApproveIOU(iouReport, policy, transactions) || + canSubmitReport(iouReport, policy, transactions, undefined, false, currentUserEmail) ); }); } @@ -8859,7 +8861,7 @@ function hasViolations( reportTransactions?: SearchTransaction[], ): boolean { const transactions = reportTransactions ?? getReportTransactions(reportID); - return transactions.some((transaction) => hasViolation(transaction, transactionViolations, shouldShowInReview)); + return transactions.some((transaction) => hasViolation(transaction, transactionViolations, currentUserEmail ?? '', shouldShowInReview)); } /** @@ -8872,7 +8874,7 @@ function hasWarningTypeViolations( reportTransactions?: SearchTransaction[], ): boolean { const transactions = reportTransactions ?? getReportTransactions(reportID); - return transactions.some((transaction) => hasWarningTypeViolation(transaction, transactionViolations, shouldShowInReview)); + return transactions.some((transaction) => hasWarningTypeViolation(transaction, transactionViolations, currentUserEmail ?? '', shouldShowInReview)); } /** @@ -8905,7 +8907,7 @@ function hasNoticeTypeViolations( reportTransactions?: SearchTransaction[], ): boolean { const transactions = reportTransactions ?? getReportTransactions(reportID); - return transactions.some((transaction) => hasNoticeTypeViolation(transaction, transactionViolations, shouldShowInReview)); + return transactions.some((transaction) => hasNoticeTypeViolation(transaction, transactionViolations, currentUserEmail ?? '', shouldShowInReview)); } /** diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index ba907bb38fe6..c4464b32e5a7 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -1252,7 +1252,7 @@ function getActions( } // We check for isAllowedToApproveExpenseReport because if the policy has preventSelfApprovals enabled, we disable the Submit action and in that case we want to show the View action instead - if (canSubmitReport(report, policy, allReportTransactions, allViolations, isIOUReportArchived || isChatReportArchived) && isAllowedToApproveExpenseReport) { + if (canSubmitReport(report, policy, allReportTransactions, allViolations, isIOUReportArchived || isChatReportArchived, currentUserEmail) && isAllowedToApproveExpenseReport) { allActions.push(CONST.SEARCH.ACTION_TYPES.SUBMIT); } diff --git a/src/libs/TransactionPreviewUtils.ts b/src/libs/TransactionPreviewUtils.ts index cdd98a05ab43..7a4751ecb6aa 100644 --- a/src/libs/TransactionPreviewUtils.ts +++ b/src/libs/TransactionPreviewUtils.ts @@ -187,6 +187,7 @@ function getTransactionPreviewTextAndTranslationPaths({ shouldShowRBR, violationMessage, reportActions, + currentUserEmail, }: { iouReport: OnyxEntry; transaction: OnyxEntry; @@ -197,6 +198,7 @@ function getTransactionPreviewTextAndTranslationPaths({ shouldShowRBR: boolean; violationMessage?: string; reportActions?: OnyxTypes.ReportActions; + currentUserEmail: string; }) { const isFetchingWaypoints = isFetchingWaypointsFromServer(transaction); const isTransactionOnHold = isOnHold(transaction); @@ -211,7 +213,7 @@ function getTransactionPreviewTextAndTranslationPaths({ const isTransactionScanning = isScanning(transaction); const hasFieldErrors = hasMissingSmartscanFields(transaction); const isPaidGroupPolicy = isPaidGroupPolicyUtil(iouReport); - const hasViolationsOfTypeNotice = hasNoticeTypeViolation(transaction, violations, true) && isPaidGroupPolicy; + const hasViolationsOfTypeNotice = hasNoticeTypeViolation(transaction, violations, currentUserEmail, true) && isPaidGroupPolicy; const hasActionWithErrors = hasActionWithErrorsForTransaction(iouReport?.reportID, transaction); const {amount: requestAmount, currency: requestCurrency} = transactionDetails; @@ -339,6 +341,7 @@ function createTransactionPreviewConditionals({ isBillSplit, isReportAPolicyExpenseChat, areThereDuplicates, + currentUserEmail, }: { iouReport: OnyxInputValue | undefined; transaction: OnyxEntry | undefined; @@ -348,6 +351,7 @@ function createTransactionPreviewConditionals({ isBillSplit: boolean; isReportAPolicyExpenseChat: boolean; areThereDuplicates: boolean; + currentUserEmail: string; }) { const {amount: requestAmount, comment: requestComment, merchant, tag, category} = transactionDetails; @@ -358,7 +362,7 @@ function createTransactionPreviewConditionals({ const isApproved = isReportApproved({report: iouReport}); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; - const hasViolationsOfTypeNotice = hasNoticeTypeViolation(transaction, violations) && iouReport && isPaidGroupPolicyUtil(iouReport); + const hasViolationsOfTypeNotice = hasNoticeTypeViolation(transaction, violations, currentUserEmail) && iouReport && isPaidGroupPolicyUtil(iouReport); const hasFieldErrors = hasMissingSmartscanFields(transaction); const isFetchingWaypoints = isFetchingWaypointsFromServer(transaction); @@ -378,8 +382,8 @@ function createTransactionPreviewConditionals({ isUnreportedAndHasInvalidDistanceRateTransaction(transaction) || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing hasViolationsOfTypeNotice || - hasWarningTypeViolation(transaction, violations) || - hasViolation(transaction, violations, true) || + hasWarningTypeViolation(transaction, violations, currentUserEmail) || + hasViolation(transaction, violations, currentUserEmail, true) || (isDistanceRequest(transaction) && violations?.some( (violation) => violation.name === CONST.VIOLATIONS.MODIFIED_AMOUNT && (violation.type === CONST.VIOLATION_TYPES.VIOLATION || violation.type === CONST.VIOLATION_TYPES.NOTICE), diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index da4424cb4280..07254ffc1781 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -145,16 +145,6 @@ Onyx.connect({ callback: (value) => (allTransactionViolations = value), }); -let deprecatedCurrentUserEmail = ''; -let deprecatedCurrentUserAccountID = -1; -Onyx.connect({ - key: ONYXKEYS.SESSION, - callback: (val) => { - deprecatedCurrentUserEmail = val?.email ?? ''; - deprecatedCurrentUserAccountID = val?.accountID ?? CONST.DEFAULT_NUMBER_ID; - }, -}); - function hasDistanceCustomUnit(transaction: OnyxEntry): boolean { const type = transaction?.comment?.type; const customUnitName = transaction?.comment?.customUnit?.name; @@ -868,7 +858,7 @@ function getAttendees(transaction: OnyxInputOrEntry): Attendee[] { const creatorAccountID = report?.ownerAccountID; if (creatorAccountID) { - const [creatorDetails] = getPersonalDetailsByIDs({accountIDs: [creatorAccountID], currentUserAccountID: deprecatedCurrentUserAccountID}); + const [creatorDetails] = getPersonalDetailsByIDs({accountIDs: [creatorAccountID]}); const creatorEmail = creatorDetails?.login ?? ''; const creatorDisplayName = creatorDetails?.displayName ?? creatorEmail; @@ -1071,7 +1061,7 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry, r function getTransactionViolations( transaction: OnyxEntry, transactionViolations: OnyxCollection, - currentUserEmail?: string, + currentUserEmail: string, ): TransactionViolations | undefined { if (!transaction || !transactionViolations) { return undefined; @@ -1102,8 +1092,12 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations | /** * Check if there is broken connection violation. */ -function hasBrokenConnectionViolation(transaction: Transaction | SearchTransaction, transactionViolations: OnyxCollection | undefined): boolean { - const violations = getTransactionViolations(transaction, transactionViolations); +function hasBrokenConnectionViolation( + transaction: Transaction | SearchTransaction, + transactionViolations: OnyxCollection | undefined, + currentUserEmail: string, +): boolean { + const violations = getTransactionViolations(transaction, transactionViolations, currentUserEmail); return !!violations?.find((violation) => isBrokenConnectionViolation(violation)); } @@ -1195,13 +1189,17 @@ function shouldShowViolation( /** * Check if there is pending rter violation in all transactionViolations with given transactionIDs. */ -function allHavePendingRTERViolation(transactions: OnyxEntry, transactionViolations: OnyxCollection | undefined): boolean { +function allHavePendingRTERViolation( + transactions: OnyxEntry, + transactionViolations: OnyxCollection | undefined, + currentUserEmail: string, +): boolean { if (!transactions) { return false; } const transactionsWithRTERViolations = transactions.map((transaction) => { - const filteredTransactionViolations = getTransactionViolations(transaction, transactionViolations); + const filteredTransactionViolations = getTransactionViolations(transaction, transactionViolations, currentUserEmail); return hasPendingRTERViolation(filteredTransactionViolations); }); return transactionsWithRTERViolations.length > 0 && transactionsWithRTERViolations.every((value) => value === true); @@ -1217,11 +1215,15 @@ function checkIfShouldShowMarkAsCashButton(hasRTERPendingViolation: boolean, sho /** * Check if there is any transaction without RTER violation within the given transactionIDs. */ -function hasAnyTransactionWithoutRTERViolation(transactions: Transaction[] | SearchTransaction[], transactionViolations: OnyxCollection | undefined): boolean { +function hasAnyTransactionWithoutRTERViolation( + transactions: Transaction[] | SearchTransaction[], + transactionViolations: OnyxCollection | undefined, + currentUserEmail: string, +): boolean { return ( transactions.length > 0 && transactions.some((transaction) => { - return !hasBrokenConnectionViolation(transaction, transactionViolations); + return !hasBrokenConnectionViolation(transaction, transactionViolations, currentUserEmail); }) ); } @@ -1309,7 +1311,7 @@ function getRecentTransactions(transactions: Record, size = 2): * Check if transaction has duplicatedTransaction violation. * @param transactionID - the transaction to check */ -function isDuplicate(transaction: OnyxEntry): boolean { +function isDuplicate(transaction: OnyxEntry, currentUserEmail: string): boolean { if (!transaction) { return false; } @@ -1317,7 +1319,7 @@ function isDuplicate(transaction: OnyxEntry): boolean { (violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, ); const hasDuplicatedTransactionViolation = !!duplicatedTransactionViolation; - const isDuplicatedTransactionViolationDismissed = isViolationDismissed(transaction, duplicatedTransactionViolation); + const isDuplicatedTransactionViolationDismissed = isViolationDismissed(transaction, duplicatedTransactionViolation, currentUserEmail); return hasDuplicatedTransactionViolation && !isDuplicatedTransactionViolationDismissed; } @@ -1336,11 +1338,11 @@ function isOnHold(transaction: OnyxEntry): boolean { /** * Checks if a violation is dismissed for the given transaction */ -function isViolationDismissed(transaction: OnyxEntry, violation: TransactionViolation | undefined, currentUserEmail?: string): boolean { +function isViolationDismissed(transaction: OnyxEntry, violation: TransactionViolation | undefined, currentUserEmail: string): boolean { if (!transaction || !violation) { return false; } - return !!transaction?.comment?.dismissedViolations?.[violation.name]?.[currentUserEmail ?? deprecatedCurrentUserEmail]; + return !!transaction?.comment?.dismissedViolations?.[violation.name]?.[currentUserEmail]; } /** @@ -1356,7 +1358,12 @@ function doesTransactionSupportViolations(transaction: Transaction | undefined): /** * Checks if any violations for the provided transaction are of type 'violation' */ -function hasViolation(transaction: Transaction | undefined, transactionViolations: TransactionViolation[] | OnyxCollection, showInReview?: boolean): boolean { +function hasViolation( + transaction: Transaction | undefined, + transactionViolations: TransactionViolation[] | OnyxCollection, + currentUserEmail: string, + showInReview?: boolean, +): boolean { if (!doesTransactionSupportViolations(transaction)) { return false; } @@ -1366,15 +1373,15 @@ function hasViolation(transaction: Transaction | undefined, transactionViolation (violation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && - !isViolationDismissed(transaction, violation), + !isViolationDismissed(transaction, violation, currentUserEmail), ); } -function hasDuplicateTransactions(iouReportID?: string, allReportTransactions?: SearchTransaction[]): boolean { +function hasDuplicateTransactions(currentUserEmail: string, iouReportID?: string, allReportTransactions?: SearchTransaction[]): boolean { const transactionsByIouReportID = getReportTransactions(iouReportID); const reportTransactions = allReportTransactions ?? transactionsByIouReportID; - return reportTransactions.length > 0 && reportTransactions.some((transaction) => isDuplicate(transaction)); + return reportTransactions.length > 0 && reportTransactions.some((transaction) => isDuplicate(transaction, currentUserEmail)); } /** @@ -1383,6 +1390,7 @@ function hasDuplicateTransactions(iouReportID?: string, allReportTransactions?: function hasNoticeTypeViolation( transaction: OnyxEntry, transactionViolations: TransactionViolation[] | OnyxCollection, + currentUserEmail: string, showInReview?: boolean, ): boolean { if (!doesTransactionSupportViolations(transaction)) { @@ -1394,7 +1402,7 @@ function hasNoticeTypeViolation( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && - !isViolationDismissed(transaction, violation), + !isViolationDismissed(transaction, violation, currentUserEmail), ); } @@ -1404,6 +1412,7 @@ function hasNoticeTypeViolation( function hasWarningTypeViolation( transaction: OnyxEntry, transactionViolations: TransactionViolation[] | OnyxCollection, + currentUserEmail: string, showInReview?: boolean, ): boolean { if (!doesTransactionSupportViolations(transaction)) { @@ -1415,7 +1424,7 @@ function hasWarningTypeViolation( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING && (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && - !isViolationDismissed(transaction, violation), + !isViolationDismissed(transaction, violation, currentUserEmail), ) ?? []; return warningTypeViolations.length > 0; @@ -1948,10 +1957,6 @@ function getAllSortedTransactions(iouReportID?: string): Array, originalTransaction: OnyxEntry): boolean { const {originalTransactionID, source, splits} = transaction?.comment ?? {}; @@ -2130,7 +2135,6 @@ export { isPerDiemRequest, isViolationDismissed, isBrokenConnectionViolation, - shouldShowRTERViolationMessage, isPartialTransaction, isPendingCardOrScanningTransaction, isScanning, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 907536704979..9afc5465aa8d 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -8730,7 +8730,7 @@ function deleteMoneyRequest( onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, value: { - hasOutstandingChildRequest: hasOutstandingChildRequest(chatReport, updatedIOUReport), + hasOutstandingChildRequest: hasOutstandingChildRequest(chatReport, updatedIOUReport, currentUserEmail), }, }); } @@ -8749,7 +8749,7 @@ function deleteMoneyRequest( onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, value: { - hasOutstandingChildRequest: hasOutstandingChildRequest(chatReport, iouReport?.reportID), + hasOutstandingChildRequest: hasOutstandingChildRequest(chatReport, iouReport?.reportID, currentUserEmail), iouReportID: null, ...optimisticLastReportData, }, @@ -9722,7 +9722,7 @@ function getPayMoneyRequestParams({ const optimisticChatReport = { ...chatReport, lastReadTime: DateUtils.getDBTime(), - hasOutstandingChildRequest: hasOutstandingChildRequest(chatReport, iouReport?.reportID), + hasOutstandingChildRequest: hasOutstandingChildRequest(chatReport, iouReport?.reportID, currentUserEmail), iouReportID: null, lastMessageText: getReportActionText(optimisticIOUReportAction), lastMessageHtml: getReportActionHtml(optimisticIOUReportAction), @@ -10134,12 +10134,13 @@ function canSubmitReport( transactions: OnyxTypes.Transaction[] | SearchTransaction[], allViolations: OnyxCollection | undefined, isReportArchived: boolean, + currentUserEmail: string, ) { const currentUserAccountID = getCurrentUserAccountID(); const isOpenExpenseReport = isOpenExpenseReportReportUtils(report); const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactions, allViolations); - const hasTransactionWithoutRTERViolation = hasAnyTransactionWithoutRTERViolation(transactions, allViolations); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactions, allViolations, currentUserEmail); + const hasTransactionWithoutRTERViolation = hasAnyTransactionWithoutRTERViolation(transactions, allViolations, currentUserEmail); const hasOnlyPendingCardOrScanFailTransactions = transactions.length > 0 && transactions.every((t) => isPendingCardOrScanningTransaction(t)); return ( @@ -10190,7 +10191,7 @@ function approveMoneyRequest( const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null; let total = expenseReport.total ?? 0; const hasHeldExpenses = hasHeldExpensesReportUtils(expenseReport.reportID); - const hasDuplicates = hasDuplicateTransactions(expenseReport.reportID); + const hasDuplicates = hasDuplicateTransactions(currentUserEmailParam, expenseReport.reportID); if (hasHeldExpenses && !full && !!expenseReport.unheldTotal) { total = expenseReport.unheldTotal; } @@ -10245,7 +10246,7 @@ function approveMoneyRequest( onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.chatReportID}`, value: { - hasOutstandingChildRequest: hasOutstandingChildRequest(chatReport, updatedExpenseReport), + hasOutstandingChildRequest: hasOutstandingChildRequest(chatReport, updatedExpenseReport, currentUserEmail), }, }; } @@ -10348,7 +10349,7 @@ function approveMoneyRequest( // Remove duplicates violations if we approve the report if (hasDuplicates) { - const transactions = getReportTransactions(expenseReport.reportID).filter((transaction) => isDuplicate(transaction)); + const transactions = getReportTransactions(expenseReport.reportID).filter((transaction) => isDuplicate(transaction, currentUserEmail)); if (!full) { transactions.filter((transaction) => !isOnHold(transaction)); } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 045131c1f2b2..0375fe7fe33e 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -4776,7 +4776,7 @@ function clearDeleteTransactionNavigateBackUrl() { } /** Deletes a report and un-reports all transactions on the report along with its reportActions, any linked reports and any linked IOU report actions. */ -function deleteAppReport(reportID: string | undefined) { +function deleteAppReport(reportID: string | undefined, currentUserEmail: string) { if (!reportID) { Log.warn('[Report] deleteReport called with no reportID'); return; @@ -5079,7 +5079,7 @@ function deleteAppReport(reportID: string | undefined) { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`, - value: {hasOutstandingChildRequest: hasOutstandingChildRequest(chatReport, report?.reportID)}, + value: {hasOutstandingChildRequest: hasOutstandingChildRequest(chatReport, report?.reportID, currentUserEmail)}, }); } diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index 9f6870c36033..bfe2c909c3fa 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -11,6 +11,7 @@ import ConfirmModal from '@components/ConfirmModal'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; import {useSearchContext} from '@components/Search/SearchContext'; import useAncestors from '@hooks/useAncestors'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDeleteTransactions from '@hooks/useDeleteTransactions'; import useDuplicateTransactionsAndViolations from '@hooks/useDuplicateTransactionsAndViolations'; import useGetIOUReportFromReportAction from '@hooks/useGetIOUReportFromReportAction'; @@ -71,6 +72,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro }); const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollViewContext); const instanceIDRef = useRef(''); + const {email} = useCurrentUserPersonalDetails(); const [isPopoverVisible, setIsPopoverVisible] = useState(false); const [isDeleteCommentConfirmModalVisible, setIsDeleteCommentConfirmModalVisible] = useState(false); @@ -363,7 +365,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro deleteTransactions([originalMessage.IOUTransactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash); } } else if (isReportPreviewAction(reportAction)) { - deleteAppReport(reportAction.childReportID); + deleteAppReport(reportAction.childReportID, email ?? ''); } else if (reportAction) { // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { diff --git a/tests/unit/IOUUtilsTest.ts b/tests/unit/IOUUtilsTest.ts index 0830c9ca0d53..2dc639f01e09 100644 --- a/tests/unit/IOUUtilsTest.ts +++ b/tests/unit/IOUUtilsTest.ts @@ -248,7 +248,7 @@ describe('hasRTERWithoutViolation', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithViolation}`, transactionWithViolation); await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithoutViolation}`, transactionWithoutViolation); - expect(hasAnyTransactionWithoutRTERViolation([transactionWithoutViolation, transactionWithViolation], violations)).toBe(true); + expect(hasAnyTransactionWithoutRTERViolation([transactionWithoutViolation, transactionWithViolation], violations, '')).toBe(true); }); test('Return false if there is no rter without violation in all transactionViolations with given transactionIDs.', async () => { @@ -337,7 +337,7 @@ describe('canSubmitReport', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithViolation}`, transactionWithViolation); await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithoutViolation}`, transactionWithoutViolation); - expect(canSubmitReport(expenseReport, fakePolicy, [transactionWithViolation, transactionWithoutViolation], violations, false)).toBe(true); + expect(canSubmitReport(expenseReport, fakePolicy, [transactionWithViolation, transactionWithoutViolation], violations, false, '')).toBe(true); }); test('Return true if report can be submitted after being reopened', async () => { diff --git a/tests/unit/ReportPrimaryActionUtilsTest.ts b/tests/unit/ReportPrimaryActionUtilsTest.ts index 435efee1c68a..c389f1b1b0fc 100644 --- a/tests/unit/ReportPrimaryActionUtilsTest.ts +++ b/tests/unit/ReportPrimaryActionUtilsTest.ts @@ -752,7 +752,7 @@ describe('isReviewDuplicatesAction', () => { } as TransactionViolation, ]); - expect(isReviewDuplicatesAction(report, [transaction])).toBe(true); + expect(isReviewDuplicatesAction(report, [transaction], CURRENT_USER_EMAIL)).toBe(true); }); it('should return false when report approver has no duplicated transactions', async () => { @@ -772,7 +772,7 @@ describe('isReviewDuplicatesAction', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TRANSACTION_ID}`, transaction); - expect(isReviewDuplicatesAction(report, [transaction])).toBe(false); + expect(isReviewDuplicatesAction(report, [transaction], CURRENT_USER_EMAIL)).toBe(false); }); it('should return false when current user is neither the report submitter nor approver', async () => { @@ -797,7 +797,7 @@ describe('isReviewDuplicatesAction', () => { } as TransactionViolation, ]); - expect(isReviewDuplicatesAction(report, [transaction])).toBe(false); + expect(isReviewDuplicatesAction(report, [transaction], CURRENT_USER_EMAIL)).toBe(false); }); }); diff --git a/tests/unit/TransactionUtilsTest.ts b/tests/unit/TransactionUtilsTest.ts index 0eb538d2ea26..d7cff4071e23 100644 --- a/tests/unit/TransactionUtilsTest.ts +++ b/tests/unit/TransactionUtilsTest.ts @@ -399,17 +399,6 @@ describe('TransactionUtils', () => { }); }); - describe('shouldShowRTERViolationMessage', () => { - it('should return true if transaction is receipt being scanned', () => { - const transaction = generateTransaction({ - receipt: { - state: CONST.IOU.RECEIPT_STATE.SCAN_READY, - }, - }); - expect(TransactionUtils.shouldShowRTERViolationMessage([transaction])).toBe(true); - }); - }); - describe('calculateTaxAmount', () => { it('returns 0 for undefined percentage', () => { const result = TransactionUtils.calculateTaxAmount(undefined, 10000, 'USD'); diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index 1139c9bb8081..eaa0806ed77c 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -625,7 +625,7 @@ describe('getViolations', () => { await Onyx.multiSet({...transactionCollectionDataSet}); // Should filter out the smartScanFailedViolation - const filteredViolations = getTransactionViolations(transaction, transactionViolationsCollection); + const filteredViolations = getTransactionViolations(transaction, transactionViolationsCollection, CARLOS_EMAIL); expect(filteredViolations).toEqual([duplicatedTransactionViolation, tagOutOfPolicyViolation]); }); @@ -643,7 +643,7 @@ describe('getViolations', () => { }; await Onyx.multiSet({...transactionCollectionDataSet}); - const hasWarningTypeViolationRes = hasWarningTypeViolation(transaction, transactionViolationsCollection); + const hasWarningTypeViolationRes = hasWarningTypeViolation(transaction, transactionViolationsCollection, CARLOS_EMAIL); expect(hasWarningTypeViolationRes).toBeTruthy(); }); }); From 1517c8a4cbbce325d40bee162b3a9a918be0d03e Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 12 Nov 2025 17:14:35 +0700 Subject: [PATCH 02/10] chore: eslint --- src/components/MoneyReportHeader.tsx | 4 ++-- src/components/MoneyRequestHeader.tsx | 4 ++-- .../TransactionPreview/TransactionPreviewContent.tsx | 4 ++-- src/libs/ReportUtils.ts | 4 ++-- src/libs/actions/IOU.ts | 6 +++--- src/libs/actions/Report.ts | 6 +++--- .../report/ContextMenu/PopoverReportActionContextMenu.tsx | 1 + tests/unit/IOUUtilsTest.ts | 8 ++++---- tests/unit/TransactionPreviewUtils.test.ts | 1 + tests/unit/TransactionUtilsTest.ts | 2 +- tests/unit/ViolationUtilsTest.ts | 4 ++-- 11 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 73fb5411c405..ee884b006a6b 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -298,7 +298,7 @@ function MoneyReportHeader({ // Check if there is pending rter violation in all transactionViolations with given transactionIDs. // wrapped in useMemo to avoid unnecessary re-renders and for better performance (array operation inside of function) - const hasAllPendingRTERViolations = useMemo(() => allHavePendingRTERViolation(transactions, violations, email ?? ''), [transactions, violations]); + const hasAllPendingRTERViolations = useMemo(() => allHavePendingRTERViolation(transactions, violations, email ?? ''), [transactions, violations, email]); // Check if user should see broken connection violation warning. const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationForMultipleTransactions(transactionIDs, moneyRequestReport, policy, violations); const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(moneyRequestReport?.reportID); @@ -1142,7 +1142,7 @@ function MoneyReportHeader({ Navigation.goBack(route.params?.backTo); // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { - deleteAppReport(moneyRequestReport?.reportID, email); + deleteAppReport(moneyRequestReport?.reportID, email ?? ''); }); }, }, diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 49dd550bd631..b753e1753c02 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -108,9 +108,9 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); - const {login: currentUserLogin} = useCurrentUserPersonalDetails(); + const {login: currentUserLogin, email} = useCurrentUserPersonalDetails(); const isOnHold = isOnHoldTransactionUtils(transaction); - const isDuplicate = isDuplicateTransactionUtils(transaction); + const isDuplicate = isDuplicateTransactionUtils(transaction, email ?? ''); const reportID = report?.reportID; const {removeTransaction, currentSearchHash} = useSearchContext(); const {isExpenseSplit} = getOriginalTransactionWithSplitInfo(transaction); diff --git a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx index 387e66fcd32e..d978b415e7d6 100644 --- a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx +++ b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx @@ -97,7 +97,7 @@ function TransactionPreviewContent({ isReportAPolicyExpenseChat, currentUserEmail: currentUserDetails.email ?? '', }), - [areThereDuplicates, transactionPreviewCommonArguments, isReportAPolicyExpenseChat], + [areThereDuplicates, transactionPreviewCommonArguments, isReportAPolicyExpenseChat, currentUserDetails.email], ); const {shouldShowRBR, shouldShowMerchant, shouldShowSplitShare, shouldShowTag, shouldShowCategory, shouldShowSkeleton, shouldShowDescription} = conditionals; @@ -116,7 +116,7 @@ function TransactionPreviewContent({ reportActions, currentUserEmail: currentUserDetails.email ?? '', }), - [transactionPreviewCommonArguments, shouldShowRBR, violationMessage, reportActions], + [transactionPreviewCommonArguments, shouldShowRBR, violationMessage, reportActions, currentUserDetails.email], ); const getTranslatedText = (item: TranslationPathOrText) => (item.translationPath ? translate(item.translationPath) : (item.text ?? '')); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9cf9aa716f3a..f1a908417087 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2675,7 +2675,7 @@ function isMoneyRequestReportEligibleForMerge(reportID: string, isAdmin: boolean return isManager && isExpenseReport(report) && isProcessingReport(report); } -function hasOutstandingChildRequest(chatReport: Report, iouReportOrID: OnyxEntry | string, currentUserEmail: string) { +function hasOutstandingChildRequest(chatReport: Report, iouReportOrID: OnyxEntry | string, currentUserEmailParam: string) { const reportActions = getAllReportActions(chatReport.reportID); // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 // eslint-disable-next-line @typescript-eslint/no-deprecated @@ -2696,7 +2696,7 @@ function hasOutstandingChildRequest(chatReport: Report, iouReportOrID: OnyxEntry return ( canIOUBePaid(iouReport, chatReport, policy, transactions) || canApproveIOU(iouReport, policy, transactions) || - canSubmitReport(iouReport, policy, transactions, undefined, false, currentUserEmail) + canSubmitReport(iouReport, policy, transactions, undefined, false, currentUserEmailParam) ); }); } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 9afc5465aa8d..dbebb9c0efba 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -10134,13 +10134,13 @@ function canSubmitReport( transactions: OnyxTypes.Transaction[] | SearchTransaction[], allViolations: OnyxCollection | undefined, isReportArchived: boolean, - currentUserEmail: string, + currentUserEmailParam: string, ) { const currentUserAccountID = getCurrentUserAccountID(); const isOpenExpenseReport = isOpenExpenseReportReportUtils(report); const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactions, allViolations, currentUserEmail); - const hasTransactionWithoutRTERViolation = hasAnyTransactionWithoutRTERViolation(transactions, allViolations, currentUserEmail); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactions, allViolations, currentUserEmailParam); + const hasTransactionWithoutRTERViolation = hasAnyTransactionWithoutRTERViolation(transactions, allViolations, currentUserEmailParam); const hasOnlyPendingCardOrScanFailTransactions = transactions.length > 0 && transactions.every((t) => isPendingCardOrScanningTransaction(t)); return ( diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 0375fe7fe33e..466a02d0783b 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -4776,7 +4776,7 @@ function clearDeleteTransactionNavigateBackUrl() { } /** Deletes a report and un-reports all transactions on the report along with its reportActions, any linked reports and any linked IOU report actions. */ -function deleteAppReport(reportID: string | undefined, currentUserEmail: string) { +function deleteAppReport(reportID: string | undefined, currentUserEmailParam: string) { if (!reportID) { Log.warn('[Report] deleteReport called with no reportID'); return; @@ -4796,7 +4796,7 @@ function deleteAppReport(reportID: string | undefined, currentUserEmail: string) const currentTime = DateUtils.getDBTime(); selfDMReport = buildOptimisticSelfDMReport(currentTime); selfDMReportID = selfDMReport.reportID; - createdAction = buildOptimisticCreatedReportAction(currentUserEmail ?? '', currentTime); + createdAction = buildOptimisticCreatedReportAction(currentUserEmailParam ?? '', currentTime); selfDMParameters = {reportID: selfDMReport.reportID, createdReportActionID: createdAction.reportActionID}; optimisticData.push( { @@ -5079,7 +5079,7 @@ function deleteAppReport(reportID: string | undefined, currentUserEmail: string) optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`, - value: {hasOutstandingChildRequest: hasOutstandingChildRequest(chatReport, report?.reportID, currentUserEmail)}, + value: {hasOutstandingChildRequest: hasOutstandingChildRequest(chatReport, report?.reportID, currentUserEmailParam)}, }); } diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index bfe2c909c3fa..8cc484a4c5db 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -386,6 +386,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro deleteTransactions, currentSearchHash, isOriginalReportArchived, + email, ]); const hideDeleteModal = () => { diff --git a/tests/unit/IOUUtilsTest.ts b/tests/unit/IOUUtilsTest.ts index 2dc639f01e09..4a43388d3a2a 100644 --- a/tests/unit/IOUUtilsTest.ts +++ b/tests/unit/IOUUtilsTest.ts @@ -277,7 +277,7 @@ describe('hasRTERWithoutViolation', () => { }; await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithViolation}`, transactionWithViolation); - expect(hasAnyTransactionWithoutRTERViolation([transactionWithViolation], violations)).toBe(false); + expect(hasAnyTransactionWithoutRTERViolation([transactionWithViolation], violations, '')).toBe(false); }); }); @@ -401,7 +401,7 @@ describe('canSubmitReport', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithViolation}`, transactionWithViolation); await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithoutViolation}`, transactionWithoutViolation); - expect(canSubmitReport(expenseReport, fakePolicy, [transactionWithViolation, transactionWithoutViolation], violations, false)).toBe(true); + expect(canSubmitReport(expenseReport, fakePolicy, [transactionWithViolation, transactionWithoutViolation], violations, false, '')).toBe(true); }); test('Return false if report can not be submitted', async () => { @@ -420,7 +420,7 @@ describe('canSubmitReport', () => { policyID: fakePolicy.id, }; - expect(canSubmitReport(expenseReport, fakePolicy, [], undefined, false)).toBe(false); + expect(canSubmitReport(expenseReport, fakePolicy, [], undefined, false, '')).toBe(false); }); it('returns false if the report is archived', async () => { @@ -445,7 +445,7 @@ describe('canSubmitReport', () => { // Simulate how components call canModifyTask() by using the hook useReportIsArchived() to see if the report is archived const {result: isReportArchived} = renderHook(() => useReportIsArchived(report?.reportID)); - expect(canSubmitReport(report, policy, [], undefined, isReportArchived.current)).toBe(false); + expect(canSubmitReport(report, policy, [], undefined, isReportArchived.current, '')).toBe(false); }); }); diff --git a/tests/unit/TransactionPreviewUtils.test.ts b/tests/unit/TransactionPreviewUtils.test.ts index 4331f657be60..199e5a83bde0 100644 --- a/tests/unit/TransactionPreviewUtils.test.ts +++ b/tests/unit/TransactionPreviewUtils.test.ts @@ -43,6 +43,7 @@ const basicProps = { shouldShowRBR: false, isReportAPolicyExpenseChat: false, areThereDuplicates: false, + currentUserEmail: '', }; describe('TransactionPreviewUtils', () => { diff --git a/tests/unit/TransactionUtilsTest.ts b/tests/unit/TransactionUtilsTest.ts index d7cff4071e23..3477edde866a 100644 --- a/tests/unit/TransactionUtilsTest.ts +++ b/tests/unit/TransactionUtilsTest.ts @@ -713,7 +713,7 @@ describe('TransactionUtils', () => { }, }); const violation = {type: CONST.VIOLATION_TYPES.VIOLATION, name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION}; - const result = TransactionUtils.isViolationDismissed(transaction, violation); + const result = TransactionUtils.isViolationDismissed(transaction, violation, CURRENT_USER_EMAIL); expect(result).toBe(true); }); }); diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index eaa0806ed77c..3025b68a4ee7 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -602,8 +602,8 @@ describe('getViolations', () => { await Onyx.multiSet({...transactionCollectionDataSet}); - const isSmartScanDismissed = isViolationDismissed(transaction, smartScanFailedViolation); - const isDuplicateViolationDismissed = isViolationDismissed(transaction, duplicatedTransactionViolation); + const isSmartScanDismissed = isViolationDismissed(transaction, smartScanFailedViolation, CARLOS_EMAIL); + const isDuplicateViolationDismissed = isViolationDismissed(transaction, duplicatedTransactionViolation, CARLOS_EMAIL); expect(isSmartScanDismissed).toBeTruthy(); expect(isDuplicateViolationDismissed).toBeFalsy(); From fc026d0e644d9fd0fcb9672466bf671ece49cfda Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 12 Nov 2025 17:27:08 +0700 Subject: [PATCH 03/10] udpate UTs --- tests/actions/ReportTest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 40ca45fbc422..ae22a1b8c8e0 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -2043,7 +2043,7 @@ describe('actions/Report', () => { }); // When deleting the expense report - Report.deleteAppReport(reportID); + Report.deleteAppReport(reportID, ''); await waitForBatchedUpdates(); // Then only the IOU action with type of CREATE and TRACK is moved to the self DM @@ -2139,7 +2139,7 @@ describe('actions/Report', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction); // When deleting the first expense report - Report.deleteAppReport(expenseReport1.reportID); + Report.deleteAppReport(expenseReport1.reportID, ''); await waitForBatchedUpdates(); const report = await new Promise>((resolve) => { From 505b58089f37b4af5886989676157400e085e401 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Thu, 13 Nov 2025 16:42:25 +0700 Subject: [PATCH 04/10] fix UTs --- src/libs/ReportPrimaryActionUtils.ts | 6 +++--- tests/unit/ReportPrimaryActionUtilsTest.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libs/ReportPrimaryActionUtils.ts b/src/libs/ReportPrimaryActionUtils.ts index 5b5b4c6bf4b4..e5c6dca5ef02 100644 --- a/src/libs/ReportPrimaryActionUtils.ts +++ b/src/libs/ReportPrimaryActionUtils.ts @@ -305,12 +305,12 @@ function isMarkAsResolvedAction(report?: Report, violations?: TransactionViolati return violations?.some((violation) => violation.name === CONST.VIOLATIONS.AUTO_REPORTED_REJECTED_EXPENSE); } -function isPrimaryMarkAsResolvedAction(report?: Report, reportTransactions?: Transaction[], violations?: OnyxCollection, policy?: Policy) { +function isPrimaryMarkAsResolvedAction(currentUserEmail: string, report?: Report, reportTransactions?: Transaction[], violations?: OnyxCollection, policy?: Policy) { if (!reportTransactions || reportTransactions.length !== 1) { return false; } - const transactionViolations = getTransactionViolations(reportTransactions.at(0), violations); + const transactionViolations = getTransactionViolations(reportTransactions.at(0), violations, currentUserEmail); return isExpenseReportUtils(report) && isMarkAsResolvedAction(report, transactionViolations, policy); } @@ -369,7 +369,7 @@ function getReportPrimaryAction(params: GetReportPrimaryActionParams): ValueOf { } as unknown as Transaction, ]; - const result = isPrimaryMarkAsResolvedAction(report, reportTransactions, violations, policy); + const result = isPrimaryMarkAsResolvedAction('', report, reportTransactions, violations, policy); expect(result).toBe(true); }); @@ -1224,7 +1224,7 @@ describe('getTransactionThreadPrimaryAction', () => { } as unknown as Transaction, ]; - const result = isPrimaryMarkAsResolvedAction(report, reportTransactions, violations, policy); + const result = isPrimaryMarkAsResolvedAction('', report, reportTransactions, violations, policy); expect(result).toBe(false); }); @@ -1254,7 +1254,7 @@ describe('getTransactionThreadPrimaryAction', () => { } as unknown as Transaction, ]; - const result = isPrimaryMarkAsResolvedAction(report, reportTransactions, violations, policy); + const result = isPrimaryMarkAsResolvedAction('', report, reportTransactions, violations, policy); expect(result).toBe(false); }); @@ -1284,7 +1284,7 @@ describe('getTransactionThreadPrimaryAction', () => { } as unknown as Transaction, ]; - const result = isPrimaryMarkAsResolvedAction(report, reportTransactions, violations, policy); + const result = isPrimaryMarkAsResolvedAction('', report, reportTransactions, violations, policy); expect(result).toBe(false); }); }); From 5671c8156d1077b0d24c95763587e206bfc1efaf Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 21 Nov 2025 15:56:37 +0700 Subject: [PATCH 05/10] conflict --- .../TransactionPreview/TransactionPreviewContent.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx index fcb4ceedaf54..a3d8198ec801 100644 --- a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx +++ b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx @@ -121,7 +121,6 @@ function TransactionPreviewContent({ reportActions, currentUserEmail: currentUserDetails.email ?? '', originalTransaction, - }), [transactionPreviewCommonArguments, shouldShowRBR, violationMessage, reportActions, currentUserDetails.email, originalTransaction], ); From f073e918ac96481f72eb7ea55a3a90900a616ae6 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 21 Nov 2025 16:40:09 +0700 Subject: [PATCH 06/10] update UTs --- src/libs/TransactionUtils/index.ts | 5 +++++ tests/unit/TransactionUtilsTest.ts | 11 ----------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 506e809136f8..0004d3353223 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1085,6 +1085,7 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry, r * Get all transaction violations of the transaction with given transactionID. */ function getTransactionViolations( + // eslint-disable-next-line @typescript-eslint/no-deprecated transaction: OnyxEntry, transactionViolations: OnyxCollection, currentUserEmail: string, @@ -1119,6 +1120,7 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations | * Check if there is broken connection violation. */ function hasBrokenConnectionViolation( + // eslint-disable-next-line @typescript-eslint/no-deprecated transaction: Transaction | SearchTransaction, transactionViolations: OnyxCollection | undefined, currentUserEmail: string, @@ -1238,6 +1240,7 @@ function shouldShowViolation( * Check if there is pending rter violation in all transactionViolations with given transactionIDs. */ function allHavePendingRTERViolation( + // eslint-disable-next-line @typescript-eslint/no-deprecated transactions: OnyxEntry, transactionViolations: OnyxCollection | undefined, currentUserEmail: string, @@ -1264,6 +1267,7 @@ function checkIfShouldShowMarkAsCashButton(hasRTERPendingViolation: boolean, sho * Check if there is any transaction without RTER violation within the given transactionIDs. */ function hasAnyTransactionWithoutRTERViolation( + // eslint-disable-next-line @typescript-eslint/no-deprecated transactions: Transaction[] | SearchTransaction[], transactionViolations: OnyxCollection | undefined, currentUserEmail: string, @@ -1427,6 +1431,7 @@ function hasViolation( ); } +// eslint-disable-next-line @typescript-eslint/no-deprecated function hasDuplicateTransactions(currentUserEmail: string, iouReportID?: string, allReportTransactions?: SearchTransaction[]): boolean { const transactionsByIouReportID = getReportTransactions(iouReportID); const reportTransactions = allReportTransactions ?? transactionsByIouReportID; diff --git a/tests/unit/TransactionUtilsTest.ts b/tests/unit/TransactionUtilsTest.ts index 16d882741561..2c370f88e791 100644 --- a/tests/unit/TransactionUtilsTest.ts +++ b/tests/unit/TransactionUtilsTest.ts @@ -452,17 +452,6 @@ describe('TransactionUtils', () => { }); }); - describe('shouldShowRTERViolationMessage', () => { - it('should return true if transaction is receipt being scanned', () => { - const transaction = generateTransaction({ - receipt: { - state: CONST.IOU.RECEIPT_STATE.SCAN_READY, - }, - }); - expect(TransactionUtils.shouldShowRTERViolationMessage([transaction])).toBe(true); - }); - }); - describe('calculateTaxAmount', () => { it('returns 0 for undefined percentage', () => { const result = TransactionUtils.calculateTaxAmount(undefined, 10000, 'USD'); From 5fc4baa6acd0891fd167c89819aea97a5c57766e Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Tue, 25 Nov 2025 23:44:30 +0700 Subject: [PATCH 07/10] resolve conflict --- src/libs/PersonalDetailsUtils.ts | 2 +- src/libs/TransactionUtils/index.ts | 14 +++++++++----- tests/unit/TransactionUtilsTest.ts | 11 ----------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 2d45d2929721..3d0e8c29d68e 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -117,7 +117,7 @@ function getPersonalDetailsByIDs({ personalDetailsParam = allPersonalDetails, }: { accountIDs: number[]; - currentUserAccountID?: number; + currentUserAccountID: number; shouldChangeUserDisplayName?: boolean; personalDetailsParam?: Partial; }): PersonalDetails[] { diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 25a17164431b..7f0abbe57f64 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -136,6 +136,14 @@ Onyx.connect({ callback: (value) => (allTransactionViolations = value), }); +let deprecatedCurrentUserAccountID = -1; +Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: (val) => { + deprecatedCurrentUserAccountID = val?.accountID ?? CONST.DEFAULT_NUMBER_ID; + }, +}); + function hasDistanceCustomUnit(transaction: OnyxEntry): boolean { const type = transaction?.comment?.type; const customUnitName = transaction?.comment?.customUnit?.name; @@ -1442,7 +1450,7 @@ function isViolationDismissed( const dismissedByEmails = Object.keys(violationDismissals); // Current user dismissed it themselves - if (dismissedByEmails.includes(currentUserEmail || deprecatedCurrentUserEmail)) { + if (dismissedByEmails.includes(currentUserEmail)) { return true; } @@ -2094,10 +2102,6 @@ function getAllSortedTransactions(iouReportID?: string): Array, policy: OnyxEntry) { - return transactions?.length === 1 && hasPendingUI(transactions?.at(0), getTransactionViolations(transactions?.at(0), allTransactionViolations, currentUserEmail, report, policy)); -} - function isExpenseSplit(transaction: OnyxEntry, originalTransaction: OnyxEntry): boolean { const {originalTransactionID, source, splits} = transaction?.comment ?? {}; diff --git a/tests/unit/TransactionUtilsTest.ts b/tests/unit/TransactionUtilsTest.ts index f5bf07ec05d7..4956ae43212a 100644 --- a/tests/unit/TransactionUtilsTest.ts +++ b/tests/unit/TransactionUtilsTest.ts @@ -453,17 +453,6 @@ describe('TransactionUtils', () => { }); }); - describe('shouldShowRTERViolationMessage', () => { - it('should return true if transaction is receipt being scanned', () => { - const transaction = generateTransaction({ - receipt: { - state: CONST.IOU.RECEIPT_STATE.SCAN_READY, - }, - }); - expect(TransactionUtils.shouldShowRTERViolationMessage([transaction], '', undefined, undefined)).toBe(true); - }); - }); - describe('calculateTaxAmount', () => { it('returns 0 for undefined percentage', () => { const result = TransactionUtils.calculateTaxAmount(undefined, 10000, 'USD'); From 2729e6381139e74e55d81cc11198c76148baa7f1 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Tue, 25 Nov 2025 23:52:06 +0700 Subject: [PATCH 08/10] minor update --- src/libs/TransactionPreviewUtils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/TransactionPreviewUtils.ts b/src/libs/TransactionPreviewUtils.ts index 55ab0b4f6884..d94ba971c188 100644 --- a/src/libs/TransactionPreviewUtils.ts +++ b/src/libs/TransactionPreviewUtils.ts @@ -10,7 +10,6 @@ import {abandonReviewDuplicateTransactions, setReviewDuplicatesKey} from './acti import {isCategoryMissing} from './CategoryUtils'; import {convertToDisplayString} from './CurrencyUtils'; import DateUtils from './DateUtils'; -import {getCurrentUserEmail} from './Network/NetworkStore'; import {getPolicy} from './PolicyUtils'; import {getOriginalMessage, isMessageDeleted, isMoneyRequestAction} from './ReportActionsUtils'; import { @@ -216,7 +215,6 @@ function getTransactionPreviewTextAndTranslationPaths({ const isTransactionScanning = isScanning(transaction); const hasFieldErrors = hasMissingSmartscanFields(transaction); const isPaidGroupPolicy = isPaidGroupPolicyUtil(iouReport); - const currentUserEmail = getCurrentUserEmail(); // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 // eslint-disable-next-line @typescript-eslint/no-deprecated From 876923df2a63734c253a127b7e21d45a2eb31241 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 26 Nov 2025 00:04:18 +0700 Subject: [PATCH 09/10] update the type --- src/libs/PersonalDetailsUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 3d0e8c29d68e..2d45d2929721 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -117,7 +117,7 @@ function getPersonalDetailsByIDs({ personalDetailsParam = allPersonalDetails, }: { accountIDs: number[]; - currentUserAccountID: number; + currentUserAccountID?: number; shouldChangeUserDisplayName?: boolean; personalDetailsParam?: Partial; }): PersonalDetails[] { From 6dba08c43bfafc9112ad5b789ed872120cd94090 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 26 Nov 2025 00:07:02 +0700 Subject: [PATCH 10/10] using currentUserEmailParam --- src/libs/actions/IOU.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 4fdf5e65dd4f..32f55edeae6b 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -260,7 +260,7 @@ import {buildAddMembersToWorkspaceOnyxData, buildUpdateWorkspaceMembersRoleOnyxD import {buildOptimisticRecentlyUsedCurrencies, buildPolicyData, generatePolicyID} from './Policy/Policy'; import {buildOptimisticPolicyRecentlyUsedTags, getPolicyTagsData} from './Policy/Tag'; import type {GuidedSetupData} from './Report'; -import {buildInviteToRoomOnyxData, completeOnboarding, getCurrentUserAccountID, getCurrentUserEmail, notifyNewAction, optimisticReportLastData} from './Report'; +import {buildInviteToRoomOnyxData, completeOnboarding, getCurrentUserAccountID, notifyNewAction, optimisticReportLastData} from './Report'; import {clearAllRelatedReportActionErrors} from './ReportActions'; import {sanitizeRecentWaypoints} from './Transaction'; import {removeDraftSplitTransaction, removeDraftTransaction, removeDraftTransactions} from './TransactionEdit'; @@ -10226,11 +10226,10 @@ function canSubmitReport( currentUserEmailParam: string, ) { const currentUserAccountID = getCurrentUserAccountID(); - const currentUserEmailValue = getCurrentUserEmail() ?? ''; const isOpenExpenseReport = isOpenExpenseReportReportUtils(report); const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactions, allViolations, currentUserEmail, report, policy); - const hasTransactionWithoutRTERViolation = hasAnyTransactionWithoutRTERViolation(transactions, allViolations, currentUserEmailValue, report, policy); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactions, allViolations, currentUserEmailParam, report, policy); + const hasTransactionWithoutRTERViolation = hasAnyTransactionWithoutRTERViolation(transactions, allViolations, currentUserEmailParam, report, policy); const hasOnlyPendingCardOrScanFailTransactions = transactions.length > 0 && transactions.every((t) => isPendingCardOrScanningTransaction(t)); return (