From 5149dc2385081468423a17318b3e3038915f8b1d Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 31 Dec 2025 01:04:55 +0800 Subject: [PATCH 1/9] gets the policy recently used tags from useOnyx --- src/libs/actions/IOU/index.ts | 69 ++- src/libs/actions/Policy/Tag.ts | 4 +- src/pages/Share/SubmitDetailsPage.tsx | 3 +- .../step/IOURequestStepCompanyInfo.tsx | 2 + .../step/IOURequestStepConfirmation.tsx | 12 + .../iou/request/step/IOURequestStepTag.tsx | 2 + tests/actions/IOUTest.ts | 476 +++++++++++++++++- 7 files changed, 525 insertions(+), 43 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index f4195718c507..c329cfeef806 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -466,6 +466,7 @@ type PerDiemExpenseTransactionParams = Omit; policyTagList?: OnyxEntry; + policyRecentlyUsedTags?: OnyxEntry; policyCategories?: OnyxEntry; policyRecentlyUsedCategories?: OnyxEntry; }; @@ -638,6 +639,7 @@ type CreateSplitsAndOnyxDataParams = { existingSplitChatReportID?: string; transactionParams: CreateSplitsTransactionParams; policyRecentlyUsedCategories?: OnyxEntry; + policyRecentlyUsedTags: OnyxEntry; isASAPSubmitBetaEnabled: boolean; transactionViolations: OnyxCollection; quickAction: OnyxEntry; @@ -776,6 +778,7 @@ type StartSplitBilActionParams = { taxAmount: number; shouldPlaySound?: boolean; policyRecentlyUsedCategories?: OnyxEntry; + policyRecentlyUsedTags: OnyxEntry; quickAction: OnyxEntry; policyRecentlyUsedCurrencies: string[]; }; @@ -894,16 +897,6 @@ Onyx.connect({ }, }); -// TODO: remove `allRecentlyUsedTags` from this file (https://github.com/Expensify/App/issues/71491) -// `allRecentlyUsedTags` was moved here temporarily from `src/libs/actions/Policy/Tag.ts` during the `Deprecate Onyx.connect` refactor. -// All uses of this variable should be replaced with `useOnyx`. -let allRecentlyUsedTags: OnyxCollection = {}; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS, - waitForCollectionCallback: true, - callback: (val) => (allRecentlyUsedTags = val), -}); - let allReports: OnyxCollection; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, @@ -983,14 +976,6 @@ Onyx.connect({ callback: (val) => (recentWaypoints = val ?? []), }); -/** - * @deprecated This function uses Onyx.connect and should be replaced with useOnyx for reactive data access. - * All usages of this function should be replaced with useOnyx hook in React components. - */ -function getPolicyRecentlyUsedTagsData(policyID: string | undefined) { - return allRecentlyUsedTags?.[`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`] ?? {}; -} - /** * @private * After finishing the action in RHP from the Inbox tab, besides dismissing the modal, we should open the report. @@ -3370,6 +3355,7 @@ type SendInvoiceOptions = { companyName?: string; companyWebsite?: string; policyRecentlyUsedCategories?: OnyxEntry; + policyRecentlyUsedTags?: OnyxEntry; }; /** Gathers all the data needed to create an invoice. */ @@ -3385,6 +3371,7 @@ function getSendInvoiceInformation({ companyName, companyWebsite, policyRecentlyUsedCategories, + policyRecentlyUsedTags, }: SendInvoiceOptions): SendInvoiceInformation { const {amount = 0, currency = '', created = '', merchant = '', category = '', tag = '', taxCode = '', taxAmount = 0, billable, comment, participants} = transaction ?? {}; const trimmedComment = (comment?.comment ?? '').trim(); @@ -3441,9 +3428,7 @@ function getSendInvoiceInformation({ const optimisticPolicyRecentlyUsedCategories = mergePolicyRecentlyUsedCategories(category, policyRecentlyUsedCategories); const optimisticPolicyRecentlyUsedTags = buildOptimisticPolicyRecentlyUsedTags({ policyTags: getPolicyTagsData(optimisticInvoiceReport.policyID), - // TODO: Replace getPolicyRecentlyUsedTagsData with useOnyx hook (https://github.com/Expensify/App/issues/71491) - // eslint-disable-next-line @typescript-eslint/no-deprecated - policyRecentlyUsedTags: getPolicyRecentlyUsedTagsData(optimisticInvoiceReport.policyID), + policyRecentlyUsedTags, transactionTags: tag, }); const optimisticRecentlyUsedCurrencies = mergePolicyRecentlyUsedCurrencies(currency, policyRecentlyUsedCurrencies); @@ -3551,7 +3536,7 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma transactionViolations, } = moneyRequestInformation; const {payeeAccountID = userAccountID, payeeEmail = currentUserEmail, participant} = participantParams; - const {policy, policyCategories, policyTagList, policyRecentlyUsedCategories} = policyParams; + const {policy, policyCategories, policyTagList, policyRecentlyUsedCategories, policyRecentlyUsedTags} = policyParams; const { attendees, amount, @@ -3694,9 +3679,7 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma const optimisticPolicyRecentlyUsedCategories = mergePolicyRecentlyUsedCategories(category, policyRecentlyUsedCategories); const optimisticPolicyRecentlyUsedTags = buildOptimisticPolicyRecentlyUsedTags({ policyTags: getPolicyTagsData(iouReport.policyID), - // TODO: Replace getPolicyRecentlyUsedTagsData with useOnyx hook (https://github.com/Expensify/App/issues/71491) - // eslint-disable-next-line @typescript-eslint/no-deprecated - policyRecentlyUsedTags: getPolicyRecentlyUsedTagsData(iouReport.policyID), + policyRecentlyUsedTags, transactionTags: tag, }); // eslint-disable-next-line @typescript-eslint/no-deprecated @@ -3938,7 +3921,7 @@ function getPerDiemExpenseInformation(perDiemExpenseInformation: PerDiemExpenseI policyRecentlyUsedCurrencies, } = perDiemExpenseInformation; const {payeeAccountID = userAccountID, payeeEmail = currentUserEmail, participant} = participantParams; - const {policy, policyCategories, policyTagList, policyRecentlyUsedCategories} = policyParams; + const {policy, policyCategories, policyTagList, policyRecentlyUsedCategories, policyRecentlyUsedTags} = policyParams; const {destinations: recentlyUsedDestinations} = recentlyUsedParams; const {comment = '', currency, created, category, tag, customUnit, billable, attendees, reimbursable} = transactionParams; @@ -4031,9 +4014,7 @@ function getPerDiemExpenseInformation(perDiemExpenseInformation: PerDiemExpenseI const optimisticPolicyRecentlyUsedCategories = mergePolicyRecentlyUsedCategories(category, policyRecentlyUsedCategories); const optimisticPolicyRecentlyUsedTags = buildOptimisticPolicyRecentlyUsedTags({ policyTags: getPolicyTagsData(iouReport.policyID), - // TODO: Replace getPolicyRecentlyUsedTagsData with useOnyx hook (https://github.com/Expensify/App/issues/71491) - // eslint-disable-next-line @typescript-eslint/no-deprecated - policyRecentlyUsedTags: getPolicyRecentlyUsedTagsData(iouReport.policyID), + policyRecentlyUsedTags, transactionTags: tag, }); const optimisticPolicyRecentlyUsedCurrencies = mergePolicyRecentlyUsedCurrencies(currency, policyRecentlyUsedCurrencies); @@ -4496,6 +4477,7 @@ type GetUpdateMoneyRequestParamsType = { transactionChanges: TransactionChanges; policy: OnyxEntry; policyTagList: OnyxTypes.OnyxInputOrEntry; + policyRecentlyUsedTags?: OnyxEntry; policyCategories: OnyxTypes.OnyxInputOrEntry; policyRecentlyUsedCategories?: OnyxEntry; violations?: OnyxEntry; @@ -4515,6 +4497,7 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U transactionChanges, policy, policyTagList, + policyRecentlyUsedTags, policyCategories, policyRecentlyUsedCategories, violations, @@ -4818,9 +4801,7 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U if (hasModifiedTag) { const optimisticPolicyRecentlyUsedTags = buildOptimisticPolicyRecentlyUsedTags({ policyTags: getPolicyTagsData(iouReport?.policyID), - // TODO: Replace getPolicyRecentlyUsedTagsData with useOnyx hook (https://github.com/Expensify/App/issues/71491) - // eslint-disable-next-line @typescript-eslint/no-deprecated - policyRecentlyUsedTags: getPolicyRecentlyUsedTagsData(iouReport?.policyID), + policyRecentlyUsedTags, transactionTags: transactionChanges.tag, }); if (!isEmptyObject(optimisticPolicyRecentlyUsedTags)) { @@ -5434,6 +5415,7 @@ function updateMoneyRequestTag( tag: string, policy: OnyxEntry, policyTagList: OnyxEntry, + policyRecentlyUsedTags: OnyxEntry, policyCategories: OnyxEntry, currentUserAccountIDParam: number, currentUserEmailParam: string, @@ -5449,6 +5431,7 @@ function updateMoneyRequestTag( transactionChanges, policy, policyTagList, + policyRecentlyUsedTags, policyCategories, hash, currentUserAccountIDParam, @@ -6713,6 +6696,7 @@ function sendInvoice({ companyName, companyWebsite, policyRecentlyUsedCategories, + policyRecentlyUsedTags, }: SendInvoiceOptions) { const parsedComment = getParsedComment(transaction?.comment?.comment?.trim() ?? ''); if (transaction?.comment) { @@ -6744,6 +6728,7 @@ function sendInvoice({ companyName, companyWebsite, policyRecentlyUsedCategories, + policyRecentlyUsedTags, }); const parameters: SendInvoiceParams = { @@ -7258,6 +7243,7 @@ function createSplitsAndOnyxData({ attendees, }, policyRecentlyUsedCategories, + policyRecentlyUsedTags, isASAPSubmitBetaEnabled, transactionViolations, quickAction, @@ -7635,9 +7621,7 @@ function createSplitsAndOnyxData({ const optimisticPolicyRecentlyUsedTags = isPolicyExpenseChat ? buildOptimisticPolicyRecentlyUsedTags({ policyTags: getPolicyTagsData(participant.policyID), - // TODO: Replace getPolicyRecentlyUsedTagsData with useOnyx hook (https://github.com/Expensify/App/issues/71491) - // eslint-disable-next-line @typescript-eslint/no-deprecated - policyRecentlyUsedTags: getPolicyRecentlyUsedTagsData(participant.policyID), + policyRecentlyUsedTags, transactionTags: tag, }) : {}; @@ -7749,6 +7733,7 @@ type SplitBillActionsParams = { taxAmount?: number; isRetry?: boolean; policyRecentlyUsedCategories?: OnyxEntry; + policyRecentlyUsedTags: OnyxEntry; isASAPSubmitBetaEnabled: boolean; transactionViolations: OnyxCollection; quickAction: OnyxEntry; @@ -7782,6 +7767,7 @@ function splitBill({ transactionViolations, quickAction, policyRecentlyUsedCurrencies, + policyRecentlyUsedTags, }: SplitBillActionsParams) { const parsedComment = getParsedComment(comment); const {splitData, splits, onyxData} = createSplitsAndOnyxData({ @@ -7805,6 +7791,7 @@ function splitBill({ taxAmount, }, policyRecentlyUsedCategories, + policyRecentlyUsedTags, isASAPSubmitBetaEnabled, transactionViolations, quickAction, @@ -7865,6 +7852,7 @@ function splitBillAndOpenReport({ taxAmount = 0, existingSplitChatReportID, policyRecentlyUsedCategories, + policyRecentlyUsedTags, isASAPSubmitBetaEnabled, transactionViolations, quickAction, @@ -7893,6 +7881,7 @@ function splitBillAndOpenReport({ taxAmount, }, policyRecentlyUsedCategories, + policyRecentlyUsedTags, transactionViolations, quickAction, policyRecentlyUsedCurrencies, @@ -7950,6 +7939,7 @@ function startSplitBill({ taxAmount = 0, shouldPlaySound = true, policyRecentlyUsedCategories, + policyRecentlyUsedTags, quickAction, policyRecentlyUsedCurrencies, }: StartSplitBilActionParams) { @@ -8122,6 +8112,7 @@ function startSplitBill({ taxAmount, quickAction, policyRecentlyUsedCurrencies, + policyRecentlyUsedTags, }; if (existingSplitChatReport) { @@ -8216,9 +8207,7 @@ function startSplitBill({ const optimisticPolicyRecentlyUsedCategories = mergePolicyRecentlyUsedCategories(category, policyRecentlyUsedCategories); const optimisticPolicyRecentlyUsedTags = buildOptimisticPolicyRecentlyUsedTags({ policyTags: getPolicyTagsData(participant.policyID), - // TODO: Replace getPolicyRecentlyUsedTagsData with useOnyx hook (https://github.com/Expensify/App/issues/71491) - // eslint-disable-next-line @typescript-eslint/no-deprecated - policyRecentlyUsedTags: getPolicyRecentlyUsedTagsData(participant.policyID), + policyRecentlyUsedTags, transactionTags: tag, }); const optimisticRecentlyUsedCurrencies = mergePolicyRecentlyUsedCurrencies(currency, policyRecentlyUsedCurrencies); @@ -8615,7 +8604,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest quickAction, policyRecentlyUsedCurrencies, } = distanceRequestInformation; - const {policy, policyCategories, policyTagList, policyRecentlyUsedCategories} = policyParams; + const {policy, policyCategories, policyTagList, policyRecentlyUsedCategories, policyRecentlyUsedTags} = policyParams; const parsedComment = getParsedComment(transactionParams.comment); transactionParams.comment = parsedComment; const { @@ -8680,6 +8669,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest attendees, }, policyRecentlyUsedCategories, + policyRecentlyUsedTags, isASAPSubmitBetaEnabled, transactionViolations, quickAction, @@ -8737,6 +8727,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest policyCategories, policyTagList, policyRecentlyUsedCategories, + policyRecentlyUsedTags, }, transactionParams: { amount, diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts index bcb7edf7a2fb..636f9cff8ff4 100644 --- a/src/libs/actions/Policy/Tag.ts +++ b/src/libs/actions/Policy/Tag.ts @@ -65,7 +65,7 @@ function openPolicyTagsPage(policyID: string) { type BuildOptimisticPolicyRecentlyUsedTagsProps = { policyTags: PolicyTagLists; - policyRecentlyUsedTags: RecentlyUsedTags; + policyRecentlyUsedTags: OnyxEntry; transactionTags?: string; }; @@ -83,7 +83,7 @@ function buildOptimisticPolicyRecentlyUsedTags({policyTags, policyRecentlyUsedTa } const tagListKey = policyTagKeys.at(index) ?? ''; - newOptimisticPolicyRecentlyUsedTags[tagListKey] = [...new Set([tag, ...(policyRecentlyUsedTags[tagListKey] ?? [])])]; + newOptimisticPolicyRecentlyUsedTags[tagListKey] = [...new Set([tag, ...(policyRecentlyUsedTags?.[tagListKey] ?? [])])]; } return newOptimisticPolicyRecentlyUsedTags; diff --git a/src/pages/Share/SubmitDetailsPage.tsx b/src/pages/Share/SubmitDetailsPage.tsx index 0765cb0843fa..677a80104b26 100644 --- a/src/pages/Share/SubmitDetailsPage.tsx +++ b/src/pages/Share/SubmitDetailsPage.tsx @@ -58,6 +58,7 @@ function SubmitDetailsPage({ const [currentDate] = useOnyx(ONYXKEYS.CURRENT_DATE, {canBeMissing: true}); const [validFilesToUpload] = useOnyx(ONYXKEYS.VALIDATED_FILE_OBJECT, {canBeMissing: true}); const [policyRecentlyUsedCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${getIOURequestPolicyID(transaction, report)}`, {canBeMissing: true}); + const [policyRecentlyUsedTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${getIOURequestPolicyID(transaction, report)}`, {canBeMissing: true}); const [currentAttachment] = useOnyx(ONYXKEYS.SHARE_TEMP_FILE, {canBeMissing: true}); const shouldUsePreValidatedFile = shouldValidateFile(currentAttachment); const isLinkedTrackedExpenseReportArchived = useReportIsArchived(transaction?.linkedTrackedExpenseReportID); @@ -147,7 +148,7 @@ function SubmitDetailsPage({ requestMoney({ report, participantParams: {payeeEmail: currentUserPersonalDetails.login, payeeAccountID: currentUserPersonalDetails.accountID, participant}, - policyParams: {policy, policyTagList: policyTags, policyCategories, policyRecentlyUsedCategories}, + policyParams: {policy, policyTagList: policyTags, policyCategories, policyRecentlyUsedCategories, policyRecentlyUsedTags}, gpsPoint, action: CONST.IOU.TYPE.CREATE, transactionParams: { diff --git a/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx b/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx index 66da33a0104c..1cb6aa303fa0 100644 --- a/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx +++ b/src/pages/iou/request/step/IOURequestStepCompanyInfo.tsx @@ -45,6 +45,7 @@ function IOURequestStepCompanyInfo({route, report, transaction}: IOURequestStepC const policy = usePolicy(policyID); const [policyRecentlyUsedCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${policyID}`, {canBeMissing: true}); const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, {canBeMissing: true}); + const [policyRecentlyUsedTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, {canBeMissing: true}); const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true}); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); @@ -86,6 +87,7 @@ function IOURequestStepCompanyInfo({route, report, transaction}: IOURequestStepC companyName: values.companyName, companyWebsite, policyRecentlyUsedCategories, + policyRecentlyUsedTags, }); }; diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 2b39a1ab2e5f..89c38b310556 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -196,6 +196,7 @@ function IOURequestStepConfirmation({ const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${draftPolicyID}`, {canBeMissing: true}); const [policyRecentlyUsedCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${realPolicyID}`, {canBeMissing: true}); const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${realPolicyID}`, {canBeMissing: true}); + const [policyRecentlyUsedTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${realPolicyID}`, {canBeMissing: true}); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION, {canBeMissing: true}); @@ -577,6 +578,7 @@ function IOURequestStepConfirmation({ policyTagList: policyTags, policyCategories, policyRecentlyUsedCategories, + policyRecentlyUsedTags, }, gpsPoint, action, @@ -625,6 +627,7 @@ function IOURequestStepConfirmation({ currentUserPersonalDetails.accountID, policy, policyTags, + policyRecentlyUsedTags, policyCategories, policyRecentlyUsedCategories, action, @@ -664,6 +667,7 @@ function IOURequestStepConfirmation({ policyParams: { policy, policyTagList: policyTags, + policyRecentlyUsedTags, policyCategories, policyRecentlyUsedCategories: policyRecentlyUsedCategoriesParam, }, @@ -700,6 +704,7 @@ function IOURequestStepConfirmation({ isASAPSubmitBetaEnabled, hasViolations, policyRecentlyUsedCurrencies, + policyRecentlyUsedTags, ], ); @@ -800,6 +805,7 @@ function IOURequestStepConfirmation({ policyCategories, policyTagList: policyTags, policyRecentlyUsedCategories, + policyRecentlyUsedTags, }, transactionParams: { amount: transaction.amount, @@ -837,6 +843,7 @@ function IOURequestStepConfirmation({ policyCategories, policyTags, policyRecentlyUsedCategories, + policyRecentlyUsedTags, isManualDistanceRequest, transactionTaxCode, transactionTaxAmount, @@ -910,6 +917,7 @@ function IOURequestStepConfirmation({ taxAmount: transactionTaxAmount, shouldPlaySound: index === transactions.length - 1, policyRecentlyUsedCategories, + policyRecentlyUsedTags, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], }); @@ -941,6 +949,7 @@ function IOURequestStepConfirmation({ taxCode: transactionTaxCode, taxAmount: transactionTaxAmount, policyRecentlyUsedCategories, + policyRecentlyUsedTags, isASAPSubmitBetaEnabled, transactionViolations, quickAction, @@ -971,6 +980,7 @@ function IOURequestStepConfirmation({ taxCode: transactionTaxCode, taxAmount: transactionTaxAmount, policyRecentlyUsedCategories, + policyRecentlyUsedTags, isASAPSubmitBetaEnabled, transactionViolations, quickAction, @@ -994,6 +1004,7 @@ function IOURequestStepConfirmation({ policyTagList: policyTags, policyCategories, policyRecentlyUsedCategories, + policyRecentlyUsedTags, }); return; } @@ -1098,6 +1109,7 @@ function IOURequestStepConfirmation({ transactionTaxCode, transactionTaxAmount, policyRecentlyUsedCategories, + policyRecentlyUsedTags, quickAction, isASAPSubmitBetaEnabled, transactionViolations, diff --git a/src/pages/iou/request/step/IOURequestStepTag.tsx b/src/pages/iou/request/step/IOURequestStepTag.tsx index cd260d1fa086..4994f870c7dc 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.tsx +++ b/src/pages/iou/request/step/IOURequestStepTag.tsx @@ -51,6 +51,7 @@ function IOURequestStepTag({ const policy = isUnreportedExpense || isCreatingTrackExpense ? policyForMovingExpenses : reportPolicy; const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, {canBeMissing: false}); const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: false}); + const [policyRecentlyUsedTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, {canBeMissing: true}); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const currentUserAccountIDParam = currentUserPersonalDetails.accountID; const currentUserEmailParam = currentUserPersonalDetails.login ?? ''; @@ -138,6 +139,7 @@ function IOURequestStepTag({ updatedTag, policy, policyTags, + policyRecentlyUsedTags, policyCategories, currentUserAccountIDParam, currentUserEmailParam, diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index e21136c21c11..fe660275ae2e 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -45,12 +45,14 @@ import { shouldOptimisticallyUpdateSearch, splitBill, startSplitBill, + submitPerDiemExpense, submitReport, trackExpense, unholdRequest, updateMoneyRequestAmountAndCurrency, updateMoneyRequestAttendees, updateMoneyRequestCategory, + updateMoneyRequestTag, updateSplitExpenseAmountField, updateSplitTransactionsFromSplitExpensesFlow, } from '@libs/actions/IOU'; @@ -86,7 +88,7 @@ import * as API from '@src/libs/API'; import DateUtils from '@src/libs/DateUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {OriginalMessageIOU, PersonalDetailsList, Policy, PolicyTagLists, RecentlyUsedCategories, Report, ReportNameValuePairs, SearchResults} from '@src/types/onyx'; +import type {OriginalMessageIOU, PersonalDetailsList, Policy, PolicyTagLists, RecentlyUsedCategories, RecentlyUsedTags, Report, ReportNameValuePairs, SearchResults} from '@src/types/onyx'; import type {Accountant, Attendee} from '@src/types/onyx/IOU'; import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails'; import type {Participant, ReportCollectionDataSet} from '@src/types/onyx/Report'; @@ -2186,6 +2188,69 @@ describe('actions/IOU', () => { expect(newNonReimbursableTotal).toBe(-100); }); + + it('should update policyRecentlyUsedTags when tag is provided', async () => { + // Given a policy recently used tags + const transactionTag = 'new tag'; + const policyID = 'A'; + const tagName = 'Tag'; + const expenseReport: Report = { + ...createRandomReport(0, undefined), + type: CONST.REPORT.TYPE.EXPENSE, + nonReimbursableTotal: 0, + total: 0, + ownerAccountID: RORY_ACCOUNT_ID, + currency: CONST.CURRENCY.USD, + policyID, + }; + const policyRecentlyUsedTags: OnyxEntry = { + [tagName]: ['old tag'], + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { + [tagName]: {name: tagName}, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags) + + // When requesting money + requestMoney({ + report: expenseReport, + existingIOUReport: expenseReport, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {reportID: '1', isPolicyExpenseChat: true}, + }, + policyParams: {policyRecentlyUsedTags}, + transactionParams: { + amount: 100, + attendees: [], + currency: CONST.CURRENCY.USD, + created: '', + merchant: '', + comment: '', + tag: transactionTag, + }, + shouldGenerateTransactionThreadReport: true, + isASAPSubmitBetaEnabled: false, + transactionViolations: {}, + currentUserAccountIDParam: currentUserPersonalDetails.accountID, + currentUserEmailParam: currentUserPersonalDetails.login ?? '', + }); + waitForBatchedUpdates(); + + // Then the transaction tag should be added to the recently used tags collection + const newPolicyRecentlyUsedTags: RecentlyUsedTags = await new Promise((resolve) => { + const connection = Onyx.connectWithoutView({ + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, + callback: (recentlyUsedTags) => { + resolve(recentlyUsedTags ?? {}); + Onyx.disconnect(connection); + }, + }); + }); + expect(newPolicyRecentlyUsedTags[tagName].length).toBe(2); + expect(newPolicyRecentlyUsedTags[tagName].at(0)).toBe(transactionTag); + }); }); describe('createDistanceRequest', () => { @@ -2308,7 +2373,126 @@ describe('actions/IOU', () => { const recentlyUsedCurrencies = await getOnyxValue(ONYXKEYS.RECENTLY_USED_CURRENCIES); expect(recentlyUsedCurrencies).toEqual([CONST.CURRENCY.GBP, ...initialCurrencies]); }); + + it('should update policyRecentlyUsedTags when tag is provided', async () => { + // Given a policy recently used tags + const transactionTag = 'new tag'; + const policyID = 'A'; + const tagName = 'Tag'; + const policyRecentlyUsedTags: OnyxEntry = { + [tagName]: ['old tag'], + }; + const iouReport = { + reportID: '3', + policyID, + type: CONST.REPORT.TYPE.EXPENSE, + ownerAccountID: currentUserPersonalDetails.accountID, + } + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, iouReport); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, {reportID: iouReport.reportID, policyID}) + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { + [tagName]: {name: tagName}, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags) + + // When creating a distance request + createDistanceRequest({ + report: iouReport, + participants: [{accountID: CARLOS_ACCOUNT_ID, login: CARLOS_EMAIL}], + currentUserLogin: RORY_EMAIL, + currentUserAccountID: RORY_ACCOUNT_ID, + transactionParams: { + amount: 1, + attendees: [], + currency: CONST.CURRENCY.GBP, + created: '', + merchant: '', + comment: '', + validWaypoints: {}, + tag: transactionTag, + }, + policyParams: {policyRecentlyUsedTags}, + isASAPSubmitBetaEnabled: false, + transactionViolations: {}, + quickAction: undefined, + policyRecentlyUsedCurrencies: [], + }); + waitForBatchedUpdates(); + + // Then the transaction tag should be added to the recently used tags collection + const newPolicyRecentlyUsedTags: RecentlyUsedTags = await new Promise((resolve) => { + const connection = Onyx.connectWithoutView({ + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, + callback: (recentlyUsedTags) => { + resolve(recentlyUsedTags ?? {}); + Onyx.disconnect(connection); + }, + }); + }); + expect(newPolicyRecentlyUsedTags[tagName].length).toBe(2); + expect(newPolicyRecentlyUsedTags[tagName].at(0)).toBe(transactionTag); + }); + + it('should update policyRecentlyUsedTags when splitting with tag is provided', async () => { + // Given a policy recently used tags + const transactionTag = 'new tag'; + const policyID = 'A'; + const tagName = 'Tag'; + const policyRecentlyUsedTags: OnyxEntry = { + [tagName]: ['old tag'], + }; + const policyExpenseChat = { + reportID: '2', + policyID, + isPolicyExpenseChat: true, + isOwnPolicyExpenseChat: true, + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`, policyExpenseChat); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { + [tagName]: {name: tagName}, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags) + + // When creating a split distance request + createDistanceRequest({ + report: policyExpenseChat, + iouType: CONST.IOU.TYPE.SPLIT, + participants: [policyExpenseChat], + currentUserLogin: RORY_EMAIL, + currentUserAccountID: RORY_ACCOUNT_ID, + transactionParams: { + amount: 1, + attendees: [], + currency: CONST.CURRENCY.GBP, + created: '', + merchant: '', + comment: '', + validWaypoints: {}, + tag: transactionTag, + }, + policyParams: {policyRecentlyUsedTags}, + isASAPSubmitBetaEnabled: false, + transactionViolations: {}, + quickAction: undefined, + policyRecentlyUsedCurrencies: [], + }); + waitForBatchedUpdates(); + + // Then the transaction tag should be added to the recently used tags collection + const newPolicyRecentlyUsedTags: RecentlyUsedTags = await new Promise((resolve) => { + const connection = Onyx.connectWithoutView({ + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, + callback: (recentlyUsedTags) => { + resolve(recentlyUsedTags ?? {}); + Onyx.disconnect(connection); + }, + }); + }); + expect(newPolicyRecentlyUsedTags[tagName].length).toBe(2); + expect(newPolicyRecentlyUsedTags[tagName].at(0)).toBe(transactionTag); + }); }); + describe('split expense', () => { it('creates and updates new chats and IOUs as needed', () => { jest.setTimeout(10 * 1000); @@ -2480,6 +2664,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], + policyRecentlyUsedTags: undefined, }, ); return waitForBatchedUpdates(); @@ -2816,6 +3001,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], + policyRecentlyUsedTags: undefined, }); await waitForBatchedUpdates(); @@ -2862,6 +3048,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], + policyRecentlyUsedTags: undefined, }); await waitForBatchedUpdates(); @@ -2882,6 +3069,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: {action: CONST.QUICK_ACTIONS.SEND_MONEY, chatReportID: '456'}, policyRecentlyUsedCurrencies: [], + policyRecentlyUsedTags: undefined, }); await waitForBatchedUpdates(); @@ -2909,6 +3097,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: initialCurrencies, + policyRecentlyUsedTags: undefined, }); await waitForBatchedUpdates(); @@ -2941,6 +3130,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], + policyRecentlyUsedTags: undefined, }); await waitForBatchedUpdates(); @@ -2959,6 +3149,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], + policyRecentlyUsedTags: undefined, }); await waitForBatchedUpdates(); @@ -3016,6 +3207,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], + policyRecentlyUsedTags: undefined, }); await waitForBatchedUpdates(); @@ -3063,6 +3255,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], + policyRecentlyUsedTags: undefined, }); await waitForBatchedUpdates(); @@ -3080,6 +3273,66 @@ describe('actions/IOU', () => { expect(report?.participants?.[RORY_ACCOUNT_ID].notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS); }); + it('should update the policyRecentlyUsedTags when tag is provided', async () => { + // Given a policy recently used tags + const policyID = 'A'; + const transactionTag = 'new tag'; + const tagName = 'Tag'; + const policyRecentlyUsedTags: OnyxEntry = { + [tagName]: ['old tag'], + }; + + const policyExpenseChat = { + reportID: '2', + policyID, + isPolicyExpenseChat: true, + isOwnPolicyExpenseChat: true, + }; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`, policyExpenseChat); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { + [tagName]: {name: tagName}, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags); + + // When doing a split bill + splitBill({ + participants: [{isPolicyExpenseChat: true, policyID}], + existingSplitChatReportID: policyExpenseChat.reportID, + currentUserLogin: currentUserPersonalDetails.login ?? '', + currentUserAccountID: currentUserPersonalDetails.accountID, + amount: 1, + created: '', + comment: '', + merchant: '', + transactionViolations: undefined, + category: undefined, + tag: transactionTag, + currency: CONST.CURRENCY.USD, + taxCode: '', + taxAmount: 0, + isASAPSubmitBetaEnabled: false, + policyRecentlyUsedTags, + quickAction: {}, + policyRecentlyUsedCurrencies: [], + }); + + waitForBatchedUpdates(); + + // Then the transaction tag should be added to the recently used tags collection + const newPolicyRecentlyUsedTags: RecentlyUsedTags = await new Promise((resolve) => { + const connection = Onyx.connectWithoutView({ + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, + callback: (recentlyUsedTags) => { + resolve(recentlyUsedTags ?? {}); + Onyx.disconnect(connection); + }, + }); + }); + expect(newPolicyRecentlyUsedTags[tagName].length).toBe(2); + expect(newPolicyRecentlyUsedTags[tagName].at(0)).toBe(transactionTag); + }); + it('the description should not be parsed again after completing the scan split bill without changing the description', async () => { const reportID = '1'; await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { @@ -3107,6 +3360,7 @@ describe('actions/IOU', () => { taxAmount: 0, quickAction: undefined, policyRecentlyUsedCurrencies: [], + policyRecentlyUsedTags: undefined, }); await waitForBatchedUpdates(); @@ -3140,6 +3394,57 @@ describe('actions/IOU', () => { }); }); + describe('startSplitBill', () => { + it('should update the policyRecentlyUsedTags when tag is provided', async () => { + // Given a policy recently used tags + const policyID = 'A'; + const transactionTag = 'new tag'; + const tagName = 'Tag'; + const policyRecentlyUsedTags: OnyxEntry = { + [tagName]: ['old tag'], + }; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { + [tagName]: {name: tagName}, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags); + + // When doing a split bill with a receipt + startSplitBill({ + participants: [ + {isPolicyExpenseChat: true, policyID} + ], + currentUserLogin: currentUserPersonalDetails.login ?? '', + currentUserAccountID: currentUserPersonalDetails.accountID, + comment: '', + receipt: {}, + category: undefined, + tag: transactionTag, + currency: CONST.CURRENCY.USD, + taxCode: '', + taxAmount: 0, + policyRecentlyUsedTags, + quickAction: {}, + policyRecentlyUsedCurrencies: [], + }); + + waitForBatchedUpdates(); + + // Then the transaction tag should be added to the recently used tags collection + const newPolicyRecentlyUsedTags: RecentlyUsedTags = await new Promise((resolve) => { + const connection = Onyx.connectWithoutView({ + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, + callback: (recentlyUsedTags) => { + resolve(recentlyUsedTags ?? {}); + Onyx.disconnect(connection); + }, + }); + }); + expect(newPolicyRecentlyUsedTags[tagName].length).toBe(2); + expect(newPolicyRecentlyUsedTags[tagName].at(0)).toBe(transactionTag); + }); + }); + describe('updateSplitTransactionsFromSplitExpensesFlow', () => { it('should delete the original transaction thread report', async () => { const expenseReport: Report = { @@ -6100,6 +6405,7 @@ describe('actions/IOU', () => { policy, companyName, companyWebsite, + policyRecentlyUsedTags: undefined, }); // Then a new invoice chat is created instead of incorrectly using the invoice chat which has been converted from individual to business @@ -6127,6 +6433,7 @@ describe('actions/IOU', () => { currentUserAccountID: 1, transaction, policyRecentlyUsedCurrencies: initialCurrencies, + policyRecentlyUsedTags: undefined, }); mockFetch?.fail?.(); @@ -6166,6 +6473,7 @@ describe('actions/IOU', () => { transaction, policyRecentlyUsedCurrencies: [], policyRecentlyUsedCategories, + policyRecentlyUsedTags: undefined, }); // Then onyxData should be passed to API.write @@ -6179,6 +6487,49 @@ describe('actions/IOU', () => { writeSpy.mockRestore(); }); + + it('should update policyRecentlyUsedTags when tag is provided', async () => { + // Given a transaction with a tag + const policyID = 'A'; + const transactionTag = 'new tag'; + const transaction: Transaction = { + ...createRandomTransaction(1), + tag: transactionTag, + participants: [ + {isSender: true, policyID}, + ], + }; + const tagName = 'Tag'; + const policyRecentlyUsedTags: OnyxEntry = { + [tagName]: ['old tag'], + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { + [tagName]: {name: tagName}, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags) + + // When sending an invoice + sendInvoice({ + currentUserAccountID: currentUserPersonalDetails.accountID, + transaction, + policyRecentlyUsedCurrencies: [], + policyRecentlyUsedTags, + }); + waitForBatchedUpdates(); + + // Then the transaction tag should be added to the recently used tags collection + const newPolicyRecentlyUsedTags: RecentlyUsedTags = await new Promise((resolve) => { + const connection = Onyx.connectWithoutView({ + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, + callback: (recentlyUsedTags) => { + resolve(recentlyUsedTags ?? {}); + Onyx.disconnect(connection); + }, + }); + }); + expect(newPolicyRecentlyUsedTags[tagName].length).toBe(2); + expect(newPolicyRecentlyUsedTags[tagName].at(0)).toBe(transactionTag); + }); }); describe('canIOUBePaid', () => { @@ -10003,6 +10354,79 @@ describe('actions/IOU', () => { }); }); + describe('submitPerDiemExpense', () => { + it('should update policyRecentlyUsedTags when tag is provided', async () => { + // Given a transaction with a tag + const iouReportID = '2'; + const policyID = 'A'; + const transactionTag = 'new tag'; + const tagName = 'Tag'; + const policyRecentlyUsedTags: OnyxEntry = { + [tagName]: ['old tag'], + }; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, { + reportID: iouReportID, + policyID, + type: CONST.REPORT.TYPE.EXPENSE, + ownerAccountID: currentUserPersonalDetails.accountID, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { + [tagName]: {name: tagName}, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags) + + // When submitting a per diem expense + submitPerDiemExpense({ + currentUserAccountIDParam: currentUserPersonalDetails.accountID, + currentUserEmailParam: currentUserPersonalDetails.login ?? '', + hasViolations: false, + isASAPSubmitBetaEnabled: false, + participantParams: { + payeeEmail: currentUserPersonalDetails.login, + payeeAccountID: currentUserPersonalDetails.accountID, + participant: {}, + }, + report: { + reportID: '1', + iouReportID, + }, + transactionParams: { + created: DateUtils.getDBTime(), + currency: CONST.CURRENCY.USD, + tag: transactionTag, + customUnit: { + customUnitID: 'A', + name: CONST.CUSTOM_UNITS.NAME_PER_DIEM_INTERNATIONAL, + customUnitRateID: 'B', + subRates: [{id: '1', name: 'rate_a', quantity: 1, rate: 2}], + attributes: {dates: {end: '', start: ''}} + }, + }, + policyRecentlyUsedCurrencies: [], + policyParams: { + policy: {...createRandomPolicy(1)}, + policyRecentlyUsedTags, + }, + }); + + waitForBatchedUpdates(); + + // Then the transaction tag should be added to the recently used tags collection + const newPolicyRecentlyUsedTags: RecentlyUsedTags = await new Promise((resolve) => { + const connection = Onyx.connectWithoutView({ + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, + callback: (recentlyUsedTags) => { + resolve(recentlyUsedTags ?? {}); + Onyx.disconnect(connection); + }, + }); + }); + expect(newPolicyRecentlyUsedTags[tagName].length).toBe(2); + expect(newPolicyRecentlyUsedTags[tagName].at(0)).toBe(transactionTag); + }); + }); + describe('getSendInvoiceInformation', () => { it('should merge policyRecentlyUsedCategories when provided', () => { // Given: Transaction with a category and existing recently used categories @@ -10436,6 +10860,56 @@ describe('actions/IOU', () => { }); }); + describe('updateMoneyRequestTag', () => { + it('should update policyRecentlyUsedTags', async () => { + // Given a policy recently used tags + const policy = createRandomPolicy(1); + const tagName = 'Tag'; + const newTag = 'new tag'; + const policyTags: PolicyTagLists = { + [tagName]: {name: tagName, required: false, orderWeight: 0, tags: {A: {enabled: true, name: 'A'}}}, + }; + const policyRecentlyUsedTags: OnyxEntry = { + [tagName]: ['old tag'], + }; + const transactionThreadReportID = '2'; + const iouReportID = '3'; + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policy.id}`, policyTags); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policy.id}`, policyRecentlyUsedTags); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, {reportID: transactionThreadReportID, parentReportID: iouReportID}) + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, {reportID: iouReportID, policyID: policy.id}) + + // When updating the expense tag + updateMoneyRequestTag( + '1', + transactionThreadReportID, + newTag, + policy, + policyTags, + policyRecentlyUsedTags, + undefined, + currentUserPersonalDetails.accountID, + currentUserPersonalDetails.email ?? '', + false, + ); + + waitForBatchedUpdates(); + + // Then the transaction tag should be added to the recently used tags collection + const newPolicyRecentlyUsedTags: RecentlyUsedTags = await new Promise((resolve) => { + const connection = Onyx.connectWithoutView({ + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policy.id}`, + callback: (recentlyUsedTags) => { + resolve(recentlyUsedTags ?? {}); + Onyx.disconnect(connection); + }, + }); + }); + expect(newPolicyRecentlyUsedTags[tagName].length).toBe(2); + expect(newPolicyRecentlyUsedTags[tagName].at(0)).toBe(newTag); + }); + }); + describe('rejectMoneyRequest', () => { const amount = 10000; const comment = 'This expense is rejected'; From cf80b7c620d0b73915bbf034682b44b98dd336a8 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 31 Dec 2025 01:05:07 +0800 Subject: [PATCH 2/9] remove the onyx.connect reference max warning --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b3d5f8418946..2156786353bb 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand", "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", - "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=111 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto", + "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=110 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto", "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh", "check-lazy-loading": "ts-node scripts/checkLazyLoading.ts", "lint-watch": "npx eslint-watch --watch --changed", From 5c42c817c7c5609457193df64371fcf52000c29f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 31 Dec 2025 01:36:51 +0800 Subject: [PATCH 3/9] fix max param lint --- src/libs/actions/IOU/index.ts | 40 +++++++++++++------ .../iou/request/step/IOURequestStepTag.tsx | 12 +++--- tests/actions/IOUTest.ts | 18 ++++----- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index c329cfeef806..9f3bb4535519 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -5408,20 +5408,34 @@ function updateMoneyRequestAttendees( API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_ATTENDEES, params, onyxData); } +type UpdateMoneyRequestTagParams = { + transactionID: string; + transactionThreadReportID: string | undefined; + tag: string; + policy: OnyxEntry; + policyTagList: OnyxEntry; + policyRecentlyUsedTags: OnyxEntry; + policyCategories: OnyxEntry; + currentUserAccountIDParam: number; + currentUserEmailParam: string; + isASAPSubmitBetaEnabled: boolean; + hash?: number; +}; + /** Updates the tag of an expense */ -function updateMoneyRequestTag( - transactionID: string, - transactionThreadReportID: string | undefined, - tag: string, - policy: OnyxEntry, - policyTagList: OnyxEntry, - policyRecentlyUsedTags: OnyxEntry, - policyCategories: OnyxEntry, - currentUserAccountIDParam: number, - currentUserEmailParam: string, - isASAPSubmitBetaEnabled: boolean, - hash?: number, -) { +function updateMoneyRequestTag({ + transactionID, + transactionThreadReportID, + tag, + policy, + policyTagList, + policyRecentlyUsedTags, + policyCategories, + currentUserAccountIDParam, + currentUserEmailParam, + isASAPSubmitBetaEnabled, + hash, +}: UpdateMoneyRequestTagParams) { const transactionChanges: TransactionChanges = { tag, }; diff --git a/src/pages/iou/request/step/IOURequestStepTag.tsx b/src/pages/iou/request/step/IOURequestStepTag.tsx index 4994f870c7dc..910f5576eb96 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.tsx +++ b/src/pages/iou/request/step/IOURequestStepTag.tsx @@ -133,19 +133,19 @@ function IOURequestStepTag({ } if (isEditing) { - updateMoneyRequestTag( + updateMoneyRequestTag({ transactionID, - report?.reportID, - updatedTag, + transactionThreadReportID: report?.reportID, + tag: updatedTag, policy, - policyTags, + policyTagList: policyTags, policyRecentlyUsedTags, policyCategories, currentUserAccountIDParam, currentUserEmailParam, isASAPSubmitBetaEnabled, - currentSearchHash, - ); + hash: currentSearchHash, + }); navigateBack(); return; } diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index fe660275ae2e..4cd6d7297f0d 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -10880,18 +10880,18 @@ describe('actions/IOU', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, {reportID: iouReportID, policyID: policy.id}) // When updating the expense tag - updateMoneyRequestTag( - '1', + updateMoneyRequestTag({ + transactionID: '1', transactionThreadReportID, - newTag, + tag: newTag, policy, - policyTags, + policyTagList: policyTags, policyRecentlyUsedTags, - undefined, - currentUserPersonalDetails.accountID, - currentUserPersonalDetails.email ?? '', - false, - ); + policyCategories: undefined, + currentUserAccountIDParam: currentUserPersonalDetails.accountID, + currentUserEmailParam: currentUserPersonalDetails.email ?? '', + isASAPSubmitBetaEnabled: false, + }); waitForBatchedUpdates(); From 16c8e41cbacd9541df1049c5dadb9f1a28b3fa24 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 31 Dec 2025 01:39:21 +0800 Subject: [PATCH 4/9] fix type --- src/pages/iou/request/step/IOURequestStepScan/index.native.tsx | 1 + src/pages/iou/request/step/IOURequestStepScan/index.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index 633f348a7d2d..8fbd20c5d8dd 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -445,6 +445,7 @@ function IOURequestStepScan({ taxAmount: transactionTaxAmount, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], + policyRecentlyUsedTags: undefined, }); return; } diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index e1f6414fda1e..edd7541828af 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -501,6 +501,7 @@ function IOURequestStepScan({ taxAmount: transactionTaxAmount, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], + policyRecentlyUsedTags: undefined, }); return; } From 7fe2bdda6516e68f93ae18ab21dcfda4e8d444b6 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 5 Jan 2026 10:41:46 +0800 Subject: [PATCH 5/9] add comment --- src/pages/iou/request/step/IOURequestStepScan/index.native.tsx | 1 + src/pages/iou/request/step/IOURequestStepScan/index.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index 8fbd20c5d8dd..d5d74af2e646 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -445,6 +445,7 @@ function IOURequestStepScan({ taxAmount: transactionTaxAmount, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], + // No need to update recently used tags because no tags are used when the confirmation step is skipped policyRecentlyUsedTags: undefined, }); return; diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index edd7541828af..1c1cd26a81cf 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -501,6 +501,7 @@ function IOURequestStepScan({ taxAmount: transactionTaxAmount, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], + // No need to update recently used tags because no tags are used when the confirmation step is skipped policyRecentlyUsedTags: undefined, }); return; From 6a31f3e658ec50f9cf7027deee9ae43c5d2d52f8 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 6 Jan 2026 00:07:34 +0800 Subject: [PATCH 6/9] prettier --- tests/actions/IOUTest.ts | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 31cab01fa47c..bc4955363d7e 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -2217,7 +2217,7 @@ describe('actions/IOU', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { [tagName]: {name: tagName}, }); - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags) + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags); // When requesting money requestMoney({ @@ -2395,13 +2395,13 @@ describe('actions/IOU', () => { policyID, type: CONST.REPORT.TYPE.EXPENSE, ownerAccountID: currentUserPersonalDetails.accountID, - } + }; await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, iouReport); - await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, {reportID: iouReport.reportID, policyID}) + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, {reportID: iouReport.reportID, policyID}); await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { [tagName]: {name: tagName}, }); - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags) + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags); // When creating a distance request createDistanceRequest({ @@ -2459,7 +2459,7 @@ describe('actions/IOU', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { [tagName]: {name: tagName}, }); - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags) + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags); // When creating a split distance request createDistanceRequest({ @@ -3419,9 +3419,7 @@ describe('actions/IOU', () => { // When doing a split bill with a receipt startSplitBill({ - participants: [ - {isPolicyExpenseChat: true, policyID} - ], + participants: [{isPolicyExpenseChat: true, policyID}], currentUserLogin: currentUserPersonalDetails.login ?? '', currentUserAccountID: currentUserPersonalDetails.accountID, comment: '', @@ -6664,10 +6662,8 @@ describe('actions/IOU', () => { const transaction: Transaction = { ...createRandomTransaction(1), tag: transactionTag, - participants: [ - {isSender: true, policyID}, - ], - }; + participants: [{isSender: true, policyID}], + }; const tagName = 'Tag'; const policyRecentlyUsedTags: OnyxEntry = { [tagName]: ['old tag'], @@ -6675,7 +6671,7 @@ describe('actions/IOU', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { [tagName]: {name: tagName}, }); - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags) + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags); // When sending an invoice sendInvoice({ @@ -10555,7 +10551,7 @@ describe('actions/IOU', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { [tagName]: {name: tagName}, }); - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags) + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags); // When submitting a per diem expense submitPerDiemExpense({ @@ -10581,7 +10577,7 @@ describe('actions/IOU', () => { name: CONST.CUSTOM_UNITS.NAME_PER_DIEM_INTERNATIONAL, customUnitRateID: 'B', subRates: [{id: '1', name: 'rate_a', quantity: 1, rate: 2}], - attributes: {dates: {end: '', start: ''}} + attributes: {dates: {end: '', start: ''}}, }, }, policyRecentlyUsedCurrencies: [], @@ -11057,8 +11053,8 @@ describe('actions/IOU', () => { const iouReportID = '3'; await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policy.id}`, policyTags); await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policy.id}`, policyRecentlyUsedTags); - await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, {reportID: transactionThreadReportID, parentReportID: iouReportID}) - await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, {reportID: iouReportID, policyID: policy.id}) + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, {reportID: transactionThreadReportID, parentReportID: iouReportID}); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, {reportID: iouReportID, policyID: policy.id}); // When updating the expense tag updateMoneyRequestTag({ From 4b5dbb0dcd1ead24a85d544b007b0a9066bb46c6 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 6 Jan 2026 00:17:02 +0800 Subject: [PATCH 7/9] fix type --- tests/actions/IOUTest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index bc4955363d7e..7f3fbbcaa027 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -2243,6 +2243,7 @@ describe('actions/IOU', () => { transactionViolations: {}, currentUserAccountIDParam: currentUserPersonalDetails.accountID, currentUserEmailParam: currentUserPersonalDetails.login ?? '', + policyRecentlyUsedCurrencies: [], }); waitForBatchedUpdates(); From 6b8e469bd249d86af46f46f0bfeeb21c0ff7d86a Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 6 Jan 2026 10:01:57 +0800 Subject: [PATCH 8/9] pass the recently used tags --- src/libs/actions/IOU/SendInvoice.ts | 10 +++--- tests/actions/IOUTest/SendInvoiceTest.ts | 43 +++++++++++++++++++++++- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/IOU/SendInvoice.ts b/src/libs/actions/IOU/SendInvoice.ts index d42246c29e7e..2ad44e7d6619 100644 --- a/src/libs/actions/IOU/SendInvoice.ts +++ b/src/libs/actions/IOU/SendInvoice.ts @@ -36,8 +36,6 @@ import type {Receipt} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import { getAllPersonalDetails, - // eslint-disable-next-line @typescript-eslint/no-deprecated - getPolicyRecentlyUsedTagsData, getReceiptError, getSearchOnyxUpdate, mergePolicyRecentlyUsedCategories, @@ -72,6 +70,7 @@ type SendInvoiceOptions = { companyName?: string; companyWebsite?: string; policyRecentlyUsedCategories?: OnyxEntry; + policyRecentlyUsedTags?: OnyxEntry; }; type BuildOnyxDataForInvoiceParams = { @@ -536,6 +535,7 @@ function getSendInvoiceInformation({ companyName, companyWebsite, policyRecentlyUsedCategories, + policyRecentlyUsedTags, }: SendInvoiceOptions): SendInvoiceInformation { const {amount = 0, currency = '', created = '', merchant = '', category = '', tag = '', taxCode = '', taxAmount = 0, billable, comment, participants} = transaction ?? {}; const trimmedComment = (comment?.comment ?? '').trim(); @@ -592,9 +592,7 @@ function getSendInvoiceInformation({ const optimisticPolicyRecentlyUsedCategories = mergePolicyRecentlyUsedCategories(category, policyRecentlyUsedCategories); const optimisticPolicyRecentlyUsedTags = buildOptimisticPolicyRecentlyUsedTags({ policyTags: getPolicyTagsData(optimisticInvoiceReport.policyID), - // TODO: Replace getPolicyRecentlyUsedTagsData with useOnyx hook (https://github.com/Expensify/App/issues/71491) - // eslint-disable-next-line @typescript-eslint/no-deprecated - policyRecentlyUsedTags: getPolicyRecentlyUsedTagsData(optimisticInvoiceReport.policyID), + policyRecentlyUsedTags, transactionTags: tag, }); const optimisticRecentlyUsedCurrencies = mergePolicyRecentlyUsedCurrencies(currency, policyRecentlyUsedCurrencies); @@ -682,6 +680,7 @@ function sendInvoice({ companyName, companyWebsite, policyRecentlyUsedCategories, + policyRecentlyUsedTags, }: SendInvoiceOptions) { const parsedComment = getParsedComment(transaction?.comment?.comment?.trim() ?? ''); if (transaction?.comment) { @@ -713,6 +712,7 @@ function sendInvoice({ companyName, companyWebsite, policyRecentlyUsedCategories, + policyRecentlyUsedTags, }); const parameters: SendInvoiceParams = { diff --git a/tests/actions/IOUTest/SendInvoiceTest.ts b/tests/actions/IOUTest/SendInvoiceTest.ts index d22a787e7ddb..42af684568b7 100644 --- a/tests/actions/IOUTest/SendInvoiceTest.ts +++ b/tests/actions/IOUTest/SendInvoiceTest.ts @@ -10,7 +10,7 @@ import IntlStore from '@src/languages/IntlStore'; import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; import * as API from '@src/libs/API'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyTagLists, RecentlyUsedCategories, Report} from '@src/types/onyx'; +import type {PolicyTagLists, RecentlyUsedCategories, RecentlyUsedTags, Report} from '@src/types/onyx'; import type {Participant as IOUParticipant} from '@src/types/onyx/IOU'; import type {InvoiceReceiver} from '@src/types/onyx/Report'; import type Transaction from '@src/types/onyx/Transaction'; @@ -614,6 +614,47 @@ describe('actions/SendInvoice', () => { writeSpy.mockRestore(); }); + + it('should update policyRecentlyUsedTags when tag is provided', async () => { + // Given a transaction with a tag + const policyID = 'A'; + const transactionTag = 'new tag'; + const transaction: Transaction = { + ...createRandomTransaction(1), + tag: transactionTag, + participants: [{isSender: true, policyID}], + }; + const tagName = 'Tag'; + const policyRecentlyUsedTags: OnyxEntry = { + [tagName]: ['old tag'], + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { + [tagName]: {name: tagName}, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, policyRecentlyUsedTags); + + // When sending an invoice + sendInvoice({ + currentUserAccountID: 1, + transaction, + policyRecentlyUsedCurrencies: [], + policyRecentlyUsedTags, + }); + waitForBatchedUpdates(); + + // Then the transaction tag should be added to the recently used tags collection + const newPolicyRecentlyUsedTags: RecentlyUsedTags = await new Promise((resolve) => { + const connection = Onyx.connectWithoutView({ + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, + callback: (recentlyUsedTags) => { + resolve(recentlyUsedTags ?? {}); + Onyx.disconnect(connection); + }, + }); + }); + expect(newPolicyRecentlyUsedTags[tagName].length).toBe(2); + expect(newPolicyRecentlyUsedTags[tagName].at(0)).toBe(transactionTag); + }); }); describe('Invoice recipient change while offline', () => { const userAAccountID = 1; From 56c15dbedafc0188ceb7eae705897183955a4911 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 6 Jan 2026 10:14:52 +0800 Subject: [PATCH 9/9] prettier --- src/libs/actions/IOU/SendInvoice.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/libs/actions/IOU/SendInvoice.ts b/src/libs/actions/IOU/SendInvoice.ts index 2ad44e7d6619..dc3f9d285fef 100644 --- a/src/libs/actions/IOU/SendInvoice.ts +++ b/src/libs/actions/IOU/SendInvoice.ts @@ -34,13 +34,7 @@ import type {InvoiceReceiver, InvoiceReceiverType} from '@src/types/onyx/Report' import type {OnyxData} from '@src/types/onyx/Request'; import type {Receipt} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import { - getAllPersonalDetails, - getReceiptError, - getSearchOnyxUpdate, - mergePolicyRecentlyUsedCategories, - mergePolicyRecentlyUsedCurrencies, -} from '.'; +import {getAllPersonalDetails, getReceiptError, getSearchOnyxUpdate, mergePolicyRecentlyUsedCategories, mergePolicyRecentlyUsedCurrencies} from '.'; import type {BasePolicyParams} from '.'; type SendInvoiceInformation = {