From d7d715aa958f030fd678ff7c4ea6726f31c0e138 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 5 Dec 2025 22:00:36 +0530 Subject: [PATCH 1/5] Refactor: Deprecate getPolicy (part 2) --- .../useGetExpensifyCardFromReportAction.ts | 8 ++++---- src/libs/actions/Policy/Category.ts | 7 ++----- src/libs/actions/Policy/Tag.ts | 5 +---- src/libs/actions/TeachersUnite.ts | 5 +---- .../TransactionDuplicate/ReviewTaxCode.tsx | 7 +++---- .../report/SystemChatReportFooterMessage.tsx | 8 +++----- .../step/IOURequestStepPerDiemWorkspace.tsx | 6 ++---- .../categories/CategoryDefaultTaxRatePage.tsx | 4 ++-- .../members/WorkspaceMemberNewCardPage.tsx | 6 ++---- src/pages/workspace/tags/TagApproverPage.tsx | 2 +- .../useGetExpensifyCardFromReportActionTest.ts | 17 ++++++++--------- 11 files changed, 29 insertions(+), 46 deletions(-) diff --git a/src/hooks/useGetExpensifyCardFromReportAction.ts b/src/hooks/useGetExpensifyCardFromReportAction.ts index f783c0e20b7cd..112f0151710b8 100644 --- a/src/hooks/useGetExpensifyCardFromReportAction.ts +++ b/src/hooks/useGetExpensifyCardFromReportAction.ts @@ -1,22 +1,22 @@ import {useCardList, useWorkspaceCardList} from '@components/OnyxListItemProvider'; -import {getPolicy, getWorkspaceAccountID, isPolicyAdmin} from '@libs/PolicyUtils'; +import {getWorkspaceAccountID, isPolicyAdmin} from '@libs/PolicyUtils'; import {getOriginalMessage, isCardIssuedAction} from '@libs/ReportActionsUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Card, ReportAction} from '@src/types/onyx'; +import usePolicy from './usePolicy'; function useGetExpensifyCardFromReportAction({reportAction, policyID}: {reportAction?: ReportAction; policyID?: string}): Card | undefined { const allUserCards = useCardList(); const workspaceAccountID = getWorkspaceAccountID(policyID); const allExpensifyCards = useWorkspaceCardList(); + const policy = usePolicy(policyID); const expensifyCards = allExpensifyCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`] ?? {}; const cardIssuedActionOriginalMessage = isCardIssuedAction(reportAction) ? getOriginalMessage(reportAction) : undefined; const cardID = cardIssuedActionOriginalMessage?.cardID ?? CONST.DEFAULT_NUMBER_ID; - // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 - // eslint-disable-next-line @typescript-eslint/no-deprecated - return isPolicyAdmin(getPolicy(policyID)) ? expensifyCards?.[cardID] : allUserCards?.[cardID]; + return isPolicyAdmin(policy) ? expensifyCards?.[cardID] : allUserCards?.[cardID]; } export default useGetExpensifyCardFromReportAction; diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 12012ea4fee79..19808b981e637 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -27,7 +27,7 @@ import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import Log from '@libs/Log'; import enhanceParameters from '@libs/Network/enhanceParameters'; import {hasEnabledOptions} from '@libs/OptionsListUtils'; -import {getPolicy, goBackWhenEnableFeature} from '@libs/PolicyUtils'; +import {goBackWhenEnableFeature} from '@libs/PolicyUtils'; import {pushTransactionViolationsOnyxData} from '@libs/ReportUtils'; import {getFinishOnboardingTaskOnyxData} from '@userActions/Task'; import CONST from '@src/CONST'; @@ -1451,10 +1451,7 @@ function setPolicyCategoryApprover(policyID: string, categoryName: string, appro API.write(WRITE_COMMANDS.SET_POLICY_CATEGORY_APPROVER, parameters, onyxData); } -function setPolicyCategoryTax(policyID: string, categoryName: string, taxID: string) { - // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 - // eslint-disable-next-line @typescript-eslint/no-deprecated - const policy = getPolicy(policyID); +function setPolicyCategoryTax(policyID: string, policy: OnyxEntry, categoryName: string, taxID: string) { const expenseRules = policy?.rules?.expenseRules ?? []; const updatedExpenseRules: ExpenseRule[] = lodashCloneDeep(expenseRules); const existingCategoryExpenseRule = updatedExpenseRules.find((rule) => rule.applyWhen.some((when) => when.value === categoryName)); diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts index b8f06cf1c53a8..ce7420586264a 100644 --- a/src/libs/actions/Policy/Tag.ts +++ b/src/libs/actions/Policy/Tag.ts @@ -1164,10 +1164,7 @@ function setPolicyTagGLCode({policyID, tagName, tagListIndex, glCode, policyTags API.write(WRITE_COMMANDS.UPDATE_POLICY_TAG_GL_CODE, parameters, onyxData); } -function setPolicyTagApprover(policyID: string, tag: string, approver: string) { - // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 - // eslint-disable-next-line @typescript-eslint/no-deprecated - const policy = PolicyUtils.getPolicy(policyID); +function setPolicyTagApprover(policyID: string, policy: OnyxEntry, tag: string, approver: string) { const prevApprovalRules = policy?.rules?.approvalRules ?? []; const approverRuleToUpdate = PolicyUtils.getTagApproverRule(policyID, tag); const filteredApprovalRules = approverRuleToUpdate ? prevApprovalRules.filter((rule) => rule.id !== approverRuleToUpdate.id) : prevApprovalRules; diff --git a/src/libs/actions/TeachersUnite.ts b/src/libs/actions/TeachersUnite.ts index 8a22ae20381e2..880723ace21b4 100644 --- a/src/libs/actions/TeachersUnite.ts +++ b/src/libs/actions/TeachersUnite.ts @@ -6,7 +6,6 @@ import {WRITE_COMMANDS} from '@libs/API/types'; import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import {addSMSDomainIfPhoneNumber} from '@libs/PhoneNumber'; -import {getPolicy} from '@libs/PolicyUtils'; import {buildOptimisticChatReport, buildOptimisticCreatedReportAction} from '@libs/ReportUtils'; import type {OptimisticCreatedReportAction} from '@libs/ReportUtils'; import CONST from '@src/CONST'; @@ -118,9 +117,7 @@ function addSchoolPrincipal( name: policyName, role: CONST.POLICY.ROLE.USER, owner: sessionEmail, - // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 - // eslint-disable-next-line @typescript-eslint/no-deprecated - outputCurrency: getPolicy(policyID)?.outputCurrency ?? localCurrencyCode ?? CONST.CURRENCY.USD, + outputCurrency: localCurrencyCode ?? CONST.CURRENCY.USD, employeeList: { [sessionEmail]: { role: CONST.POLICY.ROLE.USER, diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index e73abc2fb83c5..661caac751892 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -4,6 +4,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import usePolicy from '@hooks/usePolicy'; import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation'; import useTransactionsByID from '@hooks/useTransactionsByID'; import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; @@ -11,7 +12,7 @@ import {convertToBackendAmount} from '@libs/CurrencyUtils'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; -import {getPolicy, getTaxByID} from '@libs/PolicyUtils'; +import {getTaxByID} from '@libs/PolicyUtils'; import {calculateTaxAmount, compareDuplicateTransactionFields, getAmount, getDefaultTaxCode, getTaxValue, getTransactionID} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -25,9 +26,7 @@ function ReviewTaxRate() { const {translate} = useLocalize(); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES, {canBeMissing: true}); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reviewDuplicates?.reportID ?? route.params.threadReportID}`, {canBeMissing: true}); - // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 - // eslint-disable-next-line @typescript-eslint/no-deprecated - const policy = getPolicy(report?.policyID); + const policy = usePolicy(report?.policyID); const transactionID = getTransactionID(route.params.threadReportID); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`, {canBeMissing: true}); const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, { diff --git a/src/pages/home/report/SystemChatReportFooterMessage.tsx b/src/pages/home/report/SystemChatReportFooterMessage.tsx index e673014ce62e4..eb3764169d7f0 100644 --- a/src/pages/home/report/SystemChatReportFooterMessage.tsx +++ b/src/pages/home/report/SystemChatReportFooterMessage.tsx @@ -8,7 +8,7 @@ import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getPolicy, shouldShowPolicy} from '@libs/PolicyUtils'; +import {shouldShowPolicy} from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -24,12 +24,10 @@ function SystemChatReportFooterMessage() { const adminChatReportID = useMemo(() => { const adminPolicy = activePolicyID - ? // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 - // eslint-disable-next-line @typescript-eslint/no-deprecated - getPolicy(activePolicyID) + ? policies?.[`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`] : Object.values(policies ?? {}).find((policy) => shouldShowPolicy(policy, false, currentUserLogin) && policy?.role === CONST.POLICY.ROLE.ADMIN && policy?.chatReportIDAdmins); - return String(adminPolicy?.chatReportIDAdmins ?? -1); + return adminPolicy?.chatReportIDAdmins ?? CONST.DEFAULT_NUMBER_ID; }, [activePolicyID, policies, currentUserLogin]); const [adminChatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${adminChatReportID}`, {canBeMissing: true}); diff --git a/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx b/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx index 9f2512f41f475..f36a15478c698 100644 --- a/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx +++ b/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx @@ -12,7 +12,7 @@ import useOnyx from '@hooks/useOnyx'; import useSearchResults from '@hooks/useSearchResults'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import {getActivePoliciesWithExpenseChatAndPerDiemEnabled, getPerDiemCustomUnit, getPolicy, sortWorkspacesBySelected} from '@libs/PolicyUtils'; +import {getActivePoliciesWithExpenseChatAndPerDiemEnabled, getPerDiemCustomUnit, sortWorkspacesBySelected} from '@libs/PolicyUtils'; import {getDefaultWorkspaceAvatar, getPolicyExpenseChat} from '@libs/ReportUtils'; import tokenizedSearch from '@libs/tokenizedSearch'; import {setCustomUnitID, setMoneyRequestCategory, setMoneyRequestParticipants} from '@userActions/IOU'; @@ -92,9 +92,7 @@ function IOURequestStepPerDiemWorkspace({ if (!policyExpenseReportID) { return; } - // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 - // eslint-disable-next-line @typescript-eslint/no-deprecated - const selectedPolicy = getPolicy(item.value, allPolicies); + const selectedPolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.value}`]; const perDiemUnit = getPerDiemCustomUnit(selectedPolicy); setMoneyRequestParticipants(transactionID, [ { diff --git a/src/pages/workspace/categories/CategoryDefaultTaxRatePage.tsx b/src/pages/workspace/categories/CategoryDefaultTaxRatePage.tsx index c46868f8e5416..4250c40c785a2 100644 --- a/src/pages/workspace/categories/CategoryDefaultTaxRatePage.tsx +++ b/src/pages/workspace/categories/CategoryDefaultTaxRatePage.tsx @@ -59,10 +59,10 @@ function CategoryDefaultTaxRatePage({ return; } - setPolicyCategoryTax(policyID, categoryName, item.keyForList); + setPolicyCategoryTax(policyID, policy, categoryName, item.keyForList); Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.goBack(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(policyID, categoryName))); }, - [policyID, categoryName, selectedTaxRate], + [policyID, policy, categoryName, selectedTaxRate], ); return ( diff --git a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx index b4cdc47969463..1deda7581f582 100644 --- a/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberNewCardPage.tsx @@ -13,6 +13,7 @@ import useCardsList from '@hooks/useCardsList'; import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import usePolicy from '@hooks/usePolicy'; import useThemeIllustrations from '@hooks/useThemeIllustrations'; import useThemeStyles from '@hooks/useThemeStyles'; import { @@ -30,7 +31,6 @@ import { } from '@libs/CardUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; -import {getPolicy} from '@libs/PolicyUtils'; import Navigation from '@navigation/Navigation'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; @@ -57,9 +57,7 @@ type WorkspaceMemberNewCardPageProps = WithPolicyAndFullscreenLoadingProps & Pla function WorkspaceMemberNewCardPage({route, personalDetails}: WorkspaceMemberNewCardPageProps) { const {policyID} = route.params; - // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 - // eslint-disable-next-line @typescript-eslint/no-deprecated - const policy = getPolicy(policyID); + const policy = usePolicy(policyID); const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID; const {translate} = useLocalize(); diff --git a/src/pages/workspace/tags/TagApproverPage.tsx b/src/pages/workspace/tags/TagApproverPage.tsx index cd228e0ae9e4f..c55ffcd56c53a 100644 --- a/src/pages/workspace/tags/TagApproverPage.tsx +++ b/src/pages/workspace/tags/TagApproverPage.tsx @@ -55,7 +55,7 @@ function TagApproverPage({route}: TagApproverPageProps) { policyID={policyID} selectedApprover={tagApprover ?? ''} setApprover={(email) => { - setPolicyTagApprover(policyID, tagName, email); + setPolicyTagApprover(policyID, policy, tagName, email); Navigation.setNavigationActionToMicrotaskQueue(goBack); }} /> diff --git a/tests/unit/useGetExpensifyCardFromReportActionTest.ts b/tests/unit/useGetExpensifyCardFromReportActionTest.ts index d7eb05b4045b7..248466091b8df 100644 --- a/tests/unit/useGetExpensifyCardFromReportActionTest.ts +++ b/tests/unit/useGetExpensifyCardFromReportActionTest.ts @@ -1,7 +1,8 @@ import {renderHook} from '@testing-library/react-native'; import Onyx from 'react-native-onyx'; import {useCardList, useWorkspaceCardList} from '@components/OnyxListItemProvider'; -import {getPolicy, getWorkspaceAccountID, isPolicyAdmin} from '@libs/PolicyUtils'; +import usePolicy from '@hooks/usePolicy'; +import {getWorkspaceAccountID, isPolicyAdmin} from '@libs/PolicyUtils'; import {getOriginalMessage, isCardIssuedAction} from '@libs/ReportActionsUtils'; import CONST from '@src/CONST'; import useGetExpensifyCardFromReportAction from '@src/hooks/useGetExpensifyCardFromReportAction'; @@ -18,9 +19,7 @@ jest.mock('@components/OnyxListItemProvider', () => ({ useWorkspaceCardList: jest.fn(), })); -// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 -// eslint-disable-next-line @typescript-eslint/no-deprecated -const mockGetPolicy = getPolicy as jest.MockedFunction; +const mockUsePolicy = usePolicy as jest.MockedFunction; const mockGetWorkspaceAccountID = getWorkspaceAccountID as jest.MockedFunction; const mockIsPolicyAdmin = isPolicyAdmin as jest.MockedFunction; const mockGetOriginalMessage = getOriginalMessage as jest.MockedFunction; @@ -78,7 +77,7 @@ describe('useGetExpensifyCardFromReportAction', () => { await Onyx.clear(); jest.clearAllMocks(); - mockGetPolicy.mockReturnValue(mockPolicy); + mockUsePolicy.mockReturnValue(mockPolicy); mockGetWorkspaceAccountID.mockReturnValue(123); mockIsPolicyAdmin.mockReturnValue(false); mockGetOriginalMessage.mockReturnValue({cardID: 123, assigneeAccountID: 1}); @@ -134,7 +133,7 @@ describe('useGetExpensifyCardFromReportAction', () => { mockIsPolicyAdmin.mockReturnValue(true); mockGetWorkspaceAccountID.mockReturnValue(123); // Override the default policy for admin tests - mockGetPolicy.mockReturnValue({ + mockUsePolicy.mockReturnValue({ id: 'policy123', name: 'Test Policy', role: CONST.POLICY.ROLE.ADMIN, @@ -193,7 +192,7 @@ describe('useGetExpensifyCardFromReportAction', () => { it('updates when allExpensifyCards changes for policy admin', async () => { mockIsPolicyAdmin.mockReturnValue(true); - mockGetPolicy.mockReturnValue({ + mockUsePolicy.mockReturnValue({ id: 'policy123', name: 'Test Policy', role: CONST.POLICY.ROLE.ADMIN, @@ -250,7 +249,7 @@ describe('useGetExpensifyCardFromReportAction', () => { isPolicyExpenseChatEnabled: false, workspaceAccountID: 123, }; - mockGetPolicy.mockReturnValue(testPolicy); + mockUsePolicy.mockReturnValue(testPolicy); // eslint-disable-next-line @typescript-eslint/naming-convention mockUseCardList.mockReturnValue({'123': mockCard}); @@ -258,7 +257,7 @@ describe('useGetExpensifyCardFromReportAction', () => { const {result} = renderHook(() => useGetExpensifyCardFromReportAction({reportAction: createMockReportAction(), policyID: 'policy123'})); await waitForBatchedUpdatesWithAct(); - expect(mockGetPolicy).toHaveBeenCalledWith('policy123'); + expect(mockUsePolicy).toHaveBeenCalledWith('policy123'); expect(mockIsPolicyAdmin).toHaveBeenCalledWith(testPolicy); expect(result.current).toEqual(mockCard); }); From 7d47bbe3453e79cd0d2633b533fea4726fdea15a Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 5 Dec 2025 23:15:37 +0530 Subject: [PATCH 2/5] Fix test --- tests/unit/useGetExpensifyCardFromReportActionTest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/useGetExpensifyCardFromReportActionTest.ts b/tests/unit/useGetExpensifyCardFromReportActionTest.ts index 248466091b8df..bc5a3c1d7747c 100644 --- a/tests/unit/useGetExpensifyCardFromReportActionTest.ts +++ b/tests/unit/useGetExpensifyCardFromReportActionTest.ts @@ -18,6 +18,7 @@ jest.mock('@components/OnyxListItemProvider', () => ({ useCardList: jest.fn(), useWorkspaceCardList: jest.fn(), })); +jest.mock('@hooks/usePolicy'); const mockUsePolicy = usePolicy as jest.MockedFunction; const mockGetWorkspaceAccountID = getWorkspaceAccountID as jest.MockedFunction; From 05200ae3daf8a0e3fa76a1c561d4d0ae4a0c7e7c Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 15 Dec 2025 13:14:39 +0530 Subject: [PATCH 3/5] Applying suggestions --- src/libs/actions/Policy/Category.ts | 6 +++++- src/libs/actions/Policy/Tag.ts | 6 +++++- .../workspace/categories/CategoryDefaultTaxRatePage.tsx | 2 +- src/pages/workspace/tags/TagApproverPage.tsx | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 19808b981e637..937f02e872a9c 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -1451,7 +1451,11 @@ function setPolicyCategoryApprover(policyID: string, categoryName: string, appro API.write(WRITE_COMMANDS.SET_POLICY_CATEGORY_APPROVER, parameters, onyxData); } -function setPolicyCategoryTax(policyID: string, policy: OnyxEntry, categoryName: string, taxID: string) { +function setPolicyCategoryTax(policy: OnyxEntry, categoryName: string, taxID: string) { + if (!policy?.id) { + return; + } + const policyID = policy.id; const expenseRules = policy?.rules?.expenseRules ?? []; const updatedExpenseRules: ExpenseRule[] = lodashCloneDeep(expenseRules); const existingCategoryExpenseRule = updatedExpenseRules.find((rule) => rule.applyWhen.some((when) => when.value === categoryName)); diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts index ce7420586264a..5fcf7c1f994bd 100644 --- a/src/libs/actions/Policy/Tag.ts +++ b/src/libs/actions/Policy/Tag.ts @@ -1164,7 +1164,11 @@ function setPolicyTagGLCode({policyID, tagName, tagListIndex, glCode, policyTags API.write(WRITE_COMMANDS.UPDATE_POLICY_TAG_GL_CODE, parameters, onyxData); } -function setPolicyTagApprover(policyID: string, policy: OnyxEntry, tag: string, approver: string) { +function setPolicyTagApprover(policy: OnyxEntry, tag: string, approver: string) { + if (!policy?.id) { + return; + } + const policyID = policy.id; const prevApprovalRules = policy?.rules?.approvalRules ?? []; const approverRuleToUpdate = PolicyUtils.getTagApproverRule(policyID, tag); const filteredApprovalRules = approverRuleToUpdate ? prevApprovalRules.filter((rule) => rule.id !== approverRuleToUpdate.id) : prevApprovalRules; diff --git a/src/pages/workspace/categories/CategoryDefaultTaxRatePage.tsx b/src/pages/workspace/categories/CategoryDefaultTaxRatePage.tsx index 4250c40c785a2..e0498a810b3e9 100644 --- a/src/pages/workspace/categories/CategoryDefaultTaxRatePage.tsx +++ b/src/pages/workspace/categories/CategoryDefaultTaxRatePage.tsx @@ -59,7 +59,7 @@ function CategoryDefaultTaxRatePage({ return; } - setPolicyCategoryTax(policyID, policy, categoryName, item.keyForList); + setPolicyCategoryTax(policy, categoryName, item.keyForList); Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.goBack(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(policyID, categoryName))); }, [policyID, policy, categoryName, selectedTaxRate], diff --git a/src/pages/workspace/tags/TagApproverPage.tsx b/src/pages/workspace/tags/TagApproverPage.tsx index c55ffcd56c53a..d191bded1531b 100644 --- a/src/pages/workspace/tags/TagApproverPage.tsx +++ b/src/pages/workspace/tags/TagApproverPage.tsx @@ -55,7 +55,7 @@ function TagApproverPage({route}: TagApproverPageProps) { policyID={policyID} selectedApprover={tagApprover ?? ''} setApprover={(email) => { - setPolicyTagApprover(policyID, policy, tagName, email); + setPolicyTagApprover(policy, tagName, email); Navigation.setNavigationActionToMicrotaskQueue(goBack); }} /> From 709083819a336d1b544ac047bdc25eb7c1833f35 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:33:27 +0530 Subject: [PATCH 4/5] Apply suggestion from @shubham1206agra --- src/libs/actions/Policy/Category.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 937f02e872a9c..a3ed232423497 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -1471,7 +1471,7 @@ function setPolicyCategoryTax(policy: OnyxEntry, categoryName: string, t applyWhen: [ { condition: CONST.POLICY.RULE_CONDITIONS.MATCHES, - field: 'category', + field: CONST.POLICY.FIELDS.CATEGORY, value: categoryName, }, ], From 37e416ffe301187f694ea513d9621a50034f8138 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 15 Dec 2025 18:52:28 +0530 Subject: [PATCH 5/5] Adding tests --- tests/actions/PolicyCategoryTest.ts | 103 ++++++++++++++++++++++++++++ tests/actions/PolicyTagTest.ts | 79 +++++++++++++++++++++ 2 files changed, 182 insertions(+) diff --git a/tests/actions/PolicyCategoryTest.ts b/tests/actions/PolicyCategoryTest.ts index 2eed61da93bfc..3ad2c66a99e40 100644 --- a/tests/actions/PolicyCategoryTest.ts +++ b/tests/actions/PolicyCategoryTest.ts @@ -1,5 +1,6 @@ import {act, renderHook} from '@testing-library/react-native'; import Onyx from 'react-native-onyx'; +import OnyxUtils from 'react-native-onyx/dist/OnyxUtils'; import OnyxListItemProvider from '@components/OnyxListItemProvider'; import usePolicyData from '@hooks/usePolicyData'; import { @@ -7,6 +8,7 @@ import { deleteWorkspaceCategories, enablePolicyCategories, renamePolicyCategory, + setPolicyCategoryTax, setWorkspaceCategoryEnabled, setWorkspaceRequiresCategory, } from '@libs/actions/Policy/Category'; @@ -394,4 +396,105 @@ describe('actions/PolicyCategory', () => { }); }); }); + + describe('SetPolicyCategoryTax', () => { + it('should set expense rule when category expense rule is not present', async () => { + // Given a policy + const fakePolicy = createRandomPolicy(0); + fakePolicy.areCategoriesEnabled = true; + const categoryName = 'Fake category'; + const fakePolicyCategories = { + [categoryName]: { + name: categoryName, + enabled: false, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'GL Code': '', + unencodedName: categoryName, + externalID: '', + areCommentsRequired: false, + origin: '', + }, + }; + + mockFetch.pause(); + + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakePolicyCategories); + + setPolicyCategoryTax(fakePolicy, categoryName, 'VAT'); + await waitForBatchedUpdates(); + + // Then the approval rule should be created with the tag name + const updatedPolicy = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`); + + expect(updatedPolicy?.rules?.expenseRules).toHaveLength(1); + expect(updatedPolicy?.rules?.expenseRules?.[0]?.applyWhen?.[0]?.value).toBe(categoryName); + expect(updatedPolicy?.rules?.expenseRules?.[0]?.applyWhen?.[0]?.condition).toBe(CONST.POLICY.RULE_CONDITIONS.MATCHES); + expect(updatedPolicy?.rules?.expenseRules?.[0]?.applyWhen?.[0]?.field).toBe(CONST.POLICY.FIELDS.CATEGORY); + expect(updatedPolicy?.rules?.expenseRules?.[0].tax.field_id_TAX.externalID).toBe('VAT'); + + mockFetch.resume(); + await waitForBatchedUpdates(); + }); + + it('should update expense rule when category expense rule is present', async () => { + // Given a policy with approval rules that reference a tag + const fakePolicy = createRandomPolicy(0); + fakePolicy.areCategoriesEnabled = true; + const categoryName = 'Fake category'; + const fakePolicyCategories = { + [categoryName]: { + name: categoryName, + enabled: false, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'GL Code': '', + unencodedName: categoryName, + externalID: '', + areCommentsRequired: false, + origin: '', + }, + }; + + // Create expense rule that uses the tag + fakePolicy.rules = { + expenseRules: [ + { + tax: { + // eslint-disable-next-line @typescript-eslint/naming-convention + field_id_TAX: { + externalID: 'GST', + }, + }, + applyWhen: [ + { + condition: CONST.POLICY.RULE_CONDITIONS.MATCHES, + field: CONST.POLICY.FIELDS.CATEGORY, + value: categoryName, + }, + ], + }, + ], + }; + + mockFetch.pause(); + + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakePolicyCategories); + + setPolicyCategoryTax(fakePolicy, categoryName, 'VAT'); + await waitForBatchedUpdates(); + + // Then the approval rule should be created with the tag name + const updatedPolicy = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`); + + expect(updatedPolicy?.rules?.expenseRules).toHaveLength(1); + expect(updatedPolicy?.rules?.expenseRules?.[0]?.applyWhen?.[0]?.value).toBe(categoryName); + expect(updatedPolicy?.rules?.expenseRules?.[0]?.applyWhen?.[0]?.condition).toBe(CONST.POLICY.RULE_CONDITIONS.MATCHES); + expect(updatedPolicy?.rules?.expenseRules?.[0]?.applyWhen?.[0]?.field).toBe(CONST.POLICY.FIELDS.CATEGORY); + expect(updatedPolicy?.rules?.expenseRules?.[0].tax.field_id_TAX.externalID).toBe('VAT'); + + mockFetch.resume(); + await waitForBatchedUpdates(); + }); + }); }); diff --git a/tests/actions/PolicyTagTest.ts b/tests/actions/PolicyTagTest.ts index 195e23ad1b27d..73f4574cb5d7a 100644 --- a/tests/actions/PolicyTagTest.ts +++ b/tests/actions/PolicyTagTest.ts @@ -16,6 +16,7 @@ import { renamePolicyTag, renamePolicyTagList, setPolicyRequiresTag, + setPolicyTagApprover, setPolicyTagGLCode, setPolicyTagsRequired, setWorkspaceTagEnabled, @@ -827,6 +828,84 @@ describe('actions/Policy', () => { }); }); + describe('SetPolicyTagApprover', () => { + it('should set approval rule when tag approval rule is not present', async () => { + // Given a policy + const fakePolicy = createRandomPolicy(0); + fakePolicy.areTagsEnabled = true; + const tagListName = 'Fake tag'; + const fakePolicyTags = createRandomPolicyTags(tagListName, 1); + const tagName = Object.keys(fakePolicyTags?.[tagListName]?.tags).at(0) ?? ''; + + mockFetch.pause(); + + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags); + + setPolicyTagApprover(fakePolicy, tagName, 'admin@company.com'); + await waitForBatchedUpdates(); + + // Then the approval rule should be created with the tag name + const updatedPolicy = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`); + + expect(updatedPolicy?.rules?.approvalRules).toHaveLength(1); + expect(updatedPolicy?.rules?.approvalRules?.[0]?.applyWhen?.[0]?.value).toBe(tagName); + expect(updatedPolicy?.rules?.approvalRules?.[0]?.applyWhen?.[0]?.condition).toBe(CONST.POLICY.RULE_CONDITIONS.MATCHES); + expect(updatedPolicy?.rules?.approvalRules?.[0]?.applyWhen?.[0]?.field).toBe(CONST.POLICY.FIELDS.TAG); + expect(updatedPolicy?.rules?.approvalRules?.[0]?.approver).toBe('admin@company.com'); + + mockFetch.resume(); + await waitForBatchedUpdates(); + }); + + it('should update approval rule when tag approval rule is present', async () => { + // Given a policy with approval rules that reference a tag + const fakePolicy = createRandomPolicy(0); + fakePolicy.areTagsEnabled = true; + const tagListName = 'Fake tag'; + const fakePolicyTags = createRandomPolicyTags(tagListName, 1); + const tagName = Object.keys(fakePolicyTags?.[tagListName]?.tags).at(0) ?? ''; + + // Create approval rule that uses the tag + fakePolicy.rules = { + approvalRules: [ + { + id: 'rule-1', + applyWhen: [ + { + condition: CONST.POLICY.RULE_CONDITIONS.MATCHES, + field: CONST.POLICY.FIELDS.TAG, + value: tagName, + }, + ], + approver: 'admin2@company.com', + }, + ], + }; + + mockFetch.pause(); + + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags); + + setPolicyTagApprover(fakePolicy, tagName, 'admin@company.com'); + await waitForBatchedUpdates(); + + // Then the approval rule should be created with the tag name + const updatedPolicy = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`); + + expect(updatedPolicy?.rules?.approvalRules).toHaveLength(1); + expect(updatedPolicy?.rules?.approvalRules?.[0]?.id).toBe('rule-1'); + expect(updatedPolicy?.rules?.approvalRules?.[0]?.applyWhen?.[0]?.value).toBe(tagName); + expect(updatedPolicy?.rules?.approvalRules?.[0]?.applyWhen?.[0]?.condition).toBe(CONST.POLICY.RULE_CONDITIONS.MATCHES); + expect(updatedPolicy?.rules?.approvalRules?.[0]?.applyWhen?.[0]?.field).toBe(CONST.POLICY.FIELDS.TAG); + expect(updatedPolicy?.rules?.approvalRules?.[0]?.approver).toBe('admin@company.com'); + + mockFetch.resume(); + await waitForBatchedUpdates(); + }); + }); + describe('DeletePolicyTags', () => { it('should not modify Onyx data when policyTags is empty', async () => { const fakePolicy = createRandomPolicy(0);