diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index e622be4f23a3..348c096a970e 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -321,10 +321,20 @@ function getCustomUnitsForDuplication( return undefined; } - if (isDistanceRatesOptionSelected && isPerDiemOptionSelected) { - const distanceCustomUnit = Object.values(customUnits).find((customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); - const perDiemUnit = Object.values(customUnits).find((customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_PER_DIEM_INTERNATIONAL); + const getUnitWithoutPendingDeleteRates = (customUnit: CustomUnit | undefined) => { + if (!customUnit) { + return undefined; + } + return { + ...customUnit, + rates: Object.fromEntries(Object.entries(customUnit.rates).filter(([, rate]) => rate.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)), + }; + }; + + const distanceCustomUnit = getUnitWithoutPendingDeleteRates(Object.values(customUnits).find((customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE)); + const perDiemUnit = getUnitWithoutPendingDeleteRates(Object.values(customUnits).find((customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_PER_DIEM_INTERNATIONAL)); + if (isDistanceRatesOptionSelected && isPerDiemOptionSelected) { if (!perDiemUnit || !distanceCustomUnit || !perDiemCustomUnitID || !distanceCustomUnitID) { return undefined; } @@ -333,14 +343,12 @@ function getCustomUnitsForDuplication( } if (isDistanceRatesOptionSelected && distanceCustomUnitID) { - const distanceCustomUnit = Object.values(customUnits).find((customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); if (!distanceCustomUnit) { return undefined; } return {[distanceCustomUnitID]: distanceCustomUnit}; } - const perDiemUnit = Object.values(customUnits).find((customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_PER_DIEM_INTERNATIONAL); if (!perDiemUnit || !perDiemCustomUnitID) { return undefined; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f3000e9c33bc..77b1212c7842 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4566,7 +4566,7 @@ function getReportFieldKey(reportFieldId: string | undefined) { /** * Get the report fields attached to the policy given policyID */ -function getReportFieldsByPolicyID(policyID: string | undefined): Record { +function getReportFieldsByPolicyID(policyID: string | undefined): Policy['fieldList'] { if (!policyID) { return {}; } diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 7223a9a81863..293a18f026ad 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -104,6 +104,9 @@ function appendSetupCategoriesOnboardingData( function buildOptimisticPolicyWithExistingCategories(policyID: string, categories: PolicyCategories) { const categoriesValues = Object.values(categories); const optimisticCategoryMap = categoriesValues.reduce>>((acc, category) => { + if (category.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { + return acc; + } acc[category.name] = { ...category, errors: null, diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 119cf0e270f3..032bb4634dac 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -3095,6 +3095,98 @@ function createDraftWorkspace( return params; } +function buildOptimisticDuplicatePolicy(sourcePolicy: Policy, policyOptions: DuplicatePolicyDataOptions) { + const { + policyName: duplicatedPolicyName = '', + targetPolicyID: duplicatedPolicyID, + file: duplicatedPolicyFile, + parts: duplicatedParts, + localCurrency: duplicatedLocalCurrency, + } = policyOptions; + + const isMemberFeatureSelected = duplicatedParts?.people; + const isReportsFeatureSelected = duplicatedParts?.reports; + const isConnectionsFeatureSelected = duplicatedParts?.connections; + const isTaxesFeatureSelected = duplicatedParts?.taxes; + const isTagsFeatureSelected = duplicatedParts?.tags; + const isInvoicesFeatureSelected = duplicatedParts?.invoices; + const isDistanceRatesFeatureSelected = duplicatedParts?.distance; + const isRulesFeatureSelected = duplicatedParts?.expenses; + const isWorkflowsFeatureSelected = duplicatedParts?.exportLayouts; + const isPerDiemFeatureSelected = duplicatedParts?.perDiem; + const isOverviewFeatureSelected = duplicatedParts?.overview; + const isTravelFeatureSelected = duplicatedParts?.travel; + const isCodingRulesFeatureSelected = duplicatedParts?.codingRules; + const duplicatedOutputCurrency = isOverviewFeatureSelected ? sourcePolicy?.outputCurrency : duplicatedLocalCurrency; + const {customUnitID: duplicatedDistanceCustomUnitID} = buildOptimisticDistanceRateCustomUnits(duplicatedOutputCurrency); + const duplicatedPerDiemCustomUnitID = generateCustomUnitID(); + + const filterPendingDeleteData = (data?: Record): Record | undefined => + data + ? (Object.fromEntries( + Object.entries(data).filter(([, value]) => { + if (!value || typeof value !== 'object' || !('pendingAction' in value)) { + return true; + } + return value.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + }), + ) as Record) + : undefined; + + const codingRulesWithoutPendingDelete = filterPendingDeleteData(sourcePolicy?.rules?.codingRules); + const employeeListWithoutPendingDelete = filterPendingDeleteData(sourcePolicy?.employeeList); + const fieldListWithoutPendingDelete = filterPendingDeleteData(sourcePolicy?.fieldList); + const connectionsWithoutPendingDelete = filterPendingDeleteData(sourcePolicy?.connections); + const taxRatesWithoutPendingDelete = { + ...sourcePolicy?.taxRates, + taxes: filterPendingDeleteData(sourcePolicy?.taxRates?.taxes), + }; + + return { + ...sourcePolicy, + areCategoriesEnabled: true, + areTagsEnabled: isTagsFeatureSelected, + areDistanceRatesEnabled: isDistanceRatesFeatureSelected, + areInvoicesEnabled: isInvoicesFeatureSelected, + areRulesEnabled: isRulesFeatureSelected, + areWorkflowsEnabled: isWorkflowsFeatureSelected, + areReportFieldsEnabled: isReportsFeatureSelected, + areConnectionsEnabled: isConnectionsFeatureSelected, + arePerDiemRatesEnabled: isPerDiemFeatureSelected, + isTravelEnabled: isTravelFeatureSelected ? sourcePolicy?.isTravelEnabled : undefined, + travelSettings: undefined, + workspaceAccountID: undefined, + tax: isTaxesFeatureSelected ? sourcePolicy?.tax : undefined, + employeeList: isMemberFeatureSelected ? employeeListWithoutPendingDelete : {[sourcePolicy.owner]: sourcePolicy?.employeeList?.[sourcePolicy.owner]}, + id: duplicatedPolicyID, + name: duplicatedPolicyName, + fieldList: isReportsFeatureSelected ? fieldListWithoutPendingDelete : undefined, + connections: isConnectionsFeatureSelected ? connectionsWithoutPendingDelete : undefined, + customUnits: getCustomUnitsForDuplication(sourcePolicy, isDistanceRatesFeatureSelected, isPerDiemFeatureSelected, { + distanceCustomUnitID: duplicatedDistanceCustomUnitID, + perDiemCustomUnitID: duplicatedPerDiemCustomUnitID, + }), + taxRates: isTaxesFeatureSelected ? taxRatesWithoutPendingDelete : undefined, + rules: isCodingRulesFeatureSelected ? {codingRules: codingRulesWithoutPendingDelete} : undefined, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + pendingFields: { + autoReporting: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + approvalMode: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + reimbursementChoice: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + name: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + outputCurrency: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + address: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + description: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + type: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + areReportFieldsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + avatarURL: duplicatedPolicyFile?.uri, + originalFileName: duplicatedPolicyFile?.name, + outputCurrency: duplicatedOutputCurrency, + address: isOverviewFeatureSelected ? sourcePolicy?.address : undefined, + }; +} + function buildDuplicatePolicyData(policy: Policy, options: DuplicatePolicyDataOptions) { const {policyName = '', policyID = generatePolicyID(), file, welcomeNote, parts, targetPolicyID = generatePolicyID(), policyCategories, localCurrency} = options; @@ -3110,19 +3202,8 @@ function buildDuplicatePolicyData(policy: Policy, options: DuplicatePolicyDataOp pendingChatMembers, } = ReportUtils.buildOptimisticWorkspaceChats(targetPolicyID, policyName); const isMemberOptionSelected = parts?.people; - const isReportsOptionSelected = parts?.reports; - const isConnectionsOptionSelected = parts?.connections; const isCategoriesOptionSelected = parts?.categories; - const isTaxesOptionSelected = parts?.taxes; - const isTagsOptionSelected = parts?.tags; - const isInvoicesOptionSelected = parts?.invoices; - const isDistanceRatesOptionSelected = parts?.distance; - const isRulesOptionSelected = parts?.expenses; - const isWorkflowsOptionSelected = parts?.exportLayouts; - const isPerDiemOptionSelected = parts?.perDiem; const isOverviewOptionSelected = parts?.overview; - const isTravelOptionSelected = parts?.travel; - const isCodingRulesOptionSelected = parts?.codingRules; const outputCurrency = isOverviewOptionSelected ? policy?.outputCurrency : localCurrency; @@ -3155,46 +3236,7 @@ function buildDuplicatePolicyData(policy: Policy, options: DuplicatePolicyDataOp { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.POLICY}${targetPolicyID}`, - value: { - ...policy, - areCategoriesEnabled: true, - areTagsEnabled: isTagsOptionSelected, - areDistanceRatesEnabled: isDistanceRatesOptionSelected, - areInvoicesEnabled: isInvoicesOptionSelected, - areRulesEnabled: isRulesOptionSelected, - areWorkflowsEnabled: isWorkflowsOptionSelected, - areReportFieldsEnabled: isReportsOptionSelected, - areConnectionsEnabled: isConnectionsOptionSelected, - arePerDiemRatesEnabled: isPerDiemOptionSelected, - isTravelEnabled: isTravelOptionSelected ? policy?.isTravelEnabled : undefined, - travelSettings: undefined, - workspaceAccountID: undefined, - tax: isTaxesOptionSelected ? policy?.tax : undefined, - employeeList: isMemberOptionSelected ? policy.employeeList : {[policy.owner]: policy?.employeeList?.[policy.owner]}, - id: targetPolicyID, - name: policyName, - fieldList: isReportsOptionSelected ? policy?.fieldList : undefined, - connections: isConnectionsOptionSelected ? policy?.connections : undefined, - customUnits: getCustomUnitsForDuplication(policy, isDistanceRatesOptionSelected, isPerDiemOptionSelected, {distanceCustomUnitID, perDiemCustomUnitID}), - taxRates: isTaxesOptionSelected ? policy?.taxRates : undefined, - rules: isCodingRulesOptionSelected ? {codingRules: policy?.rules?.codingRules} : undefined, - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - pendingFields: { - autoReporting: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - approvalMode: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - reimbursementChoice: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - name: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - outputCurrency: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - address: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - description: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - type: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - areReportFieldsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - }, - avatarURL: file?.uri, - originalFileName: file?.name, - outputCurrency, - address: isOverviewOptionSelected ? policy?.address : undefined, - }, + value: buildOptimisticDuplicatePolicy(policy, {...options, targetPolicyID}), }, { onyxMethod: Onyx.METHOD.MERGE, diff --git a/src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx b/src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx index 448157300bd9..7655b7c3508e 100644 --- a/src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx +++ b/src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx @@ -38,18 +38,18 @@ function WorkspaceDuplicateSelectFeaturesForm({policyID}: WorkspaceDuplicateForm const [duplicateWorkspace] = useOnyx(ONYXKEYS.DUPLICATE_WORKSPACE); const [duplicatedWorkspaceAvatar, setDuplicatedWorkspaceAvatar] = useState(); const [isDuplicateModalOpen, setIsDuplicateModalOpen] = useState(false); - const allIds = getMemberAccountIDsForWorkspace(policy?.employeeList); + const allIds = getMemberAccountIDsForWorkspace(policy?.employeeList, false, false); const totalMembers = Object.keys(allIds).length; const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`); - const taxesLength = Object.keys(policy?.taxRates?.taxes ?? {}).length ?? 0; + const taxesLength = Object.values(policy?.taxRates?.taxes ?? {}).filter((tax) => tax.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length ?? 0; const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`); - const categoriesCount = Object.keys(policyCategories ?? {}).length; - const codingRulesCount = Object.keys(policy?.rules?.codingRules ?? {}).length; + const categoriesCount = Object.values(policyCategories ?? {}).filter((category) => category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length; + const codingRulesCount = Object.values(policy?.rules?.codingRules ?? {}).filter((rule) => rule.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length; const [selectedItems, setSelectedItems] = useState([]); - const reportFields = Object.keys(getReportFieldsByPolicyID(policyID)).length ?? 0; + const reportFields = Object.values(getReportFieldsByPolicyID(policyID) ?? {}).filter((field) => field.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length ?? 0; const customUnits = getPerDiemCustomUnit(policy); const customUnitRates: Record = customUnits?.rates ?? {}; - const allRates = Object.values(customUnitRates)?.length; + const allRates = Object.values(customUnitRates)?.filter((rate) => rate.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length ?? 0; const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); @@ -57,7 +57,7 @@ function WorkspaceDuplicateSelectFeaturesForm({policyID}: WorkspaceDuplicateForm const connectedIntegration = getAllValidConnectedIntegration(policy, accountingIntegrations); const customUnit = getDistanceRateCustomUnit(policy); - const ratesCount = Object.keys(customUnit?.rates ?? {}).length; + const ratesCount = Object.values(customUnit?.rates ?? {}).filter((rate) => rate.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length; const invoiceCompany = policy?.invoice?.companyName && policy?.invoice?.companyWebsite ? `${policy?.invoice?.companyName}, ${policy?.invoice?.companyWebsite}` @@ -67,7 +67,10 @@ function WorkspaceDuplicateSelectFeaturesForm({policyID}: WorkspaceDuplicateForm if (!policyTags) { return 0; } - return Object.values(policyTags).reduce((sum, tagGroup) => sum + Number(Object.values(tagGroup.tags)?.length ?? 0), 0); + return Object.values(policyTags).reduce( + (sum, tagGroup) => sum + Number(Object.values(tagGroup.tags)?.filter((tag) => tag.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length ?? 0), + 0, + ); }, [policyTags]); const formattedAddress = !isEmptyObject(policy) && !isEmptyObject(policy.address) ? formatAddressToString(policy.address) : ''; diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index faa07b079409..03d323c80318 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -270,7 +270,10 @@ describe('actions/Policy', () => { it('duplicate workspace', async () => { (fetch as MockFetch)?.pause?.(); await Onyx.set(ONYXKEYS.SESSION, {email: ESH_EMAIL, accountID: ESH_ACCOUNT_ID}); - const fakePolicy = createRandomPolicy(10, CONST.POLICY.TYPE.PERSONAL); + const fakePolicy = { + ...createRandomPolicy(10, CONST.POLICY.TYPE.PERSONAL), + employeeList: {}, + }; await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); await Onyx.set(`${ONYXKEYS.NVP_ACTIVE_POLICY_ID}`, fakePolicy.id); await Onyx.set(`${ONYXKEYS.NVP_INTRO_SELECTED}`, {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM});