Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions src/hooks/usePolicyData/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@ import {useAllReportsTransactionsAndViolations} from '@components/OnyxListItemPr
import useOnyx from '@hooks/useOnyx';
import usePolicy from '@hooks/usePolicy';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Policy, Report} from '@src/types/onyx';
import type {Report} from '@src/types/onyx';
import type {ReportTransactionsAndViolationsDerivedValue} from '@src/types/onyx/DerivedValues';
import type {OnyxValueWithOfflineFeedback} from '@src/types/onyx/OnyxCommon';
import type PolicyData from './types';

/**
* Retrieves policy tags, categories, reports and their associated transactions and violations.
* @param policyID The ID of the policy to retrieve data for.
* @returns An object containing policy data
*/
function usePolicyData(policyID?: string): PolicyData {
const policy = usePolicy(policyID);
function usePolicyData(policyID: string): PolicyData {
const allReportsTransactionsAndViolations = useAllReportsTransactionsAndViolations();

// Stable selector for useOnyx to avoid defining the selector inline
Expand All @@ -37,26 +35,28 @@ function usePolicyData(policyID?: string): PolicyData {
[policyID, allReportsTransactionsAndViolations],
);

const [tags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true}, [policyID]);
const policy = usePolicy(policyID);
const [tagLists] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true}, [policyID]);
const [categories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, {canBeMissing: true}, [policyID]);
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true, selector: reportsSelectorCallback}, [policyID, allReportsTransactionsAndViolations]);
const transactionsAndViolations = useMemo(() => {
if (!reports || !allReportsTransactionsAndViolations) {
return {};
}
return Object.keys(reports).reduce<ReportTransactionsAndViolationsDerivedValue>((acc, reportID) => {
if (allReportsTransactionsAndViolations[reportID]) {
return Object.entries(reports).reduce<ReportTransactionsAndViolationsDerivedValue>((acc, [reportID]) => {
if (Object.keys(allReportsTransactionsAndViolations[reportID].transactions).length > 0) {
acc[reportID] = allReportsTransactionsAndViolations[reportID];
}
return acc;
}, {});
}, [reports, allReportsTransactionsAndViolations]);
return {
transactionsAndViolations,
tags: tags ?? {},
tagLists: tagLists ?? {},
categories: categories ?? {},
policy: policy as OnyxValueWithOfflineFeedback<Policy>,
reports: Object.values(reports ?? {}) as Array<OnyxValueWithOfflineFeedback<Report>>,
policyID,
policy,
reports: Object.values(reports ?? {}),
};
}

Expand Down
11 changes: 6 additions & 5 deletions src/hooks/usePolicyData/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type {OnyxEntry} from 'react-native-onyx';
import type {Policy, PolicyCategories, PolicyTagLists, Report} from '@src/types/onyx';
import type {ReportTransactionsAndViolationsDerivedValue} from '@src/types/onyx/DerivedValues';
import type {OnyxValueWithOfflineFeedback} from '@src/types/onyx/OnyxCommon';

type PolicyData = {
policy: OnyxValueWithOfflineFeedback<Policy>;
tags: PolicyTagLists;
categories: PolicyCategories;
reports: Array<OnyxValueWithOfflineFeedback<Report>>;
policyID: string;
policy: OnyxEntry<Policy>;
tagLists: OnyxEntry<PolicyTagLists>;
categories: OnyxEntry<PolicyCategories>;
reports: Array<OnyxEntry<Report>>;
transactionsAndViolations: ReportTransactionsAndViolationsDerivedValue;
};

Expand Down
57 changes: 40 additions & 17 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1998,9 +1998,13 @@ function pushTransactionViolationsOnyxData(
categoriesUpdate: Record<string, Partial<PolicyCategory>> = {},
tagListsUpdate: Record<string, Partial<PolicyTagList>> = {},
) {
const {policy, tagLists, categories} = policyData;
if (policy === undefined) {
return;
}
const nonInvoiceReportTransactionsAndViolations = policyData.reports.reduce<ReportTransactionsAndViolations[]>((acc, report) => {
// Skipping invoice reports since they should not have any category or tag violations
if (isInvoiceReport(report)) {
if (report === undefined || isInvoiceReport(report)) {
return acc;
}
const reportTransactionsAndViolations = policyData.transactionsAndViolations[report.reportID];
Expand All @@ -2026,53 +2030,72 @@ function pushTransactionViolationsOnyxData(
}

// Merge the existing policy with the optimistic updates
const optimisticPolicy = isPolicyUpdateEmpty ? policyData.policy : {...policyData.policy, ...policyUpdate};
const optimisticPolicy = isPolicyUpdateEmpty ? policy : {...policy, ...policyUpdate};

// Merge the existing categories with the optimistic updates
const optimisticCategories = isCategoriesUpdateEmpty
? policyData.categories
? (categories ?? {})
: {
...Object.fromEntries(Object.entries(policyData.categories).filter(([categoryName]) => !(categoryName in categoriesUpdate) || !!categoriesUpdate[categoryName])),
...Object.fromEntries(Object.entries(categories ?? {}).filter(([categoryName]) => !(categoryName in categoriesUpdate) || !!categoriesUpdate[categoryName])),
...Object.entries(categoriesUpdate).reduce<PolicyCategories>((acc, [categoryName, categoryUpdate]) => {
if (!categoryUpdate) {
return acc;
}
const category = categories?.[categoryName];
if (category === undefined) {
acc[categoryName] = categoryUpdate as PolicyCategory;
return acc;
}

acc[categoryName] = {
...(policyData.categories?.[categoryName] ?? {}),
...category,
...categoryUpdate,
};
} as PolicyCategory;
return acc;
}, {}),
};

// Merge the existing tag lists with the optimistic updates
const optimisticTagLists = isTagListsUpdateEmpty
? policyData.tags
? (tagLists ?? {})
: {
...Object.fromEntries(Object.entries(policyData.tags ?? {}).filter(([tagListName]) => !(tagListName in tagListsUpdate) || !!tagListsUpdate[tagListName])),
...Object.fromEntries(Object.entries(tagLists ?? {}).filter(([tagListName]) => !(tagListName in tagListsUpdate) || !!tagListsUpdate[tagListName])),
...Object.entries(tagListsUpdate).reduce<PolicyTagLists>((acc, [tagListName, tagListUpdate]) => {
if (!tagListUpdate) {
if (!tagListName || !tagListUpdate) {
return acc;
}

const tagList = policyData.tags?.[tagListName];
const tags = tagList.tags ?? {};
const tagList = tagLists?.[tagListName];
if (!tagList) {
acc[tagListName] = tagListUpdate as PolicyTagList;
return acc;
}
const tags = tagList?.tags;
if (!tags) {
acc[tagListName] = {...tagList, ...tagListUpdate} as PolicyTagList;
return acc;
}
const tagsUpdate = tagListUpdate?.tags ?? {};

acc[tagListName] = {
...tagList,
...tagListUpdate,
tags: {
...((): PolicyTags => {
const optimisticTags: PolicyTags = Object.fromEntries(Object.entries(tags).filter(([tagName]) => !(tagName in tagsUpdate) || !!tagsUpdate[tagName]));
const optimisticTags: PolicyTags = Object.fromEntries(
Object.entries(tags ?? {}).filter(([tagName, tag]) => !tag || !(tagName in tagsUpdate) || !!tagsUpdate[tagName]),
);
for (const [tagName, tagUpdate] of Object.entries(tagsUpdate)) {
if (!tagUpdate) {
continue;
}
optimisticTags[tagName] = {
...(tags[tagName] ?? {}),
...tagUpdate,
};
const tag = tags?.[tagName];
optimisticTags[tagName] =
tag !== undefined
? {
...tag,
...tagUpdate,
}
: tagUpdate;
}
return optimisticTags;
})(),
Expand Down
81 changes: 47 additions & 34 deletions src/libs/actions/Policy/Category.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,11 +333,15 @@
setupCategoryTaskReport: OnyxEntry<Report>,
setupCategoryTaskParentReport: OnyxEntry<Report>,
currentUserAccountID: number,
hasOutstandingChildTask: boolean,

Check failure on line 336 in src/libs/actions/Policy/Category.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'hasOutstandingChildTask' is defined but never used

Check failure on line 336 in src/libs/actions/Policy/Category.ts

View workflow job for this annotation

GitHub Actions / ESLint check

'hasOutstandingChildTask' is defined but never used
parentReportAction: OnyxEntry<ReportAction> | undefined,

Check failure on line 337 in src/libs/actions/Policy/Category.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'parentReportAction' is defined but never used

Check failure on line 337 in src/libs/actions/Policy/Category.ts

View workflow job for this annotation

GitHub Actions / ESLint check

'parentReportAction' is defined but never used
) {
const policyID = policyData.policy?.id;
const policyCategoriesOptimisticData = {
const {policyID, categories} = policyData;
if (!categories) {
Log.warn('setWorkspaceCategoryEnabled invalid params', {categories});
return;
}
const categoriesOptimisticData = {
...Object.keys(categoriesToUpdate).reduce<PolicyCategories>((acc, key) => {
acc[key] = {
...categoriesToUpdate[key],
Expand All @@ -357,7 +361,7 @@
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: policyCategoriesOptimisticData,
value: categoriesOptimisticData,
},
],
successData: [
Expand Down Expand Up @@ -386,7 +390,7 @@
value: {
...Object.keys(categoriesToUpdate).reduce<PolicyCategories>((acc, key) => {
acc[key] = {
...policyData.categories[key],
...categories[key],
errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.categories.updateFailureMessage'),
pendingFields: {
enabled: null,
Expand All @@ -401,16 +405,8 @@
],
};

pushTransactionViolationsOnyxData(onyxData, policyData, {}, policyCategoriesOptimisticData);
appendSetupCategoriesOnboardingData(
onyxData,
setupCategoryTaskReport,
setupCategoryTaskParentReport,
isSetupCategoriesTaskParentReportArchived,
currentUserAccountID,
hasOutstandingChildTask,
parentReportAction,
);
pushTransactionViolationsOnyxData(onyxData, policyData, {}, categoriesOptimisticData);
appendSetupCategoriesOnboardingData(onyxData, setupCategoryTaskReport, setupCategoryTaskParentReport, isSetupCategoriesTaskParentReportArchived, currentUserAccountID);

Check failure on line 409 in src/libs/actions/Policy/Category.ts

View workflow job for this annotation

GitHub Actions / typecheck

Expected 7 arguments, but got 5.

const parameters = {
policyID,
Expand Down Expand Up @@ -552,9 +548,9 @@
}

function setPolicyCategoryReceiptsRequired(policyData: PolicyData, categoryName: string, maxAmountNoReceipt: number) {
const policyID = policyData.policy?.id;
const originalMaxAmountNoReceipt = policyData.categories[categoryName]?.maxAmountNoReceipt;
const policyCategoriesOptimisticData = {
const {policyID, categories} = policyData;
const originalMaxAmountNoReceipt = categories?.[categoryName]?.maxAmountNoReceipt;
const categoriesOptimisticData = {
[categoryName]: {
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
pendingFields: {
Expand All @@ -569,7 +565,7 @@
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: policyCategoriesOptimisticData,
value: categoriesOptimisticData,
},
],
successData: [
Expand Down Expand Up @@ -605,7 +601,7 @@
],
};

pushTransactionViolationsOnyxData(onyxData, policyData, {}, policyCategoriesOptimisticData);
pushTransactionViolationsOnyxData(onyxData, policyData, {}, categoriesOptimisticData);

const parameters: SetPolicyCategoryReceiptsRequiredParams = {
policyID,
Expand All @@ -617,9 +613,9 @@
}

function removePolicyCategoryReceiptsRequired(policyData: PolicyData, categoryName: string) {
const policyID = policyData.policy?.id;
const originalMaxAmountNoReceipt = policyData.categories[categoryName]?.maxAmountNoReceipt;
const policyCategoriesOptimisticData = {
const {policyID, categories} = policyData;
const originalMaxAmountNoReceipt = categories?.[categoryName]?.maxAmountNoReceipt;
const categoriesOptimisticData = {
[categoryName]: {
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
pendingFields: {
Expand All @@ -634,7 +630,7 @@
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: policyCategoriesOptimisticData,
value: categoriesOptimisticData,
},
],
successData: [
Expand Down Expand Up @@ -670,7 +666,7 @@
],
};

pushTransactionViolationsOnyxData(onyxData, policyData, {}, policyCategoriesOptimisticData);
pushTransactionViolationsOnyxData(onyxData, policyData, {}, categoriesOptimisticData);

const parameters: RemovePolicyCategoryReceiptsRequiredParams = {
policyID,
Expand Down Expand Up @@ -729,9 +725,8 @@
}

function renamePolicyCategory(policyData: PolicyData, policyCategory: {oldName: string; newName: string}) {
const policy = policyData.policy;
const policyID = policy.id;
const policyCategoryToUpdate = policyData.categories?.[policyCategory.oldName];
const {policyID, policy, categories} = policyData;
const policyCategoryToUpdate = categories?.[policyCategory.oldName];

const policyCategoryApproverRule = CategoryUtils.getCategoryApproverRule(policy?.rules?.approvalRules ?? [], policyCategory.oldName);
const policyCategoryExpenseRule = CategoryUtils.getCategoryExpenseRule(policy?.rules?.expenseRules ?? [], policyCategory.oldName);
Expand Down Expand Up @@ -776,7 +771,7 @@
mccGroup: updatedMccGroup,
};

const policyCategoriesOptimisticData = {
const categoriesOptimisticData = {
[policyCategory.newName]: {
...policyCategoryToUpdate,
errors: null,
Expand All @@ -797,7 +792,7 @@
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: {
[policyCategory.oldName]: null,
...policyCategoriesOptimisticData,
...categoriesOptimisticData,
},
},
{
Expand Down Expand Up @@ -860,7 +855,7 @@
return acc;
}, {});

pushTransactionViolationsOnyxData(onyxData, {...policyData, categories: policyCategories}, policyOptimisticData, policyCategoriesOptimisticData);
pushTransactionViolationsOnyxData(onyxData, {...policyData, categories: policyCategories}, policyOptimisticData, categoriesOptimisticData);

const parameters = {
policyID,
Expand Down Expand Up @@ -1007,7 +1002,7 @@
}

function setWorkspaceRequiresCategory(policyData: PolicyData, requiresCategory: boolean) {
const policyID = policyData.policy?.id;
const {policyID} = policyData;
const policyOptimisticData: Partial<Policy> = {
requiresCategory,
errors: {
Expand Down Expand Up @@ -1096,14 +1091,14 @@
hasOutstandingChildTask: boolean,
parentReportAction: OnyxEntry<ReportAction>,
) {
const policyID = policyData.policy?.id;
const {policyID, categories} = policyData;
const optimisticPolicyCategoriesData = categoryNamesToDelete.reduce<Record<string, Partial<PolicyCategory>>>((acc, categoryName) => {
acc[categoryName] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, enabled: false};
return acc;
}, {});

const shouldDisableRequiresCategory = !hasEnabledOptions(
Object.values(policyData.categories).filter((category) => !categoryNamesToDelete.includes(category.name) && category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE),
Object.values(categories ?? {}).filter((category) => !categoryNamesToDelete.includes(category.name) && category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE),
);
const optimisticPolicyData: Partial<Policy> = shouldDisableRequiresCategory
? {
Expand Down Expand Up @@ -1165,7 +1160,7 @@
}

function enablePolicyCategories(policyData: PolicyData, enabled: boolean, shouldGoBack = true) {
const policyID = policyData.policy?.id;
const {policyID, categories} = policyData;
const policyUpdate: Partial<Policy> = {
areCategoriesEnabled: enabled,
requiresCategory: enabled,
Expand All @@ -1173,8 +1168,8 @@
areCategoriesEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
};
const policyCategoriesUpdate: Record<string, Partial<PolicyCategory>> = Object.fromEntries(

Check failure on line 1171 in src/libs/actions/Policy/Category.ts

View workflow job for this annotation

GitHub Actions / typecheck

Cannot redeclare block-scoped variable 'policyCategoriesUpdate'.
Object.entries(policyData.categories).map(([categoryName]) => [

Check failure on line 1172 in src/libs/actions/Policy/Category.ts

View workflow job for this annotation

GitHub Actions / typecheck

No overload matches this call.
categoryName,
{
enabled,
Expand Down Expand Up @@ -1210,7 +1205,7 @@
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: Object.fromEntries(
Object.entries(policyData.categories).map(([categoryName]) => [

Check failure on line 1208 in src/libs/actions/Policy/Category.ts

View workflow job for this annotation

GitHub Actions / typecheck

No overload matches this call.
categoryName,
{
enabled: !enabled,
Expand All @@ -1232,6 +1227,24 @@
],
};

let policyCategoriesUpdate: Record<string, Partial<PolicyCategory>> = {};

Check failure on line 1230 in src/libs/actions/Policy/Category.ts

View workflow job for this annotation

GitHub Actions / typecheck

Cannot redeclare block-scoped variable 'policyCategoriesUpdate'.

Check failure on line 1230 in src/libs/actions/Policy/Category.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'policyCategoriesUpdate' is already defined

Check failure on line 1230 in src/libs/actions/Policy/Category.ts

View workflow job for this annotation

GitHub Actions / ESLint check

'policyCategoriesUpdate' is already defined

if (!enabled) {
policyCategoriesUpdate = Object.fromEntries(

Check failure on line 1233 in src/libs/actions/Policy/Category.ts

View workflow job for this annotation

GitHub Actions / typecheck

Cannot assign to 'policyCategoriesUpdate' because it is a constant.
Object.entries(categories ?? {}).map(([categoryName]) => [
categoryName,
{
enabled: false,
},
]),
);
onyxData.optimisticData?.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: policyCategoriesUpdate,
});
}

pushTransactionViolationsOnyxData(onyxData, policyData, policyUpdate, policyCategoriesUpdate);

const parameters: EnablePolicyCategoriesParams = {policyID, enabled};
Expand Down
Loading
Loading