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
12 changes: 8 additions & 4 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ function getRulesRevampRuleEditSegment(categoryName: string): string {
return `edit/${encodeURIComponent(categoryName)}`;
}

function getOptionalCategoryNameQuery(categoryName?: string): string {
return categoryName ? `?categoryName=${encodeURIComponent(categoryName)}` : '';
}

// This is a file containing constants for all the routes we want to be able to go to

/**
Expand Down Expand Up @@ -3198,7 +3202,7 @@ const ROUTES = {
},
RULES_REQUIRE_FIELDS_RULE_NEW: {
route: 'workspaces/:policyID/rules/require-fields-rules/new',
getRoute: (policyID: string) => `workspaces/${policyID}/rules/require-fields-rules/new` as const,
getRoute: (policyID: string, categoryName?: string) => `workspaces/${policyID}/rules/require-fields-rules/new${getOptionalCategoryNameQuery(categoryName)}` as const,
},
RULES_REQUIRE_FIELDS_RULE_EDIT: {
route: 'workspaces/:policyID/rules/require-fields-rules/edit/:categoryName',
Expand All @@ -3214,7 +3218,7 @@ const ROUTES = {
},
RULES_FLAG_FOR_REVIEW_RULE_NEW: {
route: 'workspaces/:policyID/rules/flag-for-review-rules/new',
getRoute: (policyID: string) => `workspaces/${policyID}/rules/flag-for-review-rules/new` as const,
getRoute: (policyID: string, categoryName?: string) => `workspaces/${policyID}/rules/flag-for-review-rules/new${getOptionalCategoryNameQuery(categoryName)}` as const,
},
RULES_FLAG_FOR_REVIEW_RULE_EDIT: {
route: 'workspaces/:policyID/rules/flag-for-review-rules/edit/:categoryName',
Expand Down Expand Up @@ -3278,11 +3282,11 @@ const ROUTES = {
},
RULES_NEW: {
route: 'workspaces/:policyID/rules/new',
getRoute: (policyID: string) => `workspaces/${policyID}/rules/new` as const,
getRoute: (policyID: string, categoryName?: string) => `workspaces/${policyID}/rules/new${getOptionalCategoryNameQuery(categoryName)}` as const,
},
RULES_MERCHANT_NEW: {
route: 'workspaces/:policyID/rules/merchant-rules/new',
getRoute: (policyID: string) => `workspaces/${policyID}/rules/merchant-rules/new` as const,
getRoute: (policyID: string, categoryName?: string) => `workspaces/${policyID}/rules/merchant-rules/new${getOptionalCategoryNameQuery(categoryName)}` as const,
},
RULES_MERCHANT_IMPORT: {
route: 'workspaces/:policyID/rules/merchant-rules/import',
Expand Down
3 changes: 3 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7910,6 +7910,9 @@ const translations = {
},
defaultTaxRate: 'Default tax rate',
enableWorkflows: (moreFeaturesLink: string) => `Go to [More features](${moreFeaturesLink}) and enable workflows, then add approvals to unlock this feature.`,
createNewRule: 'Create new rule',
contextualFlagForReview: (amount: string) => `If amount is above ${amount}, flag for review`,
contextualFlagForReviewDaily: (amount: string) => `If daily category total is above ${amount}, flag for review`,
},
customRules: {
title: 'Expense policy',
Expand Down
97 changes: 97 additions & 0 deletions src/libs/CategoryContextualRulesUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* Helpers for listing Rules Revamp rules that apply to a specific workspace category,
* used by the category details RHP contextual "Category rules" section.
*/
import type {LocaleContextProps} from '@components/LocaleContextProvider';

import type {CurrencyListActionsContextType} from '@hooks/useCurrencyList';

import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {Route} from '@src/ROUTES';
import type {Policy, PolicyCategory} from '@src/types/onyx';
import type {PendingAction} from '@src/types/onyx/OnyxCommon';

import {hasExplicitFlagAmount} from './FlagForReviewRulesUtils';
import {categoryHasAnyRequireFieldsRule, formatRequireFieldsRuleDescriptions, getRequireFieldsPendingAction, getRequireFieldsRuleDescriptionsForCategory} from './RequireFieldsRulesUtils';

type CategoryContextualRule = {
key: string;
summary: string;
route: Route;
pendingAction?: PendingAction;
};

function getFlagForReviewContextualSummary(
category: PolicyCategory,
translate: LocaleContextProps['translate'],
convertToDisplayString: CurrencyListActionsContextType['convertToDisplayString'],
policyCurrency: string,
): string | undefined {
if (!hasExplicitFlagAmount(category.maxExpenseAmount)) {
return undefined;
}

const amountDisplay = convertToDisplayString(category.maxExpenseAmount, policyCurrency);
const expenseLimitType = category.expenseLimitType ?? CONST.POLICY.EXPENSE_LIMIT_TYPES.EXPENSE;

if (expenseLimitType === CONST.POLICY.EXPENSE_LIMIT_TYPES.DAILY) {
return translate('workspace.rules.categoryRules.contextualFlagForReviewDaily', amountDisplay);
}

return translate('workspace.rules.categoryRules.contextualFlagForReview', amountDisplay);
}

/**
* Returns Rules Revamp rules scoped to a category for display in the category details RHP.
* Each item navigates to the same edit route used from the centralized Rules page.
*/
function getCategoryContextualRules({
policy,
category,
categoryName,
translate,
convertToDisplayString,
}: {
policy: Policy | undefined;
category: PolicyCategory | undefined;
categoryName: string;
translate: LocaleContextProps['translate'];
convertToDisplayString: CurrencyListActionsContextType['convertToDisplayString'];
}): CategoryContextualRule[] {
if (!policy?.id || !category) {
return [];
}

const policyID = policy.id;
const policyCurrency = policy.outputCurrency ?? CONST.CURRENCY.USD;
const rules: CategoryContextualRule[] = [];

const flagSummary = getFlagForReviewContextualSummary(category, translate, convertToDisplayString, policyCurrency);
if (flagSummary) {
rules.push({
key: `flag-for-review-${categoryName}`,
summary: flagSummary,
route: ROUTES.RULES_FLAG_FOR_REVIEW_RULE_EDIT.getRoute(policyID, categoryName),
pendingAction: category.pendingFields?.maxExpenseAmount,
});
}

if (categoryHasAnyRequireFieldsRule(category)) {
const descriptions = getRequireFieldsRuleDescriptionsForCategory(category, translate, convertToDisplayString, policyCurrency);
const summary = formatRequireFieldsRuleDescriptions(descriptions);
if (summary) {
rules.push({
key: `require-fields-${categoryName}`,
summary,
route: ROUTES.RULES_REQUIRE_FIELDS_RULE_EDIT.getRoute(policyID, categoryName),
pendingAction: getRequireFieldsPendingAction(category.pendingFields),
});
}
}

return rules;
}

export {getCategoryContextualRules};
export type {CategoryContextualRule};
10 changes: 9 additions & 1 deletion src/libs/FlagForReviewRulesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,5 +168,13 @@ function getFlagForReviewTableData({
return rules;
}

export {deleteFlagForReviewRule, getEffectiveFlagForReviewRuleForm, getFlagForReviewFormFromCategory, getFlagForReviewRuleAmountError, getFlagForReviewTableData, saveFlagForReviewRule};
export {
deleteFlagForReviewRule,
getEffectiveFlagForReviewRuleForm,
getFlagForReviewFormFromCategory,
getFlagForReviewRuleAmountError,
getFlagForReviewTableData,
hasExplicitFlagAmount,
saveFlagForReviewRule,
};
export type {FlagForReviewTableItem};
4 changes: 4 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1552,9 +1552,11 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.RULES_NEW]: {
policyID: string;
categoryName?: string;
};
[SCREENS.WORKSPACE.RULES_MERCHANT_NEW]: {
policyID: string;
categoryName?: string;
};
[SCREENS.WORKSPACE.RULES_MERCHANT_IMPORT]: {
policyID: string;
Expand All @@ -1567,6 +1569,7 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.RULES_REQUIRE_FIELDS_RULE_NEW]: {
policyID: string;
categoryName?: string;
};
[SCREENS.WORKSPACE.RULES_REQUIRE_FIELDS_RULE_EDIT]: {
policyID: string;
Expand All @@ -1581,6 +1584,7 @@ type SettingsNavigatorParamList = {
};
[SCREENS.WORKSPACE.RULES_FLAG_FOR_REVIEW_RULE_NEW]: {
policyID: string;
categoryName?: string;
};
[SCREENS.WORKSPACE.RULES_FLAG_FOR_REVIEW_RULE_EDIT]: {
policyID: string;
Expand Down
13 changes: 12 additions & 1 deletion src/libs/RequireFieldsRulesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,5 +314,16 @@ function getRequireFieldsTableData({
return rules.sort((a, b) => localeCompare(a.conditionText, b.conditionText));
}

export {categoryHasLegacyReceiptRules, deleteRequireFieldsRule, getEffectiveRequireFieldsRuleForm, getRequireFieldsFormFromCategory, getRequireFieldsTableData, saveRequireFieldsRule};
export {
categoryHasAnyRequireFieldsRule,
categoryHasLegacyReceiptRules,
deleteRequireFieldsRule,
formatRequireFieldsRuleDescriptions,
getEffectiveRequireFieldsRuleForm,
getRequireFieldsFormFromCategory,
getRequireFieldsPendingAction,
getRequireFieldsRuleDescriptionsForCategory,
getRequireFieldsTableData,
saveRequireFieldsRule,
};
export type {RequireFieldsTableItem};
Loading
Loading