Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
"Gclid",
"Geral",
"gitlink",
"Globex",
"Grantmaking",
"Gsuite",
"Générale",
Expand Down
4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3743,6 +3743,10 @@ const ROUTES = {
route: 'workspaces/:policyID/rules/merchant-rules/:ruleID/tax',
getRoute: (policyID: string, ruleID?: string) => `workspaces/${policyID}/rules/merchant-rules/${ruleID ?? 'new'}/tax` as const,
},
RULES_MERCHANT_VENDOR: {
route: 'workspaces/:policyID/rules/merchant-rules/:ruleID/vendor',
getRoute: (policyID: string, ruleID?: string) => `workspaces/${policyID}/rules/merchant-rules/${ruleID ?? 'new'}/vendor` as const,
},
RULES_MERCHANT_DESCRIPTION: {
route: 'workspaces/:policyID/rules/merchant-rules/:ruleID/description',
getRoute: (policyID: string, ruleID?: string) => `workspaces/${policyID}/rules/merchant-rules/${ruleID ?? 'new'}/description` as const,
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,7 @@ const SCREENS = {
RULES_MERCHANT_CATEGORY: 'Rules_Merchant_Category',
RULES_MERCHANT_TAG: 'Rules_Merchant_Tag',
RULES_MERCHANT_TAX: 'Rules_Merchant_Tax',
RULES_MERCHANT_VENDOR: 'Rules_Merchant_Vendor',
RULES_MERCHANT_DESCRIPTION: 'Rules_Merchant_Description',
RULES_MERCHANT_REIMBURSABLE: 'Rules_Merchant_Reimbursable',
RULES_MERCHANT_BILLABLE: 'Rules_Merchant_Billable',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7665,6 +7665,8 @@ Fordern Sie Spesendetails wie Belege und Beschreibungen an, legen Sie Limits und
expenseDefaultsSubtitle: 'Felder aktualisieren, ohne dass die einreichende Person etwas tun muss',
ifAnyExpenseMatches: 'Wenn eine Ausgabe übereinstimmt:',
thenApplyFollowingDefaults: 'Wenden Sie dann die folgenden Standardwerte an:',
vendorUnavailable: 'Anbieter nicht verfügbar',
supplierUnavailable: 'Lieferant nicht verfügbar',
},
categoryRules: {
title: 'Kategorienregeln',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7987,6 +7987,8 @@ ${reportName}`,
importColumnUpdatedCategory: 'Ενημερωμένη κατηγορία',
importColumnUpdatedTag: 'Ενημερωμένη ετικέτα',
importColumnUpdatedDescription: 'Ενημερωμένη περιγραφή',
vendorUnavailable: 'Ο προμηθευτής δεν είναι διαθέσιμος',
supplierUnavailable: 'Ο προμηθευτής δεν είναι διαθέσιμος',
},
newRule: {
title: 'Νέος κανόνας',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7893,6 +7893,8 @@ const translations = {
ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Update ${fieldName} to "${fieldValue}"`,
ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Mark as "${reimbursable ? 'reimbursable' : 'non-reimbursable'}"`,
ruleSummarySubtitleBillable: (billable: boolean) => `Mark as "${billable ? 'billable' : 'non-billable'}"`,
vendorUnavailable: 'Vendor unavailable',
supplierUnavailable: 'Supplier unavailable',
matchType: 'Match type',
matchTypeContains: 'Contains',
matchTypeExact: 'Exactly matches',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7623,6 +7623,8 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`,
ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Actualizar ${fieldName} a "${fieldValue}"`,
ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Marcar como "${reimbursable ? 'reembolsable' : 'no reembolsable'}"`,
ruleSummarySubtitleBillable: (billable: boolean) => `Marcar como "${billable ? 'facturable' : 'no facturable'}"`,
vendorUnavailable: 'Proveedor no disponible',
supplierUnavailable: 'Proveedor no disponible',
matchType: 'Tipo de coincidencia',
matchTypeContains: 'Contiene',
matchTypeExact: 'Coincide exactamente',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7690,6 +7690,8 @@ Rendez obligatoires des informations de dépense comme les reçus et les descrip
expenseDefaultsSubtitle: 'Mettre à jour les champs sans que le déclarant ne fasse quoi que ce soit',
ifAnyExpenseMatches: 'Si une dépense correspond :',
thenApplyFollowingDefaults: 'Appliquez ensuite les valeurs par défaut suivantes :',
vendorUnavailable: 'Fournisseur indisponible',
supplierUnavailable: 'Fournisseur indisponible',
},
categoryRules: {
title: 'Règles de catégorie',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7633,6 +7633,8 @@ Richiedi dettagli sulle spese come ricevute e descrizioni, imposta limiti e valo
expenseDefaultsSubtitle: 'Aggiorna i campi senza che chi invia debba fare nulla',
ifAnyExpenseMatches: 'Se una qualsiasi spesa corrisponde a:',
thenApplyFollowingDefaults: 'Quindi applica le seguenti impostazioni predefinite:',
vendorUnavailable: 'Fornitore non disponibile',
supplierUnavailable: 'Fornitore non disponibile',
},
categoryRules: {
title: 'Regole di categoria',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7545,6 +7545,8 @@ ${reportName}`,
expenseDefaultsSubtitle: '申請者が何も操作しなくてもフィールドを更新する',
ifAnyExpenseMatches: 'いずれかの経費が次の条件に一致する場合:',
thenApplyFollowingDefaults: '次に、以下のデフォルトを適用します。',
vendorUnavailable: 'ベンダーを利用できません',
supplierUnavailable: 'サプライヤーを利用できません',
},
categoryRules: {
title: 'カテゴリルール',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7617,6 +7617,8 @@ Vereis onkostendetails zoals bonnen en beschrijvingen, stel limieten en standaar
expenseDefaultsSubtitle: 'Velden bijwerken zonder dat de indiener iets hoeft te doen',
ifAnyExpenseMatches: 'Als een uitgave overeenkomt:',
thenApplyFollowingDefaults: 'Pas vervolgens de volgende standaardinstellingen toe:',
vendorUnavailable: 'Leverancier niet beschikbaar',
supplierUnavailable: 'Leverancier niet beschikbaar',
},
categoryRules: {
title: 'Categorisatieregels',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7596,6 +7596,8 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i
expenseDefaultsSubtitle: 'Aktualizuj pola bez wymagania działania od osoby zgłaszającej',
ifAnyExpenseMatches: 'Jeśli którykolwiek wydatek pasuje:',
thenApplyFollowingDefaults: 'Następnie zastosuj następujące domyślne ustawienia:',
vendorUnavailable: 'Dostawca niedostępny',
supplierUnavailable: 'Dostawca niedostępny',
},
categoryRules: {
title: 'Reguły kategorii',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7614,6 +7614,8 @@ Exija dados de despesas como recibos e descrições, defina limites e padrões e
expenseDefaultsSubtitle: 'Atualizar campos sem o responsável pelo envio fazer nada',
ifAnyExpenseMatches: 'Se alguma despesa corresponder:',
thenApplyFollowingDefaults: 'Em seguida, aplica os seguintes padrões:',
vendorUnavailable: 'Fornecedor indisponível',
supplierUnavailable: 'Fornecedor indisponível',
},
categoryRules: {
title: 'Regras de categoria',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7360,6 +7360,8 @@ ${reportName}`,
expenseDefaultsSubtitle: '在提交人无须执行任何操作的情况下更新字段',
ifAnyExpenseMatches: '如果任一报销符合以下条件:',
thenApplyFollowingDefaults: '然后应用以下默认设置:',
vendorUnavailable: '供应商不可用',
supplierUnavailable: '供应商不可用',
},
categoryRules: {
title: '类别规则',
Expand Down
45 changes: 41 additions & 4 deletions src/libs/MerchantTypeRulesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import type {ExpenseDefaultTableItem} from '@components/Tables/WorkspaceExpenseD
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {Route} from '@src/ROUTES';
import type {MerchantTypeRuleForm} from '@src/types/form/MerchantTypeRuleForm';
import INPUT_IDS from '@src/types/form/MerchantTypeRuleForm';
import type {MerchantTypeRuleForm} from '@src/types/form/MerchantTypeRuleForm';
import type {Policy} from '@src/types/onyx';
import type {CodingRule} from '@src/types/onyx/Policy';

Expand All @@ -15,7 +15,7 @@ import {clearPolicyCodingRuleErrors} from './actions/Policy/Rules';
import {getDecodedCategoryName} from './CategoryUtils';
import Parser from './Parser';
import {getMccGroupDisplayName} from './PolicyRulesUtils';
import {getCommaSeparatedTagNameWithSanitizedColons} from './PolicyUtils';
import {findVendorByID, getCommaSeparatedTagNameWithSanitizedColons, getMatchingVendorByID, isMatchingVendorListLoaded, isXeroActiveMatchingSource} from './PolicyUtils';

const MERCHANT_TYPE_RULE_KEY_PREFIX = 'mcc-group:';

Expand Down Expand Up @@ -115,19 +115,28 @@ function getMerchantCodingRulesTableData({
return [];
}

const isOnXero = isXeroActiveMatchingSource(policy);
const fieldLabels = {
category: translate('common.category').toLowerCase(),
tag: translate('common.tag').toLowerCase(),
description: translate('common.description').toLowerCase(),
tax: translate('common.tax').toLowerCase(),
vendor: translate(isOnXero ? 'common.supplier' : 'common.vendor').toLowerCase(),
};

return Object.entries(codingRules)
.filter(([, rule]) => !!rule && (isOffline || rule.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE))
.map(([ruleID, rule]: [string, CodingRule]) => {
const merchantName = rule.filters?.right ?? '';
const hasOnlyMerchantRename =
!!rule.merchant && !rule.category && !rule.tag && !rule.comment && !rule.tax?.field_id_TAX?.value && rule.reimbursable === undefined && rule.billable === undefined;
!!rule.merchant &&
!rule.category &&
!rule.tag &&
!rule.comment &&
!rule.tax?.field_id_TAX?.value &&
!rule.vendorID &&
rule.reimbursable === undefined &&
rule.billable === undefined;
const typeLabel = hasOnlyMerchantRename ? translate('workspace.rules.expenseDefaultsTable.rename') : translate('workspace.rules.expenseDefaultsTable.update');

const actions: string[] = [];
Expand All @@ -147,6 +156,26 @@ function getMerchantCodingRulesTableData({
if (rule.tax?.field_id_TAX?.value) {
actions.push(translate('workspace.rules.merchantRules.ruleSummarySubtitleUpdateField', fieldLabels.tax, `${rule.tax.field_id_TAX.name} (${rule.tax.field_id_TAX.value})`));
}
if (rule.vendorID) {
// Resolve the display name in three tiers so each case renders correctly:
// 1. Active-source hit — the vendor is in the active vendor-matching integration's list; render its name.
// 2. Active-source miss with a loaded list — the ID doesn't exist in that active list; render "unavailable"
// so a rule targeting a stale/inactive-connection vendor never surfaces a misleading name.
// 3. No active vendor-matching source (e.g. admin switched the non-reimbursable export mode away from
// vendor-matching) — fall back to `findVendorByID`'s permissive search across every connection's data
// so the historical vendor name still renders instead of a raw external ID; otherwise the raw ID
// as a last resort while the connection data hasn't loaded yet.
const activeVendorName = getMatchingVendorByID(policy, rule.vendorID)?.name;
let vendorValue: string;
if (activeVendorName) {
vendorValue = activeVendorName;
} else if (isMatchingVendorListLoaded(policy)) {
vendorValue = translate(isOnXero ? 'workspace.rules.merchantRules.supplierUnavailable' : 'workspace.rules.merchantRules.vendorUnavailable');
} else {
vendorValue = findVendorByID(policy, rule.vendorID)?.name ?? rule.vendorID;
}
actions.push(translate('workspace.rules.merchantRules.ruleSummarySubtitleUpdateField', fieldLabels.vendor, vendorValue));
}
if (rule.reimbursable !== undefined) {
actions.push(translate('workspace.rules.merchantRules.ruleSummarySubtitleReimbursable', rule.reimbursable));
}
Expand Down Expand Up @@ -192,4 +221,12 @@ function getExpenseDefaultsTableData({
return [...merchantRules, ...merchantTypeRules];
}

export {getDefaultMccGroupCategory, getExpenseDefaultsTableData, getMerchantTypeRuleFormFromMccGroup, isDefaultMccGroupID, isMerchantTypeRuleKey, saveMerchantTypeRule};
export {
getDefaultMccGroupCategory,
getExpenseDefaultsTableData,
getMerchantCodingRulesTableData,
getMerchantTypeRuleFormFromMccGroup,
isDefaultMccGroupID,
isMerchantTypeRuleKey,
saveMerchantTypeRule,
};
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.RULES_MERCHANT_CATEGORY]: () => require<ReactComponentModule>('../../../../pages/workspace/rules/MerchantRules/AddCategoryPage').default,
[SCREENS.WORKSPACE.RULES_MERCHANT_TAG]: () => require<ReactComponentModule>('../../../../pages/workspace/rules/MerchantRules/AddTagPage').default,
[SCREENS.WORKSPACE.RULES_MERCHANT_TAX]: () => require<ReactComponentModule>('../../../../pages/workspace/rules/MerchantRules/AddTaxPage').default,
[SCREENS.WORKSPACE.RULES_MERCHANT_VENDOR]: () => require<ReactComponentModule>('../../../../pages/workspace/rules/MerchantRules/AddVendorPage').default,
[SCREENS.WORKSPACE.RULES_MERCHANT_DESCRIPTION]: () => require<ReactComponentModule>('../../../../pages/workspace/rules/MerchantRules/AddDescriptionPage').default,
[SCREENS.WORKSPACE.RULES_MERCHANT_REIMBURSABLE]: () => require<ReactComponentModule>('../../../../pages/workspace/rules/MerchantRules/AddReimbursablePage').default,
[SCREENS.WORKSPACE.RULES_MERCHANT_BILLABLE]: () => require<ReactComponentModule>('../../../../pages/workspace/rules/MerchantRules/AddBillablePage').default,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ const WORKSPACE_TO_RHP: Partial<Record<keyof WorkspaceSplitNavigatorParamList, s
SCREENS.WORKSPACE.RULES_MERCHANT_CATEGORY,
SCREENS.WORKSPACE.RULES_MERCHANT_TAG,
SCREENS.WORKSPACE.RULES_MERCHANT_TAX,
SCREENS.WORKSPACE.RULES_MERCHANT_VENDOR,
SCREENS.WORKSPACE.RULES_MERCHANT_DESCRIPTION,
SCREENS.WORKSPACE.RULES_MERCHANT_REIMBURSABLE,
SCREENS.WORKSPACE.RULES_MERCHANT_BILLABLE,
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1369,6 +1369,9 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
[SCREENS.WORKSPACE.RULES_MERCHANT_TAX]: {
path: ROUTES.RULES_MERCHANT_TAX.route,
},
[SCREENS.WORKSPACE.RULES_MERCHANT_VENDOR]: {
path: ROUTES.RULES_MERCHANT_VENDOR.route,
},
[SCREENS.WORKSPACE.RULES_MERCHANT_DESCRIPTION]: {
path: ROUTES.RULES_MERCHANT_DESCRIPTION.route,
},
Expand Down
4 changes: 4 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1748,6 +1748,10 @@ type SettingsNavigatorParamList = {
policyID: string;
ruleID: string;
};
[SCREENS.WORKSPACE.RULES_MERCHANT_VENDOR]: {
policyID: string;
ruleID: string;
};
[SCREENS.WORKSPACE.RULES_MERCHANT_DESCRIPTION]: {
policyID: string;
ruleID: string;
Expand Down
6 changes: 6 additions & 0 deletions src/libs/actions/Policy/Rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function mapFormFieldsToRuleForOnyx(form: MerchantRuleForm, policy: Policy | und
category: form.category || null,
tag: form.tag || null,
tax: buildTaxObject(form.tax, policy) ?? null,
vendorID: form.vendorID || null,
comment: convertCommentToHTML(form.comment),
reimbursable: form.reimbursable ?? null,
billable: form.billable ?? null,
Expand All @@ -100,6 +101,9 @@ function mapFormFieldsToRuleForAPI(form: MerchantRuleForm, policy: Policy | unde
if (tax) {
rule.tax = tax;
}
if (form.vendorID) {
rule.vendorID = form.vendorID;
}
const commentHTML = convertCommentToHTML(form.comment);
if (commentHTML) {
rule.comment = commentHTML;
Expand Down Expand Up @@ -678,6 +682,8 @@ function clearPolicyAgentRuleErrors(policyID: string, agentRuleID: string, agent
export {
openPolicyRulesPage,
getAgentRuleSuggestions,
mapFormFieldsToRuleForOnyx,
mapFormFieldsToRuleForAPI,
setPolicyCodingRule,
importMerchantRulesSpreadsheet,
deletePolicyCodingRule,
Expand Down
Loading
Loading