diff --git a/src/languages/de.ts b/src/languages/de.ts index e0ec4822cea9..6d54352c36f8 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -640,6 +640,7 @@ const translations: TranslationDeepObject = { originalAmount: 'Ursprünglicher Betrag', insights: 'Einblicke', duplicateExpense: 'Doppelte Ausgabe', + newFeature: 'Neue Funktion', }, supportalNoAccess: { title: 'Nicht so schnell', @@ -6316,6 +6317,16 @@ Fordere Spesendetails wie Belege und Beschreibungen an, lege Limits und Standard title: 'Spesenrichtlinie', cardSubtitle: 'Hier befindet sich die Spesenrichtlinie eures Teams, damit alle genau wissen, was abgedeckt ist.', }, + merchantRules: { + title: 'Händler', + subtitle: 'Legen Sie Händlerregeln fest, damit Ausgaben korrekt codiert ankommen und weniger Nachbearbeitung erfordern.', + addRule: 'Händlerregel hinzufügen', + ruleSummaryTitle: (merchantName: string) => `Wenn Händler „${merchantName}“ enthält`, + ruleSummarySubtitleMerchant: (merchantName: string) => `Händler in „${merchantName}“ umbenennen`, + ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Aktualisiere ${fieldName} zu „${fieldValue}“`, + ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Als "${reimbursable ? 'erstattungsfähig' : 'nicht erstattungsfähig'}" markieren`, + ruleSummarySubtitleBillable: (billable: boolean) => `Als „${billable ? 'Abrechenbar' : 'nicht abrechenbar'}“ markieren`, + }, }, planTypePage: { planTypes: { diff --git a/src/languages/en.ts b/src/languages/en.ts index 593ef87cf92b..a8b4098c2780 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -245,6 +245,7 @@ const translations = { in: 'In', optional: 'Optional', new: 'New', + newFeature: 'New feature', search: 'Search', reports: 'Reports', find: 'Find', @@ -6165,6 +6166,16 @@ const translations = { unlockFeatureEnableWorkflowsSubtitle: (featureName: string) => `Add ${featureName} to unlock this feature.`, enableFeatureSubtitle: (featureName: string, moreFeaturesLink?: string) => `Go to [more features](${moreFeaturesLink}) and enable ${featureName} to unlock this feature.`, }, + merchantRules: { + title: 'Merchant', + subtitle: 'Set the merchant rules so expenses arrive correctly coded and require less cleanup.', + addRule: 'Add merchant rule', + ruleSummaryTitle: (merchantName: string) => `If merchant contains "${merchantName}"`, + ruleSummarySubtitleMerchant: (merchantName: string) => `Rename merchant to "${merchantName}"`, + 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'}"`, + }, categoryRules: { title: 'Category rules', approver: 'Approver', diff --git a/src/languages/es.ts b/src/languages/es.ts index c615533a3828..6b62d6109ddc 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -28,6 +28,7 @@ const translations: TranslationDeepObject = { in: 'En', optional: 'Opcional', new: 'Nuevo', + newFeature: 'Nueva función', center: 'Centrar', search: 'Buscar', reports: 'Informes', @@ -5940,6 +5941,16 @@ ${amount} para ${merchant} - ${date}`, unlockFeatureEnableWorkflowsSubtitle: (featureName) => `Añade ${featureName} para desbloquear esta función.`, enableFeatureSubtitle: (featureName, moreFeaturesLink) => `Ir a [más características](${moreFeaturesLink}) y habilita ${featureName} para desbloquear esta función.`, }, + merchantRules: { + title: 'Comerciante', + subtitle: 'Configura las reglas de comerciante para que los gastos lleguen correctamente codificados y requieran menos limpieza.', + addRule: 'Añadir regla de comerciante', + ruleSummaryTitle: (merchantName: string) => `Si el comerciante contiene "${merchantName}"`, + ruleSummarySubtitleMerchant: (merchantName: string) => `Renombrar comerciante a "${merchantName}"`, + 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'}"`, + }, categoryRules: { title: 'Reglas de categoría', approver: 'Aprobador', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 8cbc089fa00a..7a781bc2dacf 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -642,6 +642,7 @@ const translations: TranslationDeepObject = { originalAmount: 'Montant d’origine', insights: 'Analyses', duplicateExpense: 'Note de frais en double', + newFeature: 'Nouvelle fonctionnalité', }, supportalNoAccess: { title: 'Pas si vite', @@ -6327,6 +6328,16 @@ Exigez des informations de dépense comme les reçus et les descriptions, défin title: 'Politique de dépenses', cardSubtitle: 'Voici l’endroit où se trouve la politique de dépenses de votre équipe, afin que tout le monde soit sur la même longueur d’onde concernant ce qui est couvert.', }, + merchantRules: { + title: 'Commerçant', + subtitle: 'Définissez les règles de marchand afin que les dépenses arrivent correctement codées et nécessitent moins de nettoyage.', + addRule: 'Ajouter une règle de commerçant', + ruleSummaryTitle: (merchantName: string) => `Si le commerçant contient « ${merchantName} »`, + ruleSummarySubtitleMerchant: (merchantName: string) => `Renommer le marchand en « ${merchantName} »`, + ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Mettre à jour ${fieldName} sur « ${fieldValue} »`, + ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Marquer comme « ${reimbursable ? 'remboursable' : 'non remboursable'} »`, + ruleSummarySubtitleBillable: (billable: boolean) => `Marquer comme « ${billable ? 'facturable' : 'non facturable'} »`, + }, }, planTypePage: { planTypes: { diff --git a/src/languages/it.ts b/src/languages/it.ts index deb488917bbc..97ce434bb2db 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -641,6 +641,7 @@ const translations: TranslationDeepObject = { originalAmount: 'Importo originale', insights: 'Analisi', duplicateExpense: 'Spesa duplicata', + newFeature: 'Nuova funzionalità', }, supportalNoAccess: { title: 'Non così in fretta', @@ -6300,6 +6301,16 @@ Richiedi dettagli di spesa come ricevute e descrizioni, imposta limiti e valori title: 'Policy di spesa', cardSubtitle: 'Qui è dove si trova la policy di spesa del tuo team, così tutti sono allineati su cosa è coperto.', }, + merchantRules: { + title: 'Esercente', + subtitle: 'Imposta le regole per gli esercenti in modo che le spese arrivino già codificate correttamente e richiedano meno correzioni.', + addRule: 'Aggiungi regola esercente', + ruleSummaryTitle: (merchantName: string) => `Se l’esercente contiene "${merchantName}"`, + ruleSummarySubtitleMerchant: (merchantName: string) => `Rinomina esercente in "${merchantName}"`, + ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Aggiorna ${fieldName} a "${fieldValue}"`, + ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Segna come "${reimbursable ? 'rimborsabile' : 'non rimborsabile'}"`, + ruleSummarySubtitleBillable: (billable: boolean) => `Contrassegna come "${billable ? 'fatturabile' : 'non fatturabile'}"`, + }, }, planTypePage: { planTypes: { diff --git a/src/languages/ja.ts b/src/languages/ja.ts index d3cfcd265892..0a70a59323df 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -640,6 +640,7 @@ const translations: TranslationDeepObject = { originalAmount: '元の金額', insights: 'インサイト', duplicateExpense: '重複した経費', + newFeature: '新機能', }, supportalNoAccess: { title: 'ちょっと待ってください', @@ -6257,6 +6258,16 @@ ${reportName} title: '経費ポリシー', cardSubtitle: 'ここにはチームの経費ポリシーが保存されています。これにより、何が対象になるかについて全員が同じ認識を持てます。', }, + merchantRules: { + title: '加盟店', + subtitle: '取引先ルールを設定して、経費が正しくコード化された状態で届くようにし、後処理を最小限に抑えましょう。', + addRule: '店舗ルールを追加', + ruleSummaryTitle: (merchantName: string) => `もし取引先に「${merchantName}」が含まれている場合`, + ruleSummarySubtitleMerchant: (merchantName: string) => `支払先名を「${merchantName}」に変更`, + ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `${fieldName} を「${fieldValue}」に更新`, + ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `「${reimbursable ? '払い戻し対象' : '精算対象外'}」としてマーク`, + ruleSummarySubtitleBillable: (billable: boolean) => `「${billable ? '請求可能' : '請求対象外'}」としてマーク`, + }, }, planTypePage: { planTypes: { diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 90027e14228e..a4421c1d3ab6 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -641,6 +641,7 @@ const translations: TranslationDeepObject = { originalAmount: 'Oorspronkelijk bedrag', insights: 'Inzichten', duplicateExpense: 'Dubbele uitgave', + newFeature: 'Nieuwe functie', }, supportalNoAccess: { title: 'Niet zo snel', @@ -6286,6 +6287,16 @@ Vraag verplichte uitgavedetails zoals bonnetjes en beschrijvingen, stel limieten title: 'Onkostennota-beleid', cardSubtitle: 'Hier staat het onkostebeleid van je team, zodat iedereen goed weet wat wel en niet wordt vergoed.', }, + merchantRules: { + title: 'Handelaar', + subtitle: 'Stel de merchantregels zo in dat onkosten met de juiste codering binnenkomen en er minder nabewerking nodig is.', + addRule: 'Merchantregel toevoegen', + ruleSummaryTitle: (merchantName: string) => `Als handelaar "${merchantName}" bevat`, + ruleSummarySubtitleMerchant: (merchantName: string) => `Naam handelaar wijzigen in "${merchantName}"`, + ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Werk ${fieldName} bij naar "${fieldValue}"`, + ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Markeren als "${reimbursable ? 'Vergoedbaar' : 'niet-vergoedbaar'}"`, + ruleSummarySubtitleBillable: (billable: boolean) => `Markeren als "${billable ? 'factureerbaar' : 'niet-factureerbaar'}"`, + }, }, planTypePage: { planTypes: { diff --git a/src/languages/pl.ts b/src/languages/pl.ts index b39f9c4345c4..28a17b01098f 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -641,6 +641,7 @@ const translations: TranslationDeepObject = { originalAmount: 'Kwota pierwotna', insights: 'Analizy', duplicateExpense: 'Zduplikowany wydatek', + newFeature: 'Nowa funkcja', }, supportalNoAccess: { title: 'Nie tak szybko', @@ -6280,6 +6281,16 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i title: 'Polityka wydatków', cardSubtitle: 'Tutaj znajduje się polityka wydatków Twojego zespołu, aby wszyscy mieli jasność, co jest objęte.', }, + merchantRules: { + title: 'Sprzedawca', + subtitle: 'Skonfiguruj reguły dla sprzedawców, aby wydatki trafiały z poprawnym kodowaniem i wymagały mniej poprawek.', + addRule: 'Dodaj regułę sprzedawcy', + ruleSummaryTitle: (merchantName: string) => `Jeśli sprzedawca zawiera „${merchantName}”`, + ruleSummarySubtitleMerchant: (merchantName: string) => `Zmień sprzedawcę na „${merchantName}”`, + ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Zaktualizuj ${fieldName} na „${fieldValue}”`, + ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Oznacz jako "${reimbursable ? 'kwalifikujący się do zwrotu kosztów' : 'niepodlegający zwrotowi'}"`, + ruleSummarySubtitleBillable: (billable: boolean) => `Oznacz jako „${billable ? 'fakturowalne' : 'poza fakturą'}”`, + }, }, planTypePage: { planTypes: { diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 334edd7e3663..ef12d476e5b3 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -640,6 +640,7 @@ const translations: TranslationDeepObject = { originalAmount: 'Valor original', insights: 'Insights', duplicateExpense: 'Despesa duplicada', + newFeature: 'Novo recurso', }, supportalNoAccess: { title: 'Não tão rápido', @@ -6281,6 +6282,16 @@ Exija detalhes de despesas como recibos e descrições, defina limites e padrõe title: 'Política de despesas', cardSubtitle: 'É aqui que fica a política de despesas da sua equipe, para que todos estejam alinhados sobre o que é coberto.', }, + merchantRules: { + title: 'Estabelecimento', + subtitle: 'Defina as regras de comerciante para que as despesas cheguem corretamente categorizadas e exijam menos retrabalho.', + addRule: 'Adicionar regra de estabelecimento', + ruleSummaryTitle: (merchantName: string) => `Se o comerciante contiver "${merchantName}"`, + ruleSummarySubtitleMerchant: (merchantName: string) => `Renomear comerciante para "${merchantName}"`, + ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Atualizar ${fieldName} para "${fieldValue}"`, + ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Marcar como "${reimbursable ? 'reembolsável' : 'não reembolsável'}"`, + ruleSummarySubtitleBillable: (billable: boolean) => `Marcar como "${billable ? 'faturável' : 'não faturável'}"`, + }, }, planTypePage: { planTypes: { diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 4ea00ed4b59c..5c997de1759e 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -637,6 +637,7 @@ const translations: TranslationDeepObject = { originalAmount: '原始金额', insights: '洞察', duplicateExpense: '重复报销', + newFeature: '新功能', }, supportalNoAccess: { title: '先别急', @@ -6147,6 +6148,16 @@ ${reportName} title: '报销政策', cardSubtitle: '这里是你们团队报销政策所在的位置,让所有人都能清楚了解哪些费用包含在内。', }, + merchantRules: { + title: '商家', + subtitle: '设置商家规则,让报销费用自动按正确科目归类,减少后期清理工作。', + addRule: '添加商家规则', + ruleSummaryTitle: (merchantName: string) => `如果商户包含“${merchantName}”`, + ruleSummarySubtitleMerchant: (merchantName: string) => `将商家重命名为 “${merchantName}”`, + ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `将 ${fieldName} 更新为“${fieldValue}”`, + ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `标记为“${reimbursable ? '可报销' : '不予报销'}”`, + ruleSummarySubtitleBillable: (billable: boolean) => `标记为“${billable ? '可计费' : '不可计费'}”`, + }, }, planTypePage: { planTypes: { diff --git a/src/pages/workspace/rules/MerchantRulesSection.tsx b/src/pages/workspace/rules/MerchantRulesSection.tsx new file mode 100644 index 000000000000..c07048146de2 --- /dev/null +++ b/src/pages/workspace/rules/MerchantRulesSection.tsx @@ -0,0 +1,158 @@ +import React, {useMemo} from 'react'; +import {View} from 'react-native'; +import Badge from '@components/Badge'; +import MenuItem from '@components/MenuItem'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import Section from '@components/Section'; +import Text from '@components/Text'; +import useEnvironment from '@hooks/useEnvironment'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import usePolicy from '@hooks/usePolicy'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {getDecodedCategoryName} from '@libs/CategoryUtils'; +import {getCleanedTagName} from '@libs/PolicyUtils'; +import type {CodingRule} from '@src/types/onyx/Policy'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +type MerchantRulesSectionProps = { + policyID: string; +}; + +type FieldLabels = { + category: string; + tag: string; + description: string; + tax: string; +}; + +/** + * Generates a human-readable description of what a coding rule does + */ +function getRuleDescription(rule: CodingRule, translate: ReturnType['translate'], labels: FieldLabels): string { + const actions: string[] = []; + + if (rule.merchant) { + actions.push(translate('workspace.rules.merchantRules.ruleSummarySubtitleMerchant', rule.merchant)); + } + if (rule.category) { + actions.push(translate('workspace.rules.merchantRules.ruleSummarySubtitleUpdateField', labels.category, getDecodedCategoryName(rule.category))); + } + if (rule.tag) { + actions.push(translate('workspace.rules.merchantRules.ruleSummarySubtitleUpdateField', labels.tag, getCleanedTagName(rule.tag))); + } + if (rule.comment) { + actions.push(translate('workspace.rules.merchantRules.ruleSummarySubtitleUpdateField', labels.description, rule.comment)); + } + if (rule.tax?.field_id_TAX?.value) { + actions.push(translate('workspace.rules.merchantRules.ruleSummarySubtitleUpdateField', labels.tax, rule.tax.field_id_TAX.value)); + } + if (rule.reimbursable !== undefined) { + actions.push(translate('workspace.rules.merchantRules.ruleSummarySubtitleReimbursable', rule.reimbursable)); + } + if (rule.billable !== undefined) { + actions.push(translate('workspace.rules.merchantRules.ruleSummarySubtitleBillable', rule.billable)); + } + + // Lowercase any subsequent rule after the first one + return actions.map((action, index) => (index === 0 ? action : action.charAt(0).toLowerCase() + action.slice(1))).join(', '); +} + +function MerchantRulesSection({policyID}: MerchantRulesSectionProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const theme = useTheme(); + const policy = usePolicy(policyID); + const expensifyIcons = useMemoizedLazyExpensifyIcons(['Plus']); + const {isDevelopment} = useEnvironment(); + + // Hoist iterator-independent translations to avoid redundant calls in the loop + const fieldLabels: FieldLabels = useMemo( + () => ({ + category: translate('common.category').toLowerCase(), + tag: translate('common.tag').toLowerCase(), + description: translate('common.description').toLowerCase(), + tax: translate('common.tax').toLowerCase(), + }), + [translate], + ); + + const codingRules = policy?.rules?.codingRules; + const hasRules = !isEmptyObject(codingRules); + + const sortedRules = useMemo(() => { + if (!codingRules) { + return []; + } + + return Object.entries(codingRules) + .map(([ruleID, rule]) => ({ruleID, ...rule})) + .sort((a, b) => { + if (a.created && b.created) { + return a.created < b.created ? 1 : -1; + } + return 0; + }); + }, [codingRules]); + + if (!isDevelopment) { + return null; + } + + const renderTitle = () => ( + + {translate('workspace.rules.merchantRules.title')} + + + ); + + return ( +
+ {hasRules && ( + + {sortedRules.map((rule) => { + const merchantName = rule.filters?.right ?? ''; + const matchDescription = translate('workspace.rules.merchantRules.ruleSummaryTitle', merchantName); + const ruleDescription = getRuleDescription(rule, translate, fieldLabels); + + return ( + + + + ); + })} + + )} + {}} + /> +
+ ); +} + +MerchantRulesSection.displayName = 'MerchantRulesSection'; + +export default MerchantRulesSection; diff --git a/src/pages/workspace/rules/PolicyRulesPage.tsx b/src/pages/workspace/rules/PolicyRulesPage.tsx index 2dfef01ad9ea..04dc9e1da121 100644 --- a/src/pages/workspace/rules/PolicyRulesPage.tsx +++ b/src/pages/workspace/rules/PolicyRulesPage.tsx @@ -12,6 +12,7 @@ import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSection import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; import IndividualExpenseRulesSection from './IndividualExpenseRulesSection'; +import MerchantRulesSection from './MerchantRulesSection'; type PolicyRulesPageProps = PlatformStackScreenProps; @@ -49,6 +50,7 @@ function PolicyRulesPage({route}: PolicyRulesPageProps) { > + diff --git a/src/styles/index.ts b/src/styles/index.ts index 08336c5bf230..044d9b93c104 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1043,6 +1043,12 @@ const staticStyles = (theme: ThemeColors) => minHeight: 28, }, + badgeNewFeature: { + minHeight: 20, + height: 20, + paddingHorizontal: 8, + }, + badgeText: { color: theme.text, fontSize: variables.fontSizeSmall, diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index e1b8f6a400e9..cad8dd9ec6a0 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1685,6 +1685,61 @@ type ExpenseRule = { id?: string; }; +/** Coding rule filter condition */ +type CodingRuleFilter = { + /** The left side of the filter condition (e.g., 'merchant') */ + left: string; + + /** The operator for the filter, defined in CONST.SEARCH.SYNTAX_OPERATORS */ + operator: string; + + /** The right side of the filter condition (e.g., 'Snoop') */ + right: string; +}; + +/** Tax configuration for coding rule */ +type CodingRuleTax = { + /** Object wrapping the tax field - field_id_TAX matches the backend API format */ + // eslint-disable-next-line @typescript-eslint/naming-convention + field_id_TAX: { + /** The external ID of the tax rate */ + externalID: string; + + /** The display value of the tax rate */ + value: string; + }; +}; + +/** Policy coding rule data model */ +type CodingRule = { + /** Filter conditions for when this rule applies */ + filters: CodingRuleFilter; + + /** The merchant name to set on matching expenses */ + merchant?: string; + + /** Whether the expense should be billable */ + billable?: boolean; + + /** The category to set on matching expenses */ + category?: string; + + /** The comment/description to set on matching expenses */ + comment?: string; + + /** Whether the expense should be reimbursable */ + reimbursable?: boolean; + + /** The tag to set on matching expenses */ + tag?: string; + + /** Tax configuration for the expense */ + tax?: CodingRuleTax; + + /** When this rule was created */ + created?: string; +}; + /** Model of policy data */ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< { @@ -1892,6 +1947,9 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** A set of rules related to the workspace expenses */ expenseRules?: ExpenseRule[]; + + /** A set of coding rules for automatic expense field population based on merchant matching */ + codingRules?: Record; }; /** A set of custom rules defined with natural language */ @@ -2098,6 +2156,9 @@ export type { ACHAccount, ApprovalRule, ExpenseRule, + CodingRule, + CodingRuleFilter, + CodingRuleTax, NetSuiteConnectionConfig, MccGroup, Subrate,