diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 91ee0d8e6e5d..b59166c8d02c 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1584,6 +1584,7 @@ const CONST = { ADD_CUSTOM_UNIT_RATE: 'POLICYCHANGELOG_ADD_CUSTOM_UNIT_RATE', ADD_EMPLOYEE: 'POLICYCHANGELOG_ADD_EMPLOYEE', ADD_CARD_FEED: 'POLICYCHANGELOG_ADD_CARD_FEED', + ADD_EXPENSIFY_CARD_RULE: 'POLICYCHANGELOG_ADD_EXPENSIFY_CARD_RULE', ADD_INTEGRATION: 'POLICYCHANGELOG_ADD_INTEGRATION', ADD_REPORT_FIELD: 'POLICYCHANGELOG_ADD_REPORT_FIELD', ADD_TAG: 'POLICYCHANGELOG_ADD_TAG', @@ -1609,6 +1610,7 @@ const CONST = { INDIVIDUAL_BUDGET_NOTIFICATION: 'POLICYCHANGELOG_INDIVIDUAL_BUDGET_NOTIFICATION', INVITE_TO_ROOM: 'POLICYCHANGELOG_INVITETOROOM', REMOVE_FROM_ROOM: 'POLICYCHANGELOG_REMOVEFROMROOM', + REMOVE_EXPENSIFY_CARD_RULE: 'POLICYCHANGELOG_REMOVE_EXPENSIFY_CARD_RULE', LEAVE_ROOM: 'POLICYCHANGELOG_LEAVEROOM', REPLACE_CATEGORIES: 'POLICYCHANGELOG_REPLACE_CATEGORIES', SET_AUTO_REIMBURSEMENT: 'POLICYCHANGELOG_SET_AUTOREIMBURSEMENT', @@ -1635,6 +1637,7 @@ const CONST = { UPDATE_DEFAULT_TITLE_ENFORCED: 'POLICYCHANGELOG_UPDATE_DEFAULT_TITLE_ENFORCED', UPDATE_DISABLED_FIELDS: 'POLICYCHANGELOG_UPDATE_DISABLED_FIELDS', UPDATE_EMPLOYEE: 'POLICYCHANGELOG_UPDATE_EMPLOYEE', + UPDATE_EXPENSIFY_CARD_RULE: 'POLICYCHANGELOG_UPDATE_EXPENSIFY_CARD_RULE', UPDATE_FIELD: 'POLICYCHANGELOG_UPDATE_FIELD', UPDATE_ADDRESS: 'POLICYCHANGELOG_UPDATE_ADDRESS', UPDATE_FEATURE_ENABLED: 'POLICYCHANGELOG_UPDATE_FEATURE_ENABLED', diff --git a/src/languages/de.ts b/src/languages/de.ts index 4de93e2aae81..f04a1c096d67 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -7575,6 +7575,57 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc addedReportField: (fieldType: string, fieldName?: string, defaultValue?: string) => `${fieldType}-Berichtsfeld „${fieldName}“${defaultValue ? ` mit Standardwert „${defaultValue}“` : ''} hinzugefügt`, updatedRequireCompanyCards: ({enabled}: {enabled: boolean}) => `${enabled ? 'aktiviert' : 'deaktiviert'} die Anforderung für Firmenkartenkäufe`, + expensifyCardRule: { + actionVerb: {block: 'blockiert', allow: 'erlaubt'}, + amountOperator: {over: 'über', under: 'unter'}, + amountFilter: ({operator, amount}: {operator: string; amount: string}) => `Beträge ${operator} ${amount}`, + theCard: 'die Karte', + multipleCards: ({count}: {count: number}) => `${count} Karten`, + joinFilters: ({items}: {items: string[]}) => { + if (items.length === 0) { + return ''; + } + if (items.length === 1) { + return items.at(0) ?? ''; + } + if (items.length === 2) { + return `${items.at(0)} und ${items.at(1)}`; + } + return `${items.slice(0, -1).join(', ')} und ${items.at(-1)}`; + }, + addRule: ({verb, filters, cards}: {verb: string; filters: string; cards: string}) => { + let text = verb; + if (filters !== '') { + text += `${text === '' ? '' : ' '}${filters}`; + } + if (cards !== '') { + text += `${text === '' ? '' : ' '} auf ${cards}`; + } + return text; + }, + removeRule: ({cards}: {cards: string}) => (cards !== '' ? `Ausgaberegel von ${cards} entfernt` : 'Ausgaberegel entfernt'), + restrictionVerb: {block: 'Block', allow: 'nur zulassen'}, + update: { + modeChange: ({fromAction, toAction, cards}: {fromAction: string; toAction: string; cards: string}) => + cards !== '' ? `Ausgabenregel von ${fromAction} zu ${toAction} auf ${cards} geändert` : `Ausgabenregel von ${fromAction} in ${toAction} geändert`, + appliedToAdditionalCards: ({count}: {count: number}) => `Ausgaberegel auf ${count} zusätzliche Karten angewendet`, + phraseVerb: {added: 'hinzugefügt', removed: 'entfernt', changed: 'geändert', set: 'festlegen', applied: 'angewendet'}, + bodyMerchant: ({adjective, value}: {adjective: string; value: string}) => (adjective !== '' ? `${adjective} Händler „${value}“` : `Händler*in „${value}“`), + bodyMerchantChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `${adjective} Händler von „${oldValue}“ zu „${newValue}“` : `Händler*in von „${oldValue}“ zu „${newValue}“`, + bodySpendCategory: ({adjective, value}: {adjective: string; value: string}) => + adjective !== '' ? `${adjective} Ausgabenkategorie „${value}“` : `Ausgabenkategorie „${value}“`, + bodySpendCategoryChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `${adjective} Ausgabenkategorie von „${oldValue}“ zu „${newValue}“` : `Ausgabenkategorie von „${oldValue}“ auf „${newValue}“`, + bodyMaxAmount: 'Maximalbetrag', + bodyMaxAmountSet: ({value}: {value: string}) => `Maximalbetrag bis ${value}`, + bodyMaxAmountChange: ({oldValue, newValue}: {oldValue: string; newValue: string}) => `Maximalbetrag von ${oldValue} auf ${newValue}`, + bodyAppliedToAdditionalCards: ({count}: {count: number}) => `Ausgabenregel auf ${count} weitere Karten anwenden`, + bodyRemovedFromCards: ({cards}: {cards: string}) => `Ausgabelimit von ${cards}`, + composeOnCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} auf ${cards}` : content), + composeFromCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} von ${cards}` : content), + }, + }, }, roomMembersPage: { memberNotFound: 'Mitglied nicht gefunden.', diff --git a/src/languages/en.ts b/src/languages/en.ts index d1550f9a5098..241973bacc65 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -7273,6 +7273,71 @@ const translations = { updatedCardFeedLiability: (feedName: string, enabled: boolean) => `${enabled ? 'enabled' : 'disabled'} cardholders to delete card transactions for card feed "${feedName}"`, updatedCardFeedStatementPeriod: (feedName: string, newValue?: string, previousValue?: string) => `changed card feed "${feedName}" statement period end day${newValue ? ` to "${newValue}"` : ''}${previousValue ? ` (previously "${previousValue}")` : ''}`, + expensifyCardRule: { + actionVerb: { + block: 'blocked', + allow: 'allowed', + }, + amountOperator: { + over: 'over', + under: 'under', + }, + amountFilter: ({operator, amount}: {operator: string; amount: string}) => `amounts ${operator} ${amount}`, + theCard: 'the card', + multipleCards: ({count}: {count: number}) => `${count} cards`, + joinFilters: ({items}: {items: string[]}) => { + if (items.length === 0) { + return ''; + } + if (items.length === 1) { + return items.at(0) ?? ''; + } + if (items.length === 2) { + return `${items.at(0)} and ${items.at(1)}`; + } + return `${items.slice(0, -1).join(', ')}, and ${items.at(-1)}`; + }, + addRule: ({verb, filters, cards}: {verb: string; filters: string; cards: string}) => { + let text = verb; + if (filters !== '') { + text += `${text === '' ? '' : ' '}${filters}`; + } + if (cards !== '') { + text += `${text === '' ? '' : ' '}on ${cards}`; + } + return text; + }, + removeRule: ({cards}: {cards: string}) => (cards !== '' ? `removed spend rule from ${cards}` : 'removed spend rule'), + restrictionVerb: { + block: 'block', + allow: 'only allow', + }, + update: { + modeChange: ({fromAction, toAction, cards}: {fromAction: string; toAction: string; cards: string}) => + cards !== '' ? `changed spend rule from ${fromAction} to ${toAction} on ${cards}` : `changed spend rule from ${fromAction} to ${toAction}`, + appliedToAdditionalCards: ({count}: {count: number}) => `applied spend rule to ${count} additional cards`, + phraseVerb: { + added: 'added', + removed: 'removed', + changed: 'changed', + set: 'set', + applied: 'applied', + }, + bodyMerchant: ({adjective, value}: {adjective: string; value: string}) => (adjective !== '' ? `${adjective} merchant '${value}'` : `merchant '${value}'`), + bodyMerchantChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `${adjective} merchant from '${oldValue}' to '${newValue}'` : `merchant from '${oldValue}' to '${newValue}'`, + bodySpendCategory: ({adjective, value}: {adjective: string; value: string}) => (adjective !== '' ? `${adjective} spend category '${value}'` : `spend category '${value}'`), + bodySpendCategoryChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `${adjective} spend category from '${oldValue}' to '${newValue}'` : `spend category from '${oldValue}' to '${newValue}'`, + bodyMaxAmount: 'max amount', + bodyMaxAmountSet: ({value}: {value: string}) => `max amount to ${value}`, + bodyMaxAmountChange: ({oldValue, newValue}: {oldValue: string; newValue: string}) => `max amount from ${oldValue} to ${newValue}`, + bodyAppliedToAdditionalCards: ({count}: {count: number}) => `spend rule to ${count} additional cards`, + bodyRemovedFromCards: ({cards}: {cards: string}) => `spend rule from ${cards}`, + composeOnCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} on ${cards}` : content), + composeFromCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} from ${cards}` : content), + }, + }, preventSelfApproval: (oldValue: string, newValue: string) => `updated "Prevent self-approval" to "${newValue === 'true' ? 'Enabled' : 'Disabled'}" (previously "${oldValue === 'true' ? 'Enabled' : 'Disabled'}")`, updateMonthlyOffset: (oldValue: string, newValue: string) => { diff --git a/src/languages/es.ts b/src/languages/es.ts index 4d165fcfc90c..5aff1d827372 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -7126,6 +7126,71 @@ ${amount} para ${merchant} - ${date}`, `${enabled ? 'habilitó' : 'deshabilitó'} que los titulares de tarjetas eliminen transacciones de la fuente de tarjetas "${feedName}"`, updatedCardFeedStatementPeriod: (feedName: string, newValue?: string, previousValue?: string) => `cambió el día de cierre del período de estado de cuenta de la fuente de tarjetas "${feedName}"${newValue ? ` a "${newValue}"` : ''}${previousValue ? ` (previamente "${previousValue}")` : ''}`, + expensifyCardRule: { + actionVerb: { + block: 'bloqueó', + allow: 'permitió', + }, + amountOperator: { + over: 'mayores a', + under: 'menores a', + }, + amountFilter: ({operator, amount}) => `montos ${operator} ${amount}`, + theCard: 'la tarjeta', + multipleCards: ({count}) => `${count} tarjetas`, + joinFilters: ({items}) => { + if (items.length === 0) { + return ''; + } + if (items.length === 1) { + return items.at(0) ?? ''; + } + if (items.length === 2) { + return `${items.at(0)} y ${items.at(1)}`; + } + return `${items.slice(0, -1).join(', ')} y ${items.at(-1)}`; + }, + addRule: ({verb, filters, cards}) => { + let text = verb; + if (filters !== '') { + text += `${text === '' ? '' : ' '}${filters}`; + } + if (cards !== '') { + text += `${text === '' ? '' : ' '}en ${cards}`; + } + return text; + }, + removeRule: ({cards}) => (cards !== '' ? `eliminó la regla de gasto de ${cards}` : 'eliminó la regla de gasto'), + restrictionVerb: { + block: 'bloquear', + allow: 'solo permitir', + }, + update: { + modeChange: ({fromAction, toAction, cards}) => + cards !== '' ? `cambió la regla de gasto de ${fromAction} a ${toAction} en ${cards}` : `cambió la regla de gasto de ${fromAction} a ${toAction}`, + appliedToAdditionalCards: ({count}) => `aplicó la regla de gasto a ${count} tarjetas adicionales`, + phraseVerb: { + added: 'agregó', + removed: 'eliminó', + changed: 'cambió', + set: 'estableció', + applied: 'aplicó', + }, + bodyMerchant: ({adjective, value}) => (adjective !== '' ? `comerciante ${adjective} '${value}'` : `comerciante '${value}'`), + bodyMerchantChange: ({adjective, oldValue, newValue}) => + adjective !== '' ? `comerciante ${adjective} de '${oldValue}' a '${newValue}'` : `comerciante de '${oldValue}' a '${newValue}'`, + bodySpendCategory: ({adjective, value}) => (adjective !== '' ? `categoría de gasto ${adjective} '${value}'` : `categoría de gasto '${value}'`), + bodySpendCategoryChange: ({adjective, oldValue, newValue}) => + adjective !== '' ? `categoría de gasto ${adjective} de '${oldValue}' a '${newValue}'` : `categoría de gasto de '${oldValue}' a '${newValue}'`, + bodyMaxAmount: 'monto máximo', + bodyMaxAmountSet: ({value}) => `monto máximo en ${value}`, + bodyMaxAmountChange: ({oldValue, newValue}) => `monto máximo de ${oldValue} a ${newValue}`, + bodyAppliedToAdditionalCards: ({count}) => `la regla de gasto a ${count} tarjetas adicionales`, + bodyRemovedFromCards: ({cards}) => `la regla de gasto de ${cards}`, + composeOnCards: ({content, cards}) => (cards !== '' ? `${content} en ${cards}` : content), + composeFromCards: ({content, cards}) => (cards !== '' ? `${content} de ${cards}` : content), + }, + }, preventSelfApproval: (oldValue, newValue) => `actualizó "Evitar la autoaprobación" a "${newValue === 'true' ? 'Habilitada' : 'Deshabilitada'}" (previamente "${oldValue === 'true' ? 'Habilitada' : 'Deshabilitada'}")`, setReceiptRequiredAmount: (newValue) => `estableció el importe requerido del recibo en "${newValue}"`, diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 580833a8046c..ba2d1f186201 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -7597,6 +7597,57 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e addedReportField: (fieldType: string, fieldName?: string, defaultValue?: string) => `a ajouté le champ de note de frais ${fieldType} « ${fieldName} »${defaultValue ? ` avec la valeur par défaut « ${defaultValue} »` : ''}`, updatedRequireCompanyCards: ({enabled}: {enabled: boolean}) => `${enabled ? 'activé' : 'désactivé'} l’exigence d’achats par carte d’entreprise`, + expensifyCardRule: { + actionVerb: {block: 'bloqué', allow: 'autorisé'}, + amountOperator: {over: 'terminé', under: 'sous'}, + amountFilter: ({operator, amount}: {operator: string; amount: string}) => `montants ${operator} ${amount}`, + theCard: 'la carte', + multipleCards: ({count}: {count: number}) => `${count} cartes`, + joinFilters: ({items}: {items: string[]}) => { + if (items.length === 0) { + return ''; + } + if (items.length === 1) { + return items.at(0) ?? ''; + } + if (items.length === 2) { + return `${items.at(0)} et ${items.at(1)}`; + } + return `${items.slice(0, -1).join(', ')}, et ${items.at(-1)}`; + }, + addRule: ({verb, filters, cards}: {verb: string; filters: string; cards: string}) => { + let text = verb; + if (filters !== '') { + text += `${text === '' ? '' : ' '}${filters}`; + } + if (cards !== '') { + text += `${text === '' ? '' : ' '} sur ${cards}`; + } + return text; + }, + removeRule: ({cards}: {cards: string}) => (cards !== '' ? `règle de dépense supprimée de ${cards}` : 'règle de dépense supprimée'), + restrictionVerb: {block: 'bloquer', allow: 'autoriser uniquement'}, + update: { + modeChange: ({fromAction, toAction, cards}: {fromAction: string; toAction: string; cards: string}) => + cards !== '' ? `a modifié la règle de dépense de ${fromAction} à ${toAction} sur ${cards}` : `a modifié la règle de dépense de ${fromAction} à ${toAction}`, + appliedToAdditionalCards: ({count}: {count: number}) => `règle de dépense appliquée à ${count} cartes supplémentaires`, + phraseVerb: {added: 'ajouté', removed: 'supprimé', changed: 'modifié', set: 'définir', applied: 'appliqué'}, + bodyMerchant: ({adjective, value}: {adjective: string; value: string}) => (adjective !== '' ? `Commerçant·e ${adjective} « ${value} »` : `commerçant « ${value} »`), + bodyMerchantChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `${adjective} commerçant de « ${oldValue} » à « ${newValue} »` : `commerçant de « ${oldValue} » à « ${newValue} »`, + bodySpendCategory: ({adjective, value}: {adjective: string; value: string}) => + adjective !== '' ? `Catégorie de dépense ${adjective} « ${value} »` : `catégorie de dépense « ${value} »`, + bodySpendCategoryChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `Catégorie de dépense ${adjective} de « ${oldValue} » à « ${newValue} »` : `catégorie de dépense de « ${oldValue} » à « ${newValue} »`, + bodyMaxAmount: 'montant maximal', + bodyMaxAmountSet: ({value}: {value: string}) => `montant maximal à ${value}`, + bodyMaxAmountChange: ({oldValue, newValue}: {oldValue: string; newValue: string}) => `montant maximum de ${oldValue} à ${newValue}`, + bodyAppliedToAdditionalCards: ({count}: {count: number}) => `règle de dépense pour ${count} cartes supplémentaires`, + bodyRemovedFromCards: ({cards}: {cards: string}) => `règle de dépense à partir de ${cards}`, + composeOnCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} sur ${cards}` : content), + composeFromCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} depuis ${cards}` : content), + }, + }, }, roomMembersPage: { memberNotFound: 'Membre introuvable.', diff --git a/src/languages/it.ts b/src/languages/it.ts index 85412c276f1f..0566007034bc 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -7565,6 +7565,57 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, addedReportField: (fieldType: string, fieldName?: string, defaultValue?: string) => `aggiunto campo di report ${fieldType} "${fieldName}"${defaultValue ? ` con valore predefinito "${defaultValue}"` : ''}`, updatedRequireCompanyCards: ({enabled}: {enabled: boolean}) => `${enabled ? 'abilitato' : 'disabilitato'} il requisito per gli acquisti con carta aziendale`, + expensifyCardRule: { + actionVerb: {block: 'bloccato', allow: 'consentito'}, + amountOperator: {over: 'terminato', under: 'sotto'}, + amountFilter: ({operator, amount}: {operator: string; amount: string}) => `importi ${operator} ${amount}`, + theCard: 'la carta', + multipleCards: ({count}: {count: number}) => `${count} carte`, + joinFilters: ({items}: {items: string[]}) => { + if (items.length === 0) { + return ''; + } + if (items.length === 1) { + return items.at(0) ?? ''; + } + if (items.length === 2) { + return `${items.at(0)} e ${items.at(1)}`; + } + return `${items.slice(0, -1).join(', ')} e ${items.at(-1)}`; + }, + addRule: ({verb, filters, cards}: {verb: string; filters: string; cards: string}) => { + let text = verb; + if (filters !== '') { + text += `${text === '' ? '' : ' '}${filters}`; + } + if (cards !== '') { + text += `${text === '' ? '' : ' '} su ${cards}`; + } + return text; + }, + removeRule: ({cards}: {cards: string}) => (cards !== '' ? `ha rimosso la regola di spesa da ${cards}` : 'regola di spesa rimossa'), + restrictionVerb: {block: 'bloc', allow: 'consenti solo'}, + update: { + modeChange: ({fromAction, toAction, cards}: {fromAction: string; toAction: string; cards: string}) => + cards !== '' ? `ha modificato la regola di spesa da ${fromAction} a ${toAction} su ${cards}` : `ha modificato la regola di spesa da ${fromAction} a ${toAction}`, + appliedToAdditionalCards: ({count}: {count: number}) => `regola di spesa applicata a ${count} carte aggiuntive`, + phraseVerb: {added: 'aggiunto', removed: 'rimosso', changed: 'modificato', set: 'imposta', applied: 'applicato'}, + bodyMerchant: ({adjective, value}: {adjective: string; value: string}) => (adjective !== '' ? `${adjective} esercente '${value}'` : `esercente '${value}'`), + bodyMerchantChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `${adjective} esercente da '${oldValue}' a '${newValue}'` : `esercente da '${oldValue}' a '${newValue}'`, + bodySpendCategory: ({adjective, value}: {adjective: string; value: string}) => + adjective !== '' ? `categoria di spesa ${adjective} "${value}"` : `categoria di spesa '${value}'`, + bodySpendCategoryChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `categoria di spesa ${adjective} da '${oldValue}' a '${newValue}'` : `categoria di spesa da '${oldValue}' a '${newValue}'`, + bodyMaxAmount: 'importo massimo', + bodyMaxAmountSet: ({value}: {value: string}) => `importo massimo pari a ${value}`, + bodyMaxAmountChange: ({oldValue, newValue}: {oldValue: string; newValue: string}) => `importo massimo da ${oldValue} a ${newValue}`, + bodyAppliedToAdditionalCards: ({count}: {count: number}) => `regola di spesa per ${count} carte aggiuntive`, + bodyRemovedFromCards: ({cards}: {cards: string}) => `regola di spesa da ${cards}`, + composeOnCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} su ${cards}` : content), + composeFromCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} da ${cards}` : content), + }, + }, }, roomMembersPage: { memberNotFound: 'Membro non trovato.', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 35666c340a22..cd88a35a8a63 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -7474,6 +7474,56 @@ ${reportName} addedReportField: (fieldType: string, fieldName?: string, defaultValue?: string) => `${fieldType}レポートフィールド「${fieldName}」を追加しました${defaultValue ? ` デフォルト値「${defaultValue}」付き` : ''}`, updatedRequireCompanyCards: ({enabled}: {enabled: boolean}) => `${enabled ? '有効' : '無効'} の法人カード購入要件`, + expensifyCardRule: { + actionVerb: {block: 'ブロック済み', allow: '許可済み'}, + amountOperator: {over: '終了', under: '以下の条件のもと'}, + amountFilter: ({operator, amount}: {operator: string; amount: string}) => `金額 ${operator} ${amount}`, + theCard: 'カード', + multipleCards: ({count}: {count: number}) => `${count} 件のカード`, + joinFilters: ({items}: {items: string[]}) => { + if (items.length === 0) { + return ''; + } + if (items.length === 1) { + return items.at(0) ?? ''; + } + if (items.length === 2) { + return `${items.at(0)} と ${items.at(1)}`; + } + return `${items.slice(0, -1).join(', ')}、${items.at(-1)}`; + }, + addRule: ({verb, filters, cards}: {verb: string; filters: string; cards: string}) => { + let text = verb; + if (filters !== '') { + text += `${text === '' ? '' : ' '}${filters}`; + } + if (cards !== '') { + text += `${cards}での${text === '' ? '' : ' '}`; + } + return text; + }, + removeRule: ({cards}: {cards: string}) => (cards !== '' ? `${cards} から支出ルールを削除しました` : '支出ルールを削除しました'), + restrictionVerb: {block: 'ブロック', allow: 'のみ許可'}, + update: { + modeChange: ({fromAction, toAction, cards}: {fromAction: string; toAction: string; cards: string}) => + cards !== '' ? `${cards} の支出ルールを ${fromAction} から ${toAction} に変更しました` : `支出ルールを${fromAction}から${toAction}に変更しました`, + appliedToAdditionalCards: ({count}: {count: number}) => `${count} 枚の追加カードに支出ルールを適用しました`, + phraseVerb: {added: '追加済み', removed: '削除済み', changed: '変更済み', set: '設定', applied: '適用済み'}, + bodyMerchant: ({adjective, value}: {adjective: string; value: string}) => (adjective !== '' ? `${adjective}なマーチャント「${value}」` : `加盟店「${value}」`), + bodyMerchantChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `${adjective}加盟店を「${oldValue}」から「${newValue}」に変更しました` : `加盟店名を「${oldValue}」から「${newValue}」に変更`, + bodySpendCategory: ({adjective, value}: {adjective: string; value: string}) => (adjective !== '' ? `${adjective}支出カテゴリー「${value}」` : `支出カテゴリ「${value}」`), + bodySpendCategoryChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `${adjective}支出カテゴリを「${oldValue}」から「${newValue}」に変更しました` : `支出カテゴリを「${oldValue}」から「${newValue}」に変更`, + bodyMaxAmount: '最大金額', + bodyMaxAmountSet: ({value}: {value: string}) => `最大金額を${value}に設定`, + bodyMaxAmountChange: ({oldValue, newValue}: {oldValue: string; newValue: string}) => `最大金額を${oldValue}から${newValue}に変更`, + bodyAppliedToAdditionalCards: ({count}: {count: number}) => `${count} 枚の追加カードに支出ルールを適用`, + bodyRemovedFromCards: ({cards}: {cards: string}) => `${cards}の支出ルール`, + composeOnCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${cards}の${content}` : content), + composeFromCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${cards} からの ${content}` : content), + }, + }, }, roomMembersPage: { memberNotFound: 'メンバーが見つかりません。', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 11864eee56d8..67bd8a6ec2c1 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -7539,6 +7539,57 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, addedReportField: (fieldType: string, fieldName?: string, defaultValue?: string) => `heeft ${fieldType}-rapportveld "${fieldName}" toegevoegd${defaultValue ? ` met standaardwaarde "${defaultValue}"` : ''}`, updatedRequireCompanyCards: ({enabled}: {enabled: boolean}) => `vereiste ${enabled ? 'ingeschakeld' : 'uitgeschakeld'} voor bedrijfskaarttransacties`, + expensifyCardRule: { + actionVerb: {block: 'geblokkeerd', allow: 'toegestaan'}, + amountOperator: {over: 'over', under: 'onder'}, + amountFilter: ({operator, amount}: {operator: string; amount: string}) => `bedragen ${operator} ${amount}`, + theCard: 'de kaart', + multipleCards: ({count}: {count: number}) => `${count} kaarten`, + joinFilters: ({items}: {items: string[]}) => { + if (items.length === 0) { + return ''; + } + if (items.length === 1) { + return items.at(0) ?? ''; + } + if (items.length === 2) { + return `${items.at(0)} en ${items.at(1)}`; + } + return `${items.slice(0, -1).join(', ')} en ${items.at(-1)}`; + }, + addRule: ({verb, filters, cards}: {verb: string; filters: string; cards: string}) => { + let text = verb; + if (filters !== '') { + text += `${text === '' ? '' : ' '}${filters}`; + } + if (cards !== '') { + text += `${text === '' ? '' : ' '}op ${cards}`; + } + return text; + }, + removeRule: ({cards}: {cards: string}) => (cards !== '' ? `uitgavenregel verwijderd van ${cards}` : 'uitgave-regel verwijderd'), + restrictionVerb: {block: 'blokkeren', allow: 'alleen toestaan'}, + update: { + modeChange: ({fromAction, toAction, cards}: {fromAction: string; toAction: string; cards: string}) => + cards !== '' ? `uitgave-regel gewijzigd van ${fromAction} naar ${toAction} op ${cards}` : `heeft bestedingsregel gewijzigd van ${fromAction} naar ${toAction}`, + appliedToAdditionalCards: ({count}: {count: number}) => `bestedingsregel toegepast op ${count} extra kaarten`, + phraseVerb: {added: 'toegevoegd', removed: 'verwijderd', changed: 'gewijzigd', set: 'instellen', applied: 'toegepast'}, + bodyMerchant: ({adjective, value}: {adjective: string; value: string}) => (adjective !== '' ? `${adjective} handelaar '${value}'` : `handelaar '${value}'`), + bodyMerchantChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `${adjective} handelaar van '${oldValue}' naar '${newValue}'` : `handelaar van '${oldValue}' naar '${newValue}'`, + bodySpendCategory: ({adjective, value}: {adjective: string; value: string}) => + adjective !== '' ? `${adjective} uitgavencategorie '${value}'` : `uitgavencategorie '${value}'`, + bodySpendCategoryChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `${adjective} uitgavencategorie van '${oldValue}' naar '${newValue}'` : `uitgavecategorie van '${oldValue}' naar '${newValue}'`, + bodyMaxAmount: 'max. bedrag', + bodyMaxAmountSet: ({value}: {value: string}) => `max. bedrag tot ${value}`, + bodyMaxAmountChange: ({oldValue, newValue}: {oldValue: string; newValue: string}) => `max. bedrag van ${oldValue} naar ${newValue}`, + bodyAppliedToAdditionalCards: ({count}: {count: number}) => `bestedsregel naar ${count} extra kaarten`, + bodyRemovedFromCards: ({cards}: {cards: string}) => `bestedingsregel van ${cards}`, + composeOnCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} op ${cards}` : content), + composeFromCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} van ${cards}` : content), + }, + }, }, roomMembersPage: { memberNotFound: 'Lid niet gevonden.', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index fee79580a133..8442dae3cefc 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -7530,6 +7530,57 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, addedReportField: (fieldType: string, fieldName?: string, defaultValue?: string) => `dodano pole raportu typu ${fieldType} „${fieldName}”${defaultValue ? ` z domyślną wartością „${defaultValue}”` : ''}`, updatedRequireCompanyCards: ({enabled}: {enabled: boolean}) => `${enabled ? 'włączone' : 'wyłączone'} wymóg dotyczący zakupów kartą służbową`, + expensifyCardRule: { + actionVerb: {block: 'zablokowano', allow: 'dozwolone'}, + amountOperator: {over: 'ponad', under: 'pod'}, + amountFilter: ({operator, amount}: {operator: string; amount: string}) => `kwoty ${operator} ${amount}`, + theCard: 'karta', + multipleCards: ({count}: {count: number}) => `${count} karty`, + joinFilters: ({items}: {items: string[]}) => { + if (items.length === 0) { + return ''; + } + if (items.length === 1) { + return items.at(0) ?? ''; + } + if (items.length === 2) { + return `${items.at(0)} i ${items.at(1)}`; + } + return `${items.slice(0, -1).join(', ')} i ${items.at(-1)}`; + }, + addRule: ({verb, filters, cards}: {verb: string; filters: string; cards: string}) => { + let text = verb; + if (filters !== '') { + text += `${text === '' ? '' : ' '}${filters}`; + } + if (cards !== '') { + text += `${text === '' ? '' : ' '} na ${cards}`; + } + return text; + }, + removeRule: ({cards}: {cards: string}) => (cards !== '' ? `usunięto regułę wydatków z ${cards}` : 'usunięto regułę wydatków'), + restrictionVerb: {block: 'zablokuj', allow: 'zezwól tylko'}, + update: { + modeChange: ({fromAction, toAction, cards}: {fromAction: string; toAction: string; cards: string}) => + cards !== '' ? `zmieniono regułę wydatków z ${fromAction} na ${toAction} na ${cards}` : `zmienił(a) regułę wydatków z ${fromAction} na ${toAction}`, + appliedToAdditionalCards: ({count}: {count: number}) => `zastosowano regułę wydatków do ${count} dodatkowych kart`, + phraseVerb: {added: 'dodano', removed: 'usunięto', changed: 'zmieniono', set: 'ustaw', applied: 'zastosowano'}, + bodyMerchant: ({adjective, value}: {adjective: string; value: string}) => (adjective !== '' ? `${adjective} sprzedawca „${value}”` : `sprzedawca „${value}”`), + bodyMerchantChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `${adjective} sprzedawcę z „${oldValue}” na „${newValue}”` : `sprzedawcę z „${oldValue}” na „${newValue}”`, + bodySpendCategory: ({adjective, value}: {adjective: string; value: string}) => + adjective !== '' ? `kategoria wydatków ${adjective} „${value}”` : `kategoria wydatków „${value}”`, + bodySpendCategoryChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `${adjective} kategorię wydatków z „${oldValue}” na „${newValue}”` : `kategoria wydatku z „${oldValue}” na „${newValue}”`, + bodyMaxAmount: 'maksymalna kwota', + bodyMaxAmountSet: ({value}: {value: string}) => `maksymalna kwota do ${value}`, + bodyMaxAmountChange: ({oldValue, newValue}: {oldValue: string; newValue: string}) => `maksymalna kwota z ${oldValue} na ${newValue}`, + bodyAppliedToAdditionalCards: ({count}: {count: number}) => `zasada wydatków dla ${count} dodatkowych kart`, + bodyRemovedFromCards: ({cards}: {cards: string}) => `reguła wydatków z ${cards}`, + composeOnCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} na ${cards}` : content), + composeFromCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} z ${cards}` : content), + }, + }, }, roomMembersPage: { memberNotFound: 'Nie znaleziono członka.', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index d44b4ef42fa6..c3b7bdfb51c2 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -7531,6 +7531,57 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, addedReportField: (fieldType: string, fieldName?: string, defaultValue?: string) => `adicionou o campo de relatório ${fieldType} "${fieldName}"${defaultValue ? ` com valor padrão "${defaultValue}"` : ''}`, updatedRequireCompanyCards: ({enabled}: {enabled: boolean}) => `${enabled ? 'ativado' : 'desativado'} o requisito de compras com cartão corporativo`, + expensifyCardRule: { + actionVerb: {block: 'bloqueado', allow: 'permitido'}, + amountOperator: {over: 'sobre', under: 'abaixo'}, + amountFilter: ({operator, amount}: {operator: string; amount: string}) => `valores ${operator} ${amount}`, + theCard: 'o cartão', + multipleCards: ({count}: {count: number}) => `${count} cartões`, + joinFilters: ({items}: {items: string[]}) => { + if (items.length === 0) { + return ''; + } + if (items.length === 1) { + return items.at(0) ?? ''; + } + if (items.length === 2) { + return `${items.at(0)} e ${items.at(1)}`; + } + return `${items.slice(0, -1).join(', ')}, e ${items.at(-1)}`; + }, + addRule: ({verb, filters, cards}: {verb: string; filters: string; cards: string}) => { + let text = verb; + if (filters !== '') { + text += `${text === '' ? '' : ' '}${filters}`; + } + if (cards !== '') { + text += `${text === '' ? '' : ' '}em ${cards}`; + } + return text; + }, + removeRule: ({cards}: {cards: string}) => (cards !== '' ? `removeu a regra de gasto de ${cards}` : 'removeu a regra de gasto'), + restrictionVerb: {block: 'bloquear', allow: 'permitir somente'}, + update: { + modeChange: ({fromAction, toAction, cards}: {fromAction: string; toAction: string; cards: string}) => + cards !== '' ? `alterou a regra de gasto de ${fromAction} para ${toAction} em ${cards}` : `alterou a regra de gasto de ${fromAction} para ${toAction}`, + appliedToAdditionalCards: ({count}: {count: number}) => `regra de gasto aplicada a mais ${count} cartões`, + phraseVerb: {added: 'adicionado', removed: 'removido', changed: 'alterado', set: 'definir', applied: 'aplicado'}, + bodyMerchant: ({adjective, value}: {adjective: string; value: string}) => (adjective !== '' ? `Comerciante ${adjective} '${value}'` : `estabelecimento '${value}'`), + bodyMerchantChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `${adjective} comerciante de '${oldValue}' para '${newValue}'` : `estabelecimento comercial de '${oldValue}' para '${newValue}'`, + bodySpendCategory: ({adjective, value}: {adjective: string; value: string}) => + adjective !== '' ? `categoria de gasto ${adjective} '${value}'` : `categoria de despesa '${value}'`, + bodySpendCategoryChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `categoria de gasto ${adjective} de '${oldValue}' para '${newValue}'` : `categoria de gasto de '${oldValue}' para '${newValue}'`, + bodyMaxAmount: 'valor máximo', + bodyMaxAmountSet: ({value}: {value: string}) => `valor máximo de ${value}`, + bodyMaxAmountChange: ({oldValue, newValue}: {oldValue: string; newValue: string}) => `valor máximo de ${oldValue} para ${newValue}`, + bodyAppliedToAdditionalCards: ({count}: {count: number}) => `regra de gasto para ${count} cartões adicionais`, + bodyRemovedFromCards: ({cards}: {cards: string}) => `regra de gasto de ${cards}`, + composeOnCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} em ${cards}` : content), + composeFromCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${content} de ${cards}` : content), + }, + }, }, roomMembersPage: { memberNotFound: 'Membro não encontrado.', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index d416e61161e1..4db9fce46503 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -7344,6 +7344,56 @@ ${reportName} `已更改卡片流水“${feedName}”的账单周期截止日${newValue ? ` 为“${newValue}”` : ''}${previousValue ? ` (先前为“${previousValue}”)` : ''}`, addedReportField: (fieldType: string, fieldName?: string, defaultValue?: string) => `已添加 ${fieldType} 报告字段“${fieldName}”${defaultValue ? ` 默认值为“${defaultValue}”` : ''}`, updatedRequireCompanyCards: ({enabled}: {enabled: boolean}) => `${enabled ? '已启用' : '已禁用'} 公司商务卡消费要求`, + expensifyCardRule: { + actionVerb: {block: '已阻止', allow: '允许'}, + amountOperator: {over: '结束', under: '在…之下'}, + amountFilter: ({operator, amount}: {operator: string; amount: string}) => `金额 ${operator} ${amount}`, + theCard: '该卡', + multipleCards: ({count}: {count: number}) => `${count} 张卡片`, + joinFilters: ({items}: {items: string[]}) => { + if (items.length === 0) { + return ''; + } + if (items.length === 1) { + return items.at(0) ?? ''; + } + if (items.length === 2) { + return `${items.at(0)} 和 ${items.at(1)}`; + } + return `${items.slice(0, -1).join(', ')},和 ${items.at(-1)}`; + }, + addRule: ({verb, filters, cards}: {verb: string; filters: string; cards: string}) => { + let text = verb; + if (filters !== '') { + text += `${text === '' ? '' : ' '}${filters}`; + } + if (cards !== '') { + text += `${cards} 上的 ${text === '' ? '' : ' '}`; + } + return text; + }, + removeRule: ({cards}: {cards: string}) => (cards !== '' ? `已从${cards}中移除消费规则` : '已移除消费规则'), + restrictionVerb: {block: '封锁', allow: '仅允许'}, + update: { + modeChange: ({fromAction, toAction, cards}: {fromAction: string; toAction: string; cards: string}) => + cards !== '' ? `已将 ${cards} 的消费规则从 ${fromAction} 更改为 ${toAction}` : `将消费规则从 ${fromAction} 更改为 ${toAction}`, + appliedToAdditionalCards: ({count}: {count: number}) => `已将消费规则应用到另外 ${count} 张卡片`, + phraseVerb: {added: '已添加', removed: '已移除', changed: '已更改', set: '设置', applied: '已应用'}, + bodyMerchant: ({adjective, value}: {adjective: string; value: string}) => (adjective !== '' ? `${adjective} 商户“${value}”` : `商户“${value}”`), + bodyMerchantChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `将${adjective}商户从“${oldValue}”更改为“${newValue}”` : `商户从“${oldValue}”变更为“${newValue}”`, + bodySpendCategory: ({adjective, value}: {adjective: string; value: string}) => (adjective !== '' ? `${adjective} 支出类别“${value}”` : `支出类别“${value}”`), + bodySpendCategoryChange: ({adjective, oldValue, newValue}: {adjective: string; oldValue: string; newValue: string}) => + adjective !== '' ? `将${adjective}支出类别从“${oldValue}”更改为“${newValue}”` : `将支出类别从“${oldValue}”更改为“${newValue}”`, + bodyMaxAmount: '最高金额', + bodyMaxAmountSet: ({value}: {value: string}) => `最大金额为 ${value}`, + bodyMaxAmountChange: ({oldValue, newValue}: {oldValue: string; newValue: string}) => `最大金额从 ${oldValue} 变更为 ${newValue}`, + bodyAppliedToAdditionalCards: ({count}: {count: number}) => `将消费规则应用到另外 ${count} 张卡片`, + bodyRemovedFromCards: ({cards}: {cards: string}) => `来自${cards}的支出规则`, + composeOnCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `${cards} 上的 ${content}` : content), + composeFromCards: ({content, cards}: {content: string; cards: string}) => (cards !== '' ? `来自${cards}的${content}` : content), + }, + }, }, roomMembersPage: { memberNotFound: '未找到成员。', diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 72dac148400d..267d85cdcc33 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -15,6 +15,7 @@ import IntlStore from '@src/languages/IntlStore'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import {isSpendRuleCategory} from '@src/types/form/SpendRuleForm'; import type { Card, CompanyCardFeed, @@ -3902,6 +3903,406 @@ function getUpdatedApprovalRuleMessage(translate: LocalizedTranslate, reportActi return getReportActionText(reportAction); } +function getSpendRuleActionVerb(translate: LocalizedTranslate, action: string): string { + if (action === CONST.SPEND_RULES.ACTION.BLOCK) { + return translate('workspaceActions.expensifyCardRule.actionVerb.block'); + } + if (action === CONST.SPEND_RULES.ACTION.ALLOW) { + return translate('workspaceActions.expensifyCardRule.actionVerb.allow'); + } + return ''; +} + +function spendRuleAmountOperatorWord(translate: LocalizedTranslate, operator: string): string { + if (operator === CONST.SEARCH.SYNTAX_OPERATORS.LOWER_THAN_OR_EQUAL_TO) { + return translate('workspaceActions.expensifyCardRule.amountOperator.under'); + } + if (operator === CONST.SEARCH.SYNTAX_OPERATORS.GREATER_THAN) { + return translate('workspaceActions.expensifyCardRule.amountOperator.over'); + } + return ''; +} + +function getSpendRuleAmountString(translate: LocalizedTranslate, amount: {operator: string; value: string[]}, currency: string): string { + const operatorWord = spendRuleAmountOperatorWord(translate, amount.operator); + const firstValue = amount.value.at(0); + if (firstValue === undefined) { + return ''; + } + return translate('workspaceActions.expensifyCardRule.amountFilter', {operator: operatorWord, amount: convertAmountToDisplayString(Number(firstValue), currency)}); +} + +function getSpendRuleCardsSummary(translate: LocalizedTranslate, cards: ReadonlyArray<{displayName?: string}> | undefined): string { + if (!cards || cards.length === 0) { + return translate('workspaceActions.expensifyCardRule.theCard'); + } + if (cards.length === 1) { + const displayName = cards.at(0)?.displayName ?? ''; + return displayName !== '' ? displayName : translate('workspaceActions.expensifyCardRule.theCard'); + } + return translate('workspaceActions.expensifyCardRule.multipleCards', {count: cards.length}); +} + +function getSpendRuleJoinFilters(translate: LocalizedTranslate, items: readonly string[]): string { + const filtered = items.filter((value) => typeof value === 'string' && value !== ''); + return translate('workspaceActions.expensifyCardRule.joinFilters', {items: filtered}); +} + +function getSpendRuleCategoryDisplayName(translate: LocalizedTranslate, category: string): string { + if (isSpendRuleCategory(category)) { + return translate(`workspace.rules.spendRules.categoryOptions.${category}`); + } + return category; +} + +function getSpendRuleRestrictionVerb(translate: LocalizedTranslate, action: string): string { + if (action === CONST.SPEND_RULES.ACTION.BLOCK) { + return translate('workspaceActions.expensifyCardRule.restrictionVerb.block'); + } + if (action === CONST.SPEND_RULES.ACTION.ALLOW) { + return translate('workspaceActions.expensifyCardRule.restrictionVerb.allow'); + } + return action; +} + +function formatSpendRuleAmountToCents(value: string[]): number { + const firstValue = value.at(0) ?? ''; + if (firstValue === '' || !Number.isFinite(Number(firstValue))) { + return 0; + } + return Number.parseInt(firstValue, 10) * 100; +} + +function spendRuleFormatAmountValue(amount: {value: string[]}, currency: string): string { + return convertAmountToDisplayString(formatSpendRuleAmountToCents(amount.value), currency); +} + +type SpendRuleStringDiff = {added: string[]; removed: string[]}; + +function computeSpendRuleStringDiff(oldValues: string[], newValues: string[]): SpendRuleStringDiff { + const oldSet = Array.from(new Set(oldValues)); + const newSet = Array.from(new Set(newValues)); + const added = newSet.filter((value) => !oldSet.includes(value)).sort(); + const removed = oldSet.filter((value) => !newSet.includes(value)).sort(); + return {added, removed}; +} + +type SpendRuleAmount = {operator: string; value: string[]}; +type SpendRuleAmountDiff = {added: SpendRuleAmount[]; removed: SpendRuleAmount[]}; + +function computeSpendRuleAmountDiff(oldAmounts: SpendRuleAmount[], newAmounts: SpendRuleAmount[]): SpendRuleAmountDiff { + const oldAmount = oldAmounts.at(0); + const newAmount = newAmounts.at(0); + if (!oldAmount || !newAmount) { + return {added: [], removed: []}; + } + const sameAmount = formatSpendRuleAmountToCents(oldAmount.value) === formatSpendRuleAmountToCents(newAmount.value); + if (sameAmount) { + return {added: [], removed: []}; + } + return { + added: [newAmount], + removed: [oldAmount], + }; +} + +type SpendRuleCard = {cardID?: number | string; displayName?: string}; +type SpendRuleCardDiff = {added: SpendRuleCard[]; removed: SpendRuleCard[]}; + +function spendRuleCardID(card: SpendRuleCard): number | undefined { + const raw = card?.cardID; + if (typeof raw === 'number' && Number.isFinite(raw)) { + return raw; + } + if (typeof raw === 'string' && /^\d+$/.test(raw)) { + return Number.parseInt(raw, 10); + } + return undefined; +} + +function computeSpendRuleCardDiff(oldCards: SpendRuleCard[], newCards: SpendRuleCard[]): SpendRuleCardDiff { + const oldByID = new Map(); + for (const card of oldCards) { + const id = spendRuleCardID(card); + if (id !== undefined) { + oldByID.set(id, card); + } + } + const newByID = new Map(); + for (const card of newCards) { + const id = spendRuleCardID(card); + if (id !== undefined) { + newByID.set(id, card); + } + } + const added: SpendRuleCard[] = []; + for (const [id, card] of newByID) { + if (!oldByID.has(id)) { + added.push(card); + } + } + const removed: SpendRuleCard[] = []; + for (const [id, card] of oldByID) { + if (!newByID.has(id)) { + removed.push(card); + } + } + return {added, removed}; +} + +type SpendRulePhraseVerb = 'added' | 'removed' | 'changed' | 'set' | 'applied'; +type SpendRulePhraseAdjective = '' | typeof CONST.SPEND_RULES.ACTION.BLOCK | typeof CONST.SPEND_RULES.ACTION.ALLOW; + +type SpendRulePhrase = { + verb: SpendRulePhraseVerb; + adjective: SpendRulePhraseAdjective; + bodyWithAdjective: string; + bodyWithoutAdjective: string; +}; + +function spendRulePhraseVerbWord(translate: LocalizedTranslate, verb: SpendRulePhraseVerb): string { + return translate(`workspaceActions.expensifyCardRule.update.phraseVerb.${verb}`); +} + +function joinSpendRulePhrases(translate: LocalizedTranslate, phrases: readonly SpendRulePhrase[]): string { + if (phrases.length === 0) { + return ''; + } + if (phrases.length === 1) { + const phrase = phrases.at(0); + if (!phrase) { + return ''; + } + return `${spendRulePhraseVerbWord(translate, phrase.verb)} ${phrase.bodyWithAdjective}`; + } + + const firstVerb = phrases.at(0)?.verb; + const allSameVerb = firstVerb !== undefined && phrases.every((phrase) => phrase.verb === firstVerb); + + if (!allSameVerb) { + const parts = phrases.map((phrase) => `${spendRulePhraseVerbWord(translate, phrase.verb)} ${phrase.bodyWithAdjective}`); + return getSpendRuleJoinFilters(translate, parts); + } + + const firstPhrase = phrases.at(0); + if (!firstPhrase) { + return ''; + } + const firstAdjective = firstPhrase.adjective; + const parts: string[] = [`${spendRulePhraseVerbWord(translate, firstPhrase.verb)} ${firstPhrase.bodyWithAdjective}`]; + for (let i = 1; i < phrases.length; i++) { + const phrase = phrases.at(i); + if (!phrase) { + continue; + } + const useOwnAdjective = phrase.adjective !== '' && phrase.adjective !== firstAdjective; + parts.push(useOwnAdjective ? phrase.bodyWithAdjective : phrase.bodyWithoutAdjective); + } + return getSpendRuleJoinFilters(translate, parts); +} + +function getAddExpensifyCardRuleMessage(translate: LocalizedTranslate, reportAction: OnyxEntry): string { + if (!isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_EXPENSIFY_CARD_RULE)) { + return ''; + } + const message = getOriginalMessage(reportAction) ?? {}; + const action = message.action ?? CONST.SPEND_RULES.ACTION.ALLOW; + const currency = message.currency ?? CONST.CURRENCY.USD; + const merchants = message.merchants ?? []; + const categories = message.categories ?? []; + const amounts = message.amounts ?? []; + const cards = message.cards ?? []; + + const items: string[] = []; + for (const merchant of merchants) { + items.push(merchant); + } + for (const category of categories) { + items.push(getSpendRuleCategoryDisplayName(translate, category)); + } + for (const amount of amounts) { + const formattedAmount = getSpendRuleAmountString(translate, amount, currency); + if (formattedAmount !== '') { + items.push(formattedAmount); + } + } + + const verb = getSpendRuleActionVerb(translate, action); + const filters = getSpendRuleJoinFilters(translate, items); + const cardsSummary = getSpendRuleCardsSummary(translate, cards); + + if (verb === '' && filters === '' && cardsSummary === '') { + return getReportActionText(reportAction); + } + + return translate('workspaceActions.expensifyCardRule.addRule', {verb, filters, cards: cardsSummary}); +} + +function getUpdateExpensifyCardRuleMessage(translate: LocalizedTranslate, reportAction: OnyxEntry): string { + if (!isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_EXPENSIFY_CARD_RULE)) { + return ''; + } + const message = getOriginalMessage(reportAction) ?? {}; + const oldAction = message.oldAction ?? CONST.SPEND_RULES.ACTION.ALLOW; + const newAction = message.action ?? CONST.SPEND_RULES.ACTION.ALLOW; + const actionChanged = oldAction !== '' && oldAction !== newAction; + const currency = message.currency ?? CONST.CURRENCY.USD; + + const oldMerchants = message.oldMerchants ?? []; + const newMerchants = message.merchants ?? []; + const oldCategories = message.oldCategories ?? []; + const newCategories = message.categories ?? []; + const oldAmounts = message.oldAmounts ?? []; + const newAmounts = message.amounts ?? []; + const oldCards = message.oldCards ?? []; + const newCards = message.cards ?? []; + + const merchantDiff = computeSpendRuleStringDiff(oldMerchants, newMerchants); + const categoryDiff = computeSpendRuleStringDiff(oldCategories, newCategories); + const amountDiff = computeSpendRuleAmountDiff(oldAmounts, newAmounts); + const cardDiff = computeSpendRuleCardDiff(oldCards, newCards); + + const merchantsChanged = merchantDiff.added.length > 0 || merchantDiff.removed.length > 0; + const categoriesChanged = categoryDiff.added.length > 0 || categoryDiff.removed.length > 0; + const amountsChanged = amountDiff.added.length > 0 || amountDiff.removed.length > 0; + const cardsChanged = cardDiff.added.length > 0 || cardDiff.removed.length > 0; + const filtersAndCardsUnchanged = !merchantsChanged && !categoriesChanged && !amountsChanged && !cardsChanged; + + const newCardsSummary = getSpendRuleCardsSummary(translate, newCards); + + if (actionChanged && filtersAndCardsUnchanged) { + return translate('workspaceActions.expensifyCardRule.update.modeChange', { + fromAction: getSpendRuleRestrictionVerb(translate, oldAction), + toAction: getSpendRuleRestrictionVerb(translate, newAction), + cards: newCardsSummary, + }); + } + + if (cardsChanged && !merchantsChanged && !categoriesChanged && !amountsChanged && !actionChanged) { + if (cardDiff.added.length > 0 && cardDiff.removed.length === 0) { + return translate('workspaceActions.expensifyCardRule.update.appliedToAdditionalCards', {count: cardDiff.added.length}); + } + if (cardDiff.added.length === 0 && cardDiff.removed.length > 0) { + return translate('workspaceActions.expensifyCardRule.removeRule', {cards: getSpendRuleCardsSummary(translate, cardDiff.removed)}); + } + } + + const adjective: SpendRulePhraseAdjective = newAction === CONST.SPEND_RULES.ACTION.BLOCK || newAction === CONST.SPEND_RULES.ACTION.ALLOW ? newAction : ''; + const adjectiveWord = getSpendRuleActionVerb(translate, adjective); + const phrases: SpendRulePhrase[] = []; + + if (merchantDiff.added.length === 1 && merchantDiff.removed.length === 1) { + const oldValue = merchantDiff.removed.at(0) ?? ''; + const newValue = merchantDiff.added.at(0) ?? ''; + phrases.push({ + verb: 'changed', + adjective, + bodyWithAdjective: translate('workspaceActions.expensifyCardRule.update.bodyMerchantChange', {adjective: adjectiveWord, oldValue, newValue}), + bodyWithoutAdjective: translate('workspaceActions.expensifyCardRule.update.bodyMerchantChange', {adjective: '', oldValue, newValue}), + }); + } else { + for (const merchant of merchantDiff.added) { + phrases.push({ + verb: 'added', + adjective, + bodyWithAdjective: translate('workspaceActions.expensifyCardRule.update.bodyMerchant', {adjective: adjectiveWord, value: merchant}), + bodyWithoutAdjective: translate('workspaceActions.expensifyCardRule.update.bodyMerchant', {adjective: '', value: merchant}), + }); + } + for (const merchant of merchantDiff.removed) { + phrases.push({ + verb: 'removed', + adjective, + bodyWithAdjective: translate('workspaceActions.expensifyCardRule.update.bodyMerchant', {adjective: adjectiveWord, value: merchant}), + bodyWithoutAdjective: translate('workspaceActions.expensifyCardRule.update.bodyMerchant', {adjective: '', value: merchant}), + }); + } + } + + if (categoryDiff.added.length === 1 && categoryDiff.removed.length === 1) { + const oldValue = getSpendRuleCategoryDisplayName(translate, categoryDiff.removed.at(0) ?? ''); + const newValue = getSpendRuleCategoryDisplayName(translate, categoryDiff.added.at(0) ?? ''); + phrases.push({ + verb: 'changed', + adjective, + bodyWithAdjective: translate('workspaceActions.expensifyCardRule.update.bodySpendCategoryChange', {adjective: adjectiveWord, oldValue, newValue}), + bodyWithoutAdjective: translate('workspaceActions.expensifyCardRule.update.bodySpendCategoryChange', {adjective: '', oldValue, newValue}), + }); + } else { + for (const category of categoryDiff.added) { + const value = getSpendRuleCategoryDisplayName(translate, category); + phrases.push({ + verb: 'added', + adjective, + bodyWithAdjective: translate('workspaceActions.expensifyCardRule.update.bodySpendCategory', {adjective: adjectiveWord, value}), + bodyWithoutAdjective: translate('workspaceActions.expensifyCardRule.update.bodySpendCategory', {adjective: '', value}), + }); + } + for (const category of categoryDiff.removed) { + const value = getSpendRuleCategoryDisplayName(translate, category); + phrases.push({ + verb: 'removed', + adjective, + bodyWithAdjective: translate('workspaceActions.expensifyCardRule.update.bodySpendCategory', {adjective: adjectiveWord, value}), + bodyWithoutAdjective: translate('workspaceActions.expensifyCardRule.update.bodySpendCategory', {adjective: '', value}), + }); + } + } + + if (amountDiff.added.length === 1 && amountDiff.removed.length === 1) { + const oldValue = spendRuleFormatAmountValue(amountDiff.removed.at(0) ?? {value: []}, currency); + const newValue = spendRuleFormatAmountValue(amountDiff.added.at(0) ?? {value: []}, currency); + const body = translate('workspaceActions.expensifyCardRule.update.bodyMaxAmountChange', {oldValue, newValue}); + phrases.push({verb: 'changed', adjective: '', bodyWithAdjective: body, bodyWithoutAdjective: body}); + } else { + for (const amount of amountDiff.added) { + const body = translate('workspaceActions.expensifyCardRule.update.bodyMaxAmountSet', {value: spendRuleFormatAmountValue(amount, currency)}); + phrases.push({verb: 'set', adjective: '', bodyWithAdjective: body, bodyWithoutAdjective: body}); + } + if (amountDiff.removed.length > 0) { + const body = translate('workspaceActions.expensifyCardRule.update.bodyMaxAmount'); + const removedPhrase: SpendRulePhrase = {verb: 'removed', adjective: '', bodyWithAdjective: body, bodyWithoutAdjective: body}; + phrases.push(...Array.from({length: amountDiff.removed.length}).fill(removedPhrase)); + } + } + + if (cardDiff.added.length > 0) { + const body = translate('workspaceActions.expensifyCardRule.update.bodyAppliedToAdditionalCards', {count: cardDiff.added.length}); + phrases.push({verb: 'applied', adjective: '', bodyWithAdjective: body, bodyWithoutAdjective: body}); + } + if (cardDiff.removed.length > 0) { + const body = translate('workspaceActions.expensifyCardRule.update.bodyRemovedFromCards', {cards: getSpendRuleCardsSummary(translate, cardDiff.removed)}); + phrases.push({verb: 'removed', adjective: '', bodyWithAdjective: body, bodyWithoutAdjective: body}); + } + + if (phrases.length === 0) { + return getAddExpensifyCardRuleMessage(translate, reportAction); + } + + const joined = joinSpendRulePhrases(translate, phrases); + + if (cardsChanged) { + return joined; + } + + const onlyRemovedPhrase = phrases.length === 1 && phrases.at(0)?.verb === 'removed'; + if (onlyRemovedPhrase) { + return translate('workspaceActions.expensifyCardRule.update.composeFromCards', {content: joined, cards: newCardsSummary}); + } + + return translate('workspaceActions.expensifyCardRule.update.composeOnCards', {content: joined, cards: newCardsSummary}); +} + +function getRemoveExpensifyCardRuleMessage(translate: LocalizedTranslate, reportAction: OnyxEntry): string { + if (!isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.REMOVE_EXPENSIFY_CARD_RULE)) { + return ''; + } + const message = getOriginalMessage(reportAction) ?? {}; + const cards = message.cards ?? []; + const cardsSummary = getSpendRuleCardsSummary(translate, cards); + return translate('workspaceActions.expensifyCardRule.removeRule', {cards: cardsSummary}); +} + function getRemovedFromApprovalChainMessage(translate: LocalizedTranslate, reportAction: OnyxEntry>) { const originalMessage = getOriginalMessage(reportAction); const submittersNames = getPersonalDetailsByIDs({ @@ -4578,6 +4979,8 @@ export { getOneTransactionThreadReportAction, getOneTransactionThreadReportID, getOriginalMessage, + getAddExpensifyCardRuleMessage, + getUpdateExpensifyCardRuleMessage, getAddedApprovalRuleMessage, getDeletedApprovalRuleMessage, getUpdatedApprovalRuleMessage, @@ -4589,6 +4992,7 @@ export { getReportActionMessage, getReportActionMessageText, getReportActionText, + getRemoveExpensifyCardRuleMessage, getSortedReportActions, getSortedReportActionsForDisplay, isCardBrokenConnectionAction, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index b4068d56f90c..1b3aca2c13d0 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -42,6 +42,7 @@ import { getAddedBudgetMessage, getAddedCardFeedMessage, getAddedConnectionMessage, + getAddExpensifyCardRuleMessage, getAssignedCompanyCardMessage, getAutoPayApprovedReportsEnabledMessage, getAutoReimbursementMessage, @@ -78,6 +79,7 @@ import { getReimburserUpdateMessage, getRemovedCardFeedMessage, getRemovedConnectionMessage, + getRemoveExpensifyCardRuleMessage, getRenamedAction, getRenamedCardFeedMessage, getReportAction, @@ -109,6 +111,7 @@ import { getUpdatedSharedBudgetNotificationMessage, getUpdatedTimeEnabledMessage, getUpdatedTimeRateMessage, + getUpdateExpensifyCardRuleMessage, getUpdateRoomDescriptionMessage, getWorkspaceAttendeeTrackingUpdateMessage, getWorkspaceCategoriesUpdatedMessage, @@ -1161,6 +1164,12 @@ function getOptionData({ result.alternateText = getDeletedApprovalRuleMessage(translate, lastAction); } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_APPROVER_RULE) { result.alternateText = getUpdatedApprovalRuleMessage(translate, lastAction); + } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_EXPENSIFY_CARD_RULE) { + result.alternateText = getAddExpensifyCardRuleMessage(translate, lastAction); + } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_EXPENSIFY_CARD_RULE) { + result.alternateText = getUpdateExpensifyCardRuleMessage(translate, lastAction); + } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.REMOVE_EXPENSIFY_CARD_RULE) { + result.alternateText = getRemoveExpensifyCardRuleMessage(translate, lastAction); } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_MANUAL_APPROVAL_THRESHOLD) { result.alternateText = getUpdatedManualApprovalThresholdMessage(translate, lastAction); } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_BUDGET) { diff --git a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx index 628b818a9d8d..1668ba2ffcab 100644 --- a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx @@ -33,6 +33,7 @@ import { getAddedBudgetMessage, getAddedCardFeedMessage, getAddedConnectionMessage, + getAddExpensifyCardRuleMessage, getAssignedCompanyCardMessage, getAutoPayApprovedReportsEnabledMessage, getAutoReimbursementMessage, @@ -77,6 +78,7 @@ import { getReimburserUpdateMessage, getRemovedCardFeedMessage, getRemovedConnectionMessage, + getRemoveExpensifyCardRuleMessage, getRenamedAction, getRenamedCardFeedMessage, getReportAction, @@ -108,6 +110,7 @@ import { getUpdatedSharedBudgetNotificationMessage, getUpdatedTimeEnabledMessage, getUpdatedTimeRateMessage, + getUpdateExpensifyCardRuleMessage, getUpdateRoomDescriptionMessage, getWorkspaceAttendeeTrackingUpdateMessage, getWorkspaceCategoriesUpdatedMessage, @@ -1121,6 +1124,12 @@ const ContextMenuActions: ContextMenuAction[] = [ setClipboardMessage(getDeletedApprovalRuleMessage(translate, reportAction)); } else if (isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_APPROVER_RULE)) { setClipboardMessage(getUpdatedApprovalRuleMessage(translate, reportAction)); + } else if (isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_EXPENSIFY_CARD_RULE)) { + setClipboardMessage(getAddExpensifyCardRuleMessage(translate, reportAction)); + } else if (isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_EXPENSIFY_CARD_RULE)) { + setClipboardMessage(getUpdateExpensifyCardRuleMessage(translate, reportAction)); + } else if (isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.REMOVE_EXPENSIFY_CARD_RULE)) { + setClipboardMessage(getRemoveExpensifyCardRuleMessage(translate, reportAction)); } else if (isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_MANUAL_APPROVAL_THRESHOLD)) { setClipboardMessage(getUpdatedManualApprovalThresholdMessage(translate, reportAction)); } else if (isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_BUDGET)) { diff --git a/src/pages/inbox/report/actionContents/PolicyChangeLogContent.tsx b/src/pages/inbox/report/actionContents/PolicyChangeLogContent.tsx index 63ac39058150..5d20c3f7ed78 100644 --- a/src/pages/inbox/report/actionContents/PolicyChangeLogContent.tsx +++ b/src/pages/inbox/report/actionContents/PolicyChangeLogContent.tsx @@ -10,6 +10,7 @@ import { getAddedBudgetMessage, getAddedCardFeedMessage, getAddedConnectionMessage, + getAddExpensifyCardRuleMessage, getAssignedCompanyCardMessage, getAutoPayApprovedReportsEnabledMessage, getAutoReimbursementMessage, @@ -36,6 +37,7 @@ import { getReimburserUpdateMessage, getRemovedCardFeedMessage, getRemovedConnectionMessage, + getRemoveExpensifyCardRuleMessage, getRenamedCardFeedMessage, getRequireCompanyCardsEnabledMessage, getSetAutoJoinMessage, @@ -60,6 +62,7 @@ import { getUpdatedSharedBudgetNotificationMessage, getUpdatedTimeEnabledMessage, getUpdatedTimeRateMessage, + getUpdateExpensifyCardRuleMessage, getWorkspaceAttendeeTrackingUpdateMessage, getWorkspaceCategoriesUpdatedMessage, getWorkspaceCategoryUpdateMessage, @@ -163,6 +166,9 @@ const POLICY_CHANGE_LOG_RESOLVERS: Record = { [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_APPROVER_RULE]: (translate, action) => getAddedApprovalRuleMessage(translate, action), [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.DELETE_APPROVER_RULE]: (translate, action) => getDeletedApprovalRuleMessage(translate, action), [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_APPROVER_RULE]: (translate, action) => getUpdatedApprovalRuleMessage(translate, action), + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_EXPENSIFY_CARD_RULE]: (translate, action) => getAddExpensifyCardRuleMessage(translate, action), + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_EXPENSIFY_CARD_RULE]: (translate, action) => getUpdateExpensifyCardRuleMessage(translate, action), + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.REMOVE_EXPENSIFY_CARD_RULE]: (translate, action) => getRemoveExpensifyCardRuleMessage(translate, action), [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_INTEGRATION]: (translate, action) => getAddedConnectionMessage(translate, action), [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.DELETE_INTEGRATION]: (translate, action) => getRemovedConnectionMessage(translate, action), [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_CARD_FEED]: (translate, action) => getAddedCardFeedMessage(translate, action), diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index a399136ba2b9..9eccb2e23a9d 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -764,6 +764,60 @@ type OriginalMessagePolicyChangeLog = { /** Whether the user joined the workspace via joining link */ didJoinPolicy?: boolean; + + /** Spend rule action (`block` or `allow`) sent by the new structured changelog payload */ + action?: string; + + /** Previous spend rule action when the rule's restriction type changed in an update */ + oldAction?: string; + + /** Merchants included in a spend rule */ + merchants?: string[]; + + /** Previous list of merchants when a spend rule was updated */ + oldMerchants?: string[]; + + /** Categories (slugs) included in a spend rule */ + categories?: string[]; + + /** Previous list of categories when a spend rule was updated */ + oldCategories?: string[]; + + /** Max-amount filters in a spend rule */ + amounts?: Array<{ + /** Operator (`gte` for "over", `lte` for "under") */ + operator: string; + + /** Amount value as cents serialized to a string array (`['100000']`) */ + value: string[]; + }>; + + /** Previous list of max-amount filters when a spend rule was updated */ + oldAmounts?: Array<{ + /** Operator (`gte` for "over", `lte` for "under") */ + operator: string; + + /** Amount value as cents serialized to a string array (`['100000']`) */ + value: string[]; + }>; + + /** Cards a spend rule is scoped to */ + cards?: Array<{ + /** Card identifier */ + cardID: number | string; + + /** Display name shown when the rule covers a single card */ + displayName?: string; + }>; + + /** Previous list of cards when a spend rule's card scope was updated */ + oldCards?: Array<{ + /** Card identifier */ + cardID: number | string; + + /** Display name shown when the rule covers a single card */ + displayName?: string; + }>; }; /** Model of `join policy` report action */