diff --git a/config/eslint/eslint.seatbelt.tsv b/config/eslint/eslint.seatbelt.tsv index a6b9244c96f..39af0f899c8 100644 --- a/config/eslint/eslint.seatbelt.tsv +++ b/config/eslint/eslint.seatbelt.tsv @@ -733,7 +733,7 @@ "../../src/libs/ReportTitleUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/ReportUtils.ts" "@typescript-eslint/no-deprecated/getPolicy" 28 "../../src/libs/ReportUtils.ts" "@typescript-eslint/no-deprecated/translateLocal" 30 -"../../src/libs/ReportUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 33 +"../../src/libs/ReportUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 34 "../../src/libs/ReportUtils.ts" "rulesdir/no-onyx-connect" 16 "../../src/libs/SearchAutocompleteUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/libs/SearchQueryUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 72 @@ -1790,7 +1790,7 @@ "../../tests/unit/ReportActionItemSingleTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/unit/ReportActionsFollowupUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 5 "../../tests/unit/ReportActionsListPaddingViewTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 -"../../tests/unit/ReportActionsUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 35 +"../../tests/unit/ReportActionsUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 36 "../../tests/unit/ReportLayoutUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/unit/ReportNameUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 23 "../../tests/unit/ReportPrimaryActionUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 35 @@ -1801,7 +1801,7 @@ "../../tests/unit/ReportTitleUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../tests/unit/ReportTitleUtilsTest.ts" "no-restricted-imports" 1 "../../tests/unit/ReportUtilsGetIconsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/unit/ReportUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 18 +"../../tests/unit/ReportUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 19 "../../tests/unit/ReportUtilsTest.ts" "no-restricted-imports" 1 "../../tests/unit/ReportWelcomeTextTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/unit/RequestConflictUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 11 diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 52b1994f6fe..4aab3d6a4d9 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1728,6 +1728,23 @@ const CONST = { CORPORATE_UPGRADE: 'POLICYCHANGELOG_CORPORATE_UPGRADE', CORPORATE_FORCE_UPGRADE: 'POLICYCHANGELOG_CORPORATE_FORCE_UPGRADE', TEAM_DOWNGRADE: 'POLICYCHANGELOG_TEAM_DOWNGRADE', + COPY_OVERVIEW: 'POLICYCHANGELOG_COPY_OVERVIEW', + COPY_EMPLOYEES: 'POLICYCHANGELOG_COPY_EMPLOYEES', + COPY_REPORT_FIELDS: 'POLICYCHANGELOG_COPY_REPORT_FIELDS', + COPY_ACCOUNTING: 'POLICYCHANGELOG_COPY_ACCOUNTING', + COPY_RECEIPT_PARTNERS: 'POLICYCHANGELOG_COPY_RECEIPT_PARTNERS', + COPY_HR: 'POLICYCHANGELOG_COPY_HR', + COPY_CATEGORIES: 'POLICYCHANGELOG_COPY_CATEGORIES', + COPY_TAGS: 'POLICYCHANGELOG_COPY_TAGS', + COPY_TAXES: 'POLICYCHANGELOG_COPY_TAXES', + COPY_TIME_TRACKING: 'POLICYCHANGELOG_COPY_TIME_TRACKING', + COPY_WORKFLOWS: 'POLICYCHANGELOG_COPY_WORKFLOWS', + COPY_RULES: 'POLICYCHANGELOG_COPY_RULES', + COPY_CODING_RULES: 'POLICYCHANGELOG_COPY_CODING_RULES', + COPY_DISTANCE: 'POLICYCHANGELOG_COPY_DISTANCE', + COPY_PER_DIEM: 'POLICYCHANGELOG_COPY_PER_DIEM', + COPY_INVOICES: 'POLICYCHANGELOG_COPY_INVOICES', + COPY_TRAVEL: 'POLICYCHANGELOG_COPY_TRAVEL', }, RECEIPT_SCAN_FAILED: 'RECEIPTSCANFAILED', RESOLVED_DUPLICATES: 'RESOLVEDDUPLICATES', diff --git a/src/languages/de.ts b/src/languages/de.ts index 24f94eeeae1..2b8ad4b1391 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -8318,6 +8318,46 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc customUnitRateDateRangeFrom: (date: string) => `ab dem ${date}`, customUnitRateDateRangeUntilEnd: (date: string) => `bis ${date}`, customUnitRateDateRangeAllDates: () => `für alle Daten`, + policyCopy: { + overview: (sourcePolicyName: string, sourcePolicyURL: string) => `Übersicht von ${sourcePolicyName} kopiert`, + employees: (sourcePolicyName: string, sourcePolicyURL: string) => `Mitglieder von ${sourcePolicyName} kopiert`, + reportFields: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 Berichtsfeld von ${sourcePolicyName} kopiert`, + other: (count: number) => `${count} Berichtsfelder von ${sourcePolicyName} kopiert`, + }), + accounting: (sourcePolicyName: string, sourcePolicyURL: string) => `Buchhaltungseinstellungen von ${sourcePolicyName} kopiert`, + receiptPartners: (sourcePolicyName: string, sourcePolicyURL: string) => `Einstellungen für Belegpartner von ${sourcePolicyName} kopiert`, + hr: (sourcePolicyName: string, sourcePolicyURL: string) => `HR-Einstellungen von ${sourcePolicyName} kopiert`, + categories: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 Kategorie von ${sourcePolicyName} kopiert`, + other: (count: number) => `${count} Kategorien von ${sourcePolicyName} kopiert`, + }), + tags: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 Tag von ${sourcePolicyName} kopiert`, + other: (count: number) => `${count} Tags von ${sourcePolicyName} kopiert`, + }), + taxes: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 Steuersatz aus ${sourcePolicyName} kopiert`, + other: (count: number) => `${count} Steuersätze von ${sourcePolicyName} kopiert`, + }), + timeTracking: (sourcePolicyName: string, sourcePolicyURL: string) => `Zeiterfassungseinstellungen von ${sourcePolicyName} kopiert`, + workflows: (sourcePolicyName: string, sourcePolicyURL: string) => `Workflows von ${sourcePolicyName} kopiert`, + rules: (sourcePolicyName: string, sourcePolicyURL: string) => `Kopierte Regeln von ${sourcePolicyName}`, + codingRules: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 Händlerregel von ${sourcePolicyName} kopiert`, + other: (count: number) => `${count} Händlerregeln von ${sourcePolicyName} kopiert`, + }), + distanceRates: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 Entfernungssatz aus ${sourcePolicyName} kopiert`, + other: (count: number) => `${count} Entfernungssätze aus ${sourcePolicyName} kopiert`, + }), + perDiem: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 Pauschale von ${sourcePolicyName} kopiert`, + other: (count: number) => `${count} Pauschalsätze pro Tag von ${sourcePolicyName} kopiert`, + }), + invoices: (sourcePolicyName: string, sourcePolicyURL: string) => `Rechnungseinstellungen von ${sourcePolicyName} kopiert`, + travel: (sourcePolicyName: string, sourcePolicyURL: string) => `Reiseeinstellungen von ${sourcePolicyName} kopiert`, + }, }, roomMembersPage: { memberNotFound: 'Mitglied nicht gefunden.', diff --git a/src/languages/en.ts b/src/languages/en.ts index e1b8fb07ffa..2c9cc42055c 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -8385,6 +8385,46 @@ const translations = { setMaxExpenseAge: (newValue: string) => `set max expense age to "${newValue}" days`, changedMaxExpenseAge: (oldValue: string, newValue: string) => `changed max expense age to "${newValue}" days (previously "${oldValue}")`, removedMaxExpenseAge: (oldValue: string) => `removed max expense age (previously "${oldValue}" days)`, + policyCopy: { + overview: (sourcePolicyName: string, sourcePolicyURL: string) => `copied overview from ${sourcePolicyName}`, + employees: (sourcePolicyName: string, sourcePolicyURL: string) => `copied members from ${sourcePolicyName}`, + reportFields: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copied 1 report field from ${sourcePolicyName}`, + other: (count: number) => `copied ${count} report fields from ${sourcePolicyName}`, + }), + accounting: (sourcePolicyName: string, sourcePolicyURL: string) => `copied accounting settings from ${sourcePolicyName}`, + receiptPartners: (sourcePolicyName: string, sourcePolicyURL: string) => `copied receipt partner settings from ${sourcePolicyName}`, + hr: (sourcePolicyName: string, sourcePolicyURL: string) => `copied HR settings from ${sourcePolicyName}`, + categories: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copied 1 category from ${sourcePolicyName}`, + other: (count: number) => `copied ${count} categories from ${sourcePolicyName}`, + }), + tags: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copied 1 tag from ${sourcePolicyName}`, + other: (count: number) => `copied ${count} tags from ${sourcePolicyName}`, + }), + taxes: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copied 1 tax rate from ${sourcePolicyName}`, + other: (count: number) => `copied ${count} tax rates from ${sourcePolicyName}`, + }), + timeTracking: (sourcePolicyName: string, sourcePolicyURL: string) => `copied time tracking settings from ${sourcePolicyName}`, + workflows: (sourcePolicyName: string, sourcePolicyURL: string) => `copied workflows from ${sourcePolicyName}`, + rules: (sourcePolicyName: string, sourcePolicyURL: string) => `copied rules from ${sourcePolicyName}`, + codingRules: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copied 1 merchant rule from ${sourcePolicyName}`, + other: (count: number) => `copied ${count} merchant rules from ${sourcePolicyName}`, + }), + distanceRates: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copied 1 distance rate from ${sourcePolicyName}`, + other: (count: number) => `copied ${count} distance rates from ${sourcePolicyName}`, + }), + perDiem: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copied 1 per diem rate from ${sourcePolicyName}`, + other: (count: number) => `copied ${count} per diem rates from ${sourcePolicyName}`, + }), + invoices: (sourcePolicyName: string, sourcePolicyURL: string) => `copied invoice settings from ${sourcePolicyName}`, + travel: (sourcePolicyName: string, sourcePolicyURL: string) => `copied travel settings from ${sourcePolicyName}`, + }, }, roomMembersPage: { memberNotFound: 'Member not found.', diff --git a/src/languages/es.ts b/src/languages/es.ts index f8e09a6398d..4a8e9c44894 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -8108,6 +8108,46 @@ ${amount} para ${merchant} - ${date}`, customUnitRateDateRangeFrom: (date: string) => `desde ${date}`, customUnitRateDateRangeUntilEnd: (date: string) => `hasta ${date}`, customUnitRateDateRangeAllDates: () => `para todas las fechas`, + policyCopy: { + overview: (sourcePolicyName: string, sourcePolicyURL: string) => `copió la descripción general de ${sourcePolicyName}`, + employees: (sourcePolicyName: string, sourcePolicyURL: string) => `copió miembros de ${sourcePolicyName}`, + reportFields: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `se copió 1 campo de informe desde ${sourcePolicyName}`, + other: (count: number) => `copió ${count} campos de informe de ${sourcePolicyName}`, + }), + accounting: (sourcePolicyName: string, sourcePolicyURL: string) => `copió la configuración de contabilidad de ${sourcePolicyName}`, + receiptPartners: (sourcePolicyName: string, sourcePolicyURL: string) => `copió la configuración de socios de recibos de ${sourcePolicyName}`, + hr: (sourcePolicyName: string, sourcePolicyURL: string) => `copió la configuración de RR. HH. de ${sourcePolicyName}`, + categories: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copió 1 categoría de ${sourcePolicyName}`, + other: (count: number) => `copió ${count} categorías de ${sourcePolicyName}`, + }), + tags: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `se copió 1 etiqueta de ${sourcePolicyName}`, + other: (count: number) => `copió ${count} etiquetas de ${sourcePolicyName}`, + }), + taxes: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `se copió 1 tasa de impuestos de ${sourcePolicyName}`, + other: (count: number) => `copió ${count} tasas de impuestos de ${sourcePolicyName}`, + }), + timeTracking: (sourcePolicyName: string, sourcePolicyURL: string) => `copió la configuración de registro de tiempo de ${sourcePolicyName}`, + workflows: (sourcePolicyName: string, sourcePolicyURL: string) => `copió los flujos de trabajo de ${sourcePolicyName}`, + rules: (sourcePolicyName: string, sourcePolicyURL: string) => `reglas copiadas de ${sourcePolicyName}`, + codingRules: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copió 1 regla de comercio de ${sourcePolicyName}`, + other: (count: number) => `copió ${count} reglas de comercio de ${sourcePolicyName}`, + }), + distanceRates: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `se copió 1 tarifa de distancia de ${sourcePolicyName}`, + other: (count: number) => `copió ${count} tarifas por distancia de ${sourcePolicyName}`, + }), + perDiem: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copió 1 Per diem Tasa de ${sourcePolicyName}`, + other: (count: number) => `copió ${count} tasas de per diem de ${sourcePolicyName}`, + }), + invoices: (sourcePolicyName: string, sourcePolicyURL: string) => `copió la configuración de facturas de ${sourcePolicyName}`, + travel: (sourcePolicyName: string, sourcePolicyURL: string) => `copió la configuración de viaje de ${sourcePolicyName}`, + }, }, roomMembersPage: { memberNotFound: 'Miembro no encontrado.', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 88fa4ebcae0..c25d04124e0 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -8353,6 +8353,46 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e customUnitRateDateRangeFrom: (date: string) => `à partir du ${date}`, customUnitRateDateRangeUntilEnd: (date: string) => `jusqu’au ${date}`, customUnitRateDateRangeAllDates: () => `pour toutes les dates`, + policyCopy: { + overview: (sourcePolicyName: string, sourcePolicyURL: string) => `aperçu copié depuis ${sourcePolicyName}`, + employees: (sourcePolicyName: string, sourcePolicyURL: string) => `a copié les membres depuis ${sourcePolicyName}`, + reportFields: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 champ de note de frais copié depuis ${sourcePolicyName}`, + other: (count: number) => `${count} champs de note de frais copiés depuis ${sourcePolicyName}`, + }), + accounting: (sourcePolicyName: string, sourcePolicyURL: string) => `paramètres comptables copiés depuis ${sourcePolicyName}`, + receiptPartners: (sourcePolicyName: string, sourcePolicyURL: string) => `a copié les paramètres de reçu partenaire depuis ${sourcePolicyName}`, + hr: (sourcePolicyName: string, sourcePolicyURL: string) => `paramètres RH copiés depuis ${sourcePolicyName}`, + categories: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 catégorie copiée depuis ${sourcePolicyName}`, + other: (count: number) => `${count} catégories copiées depuis ${sourcePolicyName}`, + }), + tags: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 tag copié depuis ${sourcePolicyName}`, + other: (count: number) => `${count} tags copiés depuis ${sourcePolicyName}`, + }), + taxes: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 taux de taxe copié depuis ${sourcePolicyName}`, + other: (count: number) => `a copié ${count} taux de taxe depuis ${sourcePolicyName}`, + }), + timeTracking: (sourcePolicyName: string, sourcePolicyURL: string) => `paramètres de suivi du temps copiés depuis ${sourcePolicyName}`, + workflows: (sourcePolicyName: string, sourcePolicyURL: string) => `workflows copiés depuis ${sourcePolicyName}`, + rules: (sourcePolicyName: string, sourcePolicyURL: string) => `règles copiées depuis ${sourcePolicyName}`, + codingRules: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 règle de commerçant copiée depuis ${sourcePolicyName}`, + other: (count: number) => `a copié ${count} règles de commerçant depuis ${sourcePolicyName}`, + }), + distanceRates: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 taux de distance copié depuis ${sourcePolicyName}`, + other: (count: number) => `${count} taux de distance copiés depuis ${sourcePolicyName}`, + }), + perDiem: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `a copié 1 taux de per diem depuis ${sourcePolicyName}`, + other: (count: number) => `a copié ${count} taux journaliers de ${sourcePolicyName}`, + }), + invoices: (sourcePolicyName: string, sourcePolicyURL: string) => `paramètres de facturation copiés depuis ${sourcePolicyName}`, + travel: (sourcePolicyName: string, sourcePolicyURL: string) => `paramètres de déplacement copiés depuis ${sourcePolicyName}`, + }, }, roomMembersPage: { memberNotFound: 'Membre introuvable.', diff --git a/src/languages/it.ts b/src/languages/it.ts index 22f1c24f722..78bdd7ffec4 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -8308,6 +8308,48 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, customUnitRateDateRangeFrom: (date: string) => `dal ${date}`, customUnitRateDateRangeUntilEnd: (date: string) => `fino al ${date}`, customUnitRateDateRangeAllDates: () => `per tutte le date`, + policyCopy: { + overview: (sourcePolicyName: string, sourcePolicyURL: string) => `panoramica copiata da ${sourcePolicyName}`, + employees: (sourcePolicyName: string, sourcePolicyURL: string) => `ha copiato i membri da ${sourcePolicyName}`, + reportFields: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiato 1 campo del report da ${sourcePolicyName}`, + other: (count: number) => `copiati ${count} campi del report da ${sourcePolicyName}`, + }), + accounting: (sourcePolicyName: string, sourcePolicyURL: string) => `impostazioni contabili copiate da ${sourcePolicyName}`, + receiptPartners: (sourcePolicyName: string, sourcePolicyURL: string) => + `ha copiato le impostazioni del partner di ricevute da ${sourcePolicyName}`, + hr: (sourcePolicyName: string, sourcePolicyURL: string) => `impostazioni HR copiate da ${sourcePolicyName}`, + categories: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiata 1 categoria da ${sourcePolicyName}`, + other: (count: number) => `copiate ${count} categorie da ${sourcePolicyName}`, + }), + tags: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiato 1 tag da ${sourcePolicyName}`, + other: (count: number) => `copiati ${count} tag da ${sourcePolicyName}`, + }), + taxes: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiata 1 aliquota fiscale da ${sourcePolicyName}`, + other: (count: number) => `copiate ${count} aliquote fiscali da ${sourcePolicyName}`, + }), + timeTracking: (sourcePolicyName: string, sourcePolicyURL: string) => + `ha copiato le impostazioni di rilevazione del tempo da ${sourcePolicyName}`, + workflows: (sourcePolicyName: string, sourcePolicyURL: string) => `ha copiato i flussi di lavoro da ${sourcePolicyName}`, + rules: (sourcePolicyName: string, sourcePolicyURL: string) => `regole copiate da ${sourcePolicyName}`, + codingRules: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiata 1 regola esercente da ${sourcePolicyName}`, + other: (count: number) => `copiate ${count} regole esercente da ${sourcePolicyName}`, + }), + distanceRates: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiata 1 tariffa chilometrica da ${sourcePolicyName}`, + other: (count: number) => `copiate ${count} tariffe chilometriche da ${sourcePolicyName}`, + }), + perDiem: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiata 1 indennità giornaliera da ${sourcePolicyName}`, + other: (count: number) => `copiati ${count} tassi di diaria da ${sourcePolicyName}`, + }), + invoices: (sourcePolicyName: string, sourcePolicyURL: string) => `impostazioni fattura copiate da ${sourcePolicyName}`, + travel: (sourcePolicyName: string, sourcePolicyURL: string) => `impostazioni di viaggio copiate da ${sourcePolicyName}`, + }, }, roomMembersPage: { memberNotFound: 'Membro non trovato.', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index d9d7f87120e..cb2af1a3f83 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -8207,6 +8207,46 @@ ${reportName}`, customUnitRateDateRangeFrom: (date: string) => `${date} から`, customUnitRateDateRangeUntilEnd: (date: string) => `${date}まで`, customUnitRateDateRangeAllDates: () => `すべての日付に対して`, + policyCopy: { + overview: (sourcePolicyName: string, sourcePolicyURL: string) => `${sourcePolicyName} から概要をコピーしました`, + employees: (sourcePolicyName: string, sourcePolicyURL: string) => `${sourcePolicyName} からメンバーをコピーしました`, + reportFields: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `${sourcePolicyName} からレポート項目を 1 件コピーしました`, + other: (count: number) => `${sourcePolicyName} からレポート項目を ${count} 件コピーしました`, + }), + accounting: (sourcePolicyName: string, sourcePolicyURL: string) => `${sourcePolicyName} から会計設定をコピーしました`, + receiptPartners: (sourcePolicyName: string, sourcePolicyURL: string) => `${sourcePolicyName} から領収書パートナー設定をコピーしました`, + hr: (sourcePolicyName: string, sourcePolicyURL: string) => `${sourcePolicyName} から人事設定をコピーしました`, + categories: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `${sourcePolicyName} からカテゴリを 1 件コピーしました`, + other: (count: number) => `${sourcePolicyName} から ${count} 件のカテゴリをコピーしました`, + }), + tags: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `${sourcePolicyName} からタグを 1 件コピーしました`, + other: (count: number) => `${sourcePolicyName} から ${count} 個のタグをコピーしました`, + }), + taxes: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `${sourcePolicyName} から税率を 1 件コピーしました`, + other: (count: number) => `${sourcePolicyName} から税率を ${count} 件コピーしました`, + }), + timeTracking: (sourcePolicyName: string, sourcePolicyURL: string) => `${sourcePolicyName} からタイムトラッキング設定をコピーしました`, + workflows: (sourcePolicyName: string, sourcePolicyURL: string) => `${sourcePolicyName} からワークフローをコピーしました`, + rules: (sourcePolicyName: string, sourcePolicyURL: string) => `${sourcePolicyName} からルールをコピーしました`, + codingRules: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `${sourcePolicyName} から取引先ルールを 1 件コピーしました`, + other: (count: number) => `${sourcePolicyName} から ${count} 件の取引先ルールをコピーしました`, + }), + distanceRates: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `${sourcePolicyName} から距離レートを1件コピーしました`, + other: (count: number) => `${sourcePolicyName} から距離レートを ${count} 件コピーしました`, + }), + perDiem: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `${sourcePolicyName} から日当レートを1件コピーしました`, + other: (count: number) => `${sourcePolicyName} から日当レートを ${count} 件コピーしました`, + }), + invoices: (sourcePolicyName: string, sourcePolicyURL: string) => `${sourcePolicyName} から請求書の設定をコピーしました`, + travel: (sourcePolicyName: string, sourcePolicyURL: string) => `${sourcePolicyName} から出張設定をコピーしました`, + }, }, roomMembersPage: { memberNotFound: 'メンバーが見つかりません。', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 33a50fcd003..0209bb8dee7 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -8279,6 +8279,46 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, customUnitRateDateRangeFrom: (date: string) => `vanaf ${date}`, customUnitRateDateRangeUntilEnd: (date: string) => `tot ${date}`, customUnitRateDateRangeAllDates: () => `voor alle data`, + policyCopy: { + overview: (sourcePolicyName: string, sourcePolicyURL: string) => `overzicht gekopieerd van ${sourcePolicyName}`, + employees: (sourcePolicyName: string, sourcePolicyURL: string) => `leden gekopieerd van ${sourcePolicyName}`, + reportFields: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 rapportveld gekopieerd van ${sourcePolicyName}`, + other: (count: number) => `${count} rapportvelden gekopieerd van ${sourcePolicyName}`, + }), + accounting: (sourcePolicyName: string, sourcePolicyURL: string) => `boekhoudinstellingen gekopieerd van ${sourcePolicyName}`, + receiptPartners: (sourcePolicyName: string, sourcePolicyURL: string) => `instelling bonpartner gekopieerd van ${sourcePolicyName}`, + hr: (sourcePolicyName: string, sourcePolicyURL: string) => `HR-instellingen gekopieerd van ${sourcePolicyName}`, + categories: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 categorie gekopieerd van ${sourcePolicyName}`, + other: (count: number) => `${count} categorieën gekopieerd van ${sourcePolicyName}`, + }), + tags: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 label gekopieerd van ${sourcePolicyName}`, + other: (count: number) => `${count} tags gekopieerd van ${sourcePolicyName}`, + }), + taxes: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 belastingtarief gekopieerd van ${sourcePolicyName}`, + other: (count: number) => `heeft ${count} btw-tarieven gekopieerd van ${sourcePolicyName}`, + }), + timeTracking: (sourcePolicyName: string, sourcePolicyURL: string) => `tijdregistratie-instellingen gekopieerd van ${sourcePolicyName}`, + workflows: (sourcePolicyName: string, sourcePolicyURL: string) => `gekopieerde workflows van ${sourcePolicyName}`, + rules: (sourcePolicyName: string, sourcePolicyURL: string) => `regels gekopieerd van ${sourcePolicyName}`, + codingRules: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 leveranciersregel gekopieerd van ${sourcePolicyName}`, + other: (count: number) => `heeft ${count} leveranciersregels gekopieerd van ${sourcePolicyName}`, + }), + distanceRates: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 kilometervergoeding gekopieerd van ${sourcePolicyName}`, + other: (count: number) => `heeft ${count} kilometertarieven gekopieerd van ${sourcePolicyName}`, + }), + perDiem: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `1 dagvergoeding gekopieerd uit ${sourcePolicyName}`, + other: (count: number) => `${count} dagvergoedingen gekopieerd van ${sourcePolicyName}`, + }), + invoices: (sourcePolicyName: string, sourcePolicyURL: string) => `factuurinstellingen gekopieerd van ${sourcePolicyName}`, + travel: (sourcePolicyName: string, sourcePolicyURL: string) => `reiskosteninstellingen gekopieerd van ${sourcePolicyName}`, + }, }, roomMembersPage: { memberNotFound: 'Lid niet gevonden.', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 5f6f2b1f6a8..48b5bb44d38 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -8268,6 +8268,46 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, customUnitRateDateRangeFrom: (date: string) => `od ${date}`, customUnitRateDateRangeUntilEnd: (date: string) => `do ${date}`, customUnitRateDateRangeAllDates: () => `dla wszystkich dat`, + policyCopy: { + overview: (sourcePolicyName: string, sourcePolicyURL: string) => `skopiowano podsumowanie z ${sourcePolicyName}`, + employees: (sourcePolicyName: string, sourcePolicyURL: string) => `skopiowano członków z ${sourcePolicyName}`, + reportFields: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `skopiowano 1 pole raportu z ${sourcePolicyName}`, + other: (count: number) => `skopiowano ${count} pola raportu z ${sourcePolicyName}`, + }), + accounting: (sourcePolicyName: string, sourcePolicyURL: string) => `skopiowano ustawienia księgowe z ${sourcePolicyName}`, + receiptPartners: (sourcePolicyName: string, sourcePolicyURL: string) => `skopiowano ustawienia partnera paragonów z ${sourcePolicyName}`, + hr: (sourcePolicyName: string, sourcePolicyURL: string) => `skopiowano ustawienia HR z ${sourcePolicyName}`, + categories: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `skopiowano 1 kategorię z ${sourcePolicyName}`, + other: (count: number) => `skopiowano ${count} kategorie z ${sourcePolicyName}`, + }), + tags: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `skopiowano 1 znacznik z ${sourcePolicyName}`, + other: (count: number) => `skopiowano ${count} tagów z ${sourcePolicyName}`, + }), + taxes: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `skopiowano 1 stawkę podatku z ${sourcePolicyName}`, + other: (count: number) => `skopiowano ${count} stawki podatkowe z ${sourcePolicyName}`, + }), + timeTracking: (sourcePolicyName: string, sourcePolicyURL: string) => `skopiowano ustawienia śledzenia czasu z ${sourcePolicyName}`, + workflows: (sourcePolicyName: string, sourcePolicyURL: string) => `skopiowano schematy pracy z ${sourcePolicyName}`, + rules: (sourcePolicyName: string, sourcePolicyURL: string) => `skopiowano zasady z ${sourcePolicyName}`, + codingRules: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `skopiowano 1 regułę sprzedawcy z ${sourcePolicyName}`, + other: (count: number) => `skopiowano ${count} reguł sprzedawcy z ${sourcePolicyName}`, + }), + distanceRates: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `skopiowano 1 stawkę za dystans z ${sourcePolicyName}`, + other: (count: number) => `skopiowano ${count} stawki za przejazd z ${sourcePolicyName}`, + }), + perDiem: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `skopiowano 1 stawkę ryczałtową z ${sourcePolicyName}`, + other: (count: number) => `skopiowano ${count} stawki diet z ${sourcePolicyName}`, + }), + invoices: (sourcePolicyName: string, sourcePolicyURL: string) => `skopiowano ustawienia faktur z ${sourcePolicyName}`, + travel: (sourcePolicyName: string, sourcePolicyURL: string) => `skopiowano ustawienia podróży z ${sourcePolicyName}`, + }, }, roomMembersPage: { memberNotFound: 'Nie znaleziono członka.', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index f7c967cebe8..a94ffdffa85 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -8266,6 +8266,46 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, customUnitRateDateRangeFrom: (date: string) => `de ${date}`, customUnitRateDateRangeUntilEnd: (date: string) => `até ${date}`, customUnitRateDateRangeAllDates: () => `para todas as datas`, + policyCopy: { + overview: (sourcePolicyName: string, sourcePolicyURL: string) => `visão geral copiada de ${sourcePolicyName}`, + employees: (sourcePolicyName: string, sourcePolicyURL: string) => `copiou membros de ${sourcePolicyName}`, + reportFields: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiou 1 campo de relatório de ${sourcePolicyName}`, + other: (count: number) => `copiou ${count} campos de relatório de ${sourcePolicyName}`, + }), + accounting: (sourcePolicyName: string, sourcePolicyURL: string) => `configurações de contabilidade copiadas de ${sourcePolicyName}`, + receiptPartners: (sourcePolicyName: string, sourcePolicyURL: string) => `configurações de parceiro de recibos copiadas de ${sourcePolicyName}`, + hr: (sourcePolicyName: string, sourcePolicyURL: string) => `copiou as configurações de RH de ${sourcePolicyName}`, + categories: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiou 1 categoria de ${sourcePolicyName}`, + other: (count: number) => `copiou ${count} categorias de ${sourcePolicyName}`, + }), + tags: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiou 1 etiqueta de ${sourcePolicyName}`, + other: (count: number) => `copiou ${count} tags de ${sourcePolicyName}`, + }), + taxes: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiou 1 alíquota de imposto de ${sourcePolicyName}`, + other: (count: number) => `copiou ${count} taxas de imposto de ${sourcePolicyName}`, + }), + timeTracking: (sourcePolicyName: string, sourcePolicyURL: string) => `copiou as configurações de controle de horas de ${sourcePolicyName}`, + workflows: (sourcePolicyName: string, sourcePolicyURL: string) => `fluxos de trabalho copiados de ${sourcePolicyName}`, + rules: (sourcePolicyName: string, sourcePolicyURL: string) => `regras copiadas de ${sourcePolicyName}`, + codingRules: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiou 1 regra de comerciante de ${sourcePolicyName}`, + other: (count: number) => `copiou ${count} regras de comerciante de ${sourcePolicyName}`, + }), + distanceRates: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiou 1 taxa de distância de ${sourcePolicyName}`, + other: (count: number) => `copiou ${count} taxas de distância de ${sourcePolicyName}`, + }), + perDiem: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `copiou 1 tarifa de diária de ${sourcePolicyName}`, + other: (count: number) => `copiou ${count} taxas de diária de ${sourcePolicyName}`, + }), + invoices: (sourcePolicyName: string, sourcePolicyURL: string) => `configurações de fatura copiadas de ${sourcePolicyName}`, + travel: (sourcePolicyName: string, sourcePolicyURL: string) => `configurações de viagem copiadas de ${sourcePolicyName}`, + }, }, roomMembersPage: { memberNotFound: 'Membro não encontrado.', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 4ad48b461da..95d3482422e 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -8045,6 +8045,46 @@ ${reportName}`, customUnitRateDateRangeFrom: (date: string) => `自 ${date} 起`, customUnitRateDateRangeUntilEnd: (date: string) => `直到 ${date}`, customUnitRateDateRangeAllDates: () => `适用于所有日期`, + policyCopy: { + overview: (sourcePolicyName: string, sourcePolicyURL: string) => `已从 ${sourcePolicyName} 复制概览`, + employees: (sourcePolicyName: string, sourcePolicyURL: string) => `已从 ${sourcePolicyName} 复制成员`, + reportFields: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `已从 ${sourcePolicyName} 复制 1 个报表字段`, + other: (count: number) => `已从 ${sourcePolicyName} 复制了 ${count} 个报表字段`, + }), + accounting: (sourcePolicyName: string, sourcePolicyURL: string) => `已从 ${sourcePolicyName} 复制会计设置`, + receiptPartners: (sourcePolicyName: string, sourcePolicyURL: string) => `已从 ${sourcePolicyName} 复制收据合作方设置`, + hr: (sourcePolicyName: string, sourcePolicyURL: string) => `已从 ${sourcePolicyName} 复制人力资源设置`, + categories: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `已从 ${sourcePolicyName} 复制 1 个类别`, + other: (count: number) => `已从 ${sourcePolicyName} 复制了 ${count} 个类别`, + }), + tags: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `已从 ${sourcePolicyName} 复制 1 个标签`, + other: (count: number) => `已从 ${sourcePolicyName} 复制了 ${count} 个标签`, + }), + taxes: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `已从 ${sourcePolicyName} 复制 1 个税率`, + other: (count: number) => `已从 ${sourcePolicyName} 复制了 ${count} 个税率`, + }), + timeTracking: (sourcePolicyName: string, sourcePolicyURL: string) => `已从 ${sourcePolicyName} 复制时间跟踪设置`, + workflows: (sourcePolicyName: string, sourcePolicyURL: string) => `已从 ${sourcePolicyName} 复制工作流`, + rules: (sourcePolicyName: string, sourcePolicyURL: string) => `已从 ${sourcePolicyName} 复制规则`, + codingRules: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `已从 ${sourcePolicyName} 复制 1 条商家规则`, + other: (count: number) => `已从 ${sourcePolicyName} 复制 ${count} 条商户规则`, + }), + distanceRates: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `已从 ${sourcePolicyName} 复制 1 个里程费率`, + other: (count: number) => `已从 ${sourcePolicyName} 复制了 ${count} 个里程费率`, + }), + perDiem: ({sourcePolicyName, sourcePolicyURL}: {sourcePolicyName: string; sourcePolicyURL: string}) => ({ + one: `已从 ${sourcePolicyName} 复制了 1 个每日补贴标准`, + other: (count: number) => `已从${sourcePolicyName}复制了${count}个每日津贴标准`, + }), + invoices: (sourcePolicyName: string, sourcePolicyURL: string) => `已从 ${sourcePolicyName} 复制发票设置`, + travel: (sourcePolicyName: string, sourcePolicyURL: string) => `已从 ${sourcePolicyName} 复制出差设置`, + }, }, roomMembersPage: { memberNotFound: '未找到成员。', diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 672c3056a29..98fd202b071 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -105,6 +105,7 @@ import { isMovedTransactionAction, isOldDotReportAction, isPendingRemove, + isPolicyCopyReportAction, isReimbursementDeQueuedOrCanceledAction, isReimbursementQueuedAction, isRenamedAction, @@ -130,6 +131,7 @@ import { getMovedActionMessage, getMovedTransactionMessage, getParticipantsAccountIDsForDisplay, + getPolicyChangeLogCopyMessage, getPolicyChangeMessage, getPolicyName, getReimbursementDeQueuedOrCanceledActionMessage, @@ -1016,6 +1018,9 @@ function getLastMessageTextForReport({ if (isActionOfType(lastReportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_FEATURE_ENABLED)) { lastMessageTextFromReport = getWorkspaceFeatureEnabledMessage(translate, lastReportAction); } + if (isPolicyCopyReportAction(lastReportAction)) { + lastMessageTextFromReport = Parser.htmlToText(getPolicyChangeLogCopyMessage(translate, lastReportAction)); + } // we do not want to show report closed in LHN for non archived report so use getReportLastMessage as fallback instead of lastMessageText from report if (reportID && !isReportArchived && report.lastActionType === CONST.REPORT.ACTIONS.TYPE.CLOSED) { diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 749b4dbb721..bc84e108ce8 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -41,7 +41,7 @@ import type { import type {PolicyReportFieldType} from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type ReportAction from '@src/types/onyx/ReportAction'; -import type {Message, OldDotReportAction, OriginalMessage, ReportActions} from '@src/types/onyx/ReportAction'; +import type {Message, OldDotReportAction, OriginalMessage, PolicyChangeLogCopyReportActionNames, ReportActions} from '@src/types/onyx/ReportAction'; import type ReportActionName from '@src/types/onyx/ReportActionName'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import {getBankName, isCardPendingActivate} from './CardUtils'; @@ -207,7 +207,8 @@ function isDeletedAction(reportAction: OnyxInputOrEntry): boolean { return !!originalMessage && typeof originalMessage === 'object' && 'reasoning' in originalMessage && !!originalMessage.reasoning; } +function isPolicyCopyReportAction(action: OnyxInputOrEntry): action is ReportAction { + return [ + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_OVERVIEW, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_EMPLOYEES, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_REPORT_FIELDS, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_ACCOUNTING, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_RECEIPT_PARTNERS, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_HR, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CATEGORIES, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAGS, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAXES, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TIME_TRACKING, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_WORKFLOWS, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_RULES, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CODING_RULES, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_DISTANCE, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_PER_DIEM, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_INVOICES, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TRAVEL, + ].some((actionName) => actionName === action?.actionName); +} + export { doesReportHaveVisibleActions, extractLinksFromMessageHtml, @@ -4983,6 +5006,7 @@ export { stripFollowupListFromHtml, getDynamicExternalWorkflowSubmitFailedActionMessage, getDynamicExternalWorkflowApproveFailedActionMessage, + isPolicyCopyReportAction, }; export type {LastVisibleMessage}; diff --git a/src/libs/ReportNameUtils.ts b/src/libs/ReportNameUtils.ts index 47292f11839..0a50232eef5 100644 --- a/src/libs/ReportNameUtils.ts +++ b/src/libs/ReportNameUtils.ts @@ -113,6 +113,7 @@ import { isMovedAction, isOldDotReportAction, isOriginalReportDeleted, + isPolicyCopyReportAction, isReimbursementDeQueuedOrCanceledAction, isReimbursementQueuedAction, isRejectedAction, @@ -131,6 +132,7 @@ import { getMovedActionMessage, getMovedTransactionMessage, getParentReport, + getPolicyChangeLogCopyMessage, getPolicyChangeMessage, getPolicyName, getReimbursementDeQueuedOrCanceledActionMessage, @@ -820,6 +822,9 @@ function computeReportNameBasedOnReportAction( if (isActionOfType(parentReportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.REMOVE_EXPENSIFY_CARD_RULE)) { return getRemoveExpensifyCardRuleMessage(translate, parentReportAction); } + if (isPolicyCopyReportAction(parentReportAction)) { + return Parser.htmlToText(getPolicyChangeLogCopyMessage(translate, parentReportAction)); + } return undefined; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1ec30f23752..f524296708c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6900,6 +6900,72 @@ function getPolicyChangeMessage(translate: LocalizedTranslate, action: ReportAct return message; } +function getPolicyChangeLogCopyMessage(translate: LocalizedTranslate, action: ReportAction) { + const PolicyChangeLogCopyOriginalMessage = getOriginalMessage(action as ReportAction) ?? {}; + const {sourcePolicyID, quantity} = PolicyChangeLogCopyOriginalMessage; + // The name is interpolated into an tag and rendered as HTML, so it must be encoded to avoid markup in the name being parsed as HTML. + const sourcePolicyName = Str.htmlEncode(allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${sourcePolicyID}`]?.name ?? ''); + const sourcePolicyURL = sourcePolicyID ? `${environmentURL}/${ROUTES.WORKSPACE_OVERVIEW.getRoute(sourcePolicyID)}` : ''; + const count = quantity ?? 0; + let message = ''; + switch (action.actionName) { + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_OVERVIEW: + message = translate('workspaceActions.policyCopy.overview', sourcePolicyName, sourcePolicyURL); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_EMPLOYEES: + message = translate('workspaceActions.policyCopy.employees', sourcePolicyName, sourcePolicyURL); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_REPORT_FIELDS: + message = translate('workspaceActions.policyCopy.reportFields', {count, sourcePolicyName, sourcePolicyURL}); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_ACCOUNTING: + message = translate('workspaceActions.policyCopy.accounting', sourcePolicyName, sourcePolicyURL); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_RECEIPT_PARTNERS: + message = translate('workspaceActions.policyCopy.receiptPartners', sourcePolicyName, sourcePolicyURL); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_HR: + message = translate('workspaceActions.policyCopy.hr', sourcePolicyName, sourcePolicyURL); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CATEGORIES: + message = translate('workspaceActions.policyCopy.categories', {count, sourcePolicyName, sourcePolicyURL}); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAGS: + message = translate('workspaceActions.policyCopy.tags', {count, sourcePolicyName, sourcePolicyURL}); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAXES: + message = translate('workspaceActions.policyCopy.taxes', {count, sourcePolicyName, sourcePolicyURL}); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TIME_TRACKING: + message = translate('workspaceActions.policyCopy.timeTracking', sourcePolicyName, sourcePolicyURL); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_WORKFLOWS: + message = translate('workspaceActions.policyCopy.workflows', sourcePolicyName, sourcePolicyURL); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_RULES: + message = translate('workspaceActions.policyCopy.rules', sourcePolicyName, sourcePolicyURL); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CODING_RULES: + message = translate('workspaceActions.policyCopy.codingRules', {count, sourcePolicyName, sourcePolicyURL}); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_DISTANCE: + message = translate('workspaceActions.policyCopy.distanceRates', {count, sourcePolicyName, sourcePolicyURL}); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_PER_DIEM: + message = translate('workspaceActions.policyCopy.perDiem', {count, sourcePolicyName, sourcePolicyURL}); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_INVOICES: + message = translate('workspaceActions.policyCopy.invoices', sourcePolicyName, sourcePolicyURL); + break; + case CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TRAVEL: + message = translate('workspaceActions.policyCopy.travel', sourcePolicyName, sourcePolicyURL); + break; + default: + break; + } + return message; +} + /** * @param iouReportID - the report ID of the IOU report the action belongs to * @param type - IOUReportAction type. Can be oneOf(create, decline, cancel, pay, split) @@ -13557,6 +13623,7 @@ export { getReportSubtitlePrefix, computeOptimisticReportName, getPolicyChangeMessage, + getPolicyChangeLogCopyMessage, getMovedTransactionMessage, getUnreportedTransactionMessage, navigateToLinkedReportAction, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index a3e90b203ca..e2ba488e6c2 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -139,6 +139,7 @@ import { isInviteOrRemovedAction, isLeavePolicyAction, isOldDotReportAction, + isPolicyCopyReportAction, isRenamedAction, isTagModificationAction, isTaskAction, @@ -156,6 +157,7 @@ import { getIcons, getMovedTransactionMessage, getParticipantsAccountIDsForDisplay, + getPolicyChangeLogCopyMessage, getPolicyName, getReceiptUploadErrorReason, getReportDescription, @@ -1248,6 +1250,8 @@ function getOptionData({ result.alternateText = getUpdatedIndividualBudgetNotificationMessage(translate, lastAction); } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.SHARED_BUDGET_NOTIFICATION) { result.alternateText = getUpdatedSharedBudgetNotificationMessage(translate, lastAction); + } else if (isPolicyCopyReportAction(lastAction)) { + result.alternateText = Parser.htmlToText(getPolicyChangeLogCopyMessage(translate, lastAction)); } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.RETRACTED) { result.alternateText = translate('iou.retracted'); } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REOPENED) { diff --git a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx index 1481d70907b..88c512f0835 100644 --- a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx @@ -153,6 +153,7 @@ import { isMovedAction, isOldDotReportAction, isOriginalReportDeleted, + isPolicyCopyReportAction, isReimbursementDeQueuedOrCanceledAction, isReimbursementQueuedAction, isRejectedAction, @@ -178,6 +179,7 @@ import { getIOUReportActionDisplayMessage, getMovedActionMessage, getMovedTransactionMessage, + getPolicyChangeLogCopyMessage, getPolicyChangeMessage, getReimbursementDeQueuedOrCanceledActionMessage, getReimbursementQueuedActionMessage, @@ -1252,6 +1254,8 @@ const ContextMenuActions: ContextMenuAction[] = [ setClipboardMessage(getUpdatedIndividualBudgetNotificationMessage(translate, reportAction)); } else if (isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.SHARED_BUDGET_NOTIFICATION)) { setClipboardMessage(getUpdatedSharedBudgetNotificationMessage(translate, reportAction)); + } else if (isPolicyCopyReportAction(reportAction)) { + setClipboardMessage(getPolicyChangeLogCopyMessage(translate, reportAction)); } else if ( isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.TAKE_CONTROL) || isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.REROUTE) || diff --git a/src/pages/inbox/report/actionContents/PolicyChangeLogContent.tsx b/src/pages/inbox/report/actionContents/PolicyChangeLogContent.tsx index 164095ad605..e56090e81dc 100644 --- a/src/pages/inbox/report/actionContents/PolicyChangeLogContent.tsx +++ b/src/pages/inbox/report/actionContents/PolicyChangeLogContent.tsx @@ -84,7 +84,7 @@ import { getWorkspaceTaxUpdateMessage, getWorkspaceUpdateFieldMessage, } from '@libs/ReportActionsUtils'; -import {getWorkspaceNameUpdatedMessage} from '@libs/ReportUtils'; +import {getPolicyChangeLogCopyMessage, getWorkspaceNameUpdatedMessage} from '@libs/ReportUtils'; import {getAddExpensifyCardRuleMessage, getRemoveExpensifyCardRuleMessage, getUpdateExpensifyCardRuleMessage} from '@libs/SpendRuleChangeLogUtils'; import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBasicMessage'; import CONST from '@src/CONST'; @@ -99,6 +99,9 @@ type ResolverFn = (translate: LocaleContextProps['translate'], action: OnyxTypes const categoryResolver: ResolverFn = (translate, action, policy) => getWorkspaceCategoryUpdateMessage(translate, action, policy); const taxResolver: ResolverFn = (translate, action) => getWorkspaceTaxUpdateMessage(translate, action); const tagModificationResolver: ResolverFn = (translate, action) => getCleanedTagName(getWorkspaceTagUpdateMessage(translate, action)); +const copySettingsResolver: ResolverFn = (translate, action) => ({ + html: `${getPolicyChangeLogCopyMessage(translate, action)}`, +}); const POLICY_CHANGE_LOG_RESOLVERS: Record = { // Simple string translations @@ -218,6 +221,25 @@ const POLICY_CHANGE_LOG_RESOLVERS: Record = { [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.DELETE_TAG]: tagModificationResolver, [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.DELETE_MULTIPLE_TAGS]: tagModificationResolver, [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_TAG]: tagModificationResolver, + + // Policy copy settings + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_OVERVIEW]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_EMPLOYEES]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_REPORT_FIELDS]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_ACCOUNTING]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_RECEIPT_PARTNERS]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_HR]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CATEGORIES]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAGS]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAXES]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TIME_TRACKING]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_WORKFLOWS]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_RULES]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CODING_RULES]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_DISTANCE]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_PER_DIEM]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_INVOICES]: copySettingsResolver, + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TRAVEL]: copySettingsResolver, }; /** diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 3fe91c50742..a361afad8ab 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -6,6 +6,7 @@ import type {PolicyRuleTaxRate} from './ExpenseRule'; import type {Attendee} from './IOU'; import type {OldDotOriginalMessageMap} from './OldDotAction'; import type {AllConnectionName} from './Policy'; +import type {PolicyChangeLogCopyReportActionNames} from './ReportAction'; import type ReportActionName from './ReportActionName'; import type {Reservation, TransactionCommentVendor} from './Transaction'; import type TransactionPending3DSReview from './TransactionPending3DSReview'; @@ -882,6 +883,15 @@ type OriginalMessageSpendRuleChangeLog = { currency?: string; }; +/** Model of a policy copy change log action */ +type OriginalMessagePolicyChangeCopyLog = { + /** The ID of the source policy from which the user copied settings */ + sourcePolicyID?: string; + + /** The quantity of the item copied from source policy */ + quantity?: number; +}; + /** Model of `join policy` report action */ type OriginalMessageJoinPolicy = { /** What was the invited user decision */ @@ -1721,7 +1731,8 @@ type OriginalMessageMap = { [CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DIRECTOR_INFORMATION_REQUIRED]: OriginalMessageReimbursementDirectorInformationRequired; [CONST.REPORT.ACTIONS.TYPE.SETTLEMENT_ACCOUNT_LOCKED]: OriginalMessageSettlementAccountLocked; } & OldDotOriginalMessageMap & - Record, OriginalMessagePolicyChangeLog> & { + Record, OriginalMessagePolicyChangeLog> & + Record & { [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.ADD_EXPENSIFY_CARD_RULE]: OriginalMessageSpendRuleChangeLog; [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_EXPENSIFY_CARD_RULE]: OriginalMessageSpendRuleChangeLog; [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.REMOVE_EXPENSIFY_CARD_RULE]: OriginalMessageSpendRuleChangeLog; diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index 95ab730457b..6c602ccfe8b 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -314,5 +314,25 @@ type ReportActions = Record; /** Collection of mock report actions, indexed by reportActions_${reportID} */ type ReportActionsCollectionDataSet = CollectionDataSet; +/** A union type of all report action names related to policy copy log */ +type PolicyChangeLogCopyReportActionNames = + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_OVERVIEW + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_EMPLOYEES + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_REPORT_FIELDS + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_ACCOUNTING + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_RECEIPT_PARTNERS + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_HR + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CATEGORIES + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAGS + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAXES + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TIME_TRACKING + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_WORKFLOWS + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_RULES + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CODING_RULES + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_DISTANCE + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_PER_DIEM + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_INVOICES + | typeof CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TRAVEL; + export default ReportAction; -export type {ReportActions, Message, LinkMetadata, OriginalMessage, ReportActionsCollectionDataSet, OldDotReportAction}; +export type {ReportActions, Message, LinkMetadata, OriginalMessage, ReportActionsCollectionDataSet, OldDotReportAction, PolicyChangeLogCopyReportActionNames}; diff --git a/tests/unit/PolicyChangeLogContentTest.tsx b/tests/unit/PolicyChangeLogContentTest.tsx index e5ddf6cd72d..e771e8ba552 100644 --- a/tests/unit/PolicyChangeLogContentTest.tsx +++ b/tests/unit/PolicyChangeLogContentTest.tsx @@ -1,3 +1,4 @@ +import {NavigationContainer} from '@react-navigation/native'; import {act, render, screen} from '@testing-library/react-native'; import React from 'react'; import Onyx from 'react-native-onyx'; @@ -49,12 +50,14 @@ describe('PolicyChangeLogContent', () => { const fakeAction = {actionName: type, originalMessage: {}} as ReportAction; render( - - - , + + + + + , ); await waitForBatchedUpdatesWithAct(); diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index e368b958de0..5940146e078 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -5952,4 +5952,49 @@ describe('ReportActionsUtils', () => { expect(getModerationFlagState(action)).toEqual({latestDecision: undefined, hasBeenFlagged: false}); }); }); + + describe('isPolicyCopyReportAction', () => { + function buildAction(actionName: ReportAction['actionName']): ReportAction { + return { + actionName, + reportActionID: '1', + reportID: '123', + created: '2026-05-15 10:00:00.000', + message: [], + } as unknown as ReportAction; + } + + it.each([ + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_OVERVIEW, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_EMPLOYEES, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_REPORT_FIELDS, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_ACCOUNTING, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_RECEIPT_PARTNERS, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_HR, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CATEGORIES, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAGS, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAXES, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TIME_TRACKING, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_WORKFLOWS, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_RULES, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CODING_RULES, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_DISTANCE, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_PER_DIEM, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_INVOICES, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TRAVEL, + ])('returns true for the policy copy action %s', (actionName) => { + expect(ReportActionsUtils.isPolicyCopyReportAction(buildAction(actionName))).toBe(true); + }); + + it.each([CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, CONST.REPORT.ACTIONS.TYPE.CHANGE_POLICY, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_NAME, CONST.REPORT.ACTIONS.TYPE.CREATED])( + 'returns false for the non-copy action %s', + (actionName) => { + expect(ReportActionsUtils.isPolicyCopyReportAction(buildAction(actionName))).toBe(false); + }, + ); + + it('returns false for an undefined action', () => { + expect(ReportActionsUtils.isPolicyCopyReportAction(undefined)).toBe(false); + }); + }); }); diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 925f1561408..84647fcdaa4 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -102,6 +102,7 @@ import { getOutstandingChildRequest, getParentNavigationSubtitle, getParticipantsList, + getPolicyChangeLogCopyMessage, getPolicyExpenseChat, getPolicyIDsWithEmptyReportsForAccount, getPolicyName, @@ -7402,6 +7403,104 @@ describe('ReportUtils', () => { }); }); + describe('getPolicyChangeLogCopyMessage', () => { + const sourcePolicyID = 'sourcePolicy1'; + const sourcePolicyName = 'Acme HQ Workspace'; + let environmentURL: string; + + // Builds the same source policy link the implementation wraps the policy name in. + const buildLink = (policyID: string | undefined, name: string) => { + const sourcePolicyURL = policyID ? `${environmentURL}/${ROUTES.WORKSPACE_OVERVIEW.getRoute(policyID)}` : ''; + return `${name}`; + }; + + function buildCopyAction(actionName: ReportAction['actionName'], originalMessage?: {sourcePolicyID?: string; quantity?: number}): ReportAction { + return { + actionName, + reportActionID: '1', + reportID: '123', + created: '2026-05-15 10:00:00.000', + message: [], + originalMessage, + } as unknown as ReportAction; + } + + beforeEach(async () => { + environmentURL = await getEnvironmentURL(); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${sourcePolicyID}`, {id: sourcePolicyID, name: sourcePolicyName}); + await waitForBatchedUpdates(); + }); + + it.each([ + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_OVERVIEW, 'copied overview from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_EMPLOYEES, 'copied members from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_ACCOUNTING, 'copied accounting settings from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_RECEIPT_PARTNERS, 'copied receipt partner settings from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_HR, 'copied HR settings from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TIME_TRACKING, 'copied time tracking settings from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_WORKFLOWS, 'copied workflows from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_RULES, 'copied rules from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_INVOICES, 'copied invoice settings from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TRAVEL, 'copied travel settings from'], + ])('returns the non-counted message for %s', (actionName, prefix) => { + const action = buildCopyAction(actionName, {sourcePolicyID}); + expect(getPolicyChangeLogCopyMessage(translateLocal, action)).toBe(`${prefix} ${buildLink(sourcePolicyID, sourcePolicyName)}`); + }); + + it.each([ + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_REPORT_FIELDS, 1, 'copied 1 report field from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_REPORT_FIELDS, 3, 'copied 3 report fields from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CATEGORIES, 1, 'copied 1 category from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CATEGORIES, 5, 'copied 5 categories from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAGS, 1, 'copied 1 tag from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAGS, 2, 'copied 2 tags from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAXES, 1, 'copied 1 tax rate from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAXES, 4, 'copied 4 tax rates from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CODING_RULES, 1, 'copied 1 merchant rule from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CODING_RULES, 6, 'copied 6 merchant rules from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_DISTANCE, 1, 'copied 1 distance rate from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_DISTANCE, 7, 'copied 7 distance rates from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_PER_DIEM, 1, 'copied 1 per diem rate from'], + [CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_PER_DIEM, 8, 'copied 8 per diem rates from'], + ])('returns the counted message for %s with quantity %d', (actionName, quantity, prefix) => { + const action = buildCopyAction(actionName, {sourcePolicyID, quantity}); + expect(getPolicyChangeLogCopyMessage(translateLocal, action)).toBe(`${prefix} ${buildLink(sourcePolicyID, sourcePolicyName)}`); + }); + + it('falls back to a count of 0 (plural form) when quantity is missing on a counted action', () => { + const action = buildCopyAction(CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_CATEGORIES, {sourcePolicyID}); + expect(getPolicyChangeLogCopyMessage(translateLocal, action)).toBe(`copied 0 categories from ${buildLink(sourcePolicyID, sourcePolicyName)}`); + }); + + it('uses an empty source policy name when the source policy is not in Onyx', () => { + const action = buildCopyAction(CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_OVERVIEW, {sourcePolicyID: 'unknownPolicy'}); + expect(getPolicyChangeLogCopyMessage(translateLocal, action)).toBe(`copied overview from ${buildLink('unknownPolicy', '')}`); + }); + + it('uses an empty source policy name and URL when sourcePolicyID is missing', () => { + const action = buildCopyAction(CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_OVERVIEW, {}); + expect(getPolicyChangeLogCopyMessage(translateLocal, action)).toBe(`copied overview from ${buildLink(undefined, '')}`); + }); + + it('handles a missing original message (no sourcePolicyID, count defaults to 0)', () => { + const action = buildCopyAction(CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_TAGS); + expect(getPolicyChangeLogCopyMessage(translateLocal, action)).toBe(`copied 0 tags from ${buildLink(undefined, '')}`); + }); + + it('HTML-encodes a source policy name that contains markup characters', async () => { + const htmlPolicyID = 'htmlPolicy'; + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${htmlPolicyID}`, {id: htmlPolicyID, name: 'A B'}); + await waitForBatchedUpdates(); + const action = buildCopyAction(CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.COPY_OVERVIEW, {sourcePolicyID: htmlPolicyID}); + expect(getPolicyChangeLogCopyMessage(translateLocal, action)).toBe(`copied overview from ${buildLink(htmlPolicyID, 'A <b>B</b>')}`); + }); + + it('returns an empty string for an action that is not a policy copy action', () => { + const action = buildCopyAction(CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, {sourcePolicyID}); + expect(getPolicyChangeLogCopyMessage(translateLocal, action)).toBe(''); + }); + }); + describe('buildOptimisticIOUReportAction', () => { it('should set the action reportID to the provided iouReportID when tracking a personal expense', () => { const iouAction = buildOptimisticIOUReportAction({