Add merchant rules spreadsheet import flow#95105
Conversation
1dc2825 to
930cdd4
Compare
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
This comment has been minimized.
This comment has been minimized.
Lets workspace admins bulk-import merchant rules from a CSV/XLSX file instead of creating them one at a time. Reuses the shared spreadsheet import components and fires the new ImportMerchantRulesSpreadsheet backend command with client-generated ruleIDs so retries are idempotent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Hide empty cells in column previews instead of rendering comma runs - Add "Updated tag" column role and import tag values into rules - Auto-detect "Preferred Merchant (Vendor) Name", "Merchant", "Tag", and "Reimburseable" headers in the merchant rules context - Skip rows with no update values and fail fast when no rules remain Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
456aa72 to
9560b29
Compare
This comment has been minimized.
This comment has been minimized.
…uage files and update references in PolicyRulesPage components to use 'importRulesTitle' instead.
|
@dubielzyk-expensify @truph01 One of you needs to copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 227bfda1c5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…routes on rules write access Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@truph01 would it be possible to get this reviewed today, please? |
Yes, I'll review it EOD |
|
@truph01 let me know if you cannot check this today to pass it to another contributor please. |
100% out of scope, that is a preexisting UI that shouldn’t be modified here |
Co-authored-by: {"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"} <{"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"}@users.noreply.github.com>
🦜 Polyglot Parrot! 🦜Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues: View the translation diffdiff --git a/src/languages/de.ts b/src/languages/de.ts
index 8638288c44b..dad8980bea1 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -1168,7 +1168,7 @@ const translations: TranslationDeepObject<typeof en> = {
return rules > 1 ? `${rules} Händlerregeln wurden hinzugefügt.` : '1 Händlerregel wurde hinzugefügt.';
},
importMerchantRulesRequiredColumns:
- 'Ups! Du musst mindestens eine Spalte („Händler ist“ oder „Händler enthält“) zuordnen sowie mindestens ein zu aktualisierendes Feld. Bitte überprüfe es und versuche es erneut.',
+ 'Hoppla! Sie müssen mindestens eine Spalte „Händler ist“ oder „Händler enthält“ sowie mindestens ein zu aktualisierendes Feld zuordnen. Bitte überprüfen Sie die Angaben und versuchen Sie es erneut.',
importTransactionsSuccessfulDescription: ({transactions}: {transactions: number}) =>
transactions > 1 ? `${transactions} Buchungen wurden importiert.` : '1 Transaktion wurde importiert.',
importFailedTitle: 'Import fehlgeschlagen',
@@ -7475,12 +7475,12 @@ Fordern Sie Spesendetails wie Belege und Beschreibungen an, legen Sie Limits und
addRuleTitle: 'Regel hinzufügen',
editRuleTitle: 'Regel bearbeiten',
importRulesTitle: 'Händlerregeln importieren',
- importRulesSupportingText: 'Ordne jede Spalte in deiner Tabelle einem Feld der Händlerregel zu. Wenn alles gut aussieht, klicke unten, um deine Regeln zu importieren.',
+ importRulesSupportingText: 'Ordnen Sie jede Spalte in Ihrer Tabelle einem Händlerregel-Feld zu. Wenn alles gut aussieht, klicken Sie unten, um Ihre Regeln zu importieren.',
importColumnMerchantIs: 'Händler ist',
importColumnMerchantContains: 'Händler enthält',
- importColumnUpdatedMerchant: 'Aktualisierter Händler',
- importColumnUpdatedCategory: 'Aktualisierte Kategorie',
- importColumnUpdatedTag: 'Aktualisiertes Tag',
+ importColumnUpdatedMerchant: 'Händler aktualisiert',
+ importColumnUpdatedCategory: 'Kategorie aktualisiert',
+ importColumnUpdatedTag: 'Tag aktualisiert',
importColumnUpdatedDescription: 'Aktualisierte Beschreibung',
expensesWith: 'Für Ausgaben mit:',
expensesExactlyMatching: 'Für Ausgaben mit genau folgender Übereinstimmung:',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 97b2cd29d12..3f67de62efa 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1123,12 +1123,12 @@ const translations: TranslationDeepObject<typeof en> = {
importPerDiemRatesSuccessfulDescription: ({rates}) => (rates > 1 ? `Se han añadido ${rates} tasas de per diem.` : 'Se ha añadido 1 tasa de per diem.'),
importMerchantRulesSuccessfulDescription: ({rules}: {rules: number}) => {
if (rules === 0) {
- return 'No se han añadido reglas de comerciante, ya que todas ya existen.';
+ return 'No se ha añadido ninguna regla de comerciante, ya que todas ya existen.';
}
- return rules > 1 ? `Se han añadido ${rules} reglas de comerciante.` : 'Se ha añadido 1 regla de comerciante.';
+ return rules > 1 ? `Se han añadido ${rules} reglas de comerciante.` : 'Se ha añadido 1 regla de comercio.';
},
importMerchantRulesRequiredColumns:
- '¡Ups! Debes asignar al menos una columna "El comerciante es" o "El comerciante contiene", además de al menos un campo para actualizar. Por favor, revisa e inténtalo de nuevo.',
+ '¡Ups! Debes asignar al menos una columna de «Comerciante es» o «Comerciante contiene», además de al menos un campo para actualizar. Por favor, revisa e inténtalo de nuevo.',
importTransactionsSuccessfulDescription: ({transactions}) => (transactions > 1 ? `Se han importado ${transactions} transacciones.` : 'Se ha importado 1 transacción.'),
importCompanyCardTransactionsSuccessfulDescription: ({transactions}: {transactions: number}) =>
transactions > 1 ? `Se han importado ${transactions} transacciones.` : 'Se ha importado 1 transacción.',
@@ -7406,10 +7406,9 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`,
findRule: 'Buscar regla de comerciante',
addRuleTitle: 'Añadir regla',
editRuleTitle: 'Editar regla',
- importRulesTitle: 'Importar reglas de comerciante',
- importRulesSupportingText:
- 'Asigna cada columna de tu hoja de cálculo a un campo de la regla de comerciante. Cuando todo esté correcto, haz clic abajo para importar tus reglas.',
- importColumnMerchantIs: 'El comerciante es',
+ importRulesTitle: 'Importar reglas de vendedor',
+ importRulesSupportingText: 'Asigna cada columna de tu hoja de cálculo a un campo de regla de comercio. Cuando todo se vea bien, haz clic abajo para importar tus reglas.',
+ importColumnMerchantIs: 'El comercio es',
importColumnMerchantContains: 'El comerciante contiene',
importColumnUpdatedMerchant: 'Comerciante actualizado',
importColumnUpdatedCategory: 'Categoría actualizada',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 7a54c8362be..62fc48f7aea 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -1167,12 +1167,12 @@ const translations: TranslationDeepObject<typeof en> = {
importPerDiemRatesSuccessfulDescription: ({rates}: {rates: number}) => (rates > 1 ? `${rates} indemnités journalières ont été ajoutées.` : '1 taux de per diem a été ajouté.'),
importMerchantRulesSuccessfulDescription: ({rules}: {rules: number}) => {
if (rules === 0) {
- return "Aucune règle de marchand n'a été ajoutée, car elles existent toutes déjà.";
+ return 'Aucune règle de marchand n’a été ajoutée, car elles existent déjà toutes.';
}
- return rules > 1 ? `${rules} règles de marchand ont été ajoutées.` : '1 règle de marchand a été ajoutée.';
+ return rules > 1 ? `${rules} règles de commerçant ont été ajoutées.` : '1 règle de commerçant a été ajoutée.';
},
importMerchantRulesRequiredColumns:
- 'Oups ! Vous devez associer au moins une colonne « Le marchand est » ou « Le marchand contient », ainsi qu’au moins un champ à mettre à jour. Veuillez examiner et réessayer.',
+ 'Oups ! Vous devez mapper au moins une colonne « Merchant est » ou « Merchant contient », ainsi qu’au moins un champ à mettre à jour. Veuillez vérifier et réessayer.',
importTransactionsSuccessfulDescription: ({transactions}: {transactions: number}) =>
transactions > 1 ? `${transactions} transactions ont été importées.` : '1 transaction a été importée.',
importFailedTitle: 'Échec de l’importation',
@@ -7504,9 +7504,9 @@ Rendez obligatoires des informations de dépense comme les reçus et les descrip
editRuleTitle: 'Modifier la règle',
importRulesTitle: 'Importer des règles de marchand',
importRulesSupportingText:
- 'Faites correspondre chaque colonne de votre feuille de calcul à un champ de règle de marchand. Si tout semble correct, cliquez ci-dessous pour importer vos règles.',
- importColumnMerchantIs: 'Le marchand est',
- importColumnMerchantContains: 'Le marchand contient',
+ 'Associez chaque colonne de votre feuille de calcul à un champ de règle de commerçant. Une fois que tout semble correct, cliquez ci-dessous pour importer vos règles.',
+ importColumnMerchantIs: 'Le commerçant est',
+ importColumnMerchantContains: 'Le commerçant contient',
importColumnUpdatedMerchant: 'Marchand mis à jour',
importColumnUpdatedCategory: 'Catégorie mise à jour',
importColumnUpdatedTag: 'Tag mis à jour',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index a1b10e1b816..02e2fd8b024 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -1164,11 +1164,11 @@ const translations: TranslationDeepObject<typeof en> = {
importPerDiemRatesSuccessfulDescription: ({rates}: {rates: number}) => (rates > 1 ? `Sono state aggiunte le diarie giornaliere ${rates}.` : 'È stata aggiunta 1 diaria.'),
importMerchantRulesSuccessfulDescription: ({rules}: {rules: number}) => {
if (rules === 0) {
- return 'Nessuna regola esercente è stata aggiunta, poiché esistono già tutte.';
+ return 'Non sono state aggiunte regole per gli esercenti, perché esistono già tutte.';
}
- return rules > 1 ? `Sono state aggiunte ${rules} regole esercente.` : 'È stata aggiunta 1 regola esercente.';
+ return rules > 1 ? `Sono state aggiunte ${rules} regole commerciante.` : 'È stata aggiunta 1 regola per l’esercente.';
},
- importMerchantRulesRequiredColumns: 'Ops! Devi mappare almeno una colonna “L’esercente è” o “L’esercente contiene”, oltre ad almeno un campo da aggiornare. Controlla e riprova.',
+ importMerchantRulesRequiredColumns: 'Ops! Devi mappare almeno una colonna "Merchant è" o "Merchant contiene", oltre ad almeno un campo da aggiornare. Controlla e riprova.',
importTransactionsSuccessfulDescription: ({transactions}: {transactions: number}) =>
transactions > 1 ? `${transactions} transazioni sono state importate.` : '1 transazione è stata importata.',
importFailedTitle: 'Importazione non riuscita',
@@ -7455,12 +7455,12 @@ Richiedi dettagli sulle spese come ricevute e descrizioni, imposta limiti e valo
addRule: 'Aggiungi regola esercente',
addRuleTitle: 'Aggiungi regola',
editRuleTitle: 'Modifica regola',
- importRulesTitle: 'Importa regole esercente',
+ importRulesTitle: 'Importa regole esercenti',
importRulesSupportingText:
- 'Mappa ogni colonna del tuo foglio di calcolo a un campo della regola esercente. Se ti sembra tutto corretto, fai clic qui sotto per importare le tue regole.',
- importColumnMerchantIs: 'L’esercente è',
- importColumnMerchantContains: 'L’esercente contiene',
- importColumnUpdatedMerchant: 'Esercente aggiornato',
+ 'Associa ogni colonna del tuo foglio di calcolo a un campo della regola del fornitore. Quando è tutto corretto, fai clic qui sotto per importare le tue regole.',
+ importColumnMerchantIs: 'Il fornitore è',
+ importColumnMerchantContains: "L'esercente contiene",
+ importColumnUpdatedMerchant: 'Commerciante aggiornato',
importColumnUpdatedCategory: 'Categoria aggiornata',
importColumnUpdatedTag: 'Tag aggiornato',
importColumnUpdatedDescription: 'Descrizione aggiornata',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index a1b4b3eab7a..3f6df6c07bb 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -1146,12 +1146,12 @@ const translations: TranslationDeepObject<typeof en> = {
importPerDiemRatesSuccessfulDescription: ({rates}: {rates: number}) => (rates > 1 ? `${rates}件の日当レートが追加されました。` : '1件の日当レートが追加されました。'),
importMerchantRulesSuccessfulDescription: ({rules}: {rules: number}) => {
if (rules === 0) {
- return 'すべての加盟店ルールが既に存在するため、追加されませんでした。';
+ return 'すべてのルールがすでに存在するため、マーチャントルールは追加されていません。';
}
- return rules > 1 ? `${rules}件の加盟店ルールが追加されました。` : '1件の加盟店ルールが追加されました。';
+ return rules > 1 ? `${rules} 件のマーチャントルールを追加しました。` : 'マーチャントルールが1件追加されました。';
},
importMerchantRulesRequiredColumns:
- 'おっと!「加盟店が一致」または「加盟店を含む」の列を少なくとも1つ、さらに更新するフィールドを少なくとも1つマッピングしてください。確認してもう一度お試しください。',
+ 'エラーが発生しました。「Merchant is」または「Merchant contains」列のうち少なくとも1つと、更新するフィールドを少なくとも1つ対応付ける必要があります。内容を確認してもう一度お試しください。',
importTransactionsSuccessfulDescription: ({transactions}: {transactions: number}) =>
transactions > 1 ? `${transactions} 件の取引がインポートされました。` : '1件の取引がインポートされました。',
importFailedTitle: 'インポートに失敗しました',
@@ -7365,14 +7365,15 @@ ${reportName}`,
addRule: '支払先ルールを追加',
addRuleTitle: 'ルールを追加',
editRuleTitle: 'ルールを編集',
- importRulesTitle: '加盟店ルールをインポート',
- importRulesSupportingText: 'スプレッドシートの各列を加盟店ルールのフィールドに対応付けてください。問題なければ、下をクリックしてルールをインポートしてください。',
- importColumnMerchantIs: '加盟店が一致',
- importColumnMerchantContains: '加盟店を含む',
- importColumnUpdatedMerchant: '更新後の加盟店',
- importColumnUpdatedCategory: '更新後のカテゴリ',
- importColumnUpdatedTag: '更新後のタグ',
- importColumnUpdatedDescription: '更新後の説明',
+ importRulesTitle: 'マーチャントルールをインポート',
+ importRulesSupportingText:
+ 'スプレッドシート内の各列を、マーチャントルールの項目に対応付けてください。すべて問題ないことを確認したら、下のボタンをクリックしてルールをインポートします。',
+ importColumnMerchantIs: '加盟店:',
+ importColumnMerchantContains: '支払先に次を含む',
+ importColumnUpdatedMerchant: '更新された支払先',
+ importColumnUpdatedCategory: 'カテゴリを更新しました',
+ importColumnUpdatedTag: 'タグを更新しました',
+ importColumnUpdatedDescription: '更新された説明',
expensesWith: '対象となる経費条件:',
expensesExactlyMatching: '次の条件に完全一致する経費の場合:',
applyUpdates: 'これらの更新を適用する',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 368a79a6bfa..6c3e9634533 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -1162,12 +1162,12 @@ const translations: TranslationDeepObject<typeof en> = {
importPerDiemRatesSuccessfulDescription: ({rates}: {rates: number}) => (rates > 1 ? `${rates} per diem-tarieven zijn toegevoegd.` : '1 dagvergoeding is toegevoegd.'),
importMerchantRulesSuccessfulDescription: ({rules}: {rules: number}) => {
if (rules === 0) {
- return 'Er zijn geen handelaarregels toegevoegd, omdat ze allemaal al bestaan.';
+ return 'Er zijn geen leverancierregels toegevoegd, omdat ze allemaal al bestaan.';
}
- return rules > 1 ? `${rules} handelaarregels zijn toegevoegd.` : '1 handelaarregel is toegevoegd.';
+ return rules > 1 ? `${rules} handelaarsregels zijn toegevoegd.` : 'Er is 1 leveranciersregel toegevoegd.';
},
importMerchantRulesRequiredColumns:
- 'Oeps! Je moet ten minste één kolom "Handelaar is" of "Handelaar bevat" toewijzen, plus ten minste één veld om bij te werken. Controleer het en probeer het opnieuw.',
+ 'Oeps! Je moet minstens één kolom “Handelaar is” of “Handelaar bevat” koppelen, plus minstens één veld om bij te werken. Controleer alles en probeer het opnieuw.',
importTransactionsSuccessfulDescription: ({transactions}: {transactions: number}) =>
transactions > 1 ? `${transactions} transacties zijn geïmporteerd.` : '1 transactie is geïmporteerd.',
importFailedTitle: 'Import mislukt',
@@ -7436,14 +7436,14 @@ Vereis onkostendetails zoals bonnen en beschrijvingen, stel limieten en standaar
addRule: 'Merchantregel toevoegen',
addRuleTitle: 'Regel toevoegen',
editRuleTitle: 'Regel bewerken',
- importRulesTitle: 'Handelaarregels importeren',
- importRulesSupportingText: 'Koppel elke kolom in je spreadsheet aan een veld van een handelaarregel. Als alles er goed uitziet, klik hieronder om je regels te importeren.',
- importColumnMerchantIs: 'Handelaar is',
+ importRulesTitle: 'Merchantregels importeren',
+ importRulesSupportingText: 'Koppel elke kolom in je spreadsheet aan een veld voor handelsregels. Zodra alles er goed uitziet, klik je hieronder om je regels te importeren.',
+ importColumnMerchantIs: 'Winkel is',
importColumnMerchantContains: 'Handelaar bevat',
- importColumnUpdatedMerchant: 'Bijgewerkte handelaar',
- importColumnUpdatedCategory: 'Bijgewerkte categorie',
- importColumnUpdatedTag: 'Bijgewerkte tag',
- importColumnUpdatedDescription: 'Bijgewerkte omschrijving',
+ importColumnUpdatedMerchant: 'Bijgewerkte leverancier',
+ importColumnUpdatedCategory: 'Categorie bijgewerkt',
+ importColumnUpdatedTag: 'Tag bijgewerkt',
+ importColumnUpdatedDescription: 'Beschrijving bijgewerkt',
expensesWith: 'Voor declaraties met:',
expensesExactlyMatching: 'Voor onkosten die exact overeenkomen met:',
applyUpdates: 'Pas deze updates toe:',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index fe28ee1c3c7..e790bd9b2e8 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -1161,10 +1161,10 @@ const translations: TranslationDeepObject<typeof en> = {
if (rules === 0) {
return 'Nie dodano żadnych reguł sprzedawcy, ponieważ wszystkie już istnieją.';
}
- return rules > 1 ? `Dodano reguły sprzedawcy: ${rules}.` : 'Dodano 1 regułę sprzedawcy.';
+ return rules > 1 ? `Dodano ${rules} reguły handlowca.` : 'Dodano 1 regułę dla sprzedawcy.';
},
importMerchantRulesRequiredColumns:
- 'Ups! Musisz zmapować co najmniej jedną kolumnę „Sprzedawca to” lub „Sprzedawca zawiera” oraz co najmniej jedno pole do aktualizacji. Sprawdź i spróbuj ponownie.',
+ 'Ups! Musisz przypisać co najmniej jedną kolumnę „Merchant is” lub „Merchant contains” oraz co najmniej jedno pole do zaktualizowania. Sprawdź ustawienia i spróbuj ponownie.',
importTransactionsSuccessfulDescription: ({transactions}: {transactions: number}) => (transactions > 1 ? `Zaimportowano ${transactions} transakcje.` : 'Zaimportowano 1 transakcję.'),
importFailedTitle: 'Import nieudany',
importFailedDescription: 'Upewnij się, że wszystkie pola zostały poprawnie wypełnione i spróbuj ponownie. Jeśli problem będzie się powtarzał, skontaktuj się z Concierge.',
@@ -7415,15 +7415,15 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i
addRule: 'Dodaj regułę dla sprzedawcy',
addRuleTitle: 'Dodaj regułę',
editRuleTitle: 'Edytuj regułę',
- importRulesTitle: 'Importuj reguły sprzedawcy',
+ importRulesTitle: 'Importuj reguły sprzedawców',
importRulesSupportingText:
- 'Zmapuj każdą kolumnę w arkuszu kalkulacyjnym do pola reguły sprzedawcy. Jeśli wszystko wygląda dobrze, kliknij poniżej, aby zaimportować swoje reguły.',
+ 'Przypisz każdą kolumnę w swoim arkuszu kalkulacyjnym do pola reguły sprzedawcy. Gdy wszystko będzie wyglądać dobrze, kliknij poniżej, aby zaimportować swoje reguły.',
importColumnMerchantIs: 'Sprzedawca to',
importColumnMerchantContains: 'Sprzedawca zawiera',
- importColumnUpdatedMerchant: 'Zaktualizowany sprzedawca',
- importColumnUpdatedCategory: 'Zaktualizowana kategoria',
- importColumnUpdatedTag: 'Zaktualizowany tag',
- importColumnUpdatedDescription: 'Zaktualizowany opis',
+ importColumnUpdatedMerchant: 'Zaktualizowano sprzedawcę',
+ importColumnUpdatedCategory: 'Zaktualizowano kategorię',
+ importColumnUpdatedTag: 'Zaktualizowano znacznik',
+ importColumnUpdatedDescription: 'Zaktualizowano opis',
expensesWith: 'Dla wydatków z:',
expensesExactlyMatching: 'Dla wydatków dokładnie pasujących do:',
applyUpdates: 'Zastosuj te aktualizacje:',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 5c5acc3c28d..74912587faa 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -1162,12 +1162,12 @@ const translations: TranslationDeepObject<typeof en> = {
importPerDiemRatesSuccessfulDescription: ({rates}: {rates: number}) => (rates > 1 ? `Foram adicionadas ${rates} diárias.` : '1 diária foi adicionada.'),
importMerchantRulesSuccessfulDescription: ({rules}: {rules: number}) => {
if (rules === 0) {
- return 'Nenhuma regra de comerciante foi adicionada, pois todas já existem.';
+ return 'Nenhuma regra de comerciante foi adicionada, pois todas elas já existem.';
}
- return rules > 1 ? `Foram adicionadas ${rules} regras de comerciante.` : '1 regra de comerciante foi adicionada.';
+ return rules > 1 ? `${rules} regras de comerciante foram adicionadas.` : '1 regra de comerciante foi adicionada.';
},
importMerchantRulesRequiredColumns:
- 'Ops! Você precisa mapear pelo menos uma coluna "O comerciante é" ou "O comerciante contém", além de pelo menos um campo para atualizar. Revise e tente novamente.',
+ 'Ops! Você deve mapear pelo menos uma coluna de "Merchant é" ou "Merchant contém", além de pelo menos um campo para atualizar. Revise e tente novamente.',
importTransactionsSuccessfulDescription: ({transactions}: {transactions: number}) =>
transactions > 1 ? `${transactions} transações foram importadas.` : '1 transação foi importada.',
importFailedTitle: 'Falha na importação',
@@ -7428,10 +7428,10 @@ Exija dados de despesas como recibos e descrições, defina limites e padrões e
addRule: 'Adicionar regra de comerciante',
addRuleTitle: 'Adicionar regra',
editRuleTitle: 'Editar regra',
- importRulesTitle: 'Importar regras de comerciante',
- importRulesSupportingText: 'Mapeie cada coluna da sua planilha para um campo de regra de comerciante. Se tudo estiver certo, clique abaixo para importar suas regras.',
- importColumnMerchantIs: 'O comerciante é',
- importColumnMerchantContains: 'O comerciante contém',
+ importRulesTitle: 'Importar regras de estabelecimento',
+ importRulesSupportingText: 'Mapeie cada coluna da sua planilha para um campo de regra de comerciante. Quando tudo estiver certo, clique abaixo para importar suas regras.',
+ importColumnMerchantIs: 'Estabelecimento é',
+ importColumnMerchantContains: 'Estabelecimento contém',
importColumnUpdatedMerchant: 'Comerciante atualizado',
importColumnUpdatedCategory: 'Categoria atualizada',
importColumnUpdatedTag: 'Tag atualizada',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index f165fa686c2..1747313a9eb 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -1118,11 +1118,11 @@ const translations: TranslationDeepObject<typeof en> = {
importPerDiemRatesSuccessfulDescription: ({rates}: {rates: number}) => (rates > 1 ? `已添加 ${rates} 个每日补贴标准。` : '已添加 1 条日津贴费率。'),
importMerchantRulesSuccessfulDescription: ({rules}: {rules: number}) => {
if (rules === 0) {
- return '未添加任何商户规则,因为它们均已存在。';
+ return '尚未添加商家规则,因为它们都已存在。';
}
- return rules > 1 ? `已添加 ${rules} 条商户规则。` : '已添加 1 条商户规则。';
+ return rules > 1 ? `已添加 ${rules} 条商家规则。` : '已添加 1 条商户规则。';
},
- importMerchantRulesRequiredColumns: '哎呀!您必须至少映射一列“商户为”或“商户包含”,并且至少映射一个要更新的字段。请检查后重试。',
+ importMerchantRulesRequiredColumns: '哎呀!您必须至少映射一列“商户为”或“商户包含”,并且至少选择一个要更新的字段。请检查后重试。',
importTransactionsSuccessfulDescription: ({transactions}: {transactions: number}) => (transactions > 1 ? `已导入 ${transactions} 笔交易。` : '已导入 1 笔交易。'),
importFailedTitle: '导入失败',
importFailedDescription: '请确保所有字段均已正确填写,然后重试。如果问题仍然存在,请联系 Concierge。',
@@ -7214,14 +7214,14 @@ ${reportName}`,
addRule: '添加商家规则',
addRuleTitle: '添加规则',
editRuleTitle: '编辑规则',
- importRulesTitle: '导入商户规则',
- importRulesSupportingText: '将电子表格中的每一列映射到商户规则的字段。如果一切看起来都没问题,请点击下方以导入您的规则。',
+ importRulesTitle: '导入商家规则',
+ importRulesSupportingText: '将电子表格中的每一列映射到商户规则字段。确认无误后,点击下方按钮导入规则。',
importColumnMerchantIs: '商户为',
importColumnMerchantContains: '商户包含',
- importColumnUpdatedMerchant: '更新后的商户',
- importColumnUpdatedCategory: '更新后的类别',
- importColumnUpdatedTag: '更新后的标签',
- importColumnUpdatedDescription: '更新后的描述',
+ importColumnUpdatedMerchant: '已更新商户',
+ importColumnUpdatedCategory: '已更新类别',
+ importColumnUpdatedTag: '已更新的标签',
+ importColumnUpdatedDescription: '已更新描述',
expensesWith: '针对以下费用:',
expensesExactlyMatching: '对于完全匹配以下条件的报销:',
applyUpdates: '应用这些更新:',
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
|
@truph01 this has been open for quite sometime, let's prioritize its review please 🙏 |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppScreen.Recording.2026-07-15.at.06.58.18.movAndroid: mWeb ChromeiOS: HybridAppScreen.Recording.2026-07-15.at.07.06.50.moviOS: mWeb SafariScreen.Recording.2026-07-15.at.07.04.57.movMacOS: Chrome / SafariScreen.Recording.2026-07-15.at.06.53.25.mov |
|
@youssef-lr Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
@youssef-lr All yours! |
|
🚧 NicolasBonet has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/NicolasBonet in version: 9.4.36-0 🚀
|
|
🤖 I reviewed the changes in this PR and help site updates are required. This PR adds a spreadsheet import flow for Merchant Rules (bulk-import via More > Import merchant rules on the Rules page), which the existing Merchant Rules article didn't cover. I've drafted the docs update. Draft help site PR: #96359 It adds a How to import Merchant Rules using a spreadsheet section to @MelvinBot, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR |
|
@NicolasBonet can you please share the Csv file? |
|
@kavimuru You can try it #88156 (comment) |
|
Deploy Blocker #96389 was identified to be related to this PR. |
|
Deploy Blocker #96391 was identified to be related to this PR. |
Explanation of Change
Adds a spreadsheet-import flow for workspace merchant rules, so admins can bulk-import rules instead of creating them one at a time. This reuses the shared import infrastructure (
ImportSpreadsheet,ImportSpreadsheetColumns,useImportSpreadsheetConfirmModal) that Members/Categories/Tags/Per Diem already use.rulesRevampbeta) a "More" dropdown with "Import spreadsheet" next to the "Add rule" button on the Expense defaults tab.ImportMerchantRulesPage(upload) andImportedMerchantRulesPage(column mapping) underworkspaces/:policyID/rules/merchant-rules/import|imported, both gated by admin + paid policy + rules feature.findColumnName. Custom validation requires at least one merchant filter column plus at least one update column (new locale string for the conditional error).eqfilter and "Merchant contains" to acontainsfilter ("Merchant is" wins when both cells are filled on the same row); the description runs throughParser.replace()likesetPolicyCodingRuledoes; Reimbursable/Billable parsetrue/yes/false/noand are left unset otherwise; rows without a merchant filter value are skipped.ImportMerchantRulesSpreadsheetside-effect command sending{policyID, rules}whererulesis keyed by client-generated ruleIDs, so retried requests merge the same rules instead of duplicating them. The server result drives the shared final confirmation modal.The backend command lands in companion PRs: Auth and Web-Expensify.
Holding for
https://github.com/Expensify/Web-Expensify/pull/54176
Fixed Issues
$ #88156
PROPOSAL: #88156 (comment)
Tests
Merchant is, Merchant contains, Updated merchant, Updated category, Updated description, Reimbursable, Billableand a few data rows.rulesRevampbeta enabled, open the Expense defaults tab and verify the "More" dropdown next to "Add rule" contains "Import spreadsheet" and opens the same flow.Offline tests
QA Steps
Same as tests. Note: requires the backend
ImportMerchantRulesSpreadsheetcommand to be deployed (Auth + Web-Expensify companion PRs).PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)Avatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps./** comment above it */thisproperly so there are no scoping issues (i.e. foronClick={this.submit}the methodthis.submitshould be bound tothisin the constructor)thisare necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);ifthis.submitis never passed to a component event handler likeonClick)Full flow working
CleanShot.2026-07-02.at.13.06.37.mp4
With the latest change of removing the card and the option moved to the menu: