-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Create merchant rule #80545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create merchant rule #80545
Conversation
- Add routes for merchant rules pages in ROUTES.ts - Add SCREENS for merchant rules in SCREENS.ts - Add navigation types and linking config - Create MERCHANT_RULES.FIELDS constants - Create MerchantRuleForm type and Onyx keys - Create AddMerchantRulePage main component - Create reusable MerchantRuleTextBasePage and MerchantRuleBooleanBasePage - Create field pages: AddMerchantToMatchPage, AddMerchantPage, AddCategoryPage, AddTagPage, AddTaxPage, AddDescriptionPage, AddReimbursablePage, AddBillablePage - Add translations for merchant rules - Update MerchantRulesSection to navigate to AddMerchantRulePage
🦜 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 6d54352c..e9c5bed3 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -6326,6 +6326,14 @@ Fordere Spesendetails wie Belege und Beschreibungen an, lege Limits und Standard
ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Aktualisiere ${fieldName} zu „${fieldValue}“`,
ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Als "${reimbursable ? 'erstattungsfähig' : 'nicht erstattungsfähig'}" markieren`,
ruleSummarySubtitleBillable: (billable: boolean) => `Als „${billable ? 'Abrechenbar' : 'nicht abrechenbar'}“ markieren`,
+ addRuleTitle: 'Regel hinzufügen',
+ expensesWith: 'Für Ausgaben mit:',
+ applyUpdates: 'Diese Updates anwenden:',
+ merchantHint: 'Einem Händlernamen mit groß-/kleinschreibungsunabhängiger „Enthält“-Übereinstimmung zuordnen',
+ saveRule: 'Regel speichern',
+ confirmError: 'Geben Sie den Händler ein und nehmen Sie mindestens eine Änderung vor',
+ confirmErrorMerchant: 'Bitte geben Sie den Händler ein',
+ confirmErrorUpdate: 'Bitte wenden Sie mindestens eine Aktualisierung an',
},
},
planTypePage: {
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 7a781bc2..001c14e0 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -6337,6 +6337,14 @@ Exigez des informations de dépense comme les reçus et les descriptions, défin
ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Mettre à jour ${fieldName} sur « ${fieldValue} »`,
ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Marquer comme « ${reimbursable ? 'remboursable' : 'non remboursable'} »`,
ruleSummarySubtitleBillable: (billable: boolean) => `Marquer comme « ${billable ? 'facturable' : 'non facturable'} »`,
+ addRuleTitle: 'Ajouter une règle',
+ expensesWith: 'Pour les dépenses avec :',
+ applyUpdates: 'Appliquer ces mises à jour :',
+ merchantHint: 'Faire correspondre un nom de commerçant avec une correspondance « contient » insensible à la casse',
+ saveRule: 'Enregistrer la règle',
+ confirmError: 'Saisissez un marchand et appliquez au moins une mise à jour',
+ confirmErrorMerchant: 'Veuillez saisir le commerçant',
+ confirmErrorUpdate: 'Veuillez appliquer au moins une mise à jour',
},
},
planTypePage: {
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 97ce434b..c5bc724f 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -6310,6 +6310,14 @@ Richiedi dettagli di spesa come ricevute e descrizioni, imposta limiti e valori
ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Aggiorna ${fieldName} a "${fieldValue}"`,
ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Segna come "${reimbursable ? 'rimborsabile' : 'non rimborsabile'}"`,
ruleSummarySubtitleBillable: (billable: boolean) => `Contrassegna come "${billable ? 'fatturabile' : 'non fatturabile'}"`,
+ addRuleTitle: 'Aggiungi regola',
+ expensesWith: 'Per spese con:',
+ applyUpdates: 'Applica questi aggiornamenti:',
+ merchantHint: 'Abbina un nome commerciante con una corrispondenza "contiene" che non distingue tra maiuscole e minuscole',
+ saveRule: 'Salva regola',
+ confirmError: 'Inserisci l’esercente e applica almeno un aggiornamento',
+ confirmErrorMerchant: 'Per favore inserisci l’esercente',
+ confirmErrorUpdate: 'Applica almeno un aggiornamento',
},
},
planTypePage: {
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 0a70a593..566b65b9 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -6267,6 +6267,14 @@ ${reportName}
ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `${fieldName} を「${fieldValue}」に更新`,
ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `「${reimbursable ? '払い戻し対象' : '精算対象外'}」としてマーク`,
ruleSummarySubtitleBillable: (billable: boolean) => `「${billable ? '請求可能' : '請求対象外'}」としてマーク`,
+ addRuleTitle: 'ルールを追加',
+ expensesWith: '次の条件の経費について:',
+ applyUpdates: 'これらの更新を適用:',
+ merchantHint: '大文字小文字を区別しない「含む」一致で支払先名を照合する',
+ saveRule: 'ルールを保存',
+ confirmError: '支払先を入力し、少なくとも 1 つの更新を適用してください',
+ confirmErrorMerchant: '商人を入力してください',
+ confirmErrorUpdate: '少なくとも 1 件の更新を適用してください',
},
},
planTypePage: {
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index a4421c1d..b8265167 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -6296,6 +6296,14 @@ Vraag verplichte uitgavedetails zoals bonnetjes en beschrijvingen, stel limieten
ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Werk ${fieldName} bij naar "${fieldValue}"`,
ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Markeren als "${reimbursable ? 'Vergoedbaar' : 'niet-vergoedbaar'}"`,
ruleSummarySubtitleBillable: (billable: boolean) => `Markeren als "${billable ? 'factureerbaar' : 'niet-factureerbaar'}"`,
+ addRuleTitle: 'Regel toevoegen',
+ expensesWith: 'Voor uitgaven met:',
+ applyUpdates: 'Deze updates toepassen:',
+ merchantHint: 'Een handelsnaam koppelen met hoofdletterongevoelige "bevat"-overeenkomst',
+ saveRule: 'Regel opslaan',
+ confirmError: 'Voer een leverancier in en pas ten minste één wijziging toe',
+ confirmErrorMerchant: 'Voer handelaar in',
+ confirmErrorUpdate: 'Breng ten minste één wijziging aan alstublieft',
},
},
planTypePage: {
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 28a17b01..f7eb3667 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -6290,6 +6290,14 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i
ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Zaktualizuj ${fieldName} na „${fieldValue}”`,
ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Oznacz jako "${reimbursable ? 'kwalifikujący się do zwrotu kosztów' : 'niepodlegający zwrotowi'}"`,
ruleSummarySubtitleBillable: (billable: boolean) => `Oznacz jako „${billable ? 'fakturowalne' : 'poza fakturą'}”`,
+ addRuleTitle: 'Dodaj regułę',
+ expensesWith: 'Dla wydatków z:',
+ applyUpdates: 'Zastosuj te aktualizacje:',
+ merchantHint: 'Dopasuj nazwę sprzedawcy przy użyciu nieczułego na wielkość liter dopasowania typu „zawiera”',
+ saveRule: 'Zapisz regułę',
+ confirmError: 'Wprowadź sprzedawcę i zastosuj co najmniej jedną aktualizację',
+ confirmErrorMerchant: 'Wprowadź sprzedawcę',
+ confirmErrorUpdate: 'Proszę wprowadzić co najmniej jedną zmianę',
},
},
planTypePage: {
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index ef12d476..fbf7733a 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -6291,6 +6291,14 @@ Exija detalhes de despesas como recibos e descrições, defina limites e padrõe
ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `Atualizar ${fieldName} para "${fieldValue}"`,
ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `Marcar como "${reimbursable ? 'reembolsável' : 'não reembolsável'}"`,
ruleSummarySubtitleBillable: (billable: boolean) => `Marcar como "${billable ? 'faturável' : 'não faturável'}"`,
+ addRuleTitle: 'Adicionar regra',
+ expensesWith: 'Para despesas com:',
+ applyUpdates: 'Aplicar estas atualizações:',
+ merchantHint: 'Corresponder um nome de comerciante com correspondência "contém" sem diferenciação entre maiúsculas e minúsculas',
+ saveRule: 'Salvar regra',
+ confirmError: 'Insira o estabelecimento e aplique pelo menos uma atualização',
+ confirmErrorMerchant: 'Insira o comerciante',
+ confirmErrorUpdate: 'Por favor, aplique pelo menos uma atualização',
},
},
planTypePage: {
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 5c997de1..289129a8 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -6157,6 +6157,14 @@ ${reportName}
ruleSummarySubtitleUpdateField: (fieldName: string, fieldValue: string) => `将 ${fieldName} 更新为“${fieldValue}”`,
ruleSummarySubtitleReimbursable: (reimbursable: boolean) => `标记为“${reimbursable ? '可报销' : '不予报销'}”`,
ruleSummarySubtitleBillable: (billable: boolean) => `标记为“${billable ? '可计费' : '不可计费'}”`,
+ addRuleTitle: '添加规则',
+ expensesWith: '对于以下费用:',
+ applyUpdates: '应用这些更新:',
+ merchantHint: '使用不区分大小写的“包含”匹配来匹配商户名称',
+ saveRule: '保存规则',
+ confirmError: '输入商家并至少应用一项更新',
+ confirmErrorMerchant: '请输入商家',
+ confirmErrorUpdate: '请至少进行一次更新',
},
},
planTypePage: {
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
- Create SetPolicyMerchantRuleParams type - Add SET_POLICY_MERCHANT_RULE write command - Implement setPolicyMerchantRule action with optimistic updates - Call API from AddMerchantRulePage on submit
- Remove default values for policyID (rulesdir/no-default-id-values) - Use specific form properties in useMemo dependencies instead of form object - Replace forEach with for...of loops in AddTagPage - Use early return pattern in AddTagPage
- Remove incorrect '@src/Onyx' import - Use inline object pattern instead of explicit OnyxData type
- Add parseStringBoolean helper to avoid nested ternaries - Add OnyxUpdate[] type annotations to fix unsafe argument error
- Use OnyxData<typeof ONYXKEYS.COLLECTION.POLICY> type pattern - Consolidate optimisticData, successData, failureData into single onyxData object
Use 'rules' instead of 'codingRules' as the pending field key since codingRules is a nested property
Fix cspell unknown word error for 'Billables'
|
@shubham1206agra 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] |
|
@mjasikowski 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] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6d7aa25d46
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
The latest commits caused regression on boolean fields Screen.Recording.2026-01-28.at.3.50.51.AM.mov |
|
@situchan should be fixed now |
|
Still issue on both personal rule and merchant rule Screen.Recording.2026-01-28.at.4.02.48.AM.movUnselect doesn't work Screen.Recording.2026-01-28.at.4.03.53.AM.mov |
|
Updated |
| /** Whether to use string values ('true'/'false') instead of boolean values (for ExpenseRuleForm compatibility) */ | ||
| useStringValues?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any plan to use real boolean for personal rule? Or we always use "true", "false" string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not right now because that'd involve migrating data we have stored, which is not a priority
|
@chiragsalian 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] |
|
✋ 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/luacmartins in version: 9.3.11-0 🚀
|
|
Hello @luacmartins , Can we test this feature in the staging environment? we found this note here
|
|
@mitarachim no, not yet |
|
@luacmartins so, is this an internal PR with no QA involved? |
|
@IuliiaHerets correct |
Explanation of Change
Adds the ability to create a merchant rule. Includes multiple new pages that allow users to edit what merchant to match and what update to apply to it (rename merchant, update category, tag, tax, description, billable and reimbursable)
Fixed Issues
$ #80520
Tests
Pre-condition: workspace with rules enabled
Workspace > Rules > MerchantAdd merchant rulecategory, tag,taxes,billable` only shows up if those features are enabled in the policySetPolicyMerchantRuleWorkspace > Rules > MerchantNOTE: The BE is still not ready and this page is only visible in dev for now.
Offline tests
N/A
QA Steps
N/A
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, 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.ScrollViewcomponent to make it scrollable when more elements are added to the page.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)Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari