Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions docs/articles/new-expensify/settings/Personal-Expense-Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ To create a new Expense Rule:
- Reimbursable
- Billable
- Add to a report
5. Click or tap **Save rule**
5. (Optional) Enable **Apply to existing unsubmitted expenses** to also apply the rule to matching expenses that haven't been submitted yet
6. Click or tap **Save rule**

**Note:** Merchant names are case-sensitive.

Expand All @@ -63,9 +64,10 @@ To modify an existing Expense Rule:
1. Go to **Account > Expense Rules**
2. Click or tap the rule you want to update
3. Edit the fields you want to change
4. Click or tap **Save rule**
4. (Optional) Enable **Apply to existing unsubmitted expenses** to also apply the updated rule to matching expenses that haven't been submitted yet
5. Click or tap **Save rule**

Changes apply to future matching expenses.
Changes apply to future matching expenses, plus existing unsubmitted expenses when the toggle is enabled.

---

Expand All @@ -92,7 +94,7 @@ Expense Rules work as follows:
- If multiple rules match, they apply from top to bottom
- Personal Expense Rules override [Workspace Merchant Rules](https://help.expensify.com/articles/new-expensify/workspaces/Workspace-Merchant-Rules)

Rules do not apply retroactively unless you manually update existing expenses.
Rules apply retroactively to unsubmitted expenses only when you enable **Apply to existing unsubmitted expenses** while saving a rule. Submitted expenses need to be edited manually.

---

Expand All @@ -118,7 +120,7 @@ Personal Expense Rules are account-based. If you’ve used both experiences, you

## Can I apply an Expense Rule to existing expenses?

No. Expense Rules apply when expenses are created. They do not automatically update past expenses. You’ll need to manually edit existing expenses if needed.
Yes, for expenses that haven't been submitted yet. When creating or editing a rule, enable **Apply to existing unsubmitted expenses** before saving. Expenses that have already been submitted are not updated — you'll need to edit those manually if needed.

## What happens if my personal rule conflicts with a Workspace Merchant Rule?

Expand Down
2 changes: 1 addition & 1 deletion src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3148,7 +3148,7 @@ ${amount} für ${merchant} – ${date}`,
merchantHint: 'Gib . ein, um eine Regel zu erstellen, die für alle Händler gilt',
addToReport: 'Zu einem Bericht mit dem Namen hinzufügen',
createReport: 'Bericht bei Bedarf erstellen',
applyToExistingExpenses: 'Auf passende vorhandene Ausgaben anwenden',
applyToExistingExpenses: 'Auf bestehende nicht eingereichte Ausgaben anwenden',
confirmError: 'Gib ein Händlerunternehmen ein und nimm mindestens eine Aktualisierung vor',
confirmErrorMerchant: 'Bitte Händler eingeben',
confirmErrorUpdate: 'Bitte wende mindestens ein Update an',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3202,7 +3202,7 @@ ${amount} για ${merchant} - ${date}`,
merchantHint: 'Πληκτρολογήστε . για να δημιουργήσετε έναν κανόνα που ισχύει για όλους τους εμπόρους',
addToReport: 'Προσθήκη σε αναφορά με όνομα',
createReport: 'Δημιουργήστε αναφορά αν χρειάζεται',
applyToExistingExpenses: 'Εφαρμογή σε υπάρχουσες αντιστοιχισμένες δαπάνες',
applyToExistingExpenses: 'Εφαρμογή σε υφιστάμενες μη υποβληθείσες δαπάνες',
confirmError: 'Εισαγάγετε τον έμπορο και εφαρμόστε τουλάχιστον μία ενημέρωση',
confirmErrorMerchant: 'Παρακαλούμε εισαγάγετε τον έμπορο',
confirmErrorUpdate: 'Παρακαλούμε εφαρμόστε τουλάχιστον μία ενημέρωση',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3246,7 +3246,7 @@ const translations = {
merchantHint: 'Type . to create a rule that applies to all merchants',
addToReport: 'Add to a report named',
createReport: 'Create report if necessary',
applyToExistingExpenses: 'Apply to existing matching expenses',
applyToExistingExpenses: 'Apply to existing unsubmitted expenses',
confirmError: 'Enter merchant and apply at least one update',
confirmErrorMerchant: 'Please enter merchant',
confirmErrorUpdate: 'Please apply at least one update',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3056,7 +3056,7 @@ ${amount} para ${merchant} - ${date}`,
merchantHint: 'Escribe . para crear una regla que se aplique a todos los comercios',
addToReport: 'Añadir a un informe llamado',
createReport: 'Crear informe si es necesario',
applyToExistingExpenses: 'Aplicar a gastos existentes que coincidan',
applyToExistingExpenses: 'Aplicar a gastos existentes no enviados',
confirmError: 'Introduce el comercio y aplica al menos una actualización',
confirmErrorMerchant: 'Por favor, introduce el comercio',
confirmErrorUpdate: 'Por favor, aplica al menos una actualización',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3155,7 +3155,7 @@ ${amount} pour ${merchant} - ${date}`,
merchantHint: 'Tapez . pour créer une règle qui s’applique à tous les marchands',
addToReport: 'Ajouter à une note de frais nommée',
createReport: 'Créer une note de frais si nécessaire',
applyToExistingExpenses: 'Appliquer aux dépenses correspondantes existantes',
applyToExistingExpenses: 'Appliquer aux dépenses non soumises existantes',
confirmError: 'Saisissez un commerçant et appliquez au moins une mise à jour',
confirmErrorMerchant: 'Veuillez saisir le commerçant',
confirmErrorUpdate: 'Veuillez appliquer au moins une mise à jour',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3138,7 +3138,7 @@ ${amount} per ${merchant} - ${date}`,
merchantHint: 'Digita . per creare una regola che si applica a tutti gli esercenti',
addToReport: 'Aggiungi a un report chiamato',
createReport: 'Crea il report se necessario',
applyToExistingExpenses: 'Applica alle spese corrispondenti esistenti',
applyToExistingExpenses: 'Applica alle spese esistenti non inviate',
confirmError: 'Inserisci l’esercente e applica almeno un aggiornamento',
confirmErrorMerchant: 'Inserisci l’esercente',
confirmErrorUpdate: 'Applica almeno un aggiornamento',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3110,7 +3110,7 @@ ${date} の ${merchant} への ${amount}`,
merchantHint: '. を入力して、すべての加盟店に適用されるルールを作成します',
addToReport: '名前が次のレポートに追加',
createReport: '必要に応じてレポートを作成',
applyToExistingExpenses: '既存の一致する経費に適用',
applyToExistingExpenses: '既存の未提出経費に適用',
confirmError: '店舗名を入力し、少なくとも1つの更新を適用してください',
confirmErrorMerchant: '加盟店を入力してください',
confirmErrorUpdate: '少なくとも 1 つの更新を適用してください',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3140,7 +3140,7 @@ ${amount} voor ${merchant} - ${date}`,
merchantHint: 'Typ . om een regel te maken die van toepassing is op alle aanbieders',
addToReport: 'Toevoegen aan een rapport met de naam',
createReport: 'Maak indien nodig een rapport',
applyToExistingExpenses: 'Toepassen op bestaande overeenkomende uitgaven',
applyToExistingExpenses: 'Toepassen op bestaande niet-ingediende uitgaven',
confirmError: 'Voer handelaar in en pas minstens één wijziging toe',
confirmErrorMerchant: 'Voer handelaar in',
confirmErrorUpdate: 'Pas ten minste één wijziging toe',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3167,7 +3167,7 @@ ${amount} dla ${merchant} - ${date}`,
merchantHint: 'Wpisz . , aby utworzyć regułę, która będzie miała zastosowanie do wszystkich sprzedawców',
addToReport: 'Dodaj do raportu o nazwie',
createReport: 'Utwórz raport w razie potrzeby',
applyToExistingExpenses: 'Zastosuj do pasujących istniejących wydatków',
applyToExistingExpenses: 'Zastosuj do istniejących niewysłanych wydatków',
confirmError: 'Wprowadź sprzedawcę i zastosuj co najmniej jedną zmianę',
confirmErrorMerchant: 'Wprowadź sprzedawcę',
confirmErrorUpdate: 'Zastosuj co najmniej jedną aktualizację',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3130,7 +3130,7 @@ ${amount} para ${merchant} - ${date}`,
merchantHint: 'Digite . para criar uma regra que se aplique a todos os estabelecimentos',
addToReport: 'Adicionar a um relatório chamado',
createReport: 'Criar relatório se necessário',
applyToExistingExpenses: 'Aplicar às despesas correspondentes existentes',
applyToExistingExpenses: 'Aplicar às despesas existentes não enviadas',
confirmError: 'Insira o comerciante e aplique pelo menos uma atualização',
confirmErrorMerchant: 'Insira o comerciante',
confirmErrorUpdate: 'Aplicar pelo menos uma atualização',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3033,7 +3033,7 @@ ${amount},商户:${merchant} - 日期:${date}`,
merchantHint: '输入 . 可创建适用于所有商家的规则',
addToReport: '添加到报表,名称为',
createReport: '如有必要则创建报表',
applyToExistingExpenses: '应用到现有匹配报销费用',
applyToExistingExpenses: '应用到现有未提交的报销费用',
confirmError: '输入商户并应用至少一项更新',
confirmErrorMerchant: '请输入商户',
confirmErrorUpdate: '请至少应用一个更新',
Expand Down
12 changes: 12 additions & 0 deletions src/libs/API/parameters/SetPersonalExpenseRulesParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type SetPersonalExpenseRulesParams = {
/** Stringified JSON of the full personal expense rules array */
value: string;

/** Whether the saved rule should be applied to the user's existing unsubmitted expenses */
shouldUpdateMatchingTransactions: boolean;

/** Stringified JSON of the created or edited rule to apply (empty when nothing should be applied) */
ruleToApply: string;
};

export default SetPersonalExpenseRulesParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ export type {default as EditTaskParams} from './EditTaskParams';
export type {default as ReopenTaskParams} from './ReopenTaskParams';
export type {default as CompleteTaskParams} from './CompleteTaskParams';
export type {default as SetNameValuePairParams} from './SetNameValuePairParams';
export type {default as SetPersonalExpenseRulesParams} from './SetPersonalExpenseRulesParams';
export type {default as SetNameValuePairsParams} from './SetNameValuePairsParams';
export type {default as SetReportFieldParams} from './SetReportFieldParams';
export type {default as SetReportNameParams} from './SetReportNameParams';
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ const WRITE_COMMANDS = {
COMPLETE_GUIDED_SETUP: 'CompleteGuidedSetup',
SET_NAME_VALUE_PAIR: 'SetNameValuePair',
SET_NAME_VALUE_PAIRS: 'SetNameValuePairs',
SET_PERSONAL_EXPENSE_RULES: 'SetPersonalExpenseRules',
SET_REPORT_DETAILS_COLUMNS: 'SetReportDetailsColumns',
SET_REPORT_FIELD: 'Report_SetFields',
DELETE_REPORT_FIELD: 'RemoveReportField',
Expand Down Expand Up @@ -914,6 +915,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.COMPLETE_GUIDED_SETUP]: Parameters.CompleteGuidedSetupParams;
[WRITE_COMMANDS.SET_NAME_VALUE_PAIR]: Parameters.SetNameValuePairParams;
[WRITE_COMMANDS.SET_NAME_VALUE_PAIRS]: Parameters.SetNameValuePairsParams;
[WRITE_COMMANDS.SET_PERSONAL_EXPENSE_RULES]: Parameters.SetPersonalExpenseRulesParams;
[WRITE_COMMANDS.SET_REPORT_DETAILS_COLUMNS]: Parameters.SetReportDetailsColumnsParams;
[WRITE_COMMANDS.SET_REPORT_FIELD]: Parameters.SetReportFieldParams;
[WRITE_COMMANDS.SET_REPORT_NAME]: Parameters.SetReportNameParams;
Expand Down
23 changes: 16 additions & 7 deletions src/libs/actions/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
RevokeDeviceParams,
SetContactMethodAsDefaultParams,
SetNameValuePairParams,
SetPersonalExpenseRulesParams,
TogglePlatformMuteParams,
UpdateChatPriorityModeParams,
UpdateNewsletterSubscriptionParams,
Expand Down Expand Up @@ -1689,9 +1690,10 @@ function deleteExpenseRules(expenseRules: ExpenseRule[], selectedRuleKeys: strin
return rule;
});

const parameters: SetNameValuePairParams = {
name: ONYXKEYS.NVP_EXPENSE_RULES,
const parameters: SetPersonalExpenseRulesParams = {
value: JSON.stringify(rulesForAPI),
shouldUpdateMatchingTransactions: false,
ruleToApply: '',
};

const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.NVP_EXPENSE_RULES>> = [
Expand All @@ -1718,7 +1720,7 @@ function deleteExpenseRules(expenseRules: ExpenseRule[], selectedRuleKeys: strin
},
];

API.write(WRITE_COMMANDS.SET_NAME_VALUE_PAIR, parameters, {
API.write(WRITE_COMMANDS.SET_PERSONAL_EXPENSE_RULES, parameters, {
optimisticData,
successData,
failureData,
Expand Down Expand Up @@ -1748,7 +1750,13 @@ function clearExpenseRuleErrors(expenseRules: ExpenseRule[], selectedRuleKey: st
Onyx.set(ONYXKEYS.NVP_EXPENSE_RULES, updatedExpenseRules);
}

function saveExpenseRule(expenseRules: ExpenseRule[], newRule: ExpenseRule, existingRuleKey: string | undefined, getKeyForRule: (rule: ExpenseRule) => string) {
function saveExpenseRule(
expenseRules: ExpenseRule[],
newRule: ExpenseRule,
existingRuleKey: string | undefined,
getKeyForRule: (rule: ExpenseRule) => string,
shouldUpdateMatchingTransactions = false,
) {
const isEditing = !!existingRuleKey;
const pendingAction = isEditing ? CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE : CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD;

Expand Down Expand Up @@ -1802,9 +1810,10 @@ function saveExpenseRule(expenseRules: ExpenseRule[], newRule: ExpenseRule, exis
.filter((rule) => rule.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)
.map(({pendingAction: _pendingAction, errors: _errors, ...rule}) => rule);

const parameters: SetNameValuePairParams = {
name: ONYXKEYS.NVP_EXPENSE_RULES,
const parameters: SetPersonalExpenseRulesParams = {
value: JSON.stringify(rulesForAPI),
shouldUpdateMatchingTransactions,
ruleToApply: shouldUpdateMatchingTransactions ? JSON.stringify(newRule) : '',
};

const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.NVP_EXPENSE_RULES>> = [
Expand All @@ -1831,7 +1840,7 @@ function saveExpenseRule(expenseRules: ExpenseRule[], newRule: ExpenseRule, exis
},
];

API.write(WRITE_COMMANDS.SET_NAME_VALUE_PAIR, parameters, {
API.write(WRITE_COMMANDS.SET_PERSONAL_EXPENSE_RULES, parameters, {
optimisticData,
successData,
failureData,
Expand Down
13 changes: 12 additions & 1 deletion src/pages/settings/Rules/RulePageBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ function RulePageBase({titleKey, testID, hash}: RulePageBaseProps) {
// Cannot use useRef because react compiler fails
const [isSaving, setIsSaving] = useState(false);
const [shouldShowError, setShouldShowError] = useState(false);
const [shouldUpdateMatchingTransactions, setShouldUpdateMatchingTransactions] = useState(false);
const {isLoading, startWithLoading} = usePressLoading({isLoading: isSaving});
const styles = useThemeStyles();

Expand Down Expand Up @@ -149,7 +150,7 @@ function RulePageBase({titleKey, testID, hash}: RulePageBaseProps) {
setIsSaving(true);

const newRule = extractRuleFromForm(form, selectedTaxRate);
saveExpenseRule(expenseRules, newRule, hash, getKeyForRule);
saveExpenseRule(expenseRules, newRule, hash, getKeyForRule, shouldUpdateMatchingTransactions);

Navigation.goBack();
});
Expand Down Expand Up @@ -290,6 +291,16 @@ function RulePageBase({titleKey, testID, hash}: RulePageBaseProps) {
isLoading={isLoading}
shouldShowLoadingImmediatelyOnPress={false}
enabledWhenOffline
shouldRenderFooterAboveSubmit
footerContent={
<ToggleSettingOptionRow
isActive={shouldUpdateMatchingTransactions}
onToggle={setShouldUpdateMatchingTransactions}
switchAccessibilityLabel={translate('expenseRulesPage.addRule.applyToExistingExpenses')}
title={translate('expenseRulesPage.addRule.applyToExistingExpenses')}
wrapperStyle={styles.mb4}
/>
}
/>
</ScreenWrapper>
</RuleNotFoundPageWrapper>
Expand Down
Loading