Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
dbaf171
Add rules engine types and merchant rule AST helpers
Krishna2323 Sep 4, 2026
9d523ff
Fix knip and jest typecheck findings
Krishna2323 Sep 4, 2026
94f6362
Keep expense default rules when approvals are disabled
Krishna2323 Sep 4, 2026
18b1291
Read and write merchant rules through the rules engine
Krishna2323 Sep 4, 2026
e65b234
Copy and import merchant rules through the rules collection
Krishna2323 Sep 4, 2026
0edfd62
Update tests for the rules engine migration
Krishna2323 Sep 4, 2026
135b8e3
Merge upstream/main, keeping the rules engine storage under the new c…
Krishna2323 Sep 6, 2026
87009d5
Fix tests missed by the rules engine migration
Krishna2323 Sep 7, 2026
2acfff5
Assert merchant rule optimistic state with the request paused
Krishna2323 Sep 7, 2026
836b5b6
Match existing patterns for the rules collection reads and fetch trig…
Krishna2323 Sep 7, 2026
6048090
Reduce rules collection reads to what each consumer uses
Krishna2323 Sep 7, 2026
30420f2
Derive merchant rule condition text from the filter tree
Krishna2323 Sep 7, 2026
efb92da
Merge branch 'main' into krishna2323/issue-100300
Krishna2323 Sep 8, 2026
76a721a
fix ESLint and tests.
Krishna2323 Sep 8, 2026
7e1a7a5
Merge main into krishna2323/issue-100300
Krishna2323 Sep 9, 2026
cbf5092
Refuse to open a merchant rule the editor would overwrite
Krishna2323 Sep 9, 2026
4fbbe46
Resolve merchant rule tax defaults against the live rate
Krishna2323 Sep 9, 2026
227835e
Fetch rules where the merchant rule count is consumed, and throttle G…
Krishna2323 Sep 9, 2026
780e516
Use getByTestId for the guard test's presence assertion
Krishna2323 Sep 9, 2026
9cd0ae4
Drop the GetRules freshness window
Krishna2323 Sep 9, 2026
a41f492
Fetch the rules collection through a prefetch hook
Krishna2323 Sep 9, 2026
04cc8f1
Render the merchant rule guard test inside a navigator
Krishna2323 Sep 9, 2026
413e7c1
Add hasRulesDataBeenFetched to the Onyx export safe keys bucket
Krishna2323 Sep 9, 2026
15a9a44
merge main and resolve conflicts.
Krishna2323 Sep 10, 2026
eee1393
Write merchant rules through SetPolicyCodingRule instead of SetRule
Krishna2323 Sep 10, 2026
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
22 changes: 22 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8443,6 +8443,7 @@ const CONST = {
RULES: {
SCOPE: {
POLICY: 'policy',
ACCOUNT: 'account',
},
APPROVAL_WORKFLOW: {
TRIGGER: {
Expand All @@ -8454,6 +8455,27 @@ const CONST = {
APPROVE_REPORT: 'ApproveReport',
},
},
EXPENSE_DEFAULT: {
TRIGGER: {
CREATE_TRANSACTION: 'CreateTransaction',
},
ACTION: {
SET: 'Set',
},
/** Expense fields a `Set` action can write to */
FIELD: {
BILLABLE: 'billable',
CATEGORY: 'category',
COMMENT: 'comment',
MERCHANT: 'merchant',
REIMBURSABLE: 'reimbursable',
TAG: 'tag',
TAX: 'tax',
VENDOR_ID: 'vendorID',
},
/** Every expense default rule is created with the same priority, per the rules engine spec */
PRIORITY: 10000,
},
},

BOOT_SPLASH_STATE: {
Expand Down
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,9 @@ const ONYXKEYS = {
/** Set whether the search filters category data has loaded */
IS_SEARCH_FILTERS_CATEGORY_DATA_LOADED: 'isSearchFiltersCategoryDataLoaded',

/** Set once `GetRules` has answered, so screens that only consume the rules collection fetch it once */
HAS_RULES_DATA_BEEN_FETCHED: 'hasRulesDataBeenFetched',

/** Set while search filter category data is loading */
RAM_ONLY_IS_LOADING_SEARCH_FILTERS_CATEGORY_DATA: 'isLoadingSearchFiltersCategoryData',

Expand Down Expand Up @@ -1716,6 +1719,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.IS_LOADING_REPORT_DATA]: boolean;
[ONYXKEYS.IS_SEARCH_FILTERS_CARD_DATA_LOADED]: boolean;
[ONYXKEYS.IS_SEARCH_FILTERS_CATEGORY_DATA_LOADED]: boolean;
[ONYXKEYS.HAS_RULES_DATA_BEEN_FETCHED]: boolean;
[ONYXKEYS.RAM_ONLY_IS_LOADING_SEARCH_FILTERS_CATEGORY_DATA]: boolean;
[ONYXKEYS.IS_LOADING_SUBSCRIPTION_DATA]: boolean;
[ONYXKEYS.IS_PENDING_UPDATE_PERSONAL_KARMA]: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ function WorkspaceExpenseDefaultsTableRow({item, rowIndex, shouldUseNarrowTableL
<Table.Row
interactive
rowIndex={rowIndex}
disabled={isDeleting}
disabled={isDeleting || !!item.disabled}
accessibilityLabel={accessibilityLabel}
sentryLabel={SECTION_SENTRY_LABELS[item.section]}
offlineWithFeedback={{
Expand Down
35 changes: 35 additions & 0 deletions src/hooks/useRulesPrefetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {getRules} from '@libs/actions/Policy/Rules';

import ONYXKEYS from '@src/ONYXKEYS';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';

import {useEffect} from 'react';

import useNetwork from './useNetwork';
import useOnyx from './useOnyx';

/**
* Fetches the `rules_` collection for a screen that reads it without opening a workspace.
*
* Merchant rules used to arrive on the policy, so anything holding a policy could read them. They now
* live in their own collection that only `GetRules` populates, which leaves screens reached from the
* workspaces list or from Home reading an empty collection. Where that collection decides whether an
* option is offered at all, an empty read silently drops the rules rather than showing a stale count.
*
* `GetRules` takes no parameters and its response SETs the whole collection, so this fetches once per
* session rather than on every mount. The shared guards live here so callers cannot drift apart.
*/
function useRulesPrefetch(enabled = true) {
const {isOffline} = useNetwork();
const [hasBeenFetched, hasBeenFetchedResult] = useOnyx(ONYXKEYS.HAS_RULES_DATA_BEEN_FETCHED);
const isFetchNeeded = enabled && !isLoadingOnyxValue(hasBeenFetchedResult) && !isOffline && !hasBeenFetched;

useEffect(() => {
if (!isFetchNeeded) {
return;
}
getRules();
}, [isFetchNeeded]);
}

export default useRulesPrefetch;
6 changes: 6 additions & 0 deletions src/libs/API/parameters/DeleteRuleParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type DeleteRuleParams = {
/** The ID of the rule to delete */
ruleID: string;
};

export default DeleteRuleParams;
21 changes: 21 additions & 0 deletions src/libs/API/parameters/SetRuleParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
type SetRuleParams = {
/** What kind of entity the rule is scoped to. Merchant rules are always scoped to a policy */
scope: string;

/** The ID of the scoped entity, i.e. the policyID for policy-scoped rules */
scopeID: string;

/** The ID of the rule being written. A new rule uses an optimistic `rand64()` value */
ruleID: string;

/** Determines the order rules are applied in when more than one matches */
priority: number;

/** The `{filters, triggers, actions}` body of the rule, stringified */
value: string;

/** Whether to apply the rule to the transactions that already match it */
shouldUpdateMatchingTransactions: boolean;
};

export default SetRuleParams;
2 changes: 2 additions & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,8 @@ export type {default as DeleteDomainParams} from './DeleteDomainParams';
export type {default as GetDuplicateTransactionDetailsParams} from './GetDuplicateTransactionDetailsParams';
export type {default as SetPolicyCategoryReceiptsAndItemizedReceiptRequiredParams} from './SetPolicyCategoryReceiptsAndItemizedReceiptRequiredParams';
export type {default as SetPolicyCodingRuleParams} from './SetPolicyCodingRuleParams';
export type {default as SetRuleParams} from './SetRuleParams';
export type {default as DeleteRuleParams} from './DeleteRuleParams';
export type {default as SetApprovalWorkflowParams} from './SetApprovalWorkflowParams';
export type {default as RegisterAuthenticationKeyParams} from './RegisterAuthenticationKeyParams';
export type {default as RevokeMultifactorAuthenticationCredentialsParams} from './RevokeMultifactorAuthenticationCredentialsParams';
Expand Down
6 changes: 6 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,8 @@ const WRITE_COMMANDS = {
SET_POLICY_TIME_TRACKING_DEFAULT_RATE: 'SetPolicyTimeTrackingDefaultRate',
SET_POLICY_RULES_ENABLED: 'SetPolicyRulesEnabled',
SET_POLICY_CODING_RULE: 'SetPolicyCodingRule',
SET_RULE: 'SetRule',
DELETE_RULE: 'DeleteRule',
SET_APPROVAL_WORKFLOW: 'SetApprovalWorkflow',
SET_POLICY_EXPENSE_MAX_AMOUNT_NO_RECEIPT: 'SetPolicyExpenseMaxAmountNoReceipt',
SET_POLICY_EXPENSE_MAX_AMOUNT_NO_ITEMIZED_RECEIPT: 'SetPolicyExpenseMaxAmountNoItemizedReceipt',
Expand Down Expand Up @@ -993,6 +995,8 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.ENABLE_POLICY_TIME_TRACKING]: Parameters.EnablePolicyTimeTrackingParams;
[WRITE_COMMANDS.SET_POLICY_RULES_ENABLED]: Parameters.SetPolicyRulesEnabledParams;
[WRITE_COMMANDS.SET_POLICY_CODING_RULE]: Parameters.SetPolicyCodingRuleParams;
[WRITE_COMMANDS.SET_RULE]: Parameters.SetRuleParams;
[WRITE_COMMANDS.DELETE_RULE]: Parameters.DeleteRuleParams;
[WRITE_COMMANDS.SET_APPROVAL_WORKFLOW]: Parameters.SetApprovalWorkflowParams;
[WRITE_COMMANDS.SET_POLICY_REQUIRE_COMPANY_CARDS_ENABLED]: Parameters.SetPolicyRequireCompanyCardsEnabledParams;
[WRITE_COMMANDS.SET_POLICY_CATEGORY_DESCRIPTION_REQUIRED]: Parameters.SetPolicyCategoryDescriptionRequiredParams;
Expand Down Expand Up @@ -1507,6 +1511,7 @@ const READ_COMMANDS = {
OPEN_POLICY_REPORT_FIELDS_PAGE: 'OpenPolicyReportFieldsPage',
OPEN_POLICY_INVOICES_PAGE: 'OpenPolicyInvoicesPage',
OPEN_POLICY_RULES_PAGE: 'OpenPolicyRulesPage',
GET_RULES: 'GetRules',
OPEN_POLICY_EXPENSIFY_CARDS_PAGE: 'OpenPolicyExpensifyCardsPage',
OPEN_POLICY_TRAVEL_PAGE: 'OpenPolicyTravelPage',
GET_TRAVEL_BILLING_STATEMENT_PDF: 'GetTravelBillingStatementPDF',
Expand Down Expand Up @@ -1620,6 +1625,7 @@ type ReadCommandParameters = {
[READ_COMMANDS.OPEN_POLICY_REPORT_FIELDS_PAGE]: Parameters.OpenPolicyReportFieldsPageParams;
[READ_COMMANDS.OPEN_POLICY_INVOICES_PAGE]: Parameters.OpenPolicyReportFieldsPageParams;
[READ_COMMANDS.OPEN_POLICY_RULES_PAGE]: Parameters.OpenPolicyRulesPageParams;
[READ_COMMANDS.GET_RULES]: EmptyObject;
[READ_COMMANDS.OPEN_WORKSPACE_INVITE_PAGE]: Parameters.OpenWorkspaceInvitePageParams;
[READ_COMMANDS.OPEN_DRAFT_WORKSPACE_REQUEST]: Parameters.OpenDraftWorkspaceRequestParams;
[READ_COMMANDS.OPEN_DRAFT_PER_DIEM_EXPENSE]: Parameters.OpenDraftPerDiemExpenseParams;
Expand Down
Loading
Loading