fix(rules): allow Smart Rules to set Transaction Type as Expense#515
fix(rules): allow Smart Rules to set Transaction Type as Expense#515saadaltabari wants to merge 1 commit into
Conversation
…pense Use proper TYPE operators and labels in the rule editor so Expense conditions and actions work reliably instead of failing with stale or invalid operators. Co-authored-by: Cursor <cursoragent@cursor.com>
Greptile SummaryThis PR fixes Smart Rules being unable to set or match
Confidence Score: 4/5The engine and repository changes are safe; the one active defect is that "Transaction Type is not X" conditions cannot be created through the UI because the type-value chip click overwrites the operator. The centralisation of TYPE helpers and the EXPENSE label fix work correctly end-to-end. The type-value chip app/src/main/java/com/pennywiseai/tracker/ui/screens/rules/CreateRuleScreen.kt — specifically the type-value chip onClick in ConditionFieldSelector (around line 874). Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User opens Create Rule] --> B{Condition field changed?}
B -->|Yes| C[condition.copy with defaultConditionOperator]
B -->|No| D{field == TYPE &&\noperator invalid?}
D -->|Yes| E[LaunchedEffect resets\noperator to EQUALS]
D -->|No| F[Operator unchanged]
C --> G[Show operator chips\nfrom conditionOperatorsForField]
E --> G
F --> G
G --> H{User clicks operator chip\neg NOT_EQUALS}
H --> I[condition.copy operator=NOT_EQUALS]
I --> J[Show TYPE value chips\nIncome / Expense / Credit ...]
J --> K{User clicks type value chip}
K -->|Current code| L["condition.copy(value=type,\noperator=EQUALS) BUG:\nNOT_EQUALS overwritten"]
K -->|Fixed code| M["condition.copy(value=type)\noperator preserved"]
L --> N[Rule saves with IS not IS NOT]
M --> O[Rule saves with IS NOT correctly]
O --> P[isRuleApplicableToTransactionType]
P --> Q[RuleEngine.evaluateRulesForType]
Q --> R[parseRuleTransactionType SET TYPE action]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[User opens Create Rule] --> B{Condition field changed?}
B -->|Yes| C[condition.copy with defaultConditionOperator]
B -->|No| D{field == TYPE &&\noperator invalid?}
D -->|Yes| E[LaunchedEffect resets\noperator to EQUALS]
D -->|No| F[Operator unchanged]
C --> G[Show operator chips\nfrom conditionOperatorsForField]
E --> G
F --> G
G --> H{User clicks operator chip\neg NOT_EQUALS}
H --> I[condition.copy operator=NOT_EQUALS]
I --> J[Show TYPE value chips\nIncome / Expense / Credit ...]
J --> K{User clicks type value chip}
K -->|Current code| L["condition.copy(value=type,\noperator=EQUALS) BUG:\nNOT_EQUALS overwritten"]
K -->|Fixed code| M["condition.copy(value=type)\noperator preserved"]
L --> N[Rule saves with IS not IS NOT]
M --> O[Rule saves with IS NOT correctly]
O --> P[isRuleApplicableToTransactionType]
P --> Q[RuleEngine.evaluateRulesForType]
Q --> R[parseRuleTransactionType SET TYPE action]
|
| LaunchedEffect(condition.field, condition.operator) { | ||
| if (condition.field == TransactionField.TYPE && condition.operator !in supportedOperators) { | ||
| onConditionChange(condition.copy(operator = ConditionOperator.EQUALS)) | ||
| } | ||
| } |
There was a problem hiding this comment.
LaunchedEffect operator-reset scope limited to TYPE field
The LaunchedEffect only resets the operator when condition.field == TransactionField.TYPE. If an existing rule was persisted with a different incompatible (field, operator) pair — for example ACCOUNT with CONTAINS — loading and editing it in the UI will silently render an operator chip that isn't in the allowed list for that field, with no automatic correction. Since field-selection in the dropdown already calls defaultConditionOperator(), dropping the TYPE-only check and applying broadly (reset whenever condition.operator !in supportedOperators) would make this guard generally safe.
|
Nice fix, and the |
|
Thanks @saadaltabari — the EXPENSE-type fix and helper centralisation look good (Greptile 4/5). One real thing it caught: in It's fallen behind Gentle housekeeping heads-up: if there's no update in ~a week we might close it just to keep things tidy — no rejection implied, happy to reopen the moment you're back on it. 🙏 |
Summary
Smart Rules could not reliably set or match Transaction Type → Expense. The rule editor showed “Outgoing” instead of “Expense”, kept incompatible operators when switching from Amount to Transaction Type (for example
LESS_THAN), and duplicated fragile TYPE pre-filter logic across the repository and engine.This change centralizes transaction-type rule helpers, uses proper
is/is notoperators for TYPE conditions, resets operators when the condition field changes, validates TYPE values, and adds unit tests for EXPENSE matching and SET TYPE actions.Type of change
How to test
Breaking changes
No breaking changes.
Checklist
Made with Cursor