FR: Add transaction type as rule condition option (#790)

* Add transaction type condition filter for rules

Add ability to filter rules by transaction type (income, expense, transfer).
This allows users to create rules that differentiate between transactions
with the same name but different types.

- Add Rule::ConditionFilter::TransactionType with select dropdown
- Register in TransactionResource condition_filters
- Add tests for income, expense, and transfer filtering

Closes #373

* Address PR review feedback for transaction type filter

- Fix income filter to exclude transfers and investment_contribution
- Fix expense filter to include investment_contribution regardless of sign
- Add i18n for option and operator labels
- Add tests for edge cases (transfer inflows, investment contributions)

Logic now matches Transaction::Search#apply_type_filter for consistency.
This commit is contained in:
Dream
2026-01-26 10:53:05 -05:00
committed by GitHub
parent 4d867c193c
commit 51579d3731
4 changed files with 216 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
class Rule::ConditionFilter::TransactionType < Rule::ConditionFilter
# Transfer kinds matching Transaction#transfer? method
TRANSFER_KINDS = %w[funds_movement cc_payment loan_payment].freeze
def type
"select"
end
def options
[
[ I18n.t("rules.condition_filters.transaction_type.income"), "income" ],
[ I18n.t("rules.condition_filters.transaction_type.expense"), "expense" ],
[ I18n.t("rules.condition_filters.transaction_type.transfer"), "transfer" ]
]
end
def operators
[ [ I18n.t("rules.condition_filters.transaction_type.equal_to"), "=" ] ]
end
def prepare(scope)
scope.with_entry
end
def apply(scope, operator, value)
# Logic matches Transaction::Search#apply_type_filter for consistency
case value
when "income"
# Negative amounts, excluding transfers and investment_contribution
scope.where("entries.amount < 0")
.where.not(kind: TRANSFER_KINDS + %w[investment_contribution])
when "expense"
# Positive amounts OR investment_contribution (regardless of sign), excluding transfers
scope.where("entries.amount >= 0 OR transactions.kind = 'investment_contribution'")
.where.not(kind: TRANSFER_KINDS)
when "transfer"
scope.where(kind: TRANSFER_KINDS)
else
scope
end
end
end

View File

@@ -7,6 +7,7 @@ class Rule::Registry::TransactionResource < Rule::Registry
[
Rule::ConditionFilter::TransactionName.new(rule),
Rule::ConditionFilter::TransactionAmount.new(rule),
Rule::ConditionFilter::TransactionType.new(rule),
Rule::ConditionFilter::TransactionMerchant.new(rule),
Rule::ConditionFilter::TransactionCategory.new(rule),
Rule::ConditionFilter::TransactionDetails.new(rule),