1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-02 20:15:22 +02:00

Sketch out business logic and basic tests

This commit is contained in:
Zach Gollwitzer 2025-04-01 20:17:57 -04:00
parent 8effdcb2d3
commit 3bc0c18da0
11 changed files with 203 additions and 40 deletions

View file

@ -78,6 +78,8 @@ class Account < ApplicationRecord
Rails.logger.info("Processing balances (#{linked? ? 'reverse' : 'forward'})")
sync_balances
RuleProcessorJob.perform_later(family)
end
def post_sync

View file

@ -40,7 +40,7 @@ class Demo::Generator
create_tags!(family)
create_categories!(family)
create_merchants!(family)
create_rules!(family)
puts "tags, categories, merchants created for #{family_name}"
create_credit_card_account!(family)
@ -184,6 +184,19 @@ class Demo::Generator
onboarded_at: Time.current
end
def create_rules!(family)
family.rules.create!(
effective_date: 1.year.ago.to_date,
active: true,
conditions: [
Rule::Condition.new(condition_type: "match_merchant", value: "Whole Foods")
],
actions: [
Rule::Action.new(action_type: "set_category", value: "Groceries")
]
)
end
def create_tags!(family)
[ "Trips", "Emergency Fund", "Demo Tag" ].each do |tag|
family.tags.create!(name: tag)

View file

@ -22,6 +22,7 @@ class Family < ApplicationRecord
has_many :entries, through: :accounts
has_many :transactions, through: :accounts
has_many :rules, dependent: :destroy
has_many :trades, through: :accounts
has_many :holdings, through: :accounts

View file

@ -1,7 +1,72 @@
class Rule < ApplicationRecord
belongs_to :family
has_many :triggers, dependent: :destroy
has_many :conditions, dependent: :destroy
has_many :actions, dependent: :destroy
validates :effective_date, presence: true
def get_operator_symbol(operator)
case operator
when "gt"
">"
when "lt"
"<"
when "eq"
"="
end
end
def apply
case resource_type
when "transaction"
scope = family.transactions
conditions.each do |condition|
case condition.condition_type
when "match_merchant"
scope = scope.left_joins(:merchant).where(merchant: { name: condition.value })
when "compare_amount"
operator_symbol = get_operator_symbol(condition.operator)
scope = scope.joins(:entry)
.where("account_entries.amount #{Arel.sql(operator_symbol)} ?", condition.value)
when "compound"
subconditions = condition.conditions
subconditions.each do |subcondition|
case condition.operator
when "and"
case subcondition.condition_type
when "match_merchant"
scope = scope.left_joins(:merchant).where(merchant: { name: subcondition.value })
when "compare_amount"
operator_symbol = get_operator_symbol(subcondition.operator)
scope = scope.joins(:entry)
.where("account_entries.amount #{Arel.sql(operator_symbol)} ?", subcondition.value.to_f)
end
when "or"
raise "not implemented yet"
else
raise "Invalid compound operator"
end
end
else
raise "Unsupported condition type: #{condition.condition_type}"
end
end
scope.each do |transaction|
actions.each do |action|
case action.action_type
when "set_category"
category = family.categories.find_by(name: action.value)
transaction.update!(category: category)
else
raise "Unsupported action type: #{action.action_type}"
end
end
end
else
raise "Unsupported resource type: #{resource_type}"
end
end
end

View file

@ -0,0 +1,11 @@
class Rule::Condition < ApplicationRecord
OPERATORS = [ "and", "or", "gt", "lt", "eq" ]
TYPES = [ "match_merchant", "compare_amount", "compound" ]
belongs_to :rule, optional: true
belongs_to :parent, class_name: "Rule::Condition", optional: true
has_many :conditions, class_name: "Rule::Condition", foreign_key: :parent_id, dependent: :destroy
validates :operator, inclusion: { in: OPERATORS }, allow_nil: true
validates :condition_type, presence: true, inclusion: { in: TYPES }
end

View file

@ -1,7 +0,0 @@
class Rule::Trigger < ApplicationRecord
self.table_name = "rule_triggers"
belongs_to :rule
validates :trigger_type, presence: true
end