2024-02-23 21:34:33 -05:00
class Transaction < ApplicationRecord
2024-03-18 11:21:00 -04:00
include Monetizable
2024-02-23 21:34:33 -05:00
belongs_to :account
2024-03-07 19:15:50 +01:00
belongs_to :category , optional : true
2024-04-29 21:17:28 +02:00
belongs_to :merchant , optional : true
2024-02-29 08:32:52 -05:00
2024-03-15 12:21:59 -07:00
validates :name , :date , :amount , :account , presence : true
2024-03-18 11:21:00 -04:00
monetize :amount
2024-05-17 17:50:49 -04:00
scope :inflows , - > { where ( " amount <= 0 " ) }
scope :outflows , - > { where ( " amount > 0 " ) }
2024-03-18 11:21:00 -04:00
scope :active , - > { where ( excluded : false ) }
2024-04-24 13:34:50 +01:00
scope :with_converted_amount , - > ( currency = Current . family . currency ) {
# Join with exchange rates to convert the amount to the given currency
# If no rate is available, exclude the transaction from the results
select (
" transactions.* " ,
" transactions.amount * COALESCE(er.rate, 1) AS converted_amount "
)
. joins ( sanitize_sql_array ( [ " LEFT JOIN exchange_rates er ON transactions.date = er.date AND transactions.currency = er.base_currency AND er.converted_currency = ? " , currency ] ) )
. where ( " er.rate IS NOT NULL OR transactions.currency = ? " , currency )
}
def self . daily_totals ( transactions , period : Period . last_30_days , currency : Current . family . currency )
# Sum spending and income for each day in the period with the given currency
select (
" gs.date " ,
" COALESCE(SUM(converted_amount) FILTER (WHERE converted_amount > 0), 0) AS spending " ,
" COALESCE(SUM(-converted_amount) FILTER (WHERE converted_amount < 0), 0) AS income "
)
. from ( transactions . with_converted_amount ( currency ) , :t )
. joins ( sanitize_sql ( [ " RIGHT JOIN generate_series(?, ?, interval '1 day') AS gs(date) ON t.date = gs.date " , period . date_range . first , period . date_range . last ] ) )
. group ( " gs.date " )
end
def self . daily_rolling_totals ( transactions , period : Period . last_30_days , currency : Current . family . currency )
# Extend the period to include the rolling window
period_with_rolling = period . extend_backward ( period . date_range . count . days )
# Aggregate the rolling sum of spending and income based on daily totals
rolling_totals = from ( daily_totals ( transactions , period : period_with_rolling , currency : currency ) )
. select (
" * " ,
sanitize_sql_array ( [ " SUM(spending) OVER (ORDER BY date RANGE BETWEEN INTERVAL ? PRECEDING AND CURRENT ROW) as rolling_spend " , " #{ period . date_range . count } days " ] ) ,
sanitize_sql_array ( [ " SUM(income) OVER (ORDER BY date RANGE BETWEEN INTERVAL ? PRECEDING AND CURRENT ROW) as rolling_income " , " #{ period . date_range . count } days " ] )
)
. order ( " date " )
# Trim the results to the original period
select ( " * " ) . from ( rolling_totals ) . where ( " date >= ? " , period . date_range . first )
end
2024-03-18 11:21:00 -04:00
2024-03-11 14:51:16 +02:00
def self . ransackable_attributes ( auth_object = nil )
%w[ name amount date ]
end
def self . ransackable_associations ( auth_object = nil )
2024-04-29 21:17:28 +02:00
%w[ category merchant account ]
2024-03-11 14:51:16 +02:00
end
2024-03-28 13:23:54 -04:00
def self . build_filter_list ( params , family )
filters = [ ]
2024-04-19 12:03:16 -04:00
date_filters = { gteq : nil , lteq : nil }
2024-03-28 13:23:54 -04:00
if params
params . each do | key , value |
next if value . blank?
case key
when " account_id_in "
value . each do | account_id |
filters << { type : " account " , value : family . accounts . find ( account_id ) , original : { key : key , value : account_id } }
end
2024-04-02 18:17:26 +02:00
when " category_id_in "
value . each do | category_id |
filters << { type : " category " , value : family . transaction_categories . find ( category_id ) , original : { key : key , value : category_id } }
end
2024-04-29 21:17:28 +02:00
when " merchant_id_in "
value . each do | merchant_id |
filters << { type : " merchant " , value : family . transaction_merchants . find ( merchant_id ) , original : { key : key , value : merchant_id } }
end
when " category_name_or_merchant_name_or_account_name_or_name_cont "
2024-03-28 13:23:54 -04:00
filters << { type : " search " , value : value , original : { key : key , value : nil } }
2024-04-19 12:03:16 -04:00
when " date_gteq "
date_filters [ :gteq ] = value
when " date_lteq "
date_filters [ :lteq ] = value
2024-03-28 13:23:54 -04:00
end
end
2024-04-19 12:03:16 -04:00
unless date_filters . values . compact . empty?
filters << { type : " date_range " , value : date_filters , original : { key : " date_range " , value : nil } }
end
2024-03-28 13:23:54 -04:00
end
filters
end
2024-02-23 21:34:33 -05:00
end