2024-02-02 09:05:04 -06:00
class Family < ApplicationRecord
2024-11-15 13:49:37 -05:00
include Plaidable , Syncable
2024-10-23 11:20:55 -04:00
DATE_FORMATS = [ " %m-%d-%Y " , " %d-%m-%Y " , " %Y-%m-%d " , " %d/%m/%Y " , " %Y/%m/%d " , " %m/%d/%Y " , " %e/%m/%Y " , " %Y.%m.%d " ]
2024-10-02 12:07:56 -04:00
include Providable
2024-02-02 09:05:04 -06:00
has_many :users , dependent : :destroy
2024-11-01 10:23:27 -05:00
has_many :invitations , dependent : :destroy
2024-05-23 08:09:33 -04:00
has_many :tags , dependent : :destroy
2024-02-02 09:05:04 -06:00
has_many :accounts , dependent : :destroy
2024-10-01 10:47:59 -04:00
has_many :imports , dependent : :destroy
2024-07-01 10:49:43 -04:00
has_many :transactions , through : :accounts
has_many :entries , through : :accounts
2024-06-20 08:15:09 -04:00
has_many :categories , dependent : :destroy
2024-06-20 08:38:59 -04:00
has_many :merchants , dependent : :destroy
2024-08-16 12:13:48 -04:00
has_many :issues , through : :accounts
2024-11-15 13:49:37 -05:00
has_many :plaid_items , dependent : :destroy
2024-03-04 08:31:22 -05:00
2024-10-02 14:02:17 -04:00
validates :locale , inclusion : { in : I18n . available_locales . map ( & :to_s ) }
2024-10-23 11:20:55 -04:00
validates :date_format , inclusion : { in : DATE_FORMATS }
2024-10-02 14:02:17 -04:00
2024-11-15 13:49:37 -05:00
def sync_data ( start_date : nil )
update! ( last_synced_at : Time . current )
accounts . manual . each do | account |
account . sync_data ( start_date : start_date )
end
plaid_items . each do | plaid_item |
plaid_item . sync_data ( start_date : start_date )
end
end
def syncing?
super || accounts . manual . any? ( & :syncing? ) || plaid_items . any? ( & :syncing? )
end
def get_link_token ( webhooks_url : , redirect_url : , accountable_type : nil )
return nil unless plaid_provider
plaid_provider . get_link_token (
user_id : id ,
country : country ,
language : locale ,
webhooks_url : webhooks_url ,
redirect_url : redirect_url ,
accountable_type : accountable_type
) . link_token
end
2024-03-11 16:32:13 -04:00
def snapshot ( period = Period . all )
query = accounts . active . joins ( :balances )
2024-06-19 06:52:08 -04:00
. where ( " account_balances.currency = ? " , self . currency )
. select (
" account_balances.currency " ,
" account_balances.date " ,
" SUM(CASE WHEN accounts.classification = 'liability' THEN account_balances.balance ELSE 0 END) AS liabilities " ,
" SUM(CASE WHEN accounts.classification = 'asset' THEN account_balances.balance ELSE 0 END) AS assets " ,
" SUM(CASE WHEN accounts.classification = 'asset' THEN account_balances.balance WHEN accounts.classification = 'liability' THEN -account_balances.balance ELSE 0 END) AS net_worth " ,
)
. group ( " account_balances.date, account_balances.currency " )
. order ( " account_balances.date " )
2024-03-11 16:32:13 -04:00
2024-03-21 13:39:10 -04:00
query = query . where ( " account_balances.date >= ? " , period . date_range . begin ) if period . date_range . begin
query = query . where ( " account_balances.date <= ? " , period . date_range . end ) if period . date_range . end
2024-03-19 09:10:40 -04:00
result = query . to_a
2024-03-11 16:32:13 -04:00
{
2024-03-19 09:10:40 -04:00
asset_series : TimeSeries . new ( result . map { | r | { date : r . date , value : Money . new ( r . assets , r . currency ) } } ) ,
liability_series : TimeSeries . new ( result . map { | r | { date : r . date , value : Money . new ( r . liabilities , r . currency ) } } ) ,
net_worth_series : TimeSeries . new ( result . map { | r | { date : r . date , value : Money . new ( r . net_worth , r . currency ) } } )
2024-03-11 16:32:13 -04:00
}
2024-03-04 08:31:22 -05:00
end
2024-04-24 13:34:50 +01:00
def snapshot_account_transactions
period = Period . last_30_days
2024-07-01 10:49:43 -04:00
results = accounts . active . joins ( :entries )
. select (
" accounts.* " ,
" COALESCE(SUM(account_entries.amount) FILTER (WHERE account_entries.amount > 0), 0) AS spending " ,
" COALESCE(SUM(-account_entries.amount) FILTER (WHERE account_entries.amount < 0), 0) AS income "
)
. where ( " account_entries.date >= ? " , period . date_range . begin )
. where ( " account_entries.date <= ? " , period . date_range . end )
. where ( " account_entries.marked_as_transfer = ? " , false )
. where ( " account_entries.entryable_type = ? " , " Account::Transaction " )
2024-08-20 15:44:32 -04:00
. group ( " accounts.id " )
. having ( " SUM(ABS(account_entries.amount)) > 0 " )
2024-07-01 10:49:43 -04:00
. to_a
2024-04-24 13:34:50 +01:00
2024-04-24 15:02:22 +01:00
results . each do | r |
r . define_singleton_method ( :savings_rate ) do
( income - spending ) / income
end
end
2024-04-24 13:34:50 +01:00
{
top_spenders : results . sort_by ( & :spending ) . select { | a | a . spending > 0 } . reverse ,
2024-04-24 15:02:22 +01:00
top_earners : results . sort_by ( & :income ) . select { | a | a . income > 0 } . reverse ,
top_savers : results . sort_by { | a | a . savings_rate } . reverse
2024-04-24 13:34:50 +01:00
}
end
def snapshot_transactions
2024-10-31 13:05:01 +00:00
candidate_entries = entries . account_transactions . without_transfers . excluding (
entries . joins ( :account ) . where ( amount : .. 0 , accounts : { classification : Account . classifications [ :liability ] } )
)
2024-07-01 10:49:43 -04:00
rolling_totals = Account :: Entry . daily_rolling_totals ( candidate_entries , self . currency , period : Period . last_30_days )
2024-04-24 13:34:50 +01:00
spending = [ ]
income = [ ]
2024-04-24 15:02:22 +01:00
savings = [ ]
2024-04-24 13:34:50 +01:00
rolling_totals . each do | r |
spending << {
date : r . date ,
value : Money . new ( r . rolling_spend , self . currency )
}
income << {
date : r . date ,
value : Money . new ( r . rolling_income , self . currency )
}
2024-04-24 15:02:22 +01:00
savings << {
date : r . date ,
value : r . rolling_income != 0 ? ( r . rolling_income - r . rolling_spend ) / r . rolling_income : 0 . to_d
}
2024-04-24 13:34:50 +01:00
end
{
income_series : TimeSeries . new ( income , favorable_direction : " up " ) ,
2024-04-24 15:02:22 +01:00
spending_series : TimeSeries . new ( spending , favorable_direction : " down " ) ,
savings_rate_series : TimeSeries . new ( savings , favorable_direction : " up " )
2024-04-24 13:34:50 +01:00
}
end
2024-03-11 16:32:13 -04:00
def net_worth
2024-03-21 13:39:10 -04:00
assets - liabilities
2024-03-04 08:31:22 -05:00
end
2024-03-11 16:32:13 -04:00
def assets
2024-07-08 09:04:59 -04:00
Money . new ( accounts . active . assets . map { | account | account . balance_money . exchange_to ( currency , fallback_rate : 0 ) } . sum , currency )
2024-03-04 08:31:22 -05:00
end
2024-03-11 16:32:13 -04:00
def liabilities
2024-07-08 09:04:59 -04:00
Money . new ( accounts . active . liabilities . map { | account | account . balance_money . exchange_to ( currency , fallback_rate : 0 ) } . sum , currency )
2024-03-04 08:31:22 -05:00
end
2024-04-04 23:00:12 +02:00
2024-10-02 12:07:56 -04:00
def synth_usage
self . class . synth_provider & . usage
end
2024-10-08 14:37:47 -05:00
def subscribed?
2024-10-24 11:02:27 -04:00
stripe_subscription_status == " active "
2024-10-08 14:37:47 -05:00
end
def primary_user
users . order ( :created_at ) . first
end
2024-02-02 09:05:04 -06:00
end