Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve financial report #163

Merged
merged 2 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 76 additions & 13 deletions app/models/financial_report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,43 +20,106 @@ def next_year
end

def checking_data
AccountData.new(FinancialAccount.wf_checking, @year)
accounts = [
FinancialAccount.usb_checking,
FinancialAccount.wf_checking
]
AccountData.new(accounts, @year)
end

def savings_data
AccountData.new(FinancialAccount.wf_savings, @year)
accounts = [
FinancialAccount.wf_savings
]
AccountData.new(accounts, @year)
end

class AccountData
def initialize(account, year)
@account = account
def initialize(accounts, year)
@accounts = accounts
@year = year
grouped_statements
end

def starting_amounts
statements.map(&:starting_amount_cents)
grouped_statements.map do |statements|
amounts = statements.map(&:starting_amount_cents).compact
next if amounts.empty?

amounts.sum
end
end

def income_amounts
grouped_transactions.map do |transactions|
amounts = transactions.map do |transaction|
next unless transaction.amount_cents&.positive?

transaction.amount_cents
end.compact
next if amounts.empty?

amounts.sum
end
end

def expenses_amounts
grouped_transactions.map do |transactions|
amounts = transactions.map do |transaction|
next unless transaction.amount_cents&.negative?

transaction.amount_cents
end.compact
next if amounts.empty?

amounts.sum
end
end

def ending_amounts
statements.map(&:ending_amount_cents)
grouped_statements.map do |statements|
amounts = statements.map(&:ending_amount_cents).compact
next if amounts.empty?

amounts.sum
end
end

def net_amounts
statements.map(&:net_amount_cents)
grouped_statements.map do |statements|
amounts = statements.map(&:net_amount_cents).compact
next if amounts.empty?

amounts.sum
end
end

private

def compute_statements
account_statements = @account.financial_statements.for_year(@year)
def compute_grouped_statements
account_statements = @accounts.map { |account| account.financial_statements.for_year(@year) }.flatten
FinancialReport.months_for_year(@year).map do |date|
statements_for_period = account_statements.select { |statement| statement.period_start_on == date }
statements_for_period << NullStatement.instance if statements_for_period.empty?
statements_for_period
end
end

def grouped_statements
@grouped_statements ||= compute_grouped_statements
end

def compute_grouped_transactions
account_transactions = @accounts.map { |account| account.financial_transactions.for_year(@year) }.flatten
FinancialReport.months_for_year(@year).map do |date|
statement_for_period = account_statements.find { |statement| statement.period_start_on == date }
statement_for_period || NullStatement.instance
transactions_for_period = account_transactions.select { |transaction| transaction.posted_on.month == date.month }
transactions_for_period << NullTransaction.instance if transactions_for_period.empty?
transactions_for_period
end
end

def statements
@statements ||= compute_statements
def grouped_transactions
@grouped_transactions ||= compute_grouped_transactions
end
end
end
2 changes: 2 additions & 0 deletions app/models/financial_transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ class FinancialTransaction < ApplicationRecord
belongs_to :financial_account

validates_presence_of :posted_on, :amount_cents, :description

scope :for_year, ->(year) { where("extract(year from posted_on) = ?", year) }
end
6 changes: 6 additions & 0 deletions app/models/null_transaction.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class NullTransaction
include Singleton

def amount_cents
end
end
6 changes: 6 additions & 0 deletions app/views/financial_reports/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
%tr
%td.text-left Starting
= render partial: "amount_cents", collection: checking_data.starting_amounts
%tr
%td.text-left Income
= render partial: "amount_cents", collection: checking_data.income_amounts
%tr
%td.text-left Expenses
= render partial: "amount_cents", collection: checking_data.expenses_amounts
%tr
%td.text-left Ending
= render partial: "amount_cents", collection: checking_data.ending_amounts
Expand Down
159 changes: 149 additions & 10 deletions spec/system/financial_reports/admin_views_financial_report_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
scenario "with no statements for that year" do
visit "/financial_reports/#{last_year}"

header = page.find("header nav h1")
expect(header.text).to eq last_year.year.to_s
header_h1 = page.find("header nav h1")
expect(header_h1.text).to eq last_year.year.to_s

prev_link, next_link = page.all("header nav a").to_a

Expand All @@ -25,22 +25,59 @@
expect(next_link.text).to eq "next"
expect(next_link["href"]).to end_with((last_year.year + 1).to_s)

page.all("table").to_a.each do |table|
month_row, starting_row, ending_row, net_row = table.all("tr").to_a
checking_table, savings_table = page.all("table").to_a

within checking_table do
checking_trs = checking_table.all("tr").to_a

month_tr = checking_trs[0]
starting_tr = checking_trs[1]
income_tr = checking_trs[2]
expenses_tr = checking_trs[3]
ending_tr = checking_trs[4]
net_tr = checking_trs[5]

expect(month_tr.all("th").map(&:text)).to eq %w[
Month Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
]

expect(starting_tr.all("td").map(&:text)).to eq %w[
Starting - - - - - - - - - - - -
]

expect(income_tr.all("td").map(&:text)).to eq %w[
Income - - - - - - - - - - - -
]

expect(expenses_tr.all("td").map(&:text)).to eq %w[
Expenses - - - - - - - - - - - -
]

expect(ending_tr.all("td").map(&:text)).to eq %w[
Ending - - - - - - - - - - - -
]

expect(net_tr.all("td").map(&:text)).to eq %w[
Net - - - - - - - - - - - -
]
end

expect(month_row.all("th").map(&:text)).to eq %w[
within savings_table do
month_tr, starting_tr, ending_tr, net_tr = savings_table.all("tr").to_a

expect(month_tr.all("th").map(&:text)).to eq %w[
Month Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
]

expect(starting_row.all("td").map(&:text)).to eq %w[
expect(starting_tr.all("td").map(&:text)).to eq %w[
Starting - - - - - - - - - - - -
]

expect(ending_row.all("td").map(&:text)).to eq %w[
expect(ending_tr.all("td").map(&:text)).to eq %w[
Ending - - - - - - - - - - - -
]

expect(net_row.all("td").map(&:text)).to eq %w[
expect(net_tr.all("td").map(&:text)).to eq %w[
Net - - - - - - - - - - - -
]
end
Expand All @@ -58,11 +95,23 @@
visit "/financial_reports/#{last_year}"

checking_table = page.all("table").to_a.first
_month_tr, starting_tr, ending_tr, net_tr = checking_table.all("tr").to_a
checking_trs = checking_table.all("tr").to_a

starting_tr = checking_trs[1]
income_tr = checking_trs[2]
expenses_tr = checking_trs[3]
ending_tr = checking_trs[4]
net_tr = checking_trs[5]

may_starting_td = starting_tr.all("td").to_a[5]
expect(may_starting_td.text).to eq "$1,034.89"

may_income_td = income_tr.all("td").to_a[5]
expect(may_income_td.text).to eq "-"

may_expenses_td = expenses_tr.all("td").to_a[5]
expect(may_expenses_td.text).to eq "-"

may_ending_td = ending_tr.all("td").to_a[5]
expect(may_ending_td.text).to eq "$99.00"

Expand Down Expand Up @@ -94,7 +143,11 @@
checking_table, savings_table = page.all("table").to_a

within checking_table do
_month_tr, starting_tr, ending_tr, net_tr = checking_table.all("tr").to_a
checking_trs = checking_table.all("tr").to_a

starting_tr = checking_trs[1]
ending_tr = checking_trs[4]
net_tr = checking_trs[5]

_starting_label, *starting_tds = starting_tr.all("td").to_a
expect(starting_tds.count).to eq 12
Expand Down Expand Up @@ -125,4 +178,90 @@
expect(net_tds.map(&:text).uniq).to eq ["$935.89"]
end
end

scenario "with two statements on two accounts for the same period" do
FactoryBot.create(
:financial_statement,
financial_account: FinancialAccount.usb_checking,
period_start_on: Date.parse("#{last_year.year}-05-01"),
starting_amount_cents: 100_00,
ending_amount_cents: 50_00
)

FactoryBot.create(
:financial_statement,
financial_account: FinancialAccount.wf_checking,
period_start_on: Date.parse("#{last_year.year}-05-01"),
starting_amount_cents: 70_00,
ending_amount_cents: 20_00
)

visit "/financial_reports/#{last_year}"

checking_table = page.all("table").to_a.first
checking_trs = checking_table.all("tr").to_a

starting_tr = checking_trs[1]
ending_tr = checking_trs[4]
net_tr = checking_trs[5]

may_starting_td = starting_tr.all("td").to_a[5]
expect(may_starting_td.text).to eq "$170.00"

may_ending_td = ending_tr.all("td").to_a[5]
expect(may_ending_td.text).to eq "$70.00"

may_net_td = net_tr.all("td").to_a[5]
expect(may_net_td.text).to eq "($100.00)"
end

scenario "with one statement in the middle of that year" do
FactoryBot.create(
:financial_statement,
financial_account: FinancialAccount.wf_checking,
period_start_on: Date.parse("#{last_year.year}-05-01"),
starting_amount_cents: 1_034_89,
ending_amount_cents: 99_00
)

FactoryBot.create(
:financial_transaction,
financial_account: FinancialAccount.wf_checking,
posted_on: Date.parse("#{last_year.year}-05-01"),
amount_cents: 900_00
)

FactoryBot.create(
:financial_transaction,
financial_account: FinancialAccount.wf_checking,
posted_on: Date.parse("#{last_year.year}-05-01"),
amount_cents: -1_835_89
)

visit "/financial_reports/#{last_year}"

checking_table = page.all("table").to_a.first
checking_trs = checking_table.all("tr").to_a

starting_tr = checking_trs[1]
income_tr = checking_trs[2]
expenses_tr = checking_trs[3]
ending_tr = checking_trs[4]
net_tr = checking_trs[5]

may_starting_td = starting_tr.all("td").to_a[5]
expect(may_starting_td.text).to eq "$1,034.89"

may_income_td = income_tr.all("td").to_a[5]
expect(may_income_td.text).to eq "$900.00"

may_expenses_td = expenses_tr.all("td").to_a[5]
expect(may_expenses_td.text).to eq "($1,835.89)"

may_ending_td = ending_tr.all("td").to_a[5]
expect(may_ending_td.text).to eq "$99.00"

may_net_td = net_tr.all("td").to_a[5]
expect(may_net_td.text).to eq "($935.89)"
end
end