59 lines
1.8 KiB
Ruby
59 lines
1.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "bigdecimal"
|
|
|
|
module Hledger
|
|
# Turns contributed-capital balances into a per-commodity tree, where every
|
|
# row shows its share of that commodity's total.
|
|
#
|
|
# quantities are absolute, so a node equals the sum of its descendants and no
|
|
# negative values are shown.
|
|
module Equity
|
|
module_function
|
|
|
|
# entries: [[full_account_name, [{ "commodity" =>, "quantity" => BigDecimal, "style" => }]], ...]
|
|
def groups(entries)
|
|
equity_entries = entries.select { |name, _amounts| name.to_s.start_with?("equity:") }
|
|
rows =
|
|
AccountTree
|
|
.expand(equity_entries)
|
|
.reject { |node| node["root"] }
|
|
.map { |node| node.merge("depth" => node["depth"] - 1) }
|
|
|
|
commodities(rows).map { |commodity| build_group(rows, commodity) }
|
|
end
|
|
|
|
def commodities(rows)
|
|
rows.flat_map { |node| node["amounts"].map { |amount| amount["commodity"].to_s } }.uniq
|
|
end
|
|
|
|
def build_group(rows, commodity)
|
|
group_rows =
|
|
rows.filter_map do |node|
|
|
amount = node["amounts"].find { |candidate| candidate["commodity"].to_s == commodity }
|
|
node.merge("amounts" => [amount]) if amount
|
|
end
|
|
template = group_rows.first["amounts"].first
|
|
total =
|
|
group_rows
|
|
.select { |row| row["depth"].zero? }
|
|
.sum { |row| row["amounts"].first["quantity"] }
|
|
|
|
{
|
|
"commodity" => commodity,
|
|
"total" => template.merge("quantity" => total),
|
|
"rows" =>
|
|
group_rows.map do |row|
|
|
row.merge("percentage" => percentage(row["amounts"].first["quantity"], total))
|
|
end,
|
|
}
|
|
end
|
|
|
|
def percentage(quantity, total)
|
|
return "0.00" if total.zero?
|
|
|
|
format("%.2f", (quantity / total) * 100)
|
|
end
|
|
end
|
|
end
|