Skip to content

Commit

Permalink
Change balances type to map
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanperalta committed Feb 23, 2022
1 parent 141c848 commit 93b51de
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 43 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Exledger
# ExLedger

**TODO: Add description**

Expand Down
43 changes: 36 additions & 7 deletions lib/amount.ex
Original file line number Diff line number Diff line change
@@ -1,16 +1,45 @@
defmodule ExLedger.Amount do
defstruct ~w[quantity currency]a
alias Decimal, as: D

defstruct [:currency, number: D.new(0)]

@type t :: %__MODULE__{
quantity: integer(),
currency: tuple() | String.t()
number: integer(),
currency: String.t()
}

def new(attrs) do
struct!(%__MODULE__{}, attrs)
def new({num, curr}), do: new(D.new(num), curr)
def new(params), do: struct!(__MODULE__, params)
def new(num, curr), do: new(%{number: D.new(num), currency: curr})

def to_string(%__MODULE__{} = amount) do
[D.to_string(amount.number), amount.currency]
|> Enum.reject(&(&1 == nil))
|> Enum.join(" ")
end

def eq?(%{currency: curr} = amount, %{currency: curr} = other_amount) do
D.eq?(amount.number, other_amount.number)
end

def new(qty, currency) do
new(%{quantity: qty, currency: currency})
def eq?(_, _), do: false

def add(a, b), do: apply_op(:add, a, b)
def sub(a, b), do: apply_op(:sub, a, b)
def mult(a, b), do: apply_op(:mult, a, b)
def div(a, b), do: apply_op(:div, a, b)

defp apply_op(oper, %{currency: c} = a, %{currency: c} = b) do
new(apply(D, oper, [a.number, b.number]), c)
end

defp apply_op(oper, _, _) do
raise(ArithmeticError, "Unmatching currencies for #{oper} operation")
end

def zero(), do: new(0, nil)

def negate(%__MODULE__{number: number} = amount) do
%{amount | number: D.negate(number)}
end
end
2 changes: 1 addition & 1 deletion lib/exledger.ex
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
defmodule Exledger do
defmodule ExLedger do
end
2 changes: 1 addition & 1 deletion lib/ledger.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule ExLedger.Ledger do

@spec is_balanced?(__MODULE__.t()) :: bool()
def is_balanced?(%{transactions: transactions} = _ledger) do
Enum.map(transactions, &Transaction.is_balanced?/1)
Enum.map(transactions, &Transaction.balanced?/1)
|> Enum.all?()
end
end
2 changes: 1 addition & 1 deletion lib/entry.ex → lib/posting.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExLedger.Entry do
defmodule ExLedger.Posting do
defstruct ~w[account amount]a

alias ExLedger.{Account, Amount}
Expand Down
36 changes: 16 additions & 20 deletions lib/transaction.ex
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
defmodule ExLedger.Transaction do
defstruct [:date, :status, :description, balances: [], entries: []]

alias ExLedger.Entry
defstruct [:id, :description, :date, :status, postings: [], balances: %{}]

@type t :: %__MODULE__{
date: DateTime.t(),
status: bool(),
status: any(),
description: String.t(),
balances: keyword(),
entries: [Entry.t()]
balances: map(),
postings: [Posting.t()]
}

def new(), do: new(%{})
Expand All @@ -18,29 +16,27 @@ defmodule ExLedger.Transaction do
struct!(%__MODULE__{}, attrs)
end

def add_entry(%{entries: entries} = txn, account, amount) do
txn
|> Map.put(:entries, [Entry.new(account, amount) | entries])
|> compute_balance(amount)
def add_entry(txn, account, amount) do
add_entry(txn, %ExLedger.Posting{account: account, amount: amount})
end

def update_balance(balances, {qty, curr}) do
Keyword.update(balances, curr, qty, fn e -> e + qty end)
def add_entry(%{postings: postings} = txn, %ExLedger.Posting{amount: amount} = posting) do
txn
|> Map.put(:entries, [posting | postings])
|> compute_balance(amount)
end

def compute_balance(txn, amount) do
Map.put(txn, :balances, update_balance(txn.balances, amount))
end

@spec is_balanced?(__MODULE__.t()) :: bool()
def is_balanced?(txn) do
Keyword.values(txn.balances)
|> Enum.all?(&(&1 == 0))
defp update_balance(balances, {qty, curr}) do
Map.update(balances, curr, qty, fn e -> e + qty end)
end

def transactions(entries) do
entries
|> Enum.map(& &1.entries)
|> List.flatten()
def balanced?(%{balances: balances}) do
balances
|> Map.values()
|> Enum.all?(&(&1 == 0))
end
end
5 changes: 2 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Exledger.MixProject do
defmodule ExLedger.MixProject do
use Mix.Project

def project do
Expand All @@ -21,8 +21,7 @@ defmodule Exledger.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
{:decimal, "~> 2.0"}
]
end
end
3 changes: 3 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
%{
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
}
2 changes: 1 addition & 1 deletion test/exledger_test.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExledgerTest do
defmodule ExLedgerTest do
use ExUnit.Case
use ExLedger.LedgerBuilder

Expand Down
16 changes: 8 additions & 8 deletions test/transaction_test.exs
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@
defmodule Exledger.TransactionTest do
defmodule ExLedger.TransactionTest do
use ExUnit.Case
use ExLedger.LedgerBuilder

describe "add_entry/3" do
setup [:build_transaction]
setup :build_transaction

test "single currency", %{transaction: txn} do
assert %{balances: [usd: 1]} = txn
assert %{balances: %{usd: 1}} = txn
end

test "sums 2 common currency", %{transaction: txn} do
assert %{balances: [usd: 6]} =
assert %{balances: %{usd: 6}} =
txn
|> Transaction.add_entry("assets", {5, :usd})
end

test "multiple currency", %{transaction: txn} do
assert %{balances: [usd: 0, php: 0]} =
assert %{balances: %{usd: 0, php: 0}} =
txn
|> Transaction.add_entry("expenses", {-1, :usd})
|> Transaction.add_entry("assets", {1, :php})
|> Transaction.add_entry("expenses", {-1, :php})
end
end

describe "is_balanced?/1" do
describe "balanced?/1" do
test "returns true when all balances is 0" do
txn =
Transaction.new("test transaction")
|> Transaction.add_entry("assets", {-100, :usd})
|> Transaction.add_entry("expenses", {100, :usd})

assert Transaction.is_balanced?(txn)
assert Transaction.balanced?(txn)
end

test "returns false when all imbalances is 0" do
txn =
Transaction.new("test transaction")
|> Transaction.add_entry("assets", {-100, :usd})

refute Transaction.is_balanced?(txn)
refute Transaction.balanced?(txn)
end
end

Expand Down

0 comments on commit 93b51de

Please sign in to comment.