Skip to content
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
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,9 @@ Where result will be something like this:
```elixir
iex> %{
...> postings: [
...> %Numscriptex.Posting{
...> %Numscriptex.Posting{
...> amount: 100,
...> decimal_amount: 1.0,
...> asset: "USD/2",
...> destination: "bar",
...> source: "foo"
Expand All @@ -164,19 +165,25 @@ iex> %{
...> account: "foo",
...> asset: "EUR/2",
...> final_balance: 300,
...> initial_balance: 300
...> initial_balance: 300,
...> decimal_final_balance: 3.0,
...> decimal_initial_balance: 3.0,
...> },
...> %Numscriptex.Balance{
...> account: "foo",
...> asset: "USD/2",
...> final_balance: 400,
...> initial_balance: 500
...> initial_balance: 500,
...> decimal_final_balance: 4.0,
...> decimal_initial_balance: 5.0,
...> },
...> %Numscriptex.Balance{
...> account: "bar",
...> asset: "USD/2",
...> final_balance: 100,
...> initial_balance: 0
...> initial_balance: 0,
...> decimal_final_balance: 1.0,
...> decimal_initial_balance: 0.0,
...> }
...> ],
...> accountMeta: %{}
Expand Down
62 changes: 48 additions & 14 deletions lib/numscriptex/balance.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,34 @@ defmodule Numscriptex.Balance do
can see the results of all transactions.
"""

alias Numscriptex.Utilities

@derive JSON.Encoder
defstruct account: nil,
asset: nil,
final_balance: nil,
initial_balance: nil
decimal_final_balance: nil,
initial_balance: nil,
decimal_initial_balance: nil

@typedoc """
Type that represents `Numscriptex.Balance` struct.

## Fields
* `:account` the account name
* `:asset` the asset were the transaction was made
* `:final_balance` balance after the transactions
* `:initial_balance` balance before the transactions
* `:final_balance` balance after the transactions (integer)
* `:decimal_final_balance` balance after the transactions, but as float
* `:initial_balance` balance before the transactions (integer)
* `:decimal_initial_balance` balance after the transactions, but as float
"""
@type t() :: %__MODULE__{
account: bitstring(),
asset: bitstring(),
final_balance: non_neg_integer(),
initial_balance: non_neg_integer()
final_balance: integer(),
decimal_final_balance: float(),
initial_balance: integer(),
decimal_initial_balance: float()
}

@doc """
Expand Down Expand Up @@ -57,19 +65,25 @@ defmodule Numscriptex.Balance do
account: "foo",
asset: "EUR/2",
final_balance: 300,
initial_balance: 300
decimal_final_balance: 3.0,
initial_balance: 300,
decimal_initial_balance: 3.0
},
%Numscriptex.Balance{
account: "foo",
asset: "USD/2",
final_balance: 400,
initial_balance: 500
decimal_final_balance: 4.0,
initial_balance: 500,
decimal_initial_balance: 5.0
},
%Numscriptex.Balance{
account: "bar",
asset: "USD/2",
final_balance: 100,
initial_balance: 0
decimal_final_balance: 1.0,
initial_balance: 0,
decimal_initial_balance: 0.0
}
]
```
Expand All @@ -82,9 +96,8 @@ defmodule Numscriptex.Balance do
|> handle_initial_balance(account_assets)
|> handle_final_balance(postings)
|> maybe_drop_balance()
|> then(fn balances ->
Enum.map(balances, &struct(__MODULE__, &1))
end)
|> put_decimal_values()
|> Enum.map(&struct(__MODULE__, &1))
end

defp build_balances(account_assets, postings) do
Expand All @@ -100,8 +113,10 @@ defmodule Numscriptex.Balance do
%{
account: account,
asset: asset,
final_balance: 0,
decimal_final_balance: 0,
initial_balance: 0,
final_balance: 0
decimal_initial_balance: 0
}
end)
end)
Expand All @@ -113,14 +128,18 @@ defmodule Numscriptex.Balance do
%{
account: posting["source"],
asset: posting["asset"],
final_balance: 0,
decimal_final_balance: 0,
initial_balance: 0,
final_balance: 0
decimal_initial_balance: 0
},
%{
account: posting["destination"],
asset: posting["asset"],
final_balance: 0,
decimal_final_balance: 0,
initial_balance: 0,
final_balance: 0
decimal_initial_balance: 0
}
]
end)
Expand Down Expand Up @@ -177,4 +196,19 @@ defmodule Numscriptex.Balance do
balance.initial_balance == 0 and balance.final_balance == 0
end)
end

defp put_decimal_values(balances) do
Enum.map(balances, fn
%{initial_balance: initial_balance, final_balance: final_balance} = balance ->
decimal_places = Utilities.decimal_places_from_asset(balance.asset)
decimal_initial_balance = Utilities.integer_to_decimal(initial_balance, decimal_places)
decimal_final_balance = Utilities.integer_to_decimal(final_balance, decimal_places)

%{
balance
| decimal_initial_balance: decimal_initial_balance,
decimal_final_balance: decimal_final_balance
}
end)
end
end
27 changes: 23 additions & 4 deletions lib/numscriptex/posting.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ defmodule Numscriptex.Posting do
`Numscriptex.Postings` represents a financial transaction made with [Numscript](https://docs.formance.com/numscript/)
"""

alias Numscriptex.Utilities

@derive JSON.Encoder
defstruct amount: nil,
decimal_amount: nil,
asset: nil,
destination: nil,
source: nil
Expand All @@ -17,21 +20,37 @@ defmodule Numscriptex.Posting do
* `:asset` the asset were the transaction was made
* `:destination` account whose the money will go to
* `:amount` amount of money transferred (integer)
* `:decimal_amount` amount of money transferred, but as float
"""
@type t() :: %__MODULE__{
amount: pos_integer(),
decimal_amount: float(),
asset: bitstring(),
destination: bitstring(),
source: bitstring()
}

@spec from_list(map()) :: list(__MODULE__.t())
@spec from_list(list(map())) :: list(__MODULE__.t())
def from_list(postings) do
Enum.map(postings, &from_map/1)
end

@spec from_list(map()) :: __MODULE__.t()
def from_map(map) do
struct(%__MODULE__{}, map)
@spec from_map(map()) :: __MODULE__.t()
def from_map(posting) do
put_decimal_amount(posting)
end

defp put_decimal_amount(posting) when not is_struct(posting) do
posting
|> Map.put(:decimal_amount, 0)
|> then(fn elem -> struct(%__MODULE__{}, elem) end)
|> put_decimal_amount()
end

defp put_decimal_amount(posting) do
decimal_places = Utilities.decimal_places_from_asset(posting.asset)
decimal_amount = Utilities.integer_to_decimal(posting.amount, decimal_places)

%{posting | decimal_amount: decimal_amount}
end
end
60 changes: 60 additions & 0 deletions lib/numscriptex/utilities.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,64 @@ defmodule Numscriptex.Utilities do

defp keys_to_atom({key, value}),
do: {String.to_existing_atom(key), value}

@doc """
Converts an integer value to float

```elixir
iex> Utilities.integer_to_decimal(1000, 2)
10.0

iex> Utilities.integer_to_decimal(1000, 3)
1.0

iex> Utilities.integer_to_decimal(1000, 4)
0.1

iex> Utilities.integer_to_decimal(1000, 5)
0.01
```
"""
@spec integer_to_decimal(pos_integer(), pos_integer() | nil) :: float()
def integer_to_decimal(amount, decimal_places \\ 2)

def integer_to_decimal(amount, nil), do: integer_to_decimal(amount)

def integer_to_decimal(amount, decimal_places) do
divisor =
10
|> Integer.pow(decimal_places)
|> trunc()

amount / divisor
end

@doc """
Gets the decimal places from an asset if has any (e.g. decimal places for "USD/2" would be 2).
You can choose a default value (the second argument), but if you don't choose any the default will be 2.

```elixir
iex> Utilities.decimal_places_from_asset("USD/2")
2

iex> Utilities.decimal_places_from_asset("USD/4")
4

iex> Utilities.decimal_places_from_asset("USD")
2

iex> Utilities.decimal_places_from_asset("USD", 3)
3
```
"""
@spec decimal_places_from_asset(bitstring(), pos_integer()) :: pos_integer()
def decimal_places_from_asset(asset, default \\ 2) do
if String.length(asset) >= 5 do
asset
|> String.last()
|> String.to_integer()
else
default
end
end
end
12 changes: 9 additions & 3 deletions test/numscriptex/balance_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,25 @@ defmodule Numscriptex.BalanceTest do
account: "bar",
asset: "USD/2",
final_balance: 50,
initial_balance: 0
decimal_final_balance: 0.5,
initial_balance: 0,
decimal_initial_balance: 0.0
},
%Numscriptex.Balance{
account: "baz",
asset: "USD/2",
final_balance: 49,
initial_balance: 0
decimal_final_balance: 0.49,
initial_balance: 0,
decimal_initial_balance: 0.0
},
%Numscriptex.Balance{
account: "foo",
asset: "USD/2",
final_balance: 1,
initial_balance: 100
initial_balance: 100,
decimal_final_balance: 0.01,
decimal_initial_balance: 1.0
}
]

Expand Down
7 changes: 5 additions & 2 deletions test/numscriptex/posting_tests.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule Numscriptex.PostingTest do
doctest Numscriptex.Posting

describe "from_map/1" do
setup_all do
setup do
postings = [
%{
amount: 100,
Expand All @@ -18,7 +18,7 @@ defmodule Numscriptex.PostingTest do
amount: 100,
asset: "USD/2",
destination: "baz",
source: "foo"
source: "bar"
}
]

Expand All @@ -31,12 +31,14 @@ defmodule Numscriptex.PostingTest do
assert postings == [
%Numscriptex.Posting{
amount: 100,
decimal_amount: 1.0,
asset: "USD/2",
destination: "bar",
source: "foo"
},
%Numscriptex.Posting{
amount: 100,
decimal_amount: 1.0,
asset: "USD/2",
destination: "baz",
source: "bar"
Expand All @@ -49,6 +51,7 @@ defmodule Numscriptex.PostingTest do

assert posting == %Numscriptex.Posting{
amount: 100,
decimal_amount: 1.0,
asset: "USD/2",
destination: "bar",
source: "foo"
Expand Down
Loading