Current section
Files
Jump to
Current section
Files
lib/Accountant.ex
defmodule SoftBank.Accountant do
use GenServer
require Logger
@registry_name :soft_bank_accounts
@moduledoc false
alias SoftBank.Transfer
alias SoftBank.Account
alias SoftBank.Amount
alias SoftBank.Entry
alias SoftBank.Owner
alias SoftBank.Repo
def child_spec(args) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [args]},
type: :worker
}
end
def start_link(account_number) do
IO.inspect(account_number, label: "account_number")
data = Owner.fetch(%{account_number: account_number}, Repo)
name = via_tuple(account_number)
GenServer.start_link(__MODULE__, data, name: name)
end
@impl true
def init(args) do
ref =
:ets.new(String.to_atom("sb_owner." <> args.account_number), [
:set,
:named_table,
:public,
read_concurrency: true,
write_concurrency: true
])
data =
args.accounts
|> Enum.map(fn x ->
account = Account.fetch(%{account_number: x.account_number}, Repo)
%{account | balance: Account.balance(Repo, x, nil)}
end)
:ets.insert(ref, {:accounts, data})
{:ok, %{ref: ref}}
end
def shutdown(pid) do
GenServer.call(pid, :shutdown)
end
def deposit(amount, to, currency \\ :USD) do
name = via_tuple(to)
amount = Money.new!(currency, amount)
try do
GenServer.call(name, {:deposit, amount, to})
catch
:exit, _ -> {:error, "invalid_account"}
end
end
def withdrawl(amount, from, currency \\ :USD) do
name = via_tuple(from)
amount = Money.new!(currency, amount)
try do
GenServer.call(name, {:withdrawl, amount, from})
catch
:exit, _ -> {:error, "invalid_account"}
end
end
def convert(amount, dest_currency) do
latest_rates = Money.ExchangeRates.latest_rates()
rates =
case(latest_rates) do
{:error, _rates} -> []
{:ok, rates} -> rates
end
Money.to_currency(amount, dest_currency, rates)
end
def balance(account) do
name = via_tuple(account)
try do
GenServer.call(name, :balance)
catch
:exit, _ -> {:error, "invalid_account"}
end
end
def transfer(amount, from, to) do
name = via_tuple(from)
try do
GenServer.call(name, {:transfer, to, amount})
catch
:exit, _ -> {:error, "invalid_account"}
end
end
def show(account) do
name = via_tuple(account)
try do
GenServer.call(name, :show)
catch
:exit, _ -> {:error, "invalid_account"}
end
end
defp reload do
Process.send_after(self(), :reload, 1000)
end
@impl true
def handle_call(
:shutdown,
_from,
state
) do
{:stop, {:ok, "Normal Shutdown"}, state}
end
@impl true
def handle_call({:transfer, account_number, dest_account_number, amount}, _, state) do
params = %{amount: amount}
account = Account.fetch(%{account_number: account_number})
destination_account = Account.fetch(%{account_number: dest_account_number})
reply = Transfer.send(account, destination_account, params)
reload()
{:reply, reply, state}
end
@impl true
def handle_call({:withdrawl, amount, account_number}, _from, state) do
account = Account.fetch(%{account_number: account_number})
changeset =
Entry.changeset(%Entry{
description:
"Withdrawl : " <>
to_string(amount) <> " from " <> to_string(account.account_number),
date: DateTime.truncate(DateTime.utc_now(), :second),
amounts: [
%Amount{amount: amount, type: "credit", account_id: account.id},
%Amount{amount: amount, type: "debit", account_id: account.id}
]
})
config = SoftBank.Config.new()
reply = Repo.insert(config, changeset)
reload()
{:reply, reply, state}
end
@impl true
def handle_call({:deposit, amount, account_number}, _from, state) do
account = Account.fetch(%{account_number: account_number})
changeset =
Entry.changeset(%Entry{
description: "deposit : " <> to_string(amount) <> " into " <> to_string(account_number),
date: DateTime.truncate(DateTime.utc_now(), :second),
amounts: [
%Amount{amount: amount, type: "debit", account_id: account.id},
%Amount{amount: amount, type: "credit", account_id: account.id}
]
})
config = SoftBank.Config.new()
reply = Repo.insert(config, changeset)
reload()
{:reply, reply, state}
end
@impl true
def handle_call({:convert, amount, dest_currency}, _from, state) do
rates = Money.ExchangeRates.latest_rates()
new_amount = Money.to_currency(amount, dest_currency, rates)
{:reply, new_amount, state}
end
@impl true
def handle_call(:balance, _from, state) do
accounts = :ets.lookup(state.ref, :accounts)
{:ok, accounts} = Keyword.fetch(accounts, :accounts)
reply = Account.balance(Repo, accounts, nil)
{:reply, reply, state}
end
@impl true
def handle_call(:show, _from, state) do
reply = :ets.lookup(state.ref, :accounts)
{:reply, reply, state}
end
@impl true
def handle_cast(
:shutdown,
state
) do
{:stop, :normal, state}
end
@impl true
def handle_cast({:transfer, account_number, dest_account_number, amount}, state) do
destination_account = Account.fetch(%{account_number: dest_account_number})
params = %{amount: amount}
from = Account.fetch(%{account_number: account_number})
Transfer.send(from.account, destination_account, params)
reload()
{:noreply, state}
end
@impl true
def handle_info(:reload, state) do
data =
:ets.lookup(state.ref, :accounts)
|> Enum.map(fn x ->
account = Account.fetch(%{account_number: x.account_number}, Repo)
%{account | balance: Account.balance(Repo, x, nil)}
end)
:ets.insert(state.ref, {:accounts, data})
{:noreply, state}
end
@impl true
def handle_info({:DOWN, _ref, :process, _pid, _reason}, {names, refs}) do
:ets.delete(names)
{:noreply, {names, refs}}
end
@doc false
def via_tuple(hash, registry \\ @registry_name) do
{:via, Registry, {registry, hash}}
end
end