Skip to content

Instantly share code, notes, and snippets.

@boriscy
Created June 11, 2016 00:01
Show Gist options
  • Select an option

  • Save boriscy/eb744132c30539c40ab96f172e87b530 to your computer and use it in GitHub Desktop.

Select an option

Save boriscy/eb744132c30539c40ab96f172e87b530 to your computer and use it in GitHub Desktop.
Payment
defmodule Bonsai.Payment do
use Bonsai.Web, :model
alias Bonsai.{User, Ledger, Repo}
@zero Decimal.new(0)
@primary_key {:id, :binary_id, autogenerate: true}
embedded_schema do
field :amount, :decimal
field :date, Ecto.Date
field :note, :string
field :user_id, :binary_id
field :image, :map
timestamps
end
@required_fields ~w(amount date user_id)
@optional_fields ~w(note)
defp changeset(model, params) do
model
|> cast(params, @required_fields, @optional_fields)
|> validate_required([:amount, :date, :user_id])
end
@doc """
Changeset to store Ledger with type = "income"
"""
def changeset(model, params, "income") do
changeset(model, params)
|> validate_number(:amount, greater_than: @zero)
end
@doc """
Changeset to store Ledger with type = "expense"
"""
def changeset(model, params, "expense") do
changeset(model, params)
|> validate_number(:amount, less_than: @zero)
end
def create(ledger_id, params, user_id) when is_binary(ledger_id) do
case Repo.get_by(Ledger, id: ledger_id, type: params["type"]) do
nil ->
{:error, "invalid ledger"}
ledger ->
create(ledger, Map.merge(params, %{"user_id" => user_id}) )
end
end
def create(ledger, params) do
if compare_balance(ledger.type, ledger) do
create_payment(ledger, params)
else
{:error, "invalid balance"}
end
end
defp create_payment(ledger, params) do
payment = build_payment(ledger, params)
if payment.valid? do
bal = Decimal.sub(ledger.balance, payment.changes.amount)
Ledger.changeset_payment(ledger, %{balance: bal, info: %{updated_by: params["user_id"]} })
|> put_embed(:payments, Enum.map(ledger.payments, fn(l) -> change(l) end) ++ [payment] )
|> Repo.update
else
{:error, payment}
end
end
defp compare_balance("income", ledger) do
Decimal.compare(@zero, ledger.balance) == Decimal.new(-1)
end
defp compare_balance("expense", ledger) do
Decimal.compare(@zero, ledger.balance) == Decimal.new(1)
end
defp build_payment(ledger, %{"type" => "income"} = params) do
Bonsai.Payment.changeset(%Bonsai.Payment{}, params, "income")
|> validate_number(:amount, less_than_or_equal_to: ledger.balance)
end
defp build_payment(ledger, %{"type" => "expense"} = params) do
Bonsai.Payment.changeset(%Bonsai.Payment{}, params, "expense")
|> validate_number(:amount, greater_than_or_equal_to: ledger.balance)
end
end
defmodule Bonsai.PaymentTest do
use Bonsai.ModelCase
import Bonsai.Factory
alias Bonsai.{Payment, Ledger}
##################################################
# Income
@attrs_inc %{amount: "120.5", date: "2010-04-17", ledger_id: "40d8d7a7-9499-4b3c-8a57-024f14e5b895", user_id: "8a4afd36-d842-44d6-a107-e6ddf423f417"}
@invalid_attrs %{}
@user_id "019a0df4-4ee7-4f45-81ac-260450236f21"
test "changeset with valid attributes income" do
changeset = Payment.changeset(%Payment{}, @attrs_inc, "income")
assert changeset.valid?
end
test "valid amount for income" do
changeset = Payment.changeset(%Payment{}, Map.merge(@attrs_inc, %{amount: "-10"}), "income")
refute changeset.valid?
end
test "changeset with invalid attributes income" do
changeset = Payment.changeset(%Payment{}, %{amount: "-10", date: "aaa", ledger_id: ""}, "income")
refute changeset.valid?
refute changeset.errors[:date] == nil
refute changeset.errors[:amount] == nil
end
##################################################
# Expense
@attrs_exp %{amount: "-120.5", date: "2010-04-17", user_id: "8a4afd36-d842-44d6-a107-e6ddf423f417"}
test "changeset with valid attributes expense" do
changeset = Payment.changeset(%Payment{}, @attrs_exp, "expense")
assert changeset.valid?
end
test "valid amount for expense" do
changeset = Payment.changeset(%Payment{}, Map.merge(@attrs_inc, %{amount: "10"}), "expense")
refute changeset.valid?
end
test "changeset with invalid attributes expense" do
changeset = Payment.changeset(%Payment{}, %{amount: "10", date: "aaa", ledger_id: ""}, "expense")
refute changeset.valid?
refute changeset.errors[:date] == nil
refute changeset.errors[:amount] == nil
end
##############################################################
# Payments income
def create_organization, do: insert(:organization)
def create_contact, do: insert(:contact, organization_id: create_organization.id)
@income_attrs %{"amount" => "100", "balance" => "100", "date" => "2016-06-01",
"description" => "An Income", "currency" => "USD", "type" => "income"}
@valid_inc_attrs %{"amount" => "10", "date" => to_string(Ecto.Date.utc), "type" => "income" }
def create_income(attrs, user_id \\ @user_id) do
cont = create_contact
Ledger.create(Map.merge(attrs, %{"contact_name" => cont.name, "organization_id" => cont.organization_id }), user_id)
end
test "invalid ledger type" do
{:ok, ledger} = create_income @income_attrs
assert {:error, "invalid ledger"} == Payment.create(ledger.id, Map.merge(@valid_inc_attrs, %{"type" => "invalid type"}), @user_id)
end
test "invalid ledger id" do
assert {:error, "invalid ledger"} == Payment.create(@user_id, Map.merge(@valid_inc_attrs, %{"type" => "invalid type"}), @user_id)
end
test "income invalid ledger balance" do
{:ok, ledger} = create_income Map.merge(@income_attrs, %{"balance" => "0"})
assert {:error, "invalid balance"} == Payment.create(ledger.id, @valid_inc_attrs, @user_id)
end
test "income invalid amount" do
{:ok, ledger} = create_income Map.merge(@income_attrs, %{"balance" => "2"})
{:error, cs} = Payment.create(ledger.id, @valid_inc_attrs, @user_id)
assert cs.errors[:amount]
end
test "income payments 1" do
{:ok, ledger} = create_income @income_attrs
assert ledger.payments == []
{:ok, ledger} = Payment.create(ledger.id, @valid_inc_attrs, @user_id)
payment = List.last ledger.payments
assert ledger.balance == Decimal.new("90")
assert ledger.info.updated_by == @user_id
assert payment.amount == Decimal.new("10")
{:ok, date} = Ecto.Date.cast(@valid_inc_attrs["date"])
assert payment.date == date
assert payment.user_id == @user_id
attrs = Map.merge(@valid_inc_attrs, %{"amount" => 30})
{:ok, ledger} = Payment.create(ledger.id, attrs, @user_id)
assert ledger.balance == Decimal.new("60")
payment = List.last ledger.payments
assert payment.amount == Decimal.new("30")
end
test "invalid amount" do
{:ok, ledger} = create_income @income_attrs
attrs = Map.merge(@valid_inc_attrs, %{"amount" => "150"})
{:error, payment} = Payment.create(ledger.id, attrs, @user_id)
assert payment.errors[:amount] == {"must be less than or equal to %{number}", [number: Decimal.new("100")]}
end
test "invalid balance" do
{:ok, ledger} = create_income Map.merge(@income_attrs, %{"balance" => 0})
assert ledger.balance == Decimal.new(0)
{:error, "invalid balance"} = Payment.create(ledger.id, @valid_inc_attrs, @user_id)
end
##############################################################
# Payments expense
@expense_attrs %{"amount" => "-100", "balance" => "-100", "date" => "2016-06-01",
"description" => "An Expense", "currency" => "USD", "type" => "expense"}
@valid_exp_attrs %{"amount" => -10, "date" => to_string(Ecto.Date.utc), "type" => "expense"}
def create_expense(attrs, user_id \\ @user_id) do
cont = create_contact
Ledger.create(Map.merge(attrs, %{"contact_name" => cont.name, "organization_id" => cont.organization_id }), user_id)
end
test "expense payments" do
{:ok, ledger} = create_expense @expense_attrs
assert ledger.payments == []
{:ok, ledger} = Payment.create(ledger.id, @valid_exp_attrs, @user_id)
payment = List.last ledger.payments
assert ledger.balance == Decimal.new(-90)
assert ledger.info.updated_by == @user_id
assert payment.amount == Decimal.new(-10)
{:ok, date} = Ecto.Date.cast(@valid_exp_attrs["date"])
assert payment.date == date
assert payment.user_id == @user_id
attrs = Map.merge(@valid_exp_attrs, %{"amount" => -30, "type" => "expense", "ledger_id" => ledger.id })
{:ok, ledger} = Payment.create(ledger.id, attrs, @user_id)
assert ledger.balance == Decimal.new(-60)
payment = List.last ledger.payments
assert payment.amount == Decimal.new(-30)
end
test "invalid amount expense" do
{:ok, ledger} = create_expense @expense_attrs
attrs = Map.merge(@valid_exp_attrs, %{"amount" => "-150"})
{:error, payment} = Payment.create(ledger.id, attrs, @user_id)
assert payment.errors[:amount] == {"must be greater than or equal to %{number}", [number: Decimal.new("-100")]}
end
test "invalid balance expense" do
{:ok, ledger} = create_expense Map.merge(@expense_attrs, %{"balance" => 0})
assert ledger.balance == Decimal.new(0)
{:error, "invalid balance"} = Payment.create(ledger.id, @valid_exp_attrs, @user_id)
end
test "invalid ledger" do
{:ok, ledger} = create_income @income_attrs
assert ledger.type == "income"
attrs = Map.merge(@valid_inc_attrs, %{"type" => "expense"})
{:error, "invalid ledger"} = Payment.create(ledger.id, attrs, @user_id)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment