diff --git a/lib/transfer/changes/verify_transfer.ex b/lib/transfer/changes/verify_transfer.ex index 69985f6..4d3fe3c 100644 --- a/lib/transfer/changes/verify_transfer.ex +++ b/lib/transfer/changes/verify_transfer.ex @@ -9,32 +9,33 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do require Ash.Query def change(changeset, _opts, context) do - if changeset.action.type == :update do - if Enum.any?( - [:from_account_id, :to_account_id, :amount, :id], + if changeset.action.type == :update and + Enum.any?( + [:from_account_id, :to_account_id, :id], &Ash.Changeset.changing_attribute?(changeset, &1) ) do - Ash.Changeset.add_error( - changeset, - "Cannot modify a transfer's from_account_id, to_account_id, amount, or id" - ) - else - changeset - end + Ash.Changeset.add_error( + changeset, + "Cannot modify a transfer's from_account_id, to_account_id, or id" + ) else changeset |> Ash.Changeset.before_action(fn changeset -> - timestamp = Ash.Changeset.get_attribute(changeset, :timestamp) + if changeset.action.type == :create do + timestamp = Ash.Changeset.get_attribute(changeset, :timestamp) - timestamp = - case timestamp do - nil -> System.system_time(:millisecond) - timestamp -> DateTime.to_unix(timestamp, :millisecond) - end + timestamp = + case timestamp do + nil -> System.system_time(:millisecond) + timestamp -> DateTime.to_unix(timestamp, :millisecond) + end - ulid = AshDoubleEntry.ULID.generate(timestamp) + ulid = AshDoubleEntry.ULID.generate(timestamp) - Ash.Changeset.force_change_attribute(changeset, :id, ulid) + Ash.Changeset.force_change_attribute(changeset, :id, ulid) + else + changeset + end end) |> maybe_destroy_balances(context) |> Ash.Changeset.after_action(fn changeset, result -> @@ -42,6 +43,9 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do to_account_id = Ash.Changeset.get_attribute(changeset, :to_account_id) amount = Ash.Changeset.get_attribute(changeset, :amount) + amount_delta = + Money.sub!(amount, changeset.data.amount || Money.new!(0, amount.currency)) + accounts = changeset.resource |> AshDoubleEntry.Transfer.Info.transfer_account_resource!() @@ -56,11 +60,14 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do new_from_account_balance = Money.sub!( from_account.balance_as_of_ulid || Money.new!(0, from_account.currency), - amount + amount_delta ) new_to_account_balance = - Money.add!(to_account.balance_as_of_ulid || Money.new!(0, to_account.currency), amount) + Money.add!( + to_account.balance_as_of_ulid || Money.new!(0, to_account.currency), + amount_delta + ) unless changeset.action.type == :destroy do changeset.resource @@ -102,13 +109,13 @@ defmodule AshDoubleEntry.Transfer.Changes.VerifyTransfer do %{ account_id: balance.account_id, transfer_id: balance.transfer_id, - balance: Money.sub!(balance.balance, amount) + balance: Money.sub!(balance.balance, amount_delta) } else %{ account_id: balance.account_id, transfer_id: balance.transfer_id, - balance: Money.add!(balance.balance, amount) + balance: Money.add!(balance.balance, amount_delta) } end end) diff --git a/test/ash_double_entry_test.exs b/test/ash_double_entry_test.exs index 6bbda9c..e7f76f4 100644 --- a/test/ash_double_entry_test.exs +++ b/test/ash_double_entry_test.exs @@ -33,7 +33,7 @@ defmodule AshDoubleEntryTest do end actions do - defaults [:destroy] + defaults [:destroy, :update] end end @@ -190,6 +190,38 @@ defmodule AshDoubleEntryTest do ) end + test "updating transfer's amount update the balances accordingly" do + account_one = + Account + |> Ash.Changeset.for_create(:open, %{identifier: "account_one", currency: "USD"}) + |> Api.create!() + + account_two = + Account + |> Ash.Changeset.for_create(:open, %{identifier: "account_two", currency: "USD"}) + |> Api.create!() + + Transfer + |> Ash.Changeset.for_create(:transfer, %{ + amount: Money.new!(:USD, 20), + from_account_id: account_one.id, + to_account_id: account_two.id + }) + |> Api.create!() + |> Ash.Changeset.for_update(:update, %{amount: Money.new!(:USD, 10)}) + |> Api.update!() + + assert Money.equal?( + Api.load!(account_one, :balance_as_of).balance_as_of, + Money.new!(:USD, -10) + ) + + assert Money.equal?( + Api.load!(account_two, :balance_as_of).balance_as_of, + Money.new!(:USD, 10) + ) + end + test "balances can be validated" do account_one = Account