improvement: support updating transfer's amount (#8)

* support updating transfer's amount

Signed-off-by: Tw <tw19881113@gmail.com>

---------

Signed-off-by: Tw <tw19881113@gmail.com>
Co-authored-by: Zach Daniel <zachary.s.daniel@gmail.com>
This commit is contained in:
Tw 2023-12-12 21:12:44 +08:00 committed by GitHub
parent 86a9b33eb1
commit c113fa080d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 62 additions and 23 deletions

View file

@ -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)

View file

@ -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