Skip to content

Commit

Permalink
feat: update book transfer form (#289)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gladear committed Oct 8, 2024
1 parent 382da2a commit d60886b
Show file tree
Hide file tree
Showing 37 changed files with 803 additions and 1,037 deletions.
23 changes: 1 addition & 22 deletions apps/app/lib/app/accounts/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ defmodule App.Accounts.User do
"""

use Ecto.Schema
import Ecto.Changeset

alias App.Balance.BalanceConfig
import Ecto.Changeset

@type id :: integer()
@type t :: %__MODULE__{
Expand All @@ -15,8 +14,6 @@ defmodule App.Accounts.User do
password: String.t(),
hashed_password: String.t(),
confirmed_at: NaiveDateTime.t(),
balance_config: BalanceConfig.t(),
balance_config_id: BalanceConfig.id(),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
Expand All @@ -28,9 +25,6 @@ defmodule App.Accounts.User do
field :hashed_password, :string, redact: true
field :confirmed_at, :naive_datetime

# the current balance configuration of the user
belongs_to :balance_config, BalanceConfig

timestamps()
end

Expand Down Expand Up @@ -175,19 +169,4 @@ defmodule App.Accounts.User do
add_error(changeset, :current_password, "is not valid")
end
end

@doc """
A user changeset for changing the user balance config.
"""
@spec balance_config_changeset(t(), map()) :: Ecto.Changeset.t()
def balance_config_changeset(struct, attrs) do
struct
|> cast(attrs, [:balance_config_id])
|> validate_balance_config_id()
end

defp validate_balance_config_id(changeset) do
changeset
|> foreign_key_constraint(:balance_config_id)
end
end
66 changes: 27 additions & 39 deletions apps/app/lib/app/transfers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ defmodule App.Transfers do
Raises `Ecto.NoResultsError` if the Money transfer does not exist.
"""
def get_money_transfer_of_book!(id, book_id),
do: Repo.get_by!(MoneyTransfer, id: id, book_id: book_id)
def get_money_transfer_of_book!(id, %Book{} = book),
do: Repo.get_by!(MoneyTransfer, id: id, book_id: book.id)

@doc """
Find all money transfers of a book.
Expand Down Expand Up @@ -201,39 +201,32 @@ defmodule App.Transfers do
@doc """
Creates a money_transfer.
"""
@spec create_money_transfer(Book.t(), map()) ::
@spec create_money_transfer(Book.t(), MoneyTransfer.type(), map()) ::
{:ok, MoneyTransfer.t()} | {:error, Ecto.Changeset.t()}
def create_money_transfer(%Book{} = book, attrs \\ %{}) do
%MoneyTransfer{book_id: book.id}
|> MoneyTransfer.changeset(attrs)
|> MoneyTransfer.with_peers(&Peer.create_money_transfer_changeset/2)
|> link_balance_config_to_peers_changeset()
|> Repo.insert()
end
def create_money_transfer(%Book{} = book, type, attrs)
when is_map(attrs) and type in [:payment, :income] do
changeset =
%MoneyTransfer{book_id: book.id, type: type}
|> MoneyTransfer.changeset(attrs)

result =
Ecto.Multi.new()
|> Ecto.Multi.insert(:money_transfer, changeset)
|> Ecto.Multi.update_all(
:update_peers_balance_config,
fn %{money_transfer: money_transfer} ->
from [peer: peer] in Peer.transfer_query(money_transfer),
join: member in BookMember,
on: peer.member_id == member.id,
update: [set: [balance_config_id: member.balance_config_id]]
end,
[]
)
|> Repo.transaction()

defp link_balance_config_to_peers_changeset(changeset) do
case Ecto.Changeset.fetch_change(changeset, :peers) do
{:ok, peers_changeset} ->
peers =
changeset
|> Ecto.Changeset.fetch_field!(:peers)
# Members are required to fetch their balance config id
|> Repo.preload(member: from(BookMember, select: [:balance_config_id]))

balance_config_id_by_member_id =
Map.new(peers, fn peer -> {peer.member_id, peer.member.balance_config_id} end)

peers_changeset_with_balance_config =
Enum.map(peers_changeset, fn peer_changeset ->
member_id = Ecto.Changeset.fetch_field!(peer_changeset, :member_id)
balance_config_id = balance_config_id_by_member_id[member_id]
Ecto.Changeset.put_change(peer_changeset, :balance_config_id, balance_config_id)
end)

Ecto.Changeset.put_assoc(changeset, :peers, peers_changeset_with_balance_config)

_ ->
changeset
case result do
{:ok, %{money_transfer: money_transfer}} -> {:ok, money_transfer}
{:error, :money_transfer, changeset, _changes} -> {:error, changeset}
end
end

Expand All @@ -244,10 +237,7 @@ defmodule App.Transfers do
{:ok, MoneyTransfer.t()} | {:error, Ecto.Changeset.t()}
def update_money_transfer(%MoneyTransfer{} = money_transfer, attrs) do
money_transfer
# peers can be updated by the changeset
|> Repo.preload(:peers)
|> MoneyTransfer.changeset(attrs)
|> MoneyTransfer.with_peers(&Peer.update_money_transfer_changeset/2)
|> Repo.update()
end

Expand All @@ -264,9 +254,7 @@ defmodule App.Transfers do
Returns an `%Ecto.Changeset{}` for tracking money_transfer changes.
"""
def change_money_transfer(%MoneyTransfer{} = money_transfer, attrs \\ %{}) do
money_transfer
|> MoneyTransfer.changeset(attrs)
|> MoneyTransfer.with_peers(&Peer.update_money_transfer_changeset/2)
MoneyTransfer.changeset(money_transfer, attrs)
end

## Reimbursements
Expand Down
28 changes: 5 additions & 23 deletions apps/app/lib/app/transfers/money_transfer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,12 @@ defmodule App.Transfers.MoneyTransfer do

def changeset(struct, attrs) do
struct
|> cast(attrs, [:label, :amount, :type, :date, :tenant_id, :balance_params])
|> cast(attrs, [:label, :date, :balance_params, :tenant_id, :amount])
|> validate_label()
|> validate_amount()
|> validate_type()
|> validate_tenant_id()
|> validate_book_id()
|> validate_balance_params()
end

def with_peers(changeset, with_changeset) do
changeset
|> cast_assoc(:peers, with: with_changeset)
|> validate_tenant_id()
|> validate_amount()
|> cast_assoc(:peers, with: &Ecto.Changeset.cast(&1, &2, [:member_id, :weight]))
end

@doc """
Expand All @@ -87,7 +81,7 @@ defmodule App.Transfers.MoneyTransfer do
|> cast(attrs, [:label, :amount, :date, :tenant_id])
|> validate_label()
|> validate_amount()
|> cast_assoc(:peers, with: &Peer.update_money_transfer_changeset/2)
|> cast_assoc(:peers, with: &Ecto.Changeset.cast(&1, &2, [:member_id]))
|> validate_reimbursement_peers()
end

Expand All @@ -102,24 +96,12 @@ defmodule App.Transfers.MoneyTransfer do
|> validate_required(:amount)
end

defp validate_type(changeset) do
changeset
|> validate_required(:type)
|> validate_inclusion(:type, [:payment, :income])
end

defp validate_tenant_id(changeset) do
changeset
|> validate_required(:tenant_id)
|> foreign_key_constraint(:tenant_id)
end

defp validate_book_id(changeset) do
changeset
|> validate_required(:book_id)
|> foreign_key_constraint(:book_id)
end

defp validate_balance_params(changeset) do
changeset
|> validate_required(:balance_params)
Expand Down
66 changes: 11 additions & 55 deletions apps/app/lib/app/transfers/peer.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
defmodule App.Transfers.Peer do
@moduledoc """
Entity linking money transfers to users.
The entity linking money transfers to book members.
"""

use Ecto.Schema
import Ecto.Changeset

import Ecto.Query

alias App.Balance.BalanceConfig
Expand Down Expand Up @@ -33,59 +33,6 @@ defmodule App.Transfers.Peer do
field :total_weight, :decimal, virtual: true
end

def create_money_transfer_changeset(struct, attrs) do
struct
|> cast(attrs, [:member_id, :weight])
|> validate_member_id()
|> validate_unique_by_transfer_and_member()
end

def update_money_transfer_changeset(struct, attrs)

# new peer built, must set a `member_id`
def update_money_transfer_changeset(%{id: nil} = struct, attrs) do
struct
|> cast(attrs, [:member_id, :weight])
|> validate_member_id()
|> validate_unique_by_transfer_and_member()
end

# updating an existing peer, cannot change `member_id`
def update_money_transfer_changeset(struct, attrs) do
struct
|> cast(attrs, [:weight])
|> validate_unique_by_transfer_and_member()
end

defp validate_member_id(changeset) do
changeset
|> validate_required(:member_id)
|> foreign_key_constraint(:member_id)
end

defp validate_unique_by_transfer_and_member(changeset) do
changeset
|> unique_constraint([:transfer_id, :member_id],
message: "member is already a peer of this money transfer",
error_key: :member_id
)
end

@doc """
Changeset for updating the balance config of a book member.
"""
@spec balance_config_changeset(t(), map()) :: Ecto.Changeset.t()
def balance_config_changeset(struct, attrs) do
struct
|> cast(attrs, [:balance_config_id])
|> validate_balance_config_id()
end

defp validate_balance_config_id(changeset) do
changeset
|> foreign_key_constraint(:balance_config_id)
end

## Queries

@doc """
Expand All @@ -95,4 +42,13 @@ defmodule App.Transfers.Peer do
def base_query do
from peer in __MODULE__, as: :peer
end

@doc """
Returns an `%Ecto.Query{}` fetching all peers of a given money transfer.
"""
@spec transfer_query(Ecto.Queryable.t(), MoneyTransfer.t()) :: Ecto.Query.t()
def transfer_query(query \\ base_query(), %MoneyTransfer{} = transfer) do
from [peer: peer] in query,
where: peer.transfer_id == ^transfer.id
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule App.Repo.Migrations.DropUsersBalanceConfigId do
use Ecto.Migration

def change do
alter table(:users) do
remove :balance_config_id, references(:balance_configs, on_delete: :restrict)
end
end
end
40 changes: 0 additions & 40 deletions apps/app/test/app/transfers/peer_test.exs

This file was deleted.

Loading

0 comments on commit d60886b

Please sign in to comment.