cover/Elixir.DaProductApp.Settlements.Settlement.html

1 defmodule DaProductApp.Settlements.Settlement do
2 @moduledoc """
3 Schema for settlement records supporting financial reconciliation.
4
5 Settlements represent the process of transferring aggregated transaction
6 amounts to partners and merchants on a scheduled basis.
7 """
8 use Ecto.Schema
9 import Ecto.Changeset
10
11 alias DaProductApp.Partners.{Partner, Merchant}
12 alias DaProductApp.Transactions.Transaction
13
14 @settlement_types ~w(net merchant partner)
15 @settlement_statuses ~w(pending scheduled processing approved bank_processing completed failed cancelled)
16 @frequencies ~w(daily weekly monthly)
17 @priorities ~w(low normal high urgent)
18 @reconciliation_statuses ~w(pending reconciled disputed failed)
19
20
:-(
schema "settlements" do
21 field :reference_id, :string
22 field :batch_id, :string
23 field :type, :string
24 field :status, :string, default: "pending"
25 field :frequency, :string, default: "daily"
26 field :priority, :string, default: "normal"
27
28 # Amount details
29 field :settlement_amount, :decimal
30 field :fee_amount, :decimal
31 field :tax_amount, :decimal
32 field :net_amount, :decimal
33 field :transaction_count, :integer, default: 0
34 field :currency, :string, default: "INR"
35
36 # Partner/merchant details
37 field :partner_account_number, :string
38 field :partner_ifsc, :string
39 field :partner_bank_name, :string
40
41 # Timing details
42 field :scheduled_at, :utc_datetime
43 field :started_at, :utc_datetime
44 field :approved_at, :utc_datetime
45 field :bank_processed_at, :utc_datetime
46 field :completed_at, :utc_datetime
47 field :settlement_window_start, :utc_datetime
48 field :settlement_window_end, :utc_datetime
49
50 # Error handling
51 field :failure_code, :string
52 field :failure_reason, :string
53 field :retry_count, :integer, default: 0
54
55 # Reconciliation
56 field :reconciliation_status, :string, default: "pending"
57 field :reconciled_at, :utc_datetime
58 field :reconciliation_reference, :string
59
60 # Relationships
61 belongs_to :partner, Partner
62 belongs_to :merchant, Merchant
63 has_many :transactions, Transaction, foreign_key: :settlement_id
64
65 timestamps(type: :utc_datetime)
66 end
67
68 @required_fields ~w(reference_id type settlement_amount net_amount)a
69 @optional_fields ~w(
70 batch_id status frequency priority fee_amount tax_amount transaction_count currency
71 partner_id merchant_id partner_account_number partner_ifsc partner_bank_name
72 scheduled_at started_at approved_at bank_processed_at completed_at
73 settlement_window_start settlement_window_end failure_code failure_reason retry_count
74 reconciliation_status reconciled_at reconciliation_reference
75 )a
76
77 def changeset(settlement, attrs) do
78 settlement
79 |> cast(attrs, @required_fields ++ @optional_fields)
80 |> validate_required(@required_fields)
81 |> validate_inclusion(:type, @settlement_types)
82 |> validate_inclusion(:status, @settlement_statuses)
83 |> validate_inclusion(:frequency, @frequencies)
84 |> validate_inclusion(:priority, @priorities)
85 |> validate_inclusion(:reconciliation_status, @reconciliation_statuses)
86 |> validate_number(:settlement_amount, greater_than: 0)
87 |> validate_number(:net_amount, greater_than: 0)
88 |> validate_number(:retry_count, greater_than_or_equal_to: 0)
89 |> validate_length(:currency, is: 3)
90 |> unique_constraint(:reference_id)
91 |> foreign_key_constraint(:partner_id)
92 |> foreign_key_constraint(:merchant_id)
93 |> validate_partner_or_merchant_required()
94
:-(
|> calculate_net_amount()
95 end
96
97 def create_changeset(attrs) do
98 %__MODULE__{}
99 |> changeset(attrs)
100
:-(
|> put_change(:reference_id, generate_reference_id())
101 end
102
103 defp validate_partner_or_merchant_required(changeset) do
104
:-(
partner_id = get_field(changeset, :partner_id)
105
:-(
merchant_id = get_field(changeset, :merchant_id)
106
107
:-(
if is_nil(partner_id) and is_nil(merchant_id) do
108
:-(
add_error(changeset, :base, "Either partner_id or merchant_id must be present")
109 else
110
:-(
changeset
111 end
112 end
113
114 defp calculate_net_amount(changeset) do
115
:-(
settlement_amount = get_field(changeset, :settlement_amount)
116
:-(
fee_amount = get_field(changeset, :fee_amount) || Decimal.new(0)
117
:-(
tax_amount = get_field(changeset, :tax_amount) || Decimal.new(0)
118
119
:-(
if settlement_amount do
120
:-(
net_amount =
121 settlement_amount
122 |> Decimal.sub(fee_amount)
123 |> Decimal.sub(tax_amount)
124
125
:-(
put_change(changeset, :net_amount, net_amount)
126 else
127
:-(
changeset
128 end
129 end
130
131 defp generate_reference_id do
132
:-(
"SET" <>
133
:-(
(DateTime.utc_now() |> DateTime.to_unix() |> to_string()) <>
134
:-(
(:rand.uniform(9999) |> to_string() |> String.pad_leading(4, "0"))
135 end
136 end
Line Hits Source