cover/Elixir.WalletSettlement.ReconciliationRun.html

1 defmodule WalletSettlement.ReconciliationRun do
2 @moduledoc """
3 Reconciliation run struct.
4
5 A reconciliation run compares transfer records with ledger entries and
6 internal references to identify mismatches and exceptions.
7
8 Lifecycle:
9 :pending -> :running -> :completed | :failed
10
11 TypedId prefix: `rec_`
12 """
13
14 alias WalletSharedKernel.TypedId
15
16 @enforce_keys [:reconciliation_id, :window_start, :window_end, :status, :created_at]
17
:-(
defstruct [
18 :reconciliation_id,
19 :window_start,
20 :window_end,
21 :status,
22 :correlation_id,
23 :started_at,
24 :completed_at,
25 :failure_reason,
26 :created_at,
27 transfers_checked: 0,
28 ledger_entries_checked: 0,
29 mismatches_found: 0,
30 exceptions_raised: 0,
31 metadata: %{}
32 ]
33
34 @type status :: :pending | :running | :completed | :failed
35
36 @type t :: %__MODULE__{
37 reconciliation_id: String.t(),
38 window_start: DateTime.t(),
39 window_end: DateTime.t(),
40 status: status(),
41 correlation_id: String.t() | nil,
42 started_at: DateTime.t() | nil,
43 completed_at: DateTime.t() | nil,
44 failure_reason: String.t() | nil,
45 created_at: DateTime.t(),
46 transfers_checked: non_neg_integer(),
47 ledger_entries_checked: non_neg_integer(),
48 mismatches_found: non_neg_integer(),
49 exceptions_raised: non_neg_integer(),
50 metadata: map()
51 }
52
53 @doc "Creates a new pending reconciliation run."
54 @spec new(window_start :: DateTime.t(), window_end :: DateTime.t(), opts :: keyword()) :: t()
55 5 def new(window_start, window_end, opts \\ []) do
56 10 %__MODULE__{
57 reconciliation_id: TypedId.generate("rec"),
58 window_start: window_start,
59 window_end: window_end,
60 status: :pending,
61 correlation_id: Keyword.get(opts, :correlation_id),
62 created_at: DateTime.utc_now(),
63 metadata: Keyword.get(opts, :metadata, %{})
64 }
65 end
66
67 @doc "Transitions pending run to running."
68 @spec start(t()) :: {:ok, t()} | {:error, :invalid_transition}
69 6 def start(%__MODULE__{status: :pending} = run) do
70 {:ok, %{run | status: :running, started_at: DateTime.utc_now()}}
71 end
72
:-(
def start(%__MODULE__{}), do: {:error, :invalid_transition}
73
74 @doc "Marks a running reconciliation as completed with result counts."
75 @spec complete(t(), opts :: keyword()) :: {:ok, t()} | {:error, :invalid_transition}
76 1 def complete(run, opts \\ [])
77 4 def complete(%__MODULE__{status: :running} = run, opts) do
78 {:ok, %{run |
79 status: :completed,
80 completed_at: DateTime.utc_now(),
81 transfers_checked: Keyword.get(opts, :transfers_checked, 0),
82 ledger_entries_checked: Keyword.get(opts, :ledger_entries_checked, 0),
83 mismatches_found: Keyword.get(opts, :mismatches_found, 0),
84 exceptions_raised: Keyword.get(opts, :exceptions_raised, 0)
85 }}
86 end
87 1 def complete(%__MODULE__{}, _opts), do: {:error, :invalid_transition}
88
89 @doc "Fails a running reconciliation run."
90 @spec fail(t(), reason :: String.t()) :: {:ok, t()} | {:error, :invalid_transition}
91 1 def fail(%__MODULE__{status: :running} = run, reason) do
92 {:ok, %{run | status: :failed, failure_reason: reason, completed_at: DateTime.utc_now()}}
93 end
94
:-(
def fail(%__MODULE__{}), do: {:error, :invalid_transition}
95 end
Line Hits Source