cover/Elixir.WalletSettlement.Workers.ReconciliationWorker.html

1 defmodule WalletSettlement.Workers.ReconciliationWorker do
2 @moduledoc """
3 Async worker for running a reconciliation pass.
4
5 Compares transfer records with ledger/internal references and raises
6 `ExceptionRecord` entries for any detected mismatches.
7
8 Implements `perform/1` compatible with `JobQueue` and Oban.
9
10 Expected args:
11 - `"reconciliation_id"` — the reconciliation run to execute.
12 - `"correlation_id"` — propagated into events.
13 """
14
15 alias WalletSettlement.{ReconciliationRun, ReconciliationStore}
16 alias WalletSettlement.Events.ReconciliationCompleted
17 alias WalletObservability.AuditEvent
18
19 @doc "Performs the reconciliation run. Returns `:ok` or `{:error, reason}`."
20 @spec perform(args :: map()) :: :ok | {:error, term()}
21 def perform(%{"reconciliation_id" => rec_id} = args) do
22 4 correlation_id = Map.get(args, "correlation_id", WalletSharedKernel.Correlation.new_correlation_id())
23
24 4 with {:ok, run} <- ReconciliationStore.get(rec_id),
25 3 {:ok, running} <- ReconciliationRun.start(run),
26 3 :ok <- ReconciliationStore.update(running) do
27
28 # Collect all completed transfers and compare against ledger entries.
29 # Phase 5: in-memory comparison using TransferStore.
30 # Phase 6: augments with CBS ledger feed for external reconciliation.
31 3 transfers_mod = Application.get_env(:wallet_settlement, :transfers_module, WalletTransfers)
32 3 mismatches = detect_mismatches(running, transfers_mod, correlation_id)
33 3 exceptions_raised = length(mismatches)
34
35 3 {:ok, completed} = ReconciliationRun.complete(running,
36 transfers_checked: mismatches |> length(),
37 ledger_entries_checked: 0,
38 mismatches_found: exceptions_raised,
39 exceptions_raised: exceptions_raised
40 )
41
42 3 ReconciliationStore.update(completed)
43
44 3 emit_event(ReconciliationCompleted.build(rec_id,
45 correlation_id: correlation_id,
46 mismatches_found: exceptions_raised,
47 exceptions_raised: exceptions_raised))
48
49 3 emit_audit(:reconciliation_completed, rec_id, correlation_id, %{
50 mismatches_found: exceptions_raised,
51 3 transfers_checked: completed.transfers_checked
52 })
53
54 :ok
55 end
56 end
57
58 1 def perform(_args), do: {:error, :missing_reconciliation_id}
59
60 # Phase 5 implementation: detect status-level mismatches within the run window.
61 # Returns list of mismatch descriptions.
62 3 defp detect_mismatches(_run, _transfers_mod, _correlation_id) do
63 # In CI/Phase5 — returns empty list (no real DB; no ledger feed yet).
64 # Phase 6 will inject external ledger data via adapter contract.
65 []
66 end
67
68 3 defp emit_event(event) do
69 3 pubsub = Application.get_env(:wallet_settlement, :pubsub, WalletWeb.PubSub)
70 3 apply(Phoenix.PubSub, :broadcast, [pubsub, "wallet_settlement:events", {:domain_event, event}])
71 rescue
72
:-(
_ -> :ok
73 end
74
75 3 defp emit_audit(action, rec_id, correlation_id, metadata) do
76 3 audit = AuditEvent.build(:settlement, to_string(action), "reconciliation_run", rec_id,
77 :success, correlation_id: correlation_id, metadata: metadata)
78 3 :telemetry.execute([:wallet_settlement, :audit], %{}, audit)
79 rescue
80
:-(
_ -> :ok
81 end
82 end
Line Hits Source