cover/Elixir.DaProductAppWeb.ReqChkTxnLive.html

1
:-(
defmodule DaProductAppWeb.ReqChkTxnLive do
2 @moduledoc """
3 LiveView for ReqChkTxn (Transaction Check) monitoring and analytics.
4
5 Features:
6 - Read-only transaction check monitoring
7 - Search and filter capabilities
8 - Real-time check status updates
9 - Transaction check details and history
10 - Check analytics and metrics
11 """
12 use DaProductAppWeb, :live_view
13
14 alias DaProductApp.Repo
15 alias DaProductApp.Transactions.{ReqChkTxn, Transaction}
16 alias DaProductApp.Accounts
17 require Logger
18 import Ecto.Query
19
20 @impl true
21
:-(
def mount(_params, session, socket) do
22
:-(
case get_current_user(session) do
23
:-(
nil ->
24 {:ok, redirect(socket, to: ~p"/login")}
25
26 user ->
27
:-(
if has_access?(user) do
28
:-(
if connected?(socket) do
29 # Subscribe to transaction check events for real-time updates
30
:-(
Phoenix.PubSub.subscribe(DaProductApp.PubSub, "req_chk_txns")
31 end
32
33
:-(
socket =
34 socket
35 |> assign(:current_user, user)
36 |> assign(:page_title, "Transaction Check Monitoring")
37 |> assign(:current_page, :req_chk_txns)
38 |> assign(:transactions, [])
39 |> assign(:search_term, "")
40 |> assign(:status_filter, :all)
41 |> assign(:validation_type_filter, :all)
42 |> assign(:transaction_status_filter, :all)
43 |> assign(:corridor_filter, :all)
44 |> assign(:page, 1)
45 |> assign(:page_size, 50)
46 |> assign(:has_more, false)
47 |> assign(:date_from, nil)
48 |> assign(:date_to, nil)
49 |> assign(:selected_transaction, nil)
50 |> assign(:show_details_modal, false)
51 |> assign(:stats, %{})
52 |> load_transactions()
53 |> load_stats()
54
55 {:ok, socket}
56 else
57 {:ok,
58 socket
59 |> put_flash(:error, "Access denied")
60 |> redirect(to: ~p"/login")}
61 end
62 end
63 end
64
65 @impl true
66
:-(
def handle_params(params, _url, socket) do
67
:-(
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
68 end
69
70 defp apply_action(socket, :index, _params) do
71 socket
72 |> assign(:page_title, "Transaction Check Monitoring")
73
:-(
|> assign(:show_details_modal, false)
74 end
75
76 defp apply_action(socket, :show, %{"id" => id}) do
77 # Try to load a Transaction by id first (list rows are transactions)
78
:-(
transaction = get_transaction_by_id(id)
79
80 # Prepare events query ordering
81
:-(
events_query = from(e in DaProductApp.Transactions.TransactionEvent, order_by: [asc: e.seq])
82
83
:-(
selected =
84 cond do
85 # If we found a transaction, preload its events and try to locate the linked ReqChkTxn
86 transaction != nil ->
87
:-(
txn = Repo.preload(transaction, [events: events_query])
88
89 # Try several strategies to find the req_chk record linked to this transaction
90
:-(
req_chk =
91
:-(
Repo.get_by(DaProductApp.Transactions.ReqChkTxn, original_txn_id: txn.org_txn_id)
92 |> case do
93
:-(
nil -> Repo.get_by(DaProductApp.Transactions.ReqChkTxn, msg_id: txn.chktxn_ref_id)
94
:-(
r -> r
95 end
96
97
:-(
req_chk = if req_chk, do: Repo.preload(req_chk, [:transaction, events: events_query]), else: nil
98
99
:-(
req_chk || txn
100
101 # If we couldn't find a transaction, try to load a ReqChkTxn directly by id
102
:-(
true ->
103
:-(
case Integer.parse(id) do
104 {int_id, _} ->
105 Repo.get(DaProductApp.Transactions.ReqChkTxn, int_id)
106
:-(
|> case do
107
:-(
nil -> nil
108
:-(
r -> Repo.preload(r, [:transaction, events: events_query])
109 end
110
111
:-(
:error -> nil
112 end
113 end
114
115
:-(
Logger.debug("ReqChkTxnLive show id=#{inspect(id)} selected_type=#{inspect(selected && selected.__struct__)} selected_id=#{inspect(selected && Map.get(selected, :id))}")
116
117 socket
118 |> assign(:page_title, "Transaction Check Details")
119 |> assign(:show_details_modal, true)
120
:-(
|> assign(:selected_transaction, selected)
121 end
122
123 @impl true
124
:-(
def handle_event("search", %{"search_term" => search_term}, socket) do
125
:-(
socket =
126 socket
127 |> assign(:search_term, search_term)
128 |> assign(:page, 1)
129 |> load_transactions()
130
131 {:noreply, socket}
132 end
133
134
:-(
def handle_event("filter_status", %{"status" => status}, socket) do
135
:-(
status_atom = if status == "all", do: :all, else: status
136
137
:-(
socket =
138 socket
139 |> assign(:status_filter, status_atom)
140 |> assign(:page, 1)
141 |> load_transactions()
142
143 {:noreply, socket}
144 end
145
146
:-(
def handle_event("filter_validation_type", %{"validation_type" => validation_type}, socket) do
147
:-(
validation_type_atom = if validation_type == "all", do: :all, else: validation_type
148
149
:-(
socket =
150 socket
151 |> assign(:validation_type_filter, validation_type_atom)
152 |> assign(:page, 1)
153 |> load_transactions()
154
155 {:noreply, socket}
156 end
157
158
:-(
def handle_event("filter_transaction_status", %{"transaction_status" => transaction_status}, socket) do
159
:-(
transaction_status_atom = if transaction_status == "all", do: :all, else: transaction_status
160
161
:-(
socket =
162 socket
163 |> assign(:transaction_status_filter, transaction_status_atom)
164 |> assign(:page, 1)
165 |> load_transactions()
166
167 {:noreply, socket}
168 end
169
170
:-(
def handle_event("filter_corridor", %{"corridor" => corridor}, socket) do
171
:-(
corridor_atom = if corridor == "all", do: :all, else: corridor
172
173
:-(
socket =
174 socket
175 |> assign(:corridor_filter, corridor_atom)
176 |> assign(:page, 1)
177 |> load_transactions()
178
179 {:noreply, socket}
180 end
181
182
:-(
def handle_event("refresh", _params, socket) do
183
:-(
socket =
184 socket
185 |> assign(:page, 1)
186 |> load_transactions()
187 |> load_stats()
188 |> put_flash(:info, "Data refreshed successfully")
189
190 {:noreply, socket}
191 end
192
193
:-(
def handle_event("show_details", %{"id" => id}, socket) do
194
:-(
transaction = get_transaction_by_id(id)
195
196
:-(
socket =
197 socket
198 |> assign(:selected_transaction, transaction)
199 |> assign(:show_details_modal, true)
200
201 {:noreply, socket}
202 end
203
204
:-(
def handle_event("close_details", _params, socket) do
205
:-(
socket =
206 socket
207 |> assign(:show_details_modal, false)
208 |> assign(:selected_transaction, nil)
209
210 {:noreply, socket}
211 end
212
213
:-(
def handle_event("clear_filters", _params, socket) do
214
:-(
socket =
215 socket
216 |> assign(:search_term, "")
217 |> assign(:status_filter, :all)
218 |> assign(:validation_type_filter, :all)
219 |> assign(:transaction_status_filter, :all)
220 |> assign(:corridor_filter, :all)
221 |> assign(:date_from, nil)
222 |> assign(:date_to, nil)
223 |> assign(:page, 1)
224 |> load_transactions()
225 |> put_flash(:info, "Filters cleared")
226
227 {:noreply, socket}
228 end
229
230 @impl true
231
:-(
def handle_info({:transaction_updated, _transaction}, socket) do
232
:-(
socket =
233 socket
234 |> load_transactions()
235 |> load_stats()
236
237 {:noreply, socket}
238 end
239
240 defp load_transactions(socket) do
241
:-(
page = socket.assigns.page || 1
242
:-(
page_size = socket.assigns.page_size || 50
243
:-(
offset = (page - 1) * page_size
244
245
:-(
query =
246 from t in Transaction,
247 where: not is_nil(t.chktxn_ref_id),
248 order_by: [desc: t.chktxn_requested_at],
249 limit: ^page_size,
250 offset: ^offset
251
252 # Apply search filter
253
:-(
query = if socket.assigns.search_term != "" do
254
:-(
search_term = "%#{socket.assigns.search_term}%"
255
:-(
from t in query,
256 where:
257 ilike(t.org_txn_id, ^search_term) or
258 ilike(t.payer_addr, ^search_term) or
259 ilike(t.payee_addr, ^search_term) or
260 ilike(t.chktxn_ref_id, ^search_term)
261 else
262
:-(
query
263 end
264
265 # Apply status filter
266
:-(
query = if socket.assigns.status_filter != :all do
267
:-(
from t in query, where: t.status == ^socket.assigns.status_filter
268 else
269
:-(
query
270 end
271
272 # Apply validation type filter (transaction_type)
273
:-(
query = if socket.assigns.validation_type_filter != :all do
274
:-(
from t in query, where: t.transaction_type == ^socket.assigns.validation_type_filter
275 else
276
:-(
query
277 end
278
279 # Apply transaction status filter (same as status for Transaction)
280
:-(
query = if socket.assigns.transaction_status_filter != :all do
281
:-(
from t in query, where: t.status == ^socket.assigns.transaction_status_filter
282 else
283
:-(
query
284 end
285
286 # Apply corridor filter
287
:-(
query = if socket.assigns.corridor_filter != :all do
288
:-(
from t in query, where: t.corridor == ^socket.assigns.corridor_filter
289 else
290
:-(
query
291 end
292
293
:-(
transactions = Repo.all(query)
294
295
:-(
has_more = length(transactions) >= page_size
296
297 socket
298 |> assign(:transactions, transactions)
299
:-(
|> assign(:has_more, has_more)
300 end
301
302
:-(
def handle_event("prev_page", _params, socket) do
303
:-(
page = max((socket.assigns.page || 1) - 1, 1)
304
305
:-(
socket =
306 socket
307 |> assign(:page, page)
308 |> load_transactions()
309
310 {:noreply, socket}
311 end
312
313
:-(
def handle_event("next_page", _params, socket) do
314
:-(
if socket.assigns.has_more do
315
:-(
page = (socket.assigns.page || 1) + 1
316
317
:-(
socket =
318 socket
319 |> assign(:page, page)
320 |> load_transactions()
321
322 {:noreply, socket}
323 else
324 {:noreply, socket}
325 end
326 end
327
328
:-(
def handle_event("set_page_size", %{"page_size" => page_size_str}, socket) do
329
:-(
page_size = case Integer.parse(page_size_str) do
330
:-(
{v, _} when v > 0 -> v
331
:-(
_ -> socket.assigns.page_size || 50
332 end
333
334
:-(
socket =
335 socket
336 |> assign(:page_size, page_size)
337 |> assign(:page, 1)
338 |> load_transactions()
339
340 {:noreply, socket}
341 end
342
343 defp load_stats(socket) do
344 # Compute basic stats from transactions table where chktxn_ref_id is not null
345
:-(
base_query = from t in Transaction, where: not is_nil(t.chktxn_ref_id)
346
347
:-(
total_count = Repo.aggregate(base_query, :count, :id)
348
:-(
processing_count = Repo.aggregate(from(t in Transaction, where: not is_nil(t.chktxn_ref_id) and t.status == "processing"), :count, :id)
349
:-(
success_count = Repo.aggregate(from(t in Transaction, where: not is_nil(t.chktxn_ref_id) and t.status == "success"), :count, :id)
350
:-(
failure_count = Repo.aggregate(from(t in Transaction, where: not is_nil(t.chktxn_ref_id) and t.status == "failure"), :count, :id)
351
:-(
pending_count = Repo.aggregate(from(t in Transaction, where: not is_nil(t.chktxn_ref_id) and t.status == "pending"), :count, :id)
352
353
:-(
success_rate = if total_count > 0, do: Float.round(success_count / total_count * 100.0, 1), else: 0.0
354
355 # Group counts by status
356
:-(
status_counts = Repo.all(from(t in Transaction, where: not is_nil(t.chktxn_ref_id), group_by: t.status, select: {t.status, count(t.id)}))
357
:-(
|> Enum.into(%{}, fn {k, v} -> {k || "UNKNOWN", v} end)
358
359 # Group counts by transaction type
360
:-(
transaction_type_counts = Repo.all(from(t in Transaction, where: not is_nil(t.chktxn_ref_id), group_by: t.transaction_type, select: {t.transaction_type, count(t.id)}))
361
:-(
|> Enum.into(%{}, fn {k, v} -> {k || "UNKNOWN", v} end)
362
363 # Group counts by corridor
364
:-(
corridor_counts = Repo.all(from(t in Transaction, where: not is_nil(t.chktxn_ref_id), group_by: t.corridor, select: {t.corridor, count(t.id)}))
365
:-(
|> Enum.into(%{}, fn {k, v} -> {k || "UNKNOWN", v} end)
366
367
:-(
stats = %{
368 total_count: total_count,
369 processing_count: processing_count,
370 success_count: success_count,
371 failure_count: failure_count,
372 pending_count: pending_count,
373 success_rate: success_rate,
374 status_counts: status_counts,
375 transaction_type_counts: transaction_type_counts,
376 corridor_counts: corridor_counts
377 }
378
379
:-(
assign(socket, :stats, stats)
380 end
381
382 defp get_transaction_by_id(id) do
383
:-(
case Integer.parse(id) do
384 {int_id, _} ->
385 # Lookup transaction by its id where chktxn_ref_id is not null
386
:-(
case Repo.get_by(Transaction, id: int_id) do
387
:-(
nil -> nil
388 transaction ->
389
:-(
if transaction.chktxn_ref_id, do: transaction, else: nil
390 end
391
:-(
:error -> nil
392 end
393 end
394
395 defp get_sample_transactions do
396 from(
397 t in Transaction,
398 where: not is_nil(t.chktxn_ref_id),
399 order_by: [desc: t.chktxn_requested_at],
400 limit: 100
401 )
402 |> Repo.all()
403 end
404
405
:-(
defp format_amount(nil), do: "-"
406 defp format_amount(amount) when is_struct(amount, Decimal) do
407
:-(
"₹#{Decimal.to_string(amount, :normal)}"
408 end
409
:-(
defp format_amount(amount), do: "₹#{amount}"
410
411
:-(
defp format_datetime(nil), do: "-"
412 defp format_datetime(datetime) do
413
:-(
Calendar.strftime(datetime, "%d/%m/%Y %H:%M:%S")
414 end
415
416 defp format_processing_time(nil), do: "-"
417 defp format_processing_time(ms) when is_number(ms) do
418 "#{ms}ms"
419 end
420 defp format_processing_time(_), do: "-"
421
422
:-(
defp status_badge_class("PENDING"), do: "badge-warning"
423
:-(
defp status_badge_class("PROCESSED"), do: "badge-success"
424
:-(
defp status_badge_class("FAILED"), do: "badge-error"
425
:-(
defp status_badge_class(_), do: "badge-neutral"
426
427
:-(
defp transaction_status_badge_class("SUCCESS"), do: "badge-success"
428
:-(
defp transaction_status_badge_class("PENDING"), do: "badge-warning"
429
:-(
defp transaction_status_badge_class("FAILURE"), do: "badge-error"
430
:-(
defp transaction_status_badge_class("EXPIRED"), do: "badge-error"
431
:-(
defp transaction_status_badge_class("DEEMED"), do: "badge-info"
432
:-(
defp transaction_status_badge_class("REVERSED"), do: "badge-secondary"
433
:-(
defp transaction_status_badge_class(_), do: "badge-neutral"
434
435
:-(
defp validation_type_badge_class("DOMESTIC"), do: "badge-primary"
436
:-(
defp validation_type_badge_class("INTERNATIONAL"), do: "badge-accent"
437
:-(
defp validation_type_badge_class(_), do: "badge-neutral"
438
439 # Helper functions for authentication and access control
440
:-(
defp get_current_user(session) do
441
:-(
case session do
442 %{"user_id" => user_id} when is_binary(user_id) or is_integer(user_id) ->
443
:-(
Accounts.get_user!(user_id)
444
:-(
_ ->
445 nil
446 end
447 rescue
448
:-(
_ -> nil
449 end
450
451
:-(
defp has_access?(_user), do: true
452 end
Line Hits Source