defmodule DaProductApp.Monitoring do @moduledoc """ Monitoring context for read-only access to transactions, QR validations, analytics, international payments, and settlements. This context provides monitoring and analytics capabilities without performing any operational activities. It's designed for administrative oversight and insights. """ import Ecto.Query, warn: false alias DaProductApp.Repo alias DaProductApp.Transactions.{Transaction, ReqPay} alias DaProductApp.QRValidation.QRValidation alias DaProductApp.Accounts # ================================ # TRANSACTION MONITORING # ================================ @doc """ Returns a paginated list of transactions with optional filters. """ def list_transactions(opts \\ []) do search_term = Keyword.get(opts, :search_term, "") status_filter = Keyword.get(opts, :status, :all) state_filter = Keyword.get(opts, :state, :all) transaction_type_filter = Keyword.get(opts, :transaction_type, :all) date_from = Keyword.get(opts, :date_from) date_to = Keyword.get(opts, :date_to) # Pagination: support page & page_size OR offset & limit page = Keyword.get(opts, :page, nil) page_size = Keyword.get(opts, :page_size, nil) offset = Keyword.get(opts, :offset, 0) limit = Keyword.get(opts, :limit, nil) || page_size || 50 offset = case {page, page_size} do {p, ps} when is_integer(p) and is_integer(ps) and p > 0 -> (p - 1) * ps _ -> offset || 0 end query = from t in Transaction, left_join: p in assoc(t, :partner), left_join: m in assoc(t, :merchant), preload: [partner: p, merchant: m], order_by: [desc: t.inserted_at] query = apply_transaction_filters(query, search_term, status_filter, state_filter, transaction_type_filter, date_from, date_to) query |> limit(^limit) |> offset(^offset) |> Repo.all() end @doc """ Gets a single transaction with all associations. """ def get_transaction!(id) do Transaction |> preload([:partner, :merchant, :events]) |> Repo.get!(id) end @doc """ Returns transaction statistics for analytics. """ def get_transaction_stats(date_from \\ nil, date_to \\ nil) do query = from t in Transaction query = if date_from && date_to do # Convert dates to datetime if they are Date structs {start_datetime, end_datetime} = normalize_date_range(date_from, date_to) from t in query, where: t.inserted_at >= ^start_datetime and t.inserted_at <= ^end_datetime else query end total_count = Repo.aggregate(query, :count, :id) status_counts = query |> group_by([t], t.status) |> select([t], {t.status, count(t.id)}) |> Repo.all() |> Enum.into(%{}) state_counts = query |> group_by([t], t.current_state) |> select([t], {t.current_state, count(t.id)}) |> Repo.all() |> Enum.into(%{}) currency_stats = query |> where([t], not is_nil(t.foreign_currency)) |> group_by([t], t.foreign_currency) |> select([t], {t.foreign_currency, count(t.id), sum(t.foreign_amount)}) |> Repo.all() success_rate = if total_count > 0 do case Map.get(status_counts, "success", 0) do 0 -> 0.0 success_count -> Float.round(success_count / total_count * 100, 2) end else 0.0 end %{ total_count: total_count, status_counts: status_counts, state_counts: state_counts, currency_stats: currency_stats, success_rate: success_rate } end # ================================ # QR VALIDATION MONITORING # ================================ @doc """ Returns a paginated list of QR validations with optional filters. """ def list_qr_validations(opts \\ []) do search_term = Keyword.get(opts, :search_term, "") status_filter = Keyword.get(opts, :status, :all) validation_type_filter = Keyword.get(opts, :validation_type, :all) corridor_filter = Keyword.get(opts, :corridor, :all) date_from = Keyword.get(opts, :date_from) date_to = Keyword.get(opts, :date_to) # Pagination: support page & page_size OR offset & limit page = Keyword.get(opts, :page, nil) page_size = Keyword.get(opts, :page_size, nil) offset = Keyword.get(opts, :offset, 0) limit = Keyword.get(opts, :limit, nil) || page_size || 50 offset = case {page, page_size} do {p, ps} when is_integer(p) and is_integer(ps) and p > 0 -> (p - 1) * ps _ -> offset || 0 end query = from qr in QRValidation, left_join: p in assoc(qr, :partner), left_join: m in assoc(qr, :merchant), preload: [partner: p, merchant: m], order_by: [desc: qr.inserted_at] query = apply_qr_validation_filters(query, search_term, status_filter, validation_type_filter, corridor_filter, date_from, date_to) query |> limit(^limit) |> offset(^offset) |> Repo.all() end @doc """ Gets a single QR validation with all associations. """ def get_qr_validation!(id) do QRValidation |> preload([:partner, :merchant, :events]) |> Repo.get!(id) end @doc """ Returns QR validation statistics for analytics. """ def get_qr_validation_stats(date_from \\ nil, date_to \\ nil) do query = from qr in QRValidation query = if date_from && date_to do # Convert dates to datetime if they are Date structs {start_datetime, end_datetime} = normalize_date_range(date_from, date_to) from qr in query, where: qr.inserted_at >= ^start_datetime and qr.inserted_at <= ^end_datetime else query end total_count = Repo.aggregate(query, :count, :id) status_counts = query |> group_by([qr], qr.status) |> select([qr], {qr.status, count(qr.id)}) |> Repo.all() |> Enum.into(%{}) type_counts = query |> group_by([qr], qr.validation_type) |> select([qr], {qr.validation_type, count(qr.id)}) |> Repo.all() |> Enum.into(%{}) corridor_stats = query |> where([qr], not is_nil(qr.corridor)) |> group_by([qr], qr.corridor) |> select([qr], {qr.corridor, count(qr.id), sum(qr.base_amount)}) |> Repo.all() validation_rate = if total_count > 0 do case Map.get(status_counts, "validated", 0) do 0 -> 0.0 validated_count -> Float.round(validated_count / total_count * 100, 2) end else 0.0 end %{ total_count: total_count, status_counts: status_counts, type_counts: type_counts, corridor_stats: corridor_stats, validation_rate: validation_rate } end # ================================ # REQPAY MONITORING # ================================ @doc """ Returns a paginated list of ReqPay payment requests with optional filters. """ def list_req_pays(opts \\ []) do search_term = Keyword.get(opts, :search_term, "") status_filter = Keyword.get(opts, :status, :all) payment_status_filter = Keyword.get(opts, :payment_status, :all) validation_type_filter = Keyword.get(opts, :validation_type, :all) corridor_filter = Keyword.get(opts, :corridor, :all) date_from = Keyword.get(opts, :date_from) date_to = Keyword.get(opts, :date_to) page = Keyword.get(opts, :page, nil) page_size = Keyword.get(opts, :page_size, nil) offset = Keyword.get(opts, :offset, 0) limit = Keyword.get(opts, :limit, nil) || page_size || 50 offset = case {page, page_size} do {p, ps} when is_integer(p) and is_integer(ps) and p > 0 -> (p - 1) * ps _ -> offset || 0 end query = from rp in ReqPay, left_join: p in assoc(rp, :partner), left_join: m in assoc(rp, :merchant), left_join: t in assoc(rp, :transaction), preload: [partner: p, merchant: m, transaction: t], order_by: [desc: rp.inserted_at] query = apply_req_pay_filters(query, search_term, status_filter, payment_status_filter, validation_type_filter, corridor_filter, date_from, date_to) query |> limit(^limit) |> offset(^offset) |> Repo.all() end @doc """ Gets a single ReqPay with all associations. """ def get_req_pay!(id) do ReqPay |> preload([:partner, :merchant, :transaction]) |> Repo.get!(id) end @doc """ Returns ReqPay statistics for analytics. """ def get_req_pay_stats(date_from \\ nil, date_to \\ nil) do query = from rp in ReqPay query = if date_from && date_to do # Convert dates to datetime if they are Date structs {start_datetime, end_datetime} = normalize_date_range(date_from, date_to) from rp in query, where: rp.inserted_at >= ^start_datetime and rp.inserted_at <= ^end_datetime else query end total_count = Repo.aggregate(query, :count, :id) status_counts = query |> group_by([rp], rp.status) |> select([rp], {rp.status, count(rp.id)}) |> Repo.all() |> Enum.into(%{}) payment_status_counts = query |> group_by([rp], rp.payment_status) |> select([rp], {rp.payment_status, count(rp.id)}) |> Repo.all() |> Enum.into(%{}) type_counts = query |> group_by([rp], rp.validation_type) |> select([rp], {rp.validation_type, count(rp.id)}) |> Repo.all() |> Enum.into(%{}) corridor_stats = query |> where([rp], not is_nil(rp.corridor)) |> group_by([rp], rp.corridor) |> select([rp], {rp.corridor, count(rp.id), sum(rp.amount)}) |> Repo.all() success_rate = if total_count > 0 do case Map.get(payment_status_counts, "SUCCESS", 0) do 0 -> 0.0 success_count -> Float.round(success_count / total_count * 100, 2) end else 0.0 end %{ total_count: total_count, status_counts: status_counts, payment_status_counts: payment_status_counts, type_counts: type_counts, corridor_stats: corridor_stats, success_rate: success_rate } end # ================================ # ANALYTICS DASHBOARD DATA # ================================ @doc """ Returns comprehensive analytics data for the dashboard. """ def get_dashboard_analytics(date_from \\ nil, date_to \\ nil) do %{ transactions: get_transaction_stats(date_from, date_to), qr_validations: get_qr_validation_stats(date_from, date_to), req_pays: get_req_pay_stats(date_from, date_to), daily_trends: get_daily_trends(date_from, date_to), corridor_performance: get_corridor_performance(date_from, date_to) } end @doc """ Returns daily trends for charts. """ def get_daily_trends(date_from \\ nil, date_to \\ nil) do # Default to last 30 days if no dates provided {start_date, end_date} = case {date_from, date_to} do {nil, nil} -> end_date = DateTime.utc_now() |> DateTime.to_date() start_date = Date.add(end_date, -30) {start_date, end_date} {from, to} -> {from, to} end # Transaction trends transaction_trends = from t in Transaction, where: fragment("DATE(?)", t.inserted_at) >= ^start_date and fragment("DATE(?)", t.inserted_at) <= ^end_date, group_by: fragment("DATE(?)", t.inserted_at), select: {fragment("DATE(?)", t.inserted_at), count(t.id)}, order_by: [asc: fragment("DATE(?)", t.inserted_at)] # QR validation trends qr_trends = from qr in QRValidation, where: fragment("DATE(?)", qr.inserted_at) >= ^start_date and fragment("DATE(?)", qr.inserted_at) <= ^end_date, group_by: fragment("DATE(?)", qr.inserted_at), select: {fragment("DATE(?)", qr.inserted_at), count(qr.id)}, order_by: [asc: fragment("DATE(?)", qr.inserted_at)] %{ transactions: Repo.all(transaction_trends), qr_validations: Repo.all(qr_trends) } end @doc """ Returns corridor performance metrics. """ def get_corridor_performance(date_from \\ nil, date_to \\ nil) do query = from t in Transaction, where: not is_nil(t.corridor) query = if date_from && date_to do # Convert dates to datetime if they are Date structs {start_datetime, end_datetime} = normalize_date_range(date_from, date_to) from t in query, where: t.inserted_at >= ^start_datetime and t.inserted_at <= ^end_datetime else query end query |> group_by([t], [t.corridor, t.status]) |> select([t], {t.corridor, t.status, count(t.id), sum(t.foreign_amount)}) |> Repo.all() |> Enum.group_by(fn {corridor, _, _, _} -> corridor end) |> Enum.into(%{}, fn {corridor, stats} -> corridor_data = Enum.reduce(stats, %{total: 0, success: 0, failure: 0, volume: Decimal.new(0)}, fn {_, "success", count, amount}, acc -> %{acc | success: count, total: acc.total + count, volume: Decimal.add(acc.volume, amount || 0)} {_, "failure", count, amount}, acc -> %{acc | failure: count, total: acc.total + count, volume: Decimal.add(acc.volume, amount || 0)} {_, _, count, amount}, acc -> %{acc | total: acc.total + count, volume: Decimal.add(acc.volume, amount || 0)} end) success_rate = if corridor_data.total > 0 do Float.round(corridor_data.success / corridor_data.total * 100, 2) else 0.0 end {corridor, Map.put(corridor_data, :success_rate, success_rate)} end) end # ================================ # PRIVATE HELPER FUNCTIONS # ================================ defp apply_transaction_filters(query, search_term, status_filter, state_filter, transaction_type_filter, date_from, date_to) do query = if search_term != "" do search_pattern = "%#{search_term}%" from t in query, where: ilike(t.org_txn_id, ^search_pattern) or ilike(t.payer_addr, ^search_pattern) or ilike(t.payee_addr, ^search_pattern) or ilike(t.payer_name, ^search_pattern) or ilike(t.payee_name, ^search_pattern) else query end query = if status_filter != :all do from t in query, where: t.status == ^to_string(status_filter) else query end query = if state_filter != :all do from t in query, where: t.current_state == ^to_string(state_filter) else query end query = if transaction_type_filter != :all do from t in query, where: t.transaction_type == ^to_string(transaction_type_filter) else query end query = if date_from do # Convert date to datetime if it's a Date struct start_datetime = normalize_date_to_datetime(date_from, :start_of_day) from t in query, where: t.inserted_at >= ^start_datetime else query end if date_to do # Convert date to datetime if it's a Date struct end_datetime = normalize_date_to_datetime(date_to, :end_of_day) from t in query, where: t.inserted_at <= ^end_datetime else query end end defp apply_qr_validation_filters(query, search_term, status_filter, validation_type_filter, corridor_filter, date_from, date_to) do query = if search_term != "" do search_pattern = "%#{search_term}%" from qr in query, where: ilike(qr.txn_id, ^search_pattern) or ilike(qr.payer_addr, ^search_pattern) or ilike(qr.payee_addr, ^search_pattern) or ilike(qr.payer_name, ^search_pattern) or ilike(qr.payee_name, ^search_pattern) else query end query = if status_filter != :all do from qr in query, where: qr.status == ^to_string(status_filter) else query end query = if validation_type_filter != :all do from qr in query, where: qr.validation_type == ^to_string(validation_type_filter) else query end query = if corridor_filter != :all do from qr in query, where: qr.corridor == ^to_string(corridor_filter) else query end query = if date_from do # Convert date to datetime if it's a Date struct start_datetime = normalize_date_to_datetime(date_from, :start_of_day) from qr in query, where: qr.inserted_at >= ^start_datetime else query end if date_to do # Convert date to datetime if it's a Date struct end_datetime = normalize_date_to_datetime(date_to, :end_of_day) from qr in query, where: qr.inserted_at <= ^end_datetime else query end end defp apply_req_pay_filters(query, search_term, status_filter, payment_status_filter, validation_type_filter, corridor_filter, date_from, date_to) do query = if search_term != "" do search_pattern = "%#{search_term}%" from rp in query, where: ilike(rp.msg_id, ^search_pattern) or ilike(rp.txn_id, ^search_pattern) or ilike(rp.payer_addr, ^search_pattern) or ilike(rp.payee_addr, ^search_pattern) or ilike(rp.payer_name, ^search_pattern) or ilike(rp.payee_name, ^search_pattern) else query end query = if status_filter != :all do from rp in query, where: rp.status == ^to_string(status_filter) else query end query = if payment_status_filter != :all do from rp in query, where: rp.payment_status == ^to_string(payment_status_filter) else query end query = if validation_type_filter != :all do from rp in query, where: rp.validation_type == ^to_string(validation_type_filter) else query end query = if corridor_filter != :all do from rp in query, where: rp.corridor == ^to_string(corridor_filter) else query end query = if date_from do # Convert date to datetime if it's a Date struct start_datetime = normalize_date_to_datetime(date_from, :start_of_day) from rp in query, where: rp.inserted_at >= ^start_datetime else query end if date_to do # Convert date to datetime if it's a Date struct end_datetime = normalize_date_to_datetime(date_to, :end_of_day) from rp in query, where: rp.inserted_at <= ^end_datetime else query end end # Helper function to normalize date range to datetime range defp normalize_date_range(date_from, date_to) do start_datetime = normalize_date_to_datetime(date_from, :start_of_day) end_datetime = normalize_date_to_datetime(date_to, :end_of_day) {start_datetime, end_datetime} end # Helper function to convert Date to DateTime defp normalize_date_to_datetime(%Date{} = date, :start_of_day) do DateTime.new!(date, ~T[00:00:00], "Etc/UTC") end defp normalize_date_to_datetime(%Date{} = date, :end_of_day) do DateTime.new!(date, ~T[23:59:59.999999], "Etc/UTC") end # If already a DateTime, return as-is defp normalize_date_to_datetime(%DateTime{} = datetime, _), do: datetime # If it's a naive datetime, assume UTC defp normalize_date_to_datetime(%NaiveDateTime{} = naive_datetime, _) do DateTime.from_naive!(naive_datetime, "Etc/UTC") end # ================================ # INTERNATIONAL PAYMENTS MONITORING # ================================ @doc """ Returns international payments with filters for monitoring. """ def list_international_payments_with_filters(filters \\ %{}) do search_term = Map.get(filters, :search, "") corridor_filter = Map.get(filters, :corridor, :all) status_filter = Map.get(filters, :status, :all) partner_filter = Map.get(filters, :partner_id, :all) date_from = Map.get(filters, :date_from) date_to = Map.get(filters, :date_to) page = Map.get(filters, :page, 1) per_page = Map.get(filters, :per_page, 20) # Simulated international payments data international_payments = [ %{ id: 1, org_txn_id: "INTL20240115001", partner_txn_id: "SING12345", status: "success", corridor: "SGD-INR", payer_name: "John Doe", payer_addr: "john@sg-bank", payee_name: "Ravi Kumar", payee_addr: "ravi@paytm", payee_mid: nil, foreign_amount: 100.0, foreign_currency: "SGD", inr_amount: 6150.0, fx_rate: 61.50, markup_rate: 0.5, fx_provider: "reuters", fx_locked_at: ~N[2024-01-15 10:30:00], partner: %{id: 1, name: "Singapore Bank"}, inserted_at: ~N[2024-01-15 10:30:00], npci_received_at: ~N[2024-01-15 10:31:00], credit_requested_at: ~N[2024-01-15 10:31:30], partner_credited_at: ~N[2024-01-15 10:32:00], completed_at: ~N[2024-01-15 10:32:30], failure_code: nil, failure_reason: nil }, %{ id: 2, org_txn_id: "INTL20240115002", partner_txn_id: "USA67890", status: "pending", corridor: "USD-INR", payer_name: "Jane Smith", payer_addr: "jane@us-bank", payee_name: "Priya Sharma", payee_addr: "priya@phonepe", payee_mid: nil, foreign_amount: 50.0, foreign_currency: "USD", inr_amount: 4160.0, fx_rate: 83.20, markup_rate: 0.5, fx_provider: "reuters", fx_locked_at: ~N[2024-01-15 11:00:00], partner: %{id: 2, name: "US Bank Corp"}, inserted_at: ~N[2024-01-15 11:00:00], npci_received_at: nil, credit_requested_at: nil, partner_credited_at: nil, completed_at: nil, failure_code: nil, failure_reason: nil } ] # Apply filters filtered_payments = apply_international_payment_filters(international_payments, filters) # Simulate pagination offset = (page - 1) * per_page Enum.slice(filtered_payments, offset, per_page) end @doc """ Gets international payment statistics. """ def get_international_payment_stats(filters \\ %{}) do payments = list_international_payments_with_filters(Map.put(filters, :per_page, 1000)) total_count = length(payments) status_counts = Enum.group_by(payments, & &1.status) |> Enum.map(fn {k, v} -> {k, length(v)} end) |> Map.new() corridor_performance = payments |> Enum.group_by(& &1.corridor) |> Enum.map(fn {corridor, corridor_payments} -> total = length(corridor_payments) success = Enum.count(corridor_payments, &(&1.status == "success")) failure = total - success volume = corridor_payments |> Enum.map(& &1.inr_amount) |> Enum.sum() success_rate = if total > 0, do: Float.round(success / total * 100, 1), else: 0.0 {corridor, %{ total: total, success: success, failure: failure, volume: volume, success_rate: success_rate }} end) |> Map.new() fx_volume = payments |> Enum.map(& &1.inr_amount) |> Enum.sum() %{ total_count: total_count, status_counts: status_counts, corridor_performance: corridor_performance, fx_volume: fx_volume } end @doc """ Gets current FX rates for display. """ def get_current_fx_rates do %{ "USD" => %{rate: 83.20, last_updated: NaiveDateTime.utc_now()}, "SGD" => %{rate: 61.50, last_updated: NaiveDateTime.utc_now()}, "AED" => %{rate: 22.65, last_updated: NaiveDateTime.utc_now()} } end @doc """ Gets international payment by ID. """ def get_international_payment_by_id(id) do case Integer.parse(id) do {int_id, ""} -> list_international_payments_with_filters() |> Enum.find(&(&1.id == int_id)) _ -> nil end end defp apply_international_payment_filters(payments, filters) do search_term = Map.get(filters, :search, "") corridor_filter = Map.get(filters, :corridor, :all) status_filter = Map.get(filters, :status, :all) payments |> Enum.filter(fn payment -> search_match = search_term == "" or String.contains?(String.downcase(payment.org_txn_id), String.downcase(search_term)) or String.contains?(String.downcase(payment.payer_name || ""), String.downcase(search_term)) or String.contains?(String.downcase(payment.payee_name || ""), String.downcase(search_term)) corridor_match = corridor_filter == :all or payment.corridor == corridor_filter status_match = status_filter == :all or payment.status == status_filter search_match and corridor_match and status_match end) end # ================================ # SETTLEMENTS MONITORING # ================================ @doc """ Returns settlements with filters for monitoring. """ def list_settlements_with_filters(filters \\ %{}) do search_term = Map.get(filters, :search, "") status_filter = Map.get(filters, :status, :all) type_filter = Map.get(filters, :type, :all) partner_filter = Map.get(filters, :partner_id, :all) date_from = Map.get(filters, :date_from) date_to = Map.get(filters, :date_to) page = Map.get(filters, :page, 1) per_page = Map.get(filters, :per_page, 20) # Simulated settlements data settlements = [ %{ id: "SET001", reference_id: "REF2024001", batch_id: "BATCH001", type: "net", status: "completed", frequency: "daily", priority: "normal", settlement_amount: 50000.0, fee_amount: 250.0, tax_amount: 45.0, net_amount: 49705.0, transaction_count: 125, currency: "INR", partner: %{id: 1, name: "Partner Bank A"}, partner_account_number: "1234567890", partner_ifsc: "BANK0123456", partner_bank_name: "Partner Bank Ltd", inserted_at: ~N[2024-01-15 09:00:00], scheduled_at: ~N[2024-01-15 18:00:00], started_at: ~N[2024-01-15 18:00:00], approved_at: ~N[2024-01-15 18:05:00], bank_processed_at: ~N[2024-01-15 18:10:00], completed_at: ~N[2024-01-15 18:15:00], settlement_window_start: ~N[2024-01-15 18:00:00], settlement_window_end: ~N[2024-01-15 22:00:00], failure_code: nil, failure_reason: nil, retry_count: 0, reconciliation_status: "reconciled", reconciled_at: ~N[2024-01-15 18:20:00], reconciliation_reference: "REC001" }, %{ id: "SET002", reference_id: "REF2024002", batch_id: "BATCH002", type: "merchant", status: "processing", frequency: "daily", priority: "high", settlement_amount: 25000.0, fee_amount: 125.0, tax_amount: 22.5, net_amount: 24852.5, transaction_count: 62, currency: "INR", partner: %{id: 2, name: "Merchant Services Inc"}, partner_account_number: "9876543210", partner_ifsc: "BANK0987654", partner_bank_name: "Merchant Bank Ltd", inserted_at: ~N[2024-01-15 10:00:00], scheduled_at: ~N[2024-01-15 19:00:00], started_at: ~N[2024-01-15 19:00:00], approved_at: ~N[2024-01-15 19:05:00], bank_processed_at: nil, completed_at: nil, settlement_window_start: ~N[2024-01-15 19:00:00], settlement_window_end: ~N[2024-01-15 23:00:00], failure_code: nil, failure_reason: nil, retry_count: 0, reconciliation_status: "pending", reconciled_at: nil, reconciliation_reference: nil } ] # Apply filters filtered_settlements = apply_settlement_filters(settlements, filters) # Simulate pagination offset = (page - 1) * per_page Enum.slice(filtered_settlements, offset, per_page) end @doc """ Gets settlement statistics. """ def get_settlement_stats(filters \\ %{}) do settlements = list_settlements_with_filters(Map.put(filters, :per_page, 1000)) total_count = length(settlements) completed_count = Enum.count(settlements, &(&1.status == "completed")) processing_count = Enum.count(settlements, &(&1.status == "processing")) total_volume = settlements |> Enum.map(& &1.settlement_amount) |> Enum.sum() type_breakdown = settlements |> Enum.group_by(& &1.type) |> Enum.map(fn {type, type_settlements} -> count = length(type_settlements) volume = type_settlements |> Enum.map(& &1.settlement_amount) |> Enum.sum() {type, %{count: count, volume: volume}} end) |> Map.new() %{ total_count: total_count, completed_count: completed_count, processing_count: processing_count, total_volume: total_volume, type_breakdown: type_breakdown } end @doc """ Gets settlement by ID. """ def get_settlement_by_id(id) do list_settlements_with_filters() |> Enum.find(&(&1.id == id)) end @doc """ Lists active settlement partners. """ def list_active_settlement_partners do [ %{id: 1, name: "Partner Bank A"}, %{id: 2, name: "Merchant Services Inc"}, %{id: 3, name: "International Gateway Ltd"} ] end defp apply_settlement_filters(settlements, filters) do search_term = Map.get(filters, :search, "") status_filter = Map.get(filters, :status, :all) type_filter = Map.get(filters, :type, :all) settlements |> Enum.filter(fn settlement -> search_match = search_term == "" or String.contains?(String.downcase(settlement.id), String.downcase(search_term)) or String.contains?(String.downcase(settlement.reference_id || ""), String.downcase(search_term)) status_match = status_filter == :all or settlement.status == status_filter type_match = type_filter == :all or settlement.type == type_filter search_match and status_match and type_match end) end end