defmodule DaProductApp.YspAdmin do @moduledoc """ Administrative utilities for managing YSP notification configurations. This module provides helper functions for common administrative tasks related to YSP notification system management. """ alias DaProductApp.Repo alias DaProductApp.YspConfiguration alias DaProductApp.YspNotificationLog alias DaProductApp.BatchNumber import Ecto.Query @doc """ Creates or updates YSP configuration for a merchant. ## Examples iex> YspAdmin.create_or_update_config("merchant_123", %{ ...> notification_url: "https://merchant.example.com/webhook", ...> merchant_tag: "CUSTOM_TAG" ...> }) {:ok, %YspConfiguration{}} """ def create_or_update_config(merchant_id, attrs) do case Repo.get_by(YspConfiguration, merchant_id: merchant_id) do nil -> # Create new configuration %YspConfiguration{} |> YspConfiguration.changeset(Map.put(attrs, :merchant_id, merchant_id)) |> Repo.insert() existing_config -> # Update existing configuration existing_config |> YspConfiguration.changeset(attrs) |> Repo.update() end end @doc """ Lists all YSP configurations. """ def list_configurations do YspConfiguration |> order_by([c], [asc: c.merchant_id]) |> Repo.all() end @doc """ Gets notification statistics for a merchant. Returns a map with counts of notifications by status. """ def get_notification_stats(merchant_id, opts \\ []) do from_date = Keyword.get(opts, :from_date, Date.add(Date.utc_today(), -30)) to_date = Keyword.get(opts, :to_date, Date.utc_today()) query = from nl in YspNotificationLog, join: t in "transactions", on: nl.transaction_id == t.id, where: t.merchant_id == ^merchant_id, where: fragment("DATE(?)", nl.inserted_at) >= ^from_date, where: fragment("DATE(?)", nl.inserted_at) <= ^to_date, group_by: nl.status, select: {nl.status, count(nl.id)} stats = Repo.all(query) |> Enum.into(%{}) %{ total: Map.values(stats) |> Enum.sum(), success: Map.get(stats, :success, 0), pending: Map.get(stats, :pending, 0), failed: Map.get(stats, :failed, 0), max_attempts_reached: Map.get(stats, :max_attempts_reached, 0), period: {from_date, to_date} } end @doc """ Gets recent failed notifications for debugging. """ def get_recent_failures(limit \\ 50) do from(nl in YspNotificationLog, join: t in "transactions", on: nl.transaction_id == t.id, where: nl.status in [:failed, :max_attempts_reached], order_by: [desc: nl.updated_at], limit: ^limit, select: %{ id: nl.id, payment_id: nl.payment_id, merchant_id: t.merchant_id, status: nl.status, attempt_number: nl.attempt_number, error_message: nl.error_message, notification_url: nl.notification_url, updated_at: nl.updated_at } ) |> Repo.all() end @doc """ Retries a failed notification manually. This function is useful for manually retrying notifications that have reached max attempts but need to be sent again (e.g., after fixing the target endpoint). """ def manual_retry_notification(notification_log_id) do case Repo.get(YspNotificationLog, notification_log_id) do nil -> {:error, "Notification log not found"} %YspNotificationLog{status: status} = notification_log when status in [:failed, :max_attempts_reached] -> # Reset the notification for retry changeset = YspNotificationLog.changeset(notification_log, %{ status: :pending, attempt_number: 1, error_message: nil, next_retry_at: nil }) case Repo.update(changeset) do {:ok, updated_log} -> # Get config for retry config = get_config_for_notification(updated_log) # Start retry process Task.start(fn -> DaProductApp.Services.YspNotificationService.send_notification_with_retry(updated_log, config) end) {:ok, "Notification queued for retry"} {:error, changeset} -> {:error, "Failed to reset notification: #{inspect(changeset.errors)}"} end %YspNotificationLog{status: status} -> {:error, "Notification is in #{status} status and cannot be retried"} end end @doc """ Gets batch number statistics for a merchant. """ def get_batch_stats(merchant_id, opts \\ []) do from_date = Keyword.get(opts, :from_date, Date.add(Date.utc_today(), -30)) to_date = Keyword.get(opts, :to_date, Date.utc_today()) query = from bn in BatchNumber, where: bn.merchant_id == ^merchant_id, where: bn.date >= ^from_date, where: bn.date <= ^to_date, order_by: [desc: bn.date], select: %{ date: bn.date, batch_number: bn.batch_number, transaction_count: bn.transaction_count, last_used_at: bn.last_used_at } batches = Repo.all(query) total_transactions = Enum.sum(Enum.map(batches, & &1.transaction_count)) %{ batches: batches, total_days: length(batches), total_transactions: total_transactions, avg_transactions_per_day: if(length(batches) > 0, do: total_transactions / length(batches), else: 0), period: {from_date, to_date} } end @doc """ Deactivates a merchant configuration. """ def deactivate_merchant_config(merchant_id) do case Repo.get_by(YspConfiguration, merchant_id: merchant_id) do nil -> {:error, "Configuration not found"} config -> config |> YspConfiguration.changeset(%{is_active: false}) |> Repo.update() end end @doc """ Activates a merchant configuration. """ def activate_merchant_config(merchant_id) do case Repo.get_by(YspConfiguration, merchant_id: merchant_id) do nil -> {:error, "Configuration not found"} config -> config |> YspConfiguration.changeset(%{is_active: true}) |> Repo.update() end end @doc """ Gets a summary dashboard for YSP notification system. """ def get_dashboard_summary do today = Date.utc_today() yesterday = Date.add(today, -1) # Today's stats today_stats = get_system_stats_for_date(today) yesterday_stats = get_system_stats_for_date(yesterday) # Recent failures recent_failures = get_recent_failures(10) # Active configurations active_configs = from(c in YspConfiguration, where: c.is_active == true) |> Repo.all() |> length() %{ today: today_stats, yesterday: yesterday_stats, recent_failures: recent_failures, active_configurations: active_configs, generated_at: DateTime.utc_now() } end # Private helper functions defp get_config_for_notification(notification_log) do # This would need to be implemented based on how we can get merchant_id from notification_log # For now, return default config YspConfiguration.get_config_for_merchant("default") end defp get_system_stats_for_date(date) do query = from nl in YspNotificationLog, where: fragment("DATE(?)", nl.inserted_at) == ^date, group_by: nl.status, select: {nl.status, count(nl.id)} stats = Repo.all(query) |> Enum.into(%{}) %{ date: date, total: Map.values(stats) |> Enum.sum(), success: Map.get(stats, :success, 0), pending: Map.get(stats, :pending, 0), failed: Map.get(stats, :failed, 0), max_attempts_reached: Map.get(stats, :max_attempts_reached, 0) } end end