diff --git a/lib/snmpkit/snmp_lib/config.ex b/lib/snmpkit/snmp_lib/config.ex index 61579daf..5039b018 100644 --- a/lib/snmpkit/snmp_lib/config.ex +++ b/lib/snmpkit/snmp_lib/config.ex @@ -448,6 +448,7 @@ defmodule SnmpKit.SnmpLib.Config do env = System.get_env("MIX_ENV") -> String.to_atom(env) env = System.get_env("SNMP_LIB_ENV") -> String.to_atom(env) env = Application.get_env(:snmp_lib, :environment) -> env + Code.ensure_loaded?(Mix) -> Mix.env() true -> :dev end end diff --git a/lib/towerops/dashboard.ex b/lib/towerops/dashboard.ex index d910a2bf..85ab7271 100644 --- a/lib/towerops/dashboard.ex +++ b/lib/towerops/dashboard.ex @@ -7,6 +7,7 @@ defmodule Towerops.Dashboard do alias Towerops.Devices alias Towerops.Gaiia alias Towerops.Gaiia.ImpactAnalysis + alias Towerops.Integrations alias Towerops.Preseem alias Towerops.Preseem.Insights alias Towerops.Sites @@ -16,7 +17,7 @@ defmodule Towerops.Dashboard do status_counts = Devices.get_device_status_counts(organization_id) total_devices = status_counts |> Map.values() |> Enum.sum() active_alerts = Alerts.list_organization_active_alerts(organization_id) - subscriber_totals = Gaiia.get_org_subscriber_totals(organization_id) + subscriber_totals = Integrations.get_billing_summary(organization_id) insight_counts = Insights.count_active_by_urgency(organization_id) %{ @@ -83,9 +84,9 @@ defmodule Towerops.Dashboard do end end - @doc "Returns subscriber summary for the org from Gaiia data." + @doc "Returns subscriber summary for the org from billing integrations." def get_org_subscriber_summary(organization_id) do - totals = Gaiia.get_org_subscriber_totals(organization_id) + totals = Integrations.get_billing_summary(organization_id) %{ total: totals.total_subscribers, diff --git a/lib/towerops/gaiia.ex b/lib/towerops/gaiia.ex index b8c0c66c..e37da734 100644 --- a/lib/towerops/gaiia.ex +++ b/lib/towerops/gaiia.ex @@ -38,6 +38,20 @@ defmodule Towerops.Gaiia do ) end + def count_active_subscribers(organization_id) do + Account + |> where(organization_id: ^organization_id) + |> where([a], a.subscription_count > 0) + |> Repo.aggregate(:count) + end + + def sum_active_mrr(organization_id) do + BillingSubscription + |> where(organization_id: ^organization_id) + |> where(status: "ACTIVE") + |> Repo.aggregate(:sum, :mrr_amount) || Decimal.new(0) + end + # --- Network Sites --- def list_network_sites(organization_id) do diff --git a/lib/towerops/gaiia/sync.ex b/lib/towerops/gaiia/sync.ex index 47eab447..dd2e2fe5 100644 --- a/lib/towerops/gaiia/sync.ex +++ b/lib/towerops/gaiia/sync.ex @@ -37,6 +37,7 @@ defmodule Towerops.Gaiia.Sync do "Synced #{accounts_count} accounts, #{sites_count} sites, #{items_count} inventory items, #{subs_count} subscriptions" Integrations.update_sync_status(integration, "success", message) + persist_billing_totals(integration) {:ok, %{ @@ -168,6 +169,13 @@ defmodule Towerops.Gaiia.Sync do } end + defp persist_billing_totals(integration) do + org_id = integration.organization_id + subscriber_count = Gaiia.count_active_subscribers(org_id) + total_mrr = Gaiia.sum_active_mrr(org_id) + Integrations.update_billing_totals(integration, subscriber_count, total_mrr) + end + defp client_opts(integration) do case integration.credentials["endpoint"] do nil -> [] diff --git a/lib/towerops/integrations.ex b/lib/towerops/integrations.ex index d4129465..08595cf2 100644 --- a/lib/towerops/integrations.ex +++ b/lib/towerops/integrations.ex @@ -62,6 +62,32 @@ defmodule Towerops.Integrations do end end + @billing_providers ~w(gaiia splynx visp sonar) + + def update_billing_totals(%Integration{} = integration, subscriber_count, total_mrr) do + integration + |> Integration.changeset(%{subscriber_count: subscriber_count, total_mrr: total_mrr}) + |> Repo.update() + end + + def get_billing_summary(organization_id) do + result = + Integration + |> where(organization_id: ^organization_id) + |> where([i], i.provider in @billing_providers) + |> where(enabled: true) + |> select([i], %{ + total_subscribers: coalesce(sum(i.subscriber_count), 0), + total_mrr: coalesce(sum(i.total_mrr), 0) + }) + |> Repo.one() + + %{ + total_subscribers: result.total_subscribers, + total_mrr: result.total_mrr || Decimal.new(0) + } + end + def list_enabled_integrations(provider) do Integration |> where(provider: ^provider, enabled: true) diff --git a/lib/towerops/integrations/integration.ex b/lib/towerops/integrations/integration.ex index 532615af..b211fd62 100644 --- a/lib/towerops/integrations/integration.ex +++ b/lib/towerops/integrations/integration.ex @@ -25,6 +25,8 @@ defmodule Towerops.Integrations.Integration do field :last_synced_at, :utc_datetime field :last_sync_status, :string, default: "never" field :last_sync_message, :string + field :subscriber_count, :integer, default: 0 + field :total_mrr, :decimal belongs_to :organization, Towerops.Organizations.Organization @@ -41,7 +43,9 @@ defmodule Towerops.Integrations.Integration do :sync_interval_minutes, :last_synced_at, :last_sync_status, - :last_sync_message + :last_sync_message, + :subscriber_count, + :total_mrr ]) |> validate_required([:organization_id, :provider]) |> validate_inclusion(:provider, @valid_providers) diff --git a/lib/towerops/sonar/sync.ex b/lib/towerops/sonar/sync.ex index 9bae166d..56490876 100644 --- a/lib/towerops/sonar/sync.ex +++ b/lib/towerops/sonar/sync.ex @@ -35,6 +35,7 @@ defmodule Towerops.Sonar.Sync do if(mrr > 0, do: " (MRR: $#{:erlang.float_to_binary(mrr / 1, decimals: 2)})", else: "") Integrations.update_sync_status(integration, "success", message) + Integrations.update_billing_totals(integration, accounts_count, Decimal.from_float(mrr)) {:ok, %{ diff --git a/lib/towerops/splynx/sync.ex b/lib/towerops/splynx/sync.ex index 5af37e66..5880db36 100644 --- a/lib/towerops/splynx/sync.ex +++ b/lib/towerops/splynx/sync.ex @@ -33,6 +33,7 @@ defmodule Towerops.Splynx.Sync do if(mrr > 0, do: " (MRR: $#{:erlang.float_to_binary(mrr / 1, decimals: 2)})", else: "") Integrations.update_sync_status(integration, "success", message) + Integrations.update_billing_totals(integration, active_count, Decimal.from_float(mrr)) {:ok, %{ diff --git a/lib/towerops/visp/sync.ex b/lib/towerops/visp/sync.ex index 7cbdac49..213431b9 100644 --- a/lib/towerops/visp/sync.ex +++ b/lib/towerops/visp/sync.ex @@ -34,6 +34,7 @@ defmodule Towerops.Visp.Sync do if(total_mrr > 0, do: " (MRR: $#{:erlang.float_to_binary(total_mrr / 1, decimals: 2)})", else: "") Integrations.update_sync_status(integration, "success", message) + Integrations.update_billing_totals(integration, subscribers_count, Decimal.from_float(total_mrr)) {:ok, %{ diff --git a/lib/towerops_web/live/org/integrations_live.html.heex b/lib/towerops_web/live/org/integrations_live.html.heex index df4d7c4c..2f76b243 100644 --- a/lib/towerops_web/live/org/integrations_live.html.heex +++ b/lib/towerops_web/live/org/integrations_live.html.heex @@ -32,7 +32,10 @@ <%= if category.exclusive do %> <%!-- Exclusive category (billing): show as picker + single config panel --%> - <% active_billing = Enum.find(category.providers, fn p -> @integrations[p.id] && @integrations[p.id].enabled end) %> + <% active_billing = + Enum.find(category.providers, fn p -> + @integrations[p.id] && @integrations[p.id].enabled + end) %>
- {t("Receive real-time updates from Gaiia when accounts, subscriptions, or inventory items change.")} + {t( + "Receive real-time updates from Gaiia when accounts, subscriptions, or inventory items change." + )}