Allow superadmins to override free device limits and per-device pricing on a per-organization basis. Overrides cascade through subscription limits, billing calculations, and user-facing settings display. - Add custom_free_device_limit and custom_price_per_device to organizations - Add billing_override_changeset with validation - Update SubscriptionLimits.effective_device_limit to respect overrides - Update Billing to use effective free count and price per device - Add Admin.update_billing_overrides with audit logging - Add override editing UI to /admin/organizations - Update org settings page to show effective limits/pricing
131 lines
4 KiB
Elixir
131 lines
4 KiB
Elixir
defmodule ToweropsWeb.Admin.OrgLive.Index do
|
|
@moduledoc """
|
|
Admin interface for viewing and managing organizations.
|
|
"""
|
|
use ToweropsWeb, :live_view
|
|
|
|
alias Towerops.Admin
|
|
alias Towerops.Organizations.Organization
|
|
|
|
@impl true
|
|
def mount(_params, _session, socket) do
|
|
orgs = Admin.list_all_organizations()
|
|
|
|
ip =
|
|
case get_connect_info(socket, :peer_data) do
|
|
%{address: address} -> to_string(:inet_parse.ntoa(address))
|
|
_ -> "unknown"
|
|
end
|
|
|
|
{:ok,
|
|
socket
|
|
|> assign(:page_title, t("All Organizations"))
|
|
|> assign(:timezone, socket.assigns.current_scope.timezone)
|
|
|> assign(:organizations, orgs)
|
|
|> assign(:client_ip, ip)
|
|
|> assign(:editing_org, nil)
|
|
|> assign(:override_form, nil)}
|
|
end
|
|
|
|
@impl true
|
|
def handle_params(params, _url, socket) do
|
|
case params["edit"] do
|
|
nil ->
|
|
{:noreply,
|
|
socket
|
|
|> assign(:editing_org, nil)
|
|
|> assign(:override_form, nil)}
|
|
|
|
org_id ->
|
|
org = Enum.find(socket.assigns.organizations, &(&1.id == org_id))
|
|
|
|
if org do
|
|
changeset = Organization.billing_override_changeset(org, %{})
|
|
|
|
{:noreply,
|
|
socket
|
|
|> assign(:editing_org, org)
|
|
|> assign(:override_form, to_form(changeset))}
|
|
else
|
|
{:noreply,
|
|
socket
|
|
|> assign(:editing_org, nil)
|
|
|> assign(:override_form, nil)}
|
|
end
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("edit_overrides", %{"id" => org_id}, socket) do
|
|
{:noreply, push_patch(socket, to: ~p"/admin/organizations?edit=#{org_id}")}
|
|
end
|
|
|
|
def handle_event("validate_overrides", %{"organization" => params}, socket) do
|
|
changeset =
|
|
socket.assigns.editing_org
|
|
|> Organization.billing_override_changeset(params)
|
|
|> Map.put(:action, :validate)
|
|
|
|
{:noreply, assign(socket, :override_form, to_form(changeset))}
|
|
end
|
|
|
|
def handle_event("save_overrides", %{"organization" => params}, socket) do
|
|
superuser = socket.assigns.current_scope.superuser || socket.assigns.current_scope.user
|
|
org = socket.assigns.editing_org
|
|
|
|
case Admin.update_billing_overrides(org.id, params, superuser.id, socket.assigns.client_ip) do
|
|
{:ok, _updated_org} ->
|
|
orgs = Admin.list_all_organizations()
|
|
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:info, t_admin("Billing overrides updated"))
|
|
|> assign(:organizations, orgs)
|
|
|> push_patch(to: ~p"/admin/organizations")}
|
|
|
|
{:error, changeset} ->
|
|
{:noreply, assign(socket, :override_form, to_form(changeset))}
|
|
end
|
|
end
|
|
|
|
def handle_event("clear_overrides", _params, socket) do
|
|
superuser = socket.assigns.current_scope.superuser || socket.assigns.current_scope.user
|
|
org = socket.assigns.editing_org
|
|
|
|
attrs = %{custom_free_device_limit: nil, custom_price_per_device: nil}
|
|
|
|
case Admin.update_billing_overrides(org.id, attrs, superuser.id, socket.assigns.client_ip) do
|
|
{:ok, _updated_org} ->
|
|
orgs = Admin.list_all_organizations()
|
|
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:info, t_admin("Billing overrides cleared"))
|
|
|> assign(:organizations, orgs)
|
|
|> push_patch(to: ~p"/admin/organizations")}
|
|
|
|
{:error, _changeset} ->
|
|
{:noreply, put_flash(socket, :error, t_admin("Failed to clear overrides"))}
|
|
end
|
|
end
|
|
|
|
def handle_event("cancel_overrides", _params, socket) do
|
|
{:noreply, push_patch(socket, to: ~p"/admin/organizations")}
|
|
end
|
|
|
|
def handle_event("delete_org", %{"id" => org_id}, socket) do
|
|
superuser = socket.assigns.current_scope.superuser || socket.assigns.current_scope.user
|
|
ip = socket.assigns.client_ip
|
|
|
|
case Admin.delete_organization(org_id, superuser.id, ip) do
|
|
{:ok, _} ->
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:info, t_admin("Organization deleted successfully"))
|
|
|> assign(:organizations, Admin.list_all_organizations())}
|
|
|
|
{:error, _} ->
|
|
{:noreply, put_flash(socket, :error, t_admin("Failed to delete organization"))}
|
|
end
|
|
end
|
|
end
|