Permette all’admin di forzare anagrafica, fiscali, owner, staff e inviti.
Ops può correggere dati clienti errati (es. email titolare) dalla scheda società senza interventi manuali sul DB. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
module Admin
|
||||
class ClubsController < BaseController
|
||||
before_action :set_club, only: %i[show grant_comped revoke_comped set_quote revoke_quote]
|
||||
before_action :set_club, only: %i[show edit update grant_comped revoke_comped set_quote revoke_quote]
|
||||
|
||||
def index
|
||||
@clubs = Club.includes(:teams, :billing_quote, { club_memberships: :user }, subscription: %i[plan admin_comped_by])
|
||||
@@ -8,11 +8,27 @@ module Admin
|
||||
end
|
||||
|
||||
def show
|
||||
@subscription = @club.subscription || @club.build_subscription(plan: Plan["free"], status: "active")
|
||||
@plans = Plan.ordered.reject { |p| p.slug == "free" }
|
||||
@teams = @club.teams.order(:name)
|
||||
@quote = @club.active_billing_quote
|
||||
@concurrency_violations = StreamConcurrencyViolation.for_club(@club.id).recent.limit(20)
|
||||
load_show_context
|
||||
end
|
||||
|
||||
def edit
|
||||
load_edit_context
|
||||
end
|
||||
|
||||
def update
|
||||
Admin::UpdateClubData.call(
|
||||
club: @club,
|
||||
club_attrs: club_update_params,
|
||||
owner_attrs: owner_params,
|
||||
staff_attrs: staff_params,
|
||||
invitation_attrs: invitation_params,
|
||||
team_attrs: team_params
|
||||
)
|
||||
redirect_to admin_club_path(@club), notice: t("admin.flash.club_updated", club: @club.name)
|
||||
rescue Admin::UpdateClubData::Error, ActiveRecord::RecordInvalid => e
|
||||
flash.now[:alert] = e.message
|
||||
load_edit_context
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def grant_comped
|
||||
@@ -66,6 +82,75 @@ module Admin
|
||||
@club = Club.includes(club_memberships: :user).find(params[:id])
|
||||
end
|
||||
|
||||
def load_show_context
|
||||
@subscription = @club.subscription || @club.build_subscription(plan: Plan["free"], status: "active")
|
||||
@plans = Plan.ordered.reject { |p| p.slug == "free" }
|
||||
@teams = @club.teams.order(:name)
|
||||
@quote = @club.active_billing_quote
|
||||
@concurrency_violations = StreamConcurrencyViolation.for_club(@club.id).recent.limit(20)
|
||||
end
|
||||
|
||||
def load_edit_context
|
||||
@teams = @club.teams.includes(:user_teams, :team_invitations).order(:name)
|
||||
@owner = @club.owner
|
||||
@staff_users = User.joins(:user_teams)
|
||||
.where(user_teams: { team_id: @club.teams.select(:id) })
|
||||
.distinct
|
||||
.order(:email)
|
||||
.to_a
|
||||
@pending_invitations = TeamInvitation.pending
|
||||
.where(team_id: @club.teams.select(:id))
|
||||
.includes(:team)
|
||||
.order(:email)
|
||||
@sport_options = Sports::Catalog.as_api_list.map { |entry| [entry[:label], entry[:key]] }
|
||||
end
|
||||
|
||||
def club_update_params
|
||||
params.require(:club).permit(
|
||||
:name, :sport, :logo_url, :primary_color, :secondary_color,
|
||||
:billing_entity_type, :billing_legal_name, :billing_vat_number, :billing_fiscal_code,
|
||||
:billing_email, :billing_phone, :billing_address_line, :billing_city, :billing_province,
|
||||
:billing_postal_code, :billing_country, :billing_recipient_code, :billing_pec
|
||||
)
|
||||
end
|
||||
|
||||
def owner_params
|
||||
params.fetch(:owner, {}).permit(:name, :email).to_h.symbolize_keys
|
||||
end
|
||||
|
||||
def staff_params
|
||||
raw = params[:staff_users]
|
||||
return {} if raw.blank?
|
||||
|
||||
raw.permit!.to_h.each_with_object({}) do |(user_id, attrs), acc|
|
||||
next unless attrs.is_a?(Hash)
|
||||
|
||||
acc[user_id] = attrs.slice("name", "email").symbolize_keys
|
||||
end
|
||||
end
|
||||
|
||||
def invitation_params
|
||||
raw = params[:invitations]
|
||||
return {} if raw.blank?
|
||||
|
||||
raw.permit!.to_h.each_with_object({}) do |(invitation_id, attrs), acc|
|
||||
next unless attrs.is_a?(Hash)
|
||||
|
||||
acc[invitation_id] = attrs.slice("email").symbolize_keys
|
||||
end
|
||||
end
|
||||
|
||||
def team_params
|
||||
raw = params[:teams]
|
||||
return {} if raw.blank?
|
||||
|
||||
raw.permit!.to_h.each_with_object({}) do |(team_id, attrs), acc|
|
||||
next unless attrs.is_a?(Hash)
|
||||
|
||||
acc[team_id] = attrs.slice("name", "sport").symbolize_keys
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_back_or_club(notice: nil, alert: nil)
|
||||
target = params[:return_to].presence || admin_club_path(@club)
|
||||
redirect_to target, notice: notice, alert: alert
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class UpdateClubData
|
||||
class Error < StandardError; end
|
||||
|
||||
def self.call(**kwargs)
|
||||
new(**kwargs).call
|
||||
end
|
||||
|
||||
def initialize(club:, club_attrs:, owner_attrs: {}, staff_attrs: {}, invitation_attrs: {}, team_attrs: {})
|
||||
@club = club
|
||||
@club_attrs = club_attrs.to_h
|
||||
@owner_attrs = owner_attrs.to_h
|
||||
@staff_attrs = staff_attrs.to_h
|
||||
@invitation_attrs = invitation_attrs.to_h
|
||||
@team_attrs = team_attrs.to_h
|
||||
end
|
||||
|
||||
def call
|
||||
ActiveRecord::Base.transaction do
|
||||
update_club!
|
||||
update_owner!
|
||||
update_staff!
|
||||
update_invitations!
|
||||
update_teams!
|
||||
end
|
||||
@club.reload
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_club!
|
||||
attrs = @club_attrs.dup
|
||||
if attrs[:sport].present?
|
||||
attrs[:sport] = Sports::Catalog.normalize_key(attrs[:sport])
|
||||
end
|
||||
%i[primary_color secondary_color].each do |key|
|
||||
next unless attrs.key?(key)
|
||||
|
||||
attrs[key] = normalize_hex(attrs[key], key == :primary_color ? "#e53935" : "#ffffff")
|
||||
end
|
||||
%i[
|
||||
billing_legal_name billing_vat_number billing_fiscal_code billing_email billing_phone
|
||||
billing_address_line billing_city billing_province billing_postal_code billing_country
|
||||
billing_recipient_code billing_pec logo_url
|
||||
].each do |key|
|
||||
next unless attrs.key?(key)
|
||||
|
||||
attrs[key] = attrs[key].to_s.strip.presence
|
||||
end
|
||||
if attrs[:billing_province].present?
|
||||
attrs[:billing_province] = attrs[:billing_province].to_s.upcase
|
||||
end
|
||||
if attrs[:billing_country].present?
|
||||
attrs[:billing_country] = attrs[:billing_country].to_s.upcase
|
||||
end
|
||||
if attrs[:billing_recipient_code].present?
|
||||
attrs[:billing_recipient_code] = attrs[:billing_recipient_code].to_s.upcase
|
||||
end
|
||||
|
||||
@club.assign_attributes(attrs)
|
||||
@club.save!
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
raise Error, e.record.errors.full_messages.join(", ")
|
||||
end
|
||||
|
||||
def update_owner!
|
||||
return if @owner_attrs.blank?
|
||||
|
||||
owner = @club.owner
|
||||
raise Error, I18n.t("admin.clubs.edit.errors.owner_missing") if owner.blank?
|
||||
|
||||
update_user!(owner, @owner_attrs)
|
||||
end
|
||||
|
||||
def update_staff!
|
||||
return if @staff_attrs.blank?
|
||||
|
||||
allowed_ids = staff_users.index_by(&:id)
|
||||
@staff_attrs.each do |user_id, attrs|
|
||||
user = allowed_ids[user_id.to_s] || allowed_ids[user_id]
|
||||
next unless user
|
||||
|
||||
update_user!(user, attrs)
|
||||
end
|
||||
end
|
||||
|
||||
def update_invitations!
|
||||
return if @invitation_attrs.blank?
|
||||
|
||||
pending = TeamInvitation.pending.where(team_id: @club.teams.select(:id)).index_by { |inv| inv.id.to_s }
|
||||
@invitation_attrs.each do |invitation_id, attrs|
|
||||
invitation = pending[invitation_id.to_s]
|
||||
next unless invitation
|
||||
|
||||
email = attrs[:email].to_s.strip.presence
|
||||
next if email.blank?
|
||||
|
||||
invitation.update!(email: email)
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
raise Error, e.record.errors.full_messages.join(", ")
|
||||
end
|
||||
end
|
||||
|
||||
def update_teams!
|
||||
return if @team_attrs.blank?
|
||||
|
||||
teams = @club.teams.index_by { |t| t.id.to_s }
|
||||
@team_attrs.each do |team_id, attrs|
|
||||
team = teams[team_id.to_s]
|
||||
next unless team
|
||||
|
||||
updates = {}
|
||||
updates[:name] = attrs[:name].to_s.strip if attrs.key?(:name) && attrs[:name].present?
|
||||
if attrs[:sport].present?
|
||||
updates[:sport] = Sports::Catalog.normalize_key(attrs[:sport])
|
||||
end
|
||||
next if updates.empty?
|
||||
|
||||
team.update!(updates)
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
raise Error, e.record.errors.full_messages.join(", ")
|
||||
end
|
||||
end
|
||||
|
||||
def update_user!(user, attrs)
|
||||
updates = {}
|
||||
updates[:name] = attrs[:name].to_s.strip if attrs.key?(:name) && attrs[:name].present?
|
||||
if attrs.key?(:email)
|
||||
email = attrs[:email].to_s.strip.presence
|
||||
updates[:email] = email if email.present?
|
||||
end
|
||||
return if updates.empty?
|
||||
|
||||
user.update!(updates)
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
raise Error, e.record.errors.full_messages.join(", ")
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
raise Error, I18n.t("admin.clubs.edit.errors.email_taken", email: updates[:email])
|
||||
end
|
||||
|
||||
def staff_users
|
||||
User.joins(:user_teams).where(user_teams: { team_id: @club.teams.select(:id) }).distinct.to_a
|
||||
end
|
||||
|
||||
def normalize_hex(value, fallback)
|
||||
raw = value.to_s.strip
|
||||
return fallback if raw.blank?
|
||||
return raw.downcase if raw.match?(/\A#[0-9a-fA-F]{6}\z/)
|
||||
return "##{raw.downcase}" if raw.match?(/\A[0-9a-fA-F]{6}\z/)
|
||||
|
||||
fallback
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -22,6 +22,7 @@
|
||||
<span class="admin-billing-status admin-billing-status--<%= status %>">
|
||||
<%= t("admin.clubs.billing_status.#{status}") %>
|
||||
</span>
|
||||
<%= link_to t("admin.clubs.show.edit_link"), edit_admin_club_path(club), class: "admin-btn admin-btn--sm admin-btn--secondary" %>
|
||||
</div>
|
||||
|
||||
<% if status == :absent || lines.empty? %>
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
<p style="margin-bottom:16px">
|
||||
<%= link_to t("admin.clubs.edit.back"), admin_club_path(@club) %>
|
||||
</p>
|
||||
|
||||
<h2><%= t("admin.clubs.edit.title", club: @club.name) %></h2>
|
||||
<p class="muted" style="margin-bottom:20px"><%= t("admin.clubs.edit.lead") %></p>
|
||||
|
||||
<% if flash.now[:alert].present? %>
|
||||
<p class="admin-flash" style="background:#3d1b1b;border-color:#c62828"><%= flash.now[:alert] %></p>
|
||||
<% end %>
|
||||
|
||||
<%= form_with url: admin_club_path(@club), method: :patch, local: true, class: "admin-form admin-form--wide" do %>
|
||||
<section class="panel">
|
||||
<h3><%= t("admin.clubs.edit.sections.club") %></h3>
|
||||
<div class="admin-form-row">
|
||||
<div>
|
||||
<label for="club_name"><%= t("admin.clubs.edit.fields.name") %></label>
|
||||
<input type="text" name="club[name]" id="club_name" value="<%= @club.name %>" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="club_sport"><%= t("admin.clubs.edit.fields.sport") %></label>
|
||||
<select name="club[sport]" id="club_sport" required>
|
||||
<% @sport_options.each do |label, key| %>
|
||||
<option value="<%= key %>" <%= "selected" if @club.sport == key %>><%= label %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-form-row">
|
||||
<div>
|
||||
<label for="club_primary_color"><%= t("admin.clubs.edit.fields.primary_color") %></label>
|
||||
<input type="text" name="club[primary_color]" id="club_primary_color" value="<%= @club.primary_color %>" maxlength="7">
|
||||
</div>
|
||||
<div>
|
||||
<label for="club_secondary_color"><%= t("admin.clubs.edit.fields.secondary_color") %></label>
|
||||
<input type="text" name="club[secondary_color]" id="club_secondary_color" value="<%= @club.secondary_color %>" maxlength="7">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="club_logo_url"><%= t("admin.clubs.edit.fields.logo_url") %></label>
|
||||
<input type="url" name="club[logo_url]" id="club_logo_url" value="<%= @club.logo_url %>">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h3><%= t("admin.clubs.edit.sections.billing") %></h3>
|
||||
<p class="muted admin-form-hint"><%= t("admin.clubs.edit.billing_hint") %></p>
|
||||
<%= render "shared/billing_profile_fields", record: @club, show_legend: false %>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h3><%= t("admin.clubs.edit.sections.owner") %></h3>
|
||||
<% if @owner %>
|
||||
<div class="admin-form-row">
|
||||
<div>
|
||||
<label for="owner_name"><%= t("admin.clubs.edit.fields.owner_name") %></label>
|
||||
<input type="text" name="owner[name]" id="owner_name" value="<%= @owner.name %>" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="owner_email"><%= t("admin.clubs.edit.fields.owner_email") %></label>
|
||||
<input type="email" name="owner[email]" id="owner_email" value="<%= @owner.email %>" required>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="muted"><%= t("admin.clubs.show.owner_none") %></p>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h3><%= t("admin.clubs.edit.sections.staff") %></h3>
|
||||
<% if @staff_users.any? %>
|
||||
<p class="muted admin-form-hint"><%= t("admin.clubs.edit.staff_hint") %></p>
|
||||
<% @staff_users.each do |user| %>
|
||||
<div class="admin-form-row" style="margin-bottom:0.75rem">
|
||||
<div>
|
||||
<label><%= t("admin.clubs.edit.fields.staff_name") %></label>
|
||||
<input type="text" name="staff_users[<%= user.id %>][name]" value="<%= user.name %>">
|
||||
</div>
|
||||
<div>
|
||||
<label><%= t("admin.clubs.edit.fields.staff_email") %></label>
|
||||
<input type="email" name="staff_users[<%= user.id %>][email]" value="<%= user.email %>">
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<p class="muted"><%= t("admin.clubs.edit.staff_none") %></p>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h3><%= t("admin.clubs.edit.sections.invitations") %></h3>
|
||||
<% if @pending_invitations.any? %>
|
||||
<p class="muted admin-form-hint"><%= t("admin.clubs.edit.invitations_hint") %></p>
|
||||
<% @pending_invitations.each do |invitation| %>
|
||||
<div class="admin-form-row" style="margin-bottom:0.75rem">
|
||||
<div>
|
||||
<label><%= t("admin.clubs.edit.fields.invitation_team") %></label>
|
||||
<input type="text" value="<%= invitation.team.name %>" disabled>
|
||||
</div>
|
||||
<div>
|
||||
<label><%= t("admin.clubs.edit.fields.invitation_email") %></label>
|
||||
<input type="email" name="invitations[<%= invitation.id %>][email]" value="<%= invitation.email %>" required>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<p class="muted"><%= t("admin.clubs.edit.invitations_none") %></p>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h3><%= t("admin.clubs.edit.sections.teams") %></h3>
|
||||
<% if @teams.any? %>
|
||||
<% @teams.each do |team| %>
|
||||
<div class="admin-form-row" style="margin-bottom:0.75rem">
|
||||
<div>
|
||||
<label><%= t("admin.clubs.edit.fields.team_name") %></label>
|
||||
<input type="text" name="teams[<%= team.id %>][name]" value="<%= team.name %>" required>
|
||||
</div>
|
||||
<div>
|
||||
<label><%= t("admin.clubs.edit.fields.team_sport") %></label>
|
||||
<select name="teams[<%= team.id %>][sport]">
|
||||
<% @sport_options.each do |label, key| %>
|
||||
<option value="<%= key %>" <%= "selected" if team.sport == key %>><%= label %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<p class="muted"><%= t("admin.clubs.show.no_teams") %></p>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<div class="admin-form-actions">
|
||||
<%= link_to t("admin.clubs.edit.cancel"), admin_club_path(@club), class: "admin-btn admin-btn--secondary" %>
|
||||
<button type="submit" class="admin-btn admin-btn--primary"><%= t("admin.clubs.edit.submit") %></button>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -6,10 +6,12 @@
|
||||
· <%= link_to t("admin.clubs.show.recordings_archive"), admin_club_recordings_path(@club) %>
|
||||
· <%= link_to t("admin.clubs.show.billing_link"), admin_billing_path(club_id: @club.id) %>
|
||||
· <%= link_to t("admin.clubs.show.youtube_platform_link"), admin_youtube_platform_path %>
|
||||
· <%= link_to t("admin.clubs.show.edit_link"), edit_admin_club_path(@club) %>
|
||||
</p>
|
||||
|
||||
<%= render "admin/clubs/billing_profile", club: @club %>
|
||||
|
||||
|
||||
<%= render "admin/clubs/comped_form", club: @club, subscription: @subscription, return_to: admin_club_path(@club) %>
|
||||
<%= render "admin/clubs/quote_form", club: @club, quote: @quote, return_to: admin_club_path(@club) %>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<%= csrf_meta_tags %>
|
||||
<link rel="stylesheet" href="/admin.css?v=20">
|
||||
<link rel="stylesheet" href="/admin.css?v=21">
|
||||
<%= yield :head %>
|
||||
<% if content_for?(:replay_archive_styles) %>
|
||||
<link rel="stylesheet" href="/marketing.css?v=42">
|
||||
|
||||
Reference in New Issue
Block a user