Compare commits
3
Commits
adfea01cdb
...
5d4bd8544d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d4bd8544d | ||
|
|
cc95fcacbf | ||
|
|
d7dd744c32 |
@@ -1,18 +1,34 @@
|
|||||||
module Admin
|
module Admin
|
||||||
class ClubsController < BaseController
|
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
|
def index
|
||||||
@clubs = Club.includes(:teams, :billing_quote, subscription: %i[plan admin_comped_by])
|
@clubs = Club.includes(:teams, :billing_quote, { club_memberships: :user }, subscription: %i[plan admin_comped_by])
|
||||||
.order(:name)
|
.order(:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@subscription = @club.subscription || @club.build_subscription(plan: Plan["free"], status: "active")
|
load_show_context
|
||||||
@plans = Plan.ordered.reject { |p| p.slug == "free" }
|
end
|
||||||
@teams = @club.teams.order(:name)
|
|
||||||
@quote = @club.active_billing_quote
|
def edit
|
||||||
@concurrency_violations = StreamConcurrencyViolation.for_club(@club.id).recent.limit(20)
|
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
|
end
|
||||||
|
|
||||||
def grant_comped
|
def grant_comped
|
||||||
@@ -63,7 +79,76 @@ module Admin
|
|||||||
private
|
private
|
||||||
|
|
||||||
def set_club
|
def set_club
|
||||||
@club = Club.find(params[:id])
|
@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
|
end
|
||||||
|
|
||||||
def redirect_back_or_club(notice: nil, alert: nil)
|
def redirect_back_or_club(notice: nil, alert: nil)
|
||||||
|
|||||||
@@ -21,7 +21,14 @@ class Club < ApplicationRecord
|
|||||||
validates :secondary_color, presence: true
|
validates :secondary_color, presence: true
|
||||||
|
|
||||||
def owner
|
def owner
|
||||||
club_memberships.find_by(role: "owner")&.user
|
memberships = club_memberships
|
||||||
|
membership =
|
||||||
|
if memberships.loaded?
|
||||||
|
memberships.detect { |m| m.role == "owner" }
|
||||||
|
else
|
||||||
|
memberships.find_by(role: "owner")
|
||||||
|
end
|
||||||
|
membership&.user
|
||||||
end
|
end
|
||||||
|
|
||||||
def owned_by?(user)
|
def owned_by?(user)
|
||||||
|
|||||||
@@ -59,18 +59,43 @@ module ClubBillingProfile
|
|||||||
def billing_profile_invoice_lines
|
def billing_profile_invoice_lines
|
||||||
lines = []
|
lines = []
|
||||||
lines << ["Tipo", self.class.billing_entity_types[billing_entity_type]] if billing_entity_type.present?
|
lines << ["Tipo", self.class.billing_entity_types[billing_entity_type]] if billing_entity_type.present?
|
||||||
lines << ["Intestatario", billing_legal_name]
|
lines << ["Intestatario", billing_legal_name] if billing_legal_name.present?
|
||||||
lines << ["P.IVA", billing_vat_number] if billing_vat_number.present?
|
lines << ["P.IVA", billing_vat_number] if billing_vat_number.present?
|
||||||
lines << ["Codice fiscale", billing_fiscal_code] if billing_fiscal_code.present?
|
lines << ["Codice fiscale", billing_fiscal_code] if billing_fiscal_code.present?
|
||||||
lines << ["Email fatturazione", billing_email]
|
lines << ["Email fatturazione", billing_email] if billing_email.present?
|
||||||
lines << ["Telefono", billing_phone] if billing_phone.present?
|
lines << ["Telefono", billing_phone] if billing_phone.present?
|
||||||
addr = [billing_address_line, billing_postal_code, billing_city, billing_province, billing_country].compact.join(", ")
|
core_address = [billing_address_line, billing_postal_code, billing_city, billing_province].compact_blank
|
||||||
lines << ["Indirizzo", addr] if addr.present?
|
if core_address.any?
|
||||||
|
core_address << billing_country if billing_country.present?
|
||||||
|
lines << ["Indirizzo", core_address.join(", ")]
|
||||||
|
end
|
||||||
lines << ["SDI", billing_recipient_code] if billing_recipient_code.present?
|
lines << ["SDI", billing_recipient_code] if billing_recipient_code.present?
|
||||||
lines << ["PEC", billing_pec] if billing_pec.present?
|
lines << ["PEC", billing_pec] if billing_pec.present?
|
||||||
lines
|
lines
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Stato profilo fiscale per badge admin: :complete | :incomplete | :absent
|
||||||
|
def billing_profile_admin_status
|
||||||
|
return :complete if billing_profile_complete?
|
||||||
|
return :absent unless billing_profile_started?
|
||||||
|
|
||||||
|
:incomplete
|
||||||
|
end
|
||||||
|
|
||||||
|
def billing_profile_started?
|
||||||
|
billing_legal_name.present? ||
|
||||||
|
billing_vat_number.present? ||
|
||||||
|
billing_fiscal_code.present? ||
|
||||||
|
billing_email.present? ||
|
||||||
|
billing_phone.present? ||
|
||||||
|
billing_address_line.present? ||
|
||||||
|
billing_city.present? ||
|
||||||
|
billing_postal_code.present? ||
|
||||||
|
billing_province.present? ||
|
||||||
|
billing_recipient_code.present? ||
|
||||||
|
billing_pec.present?
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def billing_profile_for_invoicing
|
def billing_profile_for_invoicing
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<% owner = club.owner %>
|
||||||
|
<% status = club.billing_profile_admin_status %>
|
||||||
|
<% lines = club.billing_profile_invoice_lines.select { |_label, value| value.present? } %>
|
||||||
|
|
||||||
|
<section class="panel admin-club-profile" style="margin:16px 0 24px">
|
||||||
|
<h3 style="font-size:1rem;margin:0 0 12px"><%= t("admin.clubs.show.profile_title") %></h3>
|
||||||
|
|
||||||
|
<h4 style="font-size:0.9rem;margin:0 0 8px;color:var(--muted)"><%= t("admin.clubs.show.owner_title") %></h4>
|
||||||
|
<% if owner %>
|
||||||
|
<dl class="billing-profile-dl" style="margin-bottom:16px">
|
||||||
|
<dt><%= t("admin.clubs.show.owner_name") %></dt>
|
||||||
|
<dd><%= owner.name %></dd>
|
||||||
|
<dt><%= t("admin.clubs.show.owner_email") %></dt>
|
||||||
|
<dd><%= mail_to owner.email %></dd>
|
||||||
|
</dl>
|
||||||
|
<% else %>
|
||||||
|
<p class="muted" style="margin:0 0 16px"><%= t("admin.clubs.show.owner_none") %></p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:8px">
|
||||||
|
<h4 style="font-size:0.9rem;margin:0;color:var(--muted)"><%= t("admin.clubs.show.billing_title") %></h4>
|
||||||
|
<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? %>
|
||||||
|
<p class="muted" style="margin:0"><%= t("admin.clubs.show.billing_none") %></p>
|
||||||
|
<% else %>
|
||||||
|
<dl class="billing-profile-dl">
|
||||||
|
<% lines.each do |label, value| %>
|
||||||
|
<dt><%= label %></dt>
|
||||||
|
<dd><%= value %></dd>
|
||||||
|
<% end %>
|
||||||
|
</dl>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if status == :incomplete %>
|
||||||
|
<ul class="muted" style="margin:12px 0 0;padding-left:1.2rem;font-size:0.88rem">
|
||||||
|
<% club.billing_profile_errors.each do |message| %>
|
||||||
|
<li><%= message %></li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
<% end %>
|
||||||
|
</section>
|
||||||
@@ -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 %>
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<th><%= t("admin.clubs.index.table.club") %></th>
|
<th><%= t("admin.clubs.index.table.club") %></th>
|
||||||
<th><%= t("admin.clubs.index.table.plan") %></th>
|
<th><%= t("admin.clubs.index.table.plan") %></th>
|
||||||
<th><%= t("admin.clubs.index.table.teams") %></th>
|
<th><%= t("admin.clubs.index.table.teams") %></th>
|
||||||
|
<th><%= t("admin.clubs.index.table.billing_profile") %></th>
|
||||||
<th><%= t("admin.clubs.index.table.comped") %></th>
|
<th><%= t("admin.clubs.index.table.comped") %></th>
|
||||||
<th><%= t("admin.clubs.index.table.stripe") %></th>
|
<th><%= t("admin.clubs.index.table.stripe") %></th>
|
||||||
<th><%= t("admin.clubs.index.table.quote") %></th>
|
<th><%= t("admin.clubs.index.table.quote") %></th>
|
||||||
@@ -18,10 +19,16 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<% @clubs.each do |club| %>
|
<% @clubs.each do |club| %>
|
||||||
<% sub = club.subscription %>
|
<% sub = club.subscription %>
|
||||||
|
<% billing_status = club.billing_profile_admin_status %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong><%= club.name %></strong></td>
|
<td><strong><%= club.name %></strong></td>
|
||||||
<td><%= sub&.plan&.name || t("admin.common.free_plan") %></td>
|
<td><%= sub&.plan&.name || t("admin.common.free_plan") %></td>
|
||||||
<td><%= club.teams.size %></td>
|
<td><%= club.teams.size %></td>
|
||||||
|
<td>
|
||||||
|
<span class="admin-billing-status admin-billing-status--<%= billing_status %>">
|
||||||
|
<%= t("admin.clubs.billing_status.#{billing_status}") %>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<% if sub&.admin_comped? %>
|
<% if sub&.admin_comped? %>
|
||||||
<span style="color:#ffb74d"><%= t("admin.common.yes") %></span>
|
<span style="color:#ffb74d"><%= t("admin.common.yes") %></span>
|
||||||
|
|||||||
@@ -6,8 +6,12 @@
|
|||||||
· <%= link_to t("admin.clubs.show.recordings_archive"), admin_club_recordings_path(@club) %>
|
· <%= 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.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.youtube_platform_link"), admin_youtube_platform_path %>
|
||||||
|
· <%= link_to t("admin.clubs.show.edit_link"), edit_admin_club_path(@club) %>
|
||||||
</p>
|
</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/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) %>
|
<%= 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="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
<%= csrf_meta_tags %>
|
<%= csrf_meta_tags %>
|
||||||
<link rel="stylesheet" href="/admin.css?v=19">
|
<link rel="stylesheet" href="/admin.css?v=21">
|
||||||
<%= yield :head %>
|
<%= yield :head %>
|
||||||
<% if content_for?(:replay_archive_styles) %>
|
<% if content_for?(:replay_archive_styles) %>
|
||||||
<link rel="stylesheet" href="/marketing.css?v=42">
|
<link rel="stylesheet" href="/marketing.css?v=42">
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ de:
|
|||||||
invoice_issued: "Rechnung %{number} ausgestellt und an %{email} gesendet."
|
invoice_issued: "Rechnung %{number} ausgestellt und an %{email} gesendet."
|
||||||
invoice_updated: "Rechnung %{number} aktualisiert."
|
invoice_updated: "Rechnung %{number} aktualisiert."
|
||||||
invoice_uploaded: "Rechnung hochgeladen und an %{email} gesendet."
|
invoice_uploaded: "Rechnung hochgeladen und an %{email} gesendet."
|
||||||
|
club_updated: "Daten für %{club} aktualisiert."
|
||||||
youtube_not_configured: Konfiguriere YOUTUBE_CLIENT_ID und YOUTUBE_CLIENT_SECRET in .env
|
youtube_not_configured: Konfiguriere YOUTUBE_CLIENT_ID und YOUTUBE_CLIENT_SECRET in .env
|
||||||
announcement_created: Hinweis gespeichert
|
announcement_created: Hinweis gespeichert
|
||||||
announcement_updated: Hinweis aktualisiert
|
announcement_updated: Hinweis aktualisiert
|
||||||
@@ -239,8 +240,10 @@ de:
|
|||||||
club: Verein
|
club: Verein
|
||||||
plan: Plan
|
plan: Plan
|
||||||
teams: Teams
|
teams: Teams
|
||||||
|
billing_profile: Rechnungsdaten
|
||||||
comped: Kostenlos
|
comped: Kostenlos
|
||||||
stripe: Stripe
|
stripe: Stripe
|
||||||
|
quote: Vereinbart
|
||||||
manage: Verwalten
|
manage: Verwalten
|
||||||
invoices: Rechnungen
|
invoices: Rechnungen
|
||||||
show:
|
show:
|
||||||
@@ -254,6 +257,13 @@ de:
|
|||||||
youtube_premium_full_not_connected: Premium Full — Vereinskanal nicht verbunden (nutzt Match Live TV)
|
youtube_premium_full_not_connected: Premium Full — Vereinskanal nicht verbunden (nutzt Match Live TV)
|
||||||
no_channel_hint_html: "Ohne verbundenen Kanal nutzt die App den Match-Live-TV-Kanal. %{link}."
|
no_channel_hint_html: "Ohne verbundenen Kanal nutzt die App den Match-Live-TV-Kanal. %{link}."
|
||||||
link_channel: Kanal verbinden (Vereinsseite)
|
link_channel: Kanal verbinden (Vereinsseite)
|
||||||
|
profile_title: Kontaktdaten & Rechnungsdaten
|
||||||
|
owner_title: Kontoinhaber
|
||||||
|
owner_name: Name
|
||||||
|
owner_email: E-Mail
|
||||||
|
owner_none: Kein Inhaber zugeordnet.
|
||||||
|
billing_title: Rechnungsdaten
|
||||||
|
billing_none: Keine Rechnungsdaten hinterlegt.
|
||||||
teams_title: Teams
|
teams_title: Teams
|
||||||
no_teams: Keine Teams registriert.
|
no_teams: Keine Teams registriert.
|
||||||
table:
|
table:
|
||||||
@@ -265,6 +275,46 @@ de:
|
|||||||
concurrency_lead: Dasselbe Konto hat versucht, eine weitere Direktübertragung zu starten, während bereits eine lief.
|
concurrency_lead: Dasselbe Konto hat versucht, eine weitere Direktübertragung zu starten, während bereits eine lief.
|
||||||
concurrency_none: Keine Versuche für diesen Verein erfasst.
|
concurrency_none: Keine Versuche für diesen Verein erfasst.
|
||||||
concurrency_all: Alle Konto-Missbräuche ansehen
|
concurrency_all: Alle Konto-Missbräuche ansehen
|
||||||
|
edit_link: Daten bearbeiten
|
||||||
|
edit:
|
||||||
|
back: "← Zurück zum Verein"
|
||||||
|
title: "%{club} bearbeiten"
|
||||||
|
lead: "Kundendaten überschreiben: Profil, Abrechnung, Inhaber, Staff, Einladungen und Teams."
|
||||||
|
cancel: Abbrechen
|
||||||
|
submit: Änderungen speichern
|
||||||
|
billing_hint: Du kannst ein unvollständiges Profil speichern; der Badge auf der Vereinsseite zeigt den Status.
|
||||||
|
staff_hint: Bereits mit den Teams des Vereins verknüpfte Benutzer.
|
||||||
|
staff_none: Kein Staff mit Teams verknüpft.
|
||||||
|
invitations_hint: Nur noch ausstehende Einladungen.
|
||||||
|
invitations_none: Keine ausstehenden Einladungen.
|
||||||
|
sections:
|
||||||
|
club: Vereinsprofil
|
||||||
|
billing: Rechnungsdaten
|
||||||
|
owner: Kontoinhaber
|
||||||
|
staff: Angenommenes Staff
|
||||||
|
invitations: Ausstehende Einladungen
|
||||||
|
teams: Teams
|
||||||
|
fields:
|
||||||
|
name: Vereinsname
|
||||||
|
sport: Sportart
|
||||||
|
primary_color: Primärfarbe
|
||||||
|
secondary_color: Sekundärfarbe
|
||||||
|
logo_url: Logo-URL
|
||||||
|
owner_name: Name des Inhabers
|
||||||
|
owner_email: E-Mail des Inhabers
|
||||||
|
staff_name: Name
|
||||||
|
staff_email: E-Mail
|
||||||
|
invitation_team: Team
|
||||||
|
invitation_email: Einladungs-E-Mail
|
||||||
|
team_name: Teamname
|
||||||
|
team_sport: Teamsport
|
||||||
|
errors:
|
||||||
|
owner_missing: Kein Inhaber mit diesem Verein verknüpft.
|
||||||
|
email_taken: "E-Mail bereits vergeben: %{email}"
|
||||||
|
billing_status:
|
||||||
|
complete: Vollständig
|
||||||
|
incomplete: Unvollständig
|
||||||
|
absent: Fehlend
|
||||||
comped:
|
comped:
|
||||||
title: Kostenloses Abonnement
|
title: Kostenloses Abonnement
|
||||||
description: "Sponsor oder Aktion: Vergib Premium Light/Full ohne Stripe-Zahlung. Jederzeit widerrufbar."
|
description: "Sponsor oder Aktion: Vergib Premium Light/Full ohne Stripe-Zahlung. Jederzeit widerrufbar."
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ en:
|
|||||||
invoice_issued: "Invoice %{number} issued and sent to %{email}."
|
invoice_issued: "Invoice %{number} issued and sent to %{email}."
|
||||||
invoice_updated: "Invoice %{number} updated."
|
invoice_updated: "Invoice %{number} updated."
|
||||||
invoice_uploaded: "Invoice uploaded and sent to %{email}."
|
invoice_uploaded: "Invoice uploaded and sent to %{email}."
|
||||||
|
club_updated: "Data updated for %{club}."
|
||||||
youtube_not_configured: Configure YOUTUBE_CLIENT_ID and YOUTUBE_CLIENT_SECRET in .env
|
youtube_not_configured: Configure YOUTUBE_CLIENT_ID and YOUTUBE_CLIENT_SECRET in .env
|
||||||
announcement_created: Notice saved
|
announcement_created: Notice saved
|
||||||
announcement_updated: Notice updated
|
announcement_updated: Notice updated
|
||||||
@@ -239,8 +240,10 @@ en:
|
|||||||
club: Club
|
club: Club
|
||||||
plan: Plan
|
plan: Plan
|
||||||
teams: Teams
|
teams: Teams
|
||||||
|
billing_profile: Billing data
|
||||||
comped: Comp
|
comped: Comp
|
||||||
stripe: Stripe
|
stripe: Stripe
|
||||||
|
quote: Quoted
|
||||||
manage: Manage
|
manage: Manage
|
||||||
invoices: Invoices
|
invoices: Invoices
|
||||||
show:
|
show:
|
||||||
@@ -254,6 +257,13 @@ en:
|
|||||||
youtube_premium_full_not_connected: Premium Full — club channel not connected (using Match Live TV)
|
youtube_premium_full_not_connected: Premium Full — club channel not connected (using Match Live TV)
|
||||||
no_channel_hint_html: "Without a connected channel, the app uses the Match Live TV channel. %{link}."
|
no_channel_hint_html: "Without a connected channel, the app uses the Match Live TV channel. %{link}."
|
||||||
link_channel: Connect channel (club page)
|
link_channel: Connect channel (club page)
|
||||||
|
profile_title: Contact & billing details
|
||||||
|
owner_title: Account owner
|
||||||
|
owner_name: Name
|
||||||
|
owner_email: Email
|
||||||
|
owner_none: No owner associated.
|
||||||
|
billing_title: Billing details
|
||||||
|
billing_none: No billing details provided.
|
||||||
teams_title: Teams
|
teams_title: Teams
|
||||||
no_teams: No teams registered.
|
no_teams: No teams registered.
|
||||||
table:
|
table:
|
||||||
@@ -265,6 +275,46 @@ en:
|
|||||||
concurrency_lead: Same account tried to start another live while one was already running.
|
concurrency_lead: Same account tried to start another live while one was already running.
|
||||||
concurrency_none: No attempts recorded for this club.
|
concurrency_none: No attempts recorded for this club.
|
||||||
concurrency_all: View all account abuse
|
concurrency_all: View all account abuse
|
||||||
|
edit_link: Edit data
|
||||||
|
edit:
|
||||||
|
back: "← Back to club"
|
||||||
|
title: "Edit %{club}"
|
||||||
|
lead: "Override client-entered data: profile, billing, owner, staff, invitations and teams."
|
||||||
|
cancel: Cancel
|
||||||
|
submit: Save changes
|
||||||
|
billing_hint: You can save an incomplete profile; the badge on the club page will reflect the status.
|
||||||
|
staff_hint: Users already linked to this club's teams (including owners if also staff).
|
||||||
|
staff_none: No staff linked to teams.
|
||||||
|
invitations_hint: Only invitations still awaiting acceptance.
|
||||||
|
invitations_none: No pending invitations.
|
||||||
|
sections:
|
||||||
|
club: Club profile
|
||||||
|
billing: Billing details
|
||||||
|
owner: Account owner
|
||||||
|
staff: Accepted staff
|
||||||
|
invitations: Pending invitations
|
||||||
|
teams: Teams
|
||||||
|
fields:
|
||||||
|
name: Club name
|
||||||
|
sport: Sport
|
||||||
|
primary_color: Primary colour
|
||||||
|
secondary_color: Secondary colour
|
||||||
|
logo_url: Logo URL
|
||||||
|
owner_name: Owner name
|
||||||
|
owner_email: Owner email
|
||||||
|
staff_name: Name
|
||||||
|
staff_email: Email
|
||||||
|
invitation_team: Team
|
||||||
|
invitation_email: Invitation email
|
||||||
|
team_name: Team name
|
||||||
|
team_sport: Team sport
|
||||||
|
errors:
|
||||||
|
owner_missing: No owner associated with this club.
|
||||||
|
email_taken: "Email already in use: %{email}"
|
||||||
|
billing_status:
|
||||||
|
complete: Complete
|
||||||
|
incomplete: Incomplete
|
||||||
|
absent: Missing
|
||||||
comped:
|
comped:
|
||||||
title: Complimentary subscription
|
title: Complimentary subscription
|
||||||
description: "Sponsor or promotion: grant Premium Light/Full without a Stripe payment. Revocable at any time."
|
description: "Sponsor or promotion: grant Premium Light/Full without a Stripe payment. Revocable at any time."
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ es:
|
|||||||
invoice_issued: "Factura %{number} emitida y enviada a %{email}."
|
invoice_issued: "Factura %{number} emitida y enviada a %{email}."
|
||||||
invoice_updated: "Factura %{number} actualizada."
|
invoice_updated: "Factura %{number} actualizada."
|
||||||
invoice_uploaded: "Factura subida y enviada a %{email}."
|
invoice_uploaded: "Factura subida y enviada a %{email}."
|
||||||
|
club_updated: "Datos actualizados para %{club}."
|
||||||
youtube_not_configured: Configura YOUTUBE_CLIENT_ID y YOUTUBE_CLIENT_SECRET en .env
|
youtube_not_configured: Configura YOUTUBE_CLIENT_ID y YOUTUBE_CLIENT_SECRET en .env
|
||||||
announcement_created: Aviso guardado
|
announcement_created: Aviso guardado
|
||||||
announcement_updated: Aviso actualizado
|
announcement_updated: Aviso actualizado
|
||||||
@@ -239,8 +240,10 @@ es:
|
|||||||
club: Club
|
club: Club
|
||||||
plan: Plan
|
plan: Plan
|
||||||
teams: Equipos
|
teams: Equipos
|
||||||
|
billing_profile: Datos fiscales
|
||||||
comped: Cortesía
|
comped: Cortesía
|
||||||
stripe: Stripe
|
stripe: Stripe
|
||||||
|
quote: Acordado
|
||||||
manage: Gestionar
|
manage: Gestionar
|
||||||
invoices: Facturas
|
invoices: Facturas
|
||||||
show:
|
show:
|
||||||
@@ -254,6 +257,13 @@ es:
|
|||||||
youtube_premium_full_not_connected: Premium Full — canal del club no conectado (usa Match Live TV)
|
youtube_premium_full_not_connected: Premium Full — canal del club no conectado (usa Match Live TV)
|
||||||
no_channel_hint_html: "Sin un canal conectado, la app usa el canal de Match Live TV. %{link}."
|
no_channel_hint_html: "Sin un canal conectado, la app usa el canal de Match Live TV. %{link}."
|
||||||
link_channel: Conectar canal (página del club)
|
link_channel: Conectar canal (página del club)
|
||||||
|
profile_title: Contacto y datos fiscales
|
||||||
|
owner_title: Titular de la cuenta
|
||||||
|
owner_name: Nombre
|
||||||
|
owner_email: Correo
|
||||||
|
owner_none: No hay titular asociado.
|
||||||
|
billing_title: Datos de facturación
|
||||||
|
billing_none: No hay datos de facturación.
|
||||||
teams_title: Equipos
|
teams_title: Equipos
|
||||||
no_teams: No hay equipos registrados.
|
no_teams: No hay equipos registrados.
|
||||||
table:
|
table:
|
||||||
@@ -265,6 +275,46 @@ es:
|
|||||||
concurrency_lead: La misma cuenta intentó iniciar otro directo mientras ya había uno en curso.
|
concurrency_lead: La misma cuenta intentó iniciar otro directo mientras ya había uno en curso.
|
||||||
concurrency_none: No hay intentos registrados para este club.
|
concurrency_none: No hay intentos registrados para este club.
|
||||||
concurrency_all: Ver todos los abusos de cuenta
|
concurrency_all: Ver todos los abusos de cuenta
|
||||||
|
edit_link: Editar datos
|
||||||
|
edit:
|
||||||
|
back: "← Volver al club"
|
||||||
|
title: "Editar %{club}"
|
||||||
|
lead: "Fuerza los datos introducidos por el cliente: perfil, facturación, titular, staff, invitaciones y equipos."
|
||||||
|
cancel: Cancelar
|
||||||
|
submit: Guardar cambios
|
||||||
|
billing_hint: Puedes guardar un perfil incompleto; el badge de la ficha reflejará el estado.
|
||||||
|
staff_hint: Usuarios ya vinculados a los equipos del club.
|
||||||
|
staff_none: No hay staff vinculado a los equipos.
|
||||||
|
invitations_hint: Solo invitaciones pendientes de aceptación.
|
||||||
|
invitations_none: No hay invitaciones pendientes.
|
||||||
|
sections:
|
||||||
|
club: Perfil del club
|
||||||
|
billing: Datos de facturación
|
||||||
|
owner: Titular de la cuenta
|
||||||
|
staff: Staff aceptado
|
||||||
|
invitations: Invitaciones pendientes
|
||||||
|
teams: Equipos
|
||||||
|
fields:
|
||||||
|
name: Nombre del club
|
||||||
|
sport: Deporte
|
||||||
|
primary_color: Color primario
|
||||||
|
secondary_color: Color secundario
|
||||||
|
logo_url: URL del logo
|
||||||
|
owner_name: Nombre del titular
|
||||||
|
owner_email: Correo del titular
|
||||||
|
staff_name: Nombre
|
||||||
|
staff_email: Correo
|
||||||
|
invitation_team: Equipo
|
||||||
|
invitation_email: Correo de invitación
|
||||||
|
team_name: Nombre del equipo
|
||||||
|
team_sport: Deporte del equipo
|
||||||
|
errors:
|
||||||
|
owner_missing: No hay titular asociado a este club.
|
||||||
|
email_taken: "Correo ya en uso: %{email}"
|
||||||
|
billing_status:
|
||||||
|
complete: Completo
|
||||||
|
incomplete: Incompleto
|
||||||
|
absent: Ausentes
|
||||||
comped:
|
comped:
|
||||||
title: Suscripción de cortesía
|
title: Suscripción de cortesía
|
||||||
description: "Patrocinador o promoción: concede Premium Light/Full sin pago en Stripe. Revocable en cualquier momento."
|
description: "Patrocinador o promoción: concede Premium Light/Full sin pago en Stripe. Revocable en cualquier momento."
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ fr:
|
|||||||
invoice_issued: "Facture %{number} émise et envoyée à %{email}."
|
invoice_issued: "Facture %{number} émise et envoyée à %{email}."
|
||||||
invoice_updated: "Facture %{number} mise à jour."
|
invoice_updated: "Facture %{number} mise à jour."
|
||||||
invoice_uploaded: "Facture chargée et envoyée à %{email}."
|
invoice_uploaded: "Facture chargée et envoyée à %{email}."
|
||||||
|
club_updated: "Données mises à jour pour %{club}."
|
||||||
youtube_not_configured: Configurez YOUTUBE_CLIENT_ID et YOUTUBE_CLIENT_SECRET dans .env
|
youtube_not_configured: Configurez YOUTUBE_CLIENT_ID et YOUTUBE_CLIENT_SECRET dans .env
|
||||||
announcement_created: Alerte enregistrée
|
announcement_created: Alerte enregistrée
|
||||||
announcement_updated: Alerte mise à jour
|
announcement_updated: Alerte mise à jour
|
||||||
@@ -239,8 +240,10 @@ fr:
|
|||||||
club: Club
|
club: Club
|
||||||
plan: Forfait
|
plan: Forfait
|
||||||
teams: Équipes
|
teams: Équipes
|
||||||
|
billing_profile: Données fiscales
|
||||||
comped: Offert
|
comped: Offert
|
||||||
stripe: Stripe
|
stripe: Stripe
|
||||||
|
quote: Convenu
|
||||||
manage: Gérer
|
manage: Gérer
|
||||||
invoices: Factures
|
invoices: Factures
|
||||||
show:
|
show:
|
||||||
@@ -254,6 +257,13 @@ fr:
|
|||||||
youtube_premium_full_not_connected: Premium Full — chaîne du club non connectée (utilise Match Live TV)
|
youtube_premium_full_not_connected: Premium Full — chaîne du club non connectée (utilise Match Live TV)
|
||||||
no_channel_hint_html: "Sans chaîne connectée, l'application utilise la chaîne Match Live TV. %{link}."
|
no_channel_hint_html: "Sans chaîne connectée, l'application utilise la chaîne Match Live TV. %{link}."
|
||||||
link_channel: Connecter la chaîne (page du club)
|
link_channel: Connecter la chaîne (page du club)
|
||||||
|
profile_title: Coordonnées et données fiscales
|
||||||
|
owner_title: Titulaire du compte
|
||||||
|
owner_name: Nom
|
||||||
|
owner_email: E-mail
|
||||||
|
owner_none: Aucun titulaire associé.
|
||||||
|
billing_title: Données de facturation
|
||||||
|
billing_none: Aucune donnée de facturation renseignée.
|
||||||
teams_title: Équipes
|
teams_title: Équipes
|
||||||
no_teams: Aucune équipe enregistrée.
|
no_teams: Aucune équipe enregistrée.
|
||||||
table:
|
table:
|
||||||
@@ -265,6 +275,46 @@ fr:
|
|||||||
concurrency_lead: Le même compte a tenté de démarrer un autre direct alors qu’un était déjà en cours.
|
concurrency_lead: Le même compte a tenté de démarrer un autre direct alors qu’un était déjà en cours.
|
||||||
concurrency_none: Aucune tentative enregistrée pour ce club.
|
concurrency_none: Aucune tentative enregistrée pour ce club.
|
||||||
concurrency_all: Voir tous les abus de compte
|
concurrency_all: Voir tous les abus de compte
|
||||||
|
edit_link: Modifier les données
|
||||||
|
edit:
|
||||||
|
back: "← Retour au club"
|
||||||
|
title: "Modifier %{club}"
|
||||||
|
lead: "Forcez les données saisies par le client : profil, facturation, titulaire, staff, invitations et équipes."
|
||||||
|
cancel: Annuler
|
||||||
|
submit: Enregistrer
|
||||||
|
billing_hint: Vous pouvez enregistrer un profil incomplet ; le badge sur la fiche reflète l'état.
|
||||||
|
staff_hint: Utilisateurs déjà liés aux équipes du club.
|
||||||
|
staff_none: Aucun staff lié aux équipes.
|
||||||
|
invitations_hint: Uniquement les invitations encore en attente d'acceptation.
|
||||||
|
invitations_none: Aucune invitation en attente.
|
||||||
|
sections:
|
||||||
|
club: Profil du club
|
||||||
|
billing: Données de facturation
|
||||||
|
owner: Titulaire du compte
|
||||||
|
staff: Staff accepté
|
||||||
|
invitations: Invitations en attente
|
||||||
|
teams: Équipes
|
||||||
|
fields:
|
||||||
|
name: Nom du club
|
||||||
|
sport: Sport
|
||||||
|
primary_color: Couleur primaire
|
||||||
|
secondary_color: Couleur secondaire
|
||||||
|
logo_url: URL du logo
|
||||||
|
owner_name: Nom du titulaire
|
||||||
|
owner_email: E-mail du titulaire
|
||||||
|
staff_name: Nom
|
||||||
|
staff_email: E-mail
|
||||||
|
invitation_team: Équipe
|
||||||
|
invitation_email: E-mail d'invitation
|
||||||
|
team_name: Nom de l'équipe
|
||||||
|
team_sport: Sport de l'équipe
|
||||||
|
errors:
|
||||||
|
owner_missing: Aucun titulaire associé à ce club.
|
||||||
|
email_taken: "E-mail déjà utilisé : %{email}"
|
||||||
|
billing_status:
|
||||||
|
complete: Complet
|
||||||
|
incomplete: Incomplet
|
||||||
|
absent: Absentes
|
||||||
comped:
|
comped:
|
||||||
title: Abonnement offert
|
title: Abonnement offert
|
||||||
description: "Sponsor ou promotion : accordez Premium Light/Full sans paiement Stripe. Révocable à tout moment."
|
description: "Sponsor ou promotion : accordez Premium Light/Full sans paiement Stripe. Révocable à tout moment."
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ it:
|
|||||||
quote_revoked: "Prezzo concordato revocato per %{club}."
|
quote_revoked: "Prezzo concordato revocato per %{club}."
|
||||||
transfer_confirmed: "Bonifico confermato: piano %{plan} attivo per %{club}."
|
transfer_confirmed: "Bonifico confermato: piano %{plan} attivo per %{club}."
|
||||||
transfer_cancelled: Bonifico in attesa annullato.
|
transfer_cancelled: Bonifico in attesa annullato.
|
||||||
|
club_updated: "Dati aggiornati per %{club}."
|
||||||
youtube_not_configured: Configura YOUTUBE_CLIENT_ID e YOUTUBE_CLIENT_SECRET in .env
|
youtube_not_configured: Configura YOUTUBE_CLIENT_ID e YOUTUBE_CLIENT_SECRET in .env
|
||||||
announcement_created: Avviso salvato
|
announcement_created: Avviso salvato
|
||||||
announcement_updated: Avviso aggiornato
|
announcement_updated: Avviso aggiornato
|
||||||
@@ -243,6 +244,7 @@ it:
|
|||||||
club: Società
|
club: Società
|
||||||
plan: Piano
|
plan: Piano
|
||||||
teams: Squadre
|
teams: Squadre
|
||||||
|
billing_profile: Dati fiscali
|
||||||
comped: Omaggio
|
comped: Omaggio
|
||||||
stripe: Stripe
|
stripe: Stripe
|
||||||
quote: Concordato
|
quote: Concordato
|
||||||
@@ -259,6 +261,13 @@ it:
|
|||||||
youtube_premium_full_not_connected: Premium Full — canale società non collegato (usa Match Live TV)
|
youtube_premium_full_not_connected: Premium Full — canale società non collegato (usa Match Live TV)
|
||||||
no_channel_hint_html: "Senza canale collegato, l’app usa il canale Match Live TV. %{link}."
|
no_channel_hint_html: "Senza canale collegato, l’app usa il canale Match Live TV. %{link}."
|
||||||
link_channel: Collega canale (pagina società)
|
link_channel: Collega canale (pagina società)
|
||||||
|
profile_title: Anagrafica e dati fiscali
|
||||||
|
owner_title: Titolare account
|
||||||
|
owner_name: Nome
|
||||||
|
owner_email: Email
|
||||||
|
owner_none: Nessun titolare associato.
|
||||||
|
billing_title: Dati di fatturazione
|
||||||
|
billing_none: Nessun dato di fatturazione compilato.
|
||||||
teams_title: Squadre
|
teams_title: Squadre
|
||||||
no_teams: Nessuna squadra registrata.
|
no_teams: Nessuna squadra registrata.
|
||||||
table:
|
table:
|
||||||
@@ -270,6 +279,46 @@ it:
|
|||||||
concurrency_lead: Stesso account che ha provato ad avviare un’altra diretta mentre ne era già in corso una.
|
concurrency_lead: Stesso account che ha provato ad avviare un’altra diretta mentre ne era già in corso una.
|
||||||
concurrency_none: Nessun tentativo registrato per questa società.
|
concurrency_none: Nessun tentativo registrato per questa società.
|
||||||
concurrency_all: Vedi tutti gli abusi account
|
concurrency_all: Vedi tutti gli abusi account
|
||||||
|
edit_link: Modifica dati
|
||||||
|
edit:
|
||||||
|
back: "← Torna alla società"
|
||||||
|
title: "Modifica %{club}"
|
||||||
|
lead: "Forza i dati inseriti dal cliente: anagrafica, fatturazione, titolare, staff, inviti e squadre."
|
||||||
|
cancel: Annulla
|
||||||
|
submit: Salva modifiche
|
||||||
|
billing_hint: Puoi salvare anche un profilo incompleto; il badge in scheda rifletterà lo stato.
|
||||||
|
staff_hint: Utenti già collegati alle squadre della società (inclusi eventuali titolari).
|
||||||
|
staff_none: Nessuno staff collegato alle squadre.
|
||||||
|
invitations_hint: Solo inviti ancora in attesa di accettazione.
|
||||||
|
invitations_none: Nessun invito in attesa.
|
||||||
|
sections:
|
||||||
|
club: Anagrafica società
|
||||||
|
billing: Dati di fatturazione
|
||||||
|
owner: Titolare account
|
||||||
|
staff: Staff e invitati accettati
|
||||||
|
invitations: Inviti in attesa
|
||||||
|
teams: Squadre
|
||||||
|
fields:
|
||||||
|
name: Nome società
|
||||||
|
sport: Sport
|
||||||
|
primary_color: Colore primario
|
||||||
|
secondary_color: Colore secondario
|
||||||
|
logo_url: URL logo
|
||||||
|
owner_name: Nome titolare
|
||||||
|
owner_email: Email titolare
|
||||||
|
staff_name: Nome
|
||||||
|
staff_email: Email
|
||||||
|
invitation_team: Squadra
|
||||||
|
invitation_email: Email invito
|
||||||
|
team_name: Nome squadra
|
||||||
|
team_sport: Sport squadra
|
||||||
|
errors:
|
||||||
|
owner_missing: Nessun titolare associato a questa società.
|
||||||
|
email_taken: "Email già in uso: %{email}"
|
||||||
|
billing_status:
|
||||||
|
complete: Completo
|
||||||
|
incomplete: Incompleto
|
||||||
|
absent: Assenti
|
||||||
comped:
|
comped:
|
||||||
title: Abbonamento omaggio
|
title: Abbonamento omaggio
|
||||||
description: "Sponsor o promozione: assegna Premium Light/Full senza pagamento Stripe. Revocabile in qualsiasi momento."
|
description: "Sponsor o promozione: assegna Premium Light/Full senza pagamento Stripe. Revocabile in qualsiasi momento."
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ Rails.application.routes.draw do
|
|||||||
post "billing/transfers/:id/confirm", to: "billing#confirm_transfer", as: :billing_transfer_confirm
|
post "billing/transfers/:id/confirm", to: "billing#confirm_transfer", as: :billing_transfer_confirm
|
||||||
post "billing/transfers/:id/cancel", to: "billing#cancel_transfer", as: :billing_transfer_cancel
|
post "billing/transfers/:id/cancel", to: "billing#cancel_transfer", as: :billing_transfer_cancel
|
||||||
resources :teams, only: %i[show]
|
resources :teams, only: %i[show]
|
||||||
resources :clubs, only: %i[index show] do
|
resources :clubs, only: %i[index show edit update] do
|
||||||
member do
|
member do
|
||||||
post :grant_comped
|
post :grant_comped
|
||||||
delete :revoke_comped
|
delete :revoke_comped
|
||||||
|
|||||||
@@ -622,6 +622,35 @@ body.admin-body {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-billing-status {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
padding: 0.15rem 0.5rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid #3a3a45;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-billing-status--complete {
|
||||||
|
border-color: #2e7d32;
|
||||||
|
color: #a5f0b8;
|
||||||
|
background: #1b3d1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-billing-status--incomplete {
|
||||||
|
border-color: #f9a825;
|
||||||
|
color: #ffe082;
|
||||||
|
background: #3d3210;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-billing-status--absent {
|
||||||
|
border-color: #555;
|
||||||
|
color: #999;
|
||||||
|
background: #1a1a22;
|
||||||
|
}
|
||||||
|
|
||||||
.billing-upload-form {
|
.billing-upload-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -652,6 +681,19 @@ body.admin-body {
|
|||||||
max-width: 720px;
|
max-width: 720px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-form--wide {
|
||||||
|
max-width: 920px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-form .panel {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-form .panel h3 {
|
||||||
|
margin: 0 0 0.85rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.admin-form-row {
|
.admin-form-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
|
|||||||
@@ -41,4 +41,15 @@ RSpec.describe ClubBillingProfile do
|
|||||||
club.billing_fiscal_code = "RSSMRA80A01H501U"
|
club.billing_fiscal_code = "RSSMRA80A01H501U"
|
||||||
expect(club.billing_profile_complete?).to be true
|
expect(club.billing_profile_complete?).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "classifies admin billing status" do
|
||||||
|
expect(club.billing_profile_admin_status).to eq(:complete)
|
||||||
|
|
||||||
|
club.billing_recipient_code = nil
|
||||||
|
club.billing_pec = nil
|
||||||
|
expect(club.billing_profile_admin_status).to eq(:incomplete)
|
||||||
|
|
||||||
|
empty = Club.create!(name: "Empty", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff")
|
||||||
|
expect(empty.billing_profile_admin_status).to eq(:absent)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe "Admin clubs billing profile", type: :request do
|
||||||
|
let!(:admin) { AdminAccount.create!(username: "ops-clubs-billing", password: "Password123") }
|
||||||
|
|
||||||
|
let!(:complete_club) do
|
||||||
|
Club.create!(
|
||||||
|
name: "Club Completo",
|
||||||
|
sport: "volleyball",
|
||||||
|
primary_color: "#e53935",
|
||||||
|
secondary_color: "#ffffff",
|
||||||
|
billing_entity_type: "company",
|
||||||
|
billing_legal_name: "ASD Completo",
|
||||||
|
billing_email: "fatture@completo.it",
|
||||||
|
billing_address_line: "Via Roma 1",
|
||||||
|
billing_city: "Milano",
|
||||||
|
billing_province: "MI",
|
||||||
|
billing_postal_code: "20100",
|
||||||
|
billing_country: "IT",
|
||||||
|
billing_vat_number: "12345678901",
|
||||||
|
billing_recipient_code: "ABCDEFG"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let!(:incomplete_club) do
|
||||||
|
Club.create!(
|
||||||
|
name: "Club Incompleto",
|
||||||
|
sport: "volleyball",
|
||||||
|
primary_color: "#e53935",
|
||||||
|
secondary_color: "#ffffff",
|
||||||
|
billing_entity_type: "company",
|
||||||
|
billing_legal_name: "ASD Incompleto",
|
||||||
|
billing_vat_number: "10987654321"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let!(:absent_club) do
|
||||||
|
Club.create!(
|
||||||
|
name: "Club Assente",
|
||||||
|
sport: "volleyball",
|
||||||
|
primary_color: "#e53935",
|
||||||
|
secondary_color: "#ffffff"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let!(:owner) do
|
||||||
|
user = User.create!(email: "owner-completo@test.it", name: "Mario Rossi", password: "Password123", role: "coach")
|
||||||
|
ClubMembership.create!(user: user, club: complete_club, role: "owner")
|
||||||
|
user
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
post admin_login_path, params: { username: "ops-clubs-billing", password: "Password123" }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "mostra i dati fiscali e il titolare nella scheda società" do
|
||||||
|
get admin_club_path(complete_club)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(response.body).to include("Anagrafica e dati fiscali")
|
||||||
|
expect(response.body).to include("Mario Rossi")
|
||||||
|
expect(response.body).to include("owner-completo@test.it")
|
||||||
|
expect(response.body).to include("ASD Completo")
|
||||||
|
expect(response.body).to include("12345678901")
|
||||||
|
expect(response.body).to include("ABCDEFG")
|
||||||
|
expect(response.body).to include("Completo")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "mostra lo stato incompleto con errori mancanti" do
|
||||||
|
get admin_club_path(incomplete_club)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(response.body).to include("Incompleto")
|
||||||
|
expect(response.body).to include("ASD Incompleto")
|
||||||
|
expect(response.body).to include("10987654321")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "mostra lo stato assente quando non ci sono dati fiscali" do
|
||||||
|
get admin_club_path(absent_club)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(response.body).to include("Assenti")
|
||||||
|
expect(response.body).to include("Nessun dato di fatturazione compilato.")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "elenca lo stato del profilo fiscale nella lista società" do
|
||||||
|
get admin_clubs_path
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(response.body).to include("Dati fiscali")
|
||||||
|
expect(response.body).to include("Completo")
|
||||||
|
expect(response.body).to include("Incompleto")
|
||||||
|
expect(response.body).to include("Assenti")
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe "Admin club data edit", type: :request do
|
||||||
|
let!(:admin) { AdminAccount.create!(username: "ops-club-edit", password: "Password123") }
|
||||||
|
let!(:club) do
|
||||||
|
Club.create!(
|
||||||
|
name: "Edit Club",
|
||||||
|
sport: "pallavolo",
|
||||||
|
primary_color: "#e53935",
|
||||||
|
secondary_color: "#ffffff",
|
||||||
|
billing_entity_type: "company",
|
||||||
|
billing_legal_name: "ASD Edit",
|
||||||
|
billing_email: "old-bill@test.it",
|
||||||
|
billing_address_line: "Via 1",
|
||||||
|
billing_city: "Milano",
|
||||||
|
billing_province: "MI",
|
||||||
|
billing_postal_code: "20100",
|
||||||
|
billing_country: "IT",
|
||||||
|
billing_vat_number: "12345678901",
|
||||||
|
billing_recipient_code: "ABCDEFG"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
let!(:owner) do
|
||||||
|
user = User.create!(email: "owner-typo@test.it", name: "Owner Old", password: "Password123", role: "coach")
|
||||||
|
ClubMembership.create!(user: user, club: club, role: "owner")
|
||||||
|
user
|
||||||
|
end
|
||||||
|
let!(:team) { club.teams.create!(name: "U14", sport: "pallavolo") }
|
||||||
|
let!(:staff) do
|
||||||
|
user = User.create!(email: "staff@test.it", name: "Staff Old", password: "Password123", role: "coach")
|
||||||
|
UserTeam.create!(user: user, team: team, role: "member", staff_kind: "transmission")
|
||||||
|
user
|
||||||
|
end
|
||||||
|
let!(:invitation) do
|
||||||
|
token = TeamInvitation.generate_token
|
||||||
|
TeamInvitation.create!(
|
||||||
|
team: team,
|
||||||
|
email: "invite-old@test.it",
|
||||||
|
token_digest: Digest::SHA256.hexdigest(token),
|
||||||
|
role: "member",
|
||||||
|
staff_kind: "transmission",
|
||||||
|
expires_at: 3.days.from_now
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
post admin_login_path, params: { username: "ops-club-edit", password: "Password123" }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "mostra il form di modifica" do
|
||||||
|
get edit_admin_club_path(club)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(response.body).to include("Modifica Edit Club")
|
||||||
|
expect(response.body).to include("owner-typo@test.it")
|
||||||
|
expect(response.body).to include("invite-old@test.it")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "forza anagrafica, billing, owner, staff, invito e squadra" do
|
||||||
|
patch admin_club_path(club), params: {
|
||||||
|
club: {
|
||||||
|
name: "Edit Club Nuovo",
|
||||||
|
sport: "pallavolo",
|
||||||
|
primary_color: "#112233",
|
||||||
|
secondary_color: "#ffffff",
|
||||||
|
billing_entity_type: "company",
|
||||||
|
billing_legal_name: "ASD Edit Nuova",
|
||||||
|
billing_email: "amministrazione@test.it",
|
||||||
|
billing_vat_number: "12345678901",
|
||||||
|
billing_address_line: "Via Roma 10",
|
||||||
|
billing_city: "Milano",
|
||||||
|
billing_province: "mi",
|
||||||
|
billing_postal_code: "20121",
|
||||||
|
billing_country: "it",
|
||||||
|
billing_recipient_code: "ABCDEFG"
|
||||||
|
},
|
||||||
|
owner: { name: "Owner Fixed", email: "amministrazione@test.it" },
|
||||||
|
staff_users: {
|
||||||
|
staff.id => { name: "Staff Fixed", email: "staff-fixed@test.it" }
|
||||||
|
},
|
||||||
|
invitations: {
|
||||||
|
invitation.id => { email: "invite-fixed@test.it" }
|
||||||
|
},
|
||||||
|
teams: {
|
||||||
|
team.id => { name: "U15", sport: "pallavolo" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response).to redirect_to(admin_club_path(club))
|
||||||
|
follow_redirect!
|
||||||
|
expect(response.body).to include("Dati aggiornati per Edit Club Nuovo")
|
||||||
|
|
||||||
|
club.reload
|
||||||
|
expect(club.name).to eq("Edit Club Nuovo")
|
||||||
|
expect(club.billing_email).to eq("amministrazione@test.it")
|
||||||
|
expect(club.billing_province).to eq("MI")
|
||||||
|
expect(club.billing_legal_name).to eq("ASD Edit Nuova")
|
||||||
|
expect(owner.reload.email).to eq("amministrazione@test.it")
|
||||||
|
expect(owner.name).to eq("Owner Fixed")
|
||||||
|
expect(staff.reload.email).to eq("staff-fixed@test.it")
|
||||||
|
expect(invitation.reload.email).to eq("invite-fixed@test.it")
|
||||||
|
expect(team.reload.name).to eq("U15")
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe Admin::UpdateClubData do
|
||||||
|
let!(:club) do
|
||||||
|
Club.create!(
|
||||||
|
name: "Svc Club",
|
||||||
|
sport: "pallavolo",
|
||||||
|
primary_color: "#e53935",
|
||||||
|
secondary_color: "#ffffff"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
let!(:owner) do
|
||||||
|
user = User.create!(email: "svc-owner@test.it", name: "Owner", password: "Password123", role: "coach")
|
||||||
|
ClubMembership.create!(user: user, club: club, role: "owner")
|
||||||
|
user
|
||||||
|
end
|
||||||
|
|
||||||
|
it "updates club and owner email" do
|
||||||
|
described_class.call(
|
||||||
|
club: club,
|
||||||
|
club_attrs: {
|
||||||
|
name: "Svc Club Updated",
|
||||||
|
billing_email: "bill@test.it",
|
||||||
|
billing_province: "bz"
|
||||||
|
},
|
||||||
|
owner_attrs: { email: "owner-fixed@test.it", name: "Owner Fixed" }
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(club.reload.name).to eq("Svc Club Updated")
|
||||||
|
expect(club.billing_email).to eq("bill@test.it")
|
||||||
|
expect(club.billing_province).to eq("BZ")
|
||||||
|
expect(owner.reload.email).to eq("owner-fixed@test.it")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises when email is already taken" do
|
||||||
|
User.create!(email: "taken@test.it", name: "Other", password: "Password123", role: "coach")
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.call(
|
||||||
|
club: club,
|
||||||
|
club_attrs: {},
|
||||||
|
owner_attrs: { email: "taken@test.it" }
|
||||||
|
)
|
||||||
|
end.to raise_error(Admin::UpdateClubData::Error)
|
||||||
|
end
|
||||||
|
end
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
fake mp4
|
||||||
Reference in New Issue
Block a user