Aggiunge pagamento con bonifico e importo concordato per società.

Il piano si attiva solo dopo la conferma admin; Stripe resta a listino se non c'è un prezzo commerciale.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-18 23:20:43 +02:00
co-authored by Cursor
parent 1e4e0560f1
commit e436f5ece4
56 changed files with 1571 additions and 53 deletions
@@ -18,6 +18,52 @@
club: @filter_club,
subscription: @filter_club.subscription,
return_to: admin_billing_path(club_id: @filter_club.id) %>
<%= render "admin/clubs/quote_form",
club: @filter_club,
quote: @filter_club.active_billing_quote,
return_to: admin_billing_path(club_id: @filter_club.id) %>
<% end %>
<h2 style="margin-top:8px;font-size:1.15rem"><%= t("admin.billing.index.transfers_title") %></h2>
<p style="color:#666;margin-bottom:16px"><%= t("admin.billing.index.transfers_description") %></p>
<% if @pending_transfers.any? %>
<div class="billing-pending-list" style="margin-bottom:32px">
<% @pending_transfers.each do |order| %>
<article id="transfer-<%= order.id %>" class="billing-pending-card">
<header class="billing-pending-card__head">
<div>
<strong><%= order.club.name %></strong>
· <%= order.plan.name %>
· <%= t("admin.billing.index.kind_#{order.quoted? ? 'quote' : 'list'}") %>
· <strong><%= order.formatted_amount %></strong>
· <%= order.created_at.to_date.strftime("%d/%m/%Y") %>
</div>
</header>
<div class="billing-pending-card__body">
<p style="margin:0 0 12px">
<%= t("admin.billing.index.transfers_table.causal") %>:
<strong><%= order.payment_causal %></strong>
</p>
<%= form_with url: admin_billing_transfer_confirm_path(order),
method: :post, multipart: true, local: true, class: "billing-upload-form" do %>
<label class="billing-upload-form__label">
<%= t("admin.billing.index.pdf_optional") %>
<%= file_field_tag :pdf, accept: "application/pdf" %>
</label>
<%= submit_tag t("admin.billing.index.confirm_button"), class: "admin-btn admin-btn--primary" %>
<% end %>
<%= button_to t("admin.billing.index.cancel_button"),
admin_billing_transfer_cancel_path(order),
method: :post,
class: "admin-btn admin-btn--secondary",
form: { style: "margin-top:8px", data: { turbo_confirm: t("admin.billing.index.cancel_confirm") } } %>
</div>
</article>
<% end %>
</div>
<% else %>
<p class="muted" style="margin-bottom:28px"><%= t("admin.billing.index.transfers_none") %></p>
<% end %>
<% if @pending_payments.any? %>
@@ -0,0 +1,71 @@
<%# locals: (club:, quote:, return_to: nil) %>
<section class="admin-comped-card" style="margin-bottom:24px;padding:16px;border:1px solid #2e5a3c;border-radius:8px;background:#142018">
<h2 style="margin:0 0 8px;font-size:1.1rem"><%= t("admin.quote.title") %></h2>
<p style="color:#bbb;font-size:0.9rem;margin:0 0 14px">
<%= t("admin.quote.description") %>
</p>
<% if quote %>
<p style="margin:0 0 12px">
<%= raw t("admin.quote.active_html",
plan: quote.plan.name,
interval: t("admin.quote.interval_#{quote.billing_interval}"),
amount: quote.formatted_amount) %>
<% if quote.note.present? %>
<br><span style="color:#888;font-size:0.85rem"><%= t("admin.quote.note_label") %> <%= quote.note %></span>
<% end %>
</p>
<% else %>
<p style="margin:0 0 12px;color:#888"><%= t("admin.quote.none") %></p>
<% end %>
<%= form_with url: set_quote_admin_club_path(club), method: :post, local: true do %>
<%= hidden_field_tag :return_to, return_to if return_to.present? %>
<div style="display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end">
<label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem">
<%= t("admin.quote.plan_label") %>
<%= select_tag :plan_slug,
options_for_select(
[["Premium Light", "premium_light"], ["Premium Full", "premium_full"]],
quote&.plan_slug
),
required: true,
class: "admin-input" %>
</label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem">
<%= t("admin.quote.interval_label") %>
<%= select_tag :interval,
options_for_select(
[[t("admin.quote.interval_yearly"), "yearly"], [t("admin.quote.interval_monthly"), "monthly"]],
quote&.billing_interval || "yearly"
),
required: true,
class: "admin-input" %>
</label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem">
<%= t("admin.quote.amount_label") %>
<%= text_field_tag :amount_euros,
quote ? format("%.2f", quote.amount_cents / 100.0) : nil,
placeholder: t("admin.quote.amount_placeholder"),
required: true,
class: "admin-input",
inputmode: "decimal" %>
</label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem;flex:1;min-width:200px">
<%= t("admin.quote.commercial_note_label") %>
<%= text_field_tag :note, quote&.note, class: "admin-input", style: "width:100%" %>
</label>
<%= submit_tag t("admin.quote.save_button"), class: "admin-btn admin-btn--primary" %>
</div>
<% end %>
<% if quote %>
<div style="margin-top:12px">
<%= button_to t("admin.quote.revoke_button"),
revoke_quote_admin_club_path(club, return_to: return_to),
method: :delete,
class: "admin-btn admin-btn--secondary",
form: { data: { turbo_confirm: t("admin.quote.revoke_confirm", club: club.name) } } %>
</div>
<% end %>
</section>
@@ -11,6 +11,7 @@
<th><%= t("admin.clubs.index.table.teams") %></th>
<th><%= t("admin.clubs.index.table.comped") %></th>
<th><%= t("admin.clubs.index.table.stripe") %></th>
<th><%= t("admin.clubs.index.table.quote") %></th>
<th></th>
</tr>
</thead>
@@ -30,6 +31,14 @@
<% end %>
</td>
<td><%= sub&.stripe_subscription_id.present? ? t("admin.common.yes") : t("admin.common.dash") %></td>
<td>
<% quote = club.billing_quote %>
<% if quote %>
<%= quote.plan.name %> · <%= quote.formatted_amount %>
<% else %>
<%= t("admin.common.dash") %>
<% end %>
</td>
<td>
<%= link_to t("admin.clubs.index.manage"), admin_club_path(club) %>
· <%= link_to t("admin.clubs.index.invoices"), admin_billing_path(club_id: club.id) %>
@@ -9,6 +9,7 @@
</p>
<%= 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) %>
<% cred = @club.youtube_credential %>
<p style="margin-top:16px">