{% extends "controlpanel/base.html" %} {% load lucide ui %} {% block heading %}Billing{% endblock heading %} {% block actions %} {% endblock actions %} {% block panel %} {% url 'controlpanel:plan_create' as plan_create_url %} {% include "controlpanel/_modal_form.html" with modal_id="plan_create_modal" title="New plan" form=plan_form action_url=plan_create_url submit_label="Create plan" submit_icon="plus" box_class="max-w-2xl" two_columns=True %}

{% lucide "layers" size=18 %} Plans

{% comment %} Prices are dated, not edited. A rate change is a new row with a future active_from; every period already opened keeps the amount it was billed at, so raising the price cannot rewrite an invoice you have already sent. {% endcomment %}

A rate change only takes effect as of a certain date. Periods already billed keep the amount they were issued at.

{% for plan in plans %} {% comment %} Named for what each measures from, because that is the easy thing to get wrong: grace runs from the period START, not its end. {% endcomment %} {% empty %} {% endfor %}
Plan Clocks Clubs Prices
{{ plan.name }}
{% if plan.is_trial %}Trial{% endif %} {% if not plan.is_active %}Retired{% endif %} {% if plan.description %}
{{ plan.description }}
{% endif %}
{{ plan.duration_months }} month{{ plan.duration_months|pluralize }} long
billed {{ plan.renewal_lead_days }}d before it starts
archived {{ plan.grace_days }}d after it starts
{{ plan.club_count }} {% for price in plan.prices.all %}
€{{ price.amount|floatformat:2 }} from {{ price.active_from|date:"j M Y" }} {% if price.active_from > today %}Scheduled{% endif %}
{% empty %} No price — cannot be billed {% endfor %}
{% lucide "trash-2" size=14 %} Delete
No plans yet.
{% comment %} Dialogs live outside the table: may only contain elements. {% endcomment %} {% for plan in plans %} {% url 'controlpanel:plan_price_create' plan.pk as plan_price_url %} {% include "controlpanel/_modal_form.html" with modal_id=plan.pk|dom_id:"plan_price_modal" title="New price — "|add:plan.name form=plan.price_form action_url=plan_price_url submit_label="Add price" submit_icon="euro" %} {% url 'controlpanel:plan_update' plan.pk as plan_update_url %} {% include "controlpanel/_modal_form.html" with modal_id=plan.pk|dom_id:"plan_edit_modal" title="Edit "|add:plan.name form=plan.edit_form action_url=plan_update_url submit_label="Save" submit_icon="check" box_class="max-w-2xl" two_columns=True %} {% endfor %}

{% lucide "receipt-euro" size=18 %} Owed

{% for due in owing %} {% empty %} {% endfor %}
Club Period Owed Status
{{ due.club.name }}
{{ due.plan.name }}
{{ due.period_start|date:"j M Y" }} — {{ due.period_end|date:"j M Y" }}
Grace to {{ due.grace_until|date:"j M Y" }}
€{{ due.balance|floatformat:2 }} {% if due.grace_until < today %} {% lucide "triangle-alert" size=12 %} Overdue {% elif due.period_end < today %} {% lucide "hourglass" size=12 %} In grace {% else %} {{ due.get_status_display }} {% endif %} {% lucide "file-down" size=14 %} Download invoice
Nothing outstanding.
{% comment %} Dialogs live outside the table: may only contain elements. {% endcomment %} {% for due in owing %} {% url 'controlpanel:due_pay' due.pk as due_pay_url %} {% include "controlpanel/_modal_form.html" with modal_id=due.pk|dom_id:"due_pay_modal" title="Record payment — "|add:due.club.name form=due.payment_form action_url=due_pay_url submit_label="Record payment" submit_icon="banknote" %} {% endfor %} {% endblock panel %}