Implements BILLING.md. The architecture was sound -- snapshot-on-Due, dated prices, asymmetric dry-run commands are all kept -- so this fixes the three hardcoded assumptions rather than rewriting. The real defect: grace ran from period_END, so an annual club used the whole unpaid year plus 45 days (~410 days) before anything switched it off. Grace now runs from the period START, and every clock is per-plan. - Tier -> Plan (+ TierPrice -> PlanPrice, and every FK). Migration 0004 is hand-written: run non-interactively, makemigrations emits DeleteModel+CreateModel and drops every price, subscription and due. Its two RemoveConstraints must come first, or SQLite's table-rebuild tries to render a constraint over a just-renamed column. Verified by round-tripping real rows through it. - Plan gains duration_months / renewal_lead_days / grace_days / is_trial, with CheckConstraints and a matching clean() so the form reports an impossible plan instead of 500ing on IntegrityError. - Existing dues keep their stored grace_until. Re-deriving it would put the date in the past for every open annual period and archive the entire paying customer base on the next --commit run. - Trials take their length from the trial plan's own duration_months; start_trial() loses its trial_months argument. - New BillingNotice service drives a club-facing warning: every level on the dashboard, and on every management page once urgent. - send_billing_reminders emails club admins, once per escalation level so a daily cron is not a daily email. SMTP settings are env-driven and provider-agnostic; the backend defaults to console. - Paying does not auto-restore an archived club -- the control panel surfaces a Reactivate prompt instead, since a club can also be archived by hand.
15 lines
1.5 KiB
Plaintext
15 lines
1.5 KiB
Plaintext
{% load i18n %}{% blocktrans with club=club.name %}Hello,
|
|
|
|
This is a reminder about the RosterChief platform fees for {{ club }}.{% endblocktrans %}
|
|
|
|
{% blocktrans with amount=notice.amount_outstanding %}Outstanding: EUR {{ amount }}{% endblocktrans %}
|
|
{% blocktrans with start=due.period_start|date:"j M Y" end=due.period_end|date:"j M Y" %}Period: {{ start }} to {{ end }}{% endblocktrans %}
|
|
{% if due.invoice %}{% blocktrans with number=due.invoice.number %}Invoice: {{ number }}{% endblocktrans %}
|
|
{% endif %}
|
|
{% if notice.will_archive %}{% if notice.days_until_archive < 0 %}{% trans "This club is now past its payment deadline and is due to be archived. While archived, nobody can sign in and the club's site stops resolving. No data is deleted, and access is restored as soon as payment is received." %}{% else %}{% blocktrans count days=notice.days_until_archive %}If we do not receive payment within {{ days }} day, this club will be archived. While archived, nobody can sign in and the club's site stops resolving. No data is deleted.{% plural %}If we do not receive payment within {{ days }} days, this club will be archived. While archived, nobody can sign in and the club's site stops resolving. No data is deleted.{% endblocktrans %}{% endif %}{% else %}{% trans "Please settle this to keep your account in good standing." %}{% endif %}
|
|
|
|
{% blocktrans with contact=billing_contact %}If you have already paid, or you think this is a mistake, reply to {{ contact }} and we will sort it out.{% endblocktrans %}
|
|
|
|
{% trans "Thank you," %}
|
|
{% trans "RosterChief" %}
|