billing: MAN-Rechnungen fuer Ueberweisungskunden bearbeiten, hold-Altlast bereinigen
Bestandskunden aus dem Altsystem zahlen per Ueberweisung; nach dem 04:30-Lauf fehlte bisher der gesamte Bearbeitungsweg (kein PDF, keine Mail, kein Zahlungseingang, kein Bestandsschutz-Ende). Plan und Befund: dev/lagacy-rechnungen/entwicklungsplan-2026-09-16.md. Bereinigung (legacy:settle-unpaid, wiederholbar): - Legacy-Status hold = nie bezahlt, ersatzlos erledigt. 9 daraus abgeleitete Bestandsschutz-Buendel werden gekuendigt, MAN-00001/00002 storniert. Archivzeilen bleiben erhalten, sind aber in Admin-Archiv und Kundenkonto standardmaessig ausgeblendet. - pending/reminder (recurring) = offene Vorgaenge: Periode auf den Leistungsbeginn der unbezahlten Rechnung zurueckgedreht und als MAN-Rechnung neu ausgestellt (Archiv-Status superseded, replaces_legacy_invoice_number). - pending/reminder (single) = abgeschrieben (uncollectible). - legacy:grandfather-subscriptions wertet den Zahlungsstatus jetzt selbst aus. Workflow in /admin/payments (ManualInvoiceDunningService, je Aktion mit Eintrag im Aktivitaets-Verlauf): - Rechnung senden: Mail mit PDF-Anhang, Bankverbindung aus billing.issuer, Hinweis auf Bestandsschutz-Ende; Zahlungsziel zaehlt ab Versand. - Erinnerung: gleiche Mail im Mahn-Ton, Nachfrist bei verstrichener Frist. - Bezahlt: Zahlungseingang laut Kontoauszug verbuchen. - Bestandsschutz beenden: Rechnung storniert (nie geloescht), Buendel gekuendigt, weitere offene MAN-Rechnungen des Kunden storniert, optionale Kundenmail. - Panel "Blockierte Vereinbarungen": faellige Posten ohne Rechnung (meist fehlende Rechnungsadresse) mit "Jetzt abrechnen" oder "Entfernen". Schema: invoices bekommt user_payment_option_id, sent_at, reminded_at, reminder_count, voided_at, void_reason, replaces_legacy_invoice_number (inkl. Backfill der bestehenden MAN-Rechnungen). PDF-Primitiven des Archiv-Renderers in den Trait DrawsPdf ausgelagert; MAN-PDF on-demand ueber admin.invoices.pdf / me.invoices.manual-pdf (InvoicePolicy). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ada41cb067
commit
36691a5b13
33 changed files with 3606 additions and 195 deletions
|
|
@ -99,7 +99,51 @@ Täglicher Scheduler-Lauf (04:30): `billing:generate-manual-invoices`
|
|||
|
||||
Nicht abrechenbare Fälle (fehlende Rechnungsadresse, kein Intervall) werden
|
||||
geloggt, **die Periode bleibt stehen** — der nächste Lauf versucht es erneut.
|
||||
Optionen: `--dry-run`, `--limit=50`.
|
||||
Solche Vereinbarungen erscheinen in `/admin/payments` im Panel **„Blockierte
|
||||
Vereinbarungen"** (Grund + Link ins User-Profil, „Jetzt abrechnen" nach dem
|
||||
Nachtragen der Adresse). Optionen: `--dry-run`, `--limit=50`.
|
||||
|
||||
Jede MAN-Rechnung trägt `user_payment_option_id` (Link zur Vereinbarung) und
|
||||
ggf. `replaces_legacy_invoice_number`, wenn sie eine offene Alt-Rechnung aus
|
||||
dem Archiv ersetzt (siehe 3a).
|
||||
|
||||
### 3a. Bearbeitung nach der Ausstellung (Überweisungskunden, 16.09.2026)
|
||||
|
||||
MAN-Kunden zahlen per Überweisung. Der Weg nach dem 04:30-Lauf ist ein
|
||||
Knopfdruck-Workflow in `/admin/payments` → „Rechnungsausgang", Filter
|
||||
„Offene MAN-Rechnungen" (Default). Logik: `ManualInvoiceDunningService`,
|
||||
jede Aktion mit Eintrag im Aktivitäts-Verlauf (`ActivityType::ManualInvoice`).
|
||||
|
||||
| Aktion | Wirkung |
|
||||
|---|---|
|
||||
| **Rechnung senden** | Mail `ManualInvoiceIssued` mit PDF-Anhang (`ManualInvoicePdfRenderer`), Bankverbindung aus `billing.issuer`, Hinweis auf Bestandsschutz-Ende. Setzt `sent_at` und **`due_date = heute + manual_due_days`** — die Frist zählt ab Versand. Verweigert bei Adresse auf der Mail-Sperrliste. |
|
||||
| **Erinnerung** | Gleiche Mail im Mahn-Ton (`isReminder`), `reminded_at`/`reminder_count`. Verstrichene Frist → Nachfrist `billing.reminder_due_days` (7) ab heute, sonst bleibt `due_date`. |
|
||||
| **Bezahlt** | `status = paid`, `paid_at` = Datum laut Kontoauszug. |
|
||||
| **Bestandsschutz beenden** | Rechnung `void` (`voided_at`, `void_reason`), weitere offene MAN-Rechnungen des Kunden ebenfalls `void`, `LegacySubscriptionService::cancelBundleFor()`; optional Kundenmail `GrandfatheringTerminated`. Rechnungen werden nie gelöscht (Nummernfolge, GoBD). |
|
||||
| **Entfernen** (Panel „Blockierte Vereinbarungen") | Nicht abrechenbare Vereinbarung ohne Rechnung beenden (`abandonAgreement`): Bündel gekündigt, offene MAN-Rechnungen `void`, offene Alt-Rechnung im Archiv `uncollectible`. Keine Kundenmail. |
|
||||
|
||||
PDF-Routen: `admin.invoices.pdf` (Admin) und `me.invoices.manual-pdf`
|
||||
(Kunde, `InvoicePolicy::downloadPdf`); STR-Rechnungen liefern 404 — dort
|
||||
bleibt Stripe der Beleg.
|
||||
|
||||
### 3b. Altlast aus unbezahlten Legacy-Rechnungen (`legacy:settle-unpaid`)
|
||||
|
||||
Legacy-Status `hold` = nie bezahlt, ersatzlos erledigt (Einordnung
|
||||
16.09.2026). `pending`/`reminder` = offene Vorgänge. Der Befehl
|
||||
`legacy:settle-unpaid` (`--dry-run`, `--no-invoice`) räumt auf:
|
||||
|
||||
- Bestandsschutz-Posten mit `hold`-Quellrechnung → Bündel gekündigt, offene
|
||||
MAN-Rechnungen storniert. Die Archivzeilen bleiben erhalten und werden im
|
||||
Admin-Archiv sowie im Kundenkonto standardmäßig ausgeblendet
|
||||
(`LegacyInvoice::scopeWithoutHold`).
|
||||
- `pending`/`reminder` (recurring) → Periode auf den Leistungsbeginn der
|
||||
offenen Rechnung zurückgedreht (`legacy_conditions.open_legacy_invoice`),
|
||||
sofort MAN-Rechnung ausgestellt; die Alt-Rechnung erhält Status
|
||||
`superseded` (+ `raw_snapshot.superseded_by_invoice_number`).
|
||||
- `pending`/`reminder` (single) → `uncollectible`.
|
||||
|
||||
`legacy:grandfather-subscriptions` wertet den Zahlungsstatus seit 16.09.2026
|
||||
selbst aus (hold → übersprungen, offen → Periode auf Leistungsbeginn).
|
||||
|
||||
**Befüllung**: `legacy:grandfather-subscriptions` (Migrations-Runbook, nach
|
||||
`legacy:archive-invoices`) leitet die aktiven jährlichen Vereinbarungen aus
|
||||
|
|
@ -140,6 +184,7 @@ Rechnungsadresse bestimmt:
|
|||
| Befehl | Zweck | Scheduler |
|
||||
|---|---|---|
|
||||
| `billing:generate-manual-invoices` | MAN-Fälligkeitslauf (Abschnitt 3) | täglich 04:30 |
|
||||
| `legacy:settle-unpaid` | Altlast unbezahlter Legacy-Rechnungen auflösen (Abschnitt 3b) | manuell, wiederholbar |
|
||||
| `billing:sync-stripe-plans` | Tarife + Einzel-PM als Netto-Produkte/Preise nach Stripe synchronisieren (idempotent; `--dry-run`) | manuell |
|
||||
| — Admin-UI: `/admin/payments/plans` | Tarif-Pflege (Preise, Kontingent, Tageslimit, aktiv/inaktiv) mit Sofort-Sync nach Stripe (`StripePlanSyncService`): Preisänderung legt ein neues Price-Objekt an und deaktiviert das alte; Bestandsabos behalten ihren Preis | — |
|
||||
| `legacy:grandfather-subscriptions` | Aktive Legacy-Abos aus dem Archiv migrieren | manuell (Migrations-Runbook) |
|
||||
|
|
@ -155,7 +200,9 @@ Rechnungsadresse bestimmt:
|
|||
|---|---|---|---|
|
||||
| `enforce_booking` | `BILLING_ENFORCE_BOOKING` | `false` | Submit-Gate scharf schalten (Launch-Schalter) |
|
||||
| `invoice_number_padding` | — | `5` | Stellen der laufenden Nummer |
|
||||
| `manual_due_days` | `BILLING_MANUAL_DUE_DAYS` | `14` | Zahlungsziel MAN-Rechnungen |
|
||||
| `manual_due_days` | `BILLING_MANUAL_DUE_DAYS` | `14` | Zahlungsziel MAN-Rechnungen (ab Versand) |
|
||||
| `reminder_due_days` | `BILLING_REMINDER_DUE_DAYS` | `7` | Nachfrist einer Zahlungserinnerung |
|
||||
| `issuer.*` | `BILLING_ISSUER_*` | adametz.media | Aussteller, Bankverbindung, Steuernummer auf MAN-Rechnungen und in der Zahlungsmail |
|
||||
| `vat_rate` | `BILLING_VAT_RATE` | `0.19` | USt-Satz für steuerpflichtige Fälle |
|
||||
| `eu_country_codes` | — | EU-27 ohne DE | Basis der Drittland-/EU-Unterscheidung |
|
||||
|
||||
|
|
@ -205,8 +252,9 @@ CLI ausgegebene `whsec_…` temporär als `STRIPE_WEBHOOK_SECRET` in die `.env`.
|
|||
`billing:sync-stripe-plans` erneut ausführen (Live-Produkt-IDs).
|
||||
3. **VIES-Validierung** der USt-ID (aktuell Formatprüfung; Stripe prüft
|
||||
die im Checkout erfasste USt-ID asynchron selbst).
|
||||
4. **PDF-Erzeugung** für MAN-/STR-Rechnungen (Layout inkl. `tax_note`);
|
||||
Archiv-PDFs existieren bereits on-demand.
|
||||
4. **PDF-Erzeugung**: MAN-Rechnungen erledigt (16.09.2026,
|
||||
`ManualInvoicePdfRenderer`, inkl. `tax_note`); STR-Belege kommen weiter
|
||||
von Stripe.
|
||||
5. **`ExpireGrandfatheredSubscriptions`**: Benachrichtigung zur Umstellung
|
||||
auf neue Tarife am `grandfathered_until` (D-13-Rest); erst dann wird
|
||||
das unbegrenzte Bestandskontingent ggf. migriert.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue