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>
Phase 7 (Spur D, E2 revidiert): KI-Vorprüfung VOR der Zahlung im Modal —
rot lehnt ohne Zahlung ab, gelb/grün führt zum Stripe-Checkout
(SinglePurchaseType::Change, 10 € netto), nach Zahlungseingang übernimmt
der Webhook die geprüfte Fassung sofort (applyPaid: Snapshot, Sperre,
Verlauf). Nur einmal je PM; Obergrenze legal.change_max_distance_percent
(50 %) verhindert die verdeckte Selbst-Depublizierung.
Phase 5 (Spur B): strukturiertes Schwärzungsformular mit Pflicht-Vorschau
im verifizierten Web-Bereich UND als Modal im Kunden-Panel; Admin-Editor
wird aus payload.redaction vorbefüllt. Umsetzungsmail LegalRequestExecuted
an Antragsteller, Bestätigungsmail PressReleaseChangeApplied (Spur A/D,
neuer abbestellbarer Typ press_release_corrected).
Außerdem:
- Re-Sync-Sperre + Sitemap-Invalidierung fest in depublish() verankert
(Lücke im bezahlten Webhook-Pfad geschlossen)
- Öffentliche „Rechtliches"-Seite als zweistufige Triage: primär der
Portal-Anmeldelink, Formular nur Fallback mit Identifizierungs-Hinweis;
Store auf report/dsgvo/personal_rights gehärtet
- Panel-Button „Ändern/Melden" öffnet Anliegen-Modal (4 Spuren) statt
des Web-Formulars
- Pressekontakt-Fallback für Legacy-Bestände (30k PMs ohne Verknüpfung):
Detailseite + pressContactEmail() nutzen PM-Kontakt → Firmen-Kontakt →
Firmendaten
- Korrektur-Gate entity-fest (Editor-Normalisierung zählt nicht als
Änderung), toEditorHtml() in Admin-Edit + Korrektur-Panel,
Wochenlimit-Hinweis in Text und Bremsen-Meldung
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Default 3500 Cent in config/billing.php (per Env ueberschreibbar,
0 deaktiviert) — Checkout und /melden-Preisanzeige (Doppelausweis
35,00 netto / 41,65 brutto) sind damit ohne weiteren Env-Handgriff
aktiv. O6 aus der Entscheidungsliste geschlossen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kunden koennen eigene veroeffentlichte PMs kostenpflichtig aus der
oeffentlichen Ansicht nehmen: Panel auf der PM-Detailseite (inkl.
T1neu-Unternehmer-Checkbox und Doppelausweis netto/brutto), Stripe-
Ad-hoc-Checkout wie Credit-Topups, Erfuellung im Webhook — die PM
wechselt auf archived + depublished_at, die oeffentliche URL zeigt
dauerhaft einen Tombstone (410 Gone, ohne Inhalte, mit ehrlichem
Drittarchive-Hinweis). Doppelbuchungs-Bremse pro PM; Status-Log mit
Quelle "depublication". Gate: ohne BILLING_DEPUBLICATION_PRICE_CENTS
bleibt der Weg deaktiviert und /melden zeigt die neutrale
Preisformulierung [EXTERN: Preis festlegen, O6].
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Default von billing.enforce_booking auf true gedreht: ein in Produktion
vergessener Env-Key haette das Gate sonst still geoeffnet (jeder haette
ohne Zahlung veroeffentlichen koennen). Tests ergaenzt: Grandfathered
gueltig/abgelaufen/NULL (= abgelaufen, Entscheidung 19.06.), Active
ausserhalb der Periode, Service-Level-Submit mit Legacy-Buchung und
BookingRequiredException ohne Buchung. Scope-Logik selbst war korrekt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- VatResolver::normalizeCountryCode()/isEuCountry() als Single Source of Truth;
resolve(), VatIdValidationService und StripeCheckoutService nutzen jetzt die
zentrale EU-Pruefung statt eigener config-Lookups.
- User::findByEmail() buendelt die LOWER(email)-Lookups (ContactAccessService,
SocialAuthService). Bleibt bewusst case-insensitiv, da E-Mails nicht
normalisiert gespeichert werden; der indexfreundliche Direktvergleich
braucht erst eine Schreib-Normalisierung (offen, dokumentiert).
- config('billing.default_currency') ersetzt 8 verstreute 'EUR'/'eur'-Literale
(Stripe-Calls via strtolower). Vereinheitlicht nebenbei die zuvor uneinheitlich
gespeicherte SinglePurchase.currency.
- Neuer CustomerAccountProvisioner::provisionVerifiedCustomer() ist die
gemeinsame Provisioning-Methode (verifiziert + aktiv + customer-Rolle) fuer
ContactAccessService und SocialAuthService.
- Tests: UserFindByEmailTest, erweiterte VatResolverTest.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Einwand/Entscheidung 12.06.2026: Legacy fakturierte brutto (Steuer
inkludiert, z. B. 199 Euro; steuerbefreite Kunden mit Netto-Ausweis
167,23). Alle neuen Preise sind netto; die Steuer wird zur
Rechnungsstellung sauber validiert und ausgewiesen.
- VatResolver + VatTreatment: DE grundsaetzlich immer mit Steuer, EU nur
mit (formal plausibler) USt-ID befreit (Reverse Charge inkl.
Pflichthinweis), Drittlaender grundsaetzlich befreit;
EU-Laenderliste + vat_rate in config/billing.php
- Schema: billing_addresses.vat_id + invoice_billing_addresses.vat_id
(Snapshot pro Rechnung), invoices.tax_note; Profil-Formular schreibt
die vorhandene USt-ID jetzt auch an die Rechnungsadresse
- ManualInvoiceService: rechnet auf Netto-Vertragsbasis
(legacy_conditions.net_cents bzw. Netto-Katalogpreis) und bestimmt
Steuer/is_netto/tax_note pro Rechnung ueber den VatResolver
- legacy:grandfather-subscriptions: leitet net_cents aus der letzten
Legacy-Rechnung ab (brutto / 1,19 bzw. is_netto-Betrag direkt);
fuer DE-Bestandskunden bleibt der Bruttobetrag unveraendert
(199 brutto -> 167,23 netto + 31,77 USt = 199,00)
- Doku: Decision-Update 2.1 (Netto-Klarstellung), Phase-9-Plan,
Checkliste, 05-DATABASE-MERGE 5.6; offen: VIES-Validierung der USt-ID
Tests: VatResolverTest (Datasets fuer alle Faelle), Reverse-Charge/
EU-/Drittland-Rechnungen, Netto-Ableitung; Suite 490 passed, 4 skipped.
Pint clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tarif-Datenmodell (Decision-Update):
- plans: Starter/Business/Pro/Agency mit Monats-/Jahrespreis (Jahres =
10 x Monat), PM-Kontingent, Tageslimit, Stripe-IDs; idempotenter Seeder
- single_purchases: Einzel-PM, Extra-PM, Boost, PDF-Nachweis mit
Status-Lifecycle und Stripe-Checkout-Referenzen
- laravel/cashier ^16.5 installiert (freigegeben); User ist Billable,
Cashier-Migrationen published + ausgefuehrt; lokale invoices()-Relation
ueberschreibt bewusst die Cashier-Methode
Hybride Rechnungskreise (Entscheidung 12.06.2026):
- invoice_number_sequences + InvoiceNumberGenerator: atomare fortlaufende
Nummern pro Kreis (STR- fuer den neuen Stripe-Shop, MAN- fuer den
manuellen Legacy-Kreis); Alt-Archiv legacy_invoices bleibt unveraendert
- ManualInvoiceService + billing:generate-manual-invoices (Scheduler
taeglich 04:30): prueft aktive/grandfathered user_payment_options ohne
Stripe-Subscription auf erreichtes Periodenende, friert die
Rechnungsadresse als Snapshot ein, stellt die MAN-Rechnung aus
(Zahlungsziel billing.manual_due_days) und schaltet die Periode weiter;
Konditions-Overrides via legacy_conditions, sonst Netto-Preis +
billing.vat_rate; nicht abrechenbare Faelle werden geloggt und
beim naechsten Lauf erneut geprueft
Submit-Gate:
- User::hasActiveBooking() prueft jetzt echt (hinter
billing.enforce_booking): Cashier-Abo, bezahlter Einzel-/Extra-PM-Kauf
oder laufende Legacy-Vereinbarung (MAN-Kreis)
Suite: 468 passed, 4 skipped (17 neue Billing-Tests). Pint clean.
Offen fuer 9E: Stripe-Checkout/Webhooks, STR-Spiegelung, Slot-Logik auf
Plan-Kontingent, Migration der aktiven Legacy-Zahlungen in
user_payment_options.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9A — Gelb geht direkt live (Entscheidung 12.06.2026):
- routeByClassification(): Gelb durchlaeuft denselben Auto-Publish-Pfad
wie Gruen (autoPublishApproved); nur Rot wird abgelehnt
- Scheduler publiziert faellige gelbe + gruene PMs; unklassifizierte
bleiben als Fallback in der manuellen Queue
9B — Slot-Verbrauch bei Veroeffentlichung (Decision-Update 3.2):
- Increment aus submitForReview() entfernt; publish() und
changeStatusFromAdmin() zaehlen idempotent beim ersten
published-Uebergang (Pruefung ueber Status-Logs); Rot kostet nichts
- Submit-Guard: Einreichen erfordert freien Slot
(QuotaExceededException, API 422)
9C — Submit-Gate vorbereitet (Decision-Update 5.1):
- User::hasActiveBooking()-Stub hinter config/billing.php
(enforce_booking, Default aus); Tarif-Modul ersetzt nur den Rumpf
- Einreichungs-Modal zeigt ohne Buchung einen Buchungs-Hinweis;
Server-Guard (BookingRequiredException), API antwortet 402
- Fix: Customer-Create legte PMs bei "Zur Pruefung senden" direkt mit
Status review an (vorbei an Blacklist/Quota/KI/Status-Log) — laeuft
jetzt immer ueber submitForReview()
Suite: 451 passed, 4 skipped (9 neue Tests). Pint clean.
Plan: docs/PHASE-9-FLOW-UND-TARIFE-PLAN.md (Block 2 nach Review-Stopp).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>