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
|
|
@ -16,8 +16,11 @@ new #[Layout('components.layouts.app'), Title('Rechnungen')] class extends Compo
|
|||
|
||||
public function with(): array
|
||||
{
|
||||
// Nie bezahlte Alt-Rechnungen (hold) sind erledigt und erscheinen
|
||||
// dem Kunden nicht mehr als „offen".
|
||||
$baseQuery = LegacyInvoice::query()
|
||||
->where('user_id', auth()->id());
|
||||
->where('user_id', auth()->id())
|
||||
->withoutHold();
|
||||
|
||||
// Neue Rechnungen (STR = Stripe-Spiegelung, MAN = Bestandskreis).
|
||||
$currentQuery = Invoice::query()->where('user_id', auth()->id());
|
||||
|
|
@ -41,7 +44,7 @@ new #[Layout('components.layouts.app'), Title('Rechnungen')] class extends Compo
|
|||
'paid_count' => (clone $baseQuery)->whereNotNull('paid_at')->count()
|
||||
+ (clone $currentQuery)->where('status', InvoiceStatus::Paid->value)->count(),
|
||||
'downloadable_count' => (clone $baseQuery)->count()
|
||||
+ (clone $currentQuery)->whereNotNull('stripe_invoice_id')->count(),
|
||||
+ (clone $currentQuery)->count(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
@ -150,8 +153,13 @@ new #[Layout('components.layouts.app'), Title('Rechnungen')] class extends Compo
|
|||
<flux:table.cell>
|
||||
@if ($invoice->status === \App\Enums\InvoiceStatus::Paid)
|
||||
<span class="badge ok dot">{{ $invoice->status->label() }}</span>
|
||||
@else
|
||||
@elseif ($invoice->status === \App\Enums\InvoiceStatus::Open)
|
||||
<span class="badge warn dot">{{ $invoice->status->label() }}</span>
|
||||
@if ($invoice->due_date)
|
||||
<div class="text-[11px] text-[color:var(--color-ink-3)] mt-0.5">{{ __('zahlbar bis :date', ['date' => $invoice->due_date->format('d.m.Y')]) }}</div>
|
||||
@endif
|
||||
@else
|
||||
<span class="badge">{{ $invoice->status->label() }}</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
|
|
@ -171,7 +179,15 @@ new #[Layout('components.layouts.app'), Title('Rechnungen')] class extends Compo
|
|||
{{ __('Bei Stripe öffnen') }}
|
||||
</flux:button>
|
||||
@else
|
||||
<span class="text-[12px] text-[color:var(--color-ink-3)]">–</span>
|
||||
<flux:button
|
||||
size="sm"
|
||||
variant="filled"
|
||||
icon="document-text"
|
||||
:href="route('me.invoices.manual-pdf', $invoice)"
|
||||
target="_blank"
|
||||
>
|
||||
{{ __('PDF öffnen') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
|
|
@ -225,9 +241,13 @@ new #[Layout('components.layouts.app'), Title('Rechnungen')] class extends Compo
|
|||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if ($invoice->paid_at)
|
||||
<span class="badge ok dot">{{ $invoice->status ?? __('Bezahlt') }}</span>
|
||||
<span class="badge ok dot">{{ __('Bezahlt') }}</span>
|
||||
@elseif ($invoice->status === \App\Models\LegacyInvoice::STATUS_SUPERSEDED)
|
||||
<span class="badge hub">{{ __('Ersetzt durch :number', ['number' => $invoice->raw_snapshot['superseded_by_invoice_number'] ?? '–']) }}</span>
|
||||
@elseif ($invoice->status === \App\Models\LegacyInvoice::STATUS_UNCOLLECTIBLE)
|
||||
<span class="badge">{{ __('Erledigt') }}</span>
|
||||
@else
|
||||
<span class="badge warn dot">{{ $invoice->status ?? __('Offen') }}</span>
|
||||
<span class="badge warn dot">{{ __('Offen') }}</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue