Block 5 — MAN-Abrechnung F06 (E6): Ein explizites Legacy-Vertragsende (grandfathered_until) deckelt jetzt Auswahl und Sperr-Nachprüfung: Beginnt die nächste Periode nach dem Vertragsende, gibt es keine Rechnung mehr. Neuer täglicher Befehl billing:expire-grandfathering (04:15, vor dem Rechnungslauf) setzt abgelaufene Posten auf gekündigt und schickt je Kunde eine Mail (GrandfatheringExpired). Auf der Dev-Kopie trägt kein Posten ein Vertragsende; der Fehler war latent. F16: Nicht abrechenbare Vereinbarungen bekommen einen Sperrvermerk (billing_hold_reason/-at), sichtbar auf der Admin-Zahlungsseite, und stehen im Rechnungslauf hinten an. Der Lauf arbeitet den fälligen Bestand in Seiten ab; --limit deckelt die erstellten Rechnungen (500), nicht die betrachteten Fälle. Vorher blockierten 50 unvollständige Fälle die 51. dauerhaft, und der Durchsatz war auf 50 am Tag begrenzt. Block 6 — Entwicklung F24: Lint-Workflow prüft mit pint --test statt zu formatieren; keine Schreibrechte mehr. F27: composer run dev startet beide Vite-Server (npm run dev:all). F28: docs/README.md als historischer Stand markiert, toter Link auf das Hub-Flux-Archiv korrigiert, aktueller Einstieg dev/README.md. F26: Login-Modal übernimmt den Fokus, hält Tab im Dialog und gibt den Fokus beim Schließen an den Auslöser zurück – ohne neue Abhängigkeit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
92 lines
3.2 KiB
PHP
92 lines
3.2 KiB
PHP
<?php
|
||
|
||
namespace App\Console\Commands;
|
||
|
||
use App\Services\Billing\ManualInvoiceService;
|
||
use Illuminate\Console\Command;
|
||
|
||
/**
|
||
* Fälligkeitsprüfung des manuellen Rechnungskreises (MAN-).
|
||
*
|
||
* Läuft täglich über den Scheduler und stellt für aktive Legacy-
|
||
* Zahlungsvereinbarungen (ohne Stripe-Subscription) die fälligen
|
||
* Rechnungen aus — wie im Legacy-System. Nicht abrechenbare
|
||
* Vereinbarungen (fehlende Rechnungsadresse o. ä.) werden geloggt, mit
|
||
* Sperrvermerk versehen und beim nächsten Lauf erneut geprüft.
|
||
*
|
||
* Der Lauf arbeitet den gesamten fälligen Bestand in Seiten ab (Audit
|
||
* F16): Vorher nahm er die 50 ältesten Fälligkeiten; standen dort 50
|
||
* nicht abrechenbare Fälle, kam die 51. nie dran, und ohne Blocker war
|
||
* der Durchsatz auf 50 Rechnungen am Tag begrenzt. `--limit` deckelt jetzt
|
||
* die Zahl der erstellten Rechnungen, nicht der betrachteten Fälle.
|
||
*/
|
||
class GenerateManualInvoices extends Command
|
||
{
|
||
private const PAGE_SIZE = 100;
|
||
|
||
protected $signature = 'billing:generate-manual-invoices
|
||
{--dry-run : Nur anzeigen, was fällig ist}
|
||
{--limit=500 : Maximale Anzahl erstellter Rechnungen pro Lauf}';
|
||
|
||
protected $description = 'Stellt fällige Rechnungen des manuellen Legacy-Rechnungskreises (MAN-) aus';
|
||
|
||
public function handle(ManualInvoiceService $service): int
|
||
{
|
||
$dryRun = (bool) $this->option('dry-run');
|
||
$limit = max(1, (int) $this->option('limit'));
|
||
|
||
$created = 0;
|
||
$skipped = 0;
|
||
$seen = [];
|
||
|
||
while ($created < $limit) {
|
||
$due = $service->duePaymentOptions(limit: self::PAGE_SIZE, excludeIds: $seen);
|
||
|
||
if ($due->isEmpty()) {
|
||
break;
|
||
}
|
||
|
||
foreach ($due as $option) {
|
||
$seen[] = $option->id;
|
||
|
||
if ($dryRun) {
|
||
$this->line(sprintf(
|
||
'[dry-run] Fällig: Vereinbarung #%d (User #%s, Periode bis %s)%s',
|
||
$option->id,
|
||
$option->user_id,
|
||
$option->current_period_end->toDateString(),
|
||
$option->billing_hold_reason ? ' – gesperrt: '.$option->billing_hold_reason : '',
|
||
));
|
||
|
||
continue;
|
||
}
|
||
|
||
if ($created >= $limit) {
|
||
break;
|
||
}
|
||
|
||
$invoice = $service->invoiceFor($option);
|
||
|
||
if ($invoice) {
|
||
$created++;
|
||
$this->line(sprintf('Rechnung %s für Vereinbarung #%d erstellt.', $invoice->number, $option->id));
|
||
} else {
|
||
$skipped++;
|
||
$this->warn(sprintf('Vereinbarung #%d übersprungen: %s.', $option->id, $option->refresh()->billing_hold_reason ?? 'nicht abrechenbar'));
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($seen === []) {
|
||
$this->info('Keine fälligen Zahlungsvereinbarungen gefunden.');
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
if (! $dryRun) {
|
||
$this->info(sprintf('%d Rechnung(en) erstellt, %d übersprungen.', $created, $skipped));
|
||
}
|
||
|
||
return self::SUCCESS;
|
||
}
|
||
}
|