mivita/app/Console/Commands/RegenerateCancellationInvoicePdf.php
Kevin 1e7ae92d0c Stornorechnung: Fix überdeckte Artikelzeile + Regenerate-Command
Der ACHTUNG-Hinweiskasten hat wegen fixer 15mm-Höhe des Titel-Blocks
den Tabellenkopf und die erste Artikelzeile überdeckt (z.B. Artikel
5010400 auf den Stornos zu Bestellung 56965/56966). Titel-Block wächst
jetzt mit dem Inhalt.

Neuer Command sales:regenerate-cancellation-pdf erzeugt die PDFs
bestehender Stornorechnungen neu (gleiche Nummer/Pfade, keine
Punktebuchung, keine Mails) – per Rechnungs-ID oder mit --order per
Bestell-ID.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 14:19:28 +00:00

117 lines
3.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Console\Commands;
use App\Models\ShoppingOrder;
use App\Models\UserInvoice;
use App\Repositories\InvoiceRepository;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
class RegenerateCancellationInvoicePdf extends Command
{
/**
* Erzeugt die PDF-Dateien bestehender Stornorechnungen neu (z. B. nach Template-Fixes).
* Nummer, Datum und Dateipfade der Stornorechnung bleiben unverändert – es wird keine
* neue Rechnungsnummer vergeben und es werden keine Punkte gebucht.
*/
protected $signature = 'sales:regenerate-cancellation-pdf
{invoices* : IDs der Stornorechnungen (user_invoices.id)}
{--order : Die übergebenen IDs sind ShoppingOrder-IDs statt Rechnungs-IDs}
{--force : Ohne Rückfrage neu erzeugen}';
protected $description = 'Storno-PDFs (Rechnung + Lieferschein) bestehender Stornorechnungen neu erzeugen';
public function handle(): int
{
$invoices = $this->resolveInvoices();
if ($invoices === null) {
return self::FAILURE;
}
$this->table(
['Storno-Rechnung', 'Storno-Nr.', 'Bestellung', 'Datum', 'Datei'],
$invoices->map(fn (UserInvoice $invoice) => [
$invoice->id,
$invoice->full_number,
$invoice->shopping_order_id,
$invoice->date,
$invoice->dir.$invoice->filename,
])
);
if (! $this->option('force') && ! $this->confirm("Wirklich {$invoices->count()} Storno-PDF(s) neu erzeugen? Die bestehenden Dateien werden überschrieben.")) {
return self::SUCCESS;
}
$errors = 0;
foreach ($invoices as $invoice) {
try {
(new InvoiceRepository($invoice->shopping_order))->regenerateCancellationPDF($invoice);
$this->info("Stornorechnung #{$invoice->id} ({$invoice->full_number}) neu erzeugt: ".Storage::disk('public')->path($invoice->dir.$invoice->filename));
} catch (\Throwable $e) {
$errors++;
$this->error("Stornorechnung #{$invoice->id}: {$e->getMessage()}");
}
}
return $errors > 0 ? self::FAILURE : self::SUCCESS;
}
/**
* Löst die übergebenen IDs zu gültigen Stornorechnungen auf.
*
* @return Collection<int, UserInvoice>|null null bei Validierungsfehlern
*/
private function resolveInvoices(): ?Collection
{
$invoices = collect();
foreach ($this->argument('invoices') as $id) {
if ($this->option('order')) {
$order = ShoppingOrder::find((int) $id);
if (! $order) {
$this->error("ShoppingOrder {$id} nicht gefunden.");
return null;
}
$invoice = $order->user_cancellation_invoice;
if (! $invoice) {
$this->error("Bestellung {$id} hat keine Stornorechnung.");
return null;
}
} else {
$invoice = UserInvoice::find((int) $id);
if (! $invoice) {
$this->error("Stornorechnung {$id} nicht gefunden.");
return null;
}
}
if (! $invoice->cancellation || $invoice->cancellation_id) {
$this->error("Rechnung #{$invoice->id} ({$invoice->full_number}) ist keine Stornorechnung.");
return null;
}
if (! $invoice->shopping_order) {
$this->error("Stornorechnung #{$invoice->id} hat keine zugehörige Bestellung.");
return null;
}
$invoices->push($invoice);
}
return $invoices;
}
}