Warenwirtschaft: Logistik-Mail regeneriert auch fehlende Rechnungs-PDF
Nachtrag zum Lieferschein-Fix (7d77314): Fehlte die Rechnungs-PDF auf der
Platte (Altdaten), brach MailLogistic::build() still ab — die Mail ging ohne
Anhänge bzw. gar nicht raus, obwohl das UI Erfolg meldete. Jetzt wird die
Rechnung wie der Lieferschein mit bestehender Rechnungsnummer regeneriert.
Hinweise-Doku und Plan-Protokoll fortgeschrieben.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
6051aa6ea1
commit
a1f94ea427
4 changed files with 33 additions and 2 deletions
|
|
@ -52,6 +52,10 @@ class MailLogistic extends Mailable
|
||||||
|
|
||||||
$filename = Invoice::getFilename($this->shopping_order);
|
$filename = Invoice::getFilename($this->shopping_order);
|
||||||
$path = Invoice::getDownloadPath($this->shopping_order);
|
$path = Invoice::getDownloadPath($this->shopping_order);
|
||||||
|
if (! Storage::disk('public')->exists($path)) {
|
||||||
|
// Datei fehlt auf der Platte (Altdaten) — mit bestehender Nummer neu erzeugen
|
||||||
|
(new InvoiceRepository($this->shopping_order))->update([]);
|
||||||
|
}
|
||||||
if (! Storage::disk('public')->exists($path)) {
|
if (! Storage::disk('public')->exists($path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -269,7 +269,7 @@ Der Warenkorb kommt also **per Knopfdruck** (Fulfillment-Übergabe), nicht autom
|
||||||
| 16.07.2026 | **AP-33a (Bestell-Abwicklung: Nav „Bestellungen" + Fulfillment-Board)** | Neuer Sidenav-Punkt **„Bestellungen"** (copyreader) mit Unterpunkten **Abwicklung** (neu) und **API-Bestellungen** (umgezogen). Board `admin/inventory/orders` (`OrderFulfillmentController`): 4 Filter-Kacheln (heute eingegangen / zu konfektionieren / versandfertig / heute versendet), je Bestellung Packliste aufklappbar (Sets mit Komponenten), Gewicht, Zahlstatus-Pill, Quelle-Badge (API/Shop/Promo), Suche. **Versandfertig-Regel** (Kundenentscheid): `ShoppingOrder::isReadyToShip()` — Rechnung (fnc) sofort, Vorkasse (vor) erst bei `paid`, API (`extern`) immer, `non`/`failed` nie. **Dokumente:** Erstellen (Rechnung + Lieferschein, ohne Mailversand) über bestehendes `InvoiceRepository::create()`; Downloads (einzeln/kombiniert via `PDFMerger`) über eigene copyreader-Routen, da `storage_file` admin ≥ 7 verlangt; **API-Bestellungen bekommen keine Rechnung** — externer Link (`wp_invoice_path`). **Statuswechsel:** Logik aus `SalesController::store()` in neuen **`OrderStatusService::setShipped()`** extrahiert (Transaktion, Lock, Margin-/Promotion-Folgen, AP-13-Abzug) — Verkäufe-Modal und Board nutzen dieselbe Implementierung; Board nur vorwärts (1/2), nur aus Status 0/1. **Sperr-Flag** `shopping_orders.shipped_status_locked` (Migration `2026_07_16_130000`, Kundenentscheid Punkt 3): vom Board gesetzte Status werden vom Auto-Wechsel der Detailseiten (offen → in Bearbeitung, 3 Stellen) nicht mehr überschrieben. Commit `a367d85`. | Neuer `OrderFulfillmentTest` (10 grün: Render/Packliste, Rechte, Versandfertig-Regel, Statuswechsel + Lock + Bestandsabzug, nur-vorwärts, Alt-Status unangetastet, Detailseiten-Sperre, Dokumente ohne Mail, API ohne Rechnung). Volle Suite: **268 grün, 987 Assertions** |
|
| 16.07.2026 | **AP-33a (Bestell-Abwicklung: Nav „Bestellungen" + Fulfillment-Board)** | Neuer Sidenav-Punkt **„Bestellungen"** (copyreader) mit Unterpunkten **Abwicklung** (neu) und **API-Bestellungen** (umgezogen). Board `admin/inventory/orders` (`OrderFulfillmentController`): 4 Filter-Kacheln (heute eingegangen / zu konfektionieren / versandfertig / heute versendet), je Bestellung Packliste aufklappbar (Sets mit Komponenten), Gewicht, Zahlstatus-Pill, Quelle-Badge (API/Shop/Promo), Suche. **Versandfertig-Regel** (Kundenentscheid): `ShoppingOrder::isReadyToShip()` — Rechnung (fnc) sofort, Vorkasse (vor) erst bei `paid`, API (`extern`) immer, `non`/`failed` nie. **Dokumente:** Erstellen (Rechnung + Lieferschein, ohne Mailversand) über bestehendes `InvoiceRepository::create()`; Downloads (einzeln/kombiniert via `PDFMerger`) über eigene copyreader-Routen, da `storage_file` admin ≥ 7 verlangt; **API-Bestellungen bekommen keine Rechnung** — externer Link (`wp_invoice_path`). **Statuswechsel:** Logik aus `SalesController::store()` in neuen **`OrderStatusService::setShipped()`** extrahiert (Transaktion, Lock, Margin-/Promotion-Folgen, AP-13-Abzug) — Verkäufe-Modal und Board nutzen dieselbe Implementierung; Board nur vorwärts (1/2), nur aus Status 0/1. **Sperr-Flag** `shopping_orders.shipped_status_locked` (Migration `2026_07_16_130000`, Kundenentscheid Punkt 3): vom Board gesetzte Status werden vom Auto-Wechsel der Detailseiten (offen → in Bearbeitung, 3 Stellen) nicht mehr überschrieben. Commit `a367d85`. | Neuer `OrderFulfillmentTest` (10 grün: Render/Packliste, Rechte, Versandfertig-Regel, Statuswechsel + Lock + Bestandsabzug, nur-vorwärts, Alt-Status unangetastet, Detailseiten-Sperre, Dokumente ohne Mail, API ohne Rechnung). Volle Suite: **268 grün, 987 Assertions** |
|
||||||
| 16.07.2026 | **AP-13-Nachtrag: Stichtags-Schutz für Alt-Bestellungen** | Beim Blick auf Bestellung 1687 (versendet 12/2025) entdeckt: erneutes Speichern des Versandstatus einer **vor AP-13 versendeten** Bestellung hätte rückwirkend vom heutigen Bestand abgezogen. `syncSaleStock()` überspringt jetzt Bestellungen mit `shipped_at` vor dem Stichtag `ProductStockService::SALE_STOCK_SINCE` (16.07.2026). Kundenentscheid dokumentiert: Self-Shipping ohne Abzug bestätigt. | `SaleStockDeductionTest` +1 (11 grün). Suite: **258 grün, 944 Assertions** |
|
| 16.07.2026 | **AP-13-Nachtrag: Stichtags-Schutz für Alt-Bestellungen** | Beim Blick auf Bestellung 1687 (versendet 12/2025) entdeckt: erneutes Speichern des Versandstatus einer **vor AP-13 versendeten** Bestellung hätte rückwirkend vom heutigen Bestand abgezogen. `syncSaleStock()` überspringt jetzt Bestellungen mit `shipped_at` vor dem Stichtag `ProductStockService::SALE_STOCK_SINCE` (16.07.2026). Kundenentscheid dokumentiert: Self-Shipping ohne Abzug bestätigt. | `SaleStockDeductionTest` +1 (11 grün). Suite: **258 grün, 944 Assertions** |
|
||||||
| 16.07.2026 | **AP-13 (API-Bestellungen — Protokoll + SKU-Fehlermarkierung, Kundenanforderung 16.07.)** | Neue Tabelle **`api_order_logs`** (Migration `2026_07_16_090000`) + Model `ApiOrderLog`; `ShoppingUserController` protokolliert **jede** `wp/order`-Übertragung — erfolgreiche inkl. Zuordnungsergebnis je Position (SKU, Produkt, Menge, Preis, API-Meldungen), abgelehnte mit Grund (Kunde unbekannt, Bestellung existiert bereits, Speicherfehler). Positionen ohne Produktzuordnung ⇒ `has_errors` + Zusammenfassung („x von y Positionen ohne Produktzuordnung"). **Neuer Menüpunkt „API-Bestellungen"** (copyreader, `admin/inventory/api-orders`): Liste (Limit 500/`?all=1`, Filter „nur Fehler"), rote Markierung, **Modal „Ansehen"** mit Positionstabelle (Meldungen deutsch übersetzt, Roh-Payload bei abgelehnten). Dazu SKU-Konsistenz (Plan §4 „mitzulösen"): Warnkarte **doppelte WP-Nummern** aktiver Produkte, aufklappbare Liste **aktive Produkte ohne WP-Nummer**. Commit `71d5591`. | Neuer `ApiOrderLogTest` (8 grün, Ende-zu-Ende über `wp/order` mit `Passport::actingAs`). Suite: **257 grün, 940 Assertions** |
|
| 16.07.2026 | **AP-13 (API-Bestellungen — Protokoll + SKU-Fehlermarkierung, Kundenanforderung 16.07.)** | Neue Tabelle **`api_order_logs`** (Migration `2026_07_16_090000`) + Model `ApiOrderLog`; `ShoppingUserController` protokolliert **jede** `wp/order`-Übertragung — erfolgreiche inkl. Zuordnungsergebnis je Position (SKU, Produkt, Menge, Preis, API-Meldungen), abgelehnte mit Grund (Kunde unbekannt, Bestellung existiert bereits, Speicherfehler). Positionen ohne Produktzuordnung ⇒ `has_errors` + Zusammenfassung („x von y Positionen ohne Produktzuordnung"). **Neuer Menüpunkt „API-Bestellungen"** (copyreader, `admin/inventory/api-orders`): Liste (Limit 500/`?all=1`, Filter „nur Fehler"), rote Markierung, **Modal „Ansehen"** mit Positionstabelle (Meldungen deutsch übersetzt, Roh-Payload bei abgelehnten). Dazu SKU-Konsistenz (Plan §4 „mitzulösen"): Warnkarte **doppelte WP-Nummern** aktiver Produkte, aufklappbare Liste **aktive Produkte ohne WP-Nummer**. Commit `71d5591`. | Neuer `ApiOrderLogTest` (8 grün, Ende-zu-Ende über `wp/order` mit `Passport::actingAs`). Suite: **257 grün, 940 Assertions** |
|
||||||
| 16.07.2026 | **Bugfix Logistik-Mail (Kundenmeldung)** | Fehlversand an die Rechnungsadresse: die Versand-Mail (`MailLogistic`) hängte den **Lieferschein nur bei White-Label-Bestellungen** an. Jetzt wird er **immer** mitgesendet (unabhängig von Logo/White-Label); fehlt die PDF-Datei auf der Platte, wird sie mit bestehender Rechnungsnummer regeneriert. Dazu **tote Duplikat-Klasse `app/Mail/MailLogitic.php` entfernt** (Tippfehler-Dateiname, definierte `MailLogistic` in einer Altversion ohne Lieferschein-Anhang — Kollisionsrisiko im Classmap-Autoload). Commit `7d77314`. | Neuer `LogisticMailTest` (3 grün: Anhang ohne White-Label, Regeneration fehlender Datei, Versand-Button). Volle Suite: **275 grün, 1012 Assertions** |
|
| 16.07.2026 | **Bugfix Logistik-Mail (Kundenmeldung)** | Fehlversand an die Rechnungsadresse: die Versand-Mail (`MailLogistic`) hängte den **Lieferschein nur bei White-Label-Bestellungen** an. Jetzt wird er **immer** mitgesendet (unabhängig von Logo/White-Label); fehlt die PDF-Datei auf der Platte, wird sie mit bestehender Rechnungsnummer regeneriert. Dazu **tote Duplikat-Klasse `app/Mail/MailLogitic.php` entfernt** (Tippfehler-Dateiname, definierte `MailLogistic` in einer Altversion ohne Lieferschein-Anhang — Kollisionsrisiko im Classmap-Autoload). Commit `7d77314`. **Nachtrag:** gleiche Regeneration auch für die **Rechnungs**-PDF — vorher brach `build()` bei fehlender Rechnungsdatei still ab (Mail ohne Anhänge trotz Erfolgsmeldung im UI). | Neuer `LogisticMailTest` (4 grün: Anhang ohne White-Label, Regeneration Lieferschein + Rechnung, Versand-Button). Volle Suite: **276 grün, 1016 Assertions** |
|
||||||
| _offen_ | AP-32 | Versand im System: DHL-Etiketten per API — **neu aufgenommen + Ansatz dokumentiert (§4, 15.07.2026)**; Klärungspunkte (DHL-Konto, Versandprodukte, Druck-Setup) für nächste Anforderungsrunde notiert | – |
|
| _offen_ | AP-32 | Versand im System: DHL-Etiketten per API — **neu aufgenommen + Ansatz dokumentiert (§4, 15.07.2026)**; Klärungspunkte (DHL-Konto, Versandprodukte, Druck-Setup) für nächste Anforderungsrunde notiert | – |
|
||||||
| _offen_ | AP-14/15/16/17 | Audit-Trail, Blockrechte, 2FA, WaWi-Einstellungen | – |
|
| _offen_ | AP-14/15/16/17 | Audit-Trail, Blockrechte, 2FA, WaWi-Einstellungen | – |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,9 @@
|
||||||
Rechnung **und** Lieferschein an — unabhängig davon, ob die Bestellung
|
Rechnung **und** Lieferschein an — unabhängig davon, ob die Bestellung
|
||||||
White-Label-/Logo-Produkte enthält. Vorher fehlte der Lieferschein bei
|
White-Label-/Logo-Produkte enthält. Vorher fehlte der Lieferschein bei
|
||||||
Bestellungen ohne eigenes Etikett, was zu Fehlversänden an die
|
Bestellungen ohne eigenes Etikett, was zu Fehlversänden an die
|
||||||
Rechnungsadresse führen konnte.
|
Rechnungsadresse führen konnte. Fehlt eine der beiden PDF-Dateien auf dem
|
||||||
|
Server (Altdaten), wird sie beim Mailversand automatisch mit der
|
||||||
|
bestehenden Rechnungsnummer neu erzeugt.
|
||||||
- **Status-Vorrang der Warenwirtschaft (Kundenentscheid 16.07.2026 — wichtig):**
|
- **Status-Vorrang der Warenwirtschaft (Kundenentscheid 16.07.2026 — wichtig):**
|
||||||
Das Öffnen einer Bestell-Detailseite setzt eine offene Bestellung bislang
|
Das Öffnen einer Bestell-Detailseite setzt eine offene Bestellung bislang
|
||||||
automatisch auf „in Bearbeitung". **Sobald der Status einmal über die
|
automatisch auf „in Bearbeitung". **Sobald der Status einmal über die
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,31 @@ test('fehlende lieferschein-datei wird fuer die mail regeneriert', function () {
|
||||||
expect($names->first(fn ($n) => str_starts_with((string) $n, 'Lieferschein-')))->not->toBeNull();
|
expect($names->first(fn ($n) => str_starts_with((string) $n, 'Lieferschein-')))->not->toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('fehlende rechnungs-datei wird fuer die mail regeneriert', function () {
|
||||||
|
Storage::fake('public');
|
||||||
|
$order = lmMakeOrder();
|
||||||
|
lmCreateDocuments($order);
|
||||||
|
$order->refresh();
|
||||||
|
|
||||||
|
$invoicePath = Invoice::getDownloadPath($order);
|
||||||
|
Storage::disk('public')->delete($invoicePath);
|
||||||
|
expect(Storage::disk('public')->exists($invoicePath))->toBeFalse();
|
||||||
|
|
||||||
|
$cwd = getcwd();
|
||||||
|
chdir(public_path());
|
||||||
|
try {
|
||||||
|
$mailable = new MailLogistic($order);
|
||||||
|
$mailable->build();
|
||||||
|
} finally {
|
||||||
|
chdir($cwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(Storage::disk('public')->exists($invoicePath))->toBeTrue();
|
||||||
|
$names = collect($mailable->attachments)->pluck('options.as');
|
||||||
|
expect($names)->toHaveCount(2)
|
||||||
|
->and($names->first(fn ($n) => str_starts_with((string) $n, 'Rechnung-')))->not->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
test('versand-button verschickt die logistik-mail', function () {
|
test('versand-button verschickt die logistik-mail', function () {
|
||||||
Mail::fake();
|
Mail::fake();
|
||||||
Storage::fake('public');
|
Storage::fake('public');
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue