# Packstation Return-Label Restriction **Datum:** 23.01.2026 **Status:** ✅ Implementiert ## Problem **User-Feedback:** "Wenn ich ein Label habe aus einer Packstation, kann ich kein Return Label erzeugen." **Fehler:** ``` Fehler beim Erstellen des Retourenlabels: DHL API error (400): 0 of 1 shipment successfully printed. ``` ## Root Cause ### DHL Packstation Limitierung **Packstationen** sind **Einwegadressen**: - ✅ Sendungen **AN** eine Packstation: Erlaubt - ❌ Sendungen **VON** einer Packstation: **Nicht erlaubt** ### Return-Label Logik Bei einem Return-Label werden Sender/Empfänger vertauscht: ``` Original-Label: Sender: Shop (Musterstraße 1, Berlin) Empfänger: Kunde (Packstation 145, Hamburg) ✅ Return-Label (vertauscht): Sender: Kunde (Packstation 145, Hamburg) ❌ Empfänger: Shop (Musterstraße 1, Berlin) ``` **Problem:** Packstation kann nicht als Sender verwendet werden! ## DHL API Verhalten ### Request Payload (Fallback via ShippingService) ```json { "profile": "STANDARD_GRUPPENPROFIL", "shipments": [ { "product": "V01PAK", "shipper": { "name1": "Max Mustermann", "addressStreet": "Packstation", "addressHouse": "145", "postalCode": "20095", "city": "Hamburg", "country": "DEU", "postNumber": "1234567890" // ❌ Packstation als Sender! }, "consignee": { "name1": "Mein Shop", "addressStreet": "Musterstraße", "addressHouse": "1", "postalCode": "10115", "city": "Berlin", "country": "DEU" } } ] } ``` ### DHL API Response ```json { "Status": { "statusCode": 400, "statusText": "Bad Request" }, "labelData": "", "shipmentNumber": "", "errors": [ { "message": "0 of 1 shipment successfully printed." } ] } ``` ## Lösung ### 1. Backend-Validation (Controller) **File:** `app/Http/Controllers/DhlShipmentController.php` ```php public function createReturnLabel(Request $request, DhlShipment $shipment): JsonResponse { // ... existing checks ... // Check if recipient is a Packstation (cannot be used as return sender) $recipient = is_string($shipment->recipient) ? json_decode($shipment->recipient, true) : $shipment->recipient; $hasPostNumber = !empty($recipient['postnumber'] ?? $recipient['postNumber'] ?? ''); if ($hasPostNumber) { return response()->json([ 'success' => false, 'message' => 'Retourenlabels können nicht für Packstation-Sendungen erstellt werden. ' . 'DHL erlaubt keine Sendungen VON einer Packstation. ' . 'Der Kunde muss die Retoure über eine normale Adresse versenden.', ], 422); } // ... continue with return label creation ... } ``` **Prüflogik:** - ✅ Prüft ob `postnumber` oder `postNumber` im Empfänger-JSON existiert - ✅ Gibt HTTP 422 (Unprocessable Entity) zurück - ✅ Klare, verständliche Fehlermeldung ### 2. UI - DataTable (Cockpit) **File:** `app/Http/Controllers/DhlShipmentController.php` ```php ->addColumn('actions', function ($shipment) { // ... // Return label button (only for non-Packstation outbound shipments) if ($shipment->type == 'outbound' && ! $shipment->returns()->count()) { $recipient = is_string($shipment->recipient) ? json_decode($shipment->recipient, true) : $shipment->recipient; $hasPostNumber = !empty($recipient['postnumber'] ?? $recipient['postNumber'] ?? ''); if (!$hasPostNumber) { // Regular address: Show active button $buttons .= ''; } else { // Packstation: Show disabled button with explanation $buttons .= ''; } } // ... }) ``` **UI-Verhalten:** - ✅ **Packstation:** Disabled Button mit Tooltip - ✅ **Normale Adresse:** Aktiver Button - ✅ Icon wird grau dargestellt bei Packstation ### 3. UI - Order Detail View **File:** `resources/views/admin/sales/_detail_dhl_shipments.blade.php` ```blade @if($shipment->type === 'outbound' && !$shipment->returns->count()) @php $recipient = is_string($shipment->recipient) ? json_decode($shipment->recipient, true) : $shipment->recipient; $hasPostNumber = !empty($recipient['postnumber'] ?? $recipient['postNumber'] ?? ''); @endphp @if(!$hasPostNumber) @else @endif @endif ``` ### 4. UI - Shipment Detail View **File:** `resources/views/admin/dhl/show.blade.php` ```blade @if($shipment->type == 'outbound' && !$shipment->returns->count()) @php $recipient = is_string($shipment->recipient) ? json_decode($shipment->recipient, true) : $shipment->recipient; $hasPostNumber = !empty($recipient['postnumber'] ?? $recipient['postNumber'] ?? ''); @endphp @if(!$hasPostNumber) @else Retourenlabels können nicht für Packstation-Sendungen erstellt werden, da DHL keine Sendungen VON einer Packstation erlaubt. @endif @endif ``` **Besonderheit Detail-View:** - ✅ Zusätzlicher Info-Text unter dem Button - ✅ Icon-Indikator für besseres Verständnis ## Packstation-Erkennung ### Datenstruktur **DhlShipment Model:** ```php $shipment->recipient = '{ "firstname": "Max", "lastname": "Mustermann", "street": "Packstation 145", "postalcode": "20095", "city": "Hamburg", "country": "DE", "postnumber": "1234567890" // ← Identifikator! }' ``` ### Detection Logic ```php $recipient = is_string($shipment->recipient) ? json_decode($shipment->recipient, true) : $shipment->recipient; // Check both possible keys (case-insensitive) $hasPostNumber = !empty( $recipient['postnumber'] ?? $recipient['postNumber'] ?? '' ); if ($hasPostNumber) { // This is a Packstation delivery } ``` **Keys geprüft:** - `postnumber` (lowercase) - `postNumber` (camelCase) ## Testing ### Test 1: Packstation-Sendung - API Call blockiert ```bash # Via Tinker $shipment = DhlShipment::find(24); // Packstation-Sendung $controller = new App\Http\Controllers\DhlShipmentController(); $response = $controller->createReturnLabel(new Illuminate\Http\Request(), $shipment); # Expected Response: { "success": false, "message": "Retourenlabels können nicht für Packstation-Sendungen erstellt werden. DHL erlaubt keine Sendungen VON einer Packstation. Der Kunde muss die Retoure über eine normale Adresse versenden." } # HTTP Status: 422 ``` ### Test 2: Packstation-Sendung - Button disabled **Schritte:** 1. Cockpit öffnen 2. Packstation-Sendung suchen (ID 24) 3. ✅ Return-Button ist grau/disabled 4. ✅ Tooltip zeigt "Retourenlabels nicht möglich für Packstation-Sendungen" 5. ✅ Icon ist grau (`text-muted`) ### Test 3: Normale Sendung - Return funktioniert **Schritte:** 1. Normale Outbound-Sendung auswählen (kein postNumber) 2. ✅ Return-Button ist aktiv/blau 3. Button klicken 4. ✅ Return-Label wird erfolgreich erstellt ### Test 4: Detail-View mit Info-Text **Schritte:** 1. Packstation-Sendung Detail-View öffnen (ID 24) 2. ✅ "Packstation-Sendung" Button ist disabled 3. ✅ Info-Text sichtbar unter dem Button 4. ✅ Erklärt warum Return nicht möglich ist ## Database Check ```sql -- Find all Packstation shipments SELECT id, type, dhl_shipment_no, JSON_EXTRACT(recipient, '$.postnumber') as postnumber, JSON_EXTRACT(recipient, '$.street') as street FROM dhl_package_shipments WHERE type = 'outbound' AND ( JSON_EXTRACT(recipient, '$.postnumber') IS NOT NULL OR JSON_EXTRACT(recipient, '$.postNumber') IS NOT NULL ); ``` **Example Results:** ``` id | type | dhl_shipment_no | postnumber | street ----|----------|---------------------------|--------------|------------------ 24 | outbound | 0034043333301020021029524 | 1234567890 | "Packstation 145" ``` ## User Experience ### Vorher (Fehler) ``` 1. User klickt "Retourenlabel erstellen" 2. API Call wird gesendet 3. ❌ DHL API error (400): 0 of 1 shipment successfully printed 4. ❌ Kryptische Fehlermeldung 5. ❌ User weiß nicht warum es fehlschlägt ``` ### Nachher (Proaktive Verhinderung) ``` 1. User öffnet Packstation-Sendung 2. ✅ Button ist disabled mit Tooltip 3. ✅ Info-Text erklärt warum nicht möglich 4. ✅ Kein fehlgeschlagener API Call 5. ✅ Klarheit für den User ``` ## Alternative Lösungen (nicht implementiert) ### Option 1: Shop-Adresse als Return-Empfänger **Idee:** Bei Packstation-Sendungen automatisch Shop-Adresse als Rücksendeadresse verwenden. **Problem:** - Kunde muss Paket zur Post/DHL-Filiale bringen - Nicht transparent für Kunden - Komplexere Logik **Entscheidung:** ❌ Nicht umgesetzt ### Option 2: QR-Code Return ohne Label **Idee:** DHL QR-Code Return für Packstation-Kunden. **Problem:** - Erfordert separates DHL API Produkt - Nicht in allen Accounts verfügbar - Separate Implementierung nötig **Entscheidung:** ❌ Nicht umgesetzt (könnte zukünftig ergänzt werden) ## Geänderte Dateien 1. ✅ `app/Http/Controllers/DhlShipmentController.php` - Zeile 427-437: Backend-Validation für Packstation-Check - Zeile 224-236: DataTable Button-Logik mit Packstation-Check 2. ✅ `resources/views/admin/sales/_detail_dhl_shipments.blade.php` - Zeile 108-127: Blade-Logic für Return-Button (disabled bei Packstation) 3. ✅ `resources/views/admin/dhl/show.blade.php` - Zeile 161-182: Detail-View mit disabled Button + Info-Text ## Status ✅ **Backend-Validation:** Implementiert ✅ **UI-Anpassungen (Cockpit):** Implementiert ✅ **UI-Anpassungen (Order Detail):** Implementiert ✅ **UI-Anpassungen (Shipment Detail):** Implementiert ✅ **Dokumentation:** Erstellt ## Nächste Schritte 1. [ ] Testen: Packstation-Sendung im Cockpit prüfen 2. [ ] Testen: Return-Button sollte disabled sein 3. [ ] Testen: Normale Sendung kann weiterhin Return-Label erstellen 4. [ ] Optional: Kunde über alternative Return-Optionen informieren (Email) ## Lessons Learned 1. **Proaktive UI-Validierung:** Buttons disablen ist besser als Fehler zeigen 2. **Klare Fehlermeldungen:** User sollten verstehen WARUM etwas nicht geht 3. **DHL Limitierungen:** Packstationen sind Einweg-Adressen (nur AN, nicht VON) 4. **Multiple Keys prüfen:** `postnumber` vs `postNumber` - beide abfangen