# 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