88 KiB
Entwicklungskonzept DHL Modul
Stand: 27.05.2026
Ziel
Diese Datei ist die aktuelle Arbeitsgrundlage fuer die Weiterentwicklung des DHL Moduls. Die bisherigen Markdown-Dateien in diesem Ordner dokumentieren abgeschlossene oder ueberholte Zwischenstaende, insbesondere den frueheren SDK-Ansatz, Paketinstallation, SSL/cURL-Fixes und einzelne abgeschlossene Entwicklungsschritte.
Aktuelle Anforderungen kommen aus docs/dhl/Anpassung DHL Modul.md:
- Internationaler Versand ausserhalb Deutschlands, insbesondere Oesterreich und Spanien.
- Freies Feld fuer Sendungsreferenz oder interne Hinweise wie "Nachlieferung".
- Adressvalidierung vor Labelerstellung, damit fehlerhafte Labels und kostenpflichtige Stornos vermieden werden.
- Storno im DHL Cockpit pruefen und stabilisieren.
- Gewicht von Kompensationsprodukten in das DHL-Paketgewicht einrechnen.
- Tracking-Mails auf Rhythmus, Ausloeser und Mehrfachversand pruefen.
- Tracking-Codes in Admin, User-Portal und User-N-Portal sichtbar machen.
- Tracking-Mail-Versand nur bei Statusaenderung.
- DHL-Umstellung von
V62WPWarenpost aufV62KPDHL Kleinpaket bis spaetestens 31.05.2026.
Aktueller technischer Stand
Das produktive Modul basiert auf dem Paket packages/acme-laravel-dhl und verwendet die DHL REST API ueber Acme\Dhl\Support\DhlClient. Die zentrale Tabelle ist dhl_package_shipments.
Wichtige produktive Einstiegspunkte:
app/Http/Controllers/DhlShipmentController.phpapp/Services/DhlShipmentService.phpapp/Services/DhlModalService.phpapp/Services/DhlDataHelper.phpapp/Services/DhlTrackingService.phppackages/acme-laravel-dhl/src/Services/ShippingService.phppackages/acme-laravel-dhl/src/Services/ReturnsService.phppackages/acme-laravel-dhl/src/Models/DhlShipment.phpconfig/dhl.php
Historische Dokumente erwaehnen teilweise alte Strukturen wie App\Models\DhlShipment, dhl_shipments oder einen konsolidierten DhlApiService. Diese Angaben sind nicht mehr massgeblich, ausser sie werden explizit in aktuellem Code noch referenziert. Aktuell gibt es genau dort noch Altlasten, die bereinigt werden muessen.
Offensichtliche Befunde
1. Produktkuerzel V62WP ist veraltet
V62WP ist weiterhin in Konfiguration, Admin-Settings, Validierung, Produktauswahl und Sprachdateien vorhanden. DHL verlangt die Umstellung auf V62KP.
Betroffene Stellen:
config/dhl.phpapp/Http/Controllers/SettingController.phpapp/Services/DhlModalService.phppackages/acme-laravel-dhl/src/Services/ShippingService.phpresources/lang/*/dhl.php
Risiko: Kleinpaket/Warenpost-Labels koennen nach der DHL-Frist abgelehnt werden.
2. Async Tracking verwendet veraltetes Model
app/Jobs/TrackShipmentJob.php importiert App\Models\DhlShipment, dieses Model existiert im aktuellen System nicht mehr. Die produktive DHL-Integration verwendet Acme\Dhl\Models\DhlShipment.
Risiko: Asynchrones Tracking bricht zur Laufzeit, sobald Queue-Tracking genutzt wird.
3. Statuswerte fuer Storno sind inkonsistent
Im Paket wird bei erfolgreichem Storno canceled gesetzt. Andere Stellen, Uebersetzungen und historische Dokumente verwenden cancelled.
Risiko: Filter, Badges, Terminal-Status, Uebersetzungen und Storno-Buttons verhalten sich uneinheitlich.
4. Storno ist technisch vorhanden, aber nicht robust genug
Storno laeuft ueber DELETE /parcel/de/shipping/v2/orders/{shipmentNumber}. Der Code prueft canCancel(), speichert aber Fehler nur begrenzt fachlich verwertbar. Produktspezifische Einschraenkungen wie Warenpost/Kleinpaket sind nicht sauber modelliert.
Risiko: Anwender sehen generische Fehler und koennen nicht erkennen, ob ein Storno produktbedingt, statusbedingt, API-bedingt oder wegen Sandbox/Production-Mismatch scheitert.
5. Internationaler Versand ist nur teilweise vorbereitet
V53PAK ist als internationales Produkt vorhanden, und einige Laender werden in 3-stellige ISO-Codes konvertiert. Dennoch gibt es keinen zentralen Produktentscheid je Zielland, keine harte Validierung nicht unterstuetzter Laender und einen gefaehrlichen Fallback auf DEU.
Risiko: Sendungen nach Oesterreich, Spanien oder weitere Laender koennen falsche Produktcodes, falsche Abrechnungsnummern oder falsche Laendercodes erhalten.
6. Adressvalidierung ist nur formal
Aktuell prueft das Modul Pflichtfelder, Hausnummern und einfache PLZ-Regeln. Eine echte Pruefung, ob Strasse, PLZ und Ort postalisch existieren, findet nicht statt.
Empfohlene Loesung: DHL/Post & DHL DATAFACTORY AUTOCOMPLETE 2.0 fuer DE/AT/CH pruefen und integrieren. Alternativen fuer breiteren Laenderumfang: Loqate, Google Address Validation oder HERE. Wichtig ist eine harte Sperre bei nicht versandfaehigen Adressen vor Labelerstellung.
7. Referenzfeld ist API-seitig vorhanden, aber nicht im Cockpit nutzbar
ShippingService kann reference nach refNo mappen. DhlDataHelper setzt aktuell automatisch Order-{id}.
Risiko: Admins koennen Hinweise wie "Nachlieferung" nicht strukturiert am Label/API-Auftrag hinterlegen.
8. Gewicht von Kompensationsprodukten fehlt
Kompensationsprodukte werden im Warenkorb mit Gewicht 0 abgelegt, damit die Kompensationslogik nicht beeinflusst wird. Das DHL-Gewicht kommt aus ShoppingOrder->weight und enthaelt dieses Produktgewicht dadurch nicht.
Risiko: DHL-Label wird mit zu geringem Paketgewicht erstellt.
9. Tracking-Mail-Logik ist grundsaetzlich brauchbar, muss aber abgesichert werden
Der Scheduler ruft stuendlich dhl:update-tracking --days=30 --send-emails auf. Statusabhaengige Intervalle verhindern zu viele API-Calls. Automatische Mails werden nur gesendet, wenn eine Sendung neu auf in_transit wechselt und noch keine Mail markiert wurde.
Risiko: Statusspruenge direkt auf out_for_delivery oder besondere DHL-Statuscodes koennen ohne Mail bleiben. Mehrere Sendungen einer Bestellung werden teils zusammengefasst, aber die fachliche Regel muss final bestaetigt werden.
Entwicklungskonzept
Phase 1: Pflichtkorrekturen vor DHL-Frist
V62WPvollstaendig aufV62KPmigrieren.- Neue Konfigurationskeys fuer
DHL_ACCOUNT_NUMBER_V62KP, Admin-Settingdhl_account_v62kp, Dimensionen und Uebersetzungen einfuehren. ShippingServiceValidierung umV62KPerweitern undV62WPentfernen oder nur noch als Legacy-Mapping fuer Altdaten anzeigen.- Bestehende Sendungen mit
V62WPhistorisch lesbar lassen, aber neue Labelerstellung blockieren. - Tests fuer Produktcode-Auswahl, Validierung und Payload-Erstellung schreiben.
Phase 2: Stabilisierung von Tracking und Storno
TrackShipmentJobauf aktuelles Model und aktuellenDhlTrackingServiceumstellen.- Statuswerte vereinheitlichen. Empfehlung: intern
canceledverwenden, Uebersetzung auf Deutsch "Storniert". TERMINAL_STATUSES, Badges, Filter und Sprachdateien entsprechend angleichen.- Storno-Fehler strukturiert speichern: HTTP-Status, DHL-Fehlercode, DHL-Detailtext, Zeitpunkt.
- Admin-Feedback verbessern: nicht stornierbar wegen Status, Produkt, API-Antwort oder nicht auffindbarer DHL-Sendung.
- Tests fuer erfolgreiche Stornierung, bereits stornierte Sendung, nicht stornierbare Sendung und API-Fehler.
Phase 3: Internationalisierung Versand
- Zentralen Service fuer Produkt-/Billing-Entscheidung einfuehren, z. B.
DhlProductResolver. - Zielland, Produktcode, Abrechnungsnummer und erlaubte Services dort validieren.
- Regeln initial:
DE:V01PAKoderV62KPAT,ESund weitere aktivierte Laender:V53PAK
- Fallback auf
DEUentfernen. Unbekannte Laender muessen mit klarer Fehlermeldung abbrechen. - Cockpit-Formular: Produkt anhand Zielland vorschlagen, aber Admin-Korrektur erlauben.
Phase 4: Adressvalidierung vor Labelerstellung
- Neuen serverseitigen Validierungsendpunkt fuer DHL-Adressen schaffen.
- Basisvalidierung behalten: Pflichtfelder, Land, PLZ-Format, Hausnummer, Packstation/Postnummer.
- DHL DATAFACTORY AUTOCOMPLETE 2.0 fuer DE/AT/CH evaluieren.
- Falls DHL fuer alle benoetigten Laender nicht ausreicht, externen Provider evaluieren.
- UI-Status einfuehren:
- gueltig: Labelerstellung erlaubt
- Warnung: Admin kann bewusst fortfahren
- Fehler: Labelerstellung gesperrt
- Validierungsergebnis optional am Shipment/Order protokollieren.
Phase 5: Referenzfeld und Admin-UX
- Neues Formularfeld
referenceodershipment_referenceim DHL Cockpit. - Wert an
DhlDataHelper::prepareOrderData()uebergeben. ShippingServicenutzt vorhandenes Mapping nachrefNo.- Referenz im Shipment speichern, damit spaeter nachvollziehbar ist, warum eine Sendung erstellt wurde.
- Laengenlimit der DHL API beachten, aktuell maximal 35 Zeichen.
Phase 6: DHL-Gewicht korrekt berechnen
- Separaten Gewichtsdienst fuer DHL einfuehren, z. B.
DhlShipmentWeightCalculator. - Basis:
ShoppingOrder->weight. - Fuer
shopping_order_itemsmitcomp > 0das Produktgewicht ausProduct->weightnachladen und addieren. - Nur DHL-Gewicht anpassen, nicht die bestehende Warenkorb-/Versandkostenlogik.
- Rundung und DHL-Grenzwerte je Produkt testen.
Phase 7: Tracking-Codes und Mails fachlich finalisieren
- Bestehende Admin-Anzeige pruefen und bei Bedarf vereinheitlichen.
- Tracking-Code-Anzeige in User-Portal und User-N-Portal ergaenzen.
- Mail-Regel final definieren:
- automatisch nur einmal pro Sendung
- nur bei relevanter Statusaenderung
- mehrere Sendungen einer Bestellung sinnvoll zusammenfassen
- Statusspruenge wie
createddirekt nachout_for_deliveryabdecken. - Command
dhl:update-trackingTests fuer Mailausloeser und Nicht-Ausloeser ergaenzen.
Phase 8: DHL-seitige Adressvalidierung ueber mustEncode / printOnlyIfCodable
Status: umgesetzt fuer deutsche Empfaengeradressen.
Ziel:
- Die bestehende formale Vorabpruefung bleibt erhalten.
- Bei der finalen DHL-Labelerstellung soll DHL selbst pruefen, ob die Adresse leitcodierbar bzw. versandfaehig ist.
- Dafuer soll der DHL-Query-Parameter
mustEncode=truegenutzt werden. printOnlyIfCodableist laut DHL-Spezifikation der Legacy-Name dieser Funktion.- Wenn DHL die Adresse ablehnt, wird kein Etikett erstellt.
- Die DHL-Fehlermeldung wird in das bestehende Modal zurueckgespiegelt, damit der Admin die Lieferadresse direkt korrigieren kann.
- Laut DHL-Beschreibung ist diese Pruefung nur fuer deutsche Empfaengeradressen relevant.
Geplanter Ablauf:
- Admin oeffnet das DHL-Erstellungsmodal.
- Die bestehende Vorabpruefung prueft weiterhin:
- Pflichtfelder
- PLZ-Format
- Produkt-/Zielland-Kombination
- Packstation-/Paketbox-Regeln
- einfache Plausibilitaet
- Beim Klick auf
Sendung jetzt erstellenwird die Sendung an DHL uebergeben. - Der DHL-Request enthaelt
mustEncode=true. - DHL erstellt das Label nur, wenn die Adresse codeable/leitcodierbar ist.
- Wenn DHL die Adresse ablehnt:
- es wird kein Shipment als erfolgreich erstellt markiert
- die DHL-Fehlermeldung wird normalisiert
- das Modal bleibt geoeffnet
- die Fehlermeldung erscheint unten in der Vorabpruefung
- der Admin kann die Adresse korrigieren und erneut pruefen/erstellen
Geplante technische Integrationspunkte:
packages/acme-laravel-dhl/src/Services/ShippingService.php- Create-Shipment-Request um den Query-Parameter
mustEncode=trueerweitern. - Legacy-Begriff
printOnlyIfCodablenur als Dokumentations-/Kompatibilitaetshinweis fuehren, nicht als neuen internen Optionsnamen verwenden. - DHL-Fehlerantworten fuer nicht codeable Adressen strukturiert auswerten.
- Create-Shipment-Request um den Query-Parameter
app/Services/DhlDataHelper.php- Option aus Konfiguration/Settings fuer die Request-Erstellung vorbereiten.
app/Services/DhlShipmentService.php- DHL-Adressfehler als fachliche Validierungsfehler behandeln, nicht als generischen Systemfehler.
app/Http/Controllers/DhlShipmentController.php- Fehlerantwort im JSON so zurueckgeben, dass das Modal sie anzeigen kann.
resources/views/admin/dhl/modal_create_shipment.blade.php- DHL-Fehler bei Labelerstellung in der bestehenden Vorabpruefungsbox anzeigen.
- Keine separate Browser-Alert-Meldung.
config/dhl.phpund/oder DHL-Settings- Einstellung z. B.
print_only_if_codeablevorbereiten. - Standard fachlich klaeren: fuer Produktion bevorzugt aktiv, fuer Tests/Sandbox ggf. konfigurierbar.
- Einstellung z. B.
Fachliche Entscheidung:
- Die formale Pruefung bleibt vor DHL bestehen, damit offensichtliche Fehler frueh im Modal sichtbar sind.
mustEncode=trueist die finale DHL-seitige Absicherung direkt bei der Labelerstellung.- Eine abgelehnte DHL-Adresse blockiert die Labelerstellung.
- Provider-Ausfall oder API-Fehler muss von einer fachlichen DHL-Adressablehnung unterscheidbar sein.
- Die Fehlermeldung soll fuer Admins handlungsorientiert sein, z. B.
DHL kann diese Adresse nicht leitcodieren. Bitte Straße, Hausnummer, PLZ und Ort pruefen. - Fuer nicht-deutsche Empfaengeradressen darf
mustEncodenicht als vollwertige externe Adressvalidierung dargestellt werden, solange DHL diese Einschraenkung in der API-Dokumentation nennt.
Offene Klaerung nach Implementierung:
- Verhalten in Sandbox und Produktion.
- Welche DHL-Status-/Fehlercodes fuer nicht codeable Adressen zurueckkommen.
- Ob
mustEncodefuer alle genutzten Produkte gilt:V01PAKV62KPV53PAK
- Ob internationale Sendungen ignoriert werden, Warnungen liefern oder anders behandelt werden.
- Ob bestehende manuelle Admin-Uebersteuerung fachlich erlaubt sein soll oder DHL-Ablehnung immer hart blockiert.
Empfohlene Reihenfolge
V62WP->V62KP, Statuswerte undTrackShipmentJobkorrigieren.- Storno stabilisieren und bessere Fehlermeldungen im Cockpit anzeigen.
- Internationalen Produktresolver einbauen.
- Referenzfeld und Gewichtskorrektur umsetzen.
- Adressvalidierung integrieren.
- Tracking-Anzeigen und Mailregeln final testen.
- DHL-seitige Adressvalidierung ueber
mustEncode=trueintegrieren, sobald API-Verhalten und Fehlerrueckgaben geklaert sind.
Teststrategie
- Feature-Tests fuer Controller-Endpunkte: Label erstellen, Storno, Tracking-Mail, Tracking-Update.
- Unit-Tests fuer Produktresolver, Gewichtskalkulation und Adressvalidierung.
- HTTP-Fakes fuer DHL API Responses inklusive Fehlerfaelle.
- Regression-Test fuer
V62KPPayload. - Command-Test fuer
dhl:update-tracking --send-emails.
Entwicklungsprotokoll
Dieser Abschnitt dokumentiert die tatsaechlich umgesetzten Entwicklungsschritte. Jede Phase wird hier nach Abschluss mit Ziel, betroffenen Dateien, fachlicher Entscheidung und Verifikation ergaenzt.
27.05.2026 - Phase 1: Pflichtkorrekturen vor DHL-Frist
Status: abgeschlossen.
Ziel:
- Neue DHL-Labels duerfen nicht mehr mit
V62WPWarenpost erstellt werden. V62KPDHL Kleinpaket wird als neues Produkt fuer nationale Kleinpaket-Sendungen eingefuehrt.- Historische Sendungen mit
V62WPbleiben im System lesbar.
Umsetzung:
config/dhl.phpDHL_ACCOUNT_NUMBER_V62KPals neuer Konfigurationskey eingefuehrt.V62KPinaccount_numbersunddimensionsaufgenommen.V62WPaus der produktiven Konfiguration fuer neue Label entfernt.
app/Http/Controllers/SettingController.php- DHL-Konfiguration liefert nun
account_numbers.V62KPunddimensions.V62KP. - Altes Datenbank-Setting
dhl_account_v62wpwird nur noch als Fallback genutzt, fallsdhl_account_v62kpnoch nicht gepflegt ist. - Altes Default-Produkt
V62WPwird beim Lesen aufV62KPnormalisiert.
- DHL-Konfiguration liefert nun
resources/views/admin/settings/index.blade.php- Admin-Auswahl fuer Standard-Produkt von
V62WP - Warenpost NationalaufV62KP - DHL Kleinpaketumgestellt. - Admin-Feld
dhl_account_v62kpeingefuehrt. - Bestehender Wert aus
dhl_account_v62wpwird im Formular als Fallback angezeigt, damit Bestandskonfigurationen nicht leer starten.
- Admin-Auswahl fuer Standard-Produkt von
app/Services/DhlModalService.php- Produktauswahl im DHL-Cockpit bietet
V62KP - DHL KleinpaketstattV62WP - DHL Warenpost Nationalan. - Fallback-Produktsatz ebenfalls auf
V62KPumgestellt.
- Produktauswahl im DHL-Cockpit bietet
packages/acme-laravel-dhl/src/Services/ShippingService.php- Validierung fuer neue Label erlaubt
V62KP. V62WPwird fuer neue Label abgelehnt.- Billing-Nummer-Aufloesung nutzt automatisch den neuen Setting-Key
dhl_account_v62kp.
- Validierung fuer neue Label erlaubt
resources/lang/de/dhl.php,resources/lang/en/dhl.php,resources/lang/es/dhl.php,resources/lang/fr/dhl.phpV62KPals DHL Kleinpaket ergaenzt.V62WPals Legacy-Anzeige belassen, damit historische Sendungen weiterhin verstaendlich dargestellt werden.
tests/Pest.php- TestCase-Bootstrapping fuer
tests/Unit/Dhlregistriert.
- TestCase-Bootstrapping fuer
tests/Unit/Dhl/ShippingServiceProductCodeTest.php- Neuer Regression-Test fuer Phase 1 ergaenzt.
Fachliche Entscheidung:
V62WPwird nicht hart aus allen Anzeigen entfernt, weil bestehende DHL-Sendungen mit diesem Produktcode historisch nachvollziehbar bleiben muessen.- Neue Labelerstellung blockiert
V62WPbewusst ueber dieShippingService-Validierung. - Eine Datenmigration fuer bestehende Settings wurde noch nicht angelegt. Stattdessen wird
dhl_account_v62wptemporaer als Fallback verwendet. Eine spaetere Migration kann den Wert dauerhaft nachdhl_account_v62kpueberfuehren.
Verifikation:
./vendor/bin/pint --dirty --format agentphp artisan test --compact tests/Unit/Dhl/ShippingServiceProductCodeTest.php- Ergebnis: 3 Tests bestanden, 5 Assertions.
- IDE/Linter-Pruefung der geaenderten Dateien:
- Ergebnis: keine Fehler.
Offene Folgepunkte:
- Phase 2:
TrackShipmentJobauf aktuelles DHL-Model und aktuellenDhlTrackingServiceumstellen. Erledigt am 27.05.2026. - Phase 2: Storno-Statuswerte zwischen
canceledundcancelledvereinheitlichen. Erledigt am 27.05.2026. - Optional: Migration oder Admin-Hinweis fuer das alte Setting
dhl_account_v62wp, sobald die Fallback-Phase beendet werden soll.
27.05.2026 - Phase 2: Tracking-Job und Storno-Status stabilisiert
Status: abgeschlossen fuer Tracking-Job, Statusvereinheitlichung und strukturierte Storno-Fehlerbasis.
Ziel:
- Asynchrones Tracking darf nicht mehr auf das alte, nicht mehr produktive DHL-Model
App\Models\DhlShipmentzugreifen. - Storno-Statuswerte werden intern auf
canceledvereinheitlicht. - Alte Daten mit
cancelledbleiben lesbar und filterbar. - Storno-Fehler werden fachlich verwertbarer im Shipment protokolliert.
Umsetzung:
app/Jobs/TrackShipmentJob.php- Import von
App\Models\DhlShipmentaufAcme\Dhl\Models\DhlShipmentumgestellt. - Abhaengigkeit auf den alten
DhlApiServiceentfernt. - Job nutzt nun
DhlTrackingService::updateTrackingNow()ueber Laravel-Dependency-Injection imhandle()-Methodenparameter. - Queue-Tracking fuehrt dadurch synchron innerhalb des Jobs aus und dispatcht nicht erneut rekursiv in dieselbe Queue.
- Logging verwendet jetzt
dhl_shipment_nostatt des alten/inkonsistententracking_number.
- Import von
app/Services/DhlTrackingService.phpupdateTrackingNow()ergaenzt, um Tracking bewusst ohne Queue-Dispatch auszufuehren.- Bestehender Controller-/Service-Pfad
updateTracking()bleibt unveraendert und entscheidet weiterhin anhand der DHL-Konfiguration zwischen sync/async.
packages/acme-laravel-dhl/src/Models/DhlShipment.phpcancelledals Legacy-Alias fuercanceledeingefuehrt.- Statusuebersetzung und Badge-Klasse normalisieren alte
cancelled-Werte aufcanceled. - Terminal-Statusliste enthaelt
canceledundcancelled, damit Alt-Datensaetze nicht weiter getrackt werden.
packages/acme-laravel-dhl/src/Services/ShippingService.php- Erfolgreiche Stornierung setzt weiterhin intern
status = canceled. - Erfolgreiche Storno-Antwort wird unter
api_response_data.cancellationgespeichert. - Fehlgeschlagene Stornos werden unter
api_response_data.cancellation_errorstrukturiert gespeichert:statushttp_statusdhl_codedetailexception_classoccurred_at
- Erfolgreiche Stornierung setzt weiterhin intern
app/Services/DhlShipmentService.php- Lokale Storno-Validierungsfehler wie fehlende DHL-Sendungsnummer oder nicht stornierbarer Status werden ebenfalls in
api_response_data.cancellation_errorprotokolliert. - Admin-Feedback fuer nicht stornierbaren Status nutzt die uebersetzte Statusbezeichnung.
- Lokale Storno-Validierungsfehler wie fehlende DHL-Sendungsnummer oder nicht stornierbarer Status werden ebenfalls in
app/Http/Controllers/DhlShipmentController.php- Statusfilter normalisiert
cancelledaufcanceled. - Bei Filter
canceledwerden neuecanceled- und altecancelled-Sendungen gemeinsam gefunden. - DataTable-Badges verwenden intern
canceled.
- Statusfilter normalisiert
resources/views/admin/dhl/cockpit.blade.php- Statusfilter zeigt
canceledals neuen Wert fuer "Storniert". - Alte URL-/Request-Werte mit
cancelledbleiben im Select kompatibel.
- Statusfilter zeigt
resources/views/admin/dhl/show.blade.php- Detailansicht behandelt
canceledundcancelledgleich. - Aktionsbereich wird fuer beide Storno-Statuswerte ausgeblendet.
- Detailansicht behandelt
resources/views/public/tracking.blade.php- Public-Tracking behandelt
canceledundcancelledgleich.
- Public-Tracking behandelt
resources/lang/de/dhl.php,resources/lang/en/dhl.php,resources/lang/es/dhl.php,resources/lang/fr/dhl.php- Neuer Statuskey
canceledergaenzt. - Legacy-Key
cancelledbleibt erhalten.
- Neuer Statuskey
tests/Unit/Dhl/DhlShipmentStatusTest.php- Neue Tests fuer Statusnormalisierung, Uebersetzung und Badge-Klasse.
tests/Unit/Dhl/TrackShipmentJobTest.php- Neuer Test, dass der Queue-Job den aktuellen
DhlTrackingServicenutzt.
- Neuer Test, dass der Queue-Job den aktuellen
Fachliche Entscheidung:
- Intern gilt ab jetzt
canceledals kanonischer DHL-Storno-Status. cancelledwird nicht migriert oder entfernt, weil es in bestehenden Daten/URLs/Views vorkommen kann und weiterhin verstaendlich angezeigt werden soll.- Storno-Fehler werden zunaechst in
api_response_datagespeichert, um keine neue Datenbankmigration fuer den ersten Stabilisierungsschritt zu erzwingen. Falls spaeter gezielte Admin-Auswertungen gebraucht werden, koennen dedizierte Spalten oder eine eigene Fehler-Tabelle folgen.
Verifikation:
./vendor/bin/pint --dirty --format agentphp artisan test --compact tests/Unit/Dhl- Ergebnis: 6 Tests bestanden, 12 Assertions.
- IDE/Linter-Pruefung der geaenderten Dateien:
- Ergebnis: keine Fehler.
Offene Folgepunkte:
- Phase 2 Rest: Tests fuer echte Storno-API-Fehler mit HTTP-Fakes/Mocking weiter ausbauen.
- Phase 2 Rest: Admin-Detailanzeige fuer gespeicherte
api_response_data.cancellation_errorbei Bedarf sichtbar machen. - Phase 3: Internationalen Produkt-/Billing-Resolver einfuehren. Erledigt am 27.05.2026.
27.05.2026 - Phase 3: Internationaler Produkt- und Laenderresolver
Status: abgeschlossen fuer zentrale Produkt-/Laenderentscheidung, initiale Laenderfreigabe und harten Zielland-Fallback-Stopp.
Ziel:
- Produktcode, Zielland und DHL-Laendercode werden zentral entschieden.
- Deutschland nutzt fuer neue Label nur erlaubte nationale Produkte
V01PAKoderV62KP. - Oesterreich und Spanien nutzen initial
V53PAK. - Unbekannte oder nicht freigegebene Ziellaender duerfen nicht mehr still auf
DEUfallen. - Das DHL-Cockpit schlaegt den Produktcode anhand des Ziellands vor, laesst aber erlaubte Admin-Korrekturen zu.
Umsetzung:
app/Services/DhlProductResolver.php- Neuer zentraler Resolver fuer DHL-Produkt- und Laenderentscheidungen.
- Regeln initial:
DE: erlaubtV01PAK,V62KPAT: erlaubtV53PAKES: erlaubtV53PAK
- DHL-Laendercode-Konvertierung zentralisiert, z. B.
DE -> DEU,AT -> AUT,ES -> ESP. - Unbekannte Laendercodes loesen eine klare Exception aus statt auf Deutschland zurueckzufallen.
- Explizit falsch gewaehlte Produkt-/Laender-Kombinationen werden abgelehnt.
- Fehlende Billing-Nummern werden ueber
assertBillingNumber()fachlich klar abgebrochen.
app/Services/DhlDataHelper.php- Empfaengerland ist jetzt Pflicht fuer die DHL-Datenaufbereitung.
- Produktcode wird ueber
DhlProductResolver::resolveForShipment()bestimmt. - Der alte Empfaengerland-Fallback auf
DEwurde entfernt. - Dimensionen werden anhand des aufgeloesten Produktcodes gelesen.
packages/acme-laravel-dhl/src/Services/ShippingService.php- Payload-Erstellung nutzt den Resolver fuer Zielland und Produktcode.
- Billing-Nummer wird gegen den aufgeloesten Produktcode validiert.
convertCountryCode()nutzt den Resolver und gibt keinenDEU-Fallback mehr zurueck.- Normale Empfaengeradressen und Packstation/Paketbox-Payloads verwenden die gleiche harte Laendercode-Validierung.
app/Services/DhlModalService.php- Modal-Daten enthalten nun
productSuggestionsundselectedProductCode. - Initialer Produktvorschlag wird aus dem Zielland abgeleitet.
- Servervalidierung prueft Produkt-/Zielland-Kombinationen ueber den Resolver.
- Modal-Daten enthalten nun
resources/views/admin/dhl/modal_in_order_shipment.blade.php- Produktauswahl markiert den vorgeschlagenen Produktcode.
- Laenderoptionen tragen den ISO-Code als
data-country-code. - Hinweistext ergaenzt, dass der Produktcode anhand des Ziellands vorgeschlagen wird.
resources/views/admin/dhl/modal_create_shipment.blade.php- Bei Ziellandwechsel wird der passende Produktvorschlag automatisch gesetzt.
app/Http/Controllers/ModalController.php- Fallback-Daten fuer das DHL-Modal um Produktvorschlaege und Default-Produkt ergaenzt.
tests/Unit/Dhl/DhlProductResolverTest.php- Neue Tests fuer Deutschland, Oesterreich, Spanien, nicht freigegebene Laender, unbekannte Laendercodes und fehlende Billing-Nummern.
tests/Unit/Dhl/ShippingServiceProductCodeTest.php- Payload-Test fuer internationales Paket nach Oesterreich ergaenzt.
- Regression-Test ergaenzt, dass nicht freigegebene Laender nicht auf Deutschland fallen.
Fachliche Entscheidung:
DE,ATundESsind die initial freigegebenen DHL-Versandlaender fuer diese Phase.- Weitere Laender werden nicht implizit erlaubt, auch wenn eine ISO-Konvertierung technisch bekannt ist. Sie muessen fachlich freigegeben und im Resolver ergaenzt werden.
- Falls im Backend kein Produktcode explizit uebergeben wird, kann der Resolver fuer
AT/ESautomatischV53PAKvorschlagen. Wenn ein Admin explizit ein nicht erlaubtes Produkt waehlt, wird die Labelerstellung serverseitig abgelehnt. - Der alte
DEU-Fallback wurde bewusst entfernt, weil ein falsches Zielland zu falschen Labels und kostenpflichtigen Stornos fuehren kann.
Verifikation:
./vendor/bin/pint --dirty --format agentphp artisan test --compact tests/Unit/Dhl- Ergebnis: 15 Tests bestanden, 32 Assertions.
- IDE/Linter-Pruefung der geaenderten Dateien:
- Ergebnis: keine Fehler.
Offene Folgepunkte:
- Phase 3 Erweiterung: Weitere Laender erst nach fachlicher Freigabe in
DhlProductResolveraufnehmen. - Phase 3 Erweiterung: Optional Admin-Setting fuer freigegebene internationale Ziellaender einfuehren.
- Phase 4: Adressvalidierung vor Labelerstellung integrieren. Erledigt als formale Basisvalidierung am 27.05.2026.
27.05.2026 - Phase 4: Formale Adressvalidierung vor Labelerstellung
Status: abgeschlossen fuer serverseitige Basisvalidierung, Warn-/Fehlerstatus und Cockpit-Vorpruefung. Externe postalische Validierung ist noch offen.
Ziel:
- Vor der Labelerstellung wird eine DHL-Adresse serverseitig bewertet.
- Offensichtlich nicht versandfaehige Adressen blockieren die Labelerstellung.
- Pruefbeduerftige Adressen erzeugen Warnungen und muessen im Cockpit bewusst bestaetigt werden.
- Packstation/Paketbox-Faelle werden strenger validiert.
- Die bestehende Labelerstellung bleibt auch serverseitig abgesichert, falls die UI-Pruefung umgangen wird.
Umsetzung:
app/Services/DhlAddressValidator.php- Neuer zentraler Validator fuer formale DHL-Adresspruefung.
- Rueckgabeformat:
status:valid,warningodererrorcan_create_label: true/falseerrorswarningsnormalized
- Blockierende Pruefungen:
- Pflichtfelder fuer Strasse, PLZ, Ort und Land.
- Name/Firma muss vorhanden sein.
- Zielland muss im
DhlProductResolverfreigegeben sein. - PLZ-Format fuer
DE,AT,ES. - Packstation/Paketbox nur fuer Deutschland.
- DHL Postnummer muss bei Packstation/Paketbox vorhanden und 6-10-stellig sein.
- Packstation-/Paketbox-Nummer muss vorhanden und 3-stellig im Bereich 100-999 sein.
- Warnungen:
- Telefonnummer fehlt.
- E-Mail-Adresse fehlt.
- Hausnummer enthaelt keine Ziffer.
- Postnummer ist gesetzt, aber Strasse/Nr. sieht nicht nach Packstation/Paketbox aus.
app/Services/DhlModalService.php- Bestehende Modal-Adresspruefung nutzt nun den neuen
DhlAddressValidator. validateShipmentData()fuehrt die Adressvalidierung auch serverseitig vor der Labelerstellung aus.
- Bestehende Modal-Adresspruefung nutzt nun den neuen
app/Http/Controllers/DhlShipmentController.php- Neuer Endpoint
validateAddress(). - Antwortet mit Status, Fehlern, Warnungen und
can_create_label. - Gibt HTTP 422 zurueck, wenn die Adresse nicht label-faehig ist.
- Neuer Endpoint
routes/domains/crm.php- Neue Route
POST /admin/dhl/validate-addressmit Nameadmin.dhl.validate-address.
- Neue Route
resources/views/admin/dhl/modal_create_shipment.blade.php- Modal ruft vor der eigentlichen Labelerstellung den neuen Validierungsendpunkt auf.
- Bei Fehlern wird die Labelerstellung blockiert.
- Bei Warnungen muss der Admin bewusst bestaetigen, bevor das Label erstellt wird.
tests/Unit/Dhl/DhlAddressValidatorTest.php- Neue Tests fuer:
- formal gueltige Adresse
- nicht freigegebenes Zielland
- falsches PLZ-Format fuer aktiviertes Land
- Warnstatus ohne Blockade
- gueltige Packstation
- ungueltige Packstation-Postnummer
- fehlende Packstation-Pflichtdaten
- Neue Tests fuer:
Fachliche Entscheidung:
- Diese Phase implementiert noch keine echte postalische Existenzpruefung von Strasse/PLZ/Ort.
- Der Validator verhindert aber bereits die teuersten formalen Fehler vor Labelerstellung.
- Warnungen blockieren nicht automatisch, weil Admins im Cockpit bewusst korrigierte oder fachlich bekannte Sonderfaelle versenden koennen sollen.
- Die spaetere Provider-Integration kann hinter dem gleichen
DhlAddressValidatorergaenzt werden.
Verifikation:
./vendor/bin/pint --dirty --format agentphp artisan test --compact tests/Unit/Dhl- Ergebnis: 22 Tests bestanden, 50 Assertions.
- IDE/Linter-Pruefung der geaenderten Dateien:
- Ergebnis: keine Fehler.
Offene Folgepunkte:
- Phase 4 Erweiterung: DHL DATAFACTORY AUTOCOMPLETE 2.0 fuer
DE/AT/CHfachlich und technisch evaluieren. - Phase 4 Erweiterung: Falls DHL DATAFACTORY nicht fuer alle benoetigten Laender reicht, externen Provider wie Loqate, Google Address Validation oder HERE bewerten.
- Phase 4 Erweiterung: Validierungsergebnis optional dauerhaft an Shipment/Order protokollieren.
- Phase 5: Referenzfeld und Admin-UX umsetzen. Erledigt am 27.05.2026.
27.05.2026 - Phase 5: Referenzfeld und Admin-UX
Status: abgeschlossen.
Ziel:
- Admins koennen im DHL-Cockpit eine eigene Versandreferenz setzen.
- Die Referenz wird als DHL
refNoan die Shipping API uebergeben. - Die Referenz wird an der Sendung gespeichert und in der Detailansicht angezeigt.
- Das DHL-Laengenlimit von 35 Zeichen wird eingehalten.
- Ohne Admin-Eingabe bleibt der bisherige Fallback
Order-{id}erhalten.
Umsetzung:
database/migrations/2026_05_27_120253_add_reference_to_dhl_package_shipments_table.php- Neue nullable Spalte
referencemit Laenge 35 fuerdhl_package_shipments. - Migration prueft
Schema::hasColumn, damit sie robust gegen bereits vorhandene Spalten bleibt.
- Neue nullable Spalte
packages/acme-laravel-dhl/src/Models/DhlShipment.phpreferencein$fillableaufgenommen.
resources/views/admin/dhl/modal_in_order_shipment.blade.php- Neues Feld
referencein der Sendungskonfiguration. - Default-Wert:
Order-{id}. maxlength="35"und Hilfetext zum DHLrefNo.
- Neues Feld
resources/views/admin/dhl/modal_create_shipment.blade.php- Clientseitige Validierung fuer maximal 35 Zeichen ergaenzt.
app/Http/Controllers/DhlShipmentController.php- Servervalidierung fuer
referencemitmax:35. - Uebergibt
referencein die Optionen fuerDhlShipmentService.
- Servervalidierung fuer
app/Services/DhlDataHelper.php- Uebernimmt
referenceoder alternativshipment_referenceaus den Optionen. - Normalisiert Leerraum.
- Kuerzt programmgesteuert auf 35 Zeichen.
- Nutzt
Order-{id}als Fallback, wenn keine Referenz gesetzt ist.
- Uebernimmt
packages/acme-laravel-dhl/src/Services/ShippingService.php- Bestehendes Mapping nach DHL
refNobleibt aktiv. - Gesendete Referenz wird beim Erstellen des
DhlShipment-Datensatzes gespeichert.
- Bestehendes Mapping nach DHL
resources/views/admin/dhl/show.blade.php- Referenz wird in den Sendungsinformationen angezeigt.
tests/Unit/Dhl/DhlDataHelperReferenceTest.php- Neue Tests fuer Admin-Referenz, Fallback
Order-{id}und 35-Zeichen-Normalisierung.
- Neue Tests fuer Admin-Referenz, Fallback
Fachliche Entscheidung:
- Die Referenz bleibt bewusst ein kurzes Freitextfeld und wird nicht an Bestellnotizen oder interne Kommentare gekoppelt.
- Das Feld wird fuer DHL
refNound spaetere Nachvollziehbarkeit genutzt, nicht als internes Memo. - Der Fallback
Order-{id}bleibt erhalten, damit bestehende Prozesse ohne manuelle Referenz unveraendert funktionieren.
Verifikation:
./vendor/bin/pint --dirty --format agentphp artisan test --compact tests/Unit/Dhl- Ergebnis: 25 Tests bestanden, 54 Assertions.
- IDE/Linter-Pruefung der geaenderten Dateien:
- Ergebnis: keine Fehler.
Offene Folgepunkte:
- Datenbankmigration vor Nutzung in der Zielumgebung ausfuehren.
- Phase 6: DHL-Gewicht korrekt berechnen.
27.05.2026 - Abgleich DHL Geschaeftskundenportal: Aktivierte Dienste
Status: dokumentiert als fachlicher Abgleich vor Phase 6.
Quelle:
- Screenshot aus dem DHL Geschaeftskundenportal vom 27.05.2026 mit aktivierten Produkten und Abrechnungsnummern.
Aktivierte Dienste laut Portal:
63144073550101- DHL Paket GKP63144073550701- DHL Retoure Online63144073555301- DHL Paket International GKP63144073555302- DHL Retoure Int. A63144073556201- Warenpost National / DHL Kleinpaket63144073556601- Warenpost International63144073550801- DHL Retoure MAUL
Abgleich mit aktueller Implementierung:
DHL Paket GKP- Aktueller Produktcode im Modul:
V01PAK - Konfiguriert in
config/dhl.phpalsDHL_ACCOUNT_NUMBER_V01PAK. - Wird fuer nationale Paketsendungen genutzt.
- Status: abgedeckt.
- Aktueller Produktcode im Modul:
DHL Paket International GKP- Aktueller Produktcode im Modul:
V53PAK - Konfiguriert in
config/dhl.phpalsDHL_ACCOUNT_NUMBER_V53PAK. - Wird aktuell nur fuer fachlich freigegebene Ziellaender
ATundESgenutzt. - Status: technisch abgedeckt, Laenderfreigabe bleibt bewusst begrenzt.
- Aktueller Produktcode im Modul:
Warenpost National / DHL Kleinpaket- Aktueller Produktcode im Modul:
V62KP. - Konfiguriert in
config/dhl.phpalsDHL_ACCOUNT_NUMBER_V62KP. - Ersetzt das alte
V62WP. - Status: abgedeckt.
- Aktueller Produktcode im Modul:
DHL Retoure Online- Konfiguriert in
config/dhl.phpalsDHL_ACCOUNT_NUMBER_V07PAK. - Status: Konto ist konfiguriert. Der bestehende
ReturnsServicenutzt in Teilen noch Fallback-Logik und muss vor produktiver Retourennutzung separat gegen das aktive Retoure-Konto geprueft werden.
- Konfiguriert in
DHL Retoure Int. A- Konto im Portal aktiv.
- Im Modul aktuell nicht als eigenstaendiger internationaler Retourenprozess modelliert.
- Status: offen, eigener Folgepunkt.
Warenpost International- Konto im Portal aktiv.
- Im Modul aktuell nicht freigeschaltet. Laut DHL API ist dafuer
V66WPIrelevant; dafuer sind Zoll-/CN22-Daten und eigene Gewichts-/Laenderregeln erforderlich. - Status: bewusst nicht Teil der bisherigen Phasen.
DHL Retoure MAUL- Konto im Portal aktiv.
- Im Modul aktuell nicht modelliert.
- Status: offen, nur nach fachlichem Bedarf umsetzen.
Technische Entscheidung:
- Fuer Phase 6 wird nur das Gewicht fuer die bereits freigegebenen Ausgangsprodukte betrachtet:
V01PAK,V53PAK,V62KP. - Internationale Warenpost (
V66WPI) wird nicht stillschweigend aktiviert, obwohl ein Konto im Portal sichtbar ist. Die Produktart benoetigt separate Regeln und ggf. Zollangaben. - Internationale Retouren und Retoure MAUL werden nicht mit bestehenden Paket-/Retoure-Fallbacks vermischt.
Neue Folgepunkte aus dem Portal-Abgleich:
- Retourenlogik separat gegen aktives
DHL Retoure OnlineKonto pruefen und ggf.V07PAK/Returns-API sauber modellieren. - Internationales Retourenprodukt
DHL Retoure Int. Afachlich klaeren. Warenpost International/V66WPInur als eigene Phase ergaenzen, wenn Zoll-, Gewichts- und Laenderregeln geklaert sind.DHL Retoure MAULnur bei konkretem Prozessbedarf aufnehmen.
27.05.2026 - Nachtrag vor Phase 6: Internationale Paketlaender per DHL-Settings steuerbar
Status: abgeschlossen.
Ziel:
- Kunden/Admins koennen selbst entscheiden, welche Ziellaender fuer
DHL Paket Internationalaktiv sind. - Die Freigabe erfolgt im bestehenden Settings-Bereich ueber Checkboxen.
- Der
DhlProductResolvernutzt die gespeicherten Laender dynamisch statt einer fest kodiertenAT/ES-Liste. - Deutschland bleibt separat geregelt und wird nicht als internationales Paketland gespeichert.
Umsetzung:
config/dhl.php- Neuer Fallback
international_countriesausDHL_INTERNATIONAL_COUNTRIES. - Default bleibt
AT,ES.
- Neuer Fallback
app/Http/Controllers/SettingController.phpgetDhlConfig()liefert nuninternational_countries.- Bei
DHL_CONFIG_SOURCE=envwird keine Datenbankabfrage fuer diese Liste ausgefuehrt. - Bei Datenbankprioritaet wird
dhl_international_countriesaus den Settings gelesen.
resources/views/admin/settings/index.blade.php- Neuer Checkbox-Bereich
DHL Paket International Ziellaender. - Es werden aktive App-Laender angezeigt, deren ISO-Code im DHL-Resolver bekannt ist.
- Speicherung als Setting
dhl_international_countriesvom Typobject.
- Neuer Checkbox-Bereich
app/Services/DhlProductResolver.php- Feste internationale Liste durch
getSupportedInternationalCountries()ersetzt. - Resolver liest je nach
DHL_CONFIG_SOURCEentweder Config/ENV oder Datenbank-Setting. normalizeCountryCodeList()normalisiert, dedupliziert und filtert unbekannte Codes sowieDE.- Produktvorschlaege fuer das Modal werden dynamisch aus der aktivierten Laenderliste erzeugt.
- Feste internationale Liste durch
app/Http/Controllers/ModalController.php- Fallback-Daten fuer das DHL-Modal nutzen nun ebenfalls dynamische Produktvorschlaege.
tests/Unit/Dhl/DhlProductResolverTest.php- Tests fuer konfigurierbare internationale Laender ergaenzt.
- Tests fuer Normalisierung und Filterung der Laenderliste ergaenzt.
Fachliche Entscheidung:
- Die Checkboxen aktivieren nur
V53PAKfuer DHL Paket International. V66WPIWarenpost International bleibt davon unberuehrt und wird nicht versehentlich aktiviert.DEwird nicht als internationales Zielland zugelassen, weil nationale Sendungen ueberV01PAK/V62KPlaufen.
Verifikation:
./vendor/bin/pint --dirty --format agentphp artisan test --compact tests/Unit/Dhl- Ergebnis: 27 Tests bestanden, 60 Assertions.
- IDE/Linter-Pruefung der geaenderten Dateien:
- Ergebnis: keine Fehler.
27.05.2026 - Phase 6: DHL-Gewicht korrekt berechnen
Status: abgeschlossen.
Ziel:
- Das DHL-Labelgewicht beruecksichtigt Kompensationsprodukte.
- Die bestehende Warenkorb-, Versandkosten- und Kompensationslogik bleibt unveraendert.
- Admins koennen das Gewicht weiterhin im Modal erhoehen, aber nicht unter das berechnete DHL-Mindestgewicht druecken.
- Produktbezogene DHL-Gewichtsgrenzen werden geprueft.
Umsetzung:
app/Services/DhlShipmentWeightCalculator.php- Neuer zentraler Gewichtsdienst fuer DHL-Labelerstellung.
- Basisgewicht ist
ShoppingOrder->weightin Gramm. - Fuer
shopping_order_itemsmitcomp > 0wird das verknuepfteProduct->weightjeqtyaddiert. - Falls kein Gewicht vorhanden ist, wird ein sicherer Fallback von
1.0 kggenutzt. - Rundung erfolgt auf drei Nachkommastellen.
- Produktlimits:
V01PAK: 31.5 kgV53PAK: 31.5 kgV62KP: 1.0 kg
app/Services/DhlModalService.php- Laedt Bestellpositionen jetzt mit Produktrelation.
- Modal-Gewicht nutzt den neuen Kalkulator.
- Validierung prueft das Gewicht gegen das gewaehlte DHL-Produkt.
app/Http/Controllers/DhlShipmentController.php- Vor Labelerstellung wird das tatsaechliche Versandgewicht auf mindestens das berechnete DHL-Gewicht gesetzt.
- Ein manuell hoeheres Gewicht aus dem Formular bleibt erhalten.
app/Services/DhlShipmentService.php- Ermittelt serverseitig ebenfalls das Mindestgewicht, damit Queue- und Direktaufrufe abgesichert sind.
packages/acme-laravel-dhl/src/Services/ShippingService.php- Prueft produktbezogene DHL-Gewichtsgrenzen vor Payload-Erstellung.
resources/views/admin/dhl/modal_in_order_shipment.blade.php- Hinweistext am Gewichtsfeld stellt klar, dass Kompensationsprodukte eingerechnet sind.
tests/Unit/Dhl/DhlShipmentWeightCalculatorTest.php- Neue Tests fuer:
- Basisgewicht aus Bestellung
- Addition von Kompensationsprodukt-Gewichten
- Fallbackgewicht
V62KP-Gewichtslimit- erlaubtes Paketgewicht fuer
V01PAK
- Neue Tests fuer:
Fachliche Entscheidung:
- Die Berechnung wirkt nur auf das DHL-Labelgewicht.
ShoppingOrder->weightund die bestehende Checkout-/Versandkostenlogik werden nicht veraendert.- Kompensationsartikel werden nur dann addiert, wenn
comp > 0und ein Produktgewicht vorhanden ist. V62KPwird mit 1.0 kg begrenzt, damit Kleinpaket nicht versehentlich fuer schwerere Sendungen genutzt wird.
Verifikation:
./vendor/bin/pint --dirty --format agentphp artisan test --compact tests/Unit/Dhl- Ergebnis: 32 Tests bestanden, 66 Assertions.
- IDE/Linter-Pruefung der geaenderten Dateien:
- Ergebnis: keine Fehler.
27.05.2026 - Phase 7: Tracking-Codes und Tracking-Mails finalisiert
Status: abgeschlossen fuer Admin/User-Anzeige und automatische Mail-Ausloesung.
Ziel:
- Tracking-Codes sind in Admin- und User-Order-Details sichtbar.
- Automatische Tracking-Mails werden nur einmal pro Sendung versendet.
- Mails werden nur bei relevanter Statusaenderung ausgeloest.
- Statusspruenge wie
createddirekt nachout_for_deliverywerden abgedeckt. - Mehrere passende Sendungen einer Bestellung werden weiterhin in einer Mail zusammengefasst.
Umsetzung:
packages/acme-laravel-dhl/src/Models/DhlShipment.php- Neue Konstante
TRACKING_EMAIL_TRIGGER_STATUSES. - Relevante automatische Mail-Statuswerte:
in_transitout_for_delivery
- Neue Methode
shouldTriggerTrackingEmail(). - Bedingung:
- aktueller Status ist relevant
- Status hat sich gegenueber dem vorherigen Status geaendert
- Tracking-Mail wurde noch nicht markiert
- Sendung hat DHL-Sendungsnummer und Empfaengeradresse
- Scope
needsTrackingEmail()nutzt die zentrale Statusliste.
- Neue Konstante
app/Console/Commands/DhlUpdateTracking.php- Automatische Mailentscheidung nutzt nun
DhlShipment::shouldTriggerTrackingEmail(). - Beim Zusammenfassen mehrerer Sendungen werden alle unbenachrichtigten Sendungen der Bestellung mit relevantem Status aufgenommen.
- Dadurch werden direkte Spruenge nach
out_for_deliverynicht mehr uebersehen.
- Automatische Mailentscheidung nutzt nun
app/Services/DhlTrackingService.php- DHL-Statusmapping erweitert:
transit,in-transit,in_transit->in_transitout-for-delivery,out_for_delivery->out_for_delivery- weitere Varianten fuer
pre_transitundfailed
- Mapping ist nun statisch nutzbar und separat testbar.
- DHL-Statusmapping erweitert:
resources/views/admin/dhl/show.blade.php- Oeffentlicher Tracking-Link nutzt nun den korrekten Query-Parameter
tracking_number.
- Oeffentlicher Tracking-Link nutzt nun den korrekten Query-Parameter
resources/views/admin/sales/_detail_dhl_shipments.blade.php- Bereits vorhandener DHL-Block wird auch in den User-Order-Modalen mit eingebunden, Aktionen bleiben ueber
isAdminabgesichert.
- Bereits vorhandener DHL-Block wird auch in den User-Order-Modalen mit eingebunden, Aktionen bleiben ueber
tests/Unit/Dhl/DhlShipmentStatusTest.php- Tests fuer Tracking-Mail-Ausloesung bei
out_for_delivery. - Tests fuer einmalige Mail-Ausloesung.
- Tests, dass
deliveredkeine neue automatische Tracking-Mail mehr ausloest. - Tests fuer DHL-Statusmapping-Varianten.
- Tests fuer Tracking-Mail-Ausloesung bei
Fachliche Entscheidung:
- Automatische Tracking-Mails werden bei
in_transitoderout_for_deliveryversendet. deliveredloest keine neue automatische Tracking-Mail aus, weil die Benachrichtigung dann fachlich zu spaet waere und Mehrfachmails vermieden werden sollen.- Manuelles erneutes Senden im Admin bleibt weiterhin moeglich.
- Mehrere Sendungen einer Bestellung werden zusammengefasst, sofern sie noch keine Tracking-Mail erhalten haben und einen relevanten Status besitzen.
Verifikation:
./vendor/bin/pint --dirty --format agentphp artisan test --compact tests/Unit/Dhl- Ergebnis: 38 Tests bestanden, 74 Assertions.
- IDE/Linter-Pruefung der geaenderten Dateien:
- Ergebnis: keine Fehler.
27.05.2026 - Nachtrag Phase 7: Tracking-E-Mail-Historie in der Detailansicht
Status: abgeschlossen.
Ziel:
- In der DHL-Detailansicht soll nicht nur der letzte Tracking-Mail-Status sichtbar sein.
- Es soll nachvollziehbar sein, welche Tracking-E-Mails mit welchem Sendungsstatus versendet wurden.
- Automatische und manuelle Versendungen sollen unterscheidbar bleiben.
- Zusammengefasste Tracking-Mails sollen zeigen, welche Sendungen enthalten waren.
Umsetzung:
packages/acme-laravel-dhl/src/Models/DhlShipment.phpmarkTrackingEmailSent()erweitert.- Bei jedem Versand wird ein Eintrag in
api_response_data.tracking_email_historygeschrieben. - Gespeicherte Felder je Eintrag:
sent_attype(autoodermanual)recipient_emailstatustracking_statusdhl_shipment_noincluded_shipment_ids
- Neue Methode
getTrackingEmailHistory()liefert die Historie mit neuestem Eintrag zuerst. - Neue statische Methode
getStatusBadgeClassFor()fuer Status-Badges in historischen Eintraegen.
app/Http/Controllers/DhlShipmentController.php- Manueller Tracking-Mail-Versand uebergibt Empfaenger und enthaltene Sendungen an
markTrackingEmailSent().
- Manueller Tracking-Mail-Versand uebergibt Empfaenger und enthaltene Sendungen an
app/Console/Commands/DhlUpdateTracking.php- Automatischer Tracking-Mail-Versand uebergibt Empfaenger und enthaltene Sendungen an
markTrackingEmailSent().
- Automatischer Tracking-Mail-Versand uebergibt Empfaenger und enthaltene Sendungen an
resources/views/admin/dhl/show.blade.php- Bereich
Tracking-E-Mail Statuszeigt weiterhin den letzten Versand prominent an. - Darunter wird eine Historientabelle angezeigt mit:
- Zeitpunkt
- Typ
- Status zum Versandzeitpunkt
- Empfaenger
- enthaltene Sendungs-IDs
- Bereich
tests/Unit/Dhl/DhlShipmentStatusTest.php- Tests fuer Tracking-Mail-Historie mit neuestem Eintrag zuerst.
- Test fuer Legacy-Sendungen ohne Historie.
Fachliche Entscheidung:
- Die Historie wird im bestehenden
api_response_dataJSON der Sendung gespeichert, damit keine neue Tabelle notwendig ist. - Die bestehenden Felder
tracking_email_sent_atundtracking_email_typebleiben fuer schnelle Anzeige und bestehende Logik erhalten. - Historische Sendungen, die nur die alten Felder besitzen, bleiben kompatibel und zeigen weiterhin den letzten Versandstatus.
Verifikation:
./vendor/bin/pint --dirty --format agentphp artisan test --compact tests/Unit/Dhl- Ergebnis: 40 Tests bestanden, 79 Assertions.
- IDE/Linter-Pruefung der geaenderten Dateien:
- Ergebnis: keine Fehler.
27.05.2026 - Nachtrag: Modale Vorabpruefung vor Labelerstellung
Status: abgeschlossen.
Ziel:
- Vor der finalen DHL-Labelerstellung soll im bestehenden Erstellungsmodal eine sichtbare Vorabpruefung erscheinen.
- Der Admin soll Produktcode, nationale/internationale Sendungsart, Zielland und Lieferadressstatus sehen.
- Fehler blockieren die Labelerstellung; Warnungen bleiben sichtbar und muessen bewusst bestaetigt werden.
Umsetzung:
app/Http/Controllers/DhlShipmentController.php- Der bestehende Endpoint
validateAddress()liefert nun zusaetzlich strukturiertepreflight-Daten. - Die Produktpruefung nutzt
DhlProductResolver. - Fehler aus Produkt-/Zielland-Kombinationen werden gemeinsam mit Adressfehlern zurueckgegeben.
- Der bestehende Endpoint
app/Services/DhlProductResolver.php- Neue Methoden
getProductScope()undgetProductScopeLabel(). - Dadurch kann die UI Produktcodes fachlich als national oder international anzeigen.
- Neue Methoden
resources/views/admin/dhl/modal_in_order_shipment.blade.php- Neuer Statusbereich
Vorabpruefung vor Labelerstellung. - Der Statusbereich sitzt am Ende des Formulars direkt vor den Aktionsbuttons.
- Initialer Buttontext:
Vorabpruefung durchfuehren.
- Neuer Statusbereich
app/Services/DhlAddressValidator.php- Die Lieferadresse wird zusaetzlich auf plausible Feldinhalte geprueft.
- Offensichtlich ungueltige Werte bei Straße, PLZ oder Ort fuehren jetzt zu Fehlern statt nur zu einer positiven Formalpruefung.
- Fuer DE, AT und CH ist eine formale DACH-Pruefung hinterlegt.
- Diese umfasst Pflichtfelder, PLZ-Format, Plausibilitaet, Platzhalter-/Testdaten und Packstation-Regeln.
- DACH-Hausnummern ohne Ziffer werden als Fehler blockiert.
- Die UI weist nun ausdruecklich darauf hin, dass keine echte DHL-/Adressdatenbank- oder Leitcodepruefung angebunden ist.
- Fuer unterstuetzte Ziellaender ohne landesspezifische Pruefung wird ein Hinweis auf die reine Basis-Adresspruefung ausgegeben.
resources/views/admin/dhl/modal_create_shipment.blade.php- Der erste Klick fuehrt nur die Vorabpruefung aus.
- Die separate Browser-Alert-Fehlermeldung bei Vorpruefungsfehlern wurde entfernt.
- Der Validierungsstatus zeigt nun
Formale DACH-Pruefungstatt irrefuehrendAktiv. - Das Ergebnis wird direkt im Modal angezeigt:
- Produktcode
- Sendungsart
- Zielland
- normalisierte Lieferadresse
- Status der Adressvalidierung
- Fehler und Hinweise
- Erst nach erfolgreicher Vorabpruefung wechselt der Button auf
Sendung jetzt erstellen. - Aenderungen an Formularfeldern setzen die Freigabe automatisch zurueck.
tests/Unit/Dhl/DhlProductResolverTest.php- Neuer Test fuer nationale und internationale Produktklassifizierung.
tests/Unit/Dhl/DhlAddressValidatorTest.php- Neuer Test fuer unplausible Lieferadressfelder.
- Neue Tests fuer CH-Adressvalidierung und Basispruefungs-Hinweise.
- Neue Tests fuer Platzhalteradressen und DACH-Hausnummern ohne Ziffer.
Fachliche Entscheidung:
- Die bestehende Servervalidierung bleibt die Quelle der Wahrheit.
- Die Vorabpruefung ersetzt keine Validierung bei der finalen Erstellung; vor dem Erstellen wird nochmals gegen denselben Endpoint geprueft.
- Dadurch werden nachtraegliche Formularaenderungen oder veraltete Modalzustande nicht ungeprueft an DHL uebergeben.
Verifikation:
./vendor/bin/pint --dirty --format agentphp artisan test --compact tests/Unit/Dhl- Ergebnis: 46 Tests bestanden, 109 Assertions.
- IDE/Linter-Pruefung der geaenderten Dateien:
- Ergebnis: keine Fehler.
27.05.2026 - Phase 8: DHL-seitige Adressvalidierung ueber mustEncode
Status: umgesetzt fuer deutsche Empfaengeradressen.
Ziel:
- DHL soll selbst die finale Adress-/Leitcodefaehigkeit pruefen.
- Grundlage ist der DHL-Query-Parameter
mustEncode=true. printOnlyIfCodableist laut DHL-Spezifikation der Legacy-Name.- Wenn diese Option gesetzt ist, soll DHL das Etikett nur dann erstellen, wenn die Adresse codeable/leitcodierbar ist.
- Wird die Adresse von DHL abgelehnt, soll der Fehler im bestehenden Modal erscheinen und dort korrigierbar sein.
- Laut DHL-Dokumentation ist die Funktion nur fuer deutsche Empfaengeradressen relevant.
Umsetzung:
- Die aktuelle formale Vorabpruefung bleibt erhalten.
config/dhl.php- Neue Option
print_only_if_codeable, steuerbar ueberDHL_PRINT_ONLY_IF_CODEABLE. - Standard: aktiv.
- Neue Option
app/Http/Controllers/SettingController.php- DHL-Konfiguration liefert
print_only_if_codeable.
- DHL-Konfiguration liefert
resources/views/admin/settings/index.blade.php- Neue Checkbox
DHL-Leitcodierung erzwingen (mustEncode).
- Neue Checkbox
app/Services/DhlDataHelper.php- Option wird in die Orderdaten fuer den DHL-Request uebernommen.
packages/acme-laravel-dhl/src/Services/ShippingService.php- Fuer deutsche Empfaengeradressen wird bei aktiver Option der Query-Parameter
mustEncode=truean die DHL Create-Shipment-Operation uebergeben. - DHL-Responses ohne Label/Shipment oder mit Item-Fehlerstatus werden vor dem Speichern der Sendung abgefangen.
- Nicht leitcodierbare Adressen werden als
DhlAddressValidationExceptionnormalisiert.
- Fuer deutsche Empfaengeradressen wird bei aktiver Option der Query-Parameter
packages/acme-laravel-dhl/src/Exceptions/DhlAddressValidationException.php- Neue fachliche Exception fuer DHL-Adressablehnungen.
app/Services/DhlShipmentService.php- DHL-Adressablehnungen werden als Validierungsfehler zurueckgegeben.
- Wenn Queue aktiv ist, aber
mustEncodefuer eine deutsche Empfaengeradresse greift, wird synchron erstellt, damit der DHL-Fehler direkt im Modal sichtbar bleibt.
app/Http/Controllers/DhlShipmentController.php- Gibt DHL-Adressvalidierungsfehler mit HTTP 422 zurueck.
resources/views/admin/dhl/modal_create_shipment.blade.php- Fehler aus der finalen DHL-Erstellung werden in der bestehenden Vorabpruefungsbox angezeigt.
- Keine separate Browser-Alert-Meldung.
27.05.2026 - Nachtrag: DHL-Laenderauswahl in Settings speicherbar
Status: abgeschlossen.
Problem:
- Die Checkboxen fuer
DHL Paket International Ziellaenderliessen sich im Admin sichtbar anklicken. - Nach dem Speichern wurden die ausgewaehlten Laender aber nicht wieder angezeigt.
- Ursache war
DHL_CONFIG_SOURCE=env: Dadurch wurden die gespeicherten Datenbankwerte fuer diese Laenderliste beim Lesen ueberdeckt. - Zusaetzlich wurde eine leere Checkbox-Auswahl als
nullgespeichert und fiel dadurch wieder auf die.env-Konfiguration zurueck.
Umsetzung:
app/Http/Controllers/SettingController.phpdhl_international_countrieswird beim Speichern normalisiert.- Ein leerer Wert wird als leere Liste gespeichert.
- Beim Lesen wird ein vorhandener gespeicherter Array-Wert fuer diese Laenderliste verwendet, auch wenn
DHL_CONFIG_SOURCE=envaktiv ist.
app/Services/DhlProductResolver.php- Nutzt gespeicherte DHL-Laender, sobald sie als Array vorhanden sind.
- Wenn keine
settings-Tabelle verfuegbar ist, bleibt der Config-Fallback erhalten.
app/Models/Setting.php- Object-Settings speichern leere Arrays jetzt als Array statt als
null.
- Object-Settings speichern leere Arrays jetzt als Array statt als
tests/Unit/Dhl/DhlProductResolverTest.php- Tests fuer gespeicherte Laender bei
env-Prioritaet. - Test fuer bewusst leer gespeicherte Laenderliste.
- Tests fuer gespeicherte Laender bei
Verifikation:
./vendor/bin/pint --dirty --format agentphp artisan test --compact tests/Unit/Dhl- Ergebnis: 51 Tests bestanden, 117 Assertions.
Offene Punkte:
- DHL-Sandbox-Verhalten testen.
- Fehlercodes fuer nicht codeable Adressen sammeln.
- Produktabdeckung fuer
V01PAK,V62KPundV53PAKklaeren. - Klaeren, wie internationale Sendungen behandelt werden, da DHL
mustEncodeals nur fuer deutsche Empfaengeradressen relevant beschreibt.
Verifikation:
./vendor/bin/pint --dirty --format agentphp artisan test --compact tests/Unit/Dhl- Ergebnis: 49 Tests bestanden, 115 Assertions.
Phase 9 - Security & Code-Hygiene Hardening (Audit 2026-05-27)
Status: umgesetzt am 2026-05-27.
Im Rahmen eines Audits der bestehenden DHL-Umsetzung wurden mehrere sicherheits- und konsistenzrelevante Punkte mit Prio 1 identifiziert und behoben.
Prio 1.1 – Credentials und PII aus Logs entfernen
Problem:
DhlShipmentService::createShipment()schrieb die komplette DHL-Config inklusiveapi_key,username,password,api_secretund Abrechnungsnummern via\Log::info('dhlConfig', $dhlConfig)in das Application-Log.DhlDataHelper::prepareOrderData()schrieb das Roh-Options-Array (inkl. Versandadresse, Name, Telefon) in das Log.ShippingService::createLabel()schrieb die fertige Order-Daten und in[DHL API] Sending payload to DHLzusaetzlichpayload_jsonmit dem kompletten Payload (Adressen, Abrechnungsnummer) ins Log. Die Fehlerbranch loggte den vollen Payload ebenfalls. Die Response loggte zudem das base64-kodierte Label.
Umsetzung:
app/Services/DhlShipmentService.php- Neue statische Helper
sanitizeDhlConfigForLog()undsanitizeOrderDataForLog(). sanitizeDhlConfigForLog()ersetzt Geheimnisse durch booleschehas_*-Flags und gibt nur nicht-vertrauliche Konfigurationsmetadaten zurueck.sanitizeOrderDataForLog()reduziert Bestelldaten auf Routing-Felder (Order-ID, Produktcode, Empfaenger-Land, ersten beiden Stellen der PLZ, Reference-Flag).- Bestehende Logs nutzen jetzt diese Helper.
- Neue statische Helper
app/Services/DhlDataHelper.php- Kein Roh-Dump des
$options-Arrays mehr. Statt dessen strukturiertes Log mitorder_id, Produktcode und Flags.
- Kein Roh-Dump des
packages/acme-laravel-dhl/src/Services/ShippingService.php- Neue private Helper
buildPayloadLogContext()undbuildResponseLogContext(). createLabel()-Log nutztDhlShipmentService::sanitizeOrderDataForLog().- Payload-Log enthaelt nur Produktcode, letzte vier Stellen der Abrechnungsnummer, Gewicht, Empfaenger-Land,
must_encode-Flag. - Response-Log enthaelt nur Sendungsnummer, Routing-Code, Status. Das base64-Label wird nicht mehr geloggt.
- Error-Branch verwendet ebenfalls den redaktierten Payload-Log.
- Neue private Helper
Tests:
tests/Unit/Dhl/DhlSanitizeLoggingTest.php(neu)- Stellt sicher, dass
api_key,password,api_secret,usernameund Abrechnungsnummern niemals im Log-Kontext landen. - Stellt sicher, dass Name, Strasse, Telefon, E-Mail und PLZ aus den Bestelldaten heraus normalisiert werden.
- Stellt sicher, dass
Prio 1.2 – CreateShipmentJob darf dhlConfig nicht serialisieren
Problem:
App\Jobs\CreateShipmentJobhat die DHL-Konfiguration im Konstruktor in einepublic $dhlConfig-Property geschrieben. Dadurch wurden API-Key, Basic-Auth-Passwort und Abrechnungsnummern beim Dispatch (perserialize()) in den Queue-Speicher (Redis/Datenbank) abgelegt.
Umsetzung:
app/Jobs/CreateShipmentJob.phppublic $dhlConfigentfernt.- Der Konstruktor akzeptiert den Parameter weiterhin (Backwards-Compat), persistiert ihn aber nicht mehr.
handle()laedt die DHL-Konfiguration jetzt ueberSettingController::getDhlConfig()direkt aus der kanonischen Quelle. Der Worker hat ohnehin Zugriff darauf.
Tests:
tests/Unit/Dhl/CreateShipmentJobSerializationTest.php(neu)- Serialisiert eine Job-Instanz und stellt sicher, dass keine der DHL-Secrets im Payload landen.
- Bestaetigt, dass es keine
dhlConfig-Property mehr gibt.
Prio 1.3 – IDOR im DHL-Versand-Modal verhindern
Problem:
ModalController::handleDhlShipmentModal()haengt amauth-Middleware, aber nicht amadmin-Guard. Jeder eingeloggte CRM-Nutzer konntePOST /modal/loadmitaction=create-dhl-shipmentund einer beliebigen Order-ID aufrufen und damit Empfaenger-Name, Adresse, E-Mail sowie bestehende Sendungen einer fremden Bestellung auslesen.
Umsetzung:
app/Http/Controllers/ModalController.php- Neue private Methode
authorizeDhlShipmentModal()prueftAuth::user()->isAdmin()und antwortet sonst mit403. - Wird vor
handleDhlShipmentModal()aufgerufen.
- Neue private Methode
Tests:
tests/Unit/Dhl/DhlModalAuthorizationTest.php(neu)- Gaeste, VIP-User (
admin == 1) und regulaere Berater (admin == 0) erhalten403. - Echte Admins (
admin >= 2) werden durchgelassen.
- Gaeste, VIP-User (
Prio 1.4 – XSS-Schutz im Tracking-Frontend und in DataTables
Problem:
- Im Backend-DataTable
DhlShipmentController::index()/list()wurdecustomer = firstname + ' ' + lastnameperaddColumnmit anschliessendemrawColumns(['customer', ...])ausgegeben. Manipulierte Adressdaten haetten HTML/JS injizieren koennen. - Im oeffentlichen Tracking-Frontend
resources/views/public/tracking.blade.phpwurdendata.tracking_status,data.tracking_number,data.last_tracked_atunddata.statusungefiltert in jQuery-.html()-Templates eingebaut.
Umsetzung:
app/Http/Controllers/DhlShipmentController.phpcustomer-Spalte ist jetzte(trim($firstname.' '.$lastname)).
resources/views/public/tracking.blade.php- Neue JS-Funktion
escapeTrackingHtml()(HTML-Entity-Encoding fuer&,<,>,",'). - Alle Felder aus der Tracking-Response werden vor dem Einsetzen damit escaped.
- Trackingnummer fuer den DHL-Link wird zusaetzlich mit
encodeURIComponent()geschuetzt. showError()nutzt.text()statt.html().getStatusBadge()escaped Status-Default und CSS-Klassen, sodass unbekannte DHL-Statuscodes nicht aus demclass-Attribut ausbrechen koennen.
- Neue JS-Funktion
Prio 1.5 – ReturnsService an DhlProductResolver anbinden, 'DEU'-Fallback entfernen
Problem:
ReturnsService::convertCountryCode()undconvertAddressFor2LetterCountry()hielten eine eigene Hardcoded-Liste an Laendercodes und fielen bei unbekannten Codes still auf'DEU'zurueck. Damit konnten Retouren fuer auslaendische Empfaenger versehentlich nach Deutschland geroutet werden und derDhlProductResolver(Single Source of Truth) wurde umgangen.
Umsetzung:
packages/acme-laravel-dhl/src/Services/ReturnsService.phpconvertCountryCode()delegiert anDhlProductResolver::toDhlCountryCode().convertAddressFor2LetterCountry()delegiert anDhlProductResolver::normalizeCountryCode().- Unbekannte Laender werfen jetzt eine
InvalidArgumentException, statt unbemerkt eine deutsche Retoure zu erzeugen.
app/Http/Controllers/DhlShipmentController.php- Alle
'DEU'-Defaults in den Retouren-Pfaden (getBillingAddressForReturn(),createReturnLabelSync()) durchDhlProductResolver::DOMESTIC_COUNTRY('DE') ersetzt, damit konsistente ISO-2-Codes an den Resolver gehen.
- Alle
Tests:
tests/Unit/Dhl/ReturnsServiceCountryCodeTest.php(neu)- Mappt
DE/AT/CH/ESkorrekt auf ISO-3. - Akzeptiert bereits korrekte ISO-3-Codes.
- Wirft fuer unbekannte Codes statt still
DEUzurueckzugeben. - Normalisiert Adressen zurueck auf ISO-2 und respektiert fehlende
country-Felder.
- Mappt
Prio 1.6 – houseNumber = '1'-Default in ShippingService::parseAddressFields entfernen
Problem:
- Wenn der Adressparser keine Hausnummer aus der Strasse extrahieren konnte, hat er die Hausnummer still auf
'1'gesetzt. Das fuehrte dazu, dass Pakete an die falsche Adresse zugestellt werden konnten, ohne dass der Operator etwas davon mitbekam.
Umsetzung:
packages/acme-laravel-dhl/src/Services/ShippingService.phpparseAddressFields()wirft jetzt eineInvalidArgumentExceptionmit klarer deutscher Fehlermeldung, sobald keine Hausnummer ermittelt werden konnte.- Das interne Erfolgs-Log enthaelt zusaetzlich nur noch Laengen / Praefixe, nicht mehr die volle Adresse.
Tests:
tests/Unit/Dhl/ShippingServiceParseAddressTest.php(neu)- Explizite Hausnummer wird unveraendert uebernommen.
- Kombiniertes Strassenfeld wird sauber zerlegt.
- Adresse ohne erkennbare Hausnummer fuehrt zu einer Validierungs-Exception.
- Leere Eingabe fuehrt nicht zur Exception (kein neues Verhalten).
Verifikation Phase 9
php artisan test --compact tests/Unit/Dhl tests/Feature/DhlApiCurlLoggingTest.php- Ergebnis: 75 Tests bestanden, 195 Assertions.
./vendor/bin/pint --dirty --format agent
Offene Punkte / weiterhin im Backlog
Die in Phase 9 noch offenen Backlog-Items wurden inzwischen in Phase 10 abgearbeitet (siehe dort).
Phase 10 - Backlog-Aufraeumen (2026-05-27)
Status: umgesetzt am 2026-05-27 direkt im Anschluss an Phase 9.
Prio 2.1 - Legacy DhlApiService entfernen
Problem:
app/Services/DhlApiService.php(1311 Zeilen) war ein veralteter SOAP-/SDK-basierter Service. Er referenzierte ein nicht existierendes ModelApp\Models\DhlShipmentund das nicht installierte SDKchristophschaeffer/dhl-business-shipping. Die Klasse wurde vom Autoloader geladen, war aber komplett tot (keine Aufrufer inapp/,routes/,resources/,tests/,packages/). Jeder versehentliche Aufruf haette zur Laufzeit zuClass not foundgefuehrt.
Umsetzung:
app/Services/DhlApiService.phpersatzlos geloescht.
Verifikation:
grep -r DhlApiService app/ routes/ resources/ packages/ tests/ config/→ keine Treffer mehr.- Verbleibende Vorkommen ausschliesslich in
dev/2026-05-13-dhl-modul/legacy/...(historische Markdowns) unddev/app-bak/(Backup-Verzeichnis).
Prio 2.2 - Request-/Prozess-Caching fuer getDhlConfig()
Problem:
SettingController::getDhlConfig()baute die DHL-Konfiguration bei jedem Aufruf von Grund auf neu auf und holte dabei rund 25 Werte einzeln perSetting::getContentBySlug()aus der Datenbank. Pro DHL-Vorgang gibt es mehrere Aufrufer (DhlShipmentService,CreateShipmentJob,DhlShipmentControllerfuer Cancel/Return, …), so dass schnell 50-100 redundanteSELECT ... FROM settings-Queries pro Request entstanden.
Umsetzung:
app/Http/Controllers/SettingController.php- Neue statische Property
private static ?array $cachedDhlConfig = nullals prozessweiter In-Memory-Cache. getDhlConfig()liefert beim zweiten Aufruf direkt aus dem Cache und triggert keine DB-Queries mehr.- Neue oeffentliche Methode
flushDhlConfigCache()setzt den Cache zurueck. Sie wird automatisch instore()undupdateDhlConfigCache()aufgerufen, sodass nach einer Settings-Aenderung der naechste Aufruf wieder frische Werte liefert.
- Neue statische Property
Tests:
tests/Unit/Dhl/DhlConfigCachingTest.php(neu)- Verifiziert, dass
getDhlConfig()einen vorbefuellten Cache zurueckgibt, ohne die Datenbank zu beruehren. - Verifiziert, dass
flushDhlConfigCache()den Cache zuverlaessig leert.
- Verifiziert, dass
Prio 2.3 - Doppelte public.tracking-Route bereinigen
Problem:
routes/domains/crm.phpregistrierteGET /admin/dhl/public/trackmit dem Namenpublic.trackinginnerhalb deradmin-Middleware-Gruppe. Dadurch war die Route in Wahrheit nicht oeffentlich, sondern admin-only. Gleichzeitig existiert inroutes/domains/main.phpdie korrekte oeffentliche RouteGET /trackingebenfalls unter dem Namenpublic.tracking. Da pro Request immer nur eine Domain-Routedatei geladen wird, gab es keinen direkten Konflikt, aber auf der CRM-Subdomain zeigte der Routenname auf den falschen Endpunkt.
Umsetzung:
routes/domains/crm.php- Die
public.tracking-Definition wurde entfernt und durch einen erklaerenden Kommentar ersetzt.
- Die
resources/views/admin/dhl/show.blade.php- Der bisher einzige Verbraucher (
route('public.tracking')im aktuell deaktivierten@if(false)-Block) zeigt jetzt absolut auf die Main-Domain via\App\Domain\EarlyDomainParser::getMainUrl().'/tracking'. Die Trackingnummer wird zusaetzlich miturlencode()geschuetzt.
- Der bisher einzige Verbraucher (
Tests:
tests/Unit/Dhl/DhlRouteRegistrationTest.php(neu)- Stellt sicher, dass im CRM-Routing kein
Route::...->name('public.tracking')-Eintrag mehr existiert. - Stellt sicher, dass die Main-Domain weiterhin
GET /trackingalspublic.trackingregistriert.
- Stellt sicher, dass im CRM-Routing kein
Verifikation Phase 10
php artisan test --compact tests/Unit/Dhl tests/Feature/DhlApiCurlLoggingTest.php- Ergebnis: 79 Tests bestanden, 201 Assertions.
./vendor/bin/pint --dirty --format agent
Verbleibendes Backlog
updateDhlConfigCache()ruft weiterhin\Artisan::call('config:clear')auf. Das ist global und kann bei gecachter Konfiguration zu kurzfristiger Latenz fuehren - akzeptabel, weil das Setting-Update ein seltener Admin-Vorgang ist.dev/app-bak/Services/DhlApiService.php(Backup-Kopie ausserhalb des Autoloaders) bleibt als historische Referenz erhalten.
Phase 11 - Tracking-Hardening (Bugfix Live, 2026-05-27)
Status: umgesetzt am 2026-05-27.
Ausloeser: Im Live-System lieferte das Cockpit beim Klick auf "Tracking aktualisieren" die irrefuehrende Meldung Sendung nicht gefunden oder noch nicht im System erfasst. HTTP Status: 401, obwohl die Sendung auf der DHL-Website laengst zugestellt war. Gleichzeitig zeigte das Cockpit einen frischen last_tracked_at-Zeitstempel mit altem tracking_status-Text - typisches Symptom eines schon laenger fehlschlagenden Trackings.
Wurzelursachen
- Phantom-Fallback-Endpoint:
DhlTrackingService::trackShipmentDE()riefhttps://api-eu.dhl.com/parcel/de/tracking/v0/shipmentsauf. Dieser Endpoint existiert in der offiziellen DHL-Doku nicht (siehehttps://developer.dhl.com/api-reference/shipment-tracking). Es gibt nur die "Shipment Tracking - Unified API" unterhttps://api-eu.dhl.com/track/shipments. Der Fallback produzierte daher zwangslaeufig 401/404. - Falsche User-Message bei 401: Eine
401 Unauthorized-Antwort wurde dem Operator als "Sendung nicht gefunden" gezeigt. Der eigentliche Fehler (DHL-API-Key ohne Tracking-Subscription im DHL Developer Portal) blieb verborgen. - Stale-Daten erscheinen frisch: Sowohl
updateTrackingSync()als auchupdateTrackingBatch()setztenlast_tracked_at = now()selbst dann, wenn die Tracking-Antwort fehlschlug. Damit wirkte ein veralteter Status im Cockpit "gerade eben aktualisiert". - Per-Shipment-Fallback verbrennt Quota: Bei einem Batch-Auth-Fehler ist der Code in eine Schleife mit
updateTracking()pro Sendung gegangen, was den 401 mitNFolgeaufrufen multipliziert. - Tote
api_secret-Property: Wurde im Konstruktor geladen aber nirgends im Request verwendet (Tracking-Unified-API kennt kein OAuth/Basic).
Umsetzung
app/Services/DhlTrackingService.php- Neue Konstante
TRACKING_ENDPOINT = 'https://api-eu.dhl.com/track/shipments'. trackShipmentDE()ersatzlos entfernt;trackShipment()hat keinen Fallback mehr.- Neue private Helper
buildHttpOptions(),processSingleShipmentResponse(),isAuthErrorStatus(),buildAuthErrorMessage(),redactApiKey(). - 401/403 werden explizit als
auth_error => trueplushttp_statuszurueckgegeben, mit klarer User-Message: "DHL Tracking API: Authentifizierung fehlgeschlagen (HTTP 401). Bitte pruefen, ob der hinterlegte DHL-API-Key gueltig ist und im DHL Developer Portal fuer 'Shipment Tracking - Unified' freigeschaltet wurde.". - 404 oder leere Sendungsliste werden als
not_found => truemarkiert (eindeutig getrennt von Auth-Fehlern). trackMultipleShipments()nutzt jetzt denselben gemeinsamen Pfad und liefert ebenfalls strukturierteauth_error-Antworten.updateTrackingSync()setztlast_tracked_atnur noch bei Erfolg oder bei explizitemnot_found. Auth- und Transport-Fehler lassen den Zeitstempel unangetastet, damit der naechste Cron-Lauf erneut versucht und das Cockpit den fehlschlagenden Zustand sichtbar macht.updateTrackingBatch()bricht beim ersten Auth-Fehler ab (kein Per-Shipment-Fallback, kein erneutes Tracking) und markiert alle Sendungen der laufenden Charge mitauth_error. Transport-Exceptions fuehren ebenfalls nicht mehr zu einemlast_tracked_at-Update.- Properties
apiSecretundisSandboxentfernt. - Diagnose-Logs enthalten jetzt Endpoint, Status-Code und ein redaktiertes API-Key-Suffix (
***<letzte 4>), aber niemals den vollen Key.
- Neue Konstante
Tests
tests/Unit/Dhl/DhlTrackingAuthErrorTest.php(neu, 6 Tests)- HTTP 401 / 403 fuehrt zu
auth_error => truemit klarer Message. - Es wird kein Fallback-Request mehr an
/parcel/de/tracking/...gesendet. - 404 / leere Sendungsliste werden eindeutig als
not_foundzurueckgegeben. - Multi-Tracking liefert ebenfalls strukturierte
auth_error-Antworten. - Das Auth-Fehler-Log enthaelt den vollen API-Key nicht (nur Suffix).
- HTTP 401 / 403 fuehrt zu
tests/Unit/Dhl/DhlTrackingStaleProtectionTest.php(neu, 3 Tests)updateTracking()ruft bei 401 kein$shipment->update(...)auf -last_tracked_atbleibt also fuer den naechsten Cron-Versuch alt.updateTracking()setzt bei "not found" gezielt nurlast_tracked_at, damit nicht sofort erneut probiert wird.updateTrackingBatch()bricht bei einem Auth-Fehler nach dem ersten Versuch ab (Http::assertSentCount(1)) und markiert alle drei Sendungen der Charge alsauth_error => true.
Verifikation Phase 11
php artisan test --compact tests/Unit/Dhl tests/Feature/DhlApiCurlLoggingTest.php- Ergebnis: 88 Tests bestanden, 263 Assertions (vorher Phase 10: 79 Tests, 201 Assertions).
./vendor/bin/pint --dirty --format agent
Operatives Vorgehen bei einem 401 im Live
Der Code kann das Problem nur sichtbar machen und nicht beheben - die eigentliche Loesung erfolgt im DHL Developer Portal:
- DHL Developer Portal → My Apps → die produktive App oeffnen.
- Unter "APIs" pruefen, ob Shipment Tracking - Unified API aktiv und genehmigt ist. Falls nicht: hinzufuegen und Production-Freischaltung beantragen (manuelle DHL-Approval).
- Den
DHL_API_KEY(Consumer Key) aus dem Portal mit dem Wert in den Einstellungen /.envabgleichen. - Nach Korrektur einmal Tracking aktualisieren im Cockpit anstossen; der
last_tracked_at-Zeitstempel wird jetzt nur noch bei tatsaechlich erfolgreicher Antwort frisch gesetzt.
Phase 12 - 429-Handling und lokale Quota-Pause (Bugfix Live, 2026-05-27)
Status: umgesetzt am 2026-05-27 (Folgebefund zu Phase 11).
Ausloeser: Trotz Phase 11 zeigte das Live-System weiterhin veraltete Tracking-Stati. Konkretes Beispiel: Sendung 5082 (Trackingnummer 00340435065133094790) blieb seit 2026-05-27 05:00:33 auf "in der Region des Empfaengers angekommen", obwohl DHL bereits um 13:41 Uhr "Zustellung erfolgreich" meldete. Quer ueber alle 381 aktiven Sendungen war MAX(last_tracked_at) = 06:00:35 - der stuendliche Cron dhl:update-tracking lieferte ab 07:00 Uhr ueber 11 Stunden keine Updates mehr.
Wurzelursachen (revidiert)
-
DHL-Tageslimit von 250 Aufrufen pro Tag erschoepft. Laut
developer.dhl.com/api-reference/shipment-trackingbekommt jede Standard-App initial 250 calls per day, with a maximum of 1 call every 5 seconds. Direkter Test gegen den Live-Endpunkt belegte das:curl https://api-eu.dhl.com/track/shipments?trackingNumber=...lieferteHTTP 429mit Body{"status":429,"title":"Too Many Requests","detail":"Too many requests within defined time period, please try again later."}. Der Antrag auf eine hoehere Quota muss im DHL Developer Portal gestellt werden.Hinweis zur ersten Diagnose: Wir hatten zunaechst vermutet, der Live-
DHL_API_KEYsei der oeffentliche Sandbox-Demo-Key aus der DHL-Doku, weil der Wert mit den im Repo eingecheckten Sandbox-Beispielen uebereinstimmte (tests/Feature/DhlApiCurlLoggingTest.php,tests/DHL/curl-trace.txt). Diese Annahme war falsch - der Wert in der Live-.envist ein echter produktiver Key, er hat nur das Standard-Tageslimit. Die fruehere Operator-Message mit dem Hinweis "Sandbox-Demo-Key" wurde entfernt. -
429 wurde wie ein generischer Fehler behandelt: Der Code aus Phase 11 erkannte 401/403 als
auth_errorund 404 alsnot_found, aber 429 fiel in den Pfad "Fehler beim Abrufen der Tracking-Informationen. HTTP 429". Im Batch fuehrte das zum Per-Shipment-Fallback, der pro Sendung einen weiteren 429-Aufruf abgesetzt hat - die schon erschoepfte Quota wurde so noch schneller weiter belastet und der Cron-Lauf dauerte unnoetig lange. -
Folgelaeufe verbrennen Quota nur um 429 erneut zu sehen: Solange DHL noch im selben Quota-Fenster mit 429 antwortet, kostet jeder hourly-Cron mindestens einen HTTP-Roundtrip. Bei knapper Quota ist das ein Selbstlaeufer, der den Recovery-Moment nach dem Tages-Reset spuerbar verzoegert.
Umsetzung
-
app/Services/DhlTrackingService.phpprocessSingleShipmentResponse()erkenntHTTP 429explizit und liefertrate_limited => trueplus optionalretry_after(Sekunden). Die User-Message lautet jetzt sachlich: "DHL Tracking API: Tageslimit erreicht (HTTP 429). Die DHL-API liefert vorruebergehend keine Daten mehr. Standard-Apps haben laut DHL-Doku 250 Aufrufe pro Tag und max. 1 Aufruf alle 5 Sekunden. Bei Bedarf im DHL Developer Portal eine Quota-Erhoehung beantragen." Der frueher hier eingefuegte "Sandbox-Demo-Key"-Hinweis ist entfernt.trackMultipleShipments()erkennt 429 ebenfalls und liefert dieselbe strukturierte Antwort fuer die Batch-Route.updateTrackingBatch()bekommt einen eigenenrate_limited-Pfad analog zuauth_error: Beim ersten 429 wird die gesamte Charge sofort alsrate_limitedmarkiert und der Cron-Lauf abgebrochen, ohne Per-Shipment-Fallback.last_tracked_atbleibt unangetastet, damit der naechste planmaessige Cron-Lauf die gleichen Sendungen erneut versucht (statt sie mit frischem Zeitstempel und altem Status zu konservieren).- Auch im "Sonstiger Batch-Fehler"-Fallback wird ein
rate_limited-Befund pro Sendung als Abbruchgrund erkannt - die Schleife bricht sofort ab, ebenso wie schon beiauth_error. - Neuer Helper
extractRetryAfter()interpretiert sowohl ganzzahlige Sekunden als auch RFC-7231-HTTP-date-Werte aus demRetry-After-Header. - Logging in
[DHL Tracking Service] Unified API rate-limited/Multi tracking rate-limited/Batch tracking aborted due to rate-limitenthaelt Endpoint, HTTP-Status, ein redaktiertes API-Key-Suffix (***<letzte 4>) und - falls von DHL geliefert - dieRetry-After-Sekunden.
-
Lokale Quota-Pause (neu in Phase 12.1)
- Neuer Cache-Key
dhl_tracking:quota_paused_untilhaelt einen absoluten "do not call DHL before"-Zeitstempel. Default-Pause: 1 Stunde, wenn DHL keinenRetry-After-Header sendet; andernfalls genau die signalisierte Wartezeit. - Statische Helper auf
DhlTrackingService:isQuotaPaused(),getQuotaPausedUntil(),pauseQuota(?int $retryAfterSeconds = null),clearQuotaPause(). trackShipment(),trackMultipleShipments()undupdateTrackingBatch()pruefen die Pause vor jedem HTTP-Roundtrip. Ist sie aktiv, wird sofort einerate_limited => true-Antwort mitpaused_untilzurueckgegeben - kein API-Aufruf. Damit kostet ein Cron-Lauf in einem ausgeschoepften Quota-Fenster 0 Calls statt 10+ und beschleunigt den Recovery-Moment nach Quota-Reset.- Beim ersten echten 429-Response wird
pauseQuota($retryAfter)aufgerufen, sodass ein einzelner Cron-Lauf maximal einen "Quota-Probierschuss" pro Stunde absetzt.
- Neuer Cache-Key
Tests
tests/Unit/Dhl/DhlTrackingRateLimitTest.php(neu, 9 Tests)- 429-Single-Tracking-Response: Antwort ist
rate_limited => true, enthaelthttp_status = 429,retry_after = 120und eine deutschsprachige Operator-Message mit DHL-Doku-konformem Hinweis (Tageslimit erreicht,250 Aufrufe pro Tag,Quota-Erhoehung). Der frueher gepruefte String "Sandbox-Demo-Key" darf explizit nicht mehr vorkommen. - Multi-Tracking liefert dieselbe strukturierte 429-Antwort.
updateTrackingBatch()bricht nach genau einem HTTP-Aufruf ab, markiert die Charge alsrate_limitedund ruft$shipment->update(...)nicht auf.- HTTP-date als
Retry-Afterwird korrekt in Sekunden umgerechnet. - Im Rate-Limit-Log taucht der volle API-Key nicht auf, nur das
***<suffix>. - Quota-Pause aktiviert sich aus dem
Retry-After-Header und der zweitetrackShipment()-Aufruf sendet keinen HTTP-Request mehr (Http::assertSentCount(1)). - Default-Pause ohne
Retry-After-Header dauert ~1 Stunde (mit Toleranz fuer Test-Drift). updateTrackingBatch()ueberspringt 25 Sendungen vollstaendig, wenn die Pause bereits gecached ist (Http::assertNothingSent()).clearQuotaPause()raeumt den Cache-Eintrag wieder auf - wichtig fuer Tests und fuer Operator-Eingriffe.
- 429-Single-Tracking-Response: Antwort ist
Verifikation Phase 12
php artisan test --compact tests/Unit/Dhl- Ergebnis: 96 Tests bestanden, 406 Assertions (vorher Phase 11: 88 Tests, 263 Assertions).
./vendor/bin/pint --dirty --format agent
Operatives Vorgehen bei einem 429 im Live
Der Code-Fix verhindert, dass aus einer erschoepften Tracking-Quota ein mehrstuendiger Datenstau wird, das Cockpit veraltete Stati als "gerade aktualisiert" anzeigt oder die hourly-Crons die Recovery selbst weiter verzoegern. Die eigentliche Loesung liegt aber im DHL-Account:
- DHL Developer Portal -> My Apps -> produktive App oeffnen.
- Pruefen, ob Shipment Tracking - Unified API in der App als Production approved ist (nicht nur Sandbox). Standard-Quota: 250 Aufrufe pro Tag, max. 1 Aufruf alle 5 Sekunden.
- Im DHL Developer Portal eine Quota-Erhoehung beantragen (Antrag-Formular im API-Detailbereich). Solange das offen ist: Hourly-Cron mit Status-basierten Intervallen einplanen, dazu siehe "Empfehlung Sendungsvolumen vs. Quota" unten.
- Cron-Lauf beobachten: Nach dem ersten 429 bleibt der Cron fuer 1 Stunde lokal in einer Quota-Pause, ohne weitere DHL-Aufrufe. Der naechste
dhl:update-trackingversucht es dann erneut. Sobald die Quota frei wird (Production-Erhoehung oder Tages-Reset), nimmt der Cron die Sendungen automatisch wieder mit. - Falls Operator manuell sofort entsperren moechte:
php artisan tinker->App\Services\DhlTrackingService::clearQuotaPause().
Empfehlung Sendungsvolumen vs. Quota
Aktuell sind ~381 Sendungen aktiv (status IN ('created','in_transit','out_for_delivery','exception','unknown')). Die DHL-Standardquota von 250 Calls/Tag reicht dafuer mathematisch nicht aus, sobald jede Sendung mehrmals pro Tag aktualisiert werden soll. Solange keine erhoehte Quota bewilligt ist, sind drei Stellschrauben sinnvoll - Reihenfolge nach Wirkung:
--stale-daysenger setzen (z.B. 14 statt 30). Sendungen mit Statuscreated, die seit zwei Wochen keinen Fortschritt zeigen, sind in der Praxis nicht zustellbar oder Karteileichen.markStaleShipmentsCompleted()schliesst sie automatisch ab und reduziert die Cron-Last spuerbar (in der aktuellen DB sind 266 von 381 aktiven Sendungen im Statuscreated).- Status-Intervalle verlaengern (
DhlShipment::TRACKING_INTERVALS). Vorschlag fuer 250/Tag:out_for_delivery = 1h,in_transit = 6h,exception = 8h,unknown = 12h,created = 24h. Das passt grob in die Quota und behaelt schnelle Reaktion fuer die "letzte Meile". - Vorhandener "Batch"-Pfad ist faktisch keine Ersparnis: Die DHL Unified Tracking API kennt laut Doku nur den Singular-Parameter
trackingNumber.trackMultipleShipments()sendet?trackingNumber=A,B,C,...- das wird als eine einzige unbekannte Sendungs-ID behandelt, schickt den Lauf in den Per-Shipment-Fallback und kostet einen Call extra pro Chunk. Wenn die Quota verlaesslich erhoeht ist, sollte dieser Pfad entweder gegen einzelne Calls mit 5-Sekunden-Throttling ersetzt oder ganz entfernt werden. Bewusst noch nicht angefasst, weil das Refactoring umfangreich ist - separate Phase 13 wenn gewuenscht.
Verbleibende Beobachtungen
- Eine zusaetzliche Beobachtung (Monitoring) waere sinnvoll: ein Alarm wenn
MAX(last_tracked_at)quer ueber alle aktiven Sendungen aelter als z.B. 3 Stunden ist. Das macht eine erschoepfte Quota oder einen haengenden Cron sofort sichtbar, ohne dass jemand einzelne Sendungen manuell prueft. - Default-Pause von 1 Stunde ist eine sinnvolle Heuristik, weil DHL kein verlaessliches
Retry-Aftermitsendet. Falls die echte Quota-Reset-Zeit bekannt wird, koennte man die Default-Pause dynamisch bis zum naechsten Reset stellen.
Phase 13 - Echtes Single-Tracking + Quota-vertraegliche Intervalle (2026-05-27)
Status: umgesetzt am 2026-05-27 (Folge zu Phase 12 - die Quota-Erhoehung auf 10.000 calls/day wurde im DHL Developer Portal beantragt, kann aber nach Erfahrung mehrere Wochen brauchen. Bis dahin muss das System mit den 250/day-Standardlimits sauber funktionieren).
Ausloeser
Bei der Aufarbeitung von Phase 12 sind zwei strukturelle Schwachstellen aufgefallen:
trackMultipleShipments()war kein echter Batch-Aufruf. Die DHL Unified Tracking API kennt laut Doku nur den Singular-ParametertrackingNumber. Der Code sendete?trackingNumber=A,B,C,...,Jals eine kommaseparierte Liste - DHL interpretierte das als eine unbekannte Sendungs-ID, lieferte ein leeresshipments-Array zurueck und der Code fiel in den Per-Shipment-Fallback. Effekt: ein verschwendeter Aufruf pro Chunk plus die zehn echten Aufrufe danach.- Status-Intervalle aus Vor-Audit-Zeit:
in_transit = 2hundcreated = 6hwaren so eng, dass selbst mit perfekt funktionierender API ca. 2.500 Aufrufe pro Tag nötig waeren - rund das 10-fache des dokumentierten Standardlimits von 250 calls/day.
Umsetzung
-
app/Services/DhlTrackingService.phptrackMultipleShipments()komplett entfernt. Damit existiert nur noch der Single-Tracking-PfadtrackShipment(), der einen einzelnentrackingNumberan DHL sendet. Ein neuer Unit-Test (it does not have a trackMultipleShipments method anymore) friert diese Entfernung als Vertrag ein.- Neue statische Property
$callIntervalSeconds = 5plus Setter/GettersetCallIntervalSeconds()/getCallIntervalSeconds(). Voreinstellung folgt der DHL-Doku ("max 1 call every 5 seconds"). In Tests wird der Wert auf0gesetzt, damit die Suite nicht real schlaeft. updateTrackingBatch()neu geschrieben: Anstelle der Chunks von 10 mit Pseudo-Batch-API laeuft jetzt eine echte HTTP-Anfrage pro Sendung, dazwischensleep($callIntervalSeconds). Abbruchbedingungen unveraendert: Quota-Pause vor dem ersten Call,auth_erroroderrate_limitedbrechen die Schleife sofort ab, alle restlichen Sendungen werden ohne weiteren API-Aufruf uebersprungen. Bei einem Transport-Fehler (DNS/TLS) wird die einzelne Sendung alstransport_errormarkiert; der Loop laeuft weiter, weil das ein vorruebergehendes Netzproblem sein kann.- Das Cron-Log enthaelt jetzt zusaetzlich die Felder
http_callsundcall_interval_seconds, sodass Quota- und Throttle-Verhalten direkt nachvollziehbar sind.
-
packages/acme-laravel-dhl/src/Models/DhlShipment.phpTRACKING_INTERVALSangehoben:out_for_delivery: 1 h (unveraendert - kundenrelevant, betrifft nur sehr wenige Sendungen gleichzeitig)in_transit: 2 h -> 6 hexception: 4 h -> 8 hunknown: 4 h -> 12 hcreated: 6 h -> 24 h
DEFAULT_TRACKING_INTERVAL: 4 h -> 8 h.- Indikative Rechnung mit aktuellem Live-Bestand (266
created+ 115in_transit):created1x/Tag = 266 calls/Tagin_transit4x/Tag = 460 calls/Tag- Summe ~726 calls/Tag bei vollstaendigem Throughput - reicht fuer 10.000/Tag-Quota dreifach, aktiviert bei 250/Tag-Standardquota nach ca. 5 Stunden die Quota-Pause aus Phase 12. Da bleibt der Tracking-Status fuer alle Sendungen ohne
out_for_deliverymindestens 1x/Tag aktuell - das ist mit Standard-Quota verkraftbar.
Tests
tests/Unit/Dhl/DhlTrackingBatchThrottleTest.php(neu, 4 Tests)- 3 Sendungen erzeugen genau 3 HTTP-Calls, jede mit einzelner
trackingNumber(kein Komma in den Query-Params). setCallIntervalSeconds(1)-> 3 Sendungen brauchen >= 2 s Wall-Clock (sleep(1)zwischen Call 1->2 und 2->3).getCallIntervalSeconds()Round-Trip plus-99wird auf0geklemmt.- Vertragstest:
DhlTrackingService::trackMultipleShipmentsexistiert nicht mehr.
- 3 Sendungen erzeugen genau 3 HTTP-Calls, jede mit einzelner
tests/Unit/Dhl/DhlTrackingStaleProtectionTest.php(angepasst)- Der "Batch bricht beim ersten Auth-Fehler ab"-Test pruef jetzt
failed = 1statt3: in der neuen Logik wird nur die erste Sendung tatsaechlich angefragt, der Rest gar nicht erst gestartet. Das ist quotaschonender als das alte "Charge komplett als auth_error markieren"-Verhalten.
- Der "Batch bricht beim ersten Auth-Fehler ab"-Test pruef jetzt
tests/Unit/Dhl/DhlTrackingRateLimitTest.php(angepasst)- Der frueher gegen
trackMultipleShipments()gerichtete Test "marks the multi-tracking call as rate_limited too" ist ersatzlos entfernt, weil die Methode nicht mehr existiert. - Der 429-Batch-Test pruefe jetzt analog
failed = 1mit nur einem HTTP-Call - die anderen 11 Sendungen werden uebersprungen.
- Der frueher gegen
tests/Unit/Dhl/DhlTrackingAuthErrorTest.php(angepasst)- Der frueher direkte
trackMultipleShipments()-Test ist entfernt, weil die Methode nicht mehr existiert.
- Der frueher direkte
Verifikation Phase 13
php artisan test --compact tests/Unit/Dhl- Ergebnis: 98 Tests bestanden, 398 Assertions.
./vendor/bin/pint --dirty --format agent
Operatives Vorgehen
- Solange Standard-Quota (250/day) aktiv ist: Mit den neuen Intervallen erreicht das System ein realistisches Limit, bevor die Quota-Pause aus Phase 12 zuschlaegt.
out_for_delivery-Sendungen werden bevorzugt aktualisiert, der Rest folgt nach. - Sobald die beantragte 10.000/day-Quota durchgeht: Keine Code-Anpassung noetig. Die Intervalle sind so gewaehlt, dass auch der naechste Bestandszuwachs (mehr Sendungen) noch passt; bei sehr starkem Wachstum koennten
in_transitwieder auf 4 h gesetzt werden. - Throttle bei eigenen Service Level: Wer einen vertraglich erweiterten Service Level mit > 1 call/sec hat, kann das im Bootstrap setzen, z.B.
DhlTrackingService::setCallIntervalSeconds(2);. Default bleibt 5 s, weil das den Standard-Service-Levels und der Doku entspricht.
Verbleibende Empfehlung
--stale-daysim Cron auf 14 setzen, sobald sicher ist, dass keine legitime Bestellung > 2 Wochen im Statuscreatedbleibt. Das reduziert den getrackten Sendungspool spuerbar (266 von 381 aktiven Sendungen sind aktuellcreated, viele davon sind reine Karteileichen, deren aelteste seit2026-04-29 03:00keine Aenderung mehr hatten). Bewusst noch nicht durchgefuehrt, weil das eine Geschaeftsentscheidung ist - der aktuelle Default bleibt bei--stale-days=30.
Legacy-Dokumentation
Die bisherigen Markdown-Dateien wurden nach dev/dhl-modul/legacy verschoben. Sie bleiben als Historie erhalten, sind aber nicht mehr die aktuelle Arbeitsgrundlage.