20-02-2026
This commit is contained in:
parent
a8b395e20d
commit
a00c42e770
252 changed files with 28785 additions and 8907 deletions
|
|
@ -3,9 +3,11 @@
|
|||
namespace App\Services;
|
||||
|
||||
use Acme\Dhl\Models\DhlShipment;
|
||||
use Acme\Dhl\Models\DhlTrackingEvent;
|
||||
use App\Http\Controllers\SettingController;
|
||||
use App\Jobs\TrackShipmentJob;
|
||||
use Exception;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
|
|
@ -42,6 +44,7 @@ class DhlTrackingService
|
|||
Log::info('[DHL Tracking Service] Tracking shipment with Unified API', [
|
||||
'tracking_number' => $trackingNumber,
|
||||
'is_sandbox' => $this->isSandbox,
|
||||
'has_api_key' => ! empty($this->apiKey),
|
||||
]);
|
||||
|
||||
$response = Http::withHeaders([
|
||||
|
|
@ -60,16 +63,21 @@ class DhlTrackingService
|
|||
CURLOPT_CONNECTTIMEOUT => config('dhl.ssl.connect_timeout', 10),
|
||||
CURLOPT_TIMEOUT => config('dhl.ssl.timeout', 30),
|
||||
CURLOPT_USERAGENT => 'acme-laravel-dhl/1.0',
|
||||
]
|
||||
],
|
||||
])
|
||||
->get('https://api.dhl.com/track/shipments', [
|
||||
->get('https://api-eu.dhl.com/track/shipments', [
|
||||
'trackingNumber' => $trackingNumber,
|
||||
'service' => 'express,parcel',
|
||||
'requesterCountryCode' => 'DE',
|
||||
'originCountryCode' => 'DE',
|
||||
'language' => 'de',
|
||||
]);
|
||||
|
||||
Log::info('[DHL Tracking Service] Unified API response', [
|
||||
'tracking_number' => $trackingNumber,
|
||||
'status_code' => $response->status(),
|
||||
'successful' => $response->successful(),
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
|
||||
|
|
@ -80,8 +88,8 @@ class DhlTrackingService
|
|||
'success' => true,
|
||||
'tracking_number' => $shipment['id'],
|
||||
'status' => $shipment['status']['statusCode'] ?? 'unknown',
|
||||
'status_text' => $shipment['status']['status'] ?? 'Unbekannt',
|
||||
'description' => $shipment['status']['description'] ?? '',
|
||||
'status_text' => $shipment['status']['description'] ?? ($shipment['status']['status'] ?? 'Unbekannt'),
|
||||
'description' => $shipment['status']['remark'] ?? ($shipment['status']['description'] ?? ''),
|
||||
'last_update' => $shipment['status']['timestamp'] ?? null,
|
||||
'origin' => $shipment['origin']['address']['addressLocality'] ?? null,
|
||||
'destination' => $shipment['destination']['address']['addressLocality'] ?? null,
|
||||
|
|
@ -91,6 +99,12 @@ class DhlTrackingService
|
|||
}
|
||||
}
|
||||
|
||||
Log::warning('[DHL Tracking Service] Unified API did not find shipment, trying Parcel DE API', [
|
||||
'tracking_number' => $trackingNumber,
|
||||
'status_code' => $response->status(),
|
||||
'response_snippet' => mb_substr($response->body(), 0, 500),
|
||||
]);
|
||||
|
||||
// If Unified API fails, try Parcel DE API
|
||||
return $this->trackShipmentDE($trackingNumber, $options);
|
||||
} catch (Exception $e) {
|
||||
|
|
@ -113,13 +127,14 @@ class DhlTrackingService
|
|||
Log::info('[DHL Tracking Service] Tracking shipment with Parcel DE API', [
|
||||
'tracking_number' => $trackingNumber,
|
||||
'is_sandbox' => $this->isSandbox,
|
||||
'has_api_key' => ! empty($this->apiKey),
|
||||
'has_api_secret' => ! empty($this->apiSecret),
|
||||
]);
|
||||
|
||||
$response = Http::withBasicAuth($this->apiKey, $this->apiSecret)
|
||||
->withHeaders([
|
||||
'Accept' => 'application/json',
|
||||
'dhl-api-key' => $this->apiKey,
|
||||
])
|
||||
$response = Http::withHeaders([
|
||||
'DHL-API-Key' => $this->apiKey,
|
||||
'Accept' => 'application/json',
|
||||
])
|
||||
->withOptions([
|
||||
'verify' => config('dhl.ssl.verify_peer', true),
|
||||
'http_errors' => false,
|
||||
|
|
@ -132,13 +147,20 @@ class DhlTrackingService
|
|||
CURLOPT_CONNECTTIMEOUT => config('dhl.ssl.connect_timeout', 10),
|
||||
CURLOPT_TIMEOUT => config('dhl.ssl.timeout', 30),
|
||||
CURLOPT_USERAGENT => 'acme-laravel-dhl/1.0',
|
||||
]
|
||||
],
|
||||
])
|
||||
->get('https://api.dhl.com/parcel/de/tracking/v1/shipments', [
|
||||
'trackingNumber' => $trackingNumber,
|
||||
->get('https://api-eu.dhl.com/parcel/de/tracking/v0/shipments', [
|
||||
'shipmentId' => $trackingNumber,
|
||||
'language' => 'de',
|
||||
]);
|
||||
|
||||
Log::info('[DHL Tracking Service] Parcel DE API response', [
|
||||
'tracking_number' => $trackingNumber,
|
||||
'status_code' => $response->status(),
|
||||
'successful' => $response->successful(),
|
||||
'response_body' => $response->body(),
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
|
||||
|
|
@ -158,21 +180,33 @@ class DhlTrackingService
|
|||
}
|
||||
}
|
||||
|
||||
// Log detailed error information
|
||||
Log::warning('[DHL Tracking Service] Shipment not found or not yet tracked', [
|
||||
'tracking_number' => $trackingNumber,
|
||||
'status_code' => $response->status(),
|
||||
'response_body' => $response->body(),
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Sendung nicht gefunden oder noch nicht im System erfasst.',
|
||||
'message' => 'Sendung nicht gefunden oder noch nicht im System erfasst. HTTP Status: '.$response->status(),
|
||||
'tracking_number' => $trackingNumber,
|
||||
'api_used' => 'parcel_de',
|
||||
'debug_info' => [
|
||||
'status_code' => $response->status(),
|
||||
'response' => $response->json(),
|
||||
],
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
Log::error('[DHL Tracking Service] Parcel DE API failed', [
|
||||
'tracking_number' => $trackingNumber,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Fehler beim Abrufen der Tracking-Informationen: ' . $e->getMessage(),
|
||||
'message' => 'Fehler beim Abrufen der Tracking-Informationen: '.$e->getMessage(),
|
||||
'tracking_number' => $trackingNumber,
|
||||
'api_used' => 'parcel_de',
|
||||
];
|
||||
|
|
@ -208,11 +242,10 @@ class DhlTrackingService
|
|||
CURLOPT_CONNECTTIMEOUT => config('dhl.ssl.connect_timeout', 10),
|
||||
CURLOPT_TIMEOUT => config('dhl.ssl.timeout', 30),
|
||||
CURLOPT_USERAGENT => 'acme-laravel-dhl/1.0',
|
||||
]
|
||||
],
|
||||
])
|
||||
->get('https://api.dhl.com/track/shipments', [
|
||||
->get('https://api-eu.dhl.com/track/shipments', [
|
||||
'trackingNumber' => implode(',', $trackingNumbers),
|
||||
'service' => 'parcel',
|
||||
'requesterCountryCode' => 'DE',
|
||||
'language' => 'de',
|
||||
]);
|
||||
|
|
@ -250,7 +283,7 @@ class DhlTrackingService
|
|||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Fehler beim Abrufen der Tracking-Informationen: ' . $e->getMessage(),
|
||||
'message' => 'Fehler beim Abrufen der Tracking-Informationen: '.$e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -302,7 +335,7 @@ class DhlTrackingService
|
|||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Fehler beim Einreihen des Tracking-Updates: ' . $e->getMessage(),
|
||||
'message' => 'Fehler beim Einreihen des Tracking-Updates: '.$e->getMessage(),
|
||||
'queued' => false,
|
||||
];
|
||||
}
|
||||
|
|
@ -333,17 +366,31 @@ class DhlTrackingService
|
|||
$result = $this->trackShipment($shipment->dhl_shipment_no);
|
||||
|
||||
if ($result['success']) {
|
||||
$internalStatus = $this->mapDhlStatusToInternal($result['status']);
|
||||
|
||||
// Update shipment with tracking data
|
||||
$shipment->update([
|
||||
'status' => $this->mapDhlStatusToInternal($result['status']),
|
||||
$updateData = [
|
||||
'status' => $internalStatus,
|
||||
'tracking_status' => $result['status_text'],
|
||||
'last_tracked_at' => now(),
|
||||
]);
|
||||
];
|
||||
|
||||
// Mark tracking as completed if terminal status reached
|
||||
if (in_array($internalStatus, DhlShipment::TERMINAL_STATUSES)) {
|
||||
$updateData['tracking_completed_at'] = now();
|
||||
}
|
||||
|
||||
$shipment->update($updateData);
|
||||
|
||||
// Save tracking events
|
||||
$this->saveTrackingEvents($shipment, $result['events'] ?? []);
|
||||
|
||||
Log::info('[DHL Tracking Service] Tracking updated successfully (sync)', [
|
||||
'shipment_id' => $shipment->id,
|
||||
'dhl_shipment_no' => $shipment->dhl_shipment_no,
|
||||
'tracking_status' => $result['status'],
|
||||
'tracking_completed' => in_array($internalStatus, DhlShipment::TERMINAL_STATUSES),
|
||||
'events_count' => count($result['events'] ?? []),
|
||||
'api_used' => $result['api_used'],
|
||||
]);
|
||||
|
||||
|
|
@ -353,6 +400,7 @@ class DhlTrackingService
|
|||
'queued' => false,
|
||||
'shipment_id' => $shipment->id,
|
||||
'tracking_status' => $result['status'],
|
||||
'tracking_completed' => in_array($internalStatus, DhlShipment::TERMINAL_STATUSES),
|
||||
'tracking_details' => $result,
|
||||
];
|
||||
} else {
|
||||
|
|
@ -371,13 +419,177 @@ class DhlTrackingService
|
|||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Fehler beim Aktualisieren der Tracking-Informationen: ' . $e->getMessage(),
|
||||
'message' => 'Fehler beim Aktualisieren der Tracking-Informationen: '.$e->getMessage(),
|
||||
'queued' => false,
|
||||
'shipment_id' => $shipment->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tracking for a batch of DHL shipments using the multi-tracking API.
|
||||
* Processes shipments in chunks of 10 (DHL API limit) with rate-limiting pauses.
|
||||
*
|
||||
* @param Collection<DhlShipment> $shipments
|
||||
* @return array{updated: int, failed: int, completed: int, results: array}
|
||||
*/
|
||||
public function updateTrackingBatch(Collection $shipments): array
|
||||
{
|
||||
$stats = [
|
||||
'updated' => 0,
|
||||
'failed' => 0,
|
||||
'completed' => 0,
|
||||
'results' => [],
|
||||
];
|
||||
|
||||
// Process in chunks of 10 (DHL API limit)
|
||||
$chunks = $shipments->chunk(10);
|
||||
$chunkIndex = 0;
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
// Rate limiting: pause 1 second between batch API calls
|
||||
if ($chunkIndex > 0) {
|
||||
sleep(1);
|
||||
}
|
||||
$chunkIndex++;
|
||||
|
||||
// Build tracking number => shipment mapping
|
||||
$shipmentMap = [];
|
||||
foreach ($chunk as $shipment) {
|
||||
$shipmentMap[$shipment->dhl_shipment_no] = $shipment;
|
||||
}
|
||||
|
||||
$trackingNumbers = array_keys($shipmentMap);
|
||||
|
||||
try {
|
||||
$batchResult = $this->trackMultipleShipments($trackingNumbers);
|
||||
|
||||
if ($batchResult['success'] && ! empty($batchResult['shipments'])) {
|
||||
// Process each result from the batch API
|
||||
foreach ($batchResult['shipments'] as $trackingResult) {
|
||||
$trackingNo = $trackingResult['tracking_number'];
|
||||
$shipment = $shipmentMap[$trackingNo] ?? null;
|
||||
|
||||
if (! $shipment) {
|
||||
Log::warning('[DHL Tracking Service] Batch: tracking number not mapped', [
|
||||
'tracking_number' => $trackingNo,
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove from map so we can detect missing ones later
|
||||
unset($shipmentMap[$trackingNo]);
|
||||
|
||||
$internalStatus = $this->mapDhlStatusToInternal($trackingResult['status']);
|
||||
|
||||
$updateData = [
|
||||
'status' => $internalStatus,
|
||||
'tracking_status' => $trackingResult['status_text'],
|
||||
'last_tracked_at' => now(),
|
||||
];
|
||||
|
||||
// Mark tracking as completed if terminal status reached
|
||||
$isCompleted = in_array($internalStatus, DhlShipment::TERMINAL_STATUSES);
|
||||
if ($isCompleted) {
|
||||
$updateData['tracking_completed_at'] = now();
|
||||
$stats['completed']++;
|
||||
}
|
||||
|
||||
$shipment->update($updateData);
|
||||
|
||||
// Save tracking events
|
||||
$this->saveTrackingEvents($shipment, $trackingResult['events'] ?? []);
|
||||
|
||||
$stats['updated']++;
|
||||
$stats['results'][] = [
|
||||
'shipment_id' => $shipment->id,
|
||||
'tracking_number' => $trackingNo,
|
||||
'status' => $internalStatus,
|
||||
'completed' => $isCompleted,
|
||||
'success' => true,
|
||||
];
|
||||
}
|
||||
|
||||
// Any remaining shipments in the map were not returned by the API
|
||||
foreach ($shipmentMap as $trackingNo => $shipment) {
|
||||
// Update last_tracked_at so we don't immediately retry
|
||||
$shipment->update(['last_tracked_at' => now()]);
|
||||
$stats['failed']++;
|
||||
$stats['results'][] = [
|
||||
'shipment_id' => $shipment->id,
|
||||
'tracking_number' => $trackingNo,
|
||||
'success' => false,
|
||||
'message' => 'Nicht in Batch-Antwort enthalten',
|
||||
];
|
||||
}
|
||||
} else {
|
||||
// Entire batch failed - fall back to individual tracking
|
||||
Log::warning('[DHL Tracking Service] Batch tracking failed, falling back to individual tracking', [
|
||||
'tracking_numbers' => $trackingNumbers,
|
||||
'message' => $batchResult['message'] ?? 'Unknown error',
|
||||
]);
|
||||
|
||||
foreach ($chunk as $shipment) {
|
||||
try {
|
||||
$result = $this->updateTracking($shipment, ['auto_retrack' => false]);
|
||||
if ($result['success']) {
|
||||
$stats['updated']++;
|
||||
if (! empty($result['tracking_completed'])) {
|
||||
$stats['completed']++;
|
||||
}
|
||||
} else {
|
||||
$stats['failed']++;
|
||||
}
|
||||
$stats['results'][] = [
|
||||
'shipment_id' => $shipment->id,
|
||||
'tracking_number' => $shipment->dhl_shipment_no,
|
||||
'success' => $result['success'],
|
||||
'fallback' => true,
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
$stats['failed']++;
|
||||
$stats['results'][] = [
|
||||
'shipment_id' => $shipment->id,
|
||||
'tracking_number' => $shipment->dhl_shipment_no,
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
'fallback' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::error('[DHL Tracking Service] Batch tracking exception', [
|
||||
'tracking_numbers' => $trackingNumbers,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
// Mark all as failed but update last_tracked_at
|
||||
foreach ($chunk as $shipment) {
|
||||
$shipment->update(['last_tracked_at' => now()]);
|
||||
$stats['failed']++;
|
||||
$stats['results'][] = [
|
||||
'shipment_id' => $shipment->id,
|
||||
'tracking_number' => $shipment->dhl_shipment_no,
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log::info('[DHL Tracking Service] Batch tracking completed', [
|
||||
'total' => $shipments->count(),
|
||||
'updated' => $stats['updated'],
|
||||
'failed' => $stats['failed'],
|
||||
'completed' => $stats['completed'],
|
||||
'chunks' => $chunks->count(),
|
||||
]);
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map DHL status codes to internal status
|
||||
*/
|
||||
|
|
@ -414,6 +626,39 @@ class DhlTrackingService
|
|||
return $descriptions[$statusCode] ?? 'Unbekannter Status';
|
||||
}
|
||||
|
||||
/**
|
||||
* Save tracking events from API response to database
|
||||
*/
|
||||
private function saveTrackingEvents(DhlShipment $shipment, array $events): void
|
||||
{
|
||||
if (empty($events)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($events as $event) {
|
||||
$eventTime = isset($event['timestamp']) ? \Carbon\Carbon::parse($event['timestamp']) : now();
|
||||
|
||||
// Upsert: avoid duplicates based on shipment + event_time + status_code
|
||||
DhlTrackingEvent::updateOrCreate(
|
||||
[
|
||||
'shipment_id' => $shipment->id,
|
||||
'event_time' => $eventTime,
|
||||
'status_code' => $event['statusCode'] ?? ($event['status'] ?? 'unknown'),
|
||||
],
|
||||
[
|
||||
'status_text' => $event['description'] ?? ($event['remark'] ?? 'Unbekannt'),
|
||||
'location' => $event['location']['address']['addressLocality'] ?? null,
|
||||
'raw' => $event,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Log::info('[DHL Tracking Service] Tracking events saved', [
|
||||
'shipment_id' => $shipment->id,
|
||||
'events_saved' => count($events),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SSL version constant based on configuration
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue