mivita/packages/acme-laravel-dhl/src/Services/TrackingService.php
2025-08-22 18:18:26 +02:00

180 lines
5.5 KiB
PHP

<?php
namespace Acme\Dhl\Services;
use Acme\Dhl\Support\DhlClient;
use Acme\Dhl\Models\DhlShipment;
use Acme\Dhl\Models\DhlTrackingEvent;
use InvalidArgumentException;
use Exception;
use Acme\Dhl\Jobs\SyncTrackingJob;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
/**
* DHL Tracking Service for fetching and managing tracking information
*/
class TrackingService
{
public function __construct(protected DhlClient $client) {}
/**
* Fetch tracking status for a shipment
*
* @param string $trackingNumber DHL tracking number
* @return array Tracking data from DHL API
* @throws InvalidArgumentException When tracking number is empty
* @throws Exception When API request fails
*/
public function fetchStatus(string $trackingNumber): array
{
if (empty($trackingNumber)) {
throw new InvalidArgumentException('Tracking number is required');
}
if (config('dhl.use_queue')) {
SyncTrackingJob::dispatch($trackingNumber);
return ['queued' => true];
}
$response = $this->client->request('get', '/post-tracking/api/shipments', [], [
'trackingNumber' => $trackingNumber
]);
Log::info('Fetched tracking status', ['trackingNumber' => $trackingNumber]);
$events = data_get($response, 'shipments.0.events', []);
$shipment = $this->findOrCreateShipment($trackingNumber);
$this->updateTrackingEvents($shipment, $events);
return $response;
}
/**
* Update tracking status for multiple shipments
*
* @param array $trackingNumbers Array of tracking numbers to update
* @return array Results for each tracking number
*/
public function updateMultipleStatus(array $trackingNumbers): array
{
$results = [];
foreach ($trackingNumbers as $trackingNumber) {
try {
$results[$trackingNumber] = $this->fetchStatus($trackingNumber);
} catch (Exception $e) {
Log::error('Tracking update failed', ['trackingNumber' => $trackingNumber, 'error' => $e->getMessage()]);
$results[$trackingNumber] = ['error' => $e->getMessage()];
}
}
return $results;
}
/**
* Get latest tracking status for a shipment
*
* @param string $trackingNumber DHL tracking number
* @return array|null Latest tracking event or null if not found
*/
public function getLatestStatus(string $trackingNumber): ?array
{
$shipment = DhlShipment::where('dhl_shipment_no', $trackingNumber)->first();
if (!$shipment) {
return null;
}
$latestEvent = $shipment->trackingEvents()
->orderBy('event_time', 'desc')
->first();
return $latestEvent ? $latestEvent->toArray() : null;
}
/**
* Find existing shipment or create new one
*/
private function findOrCreateShipment(string $trackingNumber): DhlShipment
{
return DB::transaction(function () use ($trackingNumber) {
return DhlShipment::firstOrCreate(
['dhl_shipment_no' => $trackingNumber],
[
'type' => 'outbound',
'status' => 'unknown'
]
);
});
}
/**
* Update tracking events for a shipment
*/
private function updateTrackingEvents(DhlShipment $shipment, array $events): void
{
foreach ($events as $eventData) {
$this->createOrUpdateTrackingEvent($shipment, $eventData);
}
// Update shipment status based on latest event
$this->updateShipmentStatus($shipment, $events);
}
/**
* Create or update a tracking event
*/
private function createOrUpdateTrackingEvent(DhlShipment $shipment, array $eventData): void
{
DhlTrackingEvent::updateOrCreate(
[
'shipment_id' => $shipment->id,
'status_code' => $eventData['statusCode'] ?? null,
'event_time' => $eventData['timestamp'] ?? null
],
[
'status_text' => $eventData['status'] ?? null,
'location' => $this->extractLocation($eventData),
'raw' => $eventData
]
);
}
/**
* Extract location from event data
*/
private function extractLocation(array $eventData): ?string
{
return data_get($eventData, 'location.address.addressLocality')
?? data_get($eventData, 'location')
?? null;
}
/**
* Update shipment status based on latest tracking event
*/
private function updateShipmentStatus(DhlShipment $shipment, array $events): void
{
if (empty($events)) {
return;
}
// Sort events by timestamp to get the latest
usort($events, function ($a, $b) {
return strtotime($b['timestamp'] ?? '0') <=> strtotime($a['timestamp'] ?? '0');
});
$latestEvent = $events[0];
$statusCode = $latestEvent['statusCode'] ?? null;
// Map DHL status codes to our internal status
$status = DhlShipment::STATUS_MAP[$statusCode] ?? 'unknown';
$shipment->update([
'status' => $status,
'tracking_status' => $latestEvent['status'] ?? null,
'last_tracked_at' => now()
]);
Log::info('Updated shipment status', ['shipmentId' => $shipment->id, 'newStatus' => $status]);
}
}