ifnex/04_Laravel/app/Imports/TrackingDataImport.php
Kazem Alghasi f8557a0f20 feat(console): add tracking data import functionality and utility scripts
Implement a new Artisan command and Excel import logic to handle tracking data ingestion. This includes registering new commands in the application bootstrap and providing several utility scripts for debugging and testing the import process.

- Add `ImportTrackingData` command and `TrackingDataImport` class
- Register `ImportShippingRates` and `ImportTrackingData` in `app.php`
- Add `ifnex:import:tracking` closure command in `console.php`
- Include various debugging and testing scripts for Excel and shipment verification
2026-08-04 05:38:35 +03:30

225 lines
6.9 KiB
PHP

<?php
namespace App\Imports;
use App\Models\Shipment;
use App\Models\ShipmentTrackingEvent;
use App\Models\Country;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Illuminate\Support\Collection;
class TrackingDataImport implements ToCollection, WithHeadingRow
{
protected $importedEvents = 0;
protected $skippedEvents = 0;
protected $errors = [];
protected $createdShipments = 0;
public function collection(Collection $rows)
{
Log::info('Tracking import started', ['total_rows' => $rows->count()]);
if ($rows->count() === 0) {
Log::warning('No rows found');
return;
}
$firstRow = $rows->first();
Log::info('First row keys', ['keys' => $firstRow->keys()->toArray()]);
Log::info('First row awb', ['awb' => $firstRow['awb'] ?? 'N/A']);
foreach ($rows as $index => $row) {
try {
$awbNo = $this->normalizeAwb($row['awb'] ?? null);
$eventDate = $this->normalizeDate($row['date'] ?? null);
$eventTime = $this->normalizeTime($row['time'] ?? null);
$eventDescription = $this->normalizeDescription($row['state'] ?? null);
$location = $this->normalizeLocation($row['country'] ?? null);
$deliveryStatus = $this->normalizeDeliveryStatus($row['last_state'] ?? null);
if (!$awbNo || !$eventDate || !$eventDescription) {
$this->skippedEvents++;
continue;
}
$shipment = Shipment::where('awb_no', $awbNo)->first();
if (!$shipment) {
$shipment = $this->createShipmentFromTrackingRow($row, $awbNo, $location);
$this->createdShipments++;
}
ShipmentTrackingEvent::create([
'shipment_id' => $shipment->id,
'event_date' => $eventDate,
'event_time' => $eventTime,
'location' => $location,
'event_description' => $eventDescription,
'delivery_status' => $deliveryStatus,
'source' => 'manual',
]);
$this->importedEvents++;
} catch (\Throwable $e) {
$this->skippedEvents++;
$this->errors[] = "Row {$index}: " . $e->getMessage();
Log::error('Tracking import error', ['row' => $index, 'error' => $e->getMessage()]);
}
}
Log::info('Tracking import completed', [
'imported' => $this->importedEvents,
'skipped' => $this->skippedEvents,
'created' => $this->createdShipments,
'errors' => count($this->errors),
]);
}
protected function createShipmentFromTrackingRow(array $row, string $awbNo, ?string $location): Shipment
{
$countryName = $location ? explode(' - ', $location)[0] : null;
$country = Country::where('name', 'like', "%{$countryName}%")->first();
$lastState = strtolower($row['last_state'] ?? '');
$status = 'processed';
if (str_contains($lastState, 'delivered')) $status = 'delivered';
elseif (str_contains($lastState, 'in transit')) $status = 'in_transit';
elseif (str_contains($lastState, 'picked')) $status = 'picked_up';
elseif (str_contains($lastState, 'out for delivery')) $status = 'out_for_delivery';
elseif (str_contains($lastState, 'failed')) $status = 'failed';
$shipment = Shipment::create([
'awb_no' => $awbNo,
'direction' => 'export',
'type' => 'PARCEL',
'status' => $status,
'weight' => 0,
'volumetric_weight' => 0,
'chargeable_weight' => 0,
'shipping_price' => 0,
'extra_service' => 0,
'packing_cost' => 0,
'domestic_pickup' => 0,
'domestic_delivery' => 0,
'warehousing_cost' => 0,
'vat_amount' => 0,
'discount' => 0,
'total_fee' => 0,
'net_dirham' => 0,
'net_rial' => 0,
'from_country_id' => Country::where('name', 'Iran')->first()?->id,
'to_country_id' => $country?->id,
'sender_name' => 'Unknown',
'receiver_name' => 'Unknown',
]);
return $shipment;
}
public function getImportedEvents(): int
{
return $this->importedEvents;
}
public function getSkippedEvents(): int
{
return $this->skippedEvents;
}
public function getErrors(): array
{
return $this->errors;
}
public function getCreatedShipments(): int
{
return $this->createdShipments;
}
protected function normalizeAwb($value): ?string
{
if (!$value) return null;
$value = trim($value);
return $value === '' || $value === '0' ? null : $value;
}
protected function normalizeDate($value): ?string
{
if (!$value) return null;
if ($value instanceof \DateTime) {
return $value->format('Y-m-d');
}
$value = trim($value);
if ($value === '' || $value === '1899-12-31') return null;
try {
return date('Y-m-d', strtotime($value));
} catch (\Throwable $e) {
return null;
}
}
protected function normalizeTime($value): ?string
{
if (!$value) return null;
if ($value instanceof \DateTime) {
return $value->format('H:i:s');
}
$value = trim($value);
if ($value === '' || $value === '1899-12-31') return null;
try {
return date('H:i:s', strtotime($value));
} catch (\Throwable $e) {
return null;
}
}
protected function normalizeDescription($value): ?string
{
if (!$value) return null;
$value = trim($value);
return $value === '' ? null : $value;
}
protected function normalizeLocation($value): ?string
{
if (!$value) return null;
$value = trim($value);
return $value === '' || $value === '.' ? null : $value;
}
protected function normalizeDeliveryStatus($value): ?string
{
if (!$value) return null;
$value = trim($value);
if ($value === '') return null;
$statuses = [
'processed' => 'processed',
'picked up' => 'picked_up',
'in transit' => 'in_transit',
'out for delivery' => 'out_for_delivery',
'failed' => 'failed',
'delivered' => 'delivered',
'returned' => 'returned',
];
$lower = strtolower($value);
foreach ($statuses as $key => $status) {
if (str_contains($lower, $key)) {
return $status;
}
}
return null;
}
}