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
225 lines
6.9 KiB
PHP
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;
|
|
}
|
|
}
|