ultimatepos/Modules/Tms/Services/TmsShipmentService.php

234 lines
8.1 KiB
PHP

<?php
namespace Modules\Tms\Services;
use App\Transaction;
use Modules\Tms\Models\Shipment;
use Modules\Tms\Models\ShipmentStatusLog;
use Modules\Tms\Services\TmsAccountingService;
use Modules\Tms\Utils\TmsUtil;
class TmsShipmentService
{
public function __construct(
protected TmsUtil $tmsUtil,
protected TmsAccountingService $accountingService,
protected TmsGeocodingService $geocodingService,
protected TmsCustomsShipmentSyncService $customsSyncService,
protected TmsMissionSyncService $missionSyncService
) {}
public function createFromTransaction(Transaction $transaction, array $extra = []): ?Shipment
{
$allowedTypes = ['sell', 'sell_transfer', 'purchase'];
if (! in_array($transaction->type, $allowedTypes, true)) {
return null;
}
$business_id = (int) $transaction->business_id;
$existing = Shipment::where('business_id', $business_id)
->where('transaction_id', $transaction->id)
->first();
if ($existing) {
return $this->customsSyncService->linkByTransactionForShipment($existing);
}
$shipment_type = match ($transaction->type) {
'sell_transfer' => 'stock_transfer',
'purchase' => 'import',
'sell' => 'sell',
default => 'manual',
};
$status = $transaction->type === 'purchase' ? 'at_customs' : 'scheduled';
$shipment = Shipment::create([
'business_id' => $business_id,
'ref_no' => $this->tmsUtil->generateRefNo($business_id),
'shipment_type' => $shipment_type,
'transaction_id' => $transaction->id,
'pjt_project_id' => $transaction->pjt_project_id ?? ($extra['pjt_project_id'] ?? null),
'contact_id' => $transaction->contact_id,
'origin_location_id' => $transaction->location_id,
'destination_address' => $transaction->shipping_address,
'status' => $status,
'scheduled_at' => $transaction->delivery_date ?? now(),
'freight_cost' => $transaction->shipping_charges ?? 0,
'cargo_description' => $transaction->shipping_details,
'created_by' => auth()->id(),
...$extra,
]);
$this->applyDestinationCoords($shipment);
$this->logStatus($shipment, $status, __('tms::lang.auto_created_from_transaction'));
$settings = $this->tmsUtil->getTmsSettings($business_id);
if (! empty($settings['sync_transaction_shipping'])) {
$this->syncToTransaction($shipment);
}
return $this->customsSyncService->linkByTransactionForShipment($shipment);
}
public function createFromFieldMission($mission, array $extra = []): ?Shipment
{
if (! class_exists(\Modules\Maintenance\Models\FieldMission::class)) {
return null;
}
$business_id = (int) $mission->business_id;
$existing = $this->missionSyncService->findShipmentForMission($mission);
if ($existing) {
return $existing;
}
$destination = $this->missionSyncService->resolveMissionDestination($mission);
if (empty($destination)) {
throw new \InvalidArgumentException(__('tms::lang.mission_destination_required'));
}
$shipment = Shipment::create([
'business_id' => $business_id,
'ref_no' => $this->tmsUtil->generateRefNo($business_id),
'shipment_type' => 'field_service',
'source_type' => 'field_mission',
'source_id' => $mission->id,
'pjt_project_id' => $this->missionSyncService->resolveProjectIdFromMission($mission),
'driver_id' => $this->missionSyncService->resolveDriverId($mission),
'destination_address' => $destination,
'status' => in_array($mission->status, ['dispatched', 'on_site'], true) ? 'in_transit' : 'scheduled',
'scheduled_at' => $mission->planned_departure_at ?? now(),
'cargo_description' => $this->missionSyncService->buildCargoDescription($mission),
'notes' => $mission->notes,
'created_by' => auth()->id(),
...$extra,
]);
$this->applyDestinationCoords($shipment);
$this->logStatus($shipment, $shipment->status, __('tms::lang.auto_created_from_mission', ['ref' => $mission->mission_number]));
return $shipment->fresh();
}
public function createFromProject(int $business_id, int $project_id, array $extra = []): Shipment
{
return Shipment::create([
'business_id' => $business_id,
'ref_no' => $this->tmsUtil->generateRefNo($business_id),
'shipment_type' => 'project_delivery',
'pjt_project_id' => $project_id,
'status' => 'draft',
'created_by' => auth()->id(),
...$extra,
]);
}
protected function applyDestinationCoords(Shipment $shipment): void
{
if ($shipment->destination_latitude && $shipment->destination_longitude) {
return;
}
if (empty($shipment->destination_address)) {
return;
}
$coords = $this->geocodingService->geocode($shipment->destination_address);
if ($coords) {
$shipment->update([
'destination_latitude' => $coords['lat'],
'destination_longitude' => $coords['lng'],
]);
}
}
public function updateStatus(Shipment $shipment, string $status, ?string $note = null, ?string $location = null, bool $skipCustomsCheck = false): Shipment
{
if (! $skipCustomsCheck && $status === 'in_transit' && $this->customsSyncService->customsBlocksTransit($shipment)) {
throw new \InvalidArgumentException($this->customsSyncService->transitBlockMessage($shipment));
}
$updates = ['status' => $status];
if ($status === 'in_transit' && ! $shipment->pickup_at) {
$updates['pickup_at'] = now();
}
if ($status === 'delivered') {
$updates['delivered_at'] = now();
}
$shipment->update($updates);
$this->logStatus($shipment, $status, $note, $location);
$settings = $this->tmsUtil->getTmsSettings($shipment->business_id);
if (! empty($settings['sync_transaction_shipping'])) {
$this->syncToTransaction($shipment);
}
if ($status === 'delivered') {
$this->accountingService->postFreightOnDelivery(
$shipment->fresh(),
(int) (auth()->id() ?? $shipment->created_by ?? 0)
);
}
$this->missionSyncService->syncMissionFromShipment($shipment->fresh());
return $shipment->fresh();
}
public function syncToTransaction(Shipment $shipment): void
{
if (! $shipment->transaction_id) {
return;
}
$transaction = Transaction::find($shipment->transaction_id);
if (! $transaction) {
return;
}
$shipping_status = $this->tmsUtil->mapStatusToTransactionShipping($shipment->status);
if (! $shipping_status) {
return;
}
$updates = ['shipping_status' => $shipping_status];
if ($shipment->driver_id && $shipment->driver?->user_id) {
$updates['delivery_person'] = $shipment->driver->user_id;
}
if ($shipment->delivered_at) {
$updates['delivery_date'] = $shipment->delivered_at;
}
if ($shipment->destination_address) {
$updates['shipping_address'] = $shipment->destination_address;
}
if ($shipment->freight_cost) {
$updates['shipping_charges'] = $shipment->freight_cost;
}
$transaction->update($updates);
}
public function logStatus(Shipment $shipment, string $status, ?string $note = null, ?string $location = null): ShipmentStatusLog
{
return ShipmentStatusLog::create([
'shipment_id' => $shipment->id,
'status' => $status,
'note' => $note,
'location' => $location,
'created_by' => auth()->id(),
'logged_at' => now(),
]);
}
}