234 lines
8.1 KiB
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(),
|
|
]);
|
|
}
|
|
}
|