ultimatepos/Modules/Tms/Http/Controllers/ShipmentController.php

389 lines
16 KiB
PHP

<?php
namespace Modules\Tms\Http\Controllers;
use App\Transaction;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\Tms\Http\Controllers\Concerns\AuthorizesTms;
use Modules\Tms\Models\Shipment;
use Modules\Tms\Services\TmsGeocodingService;
use Modules\Tms\Services\TmsShipmentService;
use Modules\Tms\Utils\TmsUtil;
use Yajra\DataTables\Facades\DataTables;
class ShipmentController extends Controller
{
use AuthorizesTms;
public function __construct(
protected TmsUtil $tmsUtil,
protected TmsShipmentService $shipmentService,
protected TmsGeocodingService $geocodingService
) {}
public function index(Request $request)
{
$this->authorizeTmsAccess('tms.shipments.view');
$business_id = $this->tmsUtil->getBusinessId();
if ($request->ajax()) {
$query = Shipment::where('business_id', $business_id)
->with(['contact:id,name', 'driver:id,name', 'vehicle:id,plate_number', 'carrier:id,name', 'customsClearance:id,shipment_id,ref_no,status'])
->orderByDesc('created_at');
if ($status = $request->get('status')) {
$query->where('status', $status);
}
$statuses = $this->tmsUtil->shipmentStatuses();
$types = $this->tmsUtil->shipmentTypes();
$customsStatuses = $this->tmsUtil->customsStatuses();
return DataTables::of($query)
->editColumn('status', fn ($row) => '<span class="label label-info">'.($statuses[$row->status] ?? $row->status).'</span>')
->editColumn('shipment_type', fn ($row) => $types[$row->shipment_type] ?? $row->shipment_type)
->addColumn('contact_name', fn ($row) => $row->contact?->name ?? '—')
->addColumn('driver_name', fn ($row) => $row->driver?->name ?? '—')
->addColumn('vehicle_plate', fn ($row) => $row->vehicle?->plate_number ?? '—')
->addColumn('customs_status', function ($row) use ($customsStatuses) {
if (! $row->customsClearance) {
return '<span class="text-muted">—</span>';
}
$c = $row->customsClearance;
return '<span class="label label-warning" title="'.$c->ref_no.'">'.($customsStatuses[$c->status] ?? $c->status).'</span>';
})
->addColumn('action', function ($row) {
$html = '<div class="btn-group">';
$html .= '<a href="'.action([self::class, 'show'], [$row->id]).'" class="btn btn-xs btn-info"><i class="fa fa-eye"></i></a>';
if (auth()->user()->can('tms.shipments.update')) {
$html .= ' <a href="'.action([self::class, 'edit'], [$row->id]).'" class="btn btn-xs btn-primary"><i class="glyphicon glyphicon-edit"></i></a>';
}
if (auth()->user()->can('tms.shipments.delete')) {
$html .= ' <button type="button" data-href="'.action([self::class, 'destroy'], [$row->id]).'" class="btn btn-xs btn-danger delete_tms_shipment"><i class="glyphicon glyphicon-trash"></i></button>';
}
$html .= '</div>';
return $html;
})
->rawColumns(['status', 'customs_status', 'action'])
->make(true);
}
return view('tms::shipments.index', [
'statuses' => $this->tmsUtil->shipmentStatuses(),
]);
}
public function create(Request $request)
{
$this->authorizeTmsAccess('tms.shipments.create');
$business_id = $this->tmsUtil->getBusinessId();
$transaction = null;
if ($request->filled('transaction_id')) {
$transaction = Transaction::where('business_id', $business_id)
->whereIn('type', ['sell', 'sell_transfer', 'purchase'])
->find($request->transaction_id);
}
return view('tms::shipments.create', [
'shipment_types' => $this->tmsUtil->shipmentTypes(),
'statuses' => $this->tmsUtil->shipmentStatuses(),
'customers' => $this->tmsUtil->getCustomersDropdown($business_id),
'locations' => $this->tmsUtil->getLocationsDropdown($business_id),
'vehicles' => $this->tmsUtil->getVehiclesDropdown($business_id),
'drivers' => $this->tmsUtil->getDriversDropdown($business_id),
'carriers' => $this->tmsUtil->getCarriersDropdown($business_id),
'transactions' => $this->tmsUtil->getLinkableTransactionsDropdown($business_id, $transaction?->id),
'transaction' => $transaction,
]);
}
public function store(Request $request)
{
$this->authorizeTmsAccess('tms.shipments.create');
$business_id = $this->tmsUtil->getBusinessId();
$data = $request->validate([
'shipment_type' => 'required|string|max:50',
'transaction_id' => 'nullable|exists:transactions,id',
'contact_id' => 'nullable|exists:contacts,id',
'carrier_id' => 'nullable|exists:tms_carriers,id',
'vehicle_id' => 'nullable|exists:tms_vehicles,id',
'driver_id' => 'nullable|exists:tms_drivers,id',
'origin_location_id' => 'nullable|exists:business_locations,id',
'origin_address' => 'nullable|string',
'destination_address' => 'nullable|string',
'destination_latitude' => 'nullable|numeric|between:-90,90',
'destination_longitude' => 'nullable|numeric|between:-180,180',
'status' => 'required|string|max:50',
'scheduled_at' => 'nullable|date',
'freight_cost' => 'nullable|numeric|min:0',
'weight' => 'nullable|numeric|min:0',
'volume' => 'nullable|numeric|min:0',
'cargo_description' => 'nullable|string',
'notes' => 'nullable|string',
]);
$data = $this->fillDestinationCoords($data);
$shipment = Shipment::create(array_merge($data, [
'business_id' => $business_id,
'ref_no' => $this->tmsUtil->generateRefNo($business_id),
'created_by' => auth()->id(),
]));
$this->shipmentService->logStatus($shipment, $shipment->status, __('tms::lang.shipment_created'));
$settings = $this->tmsUtil->getTmsSettings($business_id);
if (! empty($settings['sync_transaction_shipping'])) {
$this->shipmentService->syncToTransaction($shipment);
}
return redirect()
->action([self::class, 'show'], [$shipment->id])
->with('status', __('tms::lang.shipment_created'));
}
public function show($id)
{
$this->authorizeTmsAccess('tms.shipments.view');
$shipment = $this->findShipment($id);
$shipment->load([
'contact', 'carrier', 'vehicle', 'driver.user',
'originLocation', 'transaction', 'statusLogs.creator', 'route', 'customsClearance',
'project', 'fieldMission',
]);
return view('tms::shipments.show', [
'shipment' => $shipment,
'statuses' => $this->tmsUtil->shipmentStatuses(),
'shipment_types' => $this->tmsUtil->shipmentTypes(),
'customs_statuses' => $this->tmsUtil->customsStatuses(),
]);
}
public function edit($id)
{
$this->authorizeTmsAccess('tms.shipments.update');
$business_id = $this->tmsUtil->getBusinessId();
$shipment = $this->findShipment($id);
return view('tms::shipments.edit', [
'shipment' => $shipment,
'shipment_types' => $this->tmsUtil->shipmentTypes(),
'statuses' => $this->tmsUtil->shipmentStatuses(),
'customers' => $this->tmsUtil->getCustomersDropdown($business_id),
'locations' => $this->tmsUtil->getLocationsDropdown($business_id),
'vehicles' => $this->tmsUtil->getVehiclesDropdown($business_id),
'drivers' => $this->tmsUtil->getDriversDropdown($business_id),
'carriers' => $this->tmsUtil->getCarriersDropdown($business_id),
'transactions' => $this->tmsUtil->getLinkableTransactionsDropdown($business_id, $shipment->transaction_id),
]);
}
public function update(Request $request, $id)
{
$this->authorizeTmsAccess('tms.shipments.update');
$shipment = $this->findShipment($id);
$old_status = $shipment->status;
$data = $request->validate([
'shipment_type' => 'required|string|max:50',
'transaction_id' => 'nullable|exists:transactions,id',
'contact_id' => 'nullable|exists:contacts,id',
'carrier_id' => 'nullable|exists:tms_carriers,id',
'vehicle_id' => 'nullable|exists:tms_vehicles,id',
'driver_id' => 'nullable|exists:tms_drivers,id',
'origin_location_id' => 'nullable|exists:business_locations,id',
'origin_address' => 'nullable|string',
'destination_address' => 'nullable|string',
'destination_latitude' => 'nullable|numeric|between:-90,90',
'destination_longitude' => 'nullable|numeric|between:-180,180',
'status' => 'required|string|max:50',
'scheduled_at' => 'nullable|date',
'freight_cost' => 'nullable|numeric|min:0',
'weight' => 'nullable|numeric|min:0',
'volume' => 'nullable|numeric|min:0',
'cargo_description' => 'nullable|string',
'notes' => 'nullable|string',
]);
$shipment->update($this->fillDestinationCoords($data));
if ($old_status !== $shipment->status) {
try {
$this->shipmentService->updateStatus($shipment, $shipment->status, __('tms::lang.status_changed'));
} catch (\InvalidArgumentException $e) {
return redirect()->back()->with('status', $e->getMessage());
}
} else {
$settings = $this->tmsUtil->getTmsSettings($shipment->business_id);
if (! empty($settings['sync_transaction_shipping'])) {
$this->shipmentService->syncToTransaction($shipment);
}
}
return redirect()
->action([self::class, 'show'], [$shipment->id])
->with('status', __('tms::lang.shipment_updated'));
}
public function updateStatus(Request $request, $id)
{
$this->authorizeTmsAccess('tms.shipments.update');
$shipment = $this->findShipment($id);
$data = $request->validate([
'status' => 'required|string|max:50',
'note' => 'nullable|string',
'location' => 'nullable|string|max:255',
]);
try {
$this->shipmentService->updateStatus($shipment, $data['status'], $data['note'] ?? null, $data['location'] ?? null);
} catch (\InvalidArgumentException $e) {
return $this->tmsUtil->jsonOrRedirect($request, false, $e->getMessage());
}
return $this->tmsUtil->jsonOrRedirect($request, true, __('tms::lang.status_updated'));
}
public function createFromTransaction(Request $request, $transaction_id)
{
$this->authorizeTmsAccess('tms.shipments.create');
$business_id = $this->tmsUtil->getBusinessId();
$transaction = Transaction::where('business_id', $business_id)
->whereIn('type', ['sell', 'sell_transfer', 'purchase'])
->findOrFail($transaction_id);
$shipment = $this->shipmentService->createFromTransaction($transaction);
if ($request->ajax() || $request->wantsJson()) {
return response()->json([
'success' => true,
'msg' => __('tms::lang.shipment_created'),
'shipment_id' => $shipment?->id,
'redirect' => $shipment ? action([self::class, 'show'], [$shipment->id]) : null,
]);
}
return redirect()->action([self::class, 'show'], [$shipment->id])
->with('status', __('tms::lang.shipment_created'));
}
public function createFromFieldMission(Request $request, $mission_id)
{
$this->authorizeTmsAccess('tms.shipments.create');
$business_id = $this->tmsUtil->getBusinessId();
if (! class_exists(\Modules\Maintenance\Models\FieldMission::class)) {
abort(404);
}
$mission = \Modules\Maintenance\Models\FieldMission::where('business_id', $business_id)
->findOrFail($mission_id);
try {
$shipment = $this->shipmentService->createFromFieldMission($mission);
} catch (\InvalidArgumentException $e) {
return $this->tmsUtil->jsonOrRedirect($request, false, $e->getMessage());
}
if ($request->ajax() || $request->wantsJson()) {
return response()->json([
'success' => true,
'msg' => __('tms::lang.shipment_created'),
'shipment_id' => $shipment?->id,
'redirect' => $shipment ? action([self::class, 'show'], [$shipment->id]) : null,
]);
}
return redirect()->action([self::class, 'show'], [$shipment->id])
->with('status', __('tms::lang.shipment_created'));
}
public function createFromProject(Request $request, $project_id)
{
$this->authorizeTmsAccess('tms.shipments.create');
$business_id = $this->tmsUtil->getBusinessId();
if (! class_exists(\Modules\Project\Entities\Project::class)) {
abort(404);
}
$project = \Modules\Project\Entities\Project::where('business_id', $business_id)
->findOrFail($project_id);
$shipment = $this->shipmentService->createFromProject($business_id, (int) $project->id);
if ($request->ajax() || $request->wantsJson()) {
return response()->json([
'success' => true,
'msg' => __('tms::lang.shipment_created'),
'redirect' => action([self::class, 'edit'], [$shipment->id]),
]);
}
return redirect()->action([self::class, 'edit'], [$shipment->id])
->with('status', __('tms::lang.shipment_created'));
}
public function destroy(Request $request, $id)
{
$this->authorizeTmsAccess('tms.shipments.delete');
$shipment = $this->findShipment($id);
$shipment->statusLogs()->delete();
$shipment->delete();
return $this->tmsUtil->jsonOrRedirect(
$request,
true,
__('tms::lang.shipment_deleted'),
action([self::class, 'index'])
);
}
public function geocodeAddress(Request $request)
{
$this->authorizeTmsAccess('tms.shipments.create');
$data = $request->validate(['address' => 'required|string|max:500']);
$coords = $this->geocodingService->geocode($data['address']);
if (! $coords) {
return response()->json(['success' => false, 'msg' => __('tms::lang.geocode_failed')]);
}
return response()->json(['success' => true, 'data' => $coords]);
}
protected function fillDestinationCoords(array $data): array
{
if (! empty($data['destination_latitude']) && ! empty($data['destination_longitude'])) {
return $data;
}
if (empty($data['destination_address'])) {
return $data;
}
$coords = $this->geocodingService->geocode($data['destination_address']);
if ($coords) {
$data['destination_latitude'] = $coords['lat'];
$data['destination_longitude'] = $coords['lng'];
}
return $data;
}
protected function findShipment($id): Shipment
{
return Shipment::where('business_id', $this->tmsUtil->getBusinessId())->findOrFail($id);
}
}