389 lines
16 KiB
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);
|
|
}
|
|
}
|