authorizeTmsAccess('tms.routes.view'); $business_id = $this->tmsUtil->getBusinessId(); $statuses = $this->routeService->routeStatuses(); if ($request->ajax()) { $query = RoutePlan::where('business_id', $business_id) ->with(['driver:id,name', 'vehicle:id,plate_number']) ->withCount('stops') ->orderByDesc('route_date'); return DataTables::of($query) ->editColumn('route_date', fn ($row) => $row->route_date?->format('Y-m-d')) ->editColumn('status', fn ($row) => $statuses[$row->status] ?? $row->status) ->addColumn('driver_name', fn ($row) => $row->driver?->name ?? '—') ->addColumn('vehicle_plate', fn ($row) => $row->vehicle?->plate_number ?? '—') ->addColumn('action', function ($row) { $html = '
'; return $html; }) ->rawColumns(['action']) ->make(true); } return view('tms::routes.index', compact('statuses')); } public function create() { $this->authorizeTmsAccess('tms.routes.create'); $business_id = $this->tmsUtil->getBusinessId(); $pendingShipments = Shipment::where('business_id', $business_id) ->whereIn('status', ['draft', 'scheduled']) ->whereNull('route_id') ->orderBy('scheduled_at') ->get(['id', 'ref_no', 'destination_address', 'destination_latitude', 'destination_longitude']); return view('tms::routes.create', [ 'drivers' => $this->tmsUtil->getDriversDropdown($business_id), 'vehicles' => $this->tmsUtil->getVehiclesDropdown($business_id), 'locations' => $this->tmsUtil->getLocationsDropdown($business_id), 'pending_shipments' => $pendingShipments, 'statuses' => $this->routeService->routeStatuses(), ]); } public function store(Request $request) { $this->authorizeTmsAccess('tms.routes.create'); $business_id = $this->tmsUtil->getBusinessId(); $data = $request->validate([ 'route_date' => 'required|date', 'driver_id' => 'nullable|exists:tms_drivers,id', 'vehicle_id' => 'nullable|exists:tms_vehicles,id', 'origin_location_id' => 'nullable|exists:business_locations,id', 'origin_latitude' => 'nullable|numeric|between:-90,90', 'origin_longitude' => 'nullable|numeric|between:-180,180', 'notes' => 'nullable|string', 'shipment_ids' => 'nullable|array', 'shipment_ids.*' => 'exists:tms_shipments,id', ]); $route = $this->routeService->createRoute($business_id, [ 'route_date' => $data['route_date'], 'driver_id' => $data['driver_id'] ?? null, 'vehicle_id' => $data['vehicle_id'] ?? null, 'origin_location_id' => $data['origin_location_id'] ?? null, 'origin_latitude' => $data['origin_latitude'] ?? null, 'origin_longitude' => $data['origin_longitude'] ?? null, 'notes' => $data['notes'] ?? null, 'status' => 'draft', ]); if (! empty($data['shipment_ids'])) { $this->routeService->addShipmentsToRoute($route, $data['shipment_ids']); } if ($request->boolean('optimize_now') && $route->stops()->exists()) { $this->routeService->optimizeStops($route); } return redirect() ->action([self::class, 'show'], [$route->id]) ->with('status', __('tms::lang.route_created')); } public function show($id) { $this->authorizeTmsAccess('tms.routes.view'); $route = $this->findRoute($id); $route->load(['stops.shipment', 'driver', 'vehicle', 'originLocation']); $mapStops = $route->stops->map(function ($s) { if (! $s->latitude || ! $s->longitude) { return null; } return [ 'lat' => (float) $s->latitude, 'lng' => (float) $s->longitude, 'label' => $s->shipment?->ref_no ?? '#'.$s->stop_order, ]; })->filter()->values(); return view('tms::routes.show', [ 'route' => $route, 'statuses' => $this->routeService->routeStatuses(), 'mapStops' => $mapStops, 'mapOrigin' => [ 'lat' => $route->origin_latitude ? (float) $route->origin_latitude : null, 'lng' => $route->origin_longitude ? (float) $route->origin_longitude : null, ], ]); } public function edit($id) { $this->authorizeTmsAccess('tms.routes.update'); $business_id = $this->tmsUtil->getBusinessId(); $route = $this->findRoute($id); $pendingShipments = Shipment::where('business_id', $business_id) ->whereIn('status', ['draft', 'scheduled']) ->where(function ($q) use ($route) { $q->whereNull('route_id')->orWhere('route_id', $route->id); }) ->orderBy('scheduled_at') ->get(['id', 'ref_no', 'destination_address', 'route_id']); $selectedIds = $route->stops()->pluck('shipment_id')->filter()->all(); return view('tms::routes.edit', [ 'route' => $route, 'drivers' => $this->tmsUtil->getDriversDropdown($business_id), 'vehicles' => $this->tmsUtil->getVehiclesDropdown($business_id), 'locations' => $this->tmsUtil->getLocationsDropdown($business_id), 'pending_shipments' => $pendingShipments, 'selected_ids' => $selectedIds, 'statuses' => $this->routeService->routeStatuses(), ]); } public function update(Request $request, $id) { $this->authorizeTmsAccess('tms.routes.update'); $route = $this->findRoute($id); $data = $request->validate([ 'route_date' => 'required|date', 'driver_id' => 'nullable|exists:tms_drivers,id', 'vehicle_id' => 'nullable|exists:tms_vehicles,id', 'origin_location_id' => 'nullable|exists:business_locations,id', 'origin_latitude' => 'nullable|numeric|between:-90,90', 'origin_longitude' => 'nullable|numeric|between:-180,180', 'notes' => 'nullable|string', 'status' => 'nullable|string|max:50', ]); $route->update($data); return redirect() ->action([self::class, 'show'], [$route->id]) ->with('status', __('tms::lang.route_updated')); } public function optimize($id) { $this->authorizeTmsAccess('tms.routes.update'); $route = $this->findRoute($id); $this->routeService->optimizeStops($route); return redirect() ->action([self::class, 'show'], [$route->id]) ->with('status', __('tms::lang.route_optimized')); } public function destroy(Request $request, $id) { $this->authorizeTmsAccess('tms.routes.delete'); $route = $this->findRoute($id); Shipment::where('route_id', $route->id)->update(['route_id' => null]); $route->stops()->delete(); $route->delete(); return $this->tmsUtil->jsonOrRedirect( $request, true, __('tms::lang.route_deleted'), action([self::class, 'index']) ); } protected function findRoute($id): RoutePlan { return RoutePlan::where('business_id', $this->tmsUtil->getBusinessId())->findOrFail($id); } }