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) => ''.($statuses[$row->status] ?? $row->status).'') ->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 ''; } $c = $row->customsClearance; return ''.($customsStatuses[$c->status] ?? $c->status).''; }) ->addColumn('action', function ($row) { $html = '
'; $html .= ''; if (auth()->user()->can('tms.shipments.update')) { $html .= ' '; } if (auth()->user()->can('tms.shipments.delete')) { $html .= ' '; } $html .= '
'; 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); } }