@fleetbase/fleetops-engine 0.6.52 → 0.6.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/addon/components/device/details.hbs +106 -135
  2. package/addon/components/device/details.js +174 -1
  3. package/addon/components/device/panel-header.hbs +2 -2
  4. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.hbs +201 -0
  5. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +654 -0
  6. package/addon/components/layout/fleet-ops-sidebar.hbs +19 -233
  7. package/addon/components/layout/fleet-ops-sidebar.js +364 -395
  8. package/addon/components/maintenance-schedule/form.js +1 -0
  9. package/addon/components/order/form/details.hbs +1 -43
  10. package/addon/components/order/form/details.js +18 -53
  11. package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
  12. package/addon/components/order/form/orchestrator-constraints.js +61 -0
  13. package/addon/components/order/form/payload.js +50 -2
  14. package/addon/components/order/form/route.js +15 -0
  15. package/addon/components/order/form/service-rate.hbs +62 -11
  16. package/addon/components/order/form/service-rate.js +124 -2
  17. package/addon/components/order/form.hbs +2 -0
  18. package/addon/components/telematic/details.hbs +7 -1
  19. package/addon/components/telematic/form.js +16 -8
  20. package/addon/components/work-order/details.hbs +5 -1
  21. package/addon/components/work-order/form.hbs +26 -4
  22. package/addon/components/work-order/form.js +5 -1
  23. package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
  24. package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
  25. package/addon/controllers/connectivity/devices/index/details/vehicle.js +74 -0
  26. package/addon/controllers/connectivity/devices/index/details.js +12 -0
  27. package/addon/controllers/connectivity/devices/index.js +4 -1
  28. package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
  29. package/addon/controllers/connectivity/telematics/details/devices.js +90 -41
  30. package/addon/controllers/connectivity/telematics/details/events.js +39 -11
  31. package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
  32. package/addon/controllers/maintenance/index.js +100 -0
  33. package/addon/controllers/maintenance/work-orders/index.js +18 -1
  34. package/addon/controllers/management/fuel-transactions/index.js +1 -1
  35. package/addon/controllers/management/index.js +88 -0
  36. package/addon/controllers/settings/index.js +142 -0
  37. package/addon/extension.js +21 -0
  38. package/addon/routes/analytics/index.js +3 -0
  39. package/addon/routes/connectivity/devices/index/details/events.js +37 -1
  40. package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
  41. package/addon/routes/connectivity/devices/index/details/vehicle.js +7 -0
  42. package/addon/routes/connectivity/devices/index.js +3 -0
  43. package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
  44. package/addon/routes/connectivity/telematics/details/devices.js +4 -0
  45. package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
  46. package/addon/routes/connectivity/telematics/details.js +1 -1
  47. package/addon/routes/connectivity/telematics/edit.js +1 -1
  48. package/addon/routes/maintenance/index.js +3 -0
  49. package/addon/routes/maintenance/work-orders/index.js +3 -0
  50. package/addon/routes/settings/index.js +3 -0
  51. package/addon/routes.js +2 -0
  52. package/addon/services/order-creation.js +54 -1
  53. package/addon/services/order-validation.js +3 -2
  54. package/addon/services/service-rate-actions.js +1 -0
  55. package/addon/styles/fleetops-engine.css +1821 -6
  56. package/addon/templates/analytics/index.hbs +14 -0
  57. package/addon/templates/analytics/reports/index.hbs +4 -4
  58. package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
  59. package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
  60. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +84 -0
  61. package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
  62. package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
  63. package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
  64. package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
  65. package/addon/templates/maintenance/index.hbs +113 -0
  66. package/addon/templates/management/fuel-transactions/index/details.hbs +1 -1
  67. package/addon/templates/management/index.hbs +109 -73
  68. package/addon/templates/management/vehicles/index/edit.hbs +1 -1
  69. package/addon/templates/operations/orders/index.hbs +1 -1
  70. package/addon/templates/operations/service-rates/index.hbs +1 -1
  71. package/addon/templates/settings/index.hbs +83 -0
  72. package/addon/templates/settings/routing.hbs +2 -2
  73. package/addon/utils/fleet-ops-options.js +38 -0
  74. package/app/components/order/form/orchestrator-constraints.js +1 -0
  75. package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
  76. package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
  77. package/app/controllers/maintenance/index.js +1 -0
  78. package/app/controllers/management/index.js +1 -0
  79. package/app/controllers/settings/index.js +1 -0
  80. package/app/routes/analytics/index.js +1 -0
  81. package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
  82. package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
  83. package/app/routes/maintenance/index.js +1 -0
  84. package/app/routes/settings/index.js +1 -0
  85. package/app/templates/analytics/index.js +1 -0
  86. package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
  87. package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
  88. package/app/templates/maintenance/index.js +1 -0
  89. package/app/templates/management/index.js +1 -0
  90. package/app/templates/settings/index.js +1 -0
  91. package/composer.json +1 -1
  92. package/extension.json +1 -1
  93. package/package.json +3 -3
  94. package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
  95. package/server/migrations/2026_06_15_000001_normalize_device_attachable_vehicle_morph_types.php +30 -0
  96. package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
  97. package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
  98. package/server/src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php +1 -0
  99. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
  100. package/server/src/Console/Commands/SyncTelematics.php +89 -0
  101. package/server/src/Exceptions/TelematicProviderException.php +13 -2
  102. package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
  103. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +112 -11
  104. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +16 -5
  105. package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
  106. package/server/src/Http/Controllers/Internal/v1/HubController.php +300 -0
  107. package/server/src/Http/Controllers/Internal/v1/LiveController.php +177 -0
  108. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
  109. package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
  110. package/server/src/Http/Controllers/Internal/v1/SearchController.php +506 -0
  111. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
  112. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -1
  113. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +94 -10
  114. package/server/src/Http/Filter/DeviceEventFilter.php +44 -0
  115. package/server/src/Http/Filter/DeviceFilter.php +131 -0
  116. package/server/src/Http/Filter/DriverFilter.php +6 -0
  117. package/server/src/Http/Filter/SensorFilter.php +125 -0
  118. package/server/src/Http/Filter/VehicleFilter.php +19 -0
  119. package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
  120. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
  121. package/server/src/Http/Resources/v1/Driver.php +11 -2
  122. package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
  123. package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
  124. package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
  125. package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
  126. package/server/src/Jobs/SyncTelematicDevicesJob.php +181 -66
  127. package/server/src/Models/Device.php +15 -1
  128. package/server/src/Models/DeviceEvent.php +1 -1
  129. package/server/src/Models/Driver.php +17 -1
  130. package/server/src/Models/Sensor.php +15 -2
  131. package/server/src/Models/Telematic.php +1 -1
  132. package/server/src/Models/Vehicle.php +5 -1
  133. package/server/src/Models/WorkOrder.php +5 -2
  134. package/server/src/Observers/DriverObserver.php +3 -3
  135. package/server/src/Observers/FleetObserver.php +23 -0
  136. package/server/src/Observers/VehicleObserver.php +3 -3
  137. package/server/src/Providers/FleetOpsServiceProvider.php +12 -0
  138. package/server/src/Support/LiveCacheService.php +1 -1
  139. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +188 -28
  140. package/server/src/Support/Telematics/Providers/SafeeProvider.php +44 -3
  141. package/server/src/Support/Telematics/TelematicService.php +330 -30
  142. package/server/src/routes.php +10 -0
  143. package/server/tests/DeviceFilterTest.php +36 -0
  144. package/server/tests/HubRoutesTest.php +62 -0
  145. package/server/tests/OperationsMonitorEndpointTest.php +38 -0
  146. package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
  147. package/server/tests/TelematicsHardeningTest.php +455 -3
  148. package/server/tests/VehicleDriverAssignmentTest.php +27 -0
  149. package/translations/ar-ae.yml +1 -1
  150. package/translations/en-us.yaml +1 -0
  151. package/translations/vi-vn.yaml +1 -1
@@ -0,0 +1,506 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
+
5
+ use Fleetbase\FleetOps\Models\Contact;
6
+ use Fleetbase\FleetOps\Models\Device;
7
+ use Fleetbase\FleetOps\Models\DeviceEvent;
8
+ use Fleetbase\FleetOps\Models\Driver;
9
+ use Fleetbase\FleetOps\Models\Equipment;
10
+ use Fleetbase\FleetOps\Models\Fleet;
11
+ use Fleetbase\FleetOps\Models\FuelProviderConnection;
12
+ use Fleetbase\FleetOps\Models\FuelProviderTransaction;
13
+ use Fleetbase\FleetOps\Models\FuelReport;
14
+ use Fleetbase\FleetOps\Models\Issue;
15
+ use Fleetbase\FleetOps\Models\Maintenance;
16
+ use Fleetbase\FleetOps\Models\MaintenanceSchedule;
17
+ use Fleetbase\FleetOps\Models\Order;
18
+ use Fleetbase\FleetOps\Models\OrderConfig;
19
+ use Fleetbase\FleetOps\Models\Part;
20
+ use Fleetbase\FleetOps\Models\Place;
21
+ use Fleetbase\FleetOps\Models\Sensor;
22
+ use Fleetbase\FleetOps\Models\ServiceRate;
23
+ use Fleetbase\FleetOps\Models\Telematic;
24
+ use Fleetbase\FleetOps\Models\Vehicle;
25
+ use Fleetbase\FleetOps\Models\Vendor;
26
+ use Fleetbase\FleetOps\Models\WorkOrder;
27
+ use Fleetbase\Http\Controllers\Controller;
28
+ use Fleetbase\Support\Auth;
29
+ use Illuminate\Database\Eloquent\Builder;
30
+ use Illuminate\Http\JsonResponse;
31
+ use Illuminate\Http\Request;
32
+ use Illuminate\Support\Collection;
33
+ use Illuminate\Support\Str;
34
+
35
+ class SearchController extends Controller
36
+ {
37
+ private const SEARCH_TYPES = [
38
+ 'orders',
39
+ 'drivers',
40
+ 'vehicles',
41
+ 'fleets',
42
+ 'vendors',
43
+ 'contacts',
44
+ 'places',
45
+ 'issues',
46
+ 'fuel_reports',
47
+ 'fuel_transactions',
48
+ 'maintenance_schedules',
49
+ 'work_orders',
50
+ 'maintenances',
51
+ 'equipment',
52
+ 'parts',
53
+ 'fuel_providers',
54
+ 'telematics',
55
+ 'devices',
56
+ 'sensors',
57
+ 'events',
58
+ 'service_rates',
59
+ 'order_configs',
60
+ ];
61
+
62
+ public function search(Request $request): JsonResponse
63
+ {
64
+ $query = trim((string) ($request->input('query') ?: $request->input('q')));
65
+ $limit = max(1, min((int) $request->input('limit', 12), 24));
66
+
67
+ if ($query === '') {
68
+ return response()->json(['results' => []]);
69
+ }
70
+
71
+ $types = $this->requestedTypes($request);
72
+ $perTypeLimit = max(1, (int) ceil($limit / max(count($types), 1)));
73
+ $results = collect();
74
+
75
+ foreach ($types as $type) {
76
+ if (!$this->canSearchType($type)) {
77
+ continue;
78
+ }
79
+
80
+ $results = $results->merge($this->searchType($type, $query, $perTypeLimit));
81
+ }
82
+
83
+ return response()->json([
84
+ 'results' => $results->take($limit)->values(),
85
+ ]);
86
+ }
87
+
88
+ private function requestedTypes(Request $request): array
89
+ {
90
+ $types = $request->input('types', self::SEARCH_TYPES);
91
+
92
+ if (is_string($types)) {
93
+ $types = array_filter(array_map('trim', explode(',', $types)));
94
+ }
95
+
96
+ if (!is_array($types)) {
97
+ return self::SEARCH_TYPES;
98
+ }
99
+
100
+ $types = array_values(array_intersect($types, self::SEARCH_TYPES));
101
+
102
+ return empty($types) ? self::SEARCH_TYPES : $types;
103
+ }
104
+
105
+ private function canSearchType(string $type): bool
106
+ {
107
+ $permissions = [
108
+ 'orders' => 'fleet-ops see order',
109
+ 'drivers' => 'fleet-ops see driver',
110
+ 'vehicles' => 'fleet-ops see vehicle',
111
+ 'fleets' => 'fleet-ops see fleet',
112
+ 'vendors' => 'fleet-ops see vendor',
113
+ 'contacts' => 'fleet-ops see contact',
114
+ 'places' => 'fleet-ops see place',
115
+ 'issues' => 'fleet-ops see issue',
116
+ 'fuel_reports' => 'fleet-ops see fuel-report',
117
+ 'fuel_transactions' => 'fleet-ops see fuel-report',
118
+ 'maintenance_schedules' => 'fleet-ops see maintenance-schedule',
119
+ 'work_orders' => 'fleet-ops see work-order',
120
+ 'maintenances' => 'fleet-ops see maintenance',
121
+ 'equipment' => 'fleet-ops see equipment',
122
+ 'parts' => 'fleet-ops see part',
123
+ 'fuel_providers' => 'fleet-ops see fuel-report',
124
+ 'telematics' => 'fleet-ops see telematic',
125
+ 'devices' => 'fleet-ops see device',
126
+ 'sensors' => 'fleet-ops see sensor',
127
+ 'events' => 'fleet-ops see device-event',
128
+ 'service_rates' => 'fleet-ops see service-rate',
129
+ 'order_configs' => 'fleet-ops see order-config',
130
+ ];
131
+
132
+ $user = Auth::getUserFromSession();
133
+
134
+ if ($user?->isAdmin()) {
135
+ return true;
136
+ }
137
+
138
+ return Auth::can($permissions[$type]);
139
+ }
140
+
141
+ private function searchType(string $type, string $query, int $limit): Collection
142
+ {
143
+ return match ($type) {
144
+ 'orders' => $this->searchOrders($query, $limit),
145
+ 'drivers' => $this->searchDrivers($query, $limit),
146
+ 'vehicles' => $this->searchVehicles($query, $limit),
147
+ 'fleets' => $this->searchFleets($query, $limit),
148
+ 'vendors' => $this->searchVendors($query, $limit),
149
+ 'contacts' => $this->searchContacts($query, $limit),
150
+ 'places' => $this->searchPlaces($query, $limit),
151
+ 'issues' => $this->searchIssues($query, $limit),
152
+ 'fuel_reports' => $this->searchFuelReports($query, $limit),
153
+ 'fuel_transactions' => $this->searchFuelTransactions($query, $limit),
154
+ 'maintenance_schedules' => $this->searchMaintenanceSchedules($query, $limit),
155
+ 'work_orders' => $this->searchWorkOrders($query, $limit),
156
+ 'maintenances' => $this->searchMaintenances($query, $limit),
157
+ 'equipment' => $this->searchEquipment($query, $limit),
158
+ 'parts' => $this->searchParts($query, $limit),
159
+ 'fuel_providers' => $this->searchFuelProviders($query, $limit),
160
+ 'telematics' => $this->searchTelematics($query, $limit),
161
+ 'devices' => $this->searchDevices($query, $limit),
162
+ 'sensors' => $this->searchSensors($query, $limit),
163
+ 'events' => $this->searchEvents($query, $limit),
164
+ 'service_rates' => $this->searchServiceRates($query, $limit),
165
+ 'order_configs' => $this->searchOrderConfigs($query, $limit),
166
+ default => collect(),
167
+ };
168
+ }
169
+
170
+ private function searchOrders(string $query, int $limit): Collection
171
+ {
172
+ return Order::with('trackingNumber')
173
+ ->where('company_uuid', session('company'))
174
+ ->where(function (Builder $builder) use ($query) {
175
+ $this->whereLike($builder, ['public_id', 'internal_id', 'uuid', 'status', 'type'], $query);
176
+ $builder->orWhereHas('trackingNumber', function (Builder $trackingNumberBuilder) use ($query) {
177
+ $this->whereLike($trackingNumberBuilder, ['tracking_number', 'barcode'], $query);
178
+ });
179
+ })
180
+ ->limit($limit)
181
+ ->get()
182
+ ->map(fn (Order $order) => [
183
+ 'label' => $order->tracking ?: $order->public_id ?: $order->internal_id,
184
+ 'description' => $this->description($order->status, $order->type, $order->public_id, $order->internal_id),
185
+ 'icon' => 'map-location-dot',
186
+ 'type' => 'Order',
187
+ 'route' => 'console.fleet-ops.operations.orders.index.details',
188
+ 'models' => [$this->routeModel($order)],
189
+ 'breadcrumb' => 'Fleet-Ops > Operations > Orders',
190
+ ]);
191
+ }
192
+
193
+ private function searchDrivers(string $query, int $limit): Collection
194
+ {
195
+ return Driver::with('user')
196
+ ->where('company_uuid', session('company'))
197
+ ->where(function (Builder $builder) use ($query) {
198
+ $this->whereLike($builder, ['public_id', 'uuid', 'drivers_license_number', 'status'], $query);
199
+ $builder->orWhereHas('user', function (Builder $userBuilder) use ($query) {
200
+ $this->whereLike($userBuilder, ['name', 'email', 'phone'], $query);
201
+ });
202
+ })
203
+ ->limit($limit)
204
+ ->get()
205
+ ->map(fn (Driver $driver) => [
206
+ 'label' => data_get($driver, 'user.name') ?: $driver->public_id,
207
+ 'description' => $this->description(data_get($driver, 'user.email'), data_get($driver, 'user.phone'), $driver->drivers_license_number),
208
+ 'icon' => 'id-card',
209
+ 'type' => 'Driver',
210
+ 'route' => 'console.fleet-ops.management.drivers.index.details',
211
+ 'models' => [$this->routeModel($driver)],
212
+ 'breadcrumb' => 'Fleet-Ops > Resources > Drivers',
213
+ ]);
214
+ }
215
+
216
+ private function searchVehicles(string $query, int $limit): Collection
217
+ {
218
+ return $this->searchGeneric(Vehicle::class, ['name', 'description', 'make', 'model', 'year', 'internal_id', 'plate_number', 'vin', 'serial_number', 'call_sign', 'public_id', 'uuid'], $query, $limit, fn (Vehicle $vehicle) => [
219
+ 'label' => $vehicle->display_name ?: $vehicle->name ?: $vehicle->public_id,
220
+ 'description' => $this->description($vehicle->plate_number, $vehicle->vin, $vehicle->status),
221
+ 'icon' => 'truck',
222
+ 'type' => 'Vehicle',
223
+ 'route' => 'console.fleet-ops.management.vehicles.index.details',
224
+ 'models' => [$this->routeModel($vehicle)],
225
+ 'breadcrumb' => 'Fleet-Ops > Resources > Vehicles',
226
+ ]);
227
+ }
228
+
229
+ private function searchFleets(string $query, int $limit): Collection
230
+ {
231
+ return $this->searchGeneric(Fleet::class, ['name', 'public_id', 'uuid'], $query, $limit, fn (Fleet $fleet) => [
232
+ 'label' => $fleet->name ?: $fleet->public_id,
233
+ 'description' => $this->description($fleet->public_id),
234
+ 'icon' => 'user-group',
235
+ 'type' => 'Fleet',
236
+ 'route' => 'console.fleet-ops.management.fleets.index.details',
237
+ 'models' => [$this->routeModel($fleet)],
238
+ 'breadcrumb' => 'Fleet-Ops > Resources > Fleets',
239
+ ]);
240
+ }
241
+
242
+ private function searchVendors(string $query, int $limit): Collection
243
+ {
244
+ return $this->searchGeneric(Vendor::class, ['name', 'email', 'phone', 'business_id', 'public_id', 'uuid'], $query, $limit, fn (Vendor $vendor) => [
245
+ 'label' => $vendor->name ?: $vendor->public_id,
246
+ 'description' => $this->description($vendor->email, $vendor->phone, $vendor->business_id),
247
+ 'icon' => 'warehouse',
248
+ 'type' => 'Vendor',
249
+ 'route' => 'console.fleet-ops.management.vendors.index.details',
250
+ 'models' => [$this->routeModel($vendor)],
251
+ 'breadcrumb' => 'Fleet-Ops > Resources > Vendors',
252
+ ]);
253
+ }
254
+
255
+ private function searchContacts(string $query, int $limit): Collection
256
+ {
257
+ return $this->searchGeneric(Contact::class, ['name', 'email', 'phone', 'public_id', 'uuid'], $query, $limit, fn (Contact $contact) => [
258
+ 'label' => $contact->name ?: $contact->public_id,
259
+ 'description' => $this->description($contact->email, $contact->phone, $contact->type),
260
+ 'icon' => 'address-book',
261
+ 'type' => 'Contact',
262
+ 'route' => 'console.fleet-ops.management.contacts.index.details',
263
+ 'models' => [$this->routeModel($contact)],
264
+ 'breadcrumb' => 'Fleet-Ops > Resources > Contacts',
265
+ ]);
266
+ }
267
+
268
+ private function searchPlaces(string $query, int $limit): Collection
269
+ {
270
+ return $this->searchGeneric(Place::class, ['name', 'street1', 'street2', 'country', 'province', 'district', 'city', 'postal_code', 'phone', 'public_id', 'uuid'], $query, $limit, fn (Place $place) => [
271
+ 'label' => $place->name ?: $place->public_id,
272
+ 'description' => $this->description($place->street1, $place->city, $place->country),
273
+ 'icon' => 'location-dot',
274
+ 'type' => 'Place',
275
+ 'route' => 'console.fleet-ops.management.places.index.details',
276
+ 'models' => [$this->routeModel($place)],
277
+ 'breadcrumb' => 'Fleet-Ops > Resources > Places',
278
+ ]);
279
+ }
280
+
281
+ private function searchIssues(string $query, int $limit): Collection
282
+ {
283
+ return $this->searchGeneric(Issue::class, ['public_id', 'uuid', 'issue_id', 'category', 'type', 'report', 'title', 'priority', 'status'], $query, $limit, fn (Issue $issue) => [
284
+ 'label' => $issue->title ?: $issue->public_id,
285
+ 'description' => $this->description($issue->status, $issue->priority, $issue->type),
286
+ 'icon' => 'triangle-exclamation',
287
+ 'type' => 'Issue',
288
+ 'route' => 'console.fleet-ops.management.issues.index.details',
289
+ 'models' => [$this->routeModel($issue)],
290
+ 'breadcrumb' => 'Fleet-Ops > Resources > Issues',
291
+ ]);
292
+ }
293
+
294
+ private function searchFuelReports(string $query, int $limit): Collection
295
+ {
296
+ return $this->searchGeneric(FuelReport::class, ['public_id', 'uuid', 'report', 'status', 'currency'], $query, $limit, fn (FuelReport $report) => [
297
+ 'label' => $report->public_id ?: $report->report,
298
+ 'description' => $this->description($report->status, $report->currency, $report->report),
299
+ 'icon' => 'gas-pump',
300
+ 'type' => 'Fuel Report',
301
+ 'route' => 'console.fleet-ops.management.fuel-reports.index.details',
302
+ 'models' => [$this->routeModel($report)],
303
+ 'breadcrumb' => 'Fleet-Ops > Resources > Fuel Reports',
304
+ ]);
305
+ }
306
+
307
+ private function searchFuelTransactions(string $query, int $limit): Collection
308
+ {
309
+ return $this->searchGeneric(FuelProviderTransaction::class, ['public_id', 'provider', 'provider_transaction_id', 'vehicle_card_id', 'internal_number', 'plate_number', 'vin', 'serial_number', 'call_sign', 'station_name', 'trip_number', 'sync_status'], $query, $limit, fn (FuelProviderTransaction $transaction) => [
310
+ 'label' => $transaction->public_id ?: $transaction->provider_transaction_id,
311
+ 'description' => $this->description($transaction->provider, $transaction->station_name, $transaction->sync_status),
312
+ 'icon' => 'credit-card',
313
+ 'type' => 'Fuel Transaction',
314
+ 'route' => 'console.fleet-ops.management.fuel-transactions.index.details',
315
+ 'models' => [$this->routeModel($transaction)],
316
+ 'breadcrumb' => 'Fleet-Ops > Resources > Fuel Transactions',
317
+ ]);
318
+ }
319
+
320
+ private function searchMaintenanceSchedules(string $query, int $limit): Collection
321
+ {
322
+ return $this->searchGeneric(MaintenanceSchedule::class, ['name', 'type', 'status', 'public_id', 'uuid'], $query, $limit, fn (MaintenanceSchedule $schedule) => [
323
+ 'label' => $schedule->name ?: $schedule->public_id,
324
+ 'description' => $this->description($schedule->type, $schedule->status),
325
+ 'icon' => 'calendar-alt',
326
+ 'type' => 'Maintenance Schedule',
327
+ 'route' => 'console.fleet-ops.maintenance.schedules.index.details',
328
+ 'models' => [$this->routeModel($schedule)],
329
+ 'breadcrumb' => 'Fleet-Ops > Maintenance > Schedules',
330
+ ]);
331
+ }
332
+
333
+ private function searchWorkOrders(string $query, int $limit): Collection
334
+ {
335
+ return $this->searchGeneric(WorkOrder::class, ['code', 'subject', 'category', 'instructions', 'status', 'priority', 'public_id', 'uuid'], $query, $limit, fn (WorkOrder $workOrder) => [
336
+ 'label' => $workOrder->code ?: $workOrder->subject,
337
+ 'description' => $this->description($workOrder->status, $workOrder->priority, $workOrder->category ?: $workOrder->subject),
338
+ 'icon' => 'clipboard-list',
339
+ 'type' => 'Work Order',
340
+ 'route' => 'console.fleet-ops.maintenance.work-orders.index.details',
341
+ 'models' => [$this->routeModel($workOrder)],
342
+ 'breadcrumb' => 'Fleet-Ops > Maintenance > Work Orders',
343
+ ]);
344
+ }
345
+
346
+ private function searchMaintenances(string $query, int $limit): Collection
347
+ {
348
+ return $this->searchGeneric(Maintenance::class, ['summary', 'notes', 'type', 'status', 'priority', 'public_id', 'uuid'], $query, $limit, fn (Maintenance $maintenance) => [
349
+ 'label' => $maintenance->summary ?: $maintenance->public_id,
350
+ 'description' => $this->description($maintenance->status, $maintenance->priority, $maintenance->type),
351
+ 'icon' => 'history',
352
+ 'type' => 'Maintenance',
353
+ 'route' => 'console.fleet-ops.maintenance.maintenances.index.details',
354
+ 'models' => [$this->routeModel($maintenance)],
355
+ 'breadcrumb' => 'Fleet-Ops > Maintenance > Maintenances',
356
+ ]);
357
+ }
358
+
359
+ private function searchEquipment(string $query, int $limit): Collection
360
+ {
361
+ return $this->searchGeneric(Equipment::class, ['name', 'code', 'type', 'serial_number', 'manufacturer', 'model', 'public_id', 'uuid'], $query, $limit, fn (Equipment $equipment) => [
362
+ 'label' => $equipment->name ?: $equipment->code,
363
+ 'description' => $this->description($equipment->type, $equipment->manufacturer, $equipment->model),
364
+ 'icon' => 'trailer',
365
+ 'type' => 'Equipment',
366
+ 'route' => 'console.fleet-ops.maintenance.equipment.index.details',
367
+ 'models' => [$this->routeModel($equipment)],
368
+ 'breadcrumb' => 'Fleet-Ops > Maintenance > Equipment',
369
+ ]);
370
+ }
371
+
372
+ private function searchParts(string $query, int $limit): Collection
373
+ {
374
+ return $this->searchGeneric(Part::class, ['sku', 'name', 'manufacturer', 'model', 'serial_number', 'barcode', 'public_id', 'uuid'], $query, $limit, fn (Part $part) => [
375
+ 'label' => $part->name ?: $part->sku,
376
+ 'description' => $this->description($part->sku, $part->manufacturer, $part->model),
377
+ 'icon' => 'cog',
378
+ 'type' => 'Part',
379
+ 'route' => 'console.fleet-ops.maintenance.parts.index.details',
380
+ 'models' => [$this->routeModel($part)],
381
+ 'breadcrumb' => 'Fleet-Ops > Maintenance > Parts',
382
+ ]);
383
+ }
384
+
385
+ private function searchFuelProviders(string $query, int $limit): Collection
386
+ {
387
+ return $this->searchGeneric(FuelProviderConnection::class, ['public_id', 'name', 'provider', 'status', 'environment', 'uuid'], $query, $limit, fn (FuelProviderConnection $connection) => [
388
+ 'label' => $connection->name ?: $connection->provider,
389
+ 'description' => $this->description($connection->provider, $connection->status, $connection->environment),
390
+ 'icon' => 'gas-pump',
391
+ 'type' => 'Fuel Integration',
392
+ 'route' => 'console.fleet-ops.connectivity.fuel-providers.details',
393
+ 'models' => [$this->routeModel($connection)],
394
+ 'breadcrumb' => 'Fleet-Ops > Connectivity > Fuel Integrations',
395
+ ]);
396
+ }
397
+
398
+ private function searchTelematics(string $query, int $limit): Collection
399
+ {
400
+ return $this->searchGeneric(Telematic::class, ['name', 'provider', 'model', 'serial_number', 'imei', 'public_id', 'uuid'], $query, $limit, fn (Telematic $telematic) => [
401
+ 'label' => $telematic->name ?: $telematic->provider,
402
+ 'description' => $this->description($telematic->provider, $telematic->model, $telematic->serial_number),
403
+ 'icon' => 'satellite-dish',
404
+ 'type' => 'Telematic Provider',
405
+ 'route' => 'console.fleet-ops.connectivity.telematics.details',
406
+ 'models' => [$this->routeModel($telematic)],
407
+ 'breadcrumb' => 'Fleet-Ops > Connectivity > Telematics',
408
+ ]);
409
+ }
410
+
411
+ private function searchDevices(string $query, int $limit): Collection
412
+ {
413
+ return $this->searchGeneric(Device::class, ['name', 'model', 'serial_number', 'manufacturer', 'device_id', 'internal_id', 'imei', 'public_id', 'uuid'], $query, $limit, fn (Device $device) => [
414
+ 'label' => $device->name ?: $device->device_id,
415
+ 'description' => $this->description($device->manufacturer, $device->model, $device->serial_number),
416
+ 'icon' => 'hard-drive',
417
+ 'type' => 'Device',
418
+ 'route' => 'console.fleet-ops.connectivity.devices.index.details',
419
+ 'models' => [$this->routeModel($device)],
420
+ 'breadcrumb' => 'Fleet-Ops > Connectivity > Devices',
421
+ ]);
422
+ }
423
+
424
+ private function searchSensors(string $query, int $limit): Collection
425
+ {
426
+ return $this->searchGeneric(Sensor::class, ['name', 'type', 'internal_id', 'unit', 'public_id', 'uuid'], $query, $limit, fn (Sensor $sensor) => [
427
+ 'label' => $sensor->name ?: $sensor->internal_id,
428
+ 'description' => $this->description($sensor->type, $sensor->unit),
429
+ 'icon' => 'temperature-full',
430
+ 'type' => 'Sensor',
431
+ 'route' => 'console.fleet-ops.connectivity.sensors.index.details',
432
+ 'models' => [$this->routeModel($sensor)],
433
+ 'breadcrumb' => 'Fleet-Ops > Connectivity > Sensors',
434
+ ]);
435
+ }
436
+
437
+ private function searchEvents(string $query, int $limit): Collection
438
+ {
439
+ return $this->searchGeneric(DeviceEvent::class, ['event_type', 'message', 'ident', 'code', 'provider', 'severity', 'public_id', 'uuid'], $query, $limit, fn (DeviceEvent $event) => [
440
+ 'label' => $event->event_type ?: $event->public_id,
441
+ 'description' => $this->description($event->severity, $event->provider, $event->message),
442
+ 'icon' => 'stream',
443
+ 'type' => 'Device Event',
444
+ 'route' => 'console.fleet-ops.connectivity.events.details',
445
+ 'models' => [$this->routeModel($event)],
446
+ 'breadcrumb' => 'Fleet-Ops > Connectivity > Events',
447
+ ]);
448
+ }
449
+
450
+ private function searchServiceRates(string $query, int $limit): Collection
451
+ {
452
+ return $this->searchGeneric(ServiceRate::class, ['public_id', 'uuid', 'service_name', 'service_type', 'currency', 'algorithm', 'rate_calculation_method'], $query, $limit, fn (ServiceRate $serviceRate) => [
453
+ 'label' => $serviceRate->service_name ?: $serviceRate->public_id,
454
+ 'description' => $this->description($serviceRate->service_type, $serviceRate->currency, $serviceRate->rate_calculation_method),
455
+ 'icon' => 'file-invoice-dollar',
456
+ 'type' => 'Service Rate',
457
+ 'route' => 'console.fleet-ops.operations.service-rates.index.details',
458
+ 'models' => [$this->routeModel($serviceRate)],
459
+ 'breadcrumb' => 'Fleet-Ops > Operations > Service Rates',
460
+ ]);
461
+ }
462
+
463
+ private function searchOrderConfigs(string $query, int $limit): Collection
464
+ {
465
+ return $this->searchGeneric(OrderConfig::class, ['name', 'description', 'key', 'namespace', 'status', 'public_id', 'uuid'], $query, $limit, fn (OrderConfig $orderConfig) => [
466
+ 'label' => $orderConfig->name ?: $orderConfig->public_id,
467
+ 'description' => $this->description($orderConfig->status, $orderConfig->description, $orderConfig->namespace),
468
+ 'icon' => 'diagram-project',
469
+ 'type' => 'Order Config',
470
+ 'route' => 'console.fleet-ops.operations.order-config',
471
+ 'queryParams' => ['query' => $query],
472
+ 'breadcrumb' => 'Fleet-Ops > Operations > Order Config',
473
+ ]);
474
+ }
475
+
476
+ private function searchGeneric(string $modelClass, array $columns, string $query, int $limit, callable $mapper): Collection
477
+ {
478
+ return $modelClass::where('company_uuid', session('company'))
479
+ ->where(function (Builder $builder) use ($columns, $query) {
480
+ $this->whereLike($builder, $columns, $query);
481
+ })
482
+ ->limit($limit)
483
+ ->get()
484
+ ->map($mapper);
485
+ }
486
+
487
+ private function whereLike(Builder $builder, array $columns, string $query): void
488
+ {
489
+ $like = '%' . Str::replace(['%', '_'], ['\\%', '\\_'], $query) . '%';
490
+
491
+ foreach ($columns as $index => $column) {
492
+ $method = $index === 0 ? 'where' : 'orWhere';
493
+ $builder->{$method}($column, 'like', $like);
494
+ }
495
+ }
496
+
497
+ private function routeModel($model): ?string
498
+ {
499
+ return $model->public_id ?: $model->uuid;
500
+ }
501
+
502
+ private function description(...$parts): string
503
+ {
504
+ return trim(implode(' ', array_filter(array_map(fn ($part) => is_scalar($part) ? (string) $part : null, $parts))));
505
+ }
506
+ }
@@ -115,7 +115,7 @@ class ServiceQuoteController extends FleetOpsController
115
115
  ]);
116
116
  });
117
117
 
118
- $quote->setRelation('items', $items);
118
+ $quote->setRelation('items', $quote->items()->get());
119
119
 
120
120
  // if single quotation requested
121
121
  if ($single) {
@@ -161,7 +161,7 @@ class ServiceQuoteController extends FleetOpsController
161
161
  ]);
162
162
  });
163
163
 
164
- $quote->setRelation('items', $items);
164
+ $quote->setRelation('items', $quote->items()->get());
165
165
  $serviceQuotes->push($quote);
166
166
  }
167
167
 
@@ -298,7 +298,7 @@ class ServiceQuoteController extends FleetOpsController
298
298
  ]);
299
299
  });
300
300
 
301
- $quote->setRelation('items', $items);
301
+ $quote->setRelation('items', $quote->items()->get());
302
302
  $serviceQuotes->push($quote);
303
303
 
304
304
  // if requesting single
@@ -346,7 +346,7 @@ class ServiceQuoteController extends FleetOpsController
346
346
  ]);
347
347
  });
348
348
 
349
- $quote->setRelation('items', $items);
349
+ $quote->setRelation('items', $quote->items()->get());
350
350
  $serviceQuotes->push($quote);
351
351
  }
352
352
 
@@ -110,7 +110,7 @@ class TelematicController extends FleetOpsController
110
110
  $telematic = $this->findTelematic($id);
111
111
 
112
112
  $jobId = $this->telematicService->discoverDevices($telematic, [
113
- 'limit' => $request->input('limit', 100),
113
+ 'limit' => $request->input('limit'),
114
114
  'filters' => $request->input('filters', []),
115
115
  ]);
116
116