@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
@@ -16,6 +16,7 @@ use Illuminate\Http\JsonResponse;
16
16
  use Illuminate\Http\Request;
17
17
  use Illuminate\Support\Arr;
18
18
  use Illuminate\Support\Facades\DB;
19
+ use Illuminate\Support\Facades\Log;
19
20
  use Illuminate\Support\Str;
20
21
  use Maatwebsite\Excel\Facades\Excel;
21
22
 
@@ -179,12 +180,31 @@ class VehicleController extends FleetOpsController
179
180
  {
180
181
  $request->validate(['device' => 'required|string']);
181
182
 
182
- $vehicle = $this->findVehicle($id);
183
- $device = $this->findDevice($request->input('device'));
183
+ $deviceId = $request->input('device');
184
+ $vehicle = $this->resolveVehicle($id);
185
+ $device = $this->resolveDevice($deviceId);
184
186
 
185
- $device->attachTo($vehicle);
186
- $device->load(['telematic', 'warranty', 'attachable']);
187
- $vehicle->load(['driver', 'devices']);
187
+ if (!$vehicle) {
188
+ $this->logDeviceAttachmentLookupFailure('attach-device', 'vehicle', $id, $deviceId);
189
+
190
+ return response()->error('Vehicle not found or not available for this organization.', 404);
191
+ }
192
+
193
+ if (!$device) {
194
+ $this->logDeviceAttachmentLookupFailure('attach-device', 'device', $id, $deviceId);
195
+
196
+ return response()->error('Device not found or not available for this organization.', 404);
197
+ }
198
+
199
+ try {
200
+ $device->attachTo($vehicle);
201
+ $device->load(['telematic', 'warranty', 'attachable']);
202
+ $vehicle->load(['driver', 'devices']);
203
+ } catch (\Throwable $e) {
204
+ $this->logDeviceAttachmentFailure('attach-device', $vehicle, $device, $e);
205
+
206
+ return response()->error('Unable to attach device to vehicle. Please try again or contact support.', 500);
207
+ }
188
208
 
189
209
  return response()->json([
190
210
  'status' => 'ok',
@@ -198,16 +218,35 @@ class VehicleController extends FleetOpsController
198
218
  {
199
219
  $request->validate(['device' => 'required|string']);
200
220
 
201
- $vehicle = $this->findVehicle($id);
202
- $device = $this->findDevice($request->input('device'));
221
+ $deviceId = $request->input('device');
222
+ $vehicle = $this->resolveVehicle($id);
223
+ $device = $this->resolveDevice($deviceId);
224
+
225
+ if (!$vehicle) {
226
+ $this->logDeviceAttachmentLookupFailure('detach-device', 'vehicle', $id, $deviceId);
227
+
228
+ return response()->error('Vehicle not found or not available for this organization.', 404);
229
+ }
230
+
231
+ if (!$device) {
232
+ $this->logDeviceAttachmentLookupFailure('detach-device', 'device', $id, $deviceId);
233
+
234
+ return response()->error('Device not found or not available for this organization.', 404);
235
+ }
203
236
 
204
237
  if ($device->attachable_uuid !== $vehicle->uuid) {
205
238
  return response()->error('This device is not attached to the selected vehicle.');
206
239
  }
207
240
 
208
- $device->detach();
209
- $device->load(['telematic', 'warranty', 'attachable']);
210
- $vehicle->load(['driver', 'devices']);
241
+ try {
242
+ $device->detach();
243
+ $device->load(['telematic', 'warranty', 'attachable']);
244
+ $vehicle->load(['driver', 'devices']);
245
+ } catch (\Throwable $e) {
246
+ $this->logDeviceAttachmentFailure('detach-device', $vehicle, $device, $e);
247
+
248
+ return response()->error('Unable to detach device from vehicle. Please try again or contact support.', 500);
249
+ }
211
250
 
212
251
  return response()->json([
213
252
  'status' => 'ok',
@@ -226,6 +265,15 @@ class VehicleController extends FleetOpsController
226
265
  ->firstOrFail();
227
266
  }
228
267
 
268
+ protected function resolveVehicle(string $id): ?Vehicle
269
+ {
270
+ return Vehicle::where(function ($query) use ($id) {
271
+ $query->where('uuid', $id)->orWhere('public_id', $id);
272
+ })
273
+ ->where('company_uuid', session('company'))
274
+ ->first();
275
+ }
276
+
229
277
  protected function findDriver(string $id): Driver
230
278
  {
231
279
  return Driver::where(function ($query) use ($id) {
@@ -235,6 +283,42 @@ class VehicleController extends FleetOpsController
235
283
  ->firstOrFail();
236
284
  }
237
285
 
286
+ protected function resolveDevice(string $id): ?Device
287
+ {
288
+ return Device::where(function ($query) use ($id) {
289
+ $query->where('uuid', $id)->orWhere('public_id', $id);
290
+ })
291
+ ->where('company_uuid', session('company'))
292
+ ->first();
293
+ }
294
+
295
+ protected function logDeviceAttachmentLookupFailure(string $action, string $missingResource, string $vehicleId, ?string $deviceId): void
296
+ {
297
+ Log::warning('Vehicle device attachment lookup failed', [
298
+ 'action' => $action,
299
+ 'missing_resource' => $missingResource,
300
+ 'vehicle_id' => $vehicleId,
301
+ 'device_id' => $deviceId,
302
+ 'company_uuid' => session('company'),
303
+ 'request_id' => request()->headers->get('X-Request-ID') ?? request()->headers->get('X-Correlation-ID'),
304
+ ]);
305
+ }
306
+
307
+ protected function logDeviceAttachmentFailure(string $action, Vehicle $vehicle, Device $device, \Throwable $exception): void
308
+ {
309
+ Log::error('Vehicle device attachment failed', [
310
+ 'action' => $action,
311
+ 'vehicle_uuid' => $vehicle->uuid,
312
+ 'vehicle_id' => $vehicle->public_id,
313
+ 'device_uuid' => $device->uuid,
314
+ 'device_id' => $device->public_id,
315
+ 'company_uuid' => session('company'),
316
+ 'request_id' => request()->headers->get('X-Request-ID') ?? request()->headers->get('X-Correlation-ID'),
317
+ 'exception_class' => get_class($exception),
318
+ 'exception' => $exception->getMessage(),
319
+ ]);
320
+ }
321
+
238
322
  protected function activeOrderUuid(Vehicle $vehicle): ?string
239
323
  {
240
324
  $vehicle->loadMissing('driver.currentOrder');
@@ -44,6 +44,50 @@ class DeviceEventFilter extends Filter
44
44
  });
45
45
  }
46
46
 
47
+ public function deviceUuid(?string $device)
48
+ {
49
+ $this->device($device);
50
+ }
51
+
52
+ public function severity(string|array $severity)
53
+ {
54
+ $severity = Utils::arrayFrom($severity);
55
+
56
+ if ($severity) {
57
+ $this->builder->whereIn('severity', $severity);
58
+ }
59
+ }
60
+
61
+ public function processed(string|array $processed)
62
+ {
63
+ $states = Utils::arrayFrom($processed);
64
+
65
+ if (!$states) {
66
+ return;
67
+ }
68
+
69
+ $this->builder->where(function ($query) use ($states) {
70
+ foreach ($states as $state) {
71
+ match ($state) {
72
+ 'processed' => $query->orWhereNotNull('processed_at'),
73
+ 'unprocessed' => $query->orWhereNull('processed_at'),
74
+ default => null,
75
+ };
76
+ }
77
+ });
78
+ }
79
+
80
+ public function occurredAt($occurredAt)
81
+ {
82
+ $occurredAt = Utils::dateRange($occurredAt);
83
+
84
+ if (is_array($occurredAt)) {
85
+ $this->builder->whereBetween('occurred_at', $occurredAt);
86
+ } else {
87
+ $this->builder->whereDate('occurred_at', $occurredAt);
88
+ }
89
+ }
90
+
47
91
  public function createdAt($createdAt)
48
92
  {
49
93
  $createdAt = Utils::dateRange($createdAt);
@@ -0,0 +1,131 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Filter;
4
+
5
+ use Fleetbase\FleetOps\Support\Utils;
6
+ use Fleetbase\Http\Filter\Filter;
7
+
8
+ class DeviceFilter extends Filter
9
+ {
10
+ public function queryForInternal()
11
+ {
12
+ $this->builder->where('company_uuid', $this->session->get('company'));
13
+ }
14
+
15
+ public function queryForPublic()
16
+ {
17
+ $this->queryForInternal();
18
+ }
19
+
20
+ public function query(?string $searchQuery)
21
+ {
22
+ $this->builder->search($searchQuery);
23
+ }
24
+
25
+ public function status(string|array $status)
26
+ {
27
+ $status = Utils::arrayFrom($status);
28
+
29
+ if ($status) {
30
+ $this->builder->whereIn('status', $status);
31
+ }
32
+ }
33
+
34
+ public function deviceId(?string $deviceId)
35
+ {
36
+ if ($deviceId) {
37
+ $this->builder->where('device_id', 'like', '%' . $deviceId . '%');
38
+ }
39
+ }
40
+
41
+ public function telematic(?string $telematic)
42
+ {
43
+ $this->builder->where('telematic_uuid', $telematic);
44
+ }
45
+
46
+ public function telematicUuid(?string $telematic)
47
+ {
48
+ $this->telematic($telematic);
49
+ }
50
+
51
+ public function provider(?string $provider)
52
+ {
53
+ $this->builder->where('provider', $provider);
54
+ }
55
+
56
+ public function warrantyUuid(?string $warranty)
57
+ {
58
+ $this->builder->where('warranty_uuid', $warranty);
59
+ }
60
+
61
+ public function attachableType(?string $attachableType)
62
+ {
63
+ $this->builder->where('attachable_type', $attachableType);
64
+ }
65
+
66
+ public function attachableUuid(?string $attachable)
67
+ {
68
+ $this->builder->where('attachable_uuid', $attachable);
69
+ }
70
+
71
+ public function vehicle(?string $vehicle)
72
+ {
73
+ if ($vehicle) {
74
+ $this->builder->where('attachable_uuid', $vehicle);
75
+ }
76
+ }
77
+
78
+ public function connectionStatus(string|array $connectionStatus)
79
+ {
80
+ $statuses = Utils::arrayFrom($connectionStatus);
81
+
82
+ if (!$statuses) {
83
+ return;
84
+ }
85
+
86
+ $this->builder->where(function ($query) use ($statuses) {
87
+ foreach ($statuses as $status) {
88
+ match ($status) {
89
+ 'online' => $query->orWhere('last_online_at', '>=', now()->subMinutes(10)),
90
+ 'recently_offline' => $query->orWhereBetween('last_online_at', [now()->subMinutes(60), now()->subMinutes(10)]),
91
+ 'offline' => $query->orWhereBetween('last_online_at', [now()->subDay(), now()->subMinutes(60)]),
92
+ 'long_offline' => $query->orWhere('last_online_at', '<', now()->subDay()),
93
+ 'never_connected' => $query->orWhereNull('last_online_at'),
94
+ default => null,
95
+ };
96
+ }
97
+ });
98
+ }
99
+
100
+ public function attachmentState(?string $attachmentState)
101
+ {
102
+ if ($attachmentState === 'attached') {
103
+ $this->builder->whereNotNull('attachable_uuid');
104
+ }
105
+
106
+ if ($attachmentState === 'unattached') {
107
+ $this->builder->whereNull('attachable_uuid');
108
+ }
109
+ }
110
+
111
+ public function lastOnlineAt(string|array $lastOnlineAt)
112
+ {
113
+ $this->filterDate('last_online_at', $lastOnlineAt);
114
+ }
115
+
116
+ public function updatedAt(string|array $updatedAt)
117
+ {
118
+ $this->filterDate('updated_at', $updatedAt);
119
+ }
120
+
121
+ protected function filterDate(string $column, string|array $value): void
122
+ {
123
+ $dates = Utils::dateRange($value);
124
+
125
+ if (is_array($dates)) {
126
+ $this->builder->whereBetween($column, $dates);
127
+ } else {
128
+ $this->builder->whereDate($column, $dates);
129
+ }
130
+ }
131
+ }
@@ -70,6 +70,12 @@ class DriverFilter extends Filter
70
70
 
71
71
  public function vehicle(string $vehicle)
72
72
  {
73
+ if ($vehicle === 'unassigned') {
74
+ $this->builder->whereNull('vehicle_uuid');
75
+
76
+ return;
77
+ }
78
+
73
79
  if (Str::isUuid($vehicle)) {
74
80
  $this->builder->where('vehicle_uuid', $vehicle);
75
81
  } else {
@@ -0,0 +1,125 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Filter;
4
+
5
+ use Fleetbase\FleetOps\Support\Utils;
6
+ use Fleetbase\Http\Filter\Filter;
7
+
8
+ class SensorFilter extends Filter
9
+ {
10
+ public function queryForInternal()
11
+ {
12
+ $this->builder->where('company_uuid', $this->session->get('company'));
13
+ }
14
+
15
+ public function queryForPublic()
16
+ {
17
+ $this->queryForInternal();
18
+ }
19
+
20
+ public function query(?string $searchQuery)
21
+ {
22
+ $this->builder->search($searchQuery);
23
+ }
24
+
25
+ public function type(string|array $type)
26
+ {
27
+ $types = Utils::arrayFrom($type);
28
+
29
+ if ($types) {
30
+ $this->builder->whereIn('type', $types);
31
+ }
32
+ }
33
+
34
+ public function sensorType(string|array $type)
35
+ {
36
+ $this->type($type);
37
+ }
38
+
39
+ public function status(string|array $status)
40
+ {
41
+ $statuses = Utils::arrayFrom($status);
42
+
43
+ if ($statuses) {
44
+ $this->builder->whereIn('status', $statuses);
45
+ }
46
+ }
47
+
48
+ public function device(?string $device)
49
+ {
50
+ if ($device) {
51
+ $this->builder->where('device_uuid', $device);
52
+ }
53
+ }
54
+
55
+ public function deviceUuid(?string $device)
56
+ {
57
+ $this->device($device);
58
+ }
59
+
60
+ public function telematic(?string $telematic)
61
+ {
62
+ if ($telematic) {
63
+ $this->builder->where('telematic_uuid', $telematic);
64
+ }
65
+ }
66
+
67
+ public function telematicUuid(?string $telematic)
68
+ {
69
+ $this->telematic($telematic);
70
+ }
71
+
72
+ public function warrantyUuid(?string $warranty)
73
+ {
74
+ if ($warranty) {
75
+ $this->builder->where('warranty_uuid', $warranty);
76
+ }
77
+ }
78
+
79
+ public function sensorableType(?string $sensorableType)
80
+ {
81
+ if ($sensorableType) {
82
+ $this->builder->where('sensorable_type', $sensorableType);
83
+ }
84
+ }
85
+
86
+ public function serialNumber(?string $serialNumber)
87
+ {
88
+ if ($serialNumber) {
89
+ $this->builder->where('serial_number', 'like', '%' . $serialNumber . '%');
90
+ }
91
+ }
92
+
93
+ public function imei(?string $imei)
94
+ {
95
+ if ($imei) {
96
+ $this->builder->where('imei', 'like', '%' . $imei . '%');
97
+ }
98
+ }
99
+
100
+ public function lastReadingAt(string|array $lastReadingAt)
101
+ {
102
+ $this->filterDate('last_reading_at', $lastReadingAt);
103
+ }
104
+
105
+ public function createdAt(string|array $createdAt)
106
+ {
107
+ $this->filterDate('created_at', $createdAt);
108
+ }
109
+
110
+ public function updatedAt(string|array $updatedAt)
111
+ {
112
+ $this->filterDate('updated_at', $updatedAt);
113
+ }
114
+
115
+ protected function filterDate(string $column, string|array $value): void
116
+ {
117
+ $dates = Utils::dateRange($value);
118
+
119
+ if (is_array($dates)) {
120
+ $this->builder->whereBetween($column, $dates);
121
+ } else {
122
+ $this->builder->whereDate($column, $dates);
123
+ }
124
+ }
125
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  namespace Fleetbase\FleetOps\Http\Filter;
4
4
 
5
+ use Fleetbase\FleetOps\Models\Vehicle;
5
6
  use Fleetbase\FleetOps\Support\Utils;
6
7
  use Fleetbase\Http\Filter\Filter;
7
8
 
@@ -59,6 +60,12 @@ class VehicleFilter extends Filter
59
60
 
60
61
  public function driver(?string $driverId)
61
62
  {
63
+ if ($driverId === 'unassigned') {
64
+ $this->builder->whereDoesntHave('driver');
65
+
66
+ return;
67
+ }
68
+
62
69
  $this->builder->whereHas(
63
70
  'driver',
64
71
  function ($query) use ($driverId) {
@@ -125,4 +132,16 @@ class VehicleFilter extends Filter
125
132
  $this->builder->whereDoesntHave('fleets');
126
133
  }
127
134
  }
135
+
136
+ public function telematicUuid(?string $telematic)
137
+ {
138
+ if (!$telematic) {
139
+ return;
140
+ }
141
+
142
+ $this->builder->whereHas('devices', function ($query) use ($telematic) {
143
+ $query->where('telematic_uuid', $telematic);
144
+ $query->whereIn('attachable_type', ['fleet-ops:vehicle', Vehicle::class]);
145
+ });
146
+ }
128
147
  }
@@ -28,19 +28,20 @@ class CreateDriverRequest extends FleetbaseRequest
28
28
  $isCreating = $this->isMethod('POST');
29
29
 
30
30
  return [
31
- 'name' => [Rule::requiredIf($isCreating)],
32
- 'email' => [Rule::requiredIf($isCreating), Rule::when($this->filled('email'), ['email']), Rule::when($isCreating, [Rule::unique('users')->whereNull('deleted_at')])],
33
- 'phone' => [Rule::requiredIf($isCreating), Rule::when($isCreating, [Rule::unique('users')->whereNull('deleted_at')])],
34
- 'password' => 'nullable|string',
35
- 'country' => 'nullable|size:2',
36
- 'city' => 'nullable|string',
37
- 'vehicle' => 'nullable|string|starts_with:vehicle_|exists:vehicles,public_id',
38
- 'status' => 'nullable|string|in:active,available,inactive',
39
- 'vendor' => 'nullable|exists:vendors,public_id',
40
- 'job' => 'nullable|exists:orders,public_id',
41
- 'location' => ['nullable', new ResolvablePoint()],
42
- 'latitude' => ['nullable', 'required_with:longitude'],
43
- 'longitude' => ['nullable', 'required_with:latitude'],
31
+ 'name' => [Rule::requiredIf($isCreating)],
32
+ 'email' => [Rule::requiredIf($isCreating), Rule::when($this->filled('email'), ['email']), Rule::when($isCreating, [Rule::unique('users')->whereNull('deleted_at')])],
33
+ 'phone' => [Rule::requiredIf($isCreating), Rule::when($isCreating, [Rule::unique('users')->whereNull('deleted_at')])],
34
+ 'password' => 'nullable|string',
35
+ 'country' => 'nullable|size:2',
36
+ 'city' => 'nullable|string',
37
+ 'vehicle' => 'nullable|string|starts_with:vehicle_|exists:vehicles,public_id',
38
+ 'license_expiry' => 'nullable|date',
39
+ 'status' => 'nullable|string|in:active,available,inactive',
40
+ 'vendor' => 'nullable|exists:vendors,public_id',
41
+ 'job' => 'nullable|exists:orders,public_id',
42
+ 'location' => ['nullable', new ResolvablePoint()],
43
+ 'latitude' => ['nullable', 'required_with:longitude'],
44
+ 'longitude' => ['nullable', 'required_with:latitude'],
44
45
  ];
45
46
  }
46
47
 
@@ -51,6 +51,7 @@ class CreateDriverRequest extends CreateDriverApiRequest
51
51
  'country' => 'nullable|string|size:2',
52
52
  'city' => 'nullable|string|max:255',
53
53
  'vehicle' => ['nullable', new ResolvableVehicle()],
54
+ 'license_expiry' => 'nullable|date',
54
55
  'status' => 'nullable|string|in:active,available,inactive',
55
56
  'vendor' => 'nullable|exists:vendors,public_id',
56
57
  'job' => 'nullable|exists:orders,public_id',
@@ -38,7 +38,7 @@ class Driver extends FleetbaseResource
38
38
  'email' => $this->email,
39
39
  'phone' => $this->phone,
40
40
  'drivers_license_number' => $this->drivers_license_number,
41
- 'license_expiry' => $this->license_expiry,
41
+ 'license_expiry' => $this->formatDateOnly($this->license_expiry),
42
42
  'photo_url' => $this->photo_url,
43
43
  'avatar_url' => $this->avatar_url,
44
44
  'avatar_value' => $this->when(Http::isInternalRequest(), $this->getOriginal('avatar_url')),
@@ -96,6 +96,15 @@ class Driver extends FleetbaseResource
96
96
  );
97
97
  }
98
98
 
99
+ protected function formatDateOnly($date): ?string
100
+ {
101
+ if (!$date) {
102
+ return null;
103
+ }
104
+
105
+ return method_exists($date, 'toDateString') ? $date->toDateString() : (string) $date;
106
+ }
107
+
99
108
  /**
100
109
  * Transform the resource into an webhook payload.
101
110
  *
@@ -110,7 +119,7 @@ class Driver extends FleetbaseResource
110
119
  'email' => $this->email,
111
120
  'phone' => $this->phone,
112
121
  'photo_url' => $this->photo_url,
113
- 'license_expiry' => $this->license_expiry,
122
+ 'license_expiry' => $this->formatDateOnly($this->license_expiry),
114
123
  'vehicle' => data_get($this, 'vehicle.public_id'),
115
124
  'current_job' => data_get($this, 'currentJob.public_id'),
116
125
  'vendor' => data_get($this, 'vendor.public_id'),
@@ -41,6 +41,7 @@ class WorkOrder extends FleetbaseResource
41
41
  // Core attributes
42
42
  'code' => $this->code,
43
43
  'subject' => $this->subject,
44
+ 'category' => $this->category,
44
45
  'status' => $this->status,
45
46
  'priority' => $this->priority,
46
47
  'instructions' => $this->instructions,
@@ -0,0 +1,47 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Jobs;
4
+
5
+ use Fleetbase\FleetOps\Events\OrderReady;
6
+ use Fleetbase\FleetOps\Models\Order;
7
+ use Fleetbase\FleetOps\Models\ServiceQuote;
8
+ use Illuminate\Bus\Queueable;
9
+ use Illuminate\Contracts\Queue\ShouldQueue;
10
+ use Illuminate\Foundation\Bus\Dispatchable;
11
+ use Illuminate\Queue\InteractsWithQueue;
12
+ use Illuminate\Queue\SerializesModels;
13
+
14
+ class FinalizeApiOrderCreation implements ShouldQueue
15
+ {
16
+ use Dispatchable;
17
+ use InteractsWithQueue;
18
+ use Queueable;
19
+ use SerializesModels;
20
+
21
+ public function __construct(
22
+ public string $orderUuid,
23
+ public ?string $serviceQuoteUuid = null,
24
+ public bool $shouldDispatch = false,
25
+ ) {
26
+ }
27
+
28
+ public function handle(): void
29
+ {
30
+ $order = Order::where('uuid', $this->orderUuid)->first();
31
+ if (!$order) {
32
+ return;
33
+ }
34
+
35
+ $serviceQuote = $this->serviceQuoteUuid ? ServiceQuote::where('uuid', $this->serviceQuoteUuid)->first() : null;
36
+
37
+ $order->notifyDriverAssigned();
38
+ $order->setPreliminaryDistanceAndTime();
39
+ $order->purchaseServiceQuote($serviceQuote);
40
+
41
+ if ($this->shouldDispatch) {
42
+ $order->dispatchWithActivity();
43
+ }
44
+
45
+ event(new OrderReady($order));
46
+ }
47
+ }
@@ -0,0 +1,35 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Jobs;
4
+
5
+ use Fleetbase\FleetOps\Events\OrderReady;
6
+ use Fleetbase\FleetOps\Models\Order;
7
+ use Illuminate\Bus\Queueable;
8
+ use Illuminate\Contracts\Queue\ShouldQueue;
9
+ use Illuminate\Foundation\Bus\Dispatchable;
10
+ use Illuminate\Queue\InteractsWithQueue;
11
+ use Illuminate\Queue\SerializesModels;
12
+
13
+ class FinalizeInternalOrderCreation implements ShouldQueue
14
+ {
15
+ use Dispatchable;
16
+ use InteractsWithQueue;
17
+ use Queueable;
18
+ use SerializesModels;
19
+
20
+ public function __construct(public string $orderUuid)
21
+ {
22
+ }
23
+
24
+ public function handle(): void
25
+ {
26
+ $order = Order::where('uuid', $this->orderUuid)->first();
27
+ if (!$order) {
28
+ return;
29
+ }
30
+
31
+ $order->notifyDriverAssigned();
32
+
33
+ event(new OrderReady($order));
34
+ }
35
+ }