@fleetbase/fleetops-engine 0.6.53 → 0.6.55

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 (212) hide show
  1. package/addon/components/admin/routing-settings.hbs +2 -1
  2. package/addon/components/cell/attached-vehicle.hbs +10 -0
  3. package/addon/components/cell/attached-vehicle.js +58 -0
  4. package/addon/components/cell/device-identity.hbs +24 -0
  5. package/addon/components/cell/device-identity.js +129 -0
  6. package/addon/components/cell/driver-identity.hbs +33 -0
  7. package/addon/components/cell/driver-identity.js +143 -0
  8. package/addon/components/cell/equipment-identity.hbs +5 -0
  9. package/addon/components/cell/equipment-identity.js +48 -0
  10. package/addon/components/cell/part-identity.hbs +5 -0
  11. package/addon/components/cell/part-identity.js +63 -0
  12. package/addon/components/cell/telematic-device.hbs +38 -0
  13. package/addon/components/cell/telematic-device.js +44 -0
  14. package/addon/components/cell/telematic-provider.hbs +33 -16
  15. package/addon/components/cell/telematic-provider.js +50 -8
  16. package/addon/components/cell/vehicle-identity.hbs +33 -0
  17. package/addon/components/cell/vehicle-identity.js +138 -0
  18. package/addon/components/device/details.hbs +106 -135
  19. package/addon/components/device/details.js +174 -1
  20. package/addon/components/device/form.hbs +2 -2
  21. package/addon/components/device/form.js +7 -0
  22. package/addon/components/device/panel-header.hbs +46 -21
  23. package/addon/components/device/panel-header.js +49 -1
  24. package/addon/components/device/panel-tabs/events.hbs +30 -0
  25. package/addon/components/device/panel-tabs/events.js +128 -0
  26. package/addon/components/device/panel-tabs/sensors.hbs +30 -0
  27. package/addon/components/device/panel-tabs/sensors.js +93 -0
  28. package/addon/components/device/panel-tabs/vehicle.hbs +68 -0
  29. package/addon/components/device/panel-tabs/vehicle.js +90 -0
  30. package/addon/components/equipment/form.js +8 -1
  31. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +76 -11
  32. package/addon/components/layout/fleet-ops-sidebar.hbs +1 -1
  33. package/addon/components/layout/fleet-ops-sidebar.js +101 -17
  34. package/addon/components/map/drawer/device-event-listing.js +2 -0
  35. package/addon/components/map/drawer/driver-listing.js +2 -0
  36. package/addon/components/map/drawer/place-listing.js +2 -0
  37. package/addon/components/map/drawer/position-listing.hbs +11 -6
  38. package/addon/components/map/drawer/position-listing.js +17 -4
  39. package/addon/components/map/drawer/vehicle-listing.js +2 -0
  40. package/addon/components/order/form/details.hbs +1 -43
  41. package/addon/components/order/form/details.js +18 -53
  42. package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
  43. package/addon/components/order/form/orchestrator-constraints.js +61 -0
  44. package/addon/components/order/form/payload.js +48 -2
  45. package/addon/components/order/form/route.js +15 -0
  46. package/addon/components/order/form/service-rate.hbs +62 -11
  47. package/addon/components/order/form/service-rate.js +124 -2
  48. package/addon/components/order/form.hbs +2 -0
  49. package/addon/components/sensor/panel-header.hbs +47 -15
  50. package/addon/components/sensor/panel-header.js +59 -1
  51. package/addon/components/table/cell/dropdown/action-item.hbs +31 -0
  52. package/addon/components/table/cell/dropdown/action-item.js +62 -0
  53. package/addon/components/table/cell/dropdown.hbs +28 -0
  54. package/addon/components/table/cell/dropdown.js +91 -0
  55. package/addon/components/telematic/details.hbs +7 -1
  56. package/addon/components/telematic/form.js +16 -8
  57. package/addon/components/vendor/panel-header.hbs +82 -0
  58. package/addon/components/vendor/panel-header.js +48 -0
  59. package/addon/components/work-order/details.hbs +5 -1
  60. package/addon/components/work-order/form.hbs +26 -4
  61. package/addon/components/work-order/form.js +5 -1
  62. package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
  63. package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
  64. package/addon/controllers/connectivity/devices/index/details/vehicle.js +122 -0
  65. package/addon/controllers/connectivity/devices/index/details.js +12 -0
  66. package/addon/controllers/connectivity/devices/index.js +144 -129
  67. package/addon/controllers/connectivity/events/details.js +3 -0
  68. package/addon/controllers/connectivity/events/index.js +187 -11
  69. package/addon/controllers/connectivity/sensors/index.js +156 -14
  70. package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
  71. package/addon/controllers/connectivity/telematics/details/devices.js +170 -129
  72. package/addon/controllers/connectivity/telematics/details/events.js +39 -11
  73. package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
  74. package/addon/controllers/maintenance/equipment/index.js +1 -2
  75. package/addon/controllers/maintenance/parts/index.js +1 -2
  76. package/addon/controllers/maintenance/work-orders/index.js +18 -1
  77. package/addon/controllers/management/drivers/index.js +82 -42
  78. package/addon/controllers/management/fuel-reports/index.js +63 -4
  79. package/addon/controllers/management/issues/index.js +63 -4
  80. package/addon/controllers/management/vehicles/index.js +33 -6
  81. package/addon/routes/connectivity/devices/index/details/events.js +37 -1
  82. package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
  83. package/addon/routes/connectivity/devices/index/details/vehicle.js +29 -0
  84. package/addon/routes/connectivity/devices/index.js +7 -0
  85. package/addon/routes/connectivity/events/details.js +3 -0
  86. package/addon/routes/connectivity/events/index.js +6 -2
  87. package/addon/routes/connectivity/sensors/index.js +7 -2
  88. package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
  89. package/addon/routes/connectivity/telematics/details/devices.js +6 -0
  90. package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
  91. package/addon/routes/connectivity/telematics/details.js +1 -1
  92. package/addon/routes/connectivity/telematics/edit.js +1 -1
  93. package/addon/routes/maintenance/work-orders/index.js +1 -0
  94. package/addon/routes.js +2 -0
  95. package/addon/services/device-actions.js +30 -0
  96. package/addon/services/device-event-actions.js +1 -1
  97. package/addon/services/equipment-actions.js +1 -0
  98. package/addon/services/order-creation.js +54 -1
  99. package/addon/services/order-validation.js +3 -2
  100. package/addon/services/sensor-actions.js +3 -0
  101. package/addon/services/service-rate-actions.js +40 -1
  102. package/addon/services/vehicle-actions.js +24 -12
  103. package/addon/services/vendor-actions.js +3 -0
  104. package/addon/styles/fleetops-engine.css +28 -4
  105. package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
  106. package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
  107. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +147 -0
  108. package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
  109. package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
  110. package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
  111. package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
  112. package/addon/templates/management/vehicles/index/edit.hbs +1 -1
  113. package/addon/templates/management/vendors/index/details.hbs +2 -2
  114. package/addon/templates/settings/notifications.hbs +2 -1
  115. package/addon/templates/settings/routing.hbs +1 -0
  116. package/addon/utils/device-table-columns.js +288 -0
  117. package/addon/utils/fleet-ops-options.js +38 -0
  118. package/addon/utils/identity-cell-resource.js +20 -0
  119. package/addon/utils/map-drawer-dropdown-position.js +47 -0
  120. package/addon/utils/trackable-option.js +93 -0
  121. package/app/components/cell/attached-vehicle.js +1 -0
  122. package/app/components/cell/device-identity.js +1 -0
  123. package/app/components/cell/driver-identity.js +1 -0
  124. package/app/components/cell/equipment-identity.js +1 -0
  125. package/app/components/cell/part-identity.js +1 -0
  126. package/app/components/cell/telematic-device.js +1 -0
  127. package/app/components/cell/vehicle-identity.js +1 -0
  128. package/app/components/device/panel-tabs/events.hbs +30 -0
  129. package/app/components/device/panel-tabs/events.js +128 -0
  130. package/app/components/device/panel-tabs/sensors.hbs +30 -0
  131. package/app/components/device/panel-tabs/sensors.js +93 -0
  132. package/app/components/device/panel-tabs/vehicle.hbs +68 -0
  133. package/app/components/device/panel-tabs/vehicle.js +90 -0
  134. package/app/components/order/form/orchestrator-constraints.js +1 -0
  135. package/app/components/table/cell/dropdown/action-item.js +1 -0
  136. package/app/components/table/cell/dropdown.js +1 -0
  137. package/app/components/vendor/panel-header.js +1 -0
  138. package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
  139. package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
  140. package/app/controllers/connectivity/events/{index/details.js → details.js} +1 -1
  141. package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
  142. package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
  143. package/app/routes/connectivity/events/{index/details.js → details.js} +1 -1
  144. package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
  145. package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
  146. package/app/templates/connectivity/events/{index/details.js → details.js} +1 -1
  147. package/app/utils/map-drawer-dropdown-position.js +1 -0
  148. package/app/utils/trackable-option.js +1 -0
  149. package/composer.json +1 -1
  150. package/extension.json +1 -1
  151. package/package.json +4 -4
  152. package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
  153. package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
  154. package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
  155. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
  156. package/server/src/Console/Commands/SyncTelematics.php +89 -0
  157. package/server/src/Exceptions/TelematicProviderException.php +13 -2
  158. package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
  159. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +55 -1
  160. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +17 -6
  161. package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
  162. package/server/src/Http/Controllers/Internal/v1/LiveController.php +178 -1
  163. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
  164. package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
  165. package/server/src/Http/Controllers/Internal/v1/SearchController.php +2 -2
  166. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
  167. package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +82 -13
  168. package/server/src/Http/Filter/DeviceEventFilter.php +65 -0
  169. package/server/src/Http/Filter/DeviceFilter.php +80 -0
  170. package/server/src/Http/Filter/SensorFilter.php +125 -0
  171. package/server/src/Http/Filter/VehicleFilter.php +13 -0
  172. package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
  173. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
  174. package/server/src/Http/Resources/v1/Driver.php +11 -2
  175. package/server/src/Http/Resources/v1/Index/Vehicle.php +19 -0
  176. package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
  177. package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
  178. package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
  179. package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
  180. package/server/src/Jobs/SyncTelematicDevicesJob.php +180 -123
  181. package/server/src/Models/Device.php +17 -1
  182. package/server/src/Models/DeviceEvent.php +82 -1
  183. package/server/src/Models/Driver.php +16 -0
  184. package/server/src/Models/Equipment.php +55 -0
  185. package/server/src/Models/Sensor.php +15 -2
  186. package/server/src/Models/Telematic.php +1 -1
  187. package/server/src/Models/Vehicle.php +5 -1
  188. package/server/src/Models/WorkOrder.php +5 -2
  189. package/server/src/Observers/DriverObserver.php +3 -3
  190. package/server/src/Observers/FleetObserver.php +23 -0
  191. package/server/src/Observers/VehicleObserver.php +3 -3
  192. package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
  193. package/server/src/Support/LiveCacheService.php +1 -1
  194. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +143 -20
  195. package/server/src/Support/Telematics/Providers/FlespiProvider.php +89 -15
  196. package/server/src/Support/Telematics/Providers/GeotabProvider.php +123 -35
  197. package/server/src/Support/Telematics/Providers/SafeeProvider.php +70 -5
  198. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +122 -14
  199. package/server/src/Support/Telematics/TelematicService.php +330 -30
  200. package/server/src/routes.php +1 -1
  201. package/server/tests/DeviceFilterTest.php +45 -0
  202. package/server/tests/DriverVehicleStatusDefaultTest.php +19 -2
  203. package/server/tests/EquipmentAssignmentMorphTypeTest.php +35 -0
  204. package/server/tests/OperationsMonitorEndpointTest.php +38 -0
  205. package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
  206. package/server/tests/ServiceRateRouteValidationTest.php +101 -0
  207. package/server/tests/TelematicsHardeningTest.php +598 -6
  208. package/server/tests/VehicleDriverAssignmentTest.php +27 -0
  209. package/translations/en-us.yaml +1 -0
  210. package/addon/controllers/connectivity/events/index/details.js +0 -3
  211. package/addon/routes/connectivity/events/index/details.js +0 -3
  212. /package/addon/templates/connectivity/events/{index/details.hbs → details.hbs} +0 -0
@@ -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
 
@@ -8,6 +8,7 @@ use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
8
8
  use Fleetbase\FleetOps\Models\ServiceRate;
9
9
  use Fleetbase\Http\Requests\ExportRequest;
10
10
  use Illuminate\Http\Request;
11
+ use Illuminate\Support\Collection;
11
12
  use Illuminate\Support\Str;
12
13
  use Maatwebsite\Excel\Facades\Excel;
13
14
 
@@ -27,24 +28,30 @@ class ServiceRateController extends FleetOpsController
27
28
  */
28
29
  public function getServicesForRoute(Request $request)
29
30
  {
30
- $coordinates = explode(';', $request->input('coordinates')); // ex. 1.3621663,103.8845049;1.353151,103.86458
31
+ $coordinates = $request->input('coordinates');
31
32
 
32
- // convert coordinates to points
33
- $waypoints = collect($coordinates)->map(
34
- function ($coord) {
35
- $coord = explode(',', $coord);
36
- [$latitude, $longitude] = $coord;
33
+ if (!is_string($coordinates) || trim($coordinates) === '') {
34
+ return response()->error('Route coordinates are required to query service rates.', 422);
35
+ }
37
36
 
38
- return Point::fromText("POINT($longitude $latitude)", 4326);
39
- }
40
- );
37
+ $waypoints = $this->parseRouteCoordinates($coordinates);
38
+
39
+ if ($waypoints === null) {
40
+ return response()->error('Invalid route coordinates provided.', 422);
41
+ }
42
+
43
+ if ($waypoints->count() < 2) {
44
+ return response()->error('At least two route coordinates are required to query service rates.', 422);
45
+ }
41
46
 
42
- $applicableServiceRates = ServiceRate::getServicableForWaypoints(
47
+ $serviceType = $this->normalizeOptionalQueryValue($request->input('service_type'));
48
+
49
+ $applicableServiceRates = $this->getServicableForWaypoints(
43
50
  $waypoints,
44
- function ($query) use ($request) {
51
+ function ($query) use ($request, $serviceType) {
45
52
  $query->where('company_uuid', $request->session()->get('company'));
46
- if ($request->filled('service_type')) {
47
- $query->where('service_type', $request->input('service_type'));
53
+ if ($serviceType) {
54
+ $query->where('service_type', $serviceType);
48
55
  }
49
56
  }
50
57
  );
@@ -52,6 +59,68 @@ class ServiceRateController extends FleetOpsController
52
59
  return response()->json($applicableServiceRates);
53
60
  }
54
61
 
62
+ /**
63
+ * Parse semicolon-delimited latitude,longitude coordinate pairs.
64
+ */
65
+ protected function parseRouteCoordinates(string $coordinates): ?Collection
66
+ {
67
+ $coordinates = collect(explode(';', $coordinates))
68
+ ->map(fn ($coordinate) => trim($coordinate));
69
+
70
+ if ($coordinates->contains('')) {
71
+ return null;
72
+ }
73
+
74
+ $waypoints = $coordinates
75
+ ->map(function ($coordinate) {
76
+ $parts = array_map('trim', explode(',', $coordinate));
77
+
78
+ if (count($parts) !== 2) {
79
+ return null;
80
+ }
81
+
82
+ [$latitude, $longitude] = $parts;
83
+
84
+ if (!is_numeric($latitude) || !is_numeric($longitude)) {
85
+ return null;
86
+ }
87
+
88
+ return Point::fromText("POINT($longitude $latitude)", 4326);
89
+ });
90
+
91
+ if ($waypoints->contains(null)) {
92
+ return null;
93
+ }
94
+
95
+ return $waypoints->values();
96
+ }
97
+
98
+ /**
99
+ * Treat placeholder query-string values from the UI as absent.
100
+ */
101
+ protected function normalizeOptionalQueryValue($value): ?string
102
+ {
103
+ if (!is_string($value)) {
104
+ return null;
105
+ }
106
+
107
+ $value = trim($value);
108
+
109
+ if ($value === '' || in_array(strtolower($value), ['null', 'undefined'], true)) {
110
+ return null;
111
+ }
112
+
113
+ return $value;
114
+ }
115
+
116
+ /**
117
+ * Resolve applicable service rates for route waypoints.
118
+ */
119
+ protected function getServicableForWaypoints(Collection $waypoints, \Closure $queryCallback): array
120
+ {
121
+ return ServiceRate::getServicableForWaypoints($waypoints, $queryCallback);
122
+ }
123
+
55
124
  /**
56
125
  * Export the service rate to excel or csv.
57
126
  *
@@ -26,6 +26,27 @@ class DeviceEventFilter extends Filter
26
26
  $this->builder->search($searchQuery);
27
27
  }
28
28
 
29
+ public function eventType(?string $eventType)
30
+ {
31
+ if ($eventType) {
32
+ $this->builder->where('event_type', 'like', '%' . $eventType . '%');
33
+ }
34
+ }
35
+
36
+ public function provider(?string $provider)
37
+ {
38
+ if ($provider) {
39
+ $this->builder->where('provider', 'like', '%' . $provider . '%');
40
+ }
41
+ }
42
+
43
+ public function code(?string $code)
44
+ {
45
+ if ($code) {
46
+ $this->builder->where('code', 'like', '%' . $code . '%');
47
+ }
48
+ }
49
+
29
50
  public function telematic(?string $telematic)
30
51
  {
31
52
  $this->builder->whereHas('device', function ($query) use ($telematic) {
@@ -44,6 +65,50 @@ class DeviceEventFilter extends Filter
44
65
  });
45
66
  }
46
67
 
68
+ public function deviceUuid(?string $device)
69
+ {
70
+ $this->device($device);
71
+ }
72
+
73
+ public function severity(string|array $severity)
74
+ {
75
+ $severity = Utils::arrayFrom($severity);
76
+
77
+ if ($severity) {
78
+ $this->builder->whereIn('severity', $severity);
79
+ }
80
+ }
81
+
82
+ public function processed(string|array $processed)
83
+ {
84
+ $states = Utils::arrayFrom($processed);
85
+
86
+ if (!$states) {
87
+ return;
88
+ }
89
+
90
+ $this->builder->where(function ($query) use ($states) {
91
+ foreach ($states as $state) {
92
+ match ($state) {
93
+ 'processed' => $query->orWhereNotNull('processed_at'),
94
+ 'unprocessed' => $query->orWhereNull('processed_at'),
95
+ default => null,
96
+ };
97
+ }
98
+ });
99
+ }
100
+
101
+ public function occurredAt($occurredAt)
102
+ {
103
+ $occurredAt = Utils::dateRange($occurredAt);
104
+
105
+ if (is_array($occurredAt)) {
106
+ $this->builder->whereBetween('occurred_at', $occurredAt);
107
+ } else {
108
+ $this->builder->whereDate('occurred_at', $occurredAt);
109
+ }
110
+ }
111
+
47
112
  public function createdAt($createdAt)
48
113
  {
49
114
  $createdAt = Utils::dateRange($createdAt);
@@ -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
 
@@ -31,6 +32,29 @@ class DeviceFilter extends Filter
31
32
  }
32
33
  }
33
34
 
35
+ public function deviceId(?string $deviceId)
36
+ {
37
+ if ($deviceId) {
38
+ $this->builder->where('device_id', 'like', '%' . $deviceId . '%');
39
+ }
40
+ }
41
+
42
+ public function type(string|array|null $type)
43
+ {
44
+ $type = Utils::arrayFrom($type);
45
+
46
+ if ($type) {
47
+ $this->builder->whereIn('type', $type);
48
+ }
49
+ }
50
+
51
+ public function serialNumber(?string $serialNumber)
52
+ {
53
+ if ($serialNumber) {
54
+ $this->builder->where('serial_number', 'like', '%' . $serialNumber . '%');
55
+ }
56
+ }
57
+
34
58
  public function telematic(?string $telematic)
35
59
  {
36
60
  $this->builder->where('telematic_uuid', $telematic);
@@ -61,6 +85,41 @@ class DeviceFilter extends Filter
61
85
  $this->builder->where('attachable_uuid', $attachable);
62
86
  }
63
87
 
88
+ public function vehicle(?string $vehicle)
89
+ {
90
+ if ($vehicle) {
91
+ $this->builder->where(function ($query) use ($vehicle) {
92
+ $query->where('attachable_uuid', $vehicle)
93
+ ->orWhereIn('attachable_uuid', Vehicle::query()
94
+ ->where('company_uuid', $this->session->get('company'))
95
+ ->where('public_id', $vehicle)
96
+ ->pluck('uuid'));
97
+ });
98
+ }
99
+ }
100
+
101
+ public function connectionStatus(string|array $connectionStatus)
102
+ {
103
+ $statuses = Utils::arrayFrom($connectionStatus);
104
+
105
+ if (!$statuses) {
106
+ return;
107
+ }
108
+
109
+ $this->builder->where(function ($query) use ($statuses) {
110
+ foreach ($statuses as $status) {
111
+ match ($status) {
112
+ 'online' => $query->orWhere('last_online_at', '>=', now()->subMinutes(10)),
113
+ 'recently_offline' => $query->orWhereBetween('last_online_at', [now()->subMinutes(60), now()->subMinutes(10)]),
114
+ 'offline' => $query->orWhereBetween('last_online_at', [now()->subDay(), now()->subMinutes(60)]),
115
+ 'long_offline' => $query->orWhere('last_online_at', '<', now()->subDay()),
116
+ 'never_connected' => $query->orWhereNull('last_online_at'),
117
+ default => null,
118
+ };
119
+ }
120
+ });
121
+ }
122
+
64
123
  public function attachmentState(?string $attachmentState)
65
124
  {
66
125
  if ($attachmentState === 'attached') {
@@ -71,4 +130,25 @@ class DeviceFilter extends Filter
71
130
  $this->builder->whereNull('attachable_uuid');
72
131
  }
73
132
  }
133
+
134
+ public function lastOnlineAt(string|array $lastOnlineAt)
135
+ {
136
+ $this->filterDate('last_online_at', $lastOnlineAt);
137
+ }
138
+
139
+ public function updatedAt(string|array $updatedAt)
140
+ {
141
+ $this->filterDate('updated_at', $updatedAt);
142
+ }
143
+
144
+ protected function filterDate(string $column, string|array $value): void
145
+ {
146
+ $dates = Utils::dateRange($value);
147
+
148
+ if (is_array($dates)) {
149
+ $this->builder->whereBetween($column, $dates);
150
+ } else {
151
+ $this->builder->whereDate($column, $dates);
152
+ }
153
+ }
74
154
  }
@@ -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
 
@@ -131,4 +132,16 @@ class VehicleFilter extends Filter
131
132
  $this->builder->whereDoesntHave('fleets');
132
133
  }
133
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
+ }
134
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'),
@@ -46,6 +46,7 @@ class Vehicle extends FleetbaseResource
46
46
  'altitude' => (int) data_get($this, 'altitude', 0),
47
47
  'speed' => (int) data_get($this, 'speed', 0),
48
48
  'online' => (bool) data_get($this, 'online', false),
49
+ 'devices' => $this->whenLoaded('devices', fn () => $this->compactDevices()),
49
50
  'assigned_orders_count' => $this->when($isInternal, $this->assignedOrdersCount()),
50
51
 
51
52
  // Meta flag to indicate this is an index resource
@@ -65,6 +66,24 @@ class Vehicle extends FleetbaseResource
65
66
  return Order::where('vehicle_assigned_uuid', $this->uuid)->count();
66
67
  }
67
68
 
69
+ protected function compactDevices(): array
70
+ {
71
+ return $this->devices->map(
72
+ fn ($device) => [
73
+ 'id' => $device->uuid,
74
+ 'uuid' => $device->uuid,
75
+ 'public_id' => $device->public_id,
76
+ 'name' => $device->name,
77
+ 'display_name' => $device->display_name,
78
+ 'device_id' => $device->device_id,
79
+ 'serial_number' => $device->serial_number,
80
+ 'imei' => $device->imei,
81
+ 'provider' => $device->provider,
82
+ 'status' => $device->status,
83
+ ]
84
+ )->values()->all();
85
+ }
86
+
68
87
  protected function currentOrderReference(): ?string
69
88
  {
70
89
  $this->loadMissing('driver.currentOrder');
@@ -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
+ }