@fleetbase/fleetops-engine 0.6.69 → 0.6.70

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 (209) hide show
  1. package/RELEASE.md +41 -8
  2. package/addon/components/admin/telematics-settings.hbs +143 -0
  3. package/addon/components/admin/telematics-settings.js +145 -0
  4. package/addon/components/fuel-integration/hub.hbs +1 -1
  5. package/addon/components/layout/fleet-ops-sidebar.js +37 -17
  6. package/addon/components/map/leaflet-live-map.hbs +4 -242
  7. package/addon/components/map/leaflet-live-map.js +2 -1
  8. package/addon/components/map/marker-card/driver.hbs +57 -0
  9. package/addon/components/map/marker-card/driver.js +3 -0
  10. package/addon/components/map/marker-card/vehicle.hbs +78 -0
  11. package/addon/components/map/marker-card/vehicle.js +3 -0
  12. package/addon/components/telematic/hub.hbs +1 -1
  13. package/addon/components/widget/live-fleet.hbs +8 -40
  14. package/addon/controllers/analytics/reports/index.js +5 -0
  15. package/addon/controllers/connectivity/devices/index.js +4 -0
  16. package/addon/controllers/connectivity/fuel-providers/index.js +6 -4
  17. package/addon/controllers/connectivity/sensors/index.js +4 -0
  18. package/addon/controllers/connectivity/telematics/index.js +4 -0
  19. package/addon/controllers/maintenance/equipment/index.js +11 -4
  20. package/addon/controllers/maintenance/inspection-forms/index.js +2 -2
  21. package/addon/controllers/maintenance/inspection-submissions/index.js +8 -2
  22. package/addon/controllers/maintenance/maintenances/index.js +4 -0
  23. package/addon/controllers/maintenance/parts/index.js +11 -4
  24. package/addon/controllers/maintenance/schedules/index.js +17 -4
  25. package/addon/controllers/maintenance/work-orders/index.js +11 -4
  26. package/addon/controllers/management/contacts/index.js +4 -0
  27. package/addon/controllers/management/drivers/index.js +4 -0
  28. package/addon/controllers/management/fleets/index.js +4 -0
  29. package/addon/controllers/management/fuel-reports/index.js +4 -0
  30. package/addon/controllers/management/fuel-transactions/index.js +9 -7
  31. package/addon/controllers/management/issues/index.js +4 -0
  32. package/addon/controllers/management/places/index.js +4 -0
  33. package/addon/controllers/management/vehicles/index.js +4 -0
  34. package/addon/controllers/management/vendors/index.js +4 -0
  35. package/addon/controllers/operations/orders/index/details.js +30 -1
  36. package/addon/controllers/operations/orders/index.js +7 -0
  37. package/addon/controllers/operations/service-rates/index.js +3 -0
  38. package/addon/controllers/settings/index.js +13 -0
  39. package/addon/controllers/settings/telematics.js +113 -0
  40. package/addon/extension.js +38 -6
  41. package/addon/routes/analytics/index.js +14 -1
  42. package/addon/routes/analytics/reports/index/details.js +1 -1
  43. package/addon/routes/analytics/reports/index/edit.js +1 -1
  44. package/addon/routes/analytics/reports/index/new.js +14 -1
  45. package/addon/routes/analytics/reports.js +14 -1
  46. package/addon/routes/connectivity/devices/index/new.js +14 -1
  47. package/addon/routes/connectivity/devices.js +14 -1
  48. package/addon/routes/connectivity/events.js +14 -1
  49. package/addon/routes/connectivity/fuel-providers/details/transactions.js +12 -0
  50. package/addon/routes/connectivity/fuel-providers/details.js +9 -0
  51. package/addon/routes/connectivity/fuel-providers/edit.js +9 -0
  52. package/addon/routes/connectivity/fuel-providers/new.js +14 -1
  53. package/addon/routes/connectivity/fuel-providers.js +14 -1
  54. package/addon/routes/connectivity/sensors/index/new.js +14 -1
  55. package/addon/routes/connectivity/sensors.js +14 -1
  56. package/addon/routes/connectivity/telematics/new.js +14 -1
  57. package/addon/routes/connectivity/telematics.js +14 -1
  58. package/addon/routes/connectivity/tracking.js +14 -1
  59. package/addon/routes/maintenance/equipment/index/details.js +2 -2
  60. package/addon/routes/maintenance/equipment/index/edit.js +2 -2
  61. package/addon/routes/maintenance/equipment/index/new.js +14 -1
  62. package/addon/routes/maintenance/equipment.js +14 -1
  63. package/addon/routes/maintenance/index.js +14 -1
  64. package/addon/routes/maintenance/inspection-forms/index/details.js +10 -1
  65. package/addon/routes/maintenance/inspection-forms/index/edit.js +10 -1
  66. package/addon/routes/maintenance/inspection-forms/index/new.js +14 -1
  67. package/addon/routes/maintenance/inspection-forms.js +14 -1
  68. package/addon/routes/maintenance/inspection-submissions/index/details.js +10 -1
  69. package/addon/routes/maintenance/inspection-submissions/index/edit.js +10 -1
  70. package/addon/routes/maintenance/inspection-submissions/index/new.js +14 -1
  71. package/addon/routes/maintenance/inspection-submissions.js +14 -1
  72. package/addon/routes/maintenance/maintenances/index/details.js +2 -2
  73. package/addon/routes/maintenance/maintenances/index/edit.js +2 -2
  74. package/addon/routes/maintenance/maintenances/index/new.js +14 -1
  75. package/addon/routes/maintenance/maintenances.js +14 -1
  76. package/addon/routes/maintenance/parts/index/details.js +2 -2
  77. package/addon/routes/maintenance/parts/index/edit.js +2 -2
  78. package/addon/routes/maintenance/parts/index/new.js +14 -1
  79. package/addon/routes/maintenance/parts.js +14 -1
  80. package/addon/routes/maintenance/schedules/index/details.js +2 -2
  81. package/addon/routes/maintenance/schedules/index/edit.js +2 -2
  82. package/addon/routes/maintenance/schedules/index/new.js +15 -1
  83. package/addon/routes/maintenance/schedules.js +15 -1
  84. package/addon/routes/maintenance/work-orders/index/details.js +2 -2
  85. package/addon/routes/maintenance/work-orders/index/edit.js +2 -2
  86. package/addon/routes/maintenance/work-orders/index/new.js +14 -1
  87. package/addon/routes/maintenance/work-orders.js +14 -1
  88. package/addon/routes/management/fuel-transactions/index/details.js +9 -0
  89. package/addon/routes/management/fuel-transactions.js +14 -1
  90. package/addon/routes/management/index.js +13 -0
  91. package/addon/routes/management/trailers.js +16 -0
  92. package/addon/routes/management/vendors/integrated/details.js +14 -1
  93. package/addon/routes/management/vendors/integrated/edit.js +14 -1
  94. package/addon/routes/management/vendors/integrated/new.js +14 -1
  95. package/addon/routes/management/vendors/integrated.js +14 -1
  96. package/addon/routes/operations/orders.js +40 -1
  97. package/addon/routes/operations/routes/index/details.js +14 -1
  98. package/addon/routes/operations/routes/index/new.js +13 -0
  99. package/addon/routes/operations/routes.js +14 -1
  100. package/addon/routes/operations/service-rates/index/new.js +11 -0
  101. package/addon/routes/operations/service-rates.js +14 -1
  102. package/addon/routes/settings/avatars.js +14 -1
  103. package/addon/routes/settings/custom-fields.js +14 -1
  104. package/addon/routes/settings/map.js +14 -1
  105. package/addon/routes/settings/notifications.js +11 -0
  106. package/addon/routes/settings/orchestrator.js +1 -1
  107. package/addon/routes/settings/payments/index.js +1 -1
  108. package/addon/routes/settings/routing.js +14 -1
  109. package/addon/routes/settings/scheduling.js +14 -1
  110. package/addon/routes/settings/telematics.js +21 -0
  111. package/addon/routes/virtual.js +11 -0
  112. package/addon/routes.js +1 -0
  113. package/addon/templates/settings/telematics.hbs +70 -0
  114. package/app/components/admin/telematics-settings.js +1 -0
  115. package/app/components/map/marker-card/driver.js +1 -0
  116. package/app/components/map/marker-card/vehicle.js +1 -0
  117. package/app/controllers/settings/telematics.js +1 -0
  118. package/app/routes/settings/telematics.js +1 -0
  119. package/app/templates/settings/telematics.js +1 -0
  120. package/composer.json +2 -2
  121. package/docs/AFAQY.md +1 -1
  122. package/docs/TELEMATICS_QUEUES.md +1 -1
  123. package/docs/TELEMETRY_ARCHITECTURE.md +72 -1
  124. package/extension.json +1 -1
  125. package/package.json +1 -1
  126. package/server/config/telemetry.php +12 -0
  127. package/server/migrations/2026_09_23_000001_add_retention_index_to_device_events.php +40 -0
  128. package/server/migrations/2026_09_23_000002_add_retention_index_to_positions.php +40 -0
  129. package/server/migrations/2026_09_23_000003_add_retention_index_to_telematic_sync_runs.php +40 -0
  130. package/server/src/Auth/Schemas/FleetOps.php +104 -5
  131. package/server/src/Console/Commands/DrainTelematicInbox.php +3 -13
  132. package/server/src/Console/Commands/ProcessOperationalAlerts.php +3 -3
  133. package/server/src/Console/Commands/PruneTelematicsData.php +318 -0
  134. package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +9 -0
  135. package/server/src/Http/Controllers/FleetOpsController.php +21 -0
  136. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +11 -0
  137. package/server/src/Http/Controllers/Internal/v1/ContactController.php +11 -0
  138. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +14 -0
  139. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +13 -0
  140. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +11 -0
  141. package/server/src/Http/Controllers/Internal/v1/DriverController.php +22 -0
  142. package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +13 -0
  143. package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +15 -0
  144. package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +9 -3
  145. package/server/src/Http/Controllers/Internal/v1/InspectionFormController.php +15 -0
  146. package/server/src/Http/Controllers/Internal/v1/LiveController.php +16 -0
  147. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +15 -0
  148. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +15 -0
  149. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +15 -0
  150. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +11 -0
  151. package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +42 -1
  152. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +12 -0
  153. package/server/src/Http/Controllers/Internal/v1/OrderController.php +22 -0
  154. package/server/src/Http/Controllers/Internal/v1/PaymentController.php +13 -0
  155. package/server/src/Http/Controllers/Internal/v1/RadarController.php +19 -0
  156. package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +9 -3
  157. package/server/src/Http/Controllers/Internal/v1/SettingController.php +465 -14
  158. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +21 -0
  159. package/server/src/Http/Controllers/Internal/v1/TrailerController.php +21 -0
  160. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +21 -0
  161. package/server/src/Http/Controllers/Internal/v1/VendorController.php +17 -0
  162. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +11 -0
  163. package/server/src/Jobs/DispatchTelematicsRetentionJobs.php +70 -0
  164. package/server/src/Jobs/PruneTelematicsDataJob.php +74 -0
  165. package/server/src/Listeners/NotifyDriverOnShiftChange.php +3 -3
  166. package/server/src/Mail/CustomerCredentialsMail.php +1 -1
  167. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +9 -1
  168. package/server/src/Providers/FleetOpsServiceProvider.php +3 -0
  169. package/server/src/Support/Analytics/LiveFleet.php +18 -2
  170. package/server/src/Support/Authorization.php +103 -0
  171. package/server/src/Support/Database/TableIndexes.php +66 -0
  172. package/server/src/Support/OrderTracker.php +2 -2
  173. package/server/src/Support/Reporting/FleetOpsReportSchema.php +476 -116
  174. package/server/src/Support/Telematics/Retention/RetentionPolicy.php +300 -0
  175. package/server/src/Support/Telematics/Retention/TelemetryActivity.php +40 -0
  176. package/server/src/Support/Telematics/TelematicService.php +12 -2
  177. package/server/src/Support/Telematics/Telemetry/Ingestor.php +4 -2
  178. package/server/src/Tracking/TrackingIntelligenceService.php +1 -1
  179. package/server/src/Tracking/TrackingOptions.php +2 -2
  180. package/server/src/Traits/AuthorizesMethods.php +37 -0
  181. package/server/src/routes.php +9 -1
  182. package/server/tests/AnalyticsRoutesTest.php +29 -2
  183. package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +16 -1
  184. package/server/tests/Feature/Http/Api/TelematicsRetentionIndexMigrationTest.php +115 -0
  185. package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +71 -0
  186. package/server/tests/Feature/Http/Internal/TelematicsStorageDeadlinesTest.php +119 -0
  187. package/server/tests/InternalImportExportControllerContractsTest.php +5 -8
  188. package/server/tests/NavigatorControllerContractsTest.php +36 -4
  189. package/server/tests/NotificationAndMailContractsTest.php +31 -0
  190. package/server/tests/ObserverContractsTest.php +1 -1
  191. package/server/tests/OrchestratorConsumableApiTest.php +42 -0
  192. package/server/tests/ProviderContractsTest.php +10 -0
  193. package/server/tests/ReportSchemaContractsTest.php +120 -6
  194. package/server/tests/SettingControllerContractsTest.php +429 -18
  195. package/server/tests/SmallControllerContractsTest.php +8 -5
  196. package/server/tests/Support/PruneTelematicsDataProbe.php +55 -0
  197. package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +2 -2
  198. package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +353 -0
  199. package/server/tests/Unit/Jobs/DispatchTelematicsRetentionJobsTest.php +114 -0
  200. package/server/tests/Unit/Jobs/PruneTelematicsDataJobTest.php +60 -0
  201. package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +20 -0
  202. package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +6 -0
  203. package/server/tests/Unit/Support/AuthorizationTest.php +142 -0
  204. package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +12 -1
  205. package/server/tests/Unit/Support/MoreQuerySeamsTest.php +11 -2
  206. package/server/tests/Unit/Support/Telematics/Retention/RetentionPolicyTest.php +190 -0
  207. package/server/tests/Unit/Support/Telematics/Retention/TelemetryActivityTest.php +68 -0
  208. package/server/tests/Unit/Tracking/TrackingOptionsCompanySettingsTest.php +109 -0
  209. package/translations/en-us.yaml +48 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
4
 
5
+ use Fleetbase\Attributes\SkipAuthorizationCheck;
5
6
  use Fleetbase\FleetOps\Exceptions\DeviceAlreadyAttachedException;
6
7
  use Fleetbase\FleetOps\Exports\VehicleExport;
7
8
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
@@ -25,6 +26,20 @@ use Maatwebsite\Excel\Facades\Excel;
25
26
 
26
27
  class VehicleController extends FleetOpsController
27
28
  {
29
+ /**
30
+ * Permissions for methods AuthorizationGuard cannot map to a schema action (see FleetOpsController).
31
+ *
32
+ * @var array<string, string|string[]>
33
+ */
34
+ protected array $methodPermissions = [
35
+ 'unassignDriver' => 'assign-driver-for vehicle',
36
+ 'unassignOrders' => 'update vehicle',
37
+ 'attachDevice' => 'update vehicle',
38
+ 'detachDevice' => 'update vehicle',
39
+ 'attachEquipment' => 'update vehicle',
40
+ 'detachEquipment' => 'update vehicle',
41
+ ];
42
+
28
43
  /**
29
44
  * The resource to query.
30
45
  *
@@ -111,6 +126,7 @@ class VehicleController extends FleetOpsController
111
126
  ]);
112
127
  }
113
128
 
129
+ #[SkipAuthorizationCheck]
114
130
  public function unassignDriver(string $id): JsonResponse
115
131
  {
116
132
  $vehicle = $this->findVehicle($id);
@@ -143,6 +159,7 @@ class VehicleController extends FleetOpsController
143
159
  ]);
144
160
  }
145
161
 
162
+ #[SkipAuthorizationCheck]
146
163
  public function unassignOrders(Request $request, string $id): JsonResponse
147
164
  {
148
165
  $request->validate([
@@ -179,6 +196,7 @@ class VehicleController extends FleetOpsController
179
196
  ]);
180
197
  }
181
198
 
199
+ #[SkipAuthorizationCheck]
182
200
  public function attachDevice(Request $request, string $id): JsonResponse
183
201
  {
184
202
  $request->validate(['device' => 'required|string']);
@@ -219,6 +237,7 @@ class VehicleController extends FleetOpsController
219
237
  ]);
220
238
  }
221
239
 
240
+ #[SkipAuthorizationCheck]
222
241
  public function detachDevice(Request $request, string $id): JsonResponse
223
242
  {
224
243
  $request->validate(['device' => 'required|string']);
@@ -265,6 +284,7 @@ class VehicleController extends FleetOpsController
265
284
  * Attach equipment to the vehicle. Equipment can only be equipped to one asset at a
266
285
  * time, so attaching moves it from any previous vehicle or trailer.
267
286
  */
287
+ #[SkipAuthorizationCheck]
268
288
  public function attachEquipment(Request $request, string $id): JsonResponse
269
289
  {
270
290
  $request->validate(['equipment' => 'required|string']);
@@ -292,6 +312,7 @@ class VehicleController extends FleetOpsController
292
312
  ]);
293
313
  }
294
314
 
315
+ #[SkipAuthorizationCheck]
295
316
  public function detachEquipment(Request $request, string $id): JsonResponse
296
317
  {
297
318
  $request->validate(['equipment' => 'required|string']);
@@ -2,6 +2,7 @@
2
2
 
3
3
  namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
4
 
5
+ use Fleetbase\Attributes\SkipAuthorizationCheck;
5
6
  use Fleetbase\FleetOps\Exports\VendorExport;
6
7
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
7
8
  use Fleetbase\FleetOps\Http\Resources\v1\Contact as ContactResource;
@@ -19,6 +20,18 @@ use Maatwebsite\Excel\Facades\Excel;
19
20
 
20
21
  class VendorController extends FleetOpsController
21
22
  {
23
+ /**
24
+ * Permissions for methods AuthorizationGuard cannot map to a schema action (see FleetOpsController).
25
+ *
26
+ * @var array<string, string|string[]>
27
+ */
28
+ protected array $methodPermissions = [
29
+ 'assignDriver' => 'update vendor',
30
+ 'removeDriver' => 'update vendor',
31
+ 'addVendorPersonnel' => 'update vendor',
32
+ 'removeVendorPersonnel' => 'update vendor',
33
+ ];
34
+
22
35
  /**
23
36
  * The resource to query.
24
37
  *
@@ -128,6 +141,7 @@ class VendorController extends FleetOpsController
128
141
  *
129
142
  * @return \Illuminate\Http\Response
130
143
  */
144
+ #[SkipAuthorizationCheck]
131
145
  public function assignDriver(string $id, Request $request)
132
146
  {
133
147
  // Validate only param
@@ -160,6 +174,7 @@ class VendorController extends FleetOpsController
160
174
  *
161
175
  * @return \Illuminate\Http\Response
162
176
  */
177
+ #[SkipAuthorizationCheck]
163
178
  public function removeDriver(string $id, Request $request)
164
179
  {
165
180
  // Validate only param
@@ -197,6 +212,7 @@ class VendorController extends FleetOpsController
197
212
  return response()->json(['personnels' => $personnels->values()]);
198
213
  }
199
214
 
215
+ #[SkipAuthorizationCheck]
200
216
  public function addVendorPersonnel(Request $request, string $vendorId)
201
217
  {
202
218
  $vendor = $this->findVendorByIdOrFail($vendorId);
@@ -220,6 +236,7 @@ class VendorController extends FleetOpsController
220
236
  ]);
221
237
  }
222
238
 
239
+ #[SkipAuthorizationCheck]
223
240
  public function removeVendorPersonnel(string $vendorId, string $contactId)
224
241
  {
225
242
  $vendor = $this->findVendorByIdOrFail($vendorId);
@@ -2,6 +2,7 @@
2
2
 
3
3
  namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
4
 
5
+ use Fleetbase\Attributes\SkipAuthorizationCheck;
5
6
  use Fleetbase\FleetOps\Exports\WorkOrderExport;
6
7
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
7
8
  use Fleetbase\FleetOps\Imports\WorkOrderImport;
@@ -16,6 +17,15 @@ use Maatwebsite\Excel\Facades\Excel;
16
17
 
17
18
  class WorkOrderController extends FleetOpsController
18
19
  {
20
+ /**
21
+ * Permissions for methods AuthorizationGuard cannot map to a schema action (see FleetOpsController).
22
+ *
23
+ * @var array<string, string|string[]>
24
+ */
25
+ protected array $methodPermissions = [
26
+ 'sendEmail' => 'update work-order',
27
+ ];
28
+
19
29
  /**
20
30
  * The resource to query.
21
31
  *
@@ -80,6 +90,7 @@ class WorkOrderController extends FleetOpsController
80
90
  * Send a work order email to the assigned vendor.
81
91
  * POST /work-orders/{id}/send.
82
92
  */
93
+ #[SkipAuthorizationCheck]
83
94
  public function sendEmail(string $id): JsonResponse
84
95
  {
85
96
  $workOrder = $this->workOrderForEmail($id);
@@ -0,0 +1,70 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Jobs;
4
+
5
+ use Fleetbase\FleetOps\Support\Telematics\Telemetry\Queue;
6
+ use Illuminate\Bus\Queueable;
7
+ use Illuminate\Contracts\Queue\ShouldBeUnique;
8
+ use Illuminate\Contracts\Queue\ShouldQueue;
9
+ use Illuminate\Foundation\Bus\Dispatchable;
10
+ use Illuminate\Queue\InteractsWithQueue;
11
+ use Illuminate\Support\Collection;
12
+ use Illuminate\Support\Facades\DB;
13
+
14
+ /**
15
+ * Fan out system-wide cleanup in small pages, without a session or membership scope.
16
+ * Each company has its own bounded prune job so one large backlog cannot prevent
17
+ * other organizations from being processed within a shared worker timeout.
18
+ */
19
+ class DispatchTelematicsRetentionJobs implements ShouldQueue, ShouldBeUnique
20
+ {
21
+ use Dispatchable;
22
+ use InteractsWithQueue;
23
+ use Queueable;
24
+
25
+ public const PAGE_SIZE = 100;
26
+
27
+ public int $uniqueFor = 900;
28
+ public int $tries = 3;
29
+ public int $timeout = 60;
30
+
31
+ public function __construct(public int $afterCompanyId = 0, public int $maxBatches = 200)
32
+ {
33
+ $this->onQueue(config('telematics.telemetry.ingestion_queue', 'default'));
34
+ }
35
+
36
+ public function uniqueId(): string
37
+ {
38
+ return 'after-company:' . $this->afterCompanyId;
39
+ }
40
+
41
+ public function handle(): void
42
+ {
43
+ $companies = $this->companies();
44
+ foreach ($companies as $company) {
45
+ $this->queueJob(new PruneTelematicsDataJob($company->uuid, $this->maxBatches));
46
+ }
47
+
48
+ if ($companies->count() === self::PAGE_SIZE) {
49
+ $this->queueJob(new self((int) $companies->last()->id, $this->maxBatches));
50
+
51
+ return;
52
+ }
53
+
54
+ $this->queueJob(new PruneTelematicsDataJob(null, $this->maxBatches, true));
55
+ }
56
+
57
+ protected function companies(): Collection
58
+ {
59
+ return DB::table('companies')
60
+ ->where('id', '>', $this->afterCompanyId)
61
+ ->orderBy('id')
62
+ ->limit(self::PAGE_SIZE)
63
+ ->get(['id', 'uuid']);
64
+ }
65
+
66
+ protected function queueJob(ShouldBeUnique $job): void
67
+ {
68
+ Queue::dispatch($job);
69
+ }
70
+ }
@@ -0,0 +1,74 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Jobs;
4
+
5
+ use Fleetbase\FleetOps\Console\Commands\PruneTelematicsData;
6
+ use Illuminate\Bus\Queueable;
7
+ use Illuminate\Contracts\Queue\ShouldBeUnique;
8
+ use Illuminate\Contracts\Queue\ShouldQueue;
9
+ use Illuminate\Foundation\Bus\Dispatchable;
10
+ use Illuminate\Queue\InteractsWithQueue;
11
+ use Symfony\Component\Console\Input\ArrayInput;
12
+ use Symfony\Component\Console\Output\BufferedOutput;
13
+
14
+ /**
15
+ * Runs a bounded telematics prune for one company or orphaned data.
16
+ *
17
+ * The command is executed directly rather than through the console kernel so a
18
+ * worker started before this package version can still run it.
19
+ */
20
+ class PruneTelematicsDataJob implements ShouldQueue, ShouldBeUnique
21
+ {
22
+ use Dispatchable;
23
+ use InteractsWithQueue;
24
+ use Queueable;
25
+
26
+ public int $uniqueFor = 900;
27
+ public int $tries = 1;
28
+ // Finish or fail before Laravel's baseline Redis retry_after of 90 seconds.
29
+ public int $timeout = 80;
30
+ public bool $failOnTimeout = true;
31
+ public bool $orphansOnly = false;
32
+
33
+ public function __construct(public ?string $companyUuid = null, public int $maxBatches = 200, bool $orphansOnly = false)
34
+ {
35
+ $this->orphansOnly = $orphansOnly;
36
+ $this->onQueue(config('telematics.telemetry.ingestion_queue', 'default'));
37
+ }
38
+
39
+ public function uniqueId(): string
40
+ {
41
+ return $this->orphansOnly ? 'orphans' : ($this->companyUuid ?? 'all');
42
+ }
43
+
44
+ /**
45
+ * @return array<string, mixed>
46
+ */
47
+ public function parameters(): array
48
+ {
49
+ $parameters = ['--no-lock' => true, '--max-batches' => $this->maxBatches];
50
+ if ($this->companyUuid) {
51
+ $parameters['--company'] = $this->companyUuid;
52
+ }
53
+ if ($this->orphansOnly) {
54
+ $parameters['--orphans-only'] = true;
55
+ }
56
+
57
+ return $parameters;
58
+ }
59
+
60
+ public function handle(): void
61
+ {
62
+ $command = $this->command();
63
+ $command->setLaravel(app());
64
+ $exitCode = $command->run(new ArrayInput($this->parameters()), new BufferedOutput());
65
+ if ($exitCode !== PruneTelematicsData::SUCCESS) {
66
+ throw new \RuntimeException('Telematics cleanup failed with exit code ' . $exitCode . '.');
67
+ }
68
+ }
69
+
70
+ protected function command(): PruneTelematicsData
71
+ {
72
+ return app(PruneTelematicsData::class);
73
+ }
74
+ }
@@ -48,7 +48,7 @@ class NotifyDriverOnShiftChange implements ShouldQueue
48
48
  }
49
49
 
50
50
  // Check the company-level scheduling setting
51
- $settings = $this->getSchedulingSettings();
51
+ $settings = $this->getSchedulingSettings($schedule->company_uuid);
52
52
  $shouldNotify = (bool) data_get($settings, 'notify_drivers_on_shift_change', false);
53
53
  if (!$shouldNotify) {
54
54
  return;
@@ -66,9 +66,9 @@ class NotifyDriverOnShiftChange implements ShouldQueue
66
66
  return $scheduleItem->schedule()->with('subject')->first();
67
67
  }
68
68
 
69
- protected function getSchedulingSettings(): array
69
+ protected function getSchedulingSettings(?string $companyUuid): array
70
70
  {
71
- return Setting::lookupFromCompany('fleet-ops.scheduling-settings', []);
71
+ return Setting::lookupForCompany($companyUuid, 'fleet-ops.scheduling-settings', []);
72
72
  }
73
73
 
74
74
  protected function isCreatedEvent(object $event): bool
@@ -72,7 +72,7 @@ class CustomerCredentialsMail extends Mailable
72
72
  */
73
73
  private function getCustomerPortalAccessUrl(): string
74
74
  {
75
- $customerPortalConfig = Setting::lookupFromCompany('customer-portal-config');
75
+ $customerPortalConfig = Setting::lookupForCompany($this->customer->company_uuid, 'customer-portal-config');
76
76
  $accessUrlSlug = data_get($customerPortalConfig, 'accessUrlSlug', 'customer-portal');
77
77
 
78
78
  return Utils::consoleUrl($accessUrlSlug ?: 'customer-portal');
@@ -37,6 +37,12 @@ use Illuminate\Support\Facades\Log;
37
37
  */
38
38
  class VroomOrchestrationEngine implements OrchestrationEngineInterface
39
39
  {
40
+ /**
41
+ * The company whose VROOM settings apply to the current allocation run.
42
+ * Resolved from the orders so queued runs work without a company session.
43
+ */
44
+ protected ?string $companyUuid = null;
45
+
40
46
  public function getName(): string
41
47
  {
42
48
  return 'VROOM';
@@ -59,6 +65,8 @@ class VroomOrchestrationEngine implements OrchestrationEngineInterface
59
65
  */
60
66
  public function allocate(Collection $orders, Collection $vehicles, array $options = []): array
61
67
  {
68
+ $this->companyUuid = data_get($orders->first(), 'company_uuid');
69
+
62
70
  if (($options['allocation_strategy'] ?? null) === 'capacity_only') {
63
71
  return $this->allocateCapacityOnly($orders, $vehicles, $options);
64
72
  }
@@ -520,7 +528,7 @@ class VroomOrchestrationEngine implements OrchestrationEngineInterface
520
528
  protected function resolveVroomSetting(string $key, $default = null)
521
529
  {
522
530
  try {
523
- $organizationValue = data_get(Setting::lookupCompany('vroom', []), $key);
531
+ $organizationValue = data_get(Setting::lookupForCompany($this->companyUuid ?? session('company'), 'vroom', []), $key);
524
532
  if ($this->hasConfiguredValue($organizationValue)) {
525
533
  return $organizationValue;
526
534
  }
@@ -77,6 +77,7 @@ class FleetOpsServiceProvider extends CoreServiceProvider
77
77
  \Fleetbase\FleetOps\Console\Commands\ProcessOperationalAlerts::class,
78
78
  \Fleetbase\FleetOps\Console\Commands\SyncTelematics::class,
79
79
  \Fleetbase\FleetOps\Console\Commands\DrainTelematicInbox::class,
80
+ \Fleetbase\FleetOps\Console\Commands\PruneTelematicsData::class,
80
81
  ];
81
82
 
82
83
  /**
@@ -153,6 +154,8 @@ class FleetOpsServiceProvider extends CoreServiceProvider
153
154
  // leave the default 24-hour overlap lease blocking telemetry recovery.
154
155
  $schedule->command('fleetops:sync-telematics')->everyMinute()->withoutOverlapping(2)->storeOutputInDb();
155
156
  $schedule->command('fleetops:drain-telematic-inbox')->everyMinute()->withoutOverlapping(2);
157
+ // Bounded batches per company and table; a capped run simply continues on the next tick.
158
+ $schedule->command('fleetops:prune-telematics-data')->everyFifteenMinutes()->withoutOverlapping(14)->storeOutputInDb();
156
159
  });
157
160
  $this->registerNotifications();
158
161
  $this->registerAiCapabilities();
@@ -2,10 +2,13 @@
2
2
 
3
3
  namespace Fleetbase\FleetOps\Support\Analytics;
4
4
 
5
+ use Fleetbase\FleetOps\Http\Resources\v1\Index\Driver as DriverIndexResource;
6
+ use Fleetbase\FleetOps\Http\Resources\v1\Index\Vehicle as VehicleIndexResource;
5
7
  use Fleetbase\FleetOps\Models\Driver;
6
8
  use Fleetbase\FleetOps\Models\Order;
7
9
  use Fleetbase\FleetOps\Models\Vehicle;
8
10
  use Fleetbase\FleetOps\Support\Metrics\OrdersInProgressMetric;
11
+ use Illuminate\Http\Resources\Json\JsonResource;
9
12
 
10
13
  /**
11
14
  * Real-time fleet snapshot used as the initial-state payload for Live Fleet Map.
@@ -23,7 +26,8 @@ class LiveFleet extends AbstractAnalytics
23
26
 
24
27
  // Driver.name is a virtual attribute backed by users.name — eager-load the
25
28
  // user so the accessor resolves cleanly without a per-record query.
26
- $drivers = Driver::with(['user:uuid,name,avatar_uuid'])
29
+ // The user and vehicle also feed each marker's card (see card()).
30
+ $drivers = Driver::with(['user', 'vehicle'])
27
31
  ->where('drivers.company_uuid', $companyUuid)
28
32
  ->whereNotNull('drivers.location')
29
33
  ->where(function ($q) {
@@ -33,7 +37,8 @@ class LiveFleet extends AbstractAnalytics
33
37
 
34
38
  // Vehicles tracked independently of drivers (telematics-connected rigs may
35
39
  // report position without an active driver session).
36
- $vehicles = Vehicle::where('company_uuid', $companyUuid)
40
+ $vehicles = Vehicle::with(['devices', 'driver', 'currentTrailers'])
41
+ ->where('company_uuid', $companyUuid)
37
42
  ->whereNotNull('location')
38
43
  ->get();
39
44
 
@@ -79,6 +84,7 @@ class LiveFleet extends AbstractAnalytics
79
84
  'lat' => $lat,
80
85
  'lng' => $lng,
81
86
  'updated_at' => $d->last_location_update_at,
87
+ 'card' => $this->card(new DriverIndexResource($d)),
82
88
  ];
83
89
  }
84
90
 
@@ -98,9 +104,19 @@ class LiveFleet extends AbstractAnalytics
98
104
  'heading' => (float) ($v->heading ?? 0),
99
105
  'lat' => $lat,
100
106
  'lng' => $lng,
107
+ 'card' => $this->card(new VehicleIndexResource($v)),
101
108
  ];
102
109
  }
103
110
 
111
+ /**
112
+ * What the operational live map shows in a marker's popup and tooltip: the same index
113
+ * resource its live endpoints return, so the dashboard widget renders identical cards.
114
+ */
115
+ private function card(JsonResource $resource): array
116
+ {
117
+ return $resource->resolve(request());
118
+ }
119
+
104
120
  /**
105
121
  * Pull [lat, lng] out of whatever shape the spatial accessor handed us.
106
122
  * Supports Point objects, [lat, lng] arrays, and {lat, lng} hashes.
@@ -0,0 +1,103 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support;
4
+
5
+ use Fleetbase\Models\Permission;
6
+ use Fleetbase\Support\Auth;
7
+ use Illuminate\Http\Exceptions\HttpResponseException;
8
+
9
+ /**
10
+ * Explicit Fleet-Ops permission checks for endpoints that AuthorizationGuard
11
+ * cannot resolve correctly on its own: controllers that are not resource
12
+ * controllers, and custom actions whose method name does not match a schema
13
+ * action (for example `bulkDispatch`, which the guard maps to `create order`).
14
+ *
15
+ * Mirrors the guard's semantics:
16
+ * - platform admins always pass;
17
+ * - a permission is granted by its exact name, `fleet-ops * {resource}` or `fleet-ops *`;
18
+ * - while the resource has no permission rows at all (the schema has not been
19
+ * seeded yet) the check passes, exactly like an unguarded resource.
20
+ *
21
+ * Methods that call this must carry `#[SkipAuthorizationCheck]` so the guard
22
+ * does not also demand its own (wrong) permission.
23
+ */
24
+ class Authorization
25
+ {
26
+ public const SERVICE = 'fleet-ops';
27
+
28
+ /**
29
+ * Abort with a 401 JSON response unless the user holds at least one of the permissions.
30
+ *
31
+ * @param string ...$permissions Short form `{action} {resource}`, e.g. `dispatch order`
32
+ *
33
+ * @throws HttpResponseException
34
+ */
35
+ public static function authorize(string ...$permissions): void
36
+ {
37
+ if (static::canAny(...$permissions)) {
38
+ return;
39
+ }
40
+
41
+ throw new HttpResponseException(response()->json(['errors' => ['User is not authorized to ' . $permissions[0]]], 401));
42
+ }
43
+
44
+ /**
45
+ * Abort with a 401 JSON response unless the current user is a platform administrator.
46
+ *
47
+ * @throws HttpResponseException
48
+ */
49
+ public static function authorizeAdmin(): void
50
+ {
51
+ $user = static::user();
52
+ if ($user && $user->isAdmin()) {
53
+ return;
54
+ }
55
+
56
+ throw new HttpResponseException(response()->json(['errors' => ['This action requires a system administrator']], 401));
57
+ }
58
+
59
+ public static function canAny(string ...$permissions): bool
60
+ {
61
+ $user = static::user();
62
+ if (!$user) {
63
+ return false;
64
+ }
65
+
66
+ if ($user->isAdmin()) {
67
+ return true;
68
+ }
69
+
70
+ foreach ($permissions as $permission) {
71
+ if (static::can($permission)) {
72
+ return true;
73
+ }
74
+ }
75
+
76
+ return false;
77
+ }
78
+
79
+ public static function can(string $permission): bool
80
+ {
81
+ [$action, $resource] = explode(' ', $permission, 2);
82
+
83
+ // Same test as Auth::isResourceGuarded(): no permission rows for the resource yet.
84
+ if (!Permission::where('name', 'like', static::SERVICE . ' % ' . $resource)->exists()) {
85
+ return true;
86
+ }
87
+
88
+ try {
89
+ return Auth::can(static::SERVICE . " {$action} {$resource}");
90
+ } catch (\Throwable $e) {
91
+ return false;
92
+ }
93
+ }
94
+
95
+ protected static function user()
96
+ {
97
+ try {
98
+ return Auth::getUserFromSession();
99
+ } catch (\Throwable $e) {
100
+ return null;
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,66 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Database;
4
+
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ /**
8
+ * Inspect table indexes without Schema::getIndexes, which is absent in the
9
+ * Laravel versions this package supports. Fleet-Ops deploys on MySQL and runs
10
+ * its migration tests on SQLite.
11
+ */
12
+ final class TableIndexes
13
+ {
14
+ /**
15
+ * @return array<string, string[]> index name => ordered column names
16
+ */
17
+ public static function for(string $table): array
18
+ {
19
+ $connection = Schema::getConnection();
20
+ $prefixed = $connection->getTablePrefix() . $table;
21
+ $indexes = [];
22
+
23
+ if ($connection instanceof \Illuminate\Database\SQLiteConnection || $connection->getDriverName() === 'sqlite') {
24
+ $quoted = str_replace('"', '""', $prefixed);
25
+ foreach ($connection->select('PRAGMA index_list("' . $quoted . '")') as $index) {
26
+ $name = str_replace('"', '""', $index->name);
27
+ $columns = $connection->select('PRAGMA index_info("' . $name . '")');
28
+ usort($columns, fn ($a, $b) => $a->seqno <=> $b->seqno);
29
+ $indexes[$index->name] = array_column($columns, 'name');
30
+ }
31
+
32
+ return $indexes;
33
+ }
34
+
35
+ // @codeCoverageIgnoreStart
36
+ if ($connection->getDriverName() !== 'mysql') {
37
+ throw new \RuntimeException('Index inspection requires MySQL or SQLite.');
38
+ }
39
+
40
+ $quoted = str_replace('`', '``', $prefixed);
41
+ foreach ($connection->select('SHOW INDEX FROM `' . $quoted . '`') as $index) {
42
+ $indexes[$index->Key_name][(int) $index->Seq_in_index - 1] = $index->Column_name;
43
+ }
44
+ foreach ($indexes as &$columns) {
45
+ ksort($columns);
46
+ $columns = array_values($columns);
47
+ }
48
+
49
+ return $indexes;
50
+ // @codeCoverageIgnoreEnd
51
+ }
52
+
53
+ /**
54
+ * Whether any index already leads with exactly these columns, in order.
55
+ */
56
+ public static function covers(string $table, array $columns): bool
57
+ {
58
+ foreach (self::for($table) as $indexed) {
59
+ if (array_slice($indexed, 0, count($columns)) === $columns) {
60
+ return true;
61
+ }
62
+ }
63
+
64
+ return false;
65
+ }
66
+ }
@@ -14,11 +14,11 @@ class OrderTracker
14
14
 
15
15
  public function eta(array $options = []): array
16
16
  {
17
- return app(TrackingIntelligenceService::class)->eta($this->order, TrackingOptions::fromArray($options));
17
+ return app(TrackingIntelligenceService::class)->eta($this->order, TrackingOptions::fromArray($options, $this->order->company_uuid));
18
18
  }
19
19
 
20
20
  public function toArray(array $options = []): array
21
21
  {
22
- return app(TrackingIntelligenceService::class)->track($this->order, TrackingOptions::fromArray($options));
22
+ return app(TrackingIntelligenceService::class)->track($this->order, TrackingOptions::fromArray($options, $this->order->company_uuid));
23
23
  }
24
24
  }