@fleetbase/fleetops-engine 0.6.68 → 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 (294) hide show
  1. package/RELEASE.md +44 -9
  2. package/addon/components/admin/telematics-settings.hbs +143 -0
  3. package/addon/components/admin/telematics-settings.js +145 -0
  4. package/addon/components/contact/form.hbs +13 -2
  5. package/addon/components/customer/details.hbs +17 -17
  6. package/addon/components/customer/form.hbs +13 -2
  7. package/addon/components/customer/form.js +0 -56
  8. package/addon/components/driver/details.hbs +13 -19
  9. package/addon/components/driver/form.hbs +20 -35
  10. package/addon/components/driver/form.js +0 -58
  11. package/addon/components/fuel-integration/hub.hbs +1 -1
  12. package/addon/components/layout/fleet-ops-sidebar.js +37 -17
  13. package/addon/components/map/leaflet-live-map.hbs +4 -242
  14. package/addon/components/map/leaflet-live-map.js +2 -1
  15. package/addon/components/map/marker-card/driver.hbs +57 -0
  16. package/addon/components/map/marker-card/driver.js +3 -0
  17. package/addon/components/map/marker-card/vehicle.hbs +78 -0
  18. package/addon/components/map/marker-card/vehicle.js +3 -0
  19. package/addon/components/modals/driver-form.hbs +2 -2
  20. package/addon/components/modals/reset-profile-credentials.hbs +23 -0
  21. package/addon/components/modals/reset-profile-credentials.js +70 -0
  22. package/addon/components/profile-identity-hint.hbs +13 -0
  23. package/addon/components/profile-identity-hint.js +49 -0
  24. package/addon/components/telematic/hub.hbs +1 -1
  25. package/addon/components/widget/live-fleet.hbs +8 -40
  26. package/addon/controllers/analytics/reports/index.js +5 -0
  27. package/addon/controllers/connectivity/devices/index.js +4 -0
  28. package/addon/controllers/connectivity/fuel-providers/index.js +6 -4
  29. package/addon/controllers/connectivity/sensors/index.js +4 -0
  30. package/addon/controllers/connectivity/telematics/index.js +4 -0
  31. package/addon/controllers/maintenance/equipment/index.js +11 -4
  32. package/addon/controllers/maintenance/inspection-forms/index.js +2 -2
  33. package/addon/controllers/maintenance/inspection-submissions/index.js +8 -2
  34. package/addon/controllers/maintenance/maintenances/index.js +4 -0
  35. package/addon/controllers/maintenance/parts/index.js +11 -4
  36. package/addon/controllers/maintenance/schedules/index.js +17 -4
  37. package/addon/controllers/maintenance/work-orders/index.js +11 -4
  38. package/addon/controllers/management/contacts/index.js +4 -0
  39. package/addon/controllers/management/drivers/index.js +8 -0
  40. package/addon/controllers/management/fleets/index.js +4 -0
  41. package/addon/controllers/management/fuel-reports/index.js +4 -0
  42. package/addon/controllers/management/fuel-transactions/index.js +9 -7
  43. package/addon/controllers/management/issues/index.js +4 -0
  44. package/addon/controllers/management/places/index.js +4 -0
  45. package/addon/controllers/management/vehicles/index.js +4 -0
  46. package/addon/controllers/management/vendors/index.js +4 -0
  47. package/addon/controllers/operations/orders/index/details.js +30 -1
  48. package/addon/controllers/operations/orders/index.js +7 -0
  49. package/addon/controllers/operations/service-rates/index.js +3 -0
  50. package/addon/controllers/settings/index.js +13 -0
  51. package/addon/controllers/settings/telematics.js +113 -0
  52. package/addon/extension.js +42 -8
  53. package/addon/routes/analytics/index.js +14 -1
  54. package/addon/routes/analytics/reports/index/details.js +1 -1
  55. package/addon/routes/analytics/reports/index/edit.js +1 -1
  56. package/addon/routes/analytics/reports/index/new.js +14 -1
  57. package/addon/routes/analytics/reports.js +14 -1
  58. package/addon/routes/connectivity/devices/index/new.js +14 -1
  59. package/addon/routes/connectivity/devices.js +14 -1
  60. package/addon/routes/connectivity/events.js +14 -1
  61. package/addon/routes/connectivity/fuel-providers/details/transactions.js +12 -0
  62. package/addon/routes/connectivity/fuel-providers/details.js +9 -0
  63. package/addon/routes/connectivity/fuel-providers/edit.js +9 -0
  64. package/addon/routes/connectivity/fuel-providers/new.js +14 -1
  65. package/addon/routes/connectivity/fuel-providers.js +14 -1
  66. package/addon/routes/connectivity/sensors/index/new.js +14 -1
  67. package/addon/routes/connectivity/sensors.js +14 -1
  68. package/addon/routes/connectivity/telematics/new.js +14 -1
  69. package/addon/routes/connectivity/telematics.js +14 -1
  70. package/addon/routes/connectivity/tracking.js +14 -1
  71. package/addon/routes/maintenance/equipment/index/details.js +2 -2
  72. package/addon/routes/maintenance/equipment/index/edit.js +2 -2
  73. package/addon/routes/maintenance/equipment/index/new.js +14 -1
  74. package/addon/routes/maintenance/equipment.js +14 -1
  75. package/addon/routes/maintenance/index.js +14 -1
  76. package/addon/routes/maintenance/inspection-forms/index/details.js +10 -1
  77. package/addon/routes/maintenance/inspection-forms/index/edit.js +10 -1
  78. package/addon/routes/maintenance/inspection-forms/index/new.js +14 -1
  79. package/addon/routes/maintenance/inspection-forms.js +14 -1
  80. package/addon/routes/maintenance/inspection-submissions/index/details.js +10 -1
  81. package/addon/routes/maintenance/inspection-submissions/index/edit.js +10 -1
  82. package/addon/routes/maintenance/inspection-submissions/index/new.js +14 -1
  83. package/addon/routes/maintenance/inspection-submissions.js +14 -1
  84. package/addon/routes/maintenance/maintenances/index/details.js +2 -2
  85. package/addon/routes/maintenance/maintenances/index/edit.js +2 -2
  86. package/addon/routes/maintenance/maintenances/index/new.js +14 -1
  87. package/addon/routes/maintenance/maintenances.js +14 -1
  88. package/addon/routes/maintenance/parts/index/details.js +2 -2
  89. package/addon/routes/maintenance/parts/index/edit.js +2 -2
  90. package/addon/routes/maintenance/parts/index/new.js +14 -1
  91. package/addon/routes/maintenance/parts.js +14 -1
  92. package/addon/routes/maintenance/schedules/index/details.js +2 -2
  93. package/addon/routes/maintenance/schedules/index/edit.js +2 -2
  94. package/addon/routes/maintenance/schedules/index/new.js +15 -1
  95. package/addon/routes/maintenance/schedules.js +15 -1
  96. package/addon/routes/maintenance/work-orders/index/details.js +2 -2
  97. package/addon/routes/maintenance/work-orders/index/edit.js +2 -2
  98. package/addon/routes/maintenance/work-orders/index/new.js +14 -1
  99. package/addon/routes/maintenance/work-orders.js +14 -1
  100. package/addon/routes/management/fuel-transactions/index/details.js +9 -0
  101. package/addon/routes/management/fuel-transactions.js +14 -1
  102. package/addon/routes/management/index.js +13 -0
  103. package/addon/routes/management/trailers.js +16 -0
  104. package/addon/routes/management/vendors/integrated/details.js +14 -1
  105. package/addon/routes/management/vendors/integrated/edit.js +14 -1
  106. package/addon/routes/management/vendors/integrated/new.js +14 -1
  107. package/addon/routes/management/vendors/integrated.js +14 -1
  108. package/addon/routes/operations/orders.js +40 -1
  109. package/addon/routes/operations/routes/index/details.js +14 -1
  110. package/addon/routes/operations/routes/index/new.js +13 -0
  111. package/addon/routes/operations/routes.js +14 -1
  112. package/addon/routes/operations/service-rates/index/new.js +11 -0
  113. package/addon/routes/operations/service-rates.js +14 -1
  114. package/addon/routes/settings/avatars.js +14 -1
  115. package/addon/routes/settings/custom-fields.js +14 -1
  116. package/addon/routes/settings/map.js +14 -1
  117. package/addon/routes/settings/notifications.js +11 -0
  118. package/addon/routes/settings/orchestrator.js +1 -1
  119. package/addon/routes/settings/payments/index.js +1 -1
  120. package/addon/routes/settings/routing.js +14 -1
  121. package/addon/routes/settings/scheduling.js +14 -1
  122. package/addon/routes/settings/telematics.js +21 -0
  123. package/addon/routes/virtual.js +11 -0
  124. package/addon/routes.js +1 -0
  125. package/addon/services/contact-actions.js +50 -89
  126. package/addon/services/driver-actions.js +98 -0
  127. package/addon/templates/settings/telematics.hbs +70 -0
  128. package/addon/utils/profile-login-actions.js +172 -0
  129. package/app/components/admin/telematics-settings.js +1 -0
  130. package/app/components/{modals/reset-customer-credentials.js → map/marker-card/driver.js} +1 -1
  131. package/app/components/map/marker-card/vehicle.js +1 -0
  132. package/app/components/modals/reset-profile-credentials.js +1 -0
  133. package/app/components/profile-identity-hint.js +1 -0
  134. package/app/controllers/settings/telematics.js +1 -0
  135. package/app/routes/settings/telematics.js +1 -0
  136. package/app/templates/settings/telematics.js +1 -0
  137. package/composer.json +2 -2
  138. package/docs/AFAQY.md +1 -1
  139. package/docs/TELEMATICS_QUEUES.md +1 -1
  140. package/docs/TELEMETRY_ARCHITECTURE.md +72 -1
  141. package/extension.json +1 -1
  142. package/package.json +3 -3
  143. package/scripts/pest-bootstrap.php +12 -0
  144. package/server/config/telemetry.php +12 -0
  145. package/server/migrations/2026_09_21_000001_convert_profile_only_users_to_managed_accounts.php +132 -0
  146. package/server/migrations/2026_09_23_000001_add_retention_index_to_device_events.php +40 -0
  147. package/server/migrations/2026_09_23_000002_add_retention_index_to_positions.php +40 -0
  148. package/server/migrations/2026_09_23_000003_add_retention_index_to_telematic_sync_runs.php +40 -0
  149. package/server/resources/views/mail/driver-credentials.blade.php +21 -0
  150. package/server/src/Auth/Schemas/FleetOps.php +104 -5
  151. package/server/src/Console/Commands/DrainTelematicInbox.php +3 -13
  152. package/server/src/Console/Commands/FixCustomerCompanies.php +1 -1
  153. package/server/src/Console/Commands/FixDriverCompanies.php +1 -1
  154. package/server/src/Console/Commands/ProcessOperationalAlerts.php +3 -3
  155. package/server/src/Console/Commands/PruneTelematicsData.php +318 -0
  156. package/server/src/Exceptions/ProfileIdentityConflictException.php +34 -0
  157. package/server/src/Http/Controllers/Api/v1/CustomerController.php +14 -0
  158. package/server/src/Http/Controllers/Api/v1/DriverController.php +56 -39
  159. package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +9 -0
  160. package/server/src/Http/Controllers/FleetOpsController.php +21 -0
  161. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +11 -0
  162. package/server/src/Http/Controllers/Internal/v1/ContactController.php +16 -17
  163. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +54 -4
  164. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +13 -0
  165. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +11 -0
  166. package/server/src/Http/Controllers/Internal/v1/DriverController.php +204 -162
  167. package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +47 -0
  168. package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +13 -0
  169. package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +15 -0
  170. package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +9 -3
  171. package/server/src/Http/Controllers/Internal/v1/InspectionFormController.php +15 -0
  172. package/server/src/Http/Controllers/Internal/v1/LiveController.php +16 -0
  173. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +15 -0
  174. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +15 -0
  175. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +15 -0
  176. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +11 -0
  177. package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +42 -1
  178. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +12 -0
  179. package/server/src/Http/Controllers/Internal/v1/OrderController.php +178 -27
  180. package/server/src/Http/Controllers/Internal/v1/PaymentController.php +13 -0
  181. package/server/src/Http/Controllers/Internal/v1/RadarController.php +19 -0
  182. package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +9 -3
  183. package/server/src/Http/Controllers/Internal/v1/SettingController.php +465 -14
  184. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +21 -0
  185. package/server/src/Http/Controllers/Internal/v1/TrailerController.php +21 -0
  186. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +21 -0
  187. package/server/src/Http/Controllers/Internal/v1/VendorController.php +17 -0
  188. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +11 -0
  189. package/server/src/Http/Requests/CreateDriverRequest.php +6 -2
  190. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +9 -18
  191. package/server/src/Http/Resources/v1/Contact.php +3 -0
  192. package/server/src/Http/Resources/v1/Driver.php +3 -0
  193. package/server/src/Jobs/DispatchTelematicsRetentionJobs.php +70 -0
  194. package/server/src/Jobs/PruneTelematicsDataJob.php +74 -0
  195. package/server/src/Listeners/NotifyDriverOnShiftChange.php +3 -3
  196. package/server/src/Mail/CustomerCredentialsMail.php +1 -1
  197. package/server/src/Mail/DriverCredentialsMail.php +55 -0
  198. package/server/src/Models/Contact.php +55 -90
  199. package/server/src/Models/Sensor.php +3 -5
  200. package/server/src/Observers/ContactObserver.php +2 -22
  201. package/server/src/Observers/DriverObserver.php +5 -6
  202. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +9 -1
  203. package/server/src/Providers/FleetOpsServiceProvider.php +18 -0
  204. package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +62 -5
  205. package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +24 -5
  206. package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +6 -2
  207. package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +40 -7
  208. package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +86 -24
  209. package/server/src/Support/Ai/FleetOpsAiConsoleCommands.php +122 -0
  210. package/server/src/Support/Ai/Tools/CreateOrderTool.php +86 -0
  211. package/server/src/Support/Ai/Tools/ImportOrdersTool.php +53 -0
  212. package/server/src/Support/Ai/Tools/OptimizeOrderRouteTool.php +70 -0
  213. package/server/src/Support/Ai/Tools/SearchResourcesTool.php +73 -0
  214. package/server/src/Support/Analytics/LiveFleet.php +18 -2
  215. package/server/src/Support/Authorization.php +103 -0
  216. package/server/src/Support/Database/TableIndexes.php +66 -0
  217. package/server/src/Support/OrderTracker.php +2 -2
  218. package/server/src/Support/ProfileAccountManager.php +405 -0
  219. package/server/src/Support/Reporting/FleetOpsReportSchema.php +476 -116
  220. package/server/src/Support/Telematics/Retention/RetentionPolicy.php +300 -0
  221. package/server/src/Support/Telematics/Retention/TelemetryActivity.php +40 -0
  222. package/server/src/Support/Telematics/TelematicService.php +12 -2
  223. package/server/src/Support/Telematics/Telemetry/Ingestor.php +4 -2
  224. package/server/src/Tracking/TrackingIntelligenceService.php +1 -1
  225. package/server/src/Tracking/TrackingOptions.php +2 -2
  226. package/server/src/Traits/AuthorizesMethods.php +37 -0
  227. package/server/src/routes.php +14 -1
  228. package/server/tests/AiOperationalQueryCapabilityTest.php +1 -0
  229. package/server/tests/AnalyticsRoutesTest.php +29 -2
  230. package/server/tests/ApiCustomerControllerContractsTest.php +21 -0
  231. package/server/tests/ApiDriverControllerContractsTest.php +102 -20
  232. package/server/tests/CommandContractsTest.php +4 -4
  233. package/server/tests/ControllerHelperContractsTest.php +3 -11
  234. package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +21 -1
  235. package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +36 -0
  236. package/server/tests/Feature/Http/Api/DriverControllerSessionHelpersTest.php +19 -6
  237. package/server/tests/Feature/Http/Api/ProfileOnlyUserConversionMigrationTest.php +201 -0
  238. package/server/tests/Feature/Http/Api/TelematicsRetentionIndexMigrationTest.php +115 -0
  239. package/server/tests/Feature/Http/Internal/ContactControllerEndpointsTest.php +6 -4
  240. package/server/tests/Feature/Http/Internal/CustomerControllerCredentialsTest.php +6 -2
  241. package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +23 -0
  242. package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +42 -17
  243. package/server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php +168 -277
  244. package/server/tests/Feature/Http/Internal/DriverControllerLoginManagementTest.php +716 -0
  245. package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +10 -3
  246. package/server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php +18 -0
  247. package/server/tests/Feature/Http/Internal/OrderControllerTenantScopingTest.php +411 -0
  248. package/server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php +8 -20
  249. package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +71 -0
  250. package/server/tests/Feature/Http/Internal/TelematicsStorageDeadlinesTest.php +119 -0
  251. package/server/tests/InternalCustomerControllerContractsTest.php +32 -1
  252. package/server/tests/InternalImportExportControllerContractsTest.php +5 -8
  253. package/server/tests/ModelAccessorContractsTest.php +82 -20
  254. package/server/tests/NavigatorControllerContractsTest.php +36 -4
  255. package/server/tests/NotificationAndMailContractsTest.php +31 -0
  256. package/server/tests/ObserverContractsTest.php +28 -3
  257. package/server/tests/OrchestratorConsumableApiTest.php +42 -0
  258. package/server/tests/ProviderContractsTest.php +10 -0
  259. package/server/tests/ReportSchemaContractsTest.php +120 -6
  260. package/server/tests/RequestContractsTest.php +19 -11
  261. package/server/tests/SettingControllerContractsTest.php +429 -18
  262. package/server/tests/SmallControllerContractsTest.php +8 -5
  263. package/server/tests/Support/PruneTelematicsDataProbe.php +55 -0
  264. package/server/tests/SupportJobAndAiCoverageTest.php +3 -3
  265. package/server/tests/TelematicControllerContractsTest.php +22 -4
  266. package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +2 -2
  267. package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +353 -0
  268. package/server/tests/Unit/Jobs/DispatchTelematicsRetentionJobsTest.php +114 -0
  269. package/server/tests/Unit/Jobs/PruneTelematicsDataJobTest.php +60 -0
  270. package/server/tests/Unit/Models/ContactCustomerUserTest.php +90 -14
  271. package/server/tests/Unit/Models/ContactTest.php +48 -5
  272. package/server/tests/Unit/Models/SensorTest.php +2 -2
  273. package/server/tests/Unit/Models/SmallModelSurfacesTest.php +31 -32
  274. package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +7 -2
  275. package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +20 -0
  276. package/server/tests/Unit/Providers/FleetOpsAiRegistrationTest.php +15 -2
  277. package/server/tests/Unit/Support/Ai/AssetStatusCapabilityQueriesTest.php +42 -0
  278. package/server/tests/Unit/Support/Ai/Capabilities/OrderInsightsCapabilityTest.php +19 -1
  279. package/server/tests/Unit/Support/Ai/Capabilities/SearchResourcesCapabilityTest.php +85 -1
  280. package/server/tests/Unit/Support/Ai/FleetOpsAiConsoleCommandsTest.php +31 -0
  281. package/server/tests/Unit/Support/Ai/Tools/FleetOpsAiToolsTest.php +246 -0
  282. package/server/tests/Unit/Support/AiCapabilityPermissionsTest.php +3 -3
  283. package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +6 -0
  284. package/server/tests/Unit/Support/AuthorizationTest.php +142 -0
  285. package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +12 -1
  286. package/server/tests/Unit/Support/MoreQuerySeamsTest.php +11 -2
  287. package/server/tests/Unit/Support/ProfileAccountManagerTest.php +643 -0
  288. package/server/tests/Unit/Support/Telematics/Retention/RetentionPolicyTest.php +190 -0
  289. package/server/tests/Unit/Support/Telematics/Retention/TelemetryActivityTest.php +68 -0
  290. package/server/tests/Unit/Tracking/TrackingOptionsCompanySettingsTest.php +109 -0
  291. package/translations/en-us.yaml +85 -2
  292. package/translations/uk-ua.yaml +3475 -0
  293. package/addon/components/modals/reset-customer-credentials.hbs +0 -18
  294. package/addon/components/modals/reset-customer-credentials.js +0 -55
@@ -0,0 +1,190 @@
1
+ <?php
2
+
3
+ use Fleetbase\FleetOps\Support\Telematics\Retention\RetentionPolicy;
4
+ use Illuminate\Support\Carbon;
5
+
6
+ /**
7
+ * RetentionPolicy combines administrator policy with allowed company history
8
+ * preferences, enforces maximums, and caches policies for long-lived workers.
9
+ */
10
+ function fleetopsRetentionResolver(array $system, array $company, ?array &$calls = null): void
11
+ {
12
+ $calls = [];
13
+ RetentionPolicy::$settingsResolver = function (string $scope, string $key, mixed $default, ?string $companyUuid) use ($system, $company, &$calls) {
14
+ $calls[] = [$scope, $key, $companyUuid];
15
+ expect($key)->toBe(RetentionPolicy::SETTING_KEY);
16
+
17
+ return $scope === 'system' ? $system : ($company[$companyUuid] ?? $default);
18
+ };
19
+ }
20
+
21
+ beforeEach(function () {
22
+ RetentionPolicy::flush();
23
+ RetentionPolicy::$settingsResolver = null;
24
+ config(['telematics.telemetry' => ['event_retention_days' => 45, 'processed_retention_hours' => 48, 'log_telemetry_activity' => true, 'poll_queue' => 'default']]);
25
+ });
26
+
27
+ afterEach(function () {
28
+ RetentionPolicy::flush();
29
+ RetentionPolicy::$settingsResolver = null;
30
+ Carbon::setTestNow();
31
+ config(['telematics.telemetry' => []]);
32
+ });
33
+
34
+ test('defaults come from package config, overridden by the admin setting, with everything clamped', function () {
35
+ fleetopsRetentionResolver([], []);
36
+ expect(RetentionPolicy::defaults())->toBe([
37
+ 'event_retention_days' => 45,
38
+ 'event_compact_after_days' => 7,
39
+ 'position_retention_days' => 90,
40
+ 'processed_retention_hours' => 48,
41
+ 'quarantine_retention_days' => 7,
42
+ 'sync_run_retention_days' => 7,
43
+ 'max_event_retention_days' => 0,
44
+ 'max_position_retention_days' => 0,
45
+ 'log_telemetry_activity' => true,
46
+ ]);
47
+
48
+ RetentionPolicy::flush();
49
+ fleetopsRetentionResolver([
50
+ 'event_retention_days' => 99999,
51
+ 'event_compact_after_days' => '',
52
+ 'position_retention_days' => 0,
53
+ 'quarantine_retention_days' => -5,
54
+ 'processed_retention_hours' => null,
55
+ 'log_telemetry_activity' => 'no',
56
+ 'ignored' => 'value',
57
+ ], []);
58
+ $defaults = RetentionPolicy::defaults();
59
+ expect($defaults['event_retention_days'])->toBe(3650)
60
+ ->and($defaults['event_compact_after_days'])->toBe(7)
61
+ ->and($defaults['position_retention_days'])->toBe(0)
62
+ ->and($defaults['quarantine_retention_days'])->toBe(1)
63
+ ->and($defaults['processed_retention_hours'])->toBe(48)
64
+ ->and($defaults['log_telemetry_activity'])->toBeFalse()
65
+ ->and($defaults)->not->toHaveKey('ignored')
66
+ ->and(RetentionPolicy::keys())->toBe(array_keys(RetentionPolicy::FALLBACKS));
67
+ });
68
+
69
+ test('company policies layer over the defaults and are cached until flushed', function () {
70
+ fleetopsRetentionResolver(['position_retention_days' => 30], ['company-1' => ['event_retention_days' => 10, 'log_telemetry_activity' => false]], $calls);
71
+
72
+ $policy = RetentionPolicy::forCompany('company-1');
73
+ expect($policy->get('event_retention_days'))->toBe(10)
74
+ ->and($policy->get('position_retention_days'))->toBe(30)
75
+ ->and($policy->get('processed_retention_hours'))->toBe(48)
76
+ ->and($policy->logsTelemetryActivity())->toBeTrue()
77
+ ->and($policy->toArray())->toHaveKeys(RetentionPolicy::keys())
78
+ ->and(RetentionPolicy::forCompany('company-1'))->toBe($policy)
79
+ ->and(RetentionPolicy::forCompany(null)->toArray())->toBe(RetentionPolicy::defaults())
80
+ ->and(RetentionPolicy::forCompany('company-2')->get('event_retention_days'))->toBe(45)
81
+ ->and(array_column($calls, 0))->toBe(['system', 'company', 'company']);
82
+
83
+ RetentionPolicy::flush('company-1');
84
+ RetentionPolicy::forCompany('company-1');
85
+ RetentionPolicy::forCompany('company-2');
86
+ expect(count($calls))->toBe(4);
87
+
88
+ RetentionPolicy::flush();
89
+ RetentionPolicy::forCompany('company-2');
90
+ expect(array_column($calls, 0))->toBe(['system', 'company', 'company', 'company', 'system', 'company']);
91
+ });
92
+
93
+ test('company history respects admin maximums and cannot override infrastructure or logging', function () {
94
+ fleetopsRetentionResolver([
95
+ 'event_retention_days' => 0,
96
+ 'position_retention_days' => 90,
97
+ 'max_event_retention_days' => 30,
98
+ 'max_position_retention_days' => 45,
99
+ 'log_telemetry_activity' => false,
100
+ ], [
101
+ 'company-1' => [
102
+ 'event_retention_days' => 0,
103
+ 'position_retention_days' => 120,
104
+ 'max_event_retention_days' => 0,
105
+ 'max_position_retention_days' => 0,
106
+ 'event_compact_after_days' => 0,
107
+ 'processed_retention_hours' => 0,
108
+ 'quarantine_retention_days' => 0,
109
+ 'sync_run_retention_days' => 0,
110
+ 'log_telemetry_activity' => true,
111
+ ],
112
+ 'company-2' => ['event_retention_days' => 5, 'position_retention_days' => 15],
113
+ ]);
114
+
115
+ $policy = RetentionPolicy::forCompany('company-1');
116
+ expect($policy->get('event_retention_days'))->toBe(30)
117
+ ->and($policy->get('position_retention_days'))->toBe(45)
118
+ ->and($policy->get('event_compact_after_days'))->toBe(7)
119
+ ->and($policy->get('processed_retention_hours'))->toBe(48)
120
+ ->and($policy->get('quarantine_retention_days'))->toBe(7)
121
+ ->and($policy->get('sync_run_retention_days'))->toBe(7)
122
+ ->and($policy->logsTelemetryActivity())->toBeFalse()
123
+ ->and(RetentionPolicy::forCompany('company-2')->get('event_retention_days'))->toBe(5)
124
+ ->and(RetentionPolicy::forCompany('company-2')->get('position_retention_days'))->toBe(15)
125
+ ->and(RetentionPolicy::forCompany('inherited')->get('event_retention_days'))->toBe(30)
126
+ ->and(RetentionPolicy::forCompany('inherited')->get('position_retention_days'))->toBe(45)
127
+ ->and(RetentionPolicy::forCompany(null)->get('event_retention_days'))->toBe(30);
128
+ });
129
+
130
+ test('unconfigured maximums preserve unlimited history and null preferences inherit defaults', function () {
131
+ fleetopsRetentionResolver([], ['company-1' => ['event_retention_days' => 0, 'position_retention_days' => null]]);
132
+
133
+ expect(RetentionPolicy::forCompany('company-1')->get('event_retention_days'))->toBe(0)
134
+ ->and(RetentionPolicy::forCompany('company-1')->get('position_retention_days'))->toBe(90)
135
+ ->and(RetentionPolicy::companyPreferences(['event_retention_days' => 'invalid', 'position_retention_days' => false]))->toBe([])
136
+ ->and(RetentionPolicy::companyPreferences(['event_retention_days' => null, 'position_retention_days' => '', 'log_telemetry_activity' => true]))->toBe([]);
137
+ });
138
+
139
+ test('constrained preferences keep zero explicit when allowed and replace it when an administrator imposes a maximum', function () {
140
+ $preferences = ['event_retention_days' => 0, 'position_retention_days' => null];
141
+ expect(RetentionPolicy::constrainCompanyPreferences($preferences, RetentionPolicy::FALLBACKS))->toBe(['event_retention_days' => 0]);
142
+
143
+ $defaults = array_replace(RetentionPolicy::FALLBACKS, ['max_event_retention_days' => 90, 'max_position_retention_days' => 360]);
144
+ $bounded = RetentionPolicy::constrainCompanyPreferences($preferences, $defaults);
145
+ expect($bounded)->toBe(['event_retention_days' => 90])
146
+ ->and(RetentionPolicy::constrainCompanyPreferences(['event_retention_days' => 30, 'position_retention_days' => 720], $defaults))->toBe(['event_retention_days' => 30, 'position_retention_days' => 360])
147
+ ->and(RetentionPolicy::constrainCompanyPreferences([], $defaults))->toBe([])
148
+ ->and(RetentionPolicy::constrainCompanyPreferences($bounded, RetentionPolicy::FALLBACKS))->toBe(['event_retention_days' => 90]);
149
+ });
150
+
151
+ test('cutoffs, compaction and deletion rules follow the clamped values', function () {
152
+ Carbon::setTestNow('2026-09-23 12:00:00 UTC');
153
+
154
+ $policy = RetentionPolicy::fromArray(['event_retention_days' => 10, 'event_compact_after_days' => 10, 'processed_retention_hours' => 6, 'position_retention_days' => 0]);
155
+ expect($policy->deletesEvents())->toBeTrue()
156
+ ->and($policy->compactsEvents())->toBeFalse()
157
+ ->and($policy->cutoff('processed_retention_hours')?->toDateTimeString())->toBe('2026-09-23 06:00:00')
158
+ ->and($policy->cutoff('event_retention_days')?->toDateTimeString())->toBe('2026-09-13 12:00:00')
159
+ ->and($policy->cutoff('position_retention_days'))->toBeNull()
160
+ ->and($policy->cutoff('unknown'))->toBeNull()
161
+ ->and($policy->get('unknown'))->toBeNull();
162
+
163
+ expect(RetentionPolicy::fromArray(['event_retention_days' => 10, 'event_compact_after_days' => 3])->compactsEvents())->toBeTrue()
164
+ ->and(RetentionPolicy::fromArray(['event_retention_days' => 0, 'event_compact_after_days' => 5])->compactsEvents())->toBeTrue()
165
+ ->and(RetentionPolicy::fromArray(['event_retention_days' => 0, 'event_compact_after_days' => 5])->deletesEvents())->toBeFalse()
166
+ ->and(RetentionPolicy::fromArray(['event_compact_after_days' => 0])->compactsEvents())->toBeFalse()
167
+ ->and(RetentionPolicy::fromArray([], ['event_retention_days' => 12])->get('event_retention_days'))->toBe(12)
168
+ ->and(RetentionPolicy::normalize(['log_telemetry_activity' => '1'], [])['log_telemetry_activity'])->toBeTrue();
169
+ });
170
+
171
+ test('a failing or malformed settings store falls back to the layer below', function () {
172
+ RetentionPolicy::$settingsResolver = fn () => throw new RuntimeException('settings unavailable');
173
+ expect(RetentionPolicy::forCompany('company-1')->get('event_retention_days'))->toBe(45);
174
+
175
+ RetentionPolicy::flush();
176
+ RetentionPolicy::$settingsResolver = fn () => 'not-an-array';
177
+ expect(RetentionPolicy::defaults()['processed_retention_hours'])->toBe(48);
178
+ });
179
+
180
+ test('company lookups scope the session for the setting store and restore it afterwards', function () {
181
+ // Without a resolver the policy reaches Setting, which has no database here and throws.
182
+ app('session')->put('company', 'previous-company');
183
+ expect(RetentionPolicy::forCompany('company-9')->get('event_retention_days'))->toBe(45)
184
+ ->and(app('session')->get('company'))->toBe('previous-company');
185
+
186
+ app('session')->forget('company');
187
+ RetentionPolicy::flush();
188
+ expect(RetentionPolicy::forCompany('company-9')->get('processed_retention_hours'))->toBe(48)
189
+ ->and(app('session')->has('company'))->toBeFalse();
190
+ });
@@ -0,0 +1,68 @@
1
+ <?php
2
+
3
+ use Fleetbase\FleetOps\Support\Telematics\Retention\RetentionPolicy;
4
+ use Fleetbase\FleetOps\Support\Telematics\Retention\TelemetryActivity;
5
+ use Spatie\Activitylog\ActivityLogger;
6
+
7
+ /**
8
+ * Telemetry writes bypass the activity log unless an administrator enabled it, and
9
+ * still run when no activity logger is available.
10
+ */
11
+ function fleetopsTelemetryActivityLogger(): ActivityLogger
12
+ {
13
+ $logger = new class extends ActivityLogger {
14
+ public int $suppressed = 0;
15
+
16
+ public function __construct()
17
+ {
18
+ }
19
+
20
+ public function withoutLogs(Closure $callback): mixed
21
+ {
22
+ $this->suppressed++;
23
+
24
+ return $callback();
25
+ }
26
+ };
27
+ app()->instance(ActivityLogger::class, $logger);
28
+
29
+ return $logger;
30
+ }
31
+
32
+ beforeEach(function () {
33
+ RetentionPolicy::flush();
34
+ config(['telematics.telemetry' => []]);
35
+ });
36
+
37
+ afterEach(function () {
38
+ RetentionPolicy::flush();
39
+ RetentionPolicy::$settingsResolver = null;
40
+ app()->forgetInstance(ActivityLogger::class);
41
+ });
42
+
43
+ test('telemetry activity follows the system policy and ignores legacy company overrides', function () {
44
+ RetentionPolicy::$settingsResolver = fn (string $scope, string $key, mixed $default, ?string $company) => $company === 'verbose' ? ['log_telemetry_activity' => true] : [];
45
+ $logger = fleetopsTelemetryActivityLogger();
46
+
47
+ expect(TelemetryActivity::run('quiet', fn () => 'saved'))->toBe('saved')
48
+ ->and($logger->suppressed)->toBe(1)
49
+ ->and(TelemetryActivity::run('verbose', fn () => 'saved'))->toBe('saved')
50
+ ->and($logger->suppressed)->toBe(2)
51
+ ->and(TelemetryActivity::run(null, fn () => 'system'))->toBe('system')
52
+ ->and($logger->suppressed)->toBe(3);
53
+
54
+ RetentionPolicy::flush();
55
+ RetentionPolicy::$settingsResolver = fn (string $scope) => ['log_telemetry_activity' => $scope === 'system'];
56
+
57
+ expect(TelemetryActivity::run('quiet', fn () => 'logged'))->toBe('logged')
58
+ ->and(TelemetryActivity::run('verbose', fn () => 'logged'))->toBe('logged')
59
+ ->and(TelemetryActivity::run(null, fn () => 'system'))->toBe('system')
60
+ ->and($logger->suppressed)->toBe(3);
61
+ });
62
+
63
+ test('telemetry writes still run when the activity logger cannot be resolved', function () {
64
+ RetentionPolicy::$settingsResolver = fn () => [];
65
+ app()->forgetInstance(ActivityLogger::class);
66
+
67
+ expect(TelemetryActivity::run('company-1', fn () => 'unlogged'))->toBe('unlogged');
68
+ });
@@ -0,0 +1,109 @@
1
+ <?php
2
+
3
+ if (!function_exists('Fleetbase\Models\session')) {
4
+ eval('namespace Fleetbase\Models; function session($key = null, $default = null) { if ($key === null) { return new class { public function has($k) { return \session($k) !== null; } public function get($k, $d = null) { return \session($k, $d); } public function missing($k) { return \session($k) === null; } }; } return \session($key, $default); }');
5
+ }
6
+
7
+ use Fleetbase\FleetOps\Models\Order;
8
+ use Fleetbase\FleetOps\Support\OrderTracker;
9
+ use Fleetbase\FleetOps\Tracking\TrackingIntelligenceService;
10
+ use Fleetbase\FleetOps\Tracking\TrackingOptions;
11
+ use Illuminate\Database\ConnectionResolver;
12
+ use Illuminate\Database\Eloquent\Model as EloquentModel;
13
+ use Illuminate\Database\SQLiteConnection;
14
+
15
+ /**
16
+ * Covers company tracking settings resolution for TrackingOptions: the
17
+ * order's company is used when there is no company session (queued jobs,
18
+ * console commands), and the session company remains the fallback.
19
+ */
20
+ function fleetopsTrackingOptionsBoot(): SQLiteConnection
21
+ {
22
+ $connection = new SQLiteConnection(new PDO('sqlite::memory:'));
23
+ $resolver = new ConnectionResolver(['default' => $connection, 'mysql' => $connection]);
24
+ $resolver->setDefaultConnection('mysql');
25
+ EloquentModel::setConnectionResolver($resolver);
26
+
27
+ $connection->getSchemaBuilder()->create('settings', function ($blueprint) {
28
+ $blueprint->increments('id');
29
+ $blueprint->string('key')->nullable();
30
+ $blueprint->text('value')->nullable();
31
+ $blueprint->timestamps();
32
+ });
33
+
34
+ $connection->table('settings')->insert([
35
+ ['key' => 'company.company-a.tracking', 'value' => json_encode(['provider' => 'osrm', 'cache_ttl_seconds' => 15])],
36
+ ['key' => 'company.company-b.tracking', 'value' => json_encode(['provider' => 'calculated', 'cache_ttl_seconds' => 45])],
37
+ ]);
38
+
39
+ session(['company' => null]);
40
+
41
+ return $connection;
42
+ }
43
+
44
+ test('tracking options read the given company settings regardless of session', function (?string $sessionCompany) {
45
+ fleetopsTrackingOptionsBoot();
46
+ session(['company' => $sessionCompany]);
47
+
48
+ $options = TrackingOptions::fromArray([], 'company-a');
49
+
50
+ expect($options->provider)->toBe('osrm')
51
+ ->and($options->cacheTtlSeconds)->toBe(15);
52
+
53
+ // Explicit options still win over company settings
54
+ expect(TrackingOptions::fromArray(['provider' => 'google'], 'company-a')->provider)->toBe('google');
55
+ })->with([null, 'company-b']);
56
+
57
+ test('tracking options fall back to the session company when no company is given', function () {
58
+ fleetopsTrackingOptionsBoot();
59
+ session(['company' => 'company-b']);
60
+
61
+ $options = TrackingOptions::fromArray([]);
62
+ session(['company' => null]);
63
+
64
+ expect($options->provider)->toBe('calculated')
65
+ ->and($options->cacheTtlSeconds)->toBe(45);
66
+ });
67
+
68
+ test('order tracker builds options from the order company', function () {
69
+ fleetopsTrackingOptionsBoot();
70
+
71
+ $service = new class extends TrackingIntelligenceService {
72
+ public ?TrackingOptions $received = null;
73
+
74
+ public function __construct()
75
+ {
76
+ }
77
+
78
+ public function track(Order $order, array|TrackingOptions $options = []): array
79
+ {
80
+ $this->received = $options;
81
+
82
+ return [];
83
+ }
84
+ };
85
+ app()->instance(TrackingIntelligenceService::class, $service);
86
+
87
+ $order = new Order();
88
+ $order->setRawAttributes(['uuid' => 'order-tracking-options', 'company_uuid' => 'company-a'], true);
89
+
90
+ (new OrderTracker($order))->toArray();
91
+ app()->forgetInstance(TrackingIntelligenceService::class);
92
+
93
+ expect($service->received)->toBeInstanceOf(TrackingOptions::class)
94
+ ->and($service->received->provider)->toBe('osrm');
95
+ });
96
+
97
+ test('tracking remains available with configured defaults when the settings database is unavailable', function () {
98
+ $connection = fleetopsTrackingOptionsBoot();
99
+ $connection->getSchemaBuilder()->drop('settings');
100
+ $original = config('fleetops.tracking', []);
101
+ try {
102
+ config(['fleetops.tracking' => ['provider' => 'calculated', 'cache_ttl_seconds' => 17]]);
103
+ $options = TrackingOptions::fromArray([], 'uncached-unavailable-company');
104
+ expect($options->provider)->toBe('calculated')->and($options->cacheTtlSeconds)->toBe(17);
105
+ expect(TrackingOptions::fromArray(['provider' => 'osrm'], 'uncached-unavailable-company')->provider)->toBe('osrm');
106
+ } finally {
107
+ config(['fleetops.tracking' => $original]);
108
+ }
109
+ });
@@ -114,6 +114,7 @@ menu:
114
114
  maintenances: Maintenances
115
115
  orchestrator: Orchestrator
116
116
  orchestrator-settings: Orchestrator Settings
117
+ telematics-settings: Telematics
117
118
 
118
119
  trailer:
119
120
  navigation-description: Manage towed assets, towing connections, equipment, and telematics.
@@ -770,6 +771,9 @@ contact:
770
771
 
771
772
  customer:
772
773
  fields:
774
+ addresses: Addresses
775
+ no-addresses: This customer has no saved addresses.
776
+ primary-address: Primary
773
777
  customer-details: Customer Details
774
778
  select-address: Select Address
775
779
  new-address: New Adddress
@@ -801,11 +805,9 @@ driver:
801
805
  fields:
802
806
  avatar: Avatar
803
807
  driver-details: Driver Details
804
- select-user: Select User
805
808
  select-vehicle: Select Vehicle
806
809
  upload-new-photo: Upload new photo
807
810
  user-account: User Account
808
- user-account-help-text: Each driver profile needs to be connected to a specific user account. This association is essential for handling the authentication process, ensuring that each driver can securely access and manage their profile.
809
811
  vendor: Vendor
810
812
  select-vendor: Select Vendor
811
813
  driver-license: Drivers License
@@ -837,6 +839,40 @@ driver:
837
839
  unassign-vehicle-body: This removes {driverName} from {vehicleName}. Order assignments are not changed.
838
840
  unassign-vehicle-success: Unassigned {vehicleName} from {driverName}.
839
841
  issue-title: Issue reported for {driverName}
842
+
843
+ profile-account:
844
+ staff-linked-helper: Linked to a team member account — manage email and phone from IAM.
845
+ will-link-to-staff: Will be linked to existing team member {name}.
846
+ actions:
847
+ reset-password: Reset Password
848
+ send-credentials: Send Credentials
849
+ deactivate-login: Deactivate Login
850
+ reactivate-login: Reactivate Login
851
+ convert-to-vendor: Convert to Vendor
852
+ prompts:
853
+ send-credentials-title: Send Login Credentials
854
+ send-credentials-body: Generate a new temporary password and send {name} their login credentials.
855
+ send-credentials-success: Login credentials sent to {name}.
856
+ deactivate-login-body: Deactivate the login for {name}. Their profile and history will be preserved.
857
+ deactivate-login-success: Login deactivated for {name}.
858
+ reactivate-login-body: Reactivate the login for {name}.
859
+ reactivate-login-success: Login reactivated for {name}.
860
+ send-portal-credentials-title: Send Portal Credentials
861
+ send-portal-credentials-body: Generate a new temporary password and email this contact their portal credentials.
862
+ send-portal-credentials-success: Portal credentials sent.
863
+ deactivate-portal-login-body: Deactivate portal access for this contact. Their profile and history will be preserved.
864
+ deactivate-portal-login-success: Portal login deactivated.
865
+ reactivate-portal-login-body: Reactivate portal access for this contact.
866
+ reactivate-portal-login-success: Portal login reactivated.
867
+ reset-password-title: Reset Login Credentials
868
+ reset-password-heading: You are about to reset the password for {name}
869
+ reset-password-body: Please enter the new password and confirm it below. You can optionally send the new credentials by selecting the checkbox.
870
+ reset-password-accept: Reset Credentials
871
+ reset-password-success: Password reset.
872
+ new-password: New Password
873
+ confirm-new-password: Confirm New Password
874
+ send-new-credentials: Send the new credentials to this account
875
+
840
876
  device:
841
877
  attachment:
842
878
  title: Attached asset
@@ -2688,6 +2724,53 @@ route-list:
2688
2724
  more-waypoints: more waypoints
2689
2725
 
2690
2726
  settings:
2727
+ telematics:
2728
+ telematics-settings: Telematics Settings
2729
+ intro: Choose how long your organization keeps device events and position history within the limits set by your system administrator.
2730
+ use-system-default: Use system default
2731
+ history-policy-maximum: "Your administrator allows up to {days} days of history. Unlimited retention is not available."
2732
+ history-policy-unlimited: Your administrator allows unlimited history. Set the retention period to 0 to keep history forever.
2733
+ device-events: Device Events Retention
2734
+ event-retention-days: Keep device events for (days)
2735
+ event-retention-days-help: Events older than this are permanently deleted. Applies to telemetry updates and provider alerts alike.
2736
+ event-compact-after-days: Strip raw payloads after (days)
2737
+ event-compact-after-days-help: Events older than this keep their type, severity, location and normalized data, but the raw provider payload is removed. Raw payloads are the bulk of each event's size.
2738
+ compaction-warning: Raw payloads are only stripped from events that are kept. Set this lower than the event retention or set it to 0 to disable compaction.
2739
+ positions: Position History Retention
2740
+ position-retention-days: Keep positions for (days)
2741
+ position-retention-days-help: Vehicle and trailer position history older than this is permanently deleted. Route replay and history views only reach back this far.
2742
+ inbox: Delivery Inbox & Sync Run Retention
2743
+ processed-retention-hours: Keep processed deliveries for (hours)
2744
+ processed-retention-hours-help: Raw provider batches are stored encrypted until processed and kept briefly for replay and diagnostics. Each batch can be close to a megabyte.
2745
+ quarantine-retention-days: Keep quarantined deliveries for (days)
2746
+ quarantine-retention-days-help: Batches that could not be processed stay available for inspection and replay for this long.
2747
+ sync-run-retention-days: Keep sync run diagnostics for (days)
2748
+ sync-run-retention-days-help: One sync run row is recorded per connection per poll. In-flight runs are never removed.
2749
+ activity-logging: Telemetry Activity Logging
2750
+ log-telemetry-activity: Log telemetry activity
2751
+ log-telemetry-activity-help: Write telemetry-driven device, event and vehicle saves to the activity log. Off by default; every poll otherwise adds several activity rows per device.
2752
+ storage-usage: System Telematics Storage Usage
2753
+ table: Table
2754
+ rows: Rows
2755
+ estimated-rows: "{count} (estimated)"
2756
+ estimated-size: Estimated size
2757
+ oldest: Oldest row
2758
+ not-available: n/a
2759
+ raw-payload-rows: "{count} rows still carry raw payloads, {compactable} eligible for compaction now"
2760
+ refresh: Refresh
2761
+ run-cleanup: Run system cleanup
2762
+ run-cleanup-confirm: This queues telematics cleanup across all organizations, using the saved system policy and each organization's effective history preferences. Expired records are permanently deleted and cannot be recovered. Continue?
2763
+ cleanup-queued: Telematics cleanup has been queued across all organizations. Usage refreshes shortly; cleanup may still be running.
2764
+ cleanup-unavailable: System-wide telematics cleanup could not be confirmed. Please try again.
2765
+ usage-unavailable: Storage usage could not be loaded. Select Refresh to try again.
2766
+ space-reclaim-note: Usage includes all telematics data across the system. Row counts marked as estimated use database statistics. Sizes include table data and indexes. Deleting records frees space for reuse within the database; physical disk usage may not decrease immediately.
2767
+ settings-saved: Telematics settings saved.
2768
+ settings-unavailable: Telematics settings could not be loaded. Please try again.
2769
+ tables:
2770
+ device_events: Device events
2771
+ positions: Positions
2772
+ telematic_deliveries: Delivery inbox
2773
+ telematic_sync_runs: Sync runs
2691
2774
  avatar-management: Avatar Management
2692
2775
  custom-fields: Custom Fields
2693
2776
  notifications: