@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
@@ -151,14 +151,27 @@ export default class MaintenanceSchedulesIndexController extends Controller {
151
151
  helpText: this.intl.t('common.change-layout'),
152
152
  },
153
153
  { icon: 'refresh', onClick: this.maintenanceScheduleActions.refresh, helpText: this.intl.t('common.refresh') },
154
- { text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.maintenanceScheduleActions.transition.create },
155
- { text: this.intl.t('common.import'), type: 'magic', icon: 'upload', onClick: this.maintenanceScheduleActions.import },
156
- { text: this.intl.t('common.export'), icon: 'long-arrow-up', iconClass: 'rotate-icon-45', wrapperClass: 'hidden md:flex', onClick: this.maintenanceScheduleActions.export },
154
+ {
155
+ text: this.intl.t('common.new'),
156
+ type: 'primary',
157
+ icon: 'plus',
158
+ onClick: this.maintenanceScheduleActions.transition.create,
159
+ permission: 'fleet-ops create maintenance-schedule',
160
+ },
161
+ { text: this.intl.t('common.import'), type: 'magic', icon: 'upload', onClick: this.maintenanceScheduleActions.import, permission: 'fleet-ops import maintenance-schedule' },
162
+ {
163
+ text: this.intl.t('common.export'),
164
+ icon: 'long-arrow-up',
165
+ iconClass: 'rotate-icon-45',
166
+ wrapperClass: 'hidden md:flex',
167
+ onClick: this.maintenanceScheduleActions.export,
168
+ permission: 'fleet-ops export maintenance-schedule',
169
+ },
157
170
  ];
158
171
  }
159
172
 
160
173
  get bulkActions() {
161
- return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.maintenanceScheduleActions.bulkDelete }];
174
+ return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.maintenanceScheduleActions.bulkDelete, permission: 'fleet-ops delete maintenance-schedule' }];
162
175
  }
163
176
 
164
177
  get columns() {
@@ -21,14 +21,21 @@ export default class MaintenanceWorkOrdersIndexController extends Controller {
21
21
  get actionButtons() {
22
22
  return [
23
23
  { icon: 'refresh', onClick: this.workOrderActions.refresh, helpText: this.intl.t('common.refresh') },
24
- { text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.workOrderActions.transition.create },
25
- { text: this.intl.t('common.import'), type: 'magic', icon: 'upload', onClick: this.workOrderActions.import },
26
- { text: this.intl.t('common.export'), icon: 'long-arrow-up', iconClass: 'rotate-icon-45', wrapperClass: 'hidden md:flex', onClick: this.workOrderActions.export },
24
+ { text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.workOrderActions.transition.create, permission: 'fleet-ops create work-order' },
25
+ { text: this.intl.t('common.import'), type: 'magic', icon: 'upload', onClick: this.workOrderActions.import, permission: 'fleet-ops import work-order' },
26
+ {
27
+ text: this.intl.t('common.export'),
28
+ icon: 'long-arrow-up',
29
+ iconClass: 'rotate-icon-45',
30
+ wrapperClass: 'hidden md:flex',
31
+ onClick: this.workOrderActions.export,
32
+ permission: 'fleet-ops export work-order',
33
+ },
27
34
  ];
28
35
  }
29
36
 
30
37
  get bulkActions() {
31
- return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.workOrderActions.bulkDelete }];
38
+ return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.workOrderActions.bulkDelete, permission: 'fleet-ops delete work-order' }];
32
39
  }
33
40
 
34
41
  get columns() {
@@ -62,12 +62,14 @@ export default class ManagementContactsIndexController extends Controller {
62
62
  type: 'primary',
63
63
  icon: 'plus',
64
64
  onClick: this.contactActions.transition.create,
65
+ permission: 'fleet-ops create contact',
65
66
  },
66
67
  {
67
68
  text: this.intl.t('common.import'),
68
69
  type: 'magic',
69
70
  icon: 'upload',
70
71
  onClick: this.contactActions.import,
72
+ permission: 'fleet-ops import contact',
71
73
  },
72
74
  {
73
75
  text: this.intl.t('common.export'),
@@ -75,6 +77,7 @@ export default class ManagementContactsIndexController extends Controller {
75
77
  iconClass: 'rotate-icon-45',
76
78
  wrapperClass: 'hidden md:flex',
77
79
  onClick: this.contactActions.export,
80
+ permission: 'fleet-ops export contact',
78
81
  },
79
82
  ];
80
83
  }
@@ -88,6 +91,7 @@ export default class ManagementContactsIndexController extends Controller {
88
91
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
89
92
  class: 'text-red-500',
90
93
  fn: this.contactActions.bulkDelete,
94
+ permission: 'fleet-ops delete contact',
91
95
  },
92
96
  ];
93
97
  }
@@ -103,12 +103,14 @@ export default class ManagementDriversIndexController extends Controller {
103
103
  type: 'primary',
104
104
  icon: 'plus',
105
105
  onClick: this.driverActions.transition.create,
106
+ permission: 'fleet-ops create driver',
106
107
  },
107
108
  {
108
109
  text: this.intl.t('common.import'),
109
110
  type: 'magic',
110
111
  icon: 'upload',
111
112
  onClick: this.driverActions.import,
113
+ permission: 'fleet-ops import driver',
112
114
  },
113
115
  {
114
116
  text: this.intl.t('common.export'),
@@ -116,6 +118,7 @@ export default class ManagementDriversIndexController extends Controller {
116
118
  iconClass: 'rotate-icon-45',
117
119
  wrapperClass: 'hidden md:flex',
118
120
  onClick: this.driverActions.export,
121
+ permission: 'fleet-ops export driver',
119
122
  },
120
123
  ];
121
124
  }
@@ -129,6 +132,7 @@ export default class ManagementDriversIndexController extends Controller {
129
132
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
130
133
  class: 'text-red-500',
131
134
  fn: this.driverActions.bulkDelete,
135
+ permission: 'fleet-ops delete driver',
132
136
  },
133
137
  ];
134
138
  }
@@ -358,6 +362,10 @@ export default class ManagementDriversIndexController extends Controller {
358
362
  {
359
363
  separator: true,
360
364
  },
365
+ ...this.driverActions.accountRowActionItems(),
366
+ {
367
+ separator: true,
368
+ },
361
369
  {
362
370
  label: this.intl.t('common.delete-resource', { resource: this.intl.t('resource.driver') }),
363
371
  icon: 'trash',
@@ -42,12 +42,14 @@ export default class ManagementFleetsIndexController extends Controller {
42
42
  type: 'primary',
43
43
  icon: 'plus',
44
44
  onClick: this.fleetActions.transition.create,
45
+ permission: 'fleet-ops create fleet',
45
46
  },
46
47
  {
47
48
  text: this.intl.t('common.import'),
48
49
  type: 'magic',
49
50
  icon: 'upload',
50
51
  onClick: this.fleetActions.import,
52
+ permission: 'fleet-ops import fleet',
51
53
  },
52
54
  {
53
55
  text: this.intl.t('common.export'),
@@ -55,6 +57,7 @@ export default class ManagementFleetsIndexController extends Controller {
55
57
  iconClass: 'rotate-icon-45',
56
58
  wrapperClass: 'hidden md:flex',
57
59
  onClick: this.fleetActions.export,
60
+ permission: 'fleet-ops export fleet',
58
61
  },
59
62
  ];
60
63
  }
@@ -68,6 +71,7 @@ export default class ManagementFleetsIndexController extends Controller {
68
71
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
69
72
  class: 'text-red-500',
70
73
  fn: this.fleetActions.bulkDelete,
74
+ permission: 'fleet-ops delete fleet',
71
75
  },
72
76
  ];
73
77
  }
@@ -56,12 +56,14 @@ export default class ManagementFuelReportsIndexController extends Controller {
56
56
  type: 'primary',
57
57
  icon: 'plus',
58
58
  onClick: this.fuelReportActions.transition.create,
59
+ permission: 'fleet-ops create fuel-report',
59
60
  },
60
61
  {
61
62
  text: this.intl.t('common.import'),
62
63
  type: 'magic',
63
64
  icon: 'upload',
64
65
  onClick: this.fuelReportActions.import,
66
+ permission: 'fleet-ops import fuel-report',
65
67
  },
66
68
  {
67
69
  text: this.intl.t('common.export'),
@@ -69,6 +71,7 @@ export default class ManagementFuelReportsIndexController extends Controller {
69
71
  iconClass: 'rotate-icon-45',
70
72
  wrapperClass: 'hidden md:flex',
71
73
  onClick: this.fuelReportActions.export,
74
+ permission: 'fleet-ops export fuel-report',
72
75
  },
73
76
  ];
74
77
  }
@@ -82,6 +85,7 @@ export default class ManagementFuelReportsIndexController extends Controller {
82
85
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
83
86
  class: 'text-red-500',
84
87
  fn: this.fuelReportActions.bulkDelete,
88
+ permission: 'fleet-ops delete fuel-report',
85
89
  },
86
90
  ];
87
91
  }
@@ -71,6 +71,7 @@ export default class ManagementFuelTransactionsIndexController extends Controlle
71
71
  icon: 'gas-pump',
72
72
  text: 'Fuel Integrations',
73
73
  onClick: () => this.hostRouter.transitionTo('console.fleet-ops.connectivity.fuel-providers.index'),
74
+ permission: 'fleet-ops list fuel-provider-connection',
74
75
  },
75
76
  ];
76
77
  }
@@ -86,6 +87,7 @@ export default class ManagementFuelTransactionsIndexController extends Controlle
86
87
  {
87
88
  label: `Reprocess ${selected.length} selected`,
88
89
  fn: () => this.confirmAction('reprocess', selected),
90
+ permission: 'fleet-ops update fuel-provider-transaction',
89
91
  },
90
92
  ];
91
93
  }
@@ -196,14 +198,14 @@ export default class ManagementFuelTransactionsIndexController extends Controlle
196
198
  sticky: 'right',
197
199
  width: 60,
198
200
  actions: [
199
- { label: 'Review Details', fn: this.openDetails },
200
- { label: 'Open Fuel Report', fn: this.openFuelReport, isVisible: (transaction) => Boolean(transaction.fuel_report_id) },
201
+ { label: 'Review Details', fn: this.openDetails, permission: 'fleet-ops view fuel-provider-transaction' },
202
+ { label: 'Open Fuel Report', fn: this.openFuelReport, isVisible: (transaction) => Boolean(transaction.fuel_report_id), permission: 'fleet-ops view fuel-report' },
201
203
  { separator: true },
202
- { label: 'Match to Vehicle', fn: this.matchVehicle },
203
- { label: 'Match to Order', fn: this.matchOrder },
204
- { label: 'Reprocess / Rematch', fn: (transaction) => this.confirmAction('reprocess', transaction) },
205
- { label: 'Ignore Transaction', fn: (transaction) => this.confirmAction('ignored', transaction) },
206
- { label: 'Mark Reviewed', fn: (transaction) => this.confirmAction('reviewed', transaction) },
204
+ { label: 'Match to Vehicle', fn: this.matchVehicle, permission: 'fleet-ops update fuel-provider-transaction' },
205
+ { label: 'Match to Order', fn: this.matchOrder, permission: 'fleet-ops update fuel-provider-transaction' },
206
+ { label: 'Reprocess / Rematch', fn: (transaction) => this.confirmAction('reprocess', transaction), permission: 'fleet-ops update fuel-provider-transaction' },
207
+ { label: 'Ignore Transaction', fn: (transaction) => this.confirmAction('ignored', transaction), permission: 'fleet-ops review fuel-provider-transaction' },
208
+ { label: 'Mark Reviewed', fn: (transaction) => this.confirmAction('reviewed', transaction), permission: 'fleet-ops review fuel-provider-transaction' },
207
209
  ],
208
210
  sortable: false,
209
211
  filterable: false,
@@ -56,12 +56,14 @@ export default class ManagementIssuesIndexController extends Controller {
56
56
  type: 'primary',
57
57
  icon: 'plus',
58
58
  onClick: this.issueActions.transition.create,
59
+ permission: 'fleet-ops create issue',
59
60
  },
60
61
  {
61
62
  text: this.intl.t('common.import'),
62
63
  type: 'magic',
63
64
  icon: 'upload',
64
65
  onClick: this.issueActions.import,
66
+ permission: 'fleet-ops import issue',
65
67
  },
66
68
  {
67
69
  text: this.intl.t('common.export'),
@@ -69,6 +71,7 @@ export default class ManagementIssuesIndexController extends Controller {
69
71
  iconClass: 'rotate-icon-45',
70
72
  wrapperClass: 'hidden md:flex',
71
73
  onClick: this.issueActions.export,
74
+ permission: 'fleet-ops export issue',
72
75
  },
73
76
  ];
74
77
  }
@@ -82,6 +85,7 @@ export default class ManagementIssuesIndexController extends Controller {
82
85
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
83
86
  class: 'text-red-500',
84
87
  fn: this.issueActions.bulkDelete,
88
+ permission: 'fleet-ops delete issue',
85
89
  },
86
90
  ];
87
91
  }
@@ -34,12 +34,14 @@ export default class ManagementPlacesIndexController extends Controller {
34
34
  type: 'primary',
35
35
  icon: 'plus',
36
36
  onClick: this.placeActions.transition.create,
37
+ permission: 'fleet-ops create place',
37
38
  },
38
39
  {
39
40
  text: this.intl.t('common.import'),
40
41
  type: 'magic',
41
42
  icon: 'upload',
42
43
  onClick: this.placeActions.import,
44
+ permission: 'fleet-ops import place',
43
45
  },
44
46
  {
45
47
  text: this.intl.t('common.export'),
@@ -47,6 +49,7 @@ export default class ManagementPlacesIndexController extends Controller {
47
49
  iconClass: 'rotate-icon-45',
48
50
  wrapperClass: 'hidden md:flex',
49
51
  onClick: this.placeActions.export,
52
+ permission: 'fleet-ops export place',
50
53
  },
51
54
  ];
52
55
  }
@@ -60,6 +63,7 @@ export default class ManagementPlacesIndexController extends Controller {
60
63
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
61
64
  class: 'text-red-500',
62
65
  fn: this.placeActions.bulkDelete,
66
+ permission: 'fleet-ops delete place',
63
67
  },
64
68
  ];
65
69
  }
@@ -102,12 +102,14 @@ export default class ManagementVehiclesIndexController extends Controller {
102
102
  type: 'primary',
103
103
  icon: 'plus',
104
104
  onClick: this.vehicleActions.transition.create,
105
+ permission: 'fleet-ops create vehicle',
105
106
  },
106
107
  {
107
108
  text: this.intl.t('common.import'),
108
109
  type: 'magic',
109
110
  icon: 'upload',
110
111
  onClick: this.vehicleActions.import,
112
+ permission: 'fleet-ops import vehicle',
111
113
  },
112
114
  {
113
115
  text: this.intl.t('common.export'),
@@ -115,6 +117,7 @@ export default class ManagementVehiclesIndexController extends Controller {
115
117
  iconClass: 'rotate-icon-45',
116
118
  wrapperClass: 'hidden md:flex',
117
119
  onClick: this.vehicleActions.export,
120
+ permission: 'fleet-ops export vehicle',
118
121
  },
119
122
  ];
120
123
  }
@@ -128,6 +131,7 @@ export default class ManagementVehiclesIndexController extends Controller {
128
131
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
129
132
  class: 'text-red-500',
130
133
  fn: this.vehicleActions.bulkDelete,
134
+ permission: 'fleet-ops delete vehicle',
131
135
  },
132
136
  ];
133
137
  }
@@ -83,12 +83,14 @@ export default class ManagementVendorsIndexController extends Controller {
83
83
  type: 'primary',
84
84
  icon: 'plus',
85
85
  onClick: this.vendorActions.transition.create,
86
+ permission: 'fleet-ops create vendor',
86
87
  },
87
88
  {
88
89
  text: this.intl.t('common.import'),
89
90
  type: 'magic',
90
91
  icon: 'upload',
91
92
  onClick: this.vendorActions.import,
93
+ permission: 'fleet-ops import vendor',
92
94
  },
93
95
  {
94
96
  text: this.intl.t('common.export'),
@@ -96,6 +98,7 @@ export default class ManagementVendorsIndexController extends Controller {
96
98
  iconClass: 'rotate-icon-45',
97
99
  wrapperClass: 'hidden md:flex',
98
100
  onClick: this.vendorActions.export,
101
+ permission: 'fleet-ops export vendor',
99
102
  },
100
103
  ];
101
104
  }
@@ -109,6 +112,7 @@ export default class ManagementVendorsIndexController extends Controller {
109
112
  label: this.intl.t('common.delete-selected-count', { count: selected.length }),
110
113
  class: 'text-red-500',
111
114
  fn: this.vendorActions.bulkDelete,
115
+ permission: 'fleet-ops delete vendor',
112
116
  },
113
117
  ];
114
118
  }
@@ -17,6 +17,7 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
17
17
  @service hostRouter;
18
18
  @service universe;
19
19
  @service sidebar;
20
+ @service abilities;
20
21
  @tracked routingControl;
21
22
  @tracked routingCompleted = false;
22
23
  @tracked realtimeOrderPublicId = null;
@@ -90,12 +91,14 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
90
91
  {
91
92
  text: 'Edit details',
92
93
  icon: 'pencil',
94
+ permission: 'fleet-ops update order',
93
95
  disabled: this.model.status === 'canceled',
94
96
  fn: () => this.orderActions.editOrderDetails(this.model),
95
97
  },
96
98
  {
97
99
  text: 'Update activity',
98
100
  icon: 'signal',
101
+ permission: 'fleet-ops update order',
99
102
  disabled: this.model.status === 'canceled',
100
103
  fn: () =>
101
104
  this.orderActions.updateActivity(this.model, {
@@ -105,12 +108,14 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
105
108
  {
106
109
  text: this.model.has_driver_assigned ? 'Unassign driver' : 'Assign driver',
107
110
  icon: this.model.has_driver_assigned ? 'user-xmark' : 'edit',
111
+ permission: 'fleet-ops assign-driver-for order',
108
112
  disabled: this.model.has_driver_assigned ? !this.model.hasActiveStatus || !this.model.driver_assigned : !this.model.hasActiveStatus,
109
113
  fn: () => (this.model.has_driver_assigned ? this.orderActions.unassignDriver(this.model) : this.orderActions.assignDriver(this.model)),
110
114
  },
111
115
  {
112
116
  text: 'View order label',
113
117
  icon: 'file-invoice',
118
+ permission: 'fleet-ops view order',
114
119
  fn: () => this.orderActions.viewLabel(this.model),
115
120
  },
116
121
  {
@@ -133,6 +138,7 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
133
138
  text: 'Cancel order',
134
139
  icon: 'ban',
135
140
  class: 'text-danger',
141
+ permission: 'fleet-ops cancel order',
136
142
  disabled: this.model.status === 'canceled',
137
143
  fn: () => this.orderActions.cancel(this.model),
138
144
  },
@@ -140,6 +146,7 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
140
146
  text: 'Delete order',
141
147
  icon: 'trash',
142
148
  class: 'text-danger',
149
+ permission: 'fleet-ops delete order',
143
150
  fn: () =>
144
151
  this.orderActions.delete(this.model, {
145
152
  taskOptions: {
@@ -151,7 +158,29 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
151
158
  },
152
159
  ].filter(Boolean),
153
160
  },
154
- ];
161
+ ].map((actionButton) => ({ ...actionButton, items: this.permittedMenuItems(actionButton.items) }));
162
+ }
163
+
164
+ /**
165
+ * The panel header dropdown renders `items` as-is and ignores `permission`, so items the
166
+ * user is not permitted to use are removed here, along with any separators left dangling.
167
+ */
168
+ permittedMenuItems(items = []) {
169
+ const permitted = items.filter((item) => !item.permission || this.abilities.can(item.permission));
170
+ const result = permitted.reduce((list, item) => {
171
+ if (item.separator && (list.length === 0 || list[list.length - 1].separator)) {
172
+ return list;
173
+ }
174
+
175
+ list.push(item);
176
+ return list;
177
+ }, []);
178
+
179
+ if (result.length && result[result.length - 1].separator) {
180
+ result.pop();
181
+ }
182
+
183
+ return result;
155
184
  }
156
185
 
157
186
  @action handleActivityModalFinish(options) {
@@ -86,6 +86,7 @@ export default class OperationsOrdersIndexController extends Controller {
86
86
  type: 'primary',
87
87
  icon: 'plus',
88
88
  onClick: this.orderActions.transition.create,
89
+ permission: 'fleet-ops create order',
89
90
  },
90
91
  {
91
92
  text: this.intl.t('common.export'),
@@ -93,12 +94,14 @@ export default class OperationsOrdersIndexController extends Controller {
93
94
  iconClass: 'rotate-icon-45',
94
95
  wrapperClass: 'hidden md:flex',
95
96
  onClick: this.orderActions.export,
97
+ permission: 'fleet-ops export order',
96
98
  },
97
99
  {
98
100
  text: this.intl.t('common.import'),
99
101
  icon: 'file-import',
100
102
  wrapperClass: 'hidden md:flex',
101
103
  onClick: () => this.orderActions.importOrders({ onImportComplete: this.orderActions.refresh }),
104
+ permission: 'fleet-ops import order',
102
105
  },
103
106
  ];
104
107
  }
@@ -110,23 +113,27 @@ export default class OperationsOrdersIndexController extends Controller {
110
113
  label: this.intl.t('common.cancel-resource', { resource: this.intl.t('resource.orders') }),
111
114
  icon: 'ban',
112
115
  fn: this.orderActions.bulkCancel,
116
+ permission: 'fleet-ops cancel order',
113
117
  },
114
118
  {
115
119
  label: this.intl.t('common.delete-resource', { resource: this.intl.t('resource.orders') }),
116
120
  icon: 'trash',
117
121
  class: 'text-red-500',
118
122
  fn: this.orderActions.bulkDelete,
123
+ permission: 'fleet-ops delete order',
119
124
  },
120
125
  { separator: true },
121
126
  {
122
127
  label: this.intl.t('common.dispatch-orders'),
123
128
  icon: 'rocket',
124
129
  fn: this.orderActions.bulkDispatch,
130
+ permission: 'fleet-ops dispatch order',
125
131
  },
126
132
  {
127
133
  label: this.intl.t('common.assign-drivers'),
128
134
  icon: 'user-plus',
129
135
  fn: this.orderActions.bulkAssignDriver,
136
+ permission: 'fleet-ops assign-driver-for order',
130
137
  },
131
138
  ];
132
139
  }
@@ -29,6 +29,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
29
29
  type: 'primary',
30
30
  icon: 'plus',
31
31
  onClick: this.serviceRateActions.transition.create,
32
+ permission: 'fleet-ops create service-rate',
32
33
  },
33
34
  {
34
35
  text: this.intl.t('common.export'),
@@ -36,6 +37,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
36
37
  iconClass: 'rotate-icon-45',
37
38
  wrapperClass: 'hidden md:flex',
38
39
  onClick: this.serviceRateActions.export,
40
+ permission: 'fleet-ops export service-rate',
39
41
  },
40
42
  ];
41
43
  }
@@ -47,6 +49,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
47
49
  label: 'Delete selected...',
48
50
  class: 'text-red-500',
49
51
  fn: this.serviceRateActions.bulkDelete,
52
+ permission: 'fleet-ops delete service-rate',
50
53
  },
51
54
  ];
52
55
  }
@@ -28,6 +28,12 @@ export default class SettingsIndexController extends Controller {
28
28
  description: 'Keep commerce, metadata, and visual conventions aligned.',
29
29
  links: [
30
30
  { label: 'Payments', route: 'settings.payments', icon: 'cash-register', description: 'Payment setup for operational commerce workflows.' },
31
+ {
32
+ label: 'Telematics',
33
+ route: 'settings.telematics',
34
+ icon: 'satellite-dish',
35
+ description: 'Device event and position history preferences within the system retention policy.',
36
+ },
31
37
  { label: 'Custom Fields', route: 'settings.custom-fields', icon: 'pen-to-square', description: 'Operational metadata fields for Fleet-Ops records.' },
32
38
  { label: 'Avatars', route: 'settings.avatars', icon: 'icons', description: 'Visual assets for driver, vehicle, and map displays.' },
33
39
  ],
@@ -113,6 +119,13 @@ export default class SettingsIndexController extends Controller {
113
119
  title: 'Scheduling settings',
114
120
  description: 'Manage schedule templates and timing rules for planned work.',
115
121
  },
122
+ {
123
+ label: 'Telematics',
124
+ icon: 'satellite-dish',
125
+ slug: 'fleet-ops/settings/telematics',
126
+ title: 'Telematics Settings',
127
+ description: 'Choose how long your organization keeps device events and position history.',
128
+ },
116
129
  {
117
130
  label: 'Custom Fields',
118
131
  icon: 'pen-to-square',
@@ -0,0 +1,113 @@
1
+ import Controller from '@ember/controller';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { inject as service } from '@ember/service';
4
+ import { action } from '@ember/object';
5
+ import { task } from 'ember-concurrency';
6
+
7
+ /** Organization history preferences, subject to the system retention policy. */
8
+ export default class SettingsTelematicsController extends Controller {
9
+ @service fetch;
10
+ @service notifications;
11
+ @service intl;
12
+
13
+ @tracked eventRetentionDays = 30;
14
+ @tracked positionRetentionDays = 90;
15
+ @tracked useDefaultEventRetention = true;
16
+ @tracked useDefaultPositionRetention = true;
17
+ @tracked defaults = {};
18
+ @tracked policy = {};
19
+ @tracked settingsLoaded = false;
20
+
21
+ get eventRetentionMinimum() {
22
+ return this.policy.max_event_retention_days > 0 ? 1 : 0;
23
+ }
24
+
25
+ get eventRetentionMaximum() {
26
+ return this.policy.max_event_retention_days > 0 ? this.policy.max_event_retention_days : 3650;
27
+ }
28
+
29
+ get positionRetentionMinimum() {
30
+ return this.policy.max_position_retention_days > 0 ? 1 : 0;
31
+ }
32
+
33
+ get positionRetentionMaximum() {
34
+ return this.policy.max_position_retention_days > 0 ? this.policy.max_position_retention_days : 3650;
35
+ }
36
+
37
+ get eventRetentionPolicyHelp() {
38
+ return this.retentionPolicyHelp(this.policy.max_event_retention_days);
39
+ }
40
+
41
+ get positionRetentionPolicyHelp() {
42
+ return this.retentionPolicyHelp(this.policy.max_position_retention_days);
43
+ }
44
+
45
+ get settingsPayload() {
46
+ return {
47
+ event_retention_days: this.useDefaultEventRetention ? null : this.eventRetentionDays,
48
+ position_retention_days: this.useDefaultPositionRetention ? null : this.positionRetentionDays,
49
+ };
50
+ }
51
+
52
+ @task({ restartable: true }) *getSettings() {
53
+ this.settingsLoaded = false;
54
+
55
+ try {
56
+ const settings = yield this.fetch.get('fleet-ops/settings/telematics-settings');
57
+ this.applySettings(settings);
58
+ this.settingsLoaded = true;
59
+ } catch (error) {
60
+ this.notifications.serverError(error);
61
+ }
62
+ }
63
+
64
+ @task({ drop: true }) *saveSettings() {
65
+ if (!this.settingsLoaded) {
66
+ return;
67
+ }
68
+
69
+ try {
70
+ const settings = yield this.fetch.post('fleet-ops/settings/telematics-settings', this.settingsPayload);
71
+ this.applySettings(settings);
72
+ this.notifications.success(this.intl.t('settings.telematics.settings-saved'));
73
+ } catch (error) {
74
+ this.notifications.serverError(error);
75
+ }
76
+ }
77
+
78
+ @action setUseDefaultEventRetention(enabled) {
79
+ this.useDefaultEventRetention = enabled;
80
+ if (enabled) {
81
+ this.eventRetentionDays = this.defaults.event_retention_days ?? this.eventRetentionDays;
82
+ }
83
+ }
84
+
85
+ @action setUseDefaultPositionRetention(enabled) {
86
+ this.useDefaultPositionRetention = enabled;
87
+ if (enabled) {
88
+ this.positionRetentionDays = this.defaults.position_retention_days ?? this.positionRetentionDays;
89
+ }
90
+ }
91
+
92
+ applySettings(settings = {}) {
93
+ const has = (object, key) => Object.prototype.hasOwnProperty.call(object ?? {}, key);
94
+ const hasHistoryMetadata = ['event_retention_days', 'position_retention_days'].every(
95
+ (key) => has(settings?.preferences, key) && has(settings?.defaults, key) && has(settings?.policy, `max_${key}`)
96
+ );
97
+
98
+ if (!hasHistoryMetadata) {
99
+ throw new Error(this.intl.t('settings.telematics.settings-unavailable'));
100
+ }
101
+
102
+ this.eventRetentionDays = settings.event_retention_days ?? this.eventRetentionDays;
103
+ this.positionRetentionDays = settings.position_retention_days ?? this.positionRetentionDays;
104
+ this.useDefaultEventRetention = (settings.preferences?.event_retention_days ?? null) === null;
105
+ this.useDefaultPositionRetention = (settings.preferences?.position_retention_days ?? null) === null;
106
+ this.defaults = settings.defaults ?? this.defaults;
107
+ this.policy = settings.policy ?? this.policy;
108
+ }
109
+
110
+ retentionPolicyHelp(maximum) {
111
+ return maximum > 0 ? this.intl.t('settings.telematics.history-policy-maximum', { days: maximum }) : this.intl.t('settings.telematics.history-policy-unlimited');
112
+ }
113
+ }