@fleetbase/fleetops-engine 0.6.70 → 0.6.72

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 (510) hide show
  1. package/RELEASE.md +18 -47
  2. package/addon/components/admin/tracking-page-settings.hbs +56 -0
  3. package/addon/components/admin/tracking-page-settings.js +67 -0
  4. package/addon/components/device/form.hbs +2 -2
  5. package/addon/components/driver/details.hbs +1 -1
  6. package/addon/components/driver/form.hbs +1 -1
  7. package/addon/components/equipment/form.hbs +1 -1
  8. package/addon/components/layout/fleet-ops-sidebar.js +11 -3
  9. package/addon/components/maintenance/form.hbs +3 -3
  10. package/addon/components/map/google-live-map.js +2 -2
  11. package/addon/components/map/leaflet-live-map.js +45 -32
  12. package/addon/components/orchestrator/phase-builder.js +2 -1
  13. package/addon/components/orchestrator-workbench.hbs +7 -1
  14. package/addon/components/orchestrator-workbench.js +16 -2
  15. package/addon/components/order/details/activity.hbs +8 -6
  16. package/addon/components/order/details/activity.js +9 -0
  17. package/addon/components/order/details.hbs +29 -0
  18. package/addon/components/order/details.js +26 -0
  19. package/addon/components/order/form/details.hbs +30 -4
  20. package/addon/components/order/form/details.js +46 -0
  21. package/addon/components/order/form/route.hbs +5 -3
  22. package/addon/components/order/form/route.js +24 -1
  23. package/addon/components/order/form.hbs +12 -0
  24. package/addon/components/order/form.js +21 -1
  25. package/addon/components/order/kanban.js +72 -0
  26. package/addon/components/order-config-manager/activity-flow.js +45 -10
  27. package/addon/components/order-config-manager/lifecycle.hbs +250 -0
  28. package/addon/components/order-config-manager/lifecycle.js +358 -0
  29. package/addon/components/order-config-manager.js +5 -1
  30. package/addon/components/service-rate/details.hbs +1 -1
  31. package/addon/components/service-rate/form.hbs +1 -1
  32. package/addon/components/tracking-page/full-view.hbs +276 -0
  33. package/addon/components/tracking-page/full-view.js +474 -0
  34. package/addon/components/tracking-page/locale-selector.js +30 -0
  35. package/addon/components/tracking-page/map.hbs +44 -0
  36. package/addon/components/tracking-page/map.js +328 -0
  37. package/addon/components/tracking-page/order-list.hbs +35 -0
  38. package/addon/components/tracking-page/public-view.hbs +60 -0
  39. package/addon/components/tracking-page/settings-preview.hbs +21 -0
  40. package/addon/components/tracking-page/settings-preview.js +23 -0
  41. package/addon/components/tracking-page/verify-panel.hbs +109 -0
  42. package/addon/components/tracking-page.hbs +142 -0
  43. package/addon/components/tracking-page.js +564 -0
  44. package/addon/components/trailer/form.hbs +2 -2
  45. package/addon/components/vehicle/form.hbs +3 -3
  46. package/addon/components/warranty/form.hbs +2 -2
  47. package/addon/components/widget/live-fleet.js +54 -6
  48. package/addon/components/widget/proof-of-delivery.hbs +49 -0
  49. package/addon/components/widget/proof-of-delivery.js +62 -0
  50. package/addon/components/work-order/form.hbs +3 -3
  51. package/addon/controllers/analytics/reports/index.js +10 -1
  52. package/addon/controllers/connectivity/devices/index/details.js +1 -0
  53. package/addon/controllers/connectivity/devices/index.js +8 -2
  54. package/addon/controllers/connectivity/events/index.js +31 -1
  55. package/addon/controllers/connectivity/sensors/index/details.js +1 -1
  56. package/addon/controllers/connectivity/sensors/index.js +37 -1
  57. package/addon/controllers/maintenance/equipment/index/details.js +2 -2
  58. package/addon/controllers/maintenance/equipment/index.js +28 -9
  59. package/addon/controllers/maintenance/inspection-forms/index.js +32 -12
  60. package/addon/controllers/maintenance/inspection-submissions/index/details.js +2 -1
  61. package/addon/controllers/maintenance/inspection-submissions/index.js +49 -9
  62. package/addon/controllers/maintenance/maintenances/index/details.js +2 -0
  63. package/addon/controllers/maintenance/maintenances/index.js +20 -1
  64. package/addon/controllers/maintenance/parts/index/details.js +2 -2
  65. package/addon/controllers/maintenance/parts/index.js +28 -8
  66. package/addon/controllers/maintenance/schedules/index.js +33 -4
  67. package/addon/controllers/maintenance/work-orders/index/details.js +3 -3
  68. package/addon/controllers/maintenance/work-orders/index.js +39 -8
  69. package/addon/controllers/management/contacts/customers.js +13 -0
  70. package/addon/controllers/management/contacts/index.js +43 -1
  71. package/addon/controllers/management/drivers/index/details.js +1 -0
  72. package/addon/controllers/management/drivers/index.js +32 -2
  73. package/addon/controllers/management/fleets/index/details.js +1 -0
  74. package/addon/controllers/management/fleets/index.js +39 -1
  75. package/addon/controllers/management/fuel-reports/index.js +22 -2
  76. package/addon/controllers/management/fuel-transactions/index.js +43 -7
  77. package/addon/controllers/management/issues/index/details.js +1 -0
  78. package/addon/controllers/management/issues/index.js +22 -2
  79. package/addon/controllers/management/places/index.js +36 -1
  80. package/addon/controllers/management/trailers/index/details.js +1 -0
  81. package/addon/controllers/management/trailers/index.js +50 -2
  82. package/addon/controllers/management/vehicles/index/details.js +1 -0
  83. package/addon/controllers/management/vehicles/index.js +33 -2
  84. package/addon/controllers/management/vendors/index.js +26 -2
  85. package/addon/controllers/operations/orders/index/details.js +50 -7
  86. package/addon/controllers/operations/orders/index.js +59 -4
  87. package/addon/controllers/operations/scheduler/index.js +45 -14
  88. package/addon/controllers/operations/service-rates/index.js +15 -1
  89. package/addon/controllers/settings/index.js +6 -0
  90. package/addon/controllers/settings/map.js +75 -0
  91. package/addon/controllers/settings/orchestrator.js +8 -6
  92. package/addon/controllers/settings/tracking-page.js +212 -0
  93. package/addon/extension.js +97 -1
  94. package/addon/helpers/parcel-box-image.js +16 -0
  95. package/addon/routes/operations/orders/index/details.js +1 -0
  96. package/addon/routes/operations/orders/index/new.js +5 -4
  97. package/addon/routes/operations/scheduler/index.js +1 -1
  98. package/addon/routes/settings/tracking-page.js +31 -0
  99. package/addon/routes.js +1 -0
  100. package/addon/services/contact-actions.js +1 -0
  101. package/addon/services/customer-actions.js +2 -1
  102. package/addon/services/device-actions.js +1 -0
  103. package/addon/services/device-event-actions.js +1 -0
  104. package/addon/services/driver-actions.js +4 -13
  105. package/addon/services/equipment-actions.js +1 -0
  106. package/addon/services/fleet-actions.js +1 -0
  107. package/addon/services/fuel-report-actions.js +1 -0
  108. package/addon/services/inspection-form-actions.js +1 -0
  109. package/addon/services/inspection-submission-actions.js +1 -0
  110. package/addon/services/integrated-vendor-actions.js +1 -0
  111. package/addon/services/issue-actions.js +1 -0
  112. package/addon/services/location.js +69 -21
  113. package/addon/services/maintenance-actions.js +1 -0
  114. package/addon/services/maintenance-schedule-actions.js +2 -1
  115. package/addon/services/map-settings.js +28 -0
  116. package/addon/services/order-actions.js +7 -0
  117. package/addon/services/order-allocation.js +1 -1
  118. package/addon/services/order-presentation.js +129 -0
  119. package/addon/services/part-actions.js +1 -0
  120. package/addon/services/place-actions.js +1 -0
  121. package/addon/services/sensor-actions.js +1 -0
  122. package/addon/services/service-area-actions.js +1 -0
  123. package/addon/services/service-rate-actions.js +1 -0
  124. package/addon/services/telematic-actions.js +1 -0
  125. package/addon/services/trailer-actions.js +4 -14
  126. package/addon/services/vehicle-actions.js +4 -13
  127. package/addon/services/vendor-actions.js +1 -0
  128. package/addon/services/work-order-actions.js +1 -0
  129. package/addon/services/zone-actions.js +1 -0
  130. package/addon/styles/fleetops-engine.css +1391 -0
  131. package/addon/templates/analytics/reports/index/details.hbs +1 -0
  132. package/addon/templates/analytics/reports/index.hbs +1 -0
  133. package/addon/templates/connectivity/devices/index/details.hbs +1 -0
  134. package/addon/templates/connectivity/devices/index.hbs +1 -0
  135. package/addon/templates/connectivity/events/index.hbs +1 -0
  136. package/addon/templates/connectivity/sensors/index/details.hbs +1 -0
  137. package/addon/templates/connectivity/sensors/index.hbs +1 -0
  138. package/addon/templates/maintenance/equipment/index/details.hbs +1 -0
  139. package/addon/templates/maintenance/equipment/index.hbs +2 -0
  140. package/addon/templates/maintenance/inspection-forms/index/details.hbs +1 -0
  141. package/addon/templates/maintenance/inspection-forms/index.hbs +1 -0
  142. package/addon/templates/maintenance/inspection-submissions/index/details.hbs +1 -0
  143. package/addon/templates/maintenance/inspection-submissions/index.hbs +1 -0
  144. package/addon/templates/maintenance/maintenances/index/details.hbs +1 -0
  145. package/addon/templates/maintenance/maintenances/index.hbs +1 -0
  146. package/addon/templates/maintenance/parts/index/details.hbs +1 -0
  147. package/addon/templates/maintenance/parts/index.hbs +2 -0
  148. package/addon/templates/maintenance/schedules/index/details.hbs +1 -0
  149. package/addon/templates/maintenance/schedules/index.hbs +2 -0
  150. package/addon/templates/maintenance/work-orders/index/details.hbs +1 -0
  151. package/addon/templates/maintenance/work-orders/index.hbs +1 -0
  152. package/addon/templates/management/contacts/customers/details.hbs +1 -0
  153. package/addon/templates/management/contacts/customers.hbs +1 -0
  154. package/addon/templates/management/contacts/index/details.hbs +1 -0
  155. package/addon/templates/management/contacts/index.hbs +1 -0
  156. package/addon/templates/management/contacts.hbs +1 -0
  157. package/addon/templates/management/drivers/index/details.hbs +1 -0
  158. package/addon/templates/management/drivers/index.hbs +2 -0
  159. package/addon/templates/management/fleets/index/details.hbs +1 -0
  160. package/addon/templates/management/fleets/index.hbs +1 -0
  161. package/addon/templates/management/fuel-reports/index/details.hbs +1 -0
  162. package/addon/templates/management/fuel-reports/index.hbs +1 -0
  163. package/addon/templates/management/fuel-transactions/index.hbs +1 -0
  164. package/addon/templates/management/issues/index/details.hbs +1 -0
  165. package/addon/templates/management/issues/index.hbs +1 -0
  166. package/addon/templates/management/places/index/details.hbs +1 -0
  167. package/addon/templates/management/places/index.hbs +1 -0
  168. package/addon/templates/management/trailers/index/details.hbs +1 -0
  169. package/addon/templates/management/trailers/index.hbs +2 -0
  170. package/addon/templates/management/vehicles/index/details.hbs +1 -0
  171. package/addon/templates/management/vehicles/index.hbs +2 -0
  172. package/addon/templates/management/vendors/index/details.hbs +1 -0
  173. package/addon/templates/management/vendors/index.hbs +2 -0
  174. package/addon/templates/management/vendors/integrated.hbs +1 -0
  175. package/addon/templates/operations/orders/index/details.hbs +1 -0
  176. package/addon/templates/operations/orders/index.hbs +1 -0
  177. package/addon/templates/operations/service-rates/index/details.hbs +1 -0
  178. package/addon/templates/operations/service-rates/index.hbs +1 -0
  179. package/addon/templates/settings/map.hbs +46 -0
  180. package/addon/templates/settings/tracking-page.hbs +318 -0
  181. package/addon/utils/identity-cell-resource.js +2 -56
  182. package/addon/utils/map-default-view.js +132 -0
  183. package/addon/utils/normalize-order-config-flow.js +6 -1
  184. package/addon/utils/placeholder-images.js +4 -69
  185. package/addon/utils/resource-descriptors/assets.js +2 -57
  186. package/addon/utils/resource-descriptors/helpers.js +23 -122
  187. package/addon/utils/resource-descriptors/operations.js +2 -61
  188. package/addon/utils/resource-descriptors/people.js +3 -142
  189. package/addon/utils/resource-descriptors/shared.js +18 -0
  190. package/addon/utils/tracking-live-source.js +123 -0
  191. package/addon/utils/tracking-page-theme.js +123 -0
  192. package/app/components/admin/tracking-page-settings.js +1 -0
  193. package/app/components/tracking-page/full-view.js +1 -0
  194. package/app/components/tracking-page/locale-selector.js +1 -0
  195. package/app/components/tracking-page/map.js +1 -0
  196. package/app/components/tracking-page/order-list.js +1 -0
  197. package/app/components/tracking-page/public-view.js +1 -0
  198. package/app/components/tracking-page/settings-preview.js +1 -0
  199. package/app/components/tracking-page/verify-panel.js +1 -0
  200. package/app/components/tracking-page.js +1 -0
  201. package/app/components/widget/proof-of-delivery.js +1 -0
  202. package/app/controllers/settings/tracking-page.js +1 -0
  203. package/app/helpers/parcel-box-image.js +1 -0
  204. package/app/routes/settings/tracking-page.js +1 -0
  205. package/app/services/order-presentation.js +1 -0
  206. package/app/templates/settings/tracking-page.js +1 -0
  207. package/app/utils/map-default-view.js +1 -0
  208. package/app/utils/tracking-live-source.js +1 -0
  209. package/app/utils/tracking-page-theme.js +1 -0
  210. package/composer.json +2 -2
  211. package/extension.json +1 -1
  212. package/package.json +4 -4
  213. package/scripts/pest-bootstrap.php +4 -0
  214. package/server/config/fleetops.php +4 -0
  215. package/server/migrations/2026_10_08_000001_create_tracking_sessions_tables.php +51 -0
  216. package/server/migrations/2026_10_10_000001_add_trashed_index_to_device_events.php +40 -0
  217. package/server/migrations/2026_10_10_000002_add_trashed_index_to_positions.php +40 -0
  218. package/server/migrations/2026_10_12_000001_add_deleted_at_to_tracking_sessions_tables.php +32 -0
  219. package/server/resources/views/labels/default.php +2 -2
  220. package/server/resources/views/labels/entity-label.php +2 -2
  221. package/server/resources/views/labels/waypoint-label.php +2 -2
  222. package/server/src/Auth/Schemas/FleetOps.php +5 -0
  223. package/server/src/Console/Commands/ProcessOperationalAlerts.php +1 -0
  224. package/server/src/Console/Commands/PruneTelematicsData.php +101 -26
  225. package/server/src/Events/OrderChanged.php +257 -0
  226. package/server/src/Events/OrderDriverUnassigned.php +68 -0
  227. package/server/src/Exceptions/OrderEditLockedException.php +32 -0
  228. package/server/src/Expansions/CompanyExpansion.php +3 -2
  229. package/server/src/Expansions/UserExpansion.php +12 -10
  230. package/server/src/Http/Controllers/Api/v1/CustomerController.php +108 -18
  231. package/server/src/Http/Controllers/Api/v1/DriverController.php +95 -10
  232. package/server/src/Http/Controllers/Api/v1/EntityController.php +66 -0
  233. package/server/src/Http/Controllers/Api/v1/FleetController.php +10 -4
  234. package/server/src/Http/Controllers/Api/v1/OrderController.php +139 -36
  235. package/server/src/Http/Controllers/Api/v1/PayloadController.php +76 -8
  236. package/server/src/Http/Controllers/Api/v1/PlaceController.php +3 -3
  237. package/server/src/Http/Controllers/Api/v1/TrackingNumberController.php +6 -32
  238. package/server/src/Http/Controllers/Api/v1/VehicleController.php +5 -0
  239. package/server/src/Http/Controllers/Internal/v1/ContactController.php +3 -0
  240. package/server/src/Http/Controllers/Internal/v1/DriverController.php +10 -0
  241. package/server/src/Http/Controllers/Internal/v1/FleetController.php +16 -2
  242. package/server/src/Http/Controllers/Internal/v1/InspectionSubmissionController.php +13 -8
  243. package/server/src/Http/Controllers/Internal/v1/IssueController.php +2 -0
  244. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +4 -2
  245. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +93 -11
  246. package/server/src/Http/Controllers/Internal/v1/OrderController.php +118 -28
  247. package/server/src/Http/Controllers/Internal/v1/PositionController.php +22 -0
  248. package/server/src/Http/Controllers/Internal/v1/ProofController.php +55 -4
  249. package/server/src/Http/Controllers/Internal/v1/SettingController.php +303 -10
  250. package/server/src/Http/Controllers/Internal/v1/TrailerController.php +1 -0
  251. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +1 -0
  252. package/server/src/Http/Controllers/Public/TrackingController.php +518 -0
  253. package/server/src/Http/Filter/ContactFilter.php +1 -1
  254. package/server/src/Http/Filter/InspectionFormFilter.php +23 -0
  255. package/server/src/Http/Filter/InspectionSubmissionFilter.php +23 -0
  256. package/server/src/Http/Filter/MaintenanceFilter.php +23 -0
  257. package/server/src/Http/Filter/OrderFilter.php +39 -4
  258. package/server/src/Http/Filter/ProofFilter.php +18 -0
  259. package/server/src/Http/Filter/RouteFilter.php +18 -0
  260. package/server/src/Http/Filter/TelematicFilter.php +23 -0
  261. package/server/src/Http/Filter/WarrantyFilter.php +23 -0
  262. package/server/src/Http/Requests/CancelOrderRequest.php +2 -1
  263. package/server/src/Http/Resources/v1/Concerns/ResolvesPublicRelationFields.php +14 -1
  264. package/server/src/Http/Resources/v1/Index/Customer.php +18 -0
  265. package/server/src/Http/Resources/v1/Index/Order.php +11 -2
  266. package/server/src/Http/Resources/v1/PublicOrderTracking.php +153 -0
  267. package/server/src/Http/Resources/v1/TrackingNumber.php +8 -34
  268. package/server/src/Http/Resources/v1/Zone.php +31 -23
  269. package/server/src/Jobs/FinalizeApiOrderCreation.php +3 -1
  270. package/server/src/Jobs/ProcessAllocationJob.php +16 -6
  271. package/server/src/Jobs/PublishTrackingUpdate.php +50 -0
  272. package/server/src/Listeners/HandleGeofenceEntered.php +31 -15
  273. package/server/src/Listeners/HandleOrderCanceled.php +9 -1
  274. package/server/src/Listeners/HandleUserRemovedFromCompany.php +2 -1
  275. package/server/src/Listeners/PublishTrackingUpdates.php +27 -0
  276. package/server/src/Mail/CustomerCredentialsMail.php +4 -2
  277. package/server/src/Models/AssetConnection.php +3 -0
  278. package/server/src/Models/Contact.php +3 -0
  279. package/server/src/Models/Device.php +28 -0
  280. package/server/src/Models/DeviceEvent.php +7 -0
  281. package/server/src/Models/Driver.php +20 -7
  282. package/server/src/Models/Entity.php +12 -4
  283. package/server/src/Models/Fleet.php +11 -32
  284. package/server/src/Models/FleetDriver.php +3 -0
  285. package/server/src/Models/FleetVehicle.php +3 -0
  286. package/server/src/Models/FuelReport.php +5 -13
  287. package/server/src/Models/Issue.php +4 -16
  288. package/server/src/Models/Order.php +234 -11
  289. package/server/src/Models/OrderConfig.php +129 -0
  290. package/server/src/Models/Payload.php +227 -35
  291. package/server/src/Models/Place.php +7 -1
  292. package/server/src/Models/ServiceArea.php +7 -0
  293. package/server/src/Models/ServiceRate.php +7 -17
  294. package/server/src/Models/TrackingNumber.php +109 -6
  295. package/server/src/Models/TrackingSession.php +33 -0
  296. package/server/src/Models/TrackingSessionGrant.php +22 -0
  297. package/server/src/Models/TrackingStatus.php +6 -0
  298. package/server/src/Models/Vehicle.php +21 -2
  299. package/server/src/Models/Vendor.php +7 -0
  300. package/server/src/Models/Waypoint.php +13 -4
  301. package/server/src/Notifications/OrderAssigned.php +2 -1
  302. package/server/src/Notifications/OrderCanceled.php +7 -1
  303. package/server/src/Notifications/OrderChanged.php +87 -0
  304. package/server/src/Notifications/OrderCompleted.php +2 -1
  305. package/server/src/Notifications/OrderDispatchFailed.php +2 -1
  306. package/server/src/Notifications/OrderDispatched.php +2 -1
  307. package/server/src/Notifications/OrderFailed.php +2 -1
  308. package/server/src/Notifications/OrderUnassigned.php +66 -0
  309. package/server/src/Notifications/WaypointCompleted.php +2 -1
  310. package/server/src/Observers/CompanyUserObserver.php +8 -7
  311. package/server/src/Observers/DriverObserver.php +6 -1
  312. package/server/src/Observers/OrderObserver.php +141 -1
  313. package/server/src/Observers/TrackingNumberObserver.php +18 -6
  314. package/server/src/Observers/UserObserver.php +2 -1
  315. package/server/src/Observers/VehicleObserver.php +8 -3
  316. package/server/src/Orchestration/Engines/RouteSequencingEngine.php +21 -3
  317. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +38 -8
  318. package/server/src/Orchestration/OrchestrationEngineRegistry.php +61 -1
  319. package/server/src/Orchestration/Support/OrchestratorSettings.php +61 -0
  320. package/server/src/Orchestration/Support/RouteMetrics.php +165 -0
  321. package/server/src/Providers/EventServiceProvider.php +11 -0
  322. package/server/src/Providers/FleetOpsServiceProvider.php +13 -0
  323. package/server/src/Support/ApiCacheThrottle.php +76 -0
  324. package/server/src/Support/LiveOrderQuery.php +3 -1
  325. package/server/src/Support/OrderEdit.php +365 -0
  326. package/server/src/Support/OrderEditPolicy.php +292 -0
  327. package/server/src/Support/ProfileAccountManager.php +54 -1
  328. package/server/src/Support/ResolvesOrderServiceStops.php +5 -0
  329. package/server/src/Support/SocketChannels.php +346 -0
  330. package/server/src/Support/Telematics/TelematicService.php +7 -2
  331. package/server/src/Support/Telematics/Telemetry/Inbox.php +8 -1
  332. package/server/src/Support/TrackingChannel.php +55 -0
  333. package/server/src/Support/TrackingCode.php +242 -0
  334. package/server/src/Support/TrackingPage/TrackingAccess.php +356 -0
  335. package/server/src/Support/TrackingPage/TrackingPageConfig.php +264 -0
  336. package/server/src/Support/TrackingPage/TrackingRecipient.php +111 -0
  337. package/server/src/Support/TrackingPage/TrackingResolver.php +201 -0
  338. package/server/src/Support/TrackingPage/TrackingTarget.php +40 -0
  339. package/server/src/Support/TrackingPage/TrackingView.php +543 -0
  340. package/server/src/Support/TrackingPublisher.php +261 -0
  341. package/server/src/Support/TrackingScope.php +182 -0
  342. package/server/src/Support/TrackingUpdate.php +402 -0
  343. package/server/src/Support/Utils.php +15 -3
  344. package/server/src/Traits/HasTrackingNumber.php +2 -0
  345. package/server/src/routes.php +39 -7
  346. package/server/tests/ApiCustomerControllerContractsTest.php +54 -7
  347. package/server/tests/ApiDriverPasswordContractsTest.php +25 -21
  348. package/server/tests/ApiEntityControllerContractsTest.php +84 -0
  349. package/server/tests/ApiOrderControllerContractsTest.php +245 -0
  350. package/server/tests/ApiPayloadControllerContractsTest.php +121 -0
  351. package/server/tests/ApiPlaceControllerContractsTest.php +1 -1
  352. package/server/tests/ApiTrackingNumberControllerContractsTest.php +6 -9
  353. package/server/tests/CompactResourceSerializationTest.php +23 -59
  354. package/server/tests/ControllerFilterContractsTest.php +1 -1
  355. package/server/tests/CustomerContactIdentitySafetyTest.php +1 -1
  356. package/server/tests/EventContractsTest.php +26 -60
  357. package/server/tests/ExpansionContractsTest.php +15 -2
  358. package/server/tests/Feature/Http/Api/CustomerControllerHelperSeamsTest.php +29 -3
  359. package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +23 -0
  360. package/server/tests/Feature/Http/Api/OrderControllerNearbyFiltersTest.php +16 -16
  361. package/server/tests/Feature/Http/Api/OrderControllerQueryFiltersTest.php +41 -14
  362. package/server/tests/Feature/Http/Api/TrackingNumberControllerHelpersTest.php +57 -22
  363. package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +23 -9
  364. package/server/tests/Feature/Http/Internal/OrchestrationControllerContractsTest.php +171 -1
  365. package/server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php +72 -0
  366. package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +110 -14
  367. package/server/tests/Feature/Http/Internal/OrderControllerCreateRecordTest.php +26 -0
  368. package/server/tests/Feature/Http/Internal/OrderControllerHelpersTest.php +7 -5
  369. package/server/tests/Feature/Http/Internal/PositionControllerReplayChannelTest.php +135 -0
  370. package/server/tests/Feature/Http/Internal/ProofControllerContractsTest.php +3 -3
  371. package/server/tests/Feature/Http/Internal/ProofControllerRecentTest.php +170 -0
  372. package/server/tests/Feature/Http/Public/TrackingControllerTest.php +495 -0
  373. package/server/tests/IndexCustomerPhotoTest.php +151 -0
  374. package/server/tests/ModelAccessorContractsTest.php +4 -3
  375. package/server/tests/NotificationAndMailContractsTest.php +5 -8
  376. package/server/tests/ObserverContractsTest.php +72 -10
  377. package/server/tests/OrderActivityFlowRegressionTest.php +3 -2
  378. package/server/tests/OrderConfigLifecycleTest.php +120 -0
  379. package/server/tests/OrderFilterExecutionTest.php +167 -2
  380. package/server/tests/ProcessAllocationJobContractsTest.php +20 -0
  381. package/server/tests/PublicOrderTrackingResourceTest.php +221 -0
  382. package/server/tests/QueueJobContractsTest.php +2 -1
  383. package/server/tests/RequestContractsTest.php +1 -1
  384. package/server/tests/RouteRegistrationExecutionTest.php +26 -0
  385. package/server/tests/RouteSequencingEngineTest.php +17 -0
  386. package/server/tests/SettingControllerContractsTest.php +110 -0
  387. package/server/tests/Support/TrackingPageDatabase.php +200 -0
  388. package/server/tests/TenantScopingTest.php +206 -0
  389. package/server/tests/TrackingPageSettingsTest.php +343 -0
  390. package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +51 -0
  391. package/server/tests/Unit/Events/OrderDispatchFailedTest.php +2 -1
  392. package/server/tests/Unit/Http/Filter/ContactFilterAddressTest.php +61 -0
  393. package/server/tests/Unit/Http/Resources/IndexOrderResourceTest.php +30 -0
  394. package/server/tests/Unit/Jobs/FinalizeApiOrderCreationDispatchGuardTest.php +266 -0
  395. package/server/tests/Unit/Listeners/HandleGeofenceEnteredTest.php +182 -22
  396. package/server/tests/Unit/Listeners/HandleUserRemovedFromCompanyTest.php +5 -0
  397. package/server/tests/Unit/Models/DriverAssignVehicleTest.php +81 -0
  398. package/server/tests/Unit/Models/FleetTest.php +4 -3
  399. package/server/tests/Unit/Models/OrderCancelReasonTest.php +143 -0
  400. package/server/tests/Unit/Models/OrderPayloadConfigAndDistanceTest.php +60 -0
  401. package/server/tests/Unit/Models/OrderPayloadRouteTest.php +4 -2
  402. package/server/tests/Unit/Models/PayloadTest.php +18 -4
  403. package/server/tests/Unit/Models/ServiceRatePersistenceAndServicabilityTest.php +2 -2
  404. package/server/tests/Unit/Models/TrackingNumberTest.php +67 -6
  405. package/server/tests/Unit/Models/VehicleTest.php +16 -2
  406. package/server/tests/Unit/Models/WaypointTest.php +4 -3
  407. package/server/tests/Unit/Notifications/OrderChangeNotificationsTest.php +59 -0
  408. package/server/tests/Unit/Notifications/TerminalOrderNotificationsTest.php +3 -3
  409. package/server/tests/Unit/Observers/DriverDeletionCleanupTest.php +158 -0
  410. package/server/tests/Unit/Observers/FleetOpsCompanyUserAndServiceAreaObserversTest.php +20 -0
  411. package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +3 -3
  412. package/server/tests/Unit/Observers/OrderAssignmentObserverTest.php +100 -0
  413. package/server/tests/Unit/Observers/OrderDeleteCascadeTest.php +213 -0
  414. package/server/tests/Unit/Observers/VehicleObserverDeleteTest.php +67 -0
  415. package/server/tests/Unit/Orchestration/EngineSelectionAndRouteMetricsTest.php +336 -0
  416. package/server/tests/Unit/Orchestration/Engines/VroomOrchestrationEngineLifecycleTest.php +8 -1
  417. package/server/tests/Unit/Support/ApiCacheDependenciesTest.php +347 -0
  418. package/server/tests/Unit/Support/OrderEditPolicyTest.php +156 -0
  419. package/server/tests/Unit/Support/OrderOperationsTest.php +514 -0
  420. package/server/tests/Unit/Support/OsrmProofAndIssueFilterTest.php +4 -3
  421. package/server/tests/Unit/Support/ProfileAccountManagerTest.php +141 -0
  422. package/server/tests/Unit/Support/QuerySeamsTest.php +5 -1
  423. package/server/tests/Unit/Support/SocketChannelsTest.php +396 -0
  424. package/server/tests/Unit/Support/TrackingChannelTest.php +232 -0
  425. package/server/tests/Unit/Support/TrackingCodeTest.php +186 -0
  426. package/server/tests/Unit/Support/TrackingPage/TrackingAccessTest.php +200 -0
  427. package/server/tests/Unit/Support/TrackingPage/TrackingRecipientTest.php +51 -0
  428. package/server/tests/Unit/Support/TrackingPage/TrackingResolverTest.php +181 -0
  429. package/server/tests/Unit/Support/TrackingPage/TrackingViewTest.php +479 -0
  430. package/server/tests/Unit/Support/TrackingPublisherTest.php +425 -0
  431. package/server/tests/Unit/Support/TrackingUpdateTest.php +363 -0
  432. package/server/tests/Unit/Support/UtilsAdditionalTest.php +9 -0
  433. package/server/tests/Unit/Support/UtilsGeoMatrixAndVendorTest.php +25 -0
  434. package/server/tests/VehicleResourceContractsTest.php +46 -0
  435. package/server/tests/VroomOrchestrationEngineTest.php +34 -1
  436. package/server/tests/ZoneGeofenceSettingsResourceTest.php +121 -0
  437. package/translations/ar-ae.yml +274 -0
  438. package/translations/bg-bg.yaml +274 -0
  439. package/translations/en-us.yaml +347 -0
  440. package/translations/es-pa.yaml +274 -0
  441. package/translations/fr-fr.yaml +274 -0
  442. package/translations/mn-mn.yaml +274 -0
  443. package/translations/pt-br.yaml +274 -0
  444. package/translations/ru-ru.yaml +276 -2
  445. package/translations/uk-ua.yaml +275 -1
  446. package/translations/vi-vn.yaml +274 -0
  447. package/addon/components/cell/contact-identity.hbs +0 -1
  448. package/addon/components/cell/customer-identity.hbs +0 -1
  449. package/addon/components/cell/driver-identity.hbs +0 -1
  450. package/addon/components/cell/fleet-identity.hbs +0 -1
  451. package/addon/components/cell/order-identity.hbs +0 -1
  452. package/addon/components/cell/place-identity.hbs +0 -1
  453. package/addon/components/cell/vehicle-identity.hbs +0 -1
  454. package/addon/components/cell/vendor-identity.hbs +0 -1
  455. package/addon/components/contact/pill.hbs +0 -24
  456. package/addon/components/contact/summary.hbs +0 -1
  457. package/addon/components/customer/pill.hbs +0 -24
  458. package/addon/components/customer/summary.hbs +0 -1
  459. package/addon/components/driver/pill.hbs +0 -24
  460. package/addon/components/driver/summary.hbs +0 -1
  461. package/addon/components/fleet/pill.hbs +0 -24
  462. package/addon/components/fleet/summary.hbs +0 -1
  463. package/addon/components/order/pill.hbs +0 -24
  464. package/addon/components/order/summary.hbs +0 -1
  465. package/addon/components/place/pill.hbs +0 -24
  466. package/addon/components/place/summary.hbs +0 -1
  467. package/addon/components/select-option/contact.hbs +0 -12
  468. package/addon/components/select-option/customer.hbs +0 -12
  469. package/addon/components/select-option/driver.hbs +0 -12
  470. package/addon/components/select-option/fleet.hbs +0 -12
  471. package/addon/components/select-option/order.hbs +0 -12
  472. package/addon/components/select-option/place.hbs +0 -12
  473. package/addon/components/select-option/vehicle.hbs +0 -12
  474. package/addon/components/select-option/vendor.hbs +0 -12
  475. package/addon/components/vehicle/pill.hbs +0 -24
  476. package/addon/components/vehicle/summary.hbs +0 -1
  477. package/addon/components/vendor/pill.hbs +0 -24
  478. package/addon/components/vendor/summary.hbs +0 -1
  479. package/app/components/cell/contact-identity.js +0 -1
  480. package/app/components/cell/customer-identity.js +0 -1
  481. package/app/components/cell/driver-identity.js +0 -1
  482. package/app/components/cell/fleet-identity.js +0 -1
  483. package/app/components/cell/order-identity.js +0 -1
  484. package/app/components/cell/place-identity.js +0 -1
  485. package/app/components/cell/vehicle-identity.js +0 -1
  486. package/app/components/cell/vendor-identity.js +0 -1
  487. package/app/components/contact/pill.js +0 -1
  488. package/app/components/contact/summary.js +0 -1
  489. package/app/components/customer/pill.js +0 -1
  490. package/app/components/customer/summary.js +0 -1
  491. package/app/components/driver/pill.js +0 -1
  492. package/app/components/driver/summary.js +0 -1
  493. package/app/components/fleet/pill.js +0 -1
  494. package/app/components/fleet/summary.js +0 -1
  495. package/app/components/order/pill.js +0 -1
  496. package/app/components/order/summary.js +0 -1
  497. package/app/components/place/pill.js +0 -1
  498. package/app/components/place/summary.js +0 -1
  499. package/app/components/select-option/contact.js +0 -1
  500. package/app/components/select-option/customer.js +0 -1
  501. package/app/components/select-option/driver.js +0 -1
  502. package/app/components/select-option/fleet.js +0 -1
  503. package/app/components/select-option/order.js +0 -1
  504. package/app/components/select-option/place.js +0 -1
  505. package/app/components/select-option/vehicle.js +0 -1
  506. package/app/components/select-option/vendor.js +0 -1
  507. package/app/components/vehicle/pill.js +0 -1
  508. package/app/components/vehicle/summary.js +0 -1
  509. package/app/components/vendor/pill.js +0 -1
  510. package/app/components/vendor/summary.js +0 -1
@@ -4,6 +4,7 @@ import { inject as service } from '@ember/service';
4
4
  import { action, computed } from '@ember/object';
5
5
  import { isNone } from '@ember/utils';
6
6
  import { isValid as isValidDate } from 'date-fns';
7
+ import { later } from '@ember/runloop';
7
8
  import { task } from 'ember-concurrency';
8
9
  import isObject from '@fleetbase/ember-core/utils/is-object';
9
10
  import isJson from '@fleetbase/ember-core/utils/is-json';
@@ -11,6 +12,9 @@ import createFullCalendarEventFromOrder from '../../../utils/create-full-calenda
11
12
  import createFullCalendarEventFromScheduleItem from '../../../utils/create-full-calendar-event-from-schedule-item';
12
13
  import toCalendarDate from '../../../utils/to-calendar-date';
13
14
 
15
+ // The ember-core socket service subscribes ~300 ms after listen() is called.
16
+ const SOCKET_SUBSCRIBE_SETTLE_MS = 500;
17
+
14
18
  /**
15
19
  * OperationsSchedulerIndexController
16
20
  *
@@ -722,28 +726,55 @@ export default class OperationsSchedulerIndexController extends Controller {
722
726
  // Real-Time Socket Subscriptions
723
727
  // -------------------------------------------------------------------------
724
728
 
729
+ // Names of the socket channels this board opened, so teardown closes only those
730
+ // and leaves every other subscription (chat, notifications, ...) alone.
731
+ _socketChannelNames = new Set();
732
+
725
733
  @action async subscribeToRealTimeUpdates() {
726
- const orgId = this.currentUser?.companyId ?? this.currentUser?.company?.id;
727
- if (!orgId) return;
728
- await this.socket.listen(`company.${orgId}.orders`, (payload) => this._handleOrderSocketEvent(payload));
729
- this.drivers.forEach(async (driver) => {
730
- await this.socket.listen(`driver.${driver.id}`, (payload) => this._handleDriverSocketEvent(payload));
731
- });
734
+ const drivers = this.drivers ?? [];
735
+ await Promise.all(
736
+ drivers.map((driver) => {
737
+ if (!driver?.id) return;
738
+ const channelName = `driver.${driver.id}`;
739
+ if (this._socketChannelNames.has(channelName)) return;
740
+ this._socketChannelNames.add(channelName);
741
+ return this.socket.listen(channelName, (payload) => this._handleDriverSocketEvent(payload));
742
+ })
743
+ );
732
744
  }
733
745
 
734
746
  @action unsubscribeFromRealTimeUpdates() {
735
- if (this.socket && typeof this.socket.closeChannels === 'function') {
736
- this.socket.closeChannels();
747
+ const names = this._socketChannelNames;
748
+ this._socketChannelNames = new Set();
749
+ if (!this.socket || names.size === 0) return;
750
+
751
+ if (typeof this.socket.closeChannel === 'function') {
752
+ names.forEach((name) => this.socket.closeChannel(name));
753
+ return;
737
754
  }
755
+
756
+ const remaining = this._closeOpenedChannels(names);
757
+ if (remaining.size === 0) return;
758
+
759
+ // The socket service subscribes after a short delay, so a channel requested just before
760
+ // teardown may not exist yet: sweep once more after that delay.
761
+ later(this, () => this._closeOpenedChannels(remaining), SOCKET_SUBSCRIBE_SETTLE_MS);
738
762
  }
739
763
 
740
- _handleOrderSocketEvent({ data } = {}) {
741
- if (!data?.id) return;
742
- try {
743
- this.store.pushPayload('order', { order: data });
744
- } catch {
745
- /* ignore */
764
+ /**
765
+ * Close the socket's channels with the given names, except any the board has opened again
766
+ * since. Returns the names that had no channel yet.
767
+ */
768
+ _closeOpenedChannels(names) {
769
+ const remaining = new Set([...names].filter((name) => !this._socketChannelNames.has(name)));
770
+ const channels = Array.isArray(this.socket.channels) ? this.socket.channels : [];
771
+ for (const channel of channels) {
772
+ if (!names.has(channel?.name) || this._socketChannelNames.has(channel.name)) continue;
773
+ remaining.delete(channel.name);
774
+ channel.close();
746
775
  }
776
+
777
+ return remaining;
747
778
  }
748
779
 
749
780
  _handleDriverSocketEvent({ event, data } = {}) {
@@ -10,7 +10,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
10
10
  @service intl;
11
11
 
12
12
  /** query params */
13
- @tracked queryParams = ['page', 'query', 'limit', 'sort', 'zone', 'service_area'];
13
+ @tracked queryParams = this.serviceRateActions.queryParamsFor(['page', 'query', 'limit', 'sort', 'zone', 'service_area']);
14
14
  @tracked page = 1;
15
15
  @tracked limit;
16
16
  @tracked query;
@@ -20,11 +20,13 @@ export default class OperationsServiceRatesIndexController extends Controller {
20
20
  get actionButtons() {
21
21
  return [
22
22
  {
23
+ id: 'refresh',
23
24
  icon: 'refresh',
24
25
  onClick: this.serviceRateActions.refresh,
25
26
  helpText: this.intl.t('common.refresh'),
26
27
  },
27
28
  {
29
+ id: 'create',
28
30
  text: this.intl.t('common.new'),
29
31
  type: 'primary',
30
32
  icon: 'plus',
@@ -32,6 +34,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
32
34
  permission: 'fleet-ops create service-rate',
33
35
  },
34
36
  {
37
+ id: 'export',
35
38
  text: this.intl.t('common.export'),
36
39
  icon: 'long-arrow-up',
37
40
  iconClass: 'rotate-icon-45',
@@ -46,6 +49,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
46
49
  get bulkActions() {
47
50
  return [
48
51
  {
52
+ id: 'bulk-delete',
49
53
  label: 'Delete selected...',
50
54
  class: 'text-red-500',
51
55
  fn: this.serviceRateActions.bulkDelete,
@@ -58,6 +62,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
58
62
  get columns() {
59
63
  return [
60
64
  {
65
+ id: 'public-id',
61
66
  sticky: true,
62
67
  label: this.intl.t('column.id'),
63
68
  valuePath: 'public_id',
@@ -70,6 +75,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
70
75
  filterComponent: 'filter/string',
71
76
  },
72
77
  {
78
+ id: 'service-name',
73
79
  label: this.intl.t('column.service'),
74
80
  valuePath: 'service_name',
75
81
  cellComponent: 'table/cell/base',
@@ -78,6 +84,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
78
84
  filterable: false,
79
85
  },
80
86
  {
87
+ id: 'service-area-name',
81
88
  label: this.intl.t('column.service-area'),
82
89
  valuePath: 'service_area.name',
83
90
  cellComponent: 'cell/service-area-identity',
@@ -93,6 +100,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
93
100
  model: 'service-area',
94
101
  },
95
102
  {
103
+ id: 'zone-name',
96
104
  label: this.intl.t('column.zone'),
97
105
  valuePath: 'zone.name',
98
106
  cellComponent: 'cell/zone-identity',
@@ -107,6 +115,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
107
115
  model: 'zone',
108
116
  },
109
117
  {
118
+ id: 'created-at',
110
119
  label: this.intl.t('column.created-at'),
111
120
  valuePath: 'createdAt',
112
121
  sortParam: 'created_at',
@@ -116,6 +125,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
116
125
  filterComponent: 'filter/date',
117
126
  },
118
127
  {
128
+ id: 'updated-at',
119
129
  label: this.intl.t('column.updated-at'),
120
130
  valuePath: 'updatedAt',
121
131
  sortParam: 'updated_at',
@@ -126,6 +136,7 @@ export default class OperationsServiceRatesIndexController extends Controller {
126
136
  filterComponent: 'filter/date',
127
137
  },
128
138
  {
139
+ id: 'row-actions',
129
140
  label: '',
130
141
  cellComponent: 'table/cell/dropdown',
131
142
  ddButtonText: false,
@@ -137,18 +148,21 @@ export default class OperationsServiceRatesIndexController extends Controller {
137
148
  width: 60,
138
149
  actions: [
139
150
  {
151
+ id: 'view',
140
152
  label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.service-rate') }),
141
153
  icon: 'eye',
142
154
  fn: this.serviceRateActions.transition.view,
143
155
  permission: 'fleet-ops view service-rate',
144
156
  },
145
157
  {
158
+ id: 'edit',
146
159
  label: this.intl.t('common.edit-resource', { resource: this.intl.t('resource.service-rate') }),
147
160
  icon: 'pencil',
148
161
  fn: this.serviceRateActions.transition.edit,
149
162
  permission: 'fleet-ops view service-rate',
150
163
  },
151
164
  {
165
+ id: 'delete',
152
166
  label: this.intl.t('common.delete-resource', { resource: this.intl.t('resource.service-rate') }),
153
167
  icon: 'trash',
154
168
  fn: this.serviceRateActions.delete,
@@ -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: 'Tracking Page',
33
+ route: 'settings.tracking-page',
34
+ icon: 'magnifying-glass-location',
35
+ description: 'The page your customers use to follow deliveries: address, branding and how they verify.',
36
+ },
31
37
  {
32
38
  label: 'Telematics',
33
39
  route: 'settings.telematics',
@@ -4,6 +4,7 @@ import { inject as service } from '@ember/service';
4
4
  import { action } from '@ember/object';
5
5
  import { task } from 'ember-concurrency';
6
6
  import MAP_PROVIDER_OPTIONS from '../../utils/map-provider-options';
7
+ import { isLatitude, isLongitude, isZoom, toCoordinate } from '../../utils/map-default-view';
7
8
 
8
9
  export default class SettingsMapController extends Controller {
9
10
  @service fetch;
@@ -11,6 +12,7 @@ export default class SettingsMapController extends Controller {
11
12
  @service intl;
12
13
  @service mapManager;
13
14
  @service mapSettings;
15
+ @service location;
14
16
 
15
17
  /**
16
18
  * Available map provider options for the provider selector.
@@ -39,6 +41,21 @@ export default class SettingsMapController extends Controller {
39
41
  @tracked showGoogleMapsTrafficLayer = false;
40
42
  @tracked showGoogleMapsTransitLayer = false;
41
43
 
44
+ /**
45
+ * The company's default live map view. Latitude and longitude are both
46
+ * set or both null; zoom is optional.
47
+ */
48
+ @tracked defaultMapLatitude = null;
49
+ @tracked defaultMapLongitude = null;
50
+ @tracked defaultMapZoom = null;
51
+ @tracked fitMapToVehicles = false;
52
+
53
+ /**
54
+ * Bumped to re-create the coordinates input, which only reads its value
55
+ * when it is constructed (e.g. after clearing the default center).
56
+ */
57
+ @tracked defaultCenterInputKey = 0;
58
+
42
59
  /**
43
60
  * Whether settings have been loaded from the server.
44
61
  *
@@ -74,6 +91,36 @@ export default class SettingsMapController extends Controller {
74
91
  return this.mapProvider === 'leaflet';
75
92
  }
76
93
 
94
+ /**
95
+ * The default center as a GeoJSON point for the coordinates input, or null.
96
+ *
97
+ * @memberof SettingsMapController
98
+ * @return {Object|null}
99
+ */
100
+ get defaultMapCenterPoint() {
101
+ // Not null: CoordinatesInput#isPoint reads `.type` on any object, and typeof null is "object".
102
+ if (!this.hasDefaultMapCenter) {
103
+ return undefined;
104
+ }
105
+
106
+ return { type: 'Point', coordinates: [this.defaultMapLongitude, this.defaultMapLatitude] };
107
+ }
108
+
109
+ get hasDefaultMapCenter() {
110
+ return isLatitude(this.defaultMapLatitude) && isLongitude(this.defaultMapLongitude);
111
+ }
112
+
113
+ /**
114
+ * The zoom to save: a whole number from 1 to 20, else null.
115
+ *
116
+ * @memberof SettingsMapController
117
+ * @return {Number|null}
118
+ */
119
+ get normalizedDefaultMapZoom() {
120
+ const zoom = toCoordinate(this.defaultMapZoom);
121
+ return isZoom(zoom) ? zoom : null;
122
+ }
123
+
77
124
  // ─── Actions ───────────────────────────────────────────────────────────────
78
125
 
79
126
  /**
@@ -86,6 +133,24 @@ export default class SettingsMapController extends Controller {
86
133
  this.mapProvider = option?.value ?? option ?? 'leaflet';
87
134
  }
88
135
 
136
+ /**
137
+ * Receives coordinates typed into, or picked on the map of, the default
138
+ * center input.
139
+ *
140
+ * @param {{latitude: *, longitude: *}} coordinates
141
+ * @memberof SettingsMapController
142
+ */
143
+ @action setDefaultMapCenter({ latitude, longitude } = {}) {
144
+ this.defaultMapLatitude = toCoordinate(latitude);
145
+ this.defaultMapLongitude = toCoordinate(longitude);
146
+ }
147
+
148
+ @action clearDefaultMapCenter() {
149
+ this.defaultMapLatitude = null;
150
+ this.defaultMapLongitude = null;
151
+ this.defaultCenterInputKey++;
152
+ }
153
+
89
154
  @task *saveSettings() {
90
155
  const settings = {
91
156
  mapProvider: this.mapProvider,
@@ -94,12 +159,19 @@ export default class SettingsMapController extends Controller {
94
159
  googleMapsMapType: this.googleMapsMapType,
95
160
  showGoogleMapsTrafficLayer: this.showGoogleMapsTrafficLayer,
96
161
  showGoogleMapsTransitLayer: this.showGoogleMapsTransitLayer,
162
+ defaultMapLatitude: this.hasDefaultMapCenter ? this.defaultMapLatitude : null,
163
+ defaultMapLongitude: this.hasDefaultMapCenter ? this.defaultMapLongitude : null,
164
+ defaultMapZoom: this.normalizedDefaultMapZoom,
165
+ fitMapToVehicles: this.fitMapToVehicles,
97
166
  };
98
167
 
99
168
  try {
100
169
  const response = yield this.fetch.post('fleet-ops/settings/map', { settings });
101
170
  this.mapSettings.applySettings(response);
102
171
 
172
+ // Re-resolve where maps open so an open live map moves to the new default.
173
+ this.location.relocate();
174
+
103
175
  if (this.mapManager && typeof this.mapManager.setActiveProvider === 'function') {
104
176
  this.mapManager.setActiveProvider(this.mapProvider);
105
177
  }
@@ -124,6 +196,9 @@ export default class SettingsMapController extends Controller {
124
196
  this.googleMapsMapType = response?.googleMapsMapType ?? 'roadmap';
125
197
  this.showGoogleMapsTrafficLayer = Boolean(response?.showGoogleMapsTrafficLayer);
126
198
  this.showGoogleMapsTransitLayer = Boolean(response?.showGoogleMapsTransitLayer);
199
+ this.setDefaultMapCenter({ latitude: response?.defaultMapLatitude, longitude: response?.defaultMapLongitude });
200
+ this.defaultMapZoom = response?.defaultMapZoom ?? null;
201
+ this.fitMapToVehicles = Boolean(response?.fitMapToVehicles);
127
202
  this.settingsLoaded = true;
128
203
  } catch (error) {
129
204
  this.notifications.serverError(error);
@@ -17,7 +17,8 @@ export default class SettingsOrchestratorController extends Controller {
17
17
  @service intl;
18
18
  @service('orchestration-engine') engineRegistry;
19
19
 
20
- @tracked allocationEngine = 'vroom';
20
+ @tracked allocationEngine = 'greedy';
21
+ @tracked serverEngines = null;
21
22
  @tracked autoAllocateOnCreate = false;
22
23
  @tracked autoReallocateOnComplete = false;
23
24
  @tracked maxTravelTimeSeconds = 3600;
@@ -26,18 +27,19 @@ export default class SettingsOrchestratorController extends Controller {
26
27
 
27
28
  /**
28
29
  * Engine options for the selector dropdown.
29
- * Populated from the allocation-engine registry so new engines appear
30
- * automatically without modifying this controller.
30
+ * Populated from the engines registered on the server (the ones a run can
31
+ * actually use), falling back to the frontend allocation-engine registry.
31
32
  */
32
33
  get engineOptions() {
33
- return this.engineRegistry.availableEngines;
34
+ return this.serverEngines ?? this.engineRegistry.availableEngines;
34
35
  }
35
36
 
36
37
  @task *loadSettings() {
37
38
  this.isLoading = true;
38
39
  try {
39
- const settings = yield this.fetch.get('fleet-ops/settings/orchestrator-settings');
40
- this.allocationEngine = settings.allocation_engine ?? 'vroom';
40
+ const [settings, engines] = yield Promise.all([this.fetch.get('fleet-ops/settings/orchestrator-settings'), this.fetch.get('fleet-ops/orchestrator/engines').catch(() => null)]);
41
+ this.serverEngines = engines?.engines ?? null;
42
+ this.allocationEngine = settings.allocation_engine ?? 'greedy';
41
43
  this.autoAllocateOnCreate = settings.auto_allocate_on_create ?? false;
42
44
  this.autoReallocateOnComplete = settings.auto_reallocate_on_complete ?? false;
43
45
  this.maxTravelTimeSeconds = settings.max_travel_time_seconds ?? 3600;
@@ -0,0 +1,212 @@
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, timeout } from 'ember-concurrency';
6
+ import trackingPageTheme, { contrastRatio, inkFor, normalizeHex, DEFAULT_ACCENT } from '../../utils/tracking-page-theme';
7
+
8
+ /**
9
+ * The locales a tracking page can be shown in, labelled in their own language.
10
+ */
11
+ export const TRACKING_PAGE_LOCALES = [
12
+ { code: 'en-us', label: 'English' },
13
+ { code: 'ar-ae', label: 'العربية' },
14
+ { code: 'bg-bg', label: 'Български' },
15
+ { code: 'es-pa', label: 'Español' },
16
+ { code: 'fr-fr', label: 'Français' },
17
+ { code: 'mn-mn', label: 'Монгол' },
18
+ { code: 'pt-br', label: 'Português' },
19
+ { code: 'ru-ru', label: 'Русский' },
20
+ { code: 'uk-ua', label: 'Українська' },
21
+ { code: 'vi-vn', label: 'Tiếng Việt' },
22
+ ];
23
+
24
+ /**
25
+ * Keys the server adds to the saved config when it reads it back. They are facts about
26
+ * the instance, not settings, so they are kept apart and never sent back on save.
27
+ */
28
+ const READ_ONLY_KEYS = ['slug_validation', 'accent_ink', 'accent_contrast', 'sms_available', 'customer_portal_installed', 'admin'];
29
+
30
+ /**
31
+ * Settings::TrackingPageController
32
+ *
33
+ * The company's customer tracking page: which pages are on and at what address, branding,
34
+ * how customers prove who they are, and what verified customers see.
35
+ */
36
+ export default class SettingsTrackingPageController extends Controller {
37
+ @service fetch;
38
+ @service notifications;
39
+ @service intl;
40
+ @service currentUser;
41
+
42
+ @tracked config = null;
43
+ @tracked slugValidation = null;
44
+ @tracked smsAvailable = false;
45
+ @tracked customerPortalInstalled = false;
46
+ @tracked loadFailed = false;
47
+
48
+ /**
49
+ * Take the settings as the server returns them.
50
+ */
51
+ load(settings) {
52
+ if (!settings) {
53
+ this.loadFailed = true;
54
+ return;
55
+ }
56
+
57
+ const config = { ...settings };
58
+ READ_ONLY_KEYS.forEach((key) => delete config[key]);
59
+
60
+ this.config = config;
61
+ this.slugValidation = settings.slug_validation ?? null;
62
+ this.smsAvailable = settings.sms_available === true;
63
+ this.customerPortalInstalled = settings.customer_portal_installed === true;
64
+ this.loadFailed = false;
65
+ }
66
+
67
+ get accent() {
68
+ return normalizeHex(this.config?.branding?.accent) ?? DEFAULT_ACCENT;
69
+ }
70
+
71
+ get accentInk() {
72
+ return inkFor(this.accent);
73
+ }
74
+
75
+ get accentContrast() {
76
+ return contrastRatio(this.accent, this.accentInk);
77
+ }
78
+
79
+ get accentReadable() {
80
+ return this.accentContrast >= 4.5;
81
+ }
82
+
83
+ get previewLightStyle() {
84
+ return trackingPageTheme(this.config?.branding, false);
85
+ }
86
+
87
+ get previewDarkStyle() {
88
+ return trackingPageTheme(this.config?.branding, true);
89
+ }
90
+
91
+ get companyName() {
92
+ return this.currentUser.companyName ?? '';
93
+ }
94
+
95
+ get displayName() {
96
+ return this.config?.branding?.display_name || this.companyName;
97
+ }
98
+
99
+ get orgPageBase() {
100
+ return `${window.location.origin}/~/t/`;
101
+ }
102
+
103
+ get orgPageUrl() {
104
+ return `${window.location.origin}/~/t/${encodeURIComponent(this.config?.org_page?.slug ?? '')}`;
105
+ }
106
+
107
+ get genericPageUrl() {
108
+ return `${window.location.origin}/~/track`;
109
+ }
110
+
111
+ get slugIsValid() {
112
+ return this.slugValidation?.valid === true;
113
+ }
114
+
115
+ get localeOptions() {
116
+ const enabled = this.config?.branding?.locales ?? [];
117
+ const defaultLocale = this.config?.branding?.default_locale;
118
+
119
+ return TRACKING_PAGE_LOCALES.map((locale) => ({
120
+ ...locale,
121
+ enabled: enabled.includes(locale.code),
122
+ isDefault: locale.code === defaultLocale,
123
+ }));
124
+ }
125
+
126
+ get linkTargetOptions() {
127
+ return ['auto', 'org', 'generic'].map((value) => ({ value, label: this.intl.t(`tracking-page.settings.links-target-${value}`) }));
128
+ }
129
+
130
+ get themeOptions() {
131
+ return ['system', 'light'].map((value) => ({ value, label: this.intl.t(`tracking-page.settings.theme-${value}`) }));
132
+ }
133
+
134
+ get driverContactOptions() {
135
+ return ['company', 'driver'].map((value) => ({ value, label: this.intl.t(`tracking-page.settings.driver-contact-${value}`) }));
136
+ }
137
+
138
+ /**
139
+ * Set one value by its dotted path, replacing the config so the form and preview update.
140
+ */
141
+ @action update(path, value) {
142
+ const config = JSON.parse(JSON.stringify(this.config));
143
+ const keys = path.split('.');
144
+ const last = keys.pop();
145
+ let target = config;
146
+ for (const key of keys) {
147
+ target = target[key];
148
+ }
149
+
150
+ target[last] = value;
151
+ this.config = config;
152
+ }
153
+
154
+ @action updateFromEvent(path, event) {
155
+ this.update(path, event.target.value);
156
+ }
157
+
158
+ @action updateNumber(path, event) {
159
+ this.update(path, parseInt(event.target.value, 10));
160
+ }
161
+
162
+ @action selectOption(path, option) {
163
+ this.update(path, option?.value ?? option);
164
+ }
165
+
166
+ @action updateSlug(event) {
167
+ const slug = event.target.value.toLowerCase().replace(/\s+/g, '-');
168
+ this.update('org_page.slug', slug);
169
+ this.validateSlug.perform(slug);
170
+ }
171
+
172
+ @action toggleLocale(code) {
173
+ const locales = this.config.branding.locales ?? [];
174
+ if (code === this.config.branding.default_locale) {
175
+ return;
176
+ }
177
+
178
+ this.update('branding.locales', locales.includes(code) ? locales.filter((locale) => locale !== code) : [...locales, code]);
179
+ }
180
+
181
+ @action setDefaultLocale(option) {
182
+ const code = option?.value ?? option;
183
+ const locales = this.config.branding.locales ?? [];
184
+ this.update('branding.default_locale', code);
185
+ if (!locales.includes(code)) {
186
+ this.update('branding.locales', [...locales, code]);
187
+ }
188
+ }
189
+
190
+ get defaultLocaleOptions() {
191
+ return TRACKING_PAGE_LOCALES.map((locale) => ({ value: locale.code, label: locale.label }));
192
+ }
193
+
194
+ @task({ restartable: true }) *validateSlug(slug) {
195
+ yield timeout(350);
196
+ try {
197
+ this.slugValidation = yield this.fetch.post('fleet-ops/settings/tracking-page-settings/validate-slug', { slug });
198
+ } catch (error) {
199
+ this.notifications.serverError(error);
200
+ }
201
+ }
202
+
203
+ @task *saveSettings() {
204
+ try {
205
+ const settings = yield this.fetch.post('fleet-ops/settings/tracking-page-settings', { trackingPage: this.config });
206
+ this.load(settings);
207
+ this.notifications.success(this.intl.t('tracking-page.settings.saved'));
208
+ } catch (error) {
209
+ this.notifications.serverError(error);
210
+ }
211
+ }
212
+ }