@fleetbase/fleetops-engine 0.6.71 → 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 (378) hide show
  1. package/RELEASE.md +15 -18
  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/form/route.hbs +1 -1
  16. package/addon/components/order/form/route.js +13 -1
  17. package/addon/components/service-rate/details.hbs +1 -1
  18. package/addon/components/service-rate/form.hbs +1 -1
  19. package/addon/components/tracking-page/full-view.hbs +276 -0
  20. package/addon/components/tracking-page/full-view.js +474 -0
  21. package/addon/components/tracking-page/locale-selector.js +30 -0
  22. package/addon/components/tracking-page/map.hbs +44 -0
  23. package/addon/components/tracking-page/map.js +328 -0
  24. package/addon/components/tracking-page/order-list.hbs +35 -0
  25. package/addon/components/tracking-page/public-view.hbs +60 -0
  26. package/addon/components/tracking-page/settings-preview.hbs +21 -0
  27. package/addon/components/tracking-page/settings-preview.js +23 -0
  28. package/addon/components/tracking-page/verify-panel.hbs +109 -0
  29. package/addon/components/tracking-page.hbs +142 -0
  30. package/addon/components/tracking-page.js +564 -0
  31. package/addon/components/trailer/form.hbs +2 -2
  32. package/addon/components/vehicle/form.hbs +3 -3
  33. package/addon/components/warranty/form.hbs +2 -2
  34. package/addon/components/widget/live-fleet.js +54 -6
  35. package/addon/components/widget/proof-of-delivery.hbs +49 -0
  36. package/addon/components/widget/proof-of-delivery.js +62 -0
  37. package/addon/components/work-order/form.hbs +3 -3
  38. package/addon/controllers/operations/orders/index/details.js +19 -3
  39. package/addon/controllers/operations/scheduler/index.js +45 -14
  40. package/addon/controllers/settings/index.js +6 -0
  41. package/addon/controllers/settings/map.js +75 -0
  42. package/addon/controllers/settings/orchestrator.js +8 -6
  43. package/addon/controllers/settings/tracking-page.js +212 -0
  44. package/addon/extension.js +65 -1
  45. package/addon/helpers/parcel-box-image.js +16 -0
  46. package/addon/routes/operations/orders/index/new.js +5 -4
  47. package/addon/routes/operations/scheduler/index.js +1 -1
  48. package/addon/routes/settings/tracking-page.js +31 -0
  49. package/addon/routes.js +1 -0
  50. package/addon/services/location.js +69 -21
  51. package/addon/services/map-settings.js +28 -0
  52. package/addon/services/order-allocation.js +1 -1
  53. package/addon/styles/fleetops-engine.css +1391 -0
  54. package/addon/templates/settings/map.hbs +46 -0
  55. package/addon/templates/settings/tracking-page.hbs +318 -0
  56. package/addon/utils/identity-cell-resource.js +2 -56
  57. package/addon/utils/map-default-view.js +132 -0
  58. package/addon/utils/placeholder-images.js +4 -69
  59. package/addon/utils/resource-descriptors/assets.js +2 -57
  60. package/addon/utils/resource-descriptors/helpers.js +23 -122
  61. package/addon/utils/resource-descriptors/operations.js +2 -61
  62. package/addon/utils/resource-descriptors/people.js +3 -142
  63. package/addon/utils/resource-descriptors/shared.js +18 -0
  64. package/addon/utils/tracking-live-source.js +123 -0
  65. package/addon/utils/tracking-page-theme.js +123 -0
  66. package/app/components/admin/tracking-page-settings.js +1 -0
  67. package/app/components/tracking-page/full-view.js +1 -0
  68. package/app/components/tracking-page/locale-selector.js +1 -0
  69. package/app/components/tracking-page/map.js +1 -0
  70. package/app/components/tracking-page/order-list.js +1 -0
  71. package/app/components/tracking-page/public-view.js +1 -0
  72. package/app/components/tracking-page/settings-preview.js +1 -0
  73. package/app/components/tracking-page/verify-panel.js +1 -0
  74. package/app/components/tracking-page.js +1 -0
  75. package/app/components/widget/proof-of-delivery.js +1 -0
  76. package/app/controllers/settings/tracking-page.js +1 -0
  77. package/app/helpers/parcel-box-image.js +1 -0
  78. package/app/routes/settings/tracking-page.js +1 -0
  79. package/app/templates/settings/tracking-page.js +1 -0
  80. package/app/utils/map-default-view.js +1 -0
  81. package/app/utils/tracking-live-source.js +1 -0
  82. package/app/utils/tracking-page-theme.js +1 -0
  83. package/composer.json +2 -2
  84. package/extension.json +1 -1
  85. package/package.json +4 -4
  86. package/scripts/pest-bootstrap.php +4 -0
  87. package/server/config/fleetops.php +4 -0
  88. package/server/migrations/2026_10_08_000001_create_tracking_sessions_tables.php +51 -0
  89. package/server/migrations/2026_10_10_000001_add_trashed_index_to_device_events.php +40 -0
  90. package/server/migrations/2026_10_10_000002_add_trashed_index_to_positions.php +40 -0
  91. package/server/migrations/2026_10_12_000001_add_deleted_at_to_tracking_sessions_tables.php +32 -0
  92. package/server/resources/views/labels/default.php +2 -2
  93. package/server/resources/views/labels/entity-label.php +2 -2
  94. package/server/resources/views/labels/waypoint-label.php +2 -2
  95. package/server/src/Auth/Schemas/FleetOps.php +5 -0
  96. package/server/src/Console/Commands/ProcessOperationalAlerts.php +1 -0
  97. package/server/src/Console/Commands/PruneTelematicsData.php +101 -26
  98. package/server/src/Events/OrderChanged.php +257 -0
  99. package/server/src/Events/OrderDriverUnassigned.php +68 -0
  100. package/server/src/Exceptions/OrderEditLockedException.php +32 -0
  101. package/server/src/Expansions/CompanyExpansion.php +3 -2
  102. package/server/src/Expansions/UserExpansion.php +12 -10
  103. package/server/src/Http/Controllers/Api/v1/CustomerController.php +108 -18
  104. package/server/src/Http/Controllers/Api/v1/DriverController.php +95 -10
  105. package/server/src/Http/Controllers/Api/v1/EntityController.php +66 -0
  106. package/server/src/Http/Controllers/Api/v1/FleetController.php +10 -4
  107. package/server/src/Http/Controllers/Api/v1/OrderController.php +139 -36
  108. package/server/src/Http/Controllers/Api/v1/PayloadController.php +76 -8
  109. package/server/src/Http/Controllers/Api/v1/PlaceController.php +3 -3
  110. package/server/src/Http/Controllers/Api/v1/TrackingNumberController.php +6 -32
  111. package/server/src/Http/Controllers/Api/v1/VehicleController.php +5 -0
  112. package/server/src/Http/Controllers/Internal/v1/ContactController.php +3 -0
  113. package/server/src/Http/Controllers/Internal/v1/DriverController.php +10 -0
  114. package/server/src/Http/Controllers/Internal/v1/FleetController.php +16 -2
  115. package/server/src/Http/Controllers/Internal/v1/InspectionSubmissionController.php +13 -8
  116. package/server/src/Http/Controllers/Internal/v1/IssueController.php +2 -0
  117. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +4 -2
  118. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +93 -11
  119. package/server/src/Http/Controllers/Internal/v1/OrderController.php +84 -24
  120. package/server/src/Http/Controllers/Internal/v1/PositionController.php +22 -0
  121. package/server/src/Http/Controllers/Internal/v1/ProofController.php +55 -4
  122. package/server/src/Http/Controllers/Internal/v1/SettingController.php +303 -10
  123. package/server/src/Http/Controllers/Internal/v1/TrailerController.php +1 -0
  124. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +1 -0
  125. package/server/src/Http/Controllers/Public/TrackingController.php +518 -0
  126. package/server/src/Http/Filter/ContactFilter.php +1 -1
  127. package/server/src/Http/Filter/InspectionFormFilter.php +23 -0
  128. package/server/src/Http/Filter/InspectionSubmissionFilter.php +23 -0
  129. package/server/src/Http/Filter/MaintenanceFilter.php +23 -0
  130. package/server/src/Http/Filter/OrderFilter.php +39 -4
  131. package/server/src/Http/Filter/ProofFilter.php +18 -0
  132. package/server/src/Http/Filter/RouteFilter.php +18 -0
  133. package/server/src/Http/Filter/TelematicFilter.php +23 -0
  134. package/server/src/Http/Filter/WarrantyFilter.php +23 -0
  135. package/server/src/Http/Requests/CancelOrderRequest.php +2 -1
  136. package/server/src/Http/Resources/v1/Concerns/ResolvesPublicRelationFields.php +14 -1
  137. package/server/src/Http/Resources/v1/Index/Customer.php +18 -0
  138. package/server/src/Http/Resources/v1/Index/Order.php +11 -2
  139. package/server/src/Http/Resources/v1/PublicOrderTracking.php +153 -0
  140. package/server/src/Http/Resources/v1/TrackingNumber.php +8 -34
  141. package/server/src/Http/Resources/v1/Zone.php +31 -23
  142. package/server/src/Jobs/FinalizeApiOrderCreation.php +3 -1
  143. package/server/src/Jobs/ProcessAllocationJob.php +16 -6
  144. package/server/src/Jobs/PublishTrackingUpdate.php +50 -0
  145. package/server/src/Listeners/HandleGeofenceEntered.php +31 -15
  146. package/server/src/Listeners/HandleOrderCanceled.php +9 -1
  147. package/server/src/Listeners/HandleUserRemovedFromCompany.php +2 -1
  148. package/server/src/Listeners/PublishTrackingUpdates.php +27 -0
  149. package/server/src/Mail/CustomerCredentialsMail.php +4 -2
  150. package/server/src/Models/AssetConnection.php +3 -0
  151. package/server/src/Models/Contact.php +3 -0
  152. package/server/src/Models/Device.php +28 -0
  153. package/server/src/Models/DeviceEvent.php +7 -0
  154. package/server/src/Models/Driver.php +20 -7
  155. package/server/src/Models/Entity.php +12 -4
  156. package/server/src/Models/Fleet.php +11 -32
  157. package/server/src/Models/FleetDriver.php +3 -0
  158. package/server/src/Models/FleetVehicle.php +3 -0
  159. package/server/src/Models/FuelReport.php +5 -13
  160. package/server/src/Models/Issue.php +4 -16
  161. package/server/src/Models/Order.php +200 -9
  162. package/server/src/Models/Payload.php +227 -35
  163. package/server/src/Models/Place.php +7 -1
  164. package/server/src/Models/ServiceArea.php +7 -0
  165. package/server/src/Models/ServiceRate.php +7 -17
  166. package/server/src/Models/TrackingNumber.php +101 -2
  167. package/server/src/Models/TrackingSession.php +33 -0
  168. package/server/src/Models/TrackingSessionGrant.php +22 -0
  169. package/server/src/Models/TrackingStatus.php +6 -0
  170. package/server/src/Models/Vehicle.php +21 -2
  171. package/server/src/Models/Vendor.php +7 -0
  172. package/server/src/Models/Waypoint.php +13 -4
  173. package/server/src/Notifications/OrderAssigned.php +2 -1
  174. package/server/src/Notifications/OrderCanceled.php +7 -1
  175. package/server/src/Notifications/OrderChanged.php +87 -0
  176. package/server/src/Notifications/OrderCompleted.php +2 -1
  177. package/server/src/Notifications/OrderDispatchFailed.php +2 -1
  178. package/server/src/Notifications/OrderDispatched.php +2 -1
  179. package/server/src/Notifications/OrderFailed.php +2 -1
  180. package/server/src/Notifications/OrderUnassigned.php +66 -0
  181. package/server/src/Notifications/WaypointCompleted.php +2 -1
  182. package/server/src/Observers/CompanyUserObserver.php +8 -7
  183. package/server/src/Observers/DriverObserver.php +6 -1
  184. package/server/src/Observers/OrderObserver.php +141 -1
  185. package/server/src/Observers/TrackingNumberObserver.php +18 -6
  186. package/server/src/Observers/UserObserver.php +2 -1
  187. package/server/src/Observers/VehicleObserver.php +8 -3
  188. package/server/src/Orchestration/Engines/RouteSequencingEngine.php +21 -3
  189. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +38 -8
  190. package/server/src/Orchestration/OrchestrationEngineRegistry.php +61 -1
  191. package/server/src/Orchestration/Support/OrchestratorSettings.php +61 -0
  192. package/server/src/Orchestration/Support/RouteMetrics.php +165 -0
  193. package/server/src/Providers/EventServiceProvider.php +11 -0
  194. package/server/src/Providers/FleetOpsServiceProvider.php +13 -0
  195. package/server/src/Support/ApiCacheThrottle.php +76 -0
  196. package/server/src/Support/LiveOrderQuery.php +3 -1
  197. package/server/src/Support/OrderEdit.php +365 -0
  198. package/server/src/Support/OrderEditPolicy.php +292 -0
  199. package/server/src/Support/ProfileAccountManager.php +54 -1
  200. package/server/src/Support/ResolvesOrderServiceStops.php +5 -0
  201. package/server/src/Support/SocketChannels.php +346 -0
  202. package/server/src/Support/Telematics/TelematicService.php +7 -2
  203. package/server/src/Support/Telematics/Telemetry/Inbox.php +8 -1
  204. package/server/src/Support/TrackingChannel.php +55 -0
  205. package/server/src/Support/TrackingCode.php +242 -0
  206. package/server/src/Support/TrackingPage/TrackingAccess.php +356 -0
  207. package/server/src/Support/TrackingPage/TrackingPageConfig.php +264 -0
  208. package/server/src/Support/TrackingPage/TrackingRecipient.php +111 -0
  209. package/server/src/Support/TrackingPage/TrackingResolver.php +201 -0
  210. package/server/src/Support/TrackingPage/TrackingTarget.php +40 -0
  211. package/server/src/Support/TrackingPage/TrackingView.php +543 -0
  212. package/server/src/Support/TrackingPublisher.php +261 -0
  213. package/server/src/Support/TrackingScope.php +182 -0
  214. package/server/src/Support/TrackingUpdate.php +402 -0
  215. package/server/src/Support/Utils.php +15 -3
  216. package/server/src/Traits/HasTrackingNumber.php +2 -0
  217. package/server/src/routes.php +39 -7
  218. package/server/tests/ApiCustomerControllerContractsTest.php +54 -7
  219. package/server/tests/ApiDriverPasswordContractsTest.php +25 -21
  220. package/server/tests/ApiEntityControllerContractsTest.php +84 -0
  221. package/server/tests/ApiOrderControllerContractsTest.php +245 -0
  222. package/server/tests/ApiPayloadControllerContractsTest.php +121 -0
  223. package/server/tests/ApiPlaceControllerContractsTest.php +1 -1
  224. package/server/tests/ApiTrackingNumberControllerContractsTest.php +6 -9
  225. package/server/tests/CompactResourceSerializationTest.php +23 -59
  226. package/server/tests/ControllerFilterContractsTest.php +1 -1
  227. package/server/tests/CustomerContactIdentitySafetyTest.php +1 -1
  228. package/server/tests/EventContractsTest.php +26 -60
  229. package/server/tests/ExpansionContractsTest.php +15 -2
  230. package/server/tests/Feature/Http/Api/CustomerControllerHelperSeamsTest.php +29 -3
  231. package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +23 -0
  232. package/server/tests/Feature/Http/Api/OrderControllerNearbyFiltersTest.php +16 -16
  233. package/server/tests/Feature/Http/Api/OrderControllerQueryFiltersTest.php +41 -14
  234. package/server/tests/Feature/Http/Api/TrackingNumberControllerHelpersTest.php +57 -22
  235. package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +23 -9
  236. package/server/tests/Feature/Http/Internal/OrchestrationControllerContractsTest.php +171 -1
  237. package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +110 -14
  238. package/server/tests/Feature/Http/Internal/OrderControllerHelpersTest.php +7 -5
  239. package/server/tests/Feature/Http/Internal/PositionControllerReplayChannelTest.php +135 -0
  240. package/server/tests/Feature/Http/Internal/ProofControllerContractsTest.php +3 -3
  241. package/server/tests/Feature/Http/Internal/ProofControllerRecentTest.php +170 -0
  242. package/server/tests/Feature/Http/Public/TrackingControllerTest.php +495 -0
  243. package/server/tests/IndexCustomerPhotoTest.php +151 -0
  244. package/server/tests/ModelAccessorContractsTest.php +4 -3
  245. package/server/tests/NotificationAndMailContractsTest.php +5 -8
  246. package/server/tests/ObserverContractsTest.php +72 -10
  247. package/server/tests/OrderActivityFlowRegressionTest.php +3 -2
  248. package/server/tests/OrderFilterExecutionTest.php +167 -2
  249. package/server/tests/ProcessAllocationJobContractsTest.php +20 -0
  250. package/server/tests/PublicOrderTrackingResourceTest.php +221 -0
  251. package/server/tests/QueueJobContractsTest.php +2 -1
  252. package/server/tests/RequestContractsTest.php +1 -1
  253. package/server/tests/RouteRegistrationExecutionTest.php +26 -0
  254. package/server/tests/RouteSequencingEngineTest.php +17 -0
  255. package/server/tests/SettingControllerContractsTest.php +110 -0
  256. package/server/tests/Support/TrackingPageDatabase.php +200 -0
  257. package/server/tests/TenantScopingTest.php +206 -0
  258. package/server/tests/TrackingPageSettingsTest.php +343 -0
  259. package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +51 -0
  260. package/server/tests/Unit/Http/Filter/ContactFilterAddressTest.php +61 -0
  261. package/server/tests/Unit/Http/Resources/IndexOrderResourceTest.php +30 -0
  262. package/server/tests/Unit/Jobs/FinalizeApiOrderCreationDispatchGuardTest.php +266 -0
  263. package/server/tests/Unit/Listeners/HandleGeofenceEnteredTest.php +182 -22
  264. package/server/tests/Unit/Listeners/HandleUserRemovedFromCompanyTest.php +5 -0
  265. package/server/tests/Unit/Models/DriverAssignVehicleTest.php +81 -0
  266. package/server/tests/Unit/Models/FleetTest.php +4 -3
  267. package/server/tests/Unit/Models/OrderCancelReasonTest.php +143 -0
  268. package/server/tests/Unit/Models/OrderPayloadConfigAndDistanceTest.php +60 -0
  269. package/server/tests/Unit/Models/OrderPayloadRouteTest.php +4 -2
  270. package/server/tests/Unit/Models/PayloadTest.php +18 -4
  271. package/server/tests/Unit/Models/ServiceRatePersistenceAndServicabilityTest.php +2 -2
  272. package/server/tests/Unit/Models/TrackingNumberTest.php +67 -6
  273. package/server/tests/Unit/Models/VehicleTest.php +16 -2
  274. package/server/tests/Unit/Models/WaypointTest.php +4 -3
  275. package/server/tests/Unit/Notifications/OrderChangeNotificationsTest.php +59 -0
  276. package/server/tests/Unit/Notifications/TerminalOrderNotificationsTest.php +3 -3
  277. package/server/tests/Unit/Observers/DriverDeletionCleanupTest.php +158 -0
  278. package/server/tests/Unit/Observers/FleetOpsCompanyUserAndServiceAreaObserversTest.php +20 -0
  279. package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +3 -3
  280. package/server/tests/Unit/Observers/OrderAssignmentObserverTest.php +100 -0
  281. package/server/tests/Unit/Observers/OrderDeleteCascadeTest.php +213 -0
  282. package/server/tests/Unit/Observers/VehicleObserverDeleteTest.php +67 -0
  283. package/server/tests/Unit/Orchestration/EngineSelectionAndRouteMetricsTest.php +336 -0
  284. package/server/tests/Unit/Orchestration/Engines/VroomOrchestrationEngineLifecycleTest.php +8 -1
  285. package/server/tests/Unit/Support/ApiCacheDependenciesTest.php +347 -0
  286. package/server/tests/Unit/Support/OrderEditPolicyTest.php +156 -0
  287. package/server/tests/Unit/Support/OrderOperationsTest.php +514 -0
  288. package/server/tests/Unit/Support/OsrmProofAndIssueFilterTest.php +4 -3
  289. package/server/tests/Unit/Support/ProfileAccountManagerTest.php +141 -0
  290. package/server/tests/Unit/Support/QuerySeamsTest.php +5 -1
  291. package/server/tests/Unit/Support/SocketChannelsTest.php +396 -0
  292. package/server/tests/Unit/Support/TrackingChannelTest.php +232 -0
  293. package/server/tests/Unit/Support/TrackingCodeTest.php +186 -0
  294. package/server/tests/Unit/Support/TrackingPage/TrackingAccessTest.php +200 -0
  295. package/server/tests/Unit/Support/TrackingPage/TrackingRecipientTest.php +51 -0
  296. package/server/tests/Unit/Support/TrackingPage/TrackingResolverTest.php +181 -0
  297. package/server/tests/Unit/Support/TrackingPage/TrackingViewTest.php +479 -0
  298. package/server/tests/Unit/Support/TrackingPublisherTest.php +425 -0
  299. package/server/tests/Unit/Support/TrackingUpdateTest.php +363 -0
  300. package/server/tests/Unit/Support/UtilsAdditionalTest.php +9 -0
  301. package/server/tests/Unit/Support/UtilsGeoMatrixAndVendorTest.php +25 -0
  302. package/server/tests/VehicleResourceContractsTest.php +46 -0
  303. package/server/tests/VroomOrchestrationEngineTest.php +34 -1
  304. package/server/tests/ZoneGeofenceSettingsResourceTest.php +121 -0
  305. package/translations/ar-ae.yml +274 -0
  306. package/translations/bg-bg.yaml +274 -0
  307. package/translations/en-us.yaml +287 -0
  308. package/translations/es-pa.yaml +274 -0
  309. package/translations/fr-fr.yaml +274 -0
  310. package/translations/mn-mn.yaml +274 -0
  311. package/translations/pt-br.yaml +274 -0
  312. package/translations/ru-ru.yaml +276 -2
  313. package/translations/uk-ua.yaml +275 -1
  314. package/translations/vi-vn.yaml +274 -0
  315. package/addon/components/cell/contact-identity.hbs +0 -1
  316. package/addon/components/cell/customer-identity.hbs +0 -1
  317. package/addon/components/cell/driver-identity.hbs +0 -1
  318. package/addon/components/cell/fleet-identity.hbs +0 -1
  319. package/addon/components/cell/order-identity.hbs +0 -1
  320. package/addon/components/cell/place-identity.hbs +0 -1
  321. package/addon/components/cell/vehicle-identity.hbs +0 -1
  322. package/addon/components/cell/vendor-identity.hbs +0 -1
  323. package/addon/components/contact/pill.hbs +0 -24
  324. package/addon/components/contact/summary.hbs +0 -1
  325. package/addon/components/customer/pill.hbs +0 -24
  326. package/addon/components/customer/summary.hbs +0 -1
  327. package/addon/components/driver/pill.hbs +0 -24
  328. package/addon/components/driver/summary.hbs +0 -1
  329. package/addon/components/fleet/pill.hbs +0 -24
  330. package/addon/components/fleet/summary.hbs +0 -1
  331. package/addon/components/order/pill.hbs +0 -24
  332. package/addon/components/order/summary.hbs +0 -1
  333. package/addon/components/place/pill.hbs +0 -24
  334. package/addon/components/place/summary.hbs +0 -1
  335. package/addon/components/select-option/contact.hbs +0 -12
  336. package/addon/components/select-option/customer.hbs +0 -12
  337. package/addon/components/select-option/driver.hbs +0 -12
  338. package/addon/components/select-option/fleet.hbs +0 -12
  339. package/addon/components/select-option/order.hbs +0 -12
  340. package/addon/components/select-option/place.hbs +0 -12
  341. package/addon/components/select-option/vehicle.hbs +0 -12
  342. package/addon/components/select-option/vendor.hbs +0 -12
  343. package/addon/components/vehicle/pill.hbs +0 -24
  344. package/addon/components/vehicle/summary.hbs +0 -1
  345. package/addon/components/vendor/pill.hbs +0 -24
  346. package/addon/components/vendor/summary.hbs +0 -1
  347. package/app/components/cell/contact-identity.js +0 -1
  348. package/app/components/cell/customer-identity.js +0 -1
  349. package/app/components/cell/driver-identity.js +0 -1
  350. package/app/components/cell/fleet-identity.js +0 -1
  351. package/app/components/cell/order-identity.js +0 -1
  352. package/app/components/cell/place-identity.js +0 -1
  353. package/app/components/cell/vehicle-identity.js +0 -1
  354. package/app/components/cell/vendor-identity.js +0 -1
  355. package/app/components/contact/pill.js +0 -1
  356. package/app/components/contact/summary.js +0 -1
  357. package/app/components/customer/pill.js +0 -1
  358. package/app/components/customer/summary.js +0 -1
  359. package/app/components/driver/pill.js +0 -1
  360. package/app/components/driver/summary.js +0 -1
  361. package/app/components/fleet/pill.js +0 -1
  362. package/app/components/fleet/summary.js +0 -1
  363. package/app/components/order/pill.js +0 -1
  364. package/app/components/order/summary.js +0 -1
  365. package/app/components/place/pill.js +0 -1
  366. package/app/components/place/summary.js +0 -1
  367. package/app/components/select-option/contact.js +0 -1
  368. package/app/components/select-option/customer.js +0 -1
  369. package/app/components/select-option/driver.js +0 -1
  370. package/app/components/select-option/fleet.js +0 -1
  371. package/app/components/select-option/order.js +0 -1
  372. package/app/components/select-option/place.js +0 -1
  373. package/app/components/select-option/vehicle.js +0 -1
  374. package/app/components/select-option/vendor.js +0 -1
  375. package/app/components/vehicle/pill.js +0 -1
  376. package/app/components/vehicle/summary.js +0 -1
  377. package/app/components/vendor/pill.js +0 -1
  378. package/app/components/vendor/summary.js +0 -1
package/RELEASE.md CHANGED
@@ -1,29 +1,26 @@
1
- > v0.6.71 ~ "Extensible tables and panels, configured order lifecycles and order presentation profiles"
1
+ > v0.6.72 ~ "Customer tracking page settings, safer public tracking and structured label codes"
2
2
 
3
3
  ---
4
4
  ## Highlights
5
5
 
6
- - **Resource view registries.** Extensions can add the following through `fleet-ops:<resource>:table:<slot>` and `fleet-ops:<resource>:details:<slot>`:
7
- - columns, row actions, bulk actions and toolbar buttons on every top-level Fleet-Ops table;
8
- - header buttons and "…" menu items on its details panels and side panels.
6
+ - **Tracking Page settings.** A new Fleet-Ops → Settings → Tracking Page lets each organization configure the customer tracking page:
7
+ - its own page at `/t/{slug}`, with a live address check;
8
+ - whether its numbers can be tracked on the shared page;
9
+ - branding: display name, accent colour with a live contrast check, an optional dark-mode accent, support contacts, "Powered by Fleetbase", and languages;
10
+ - how customers verify (text message or email codes, session length, customer sign-in);
11
+ - what verified customers see.
9
12
 
10
- Built-in items carry stable ids, so registered items can be placed before or after them.
11
- - **Configured order lifecycles.** An order config can define its own lifecycle in `meta.lifecycle`: `initial`, `completed`, `canceled` and `terminal` activities, `dispatch: false` and `strict_transitions`. The internal API starts orders at the initial activity, refuses to dispatch when dispatch is off, accepts only the flow's transitions when strict, and cancels to the configured activity. Configs without a lifecycle behave as before.
12
- - **Lifecycle tab** in Order Configuration, to view and edit a config's lifecycle.
13
- - **The board** shows a configured lifecycle's activities as its columns.
14
- - **The activity flow editor** roots at the configured initial activity, and handles flows with cycles.
15
- - **Order presentation profiles.** An extension can give its order type its own order form sections, extra and hidden fields, details view sections and hidden actions, registered in `fleet-ops:order-presentation`. **Edit details** opens the profiled form. Other order types are unchanged.
16
- - **The Order Configuration manager lists every config**, including configs created while the console is open.
17
- - **An order's activity timeline follows status changes made on the server.**
18
- - **Fix: the Activity Flow crashed on a flow with a cycle.**
19
- - **Fix: order details now load the assigned vehicle with the order.**
20
- - **Fix: the order form failed when its custom fields couldn't load.**
21
- - **Fix: registered details tabs.** Opening a driver, vehicle or trailer side panel removed the route from tabs other extensions had registered, which broke them in the full details view. Side panels also left out the tabs they could render.
22
- - **Fix: the sensor details view showed the tabs registered for places.**
13
+ Administrators set the shared page's defaults under Fleet-Ops Config → Tracking Page. The new tracking pages themselves arrive in a later release; these settings are what they will read.
14
+ - **Safer public order lookup.** `GET int/v1/fleet-ops/lookup` is rate limited per address and returns a minimal resource: tracking number, status, timeline, ETA, stop coordinates and items. It no longer returns notes, meta, internal ids, files, rates or customer details. The driver's location is included only while the order is under way. Unknown and malformed tracking numbers get the same answer.
15
+ - **Structured label codes.** Tracking-number QR codes now carry a tracking URL holding the tracking number and the owner's public id, which a phone camera opens as the tracking page. Barcodes use Code 128. Every scan endpoint still accepts the uuid codes already printed on parcels.
16
+ - **Fix: signatures on private S3 buckets.** Signatures were not stored on buckets that reject ACLs; they are now written without one and served through signed URLs. Legacy absolute avatar URLs on vehicles, drivers and places are re-signed when read.
17
+ - **Fix: the customer credentials email** linked to an unrouted page; it now links to `/customer-access/{slug}`.
18
+ - **Fix: parcel box images** on the service rate form and details showed broken images in production builds.
23
19
 
24
20
  ---
25
21
  ## Upgrading
26
- Needs fleetbase/ember-core v0.3.25 and fleetbase/ember-ui v0.4.5.
22
+ - Run `php artisan fleetbase:create-permissions` to register the new `tracking-page-settings` permission.
23
+ - Private-bucket signature storage pairs with fleetbase/core-api's `File::signStoredUrl()`; on older core-api releases stored avatar URLs pass through unchanged.
27
24
 
28
25
  ---
29
26
  ## Need help?
@@ -0,0 +1,56 @@
1
+ <ContentPanel @title="Generic Tracking Page" @open={{true}} @wrapperClass="bordered-classic">
2
+ <div class="space-y-5">
3
+ <div class="flex items-start justify-between gap-4">
4
+ <div>
5
+ <div class="text-sm font-semibold">Enable the generic tracking page</div>
6
+ <div class="text-xs text-gray-600 dark:text-gray-300">Serves /~/track for every organization that allows it. Organizations can also run their own page from their Fleet-Ops settings.</div>
7
+ </div>
8
+ <Toggle @isToggled={{this.genericEnabled}} @onToggle={{fn (mut this.genericEnabled)}} />
9
+ </div>
10
+ <div>
11
+ <label for="admin-tracking-page-name" class="block text-sm font-semibold mb-1">Display name</label>
12
+ <input id="admin-tracking-page-name" type="text" class="form-input w-full" value={{this.displayName}} placeholder="Fleetbase" {{on "input" (fn this.setValue "displayName")}} />
13
+ <p class="mt-1 text-xs text-gray-600 dark:text-gray-300">Shown before a lookup. The logo comes from the instance branding.</p>
14
+ </div>
15
+ <div>
16
+ <label for="admin-tracking-page-accent" class="block text-sm font-semibold mb-1">Accent colour</label>
17
+ <div class="flex items-center gap-3">
18
+ <input id="admin-tracking-page-accent" type="color" class="h-10 w-14 rounded-md border border-gray-300 dark:border-gray-700 bg-transparent cursor-pointer" value={{this.accent}} {{on "input" (fn this.setValue "accent")}} />
19
+ <span class="font-mono text-sm">{{this.accent}}</span>
20
+ </div>
21
+ </div>
22
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
23
+ <div>
24
+ <label for="admin-tracking-page-phone" class="block text-sm font-semibold mb-1">Support phone</label>
25
+ <input id="admin-tracking-page-phone" type="tel" class="form-input w-full" value={{this.supportPhone}} placeholder="+1 555 010 0100" {{on "input" (fn this.setValue "supportPhone")}} />
26
+ </div>
27
+ <div>
28
+ <label for="admin-tracking-page-email" class="block text-sm font-semibold mb-1">Support email</label>
29
+ <input id="admin-tracking-page-email" type="email" class="form-input w-full" value={{this.supportEmail}} placeholder="support@example.com" {{on "input" (fn this.setValue "supportEmail")}} />
30
+ </div>
31
+ <div>
32
+ <label for="admin-tracking-page-website" class="block text-sm font-semibold mb-1">Website</label>
33
+ <input id="admin-tracking-page-website" type="url" class="form-input w-full" value={{this.website}} placeholder="https://example.com" {{on "input" (fn this.setValue "website")}} />
34
+ </div>
35
+ </div>
36
+ <div class="flex items-start justify-between gap-4">
37
+ <div>
38
+ <div class="text-sm font-semibold">Show "Powered by Fleetbase"</div>
39
+ <div class="text-xs text-gray-600 dark:text-gray-300">The default for organizations that haven't chosen.</div>
40
+ </div>
41
+ <Toggle @isToggled={{this.poweredBy}} @onToggle={{fn (mut this.poweredBy)}} />
42
+ </div>
43
+ </div>
44
+ </ContentPanel>
45
+
46
+ <EmberWormhole @to="next-view-section-subheader-actions">
47
+ <Button
48
+ @type="primary"
49
+ @size="sm"
50
+ @icon="save"
51
+ @text="Save Changes"
52
+ @onClick={{perform this.saveSettings}}
53
+ @isLoading={{or this.saveSettings.isRunning this.loadSettings.isRunning}}
54
+ @disabled={{this.saveSettings.isRunning}}
55
+ />
56
+ </EmberWormhole>
@@ -0,0 +1,67 @@
1
+ import Component from '@glimmer/component';
2
+ import { inject as service } from '@ember/service';
3
+ import { tracked } from '@glimmer/tracking';
4
+ import { action } from '@ember/object';
5
+ import { task } from 'ember-concurrency';
6
+
7
+ /**
8
+ * Instance-wide defaults for the generic customer tracking page: whether it is on, and the
9
+ * branding it shows before a lookup (and for companies without their own).
10
+ */
11
+ export default class AdminTrackingPageSettingsComponent extends Component {
12
+ @service fetch;
13
+ @service notifications;
14
+ @tracked genericEnabled = true;
15
+ @tracked displayName = '';
16
+ @tracked accent = '#1F5FA8';
17
+ @tracked supportPhone = '';
18
+ @tracked supportEmail = '';
19
+ @tracked website = '';
20
+ @tracked poweredBy = true;
21
+
22
+ constructor() {
23
+ super(...arguments);
24
+ this.loadSettings.perform();
25
+ }
26
+
27
+ @task *loadSettings() {
28
+ try {
29
+ const settings = yield this.fetch.get('fleet-ops/settings/admin-tracking-page-settings');
30
+ this.genericEnabled = settings?.generic_page?.enabled ?? true;
31
+ this.displayName = settings?.branding?.display_name ?? '';
32
+ this.accent = settings?.branding?.accent ?? '#1F5FA8';
33
+ this.supportPhone = settings?.branding?.support_phone ?? '';
34
+ this.supportEmail = settings?.branding?.support_email ?? '';
35
+ this.website = settings?.branding?.website ?? '';
36
+ this.poweredBy = settings?.branding?.powered_by ?? true;
37
+ } catch (error) {
38
+ this.notifications.serverError(error);
39
+ }
40
+ }
41
+
42
+ @action setValue(property, event) {
43
+ this[property] = event.target.value;
44
+ }
45
+
46
+ @task *saveSettings() {
47
+ try {
48
+ yield this.fetch.post('fleet-ops/settings/admin-tracking-page-settings', {
49
+ trackingPage: {
50
+ generic_page: { enabled: this.genericEnabled },
51
+ branding: {
52
+ display_name: this.displayName,
53
+ accent: this.accent,
54
+ support_phone: this.supportPhone,
55
+ support_email: this.supportEmail,
56
+ website: this.website,
57
+ powered_by: this.poweredBy,
58
+ },
59
+ },
60
+ });
61
+
62
+ this.notifications.success('Tracking page defaults saved.');
63
+ } catch (error) {
64
+ this.notifications.serverError(error);
65
+ }
66
+ }
67
+ }
@@ -137,7 +137,7 @@
137
137
 
138
138
  <InputGroup @name={{t "device.fields.installation-date"}}>
139
139
  <DatePicker
140
- @value={{@resource.installation_date}}
140
+ @value={{format-date-fns @resource.installation_date "yyyy-MM-dd"}}
141
141
  @onChange={{fn (mut @resource.installation_date)}}
142
142
  @placeholder={{t "device.fields.installation-date-placeholder"}}
143
143
  @disabled={{cannot-write @resource}}
@@ -147,7 +147,7 @@
147
147
 
148
148
  <InputGroup @name={{t "device.fields.last-maintenance-date"}}>
149
149
  <DatePicker
150
- @value={{@resource.last_maintenance_date}}
150
+ @value={{format-date-fns @resource.last_maintenance_date "yyyy-MM-dd"}}
151
151
  @onChange={{fn (mut @resource.last_maintenance_date)}}
152
152
  @placeholder={{t "device.fields.last-maintenance-date-placeholder"}}
153
153
  @disabled={{cannot-write @resource}}
@@ -27,7 +27,7 @@
27
27
  </div>
28
28
  <div class="field-info-container md:col-span-3">
29
29
  <div class="field-name">{{t "driver.fields.license-expiry"}}</div>
30
- <div class="field-value">{{n-a (format-date-fns @resource.license_expiry)}}</div>
30
+ <div class="field-value">{{n-a (format-date-fns @resource.license_expiry "yyyy-MM-dd")}}</div>
31
31
  </div>
32
32
  <div class="field-info-container">
33
33
  <div class="field-name">{{t "resource.vehicle"}}</div>
@@ -66,7 +66,7 @@
66
66
 
67
67
  <InputGroup @name={{t "driver.fields.license-expiry"}}>
68
68
  <DatePicker
69
- @value={{@resource.license_expiry}}
69
+ @value={{format-date-fns @resource.license_expiry "yyyy-MM-dd"}}
70
70
  @onChange={{fn (mut @resource.license_expiry)}}
71
71
  @placeholder={{t "driver.fields.license-expiry"}}
72
72
  @disabled={{cannot-write @resource}}
@@ -159,7 +159,7 @@
159
159
  </InputGroup>
160
160
  <InputGroup @name="Purchase Date">
161
161
  <DatePicker
162
- @value={{or (format-date-fns @resource.purchased_at "dd-MM-yyyy") null}}
162
+ @value={{or (format-date-fns @resource.purchased_at "yyyy-MM-dd") null}}
163
163
  @onChange={{fn (mut @resource.purchased_at)}}
164
164
  @placeholder="DD-MM-YYYY"
165
165
  class="filter-date-input w-full flex-1"
@@ -208,6 +208,13 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
208
208
  this.createItem('menu.routing', 'route', 'settings.routing', 'fleet-ops view routing-settings', 'fleet-ops see routing-settings'),
209
209
  this.createItem('menu.orchestrator', 'circle-nodes', 'settings.orchestrator', 'fleet-ops view routing-settings', 'fleet-ops see routing-settings'),
210
210
  this.createItem('menu.scheduling', 'calendar-days', 'settings.scheduling', 'fleet-ops view scheduling-settings', 'fleet-ops see scheduling-settings'),
211
+ this.createItem(
212
+ 'tracking-page.settings.menu',
213
+ 'magnifying-glass-location',
214
+ 'settings.tracking-page',
215
+ 'fleet-ops view tracking-page-settings',
216
+ 'fleet-ops see tracking-page-settings'
217
+ ),
211
218
  this.createItem('menu.telematics-settings', 'satellite-dish', 'settings.telematics', 'fleet-ops view telematics-settings', 'fleet-ops see telematics-settings'),
212
219
  this.createItem('menu.custom-fields', 'pen-to-square', 'settings.custom-fields', 'fleet-ops list custom-field', 'fleet-ops see custom-field'),
213
220
  this.createItem('menu.avatars', 'icons', 'settings.avatars', 'fleet-ops list avatar', 'fleet-ops see avatar'),
@@ -406,9 +413,10 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
406
413
  'settings.routing': 5,
407
414
  'settings.orchestrator': 6,
408
415
  'settings.scheduling': 7,
409
- 'settings.telematics': 8,
410
- 'settings.custom-fields': 9,
411
- 'settings.avatars': 10,
416
+ 'settings.tracking-page': 8,
417
+ 'settings.telematics': 9,
418
+ 'settings.custom-fields': 10,
419
+ 'settings.avatars': 11,
412
420
  };
413
421
 
414
422
  return priorities[route] ?? 0;
@@ -185,7 +185,7 @@
185
185
  </div>
186
186
  <InputGroup @name="Scheduled At">
187
187
  <DatePicker
188
- @value={{or (format-date-fns @resource.scheduled_at "dd-MM-yyyy") null}}
188
+ @value={{or (format-date-fns @resource.scheduled_at "yyyy-MM-dd") null}}
189
189
  @onChange={{fn (mut @resource.scheduled_at)}}
190
190
  @placeholder="DD-MM-YYYY"
191
191
  class="filter-date-input w-full flex-1"
@@ -193,7 +193,7 @@
193
193
  </InputGroup>
194
194
  <InputGroup @name="Started At">
195
195
  <DatePicker
196
- @value={{or (format-date-fns @resource.started_at "dd-MM-yyyy") null}}
196
+ @value={{or (format-date-fns @resource.started_at "yyyy-MM-dd") null}}
197
197
  @onChange={{fn (mut @resource.started_at)}}
198
198
  @placeholder="DD-MM-YYYY"
199
199
  class="filter-date-input w-full flex-1"
@@ -201,7 +201,7 @@
201
201
  </InputGroup>
202
202
  <InputGroup @name="Completed At">
203
203
  <DatePicker
204
- @value={{or (format-date-fns @resource.completed_at "dd-MM-yyyy") null}}
204
+ @value={{or (format-date-fns @resource.completed_at "yyyy-MM-dd") null}}
205
205
  @onChange={{fn (mut @resource.completed_at)}}
206
206
  @placeholder="DD-MM-YYYY"
207
207
  class="filter-date-input w-full flex-1"
@@ -59,7 +59,7 @@ export default class MapGoogleLiveMapComponent extends Component {
59
59
  const marker = this.mapManager.getMarker(driver.id);
60
60
  if (!marker) {
61
61
  const createdMarker = await this.mapManager.addMarker(driver.id, coords.lat, coords.lng, {
62
- iconUrl: driver.vehicle_avatar ?? '/engines-dist/images/driver-marker.png',
62
+ iconUrl: driver.vehicle_avatar ?? '/engines-dist/images/map-marker.png',
63
63
  iconSize: [20, 20],
64
64
  title: driver.name,
65
65
  tooltip: buildDriverLiveMapContent(driver),
@@ -89,7 +89,7 @@ export default class MapGoogleLiveMapComponent extends Component {
89
89
  const marker = this.mapManager.getMarker(vehicle.id);
90
90
  if (!marker) {
91
91
  const createdMarker = await this.mapManager.addMarker(vehicle.id, coords.lat, coords.lng, {
92
- iconUrl: vehicle.avatar_url ?? '/engines-dist/images/vehicle-marker.png',
92
+ iconUrl: vehicle.avatar_url ?? '/engines-dist/images/map-marker.png',
93
93
  iconSize: [20, 20],
94
94
  title: vehicle.displayName,
95
95
  tooltip: buildVehicleLiveMapContent(vehicle),
@@ -12,6 +12,7 @@ import { task, timeout } from 'ember-concurrency';
12
12
  import { next } from '@ember/runloop';
13
13
  import getModelName from '@fleetbase/ember-core/utils/get-model-name';
14
14
  import ensureLeafletPluginsReady, { hasLeafletPluginsReady } from '../../utils/leaflet-plugin-loader';
15
+ import { FIT_VEHICLES_MAX_ZOOM, isZoom, positionedBounds } from '../../utils/map-default-view';
15
16
 
16
17
  export default class MapLeafletLiveMapComponent extends Component {
17
18
  liveViewportReloadDebounceMs = 300;
@@ -243,9 +244,45 @@ export default class MapLeafletLiveMapComponent extends Component {
243
244
  this.rebuildMapContextMenu();
244
245
  }
245
246
 
247
+ /**
248
+ * When the company chose "Fit to my vehicles", frame every positioned
249
+ * vehicle before the first viewport-filtered load. With no positioned
250
+ * vehicles the map stays on the default view resolved by the location
251
+ * service.
252
+ *
253
+ * @return {Boolean} whether the view was fitted
254
+ */
255
+ @task *fitToPositionedVehicles() {
256
+ if (!this.mapSettings.fitMapToVehicles || this.abilities.cannot('fleet-ops list vehicle')) {
257
+ return false;
258
+ }
259
+
260
+ try {
261
+ const vehicles = yield this.fetch.get('fleet-ops/live/vehicles', { limit: this.liveViewportResourceLimit });
262
+ const bounds = positionedBounds(vehicles);
263
+ if (!bounds) {
264
+ return false;
265
+ }
266
+
267
+ const options = { maxZoom: FIT_VEHICLES_MAX_ZOOM, padding: [40, 40], animate: false };
268
+ if (typeof this.mapManager.fitBounds === 'function' && this.mapManager.adapter) {
269
+ this.mapManager.fitBounds(bounds, options);
270
+ } else {
271
+ this.map?.fitBounds?.(bounds, options);
272
+ }
273
+
274
+ return true;
275
+ } catch (err) {
276
+ debug('Failed to fit live map to vehicles: ' + err.message);
277
+ return false;
278
+ }
279
+ }
280
+
246
281
  /** load resources and wait for stuff here and trigger map ready **/
247
282
  @task *load() {
248
283
  try {
284
+ yield this.fitToPositionedVehicles.perform();
285
+
249
286
  // Get initial map bounds for spatial filtering (provider-agnostic)
250
287
  const mapBounds = this.mapManager.getBounds?.() ?? (this.map ? this.map.getBounds() : null);
251
288
  const bounds = this.#serializeBoundsForRequest(mapBounds);
@@ -360,6 +397,11 @@ export default class MapLeafletLiveMapComponent extends Component {
360
397
  if (typeof zoom === 'number' && !isNaN(zoom) && zoom >= 1 && zoom <= 20) {
361
398
  return zoom;
362
399
  }
400
+ // The company's default view (or a country centre) suggests its own zoom
401
+ const locationZoom = this.location.getZoom?.();
402
+ if (isZoom(locationZoom)) {
403
+ return locationZoom;
404
+ }
363
405
  // Return default zoom of 14 if invalid
364
406
  return 14;
365
407
  }
@@ -428,6 +470,9 @@ export default class MapLeafletLiveMapComponent extends Component {
428
470
  if (coordinates && typeof coordinates.latitude === 'number' && typeof coordinates.longitude === 'number') {
429
471
  this.latitude = coordinates.latitude;
430
472
  this.longitude = coordinates.longitude;
473
+ if (isZoom(coordinates.zoom) && !isZoom(this.args.zoom)) {
474
+ this.zoom = coordinates.zoom;
475
+ }
431
476
 
432
477
  // Update map position if map is loaded
433
478
  if (this.map && this.map.setView) {
@@ -860,36 +905,4 @@ export default class MapLeafletLiveMapComponent extends Component {
860
905
 
861
906
  return contextmenuRegistry;
862
907
  }
863
-
864
- /**
865
- * Safely gets a valid latitude value with fallback to default
866
- * @returns {number} Valid latitude value
867
- */
868
- #getValidLatitude() {
869
- const lat = this.location.getLatitude();
870
-
871
- // Validate latitude is a number and within valid range (-90 to 90)
872
- if (typeof lat === 'number' && !isNaN(lat) && lat >= -90 && lat <= 90) {
873
- return lat;
874
- }
875
-
876
- // Fallback to default Singapore latitude
877
- return 1.369;
878
- }
879
-
880
- /**
881
- * Safely gets a valid longitude value with fallback to default
882
- * @returns {number} Valid longitude value
883
- */
884
- #getValidLongitude() {
885
- const lng = this.location.getLongitude();
886
-
887
- // Validate longitude is a number and within valid range (-180 to 180)
888
- if (typeof lng === 'number' && !isNaN(lng) && lng >= -180 && lng <= 180) {
889
- return lng;
890
- }
891
-
892
- // Fallback to default Singapore longitude
893
- return 103.8864;
894
- }
895
908
  }
@@ -15,6 +15,7 @@ import { inject as service } from '@ember/service';
15
15
  *
16
16
  * @arg phases - Array of phase objects (managed externally)
17
17
  * @arg availableEngines - Array of { id, name } engine options
18
+ * @arg defaultEngine - Engine id selected in Orchestrator settings; new phases start with it
18
19
  * @arg onPhasesChange - Action(phases) — called when phases array changes
19
20
  * @arg onRunPhases - Action(phases) — called when "Run" is clicked
20
21
  */
@@ -49,7 +50,7 @@ export default class OrchestratorPhaseBuilderComponent extends Component {
49
50
  id: crypto.randomUUID(),
50
51
  mode,
51
52
  label: this.intl.t(`orchestrator.mode-${mode.replace(/_/g, '-')}`),
52
- engine: 'greedy',
53
+ engine: this.args.defaultEngine ?? 'greedy',
53
54
  allocationStrategy: 'route_aware',
54
55
  vehiclePacking: 'minimize_vehicles',
55
56
  orderStatuses: ['created'],
@@ -59,7 +59,13 @@
59
59
  {{! ── Phase builder panel (collapsible, below toolbar) ── }}
60
60
  {{#if this.showPhaseBuilder}}
61
61
  <div class="workbench-phase-panel border-b dark:border-gray-700 bg-white dark:bg-gray-900 flex-shrink-0 h-80">
62
- <Orchestrator::PhaseBuilder @phases={{this.phases}} @availableEngines={{this.availableEngines}} @onPhasesChange={{this.onPhasesChange}} @onRunPhases={{this.onRunPhases}} />
62
+ <Orchestrator::PhaseBuilder
63
+ @phases={{this.phases}}
64
+ @availableEngines={{this.availableEngines}}
65
+ @defaultEngine={{this.defaultEngine}}
66
+ @onPhasesChange={{this.onPhasesChange}}
67
+ @onRunPhases={{this.onRunPhases}}
68
+ />
63
69
  </div>
64
70
  {{/if}}
65
71
 
@@ -49,6 +49,8 @@ export default class OrchestratorWorkbenchComponent extends Component {
49
49
  @tracked availableVehicles = [];
50
50
  @tracked availableDrivers = [];
51
51
  @tracked availableEngines = [];
52
+ /** The engine selected in Orchestrator settings; new phases start with it. */
53
+ @tracked defaultEngine = null;
52
54
 
53
55
  // ── Plan state ────────────────────────────────────────────────────────────
54
56
 
@@ -204,6 +206,12 @@ export default class OrchestratorWorkbenchComponent extends Component {
204
206
  } catch {
205
207
  this.availableEngines = [{ id: 'greedy', name: 'Greedy (built-in)' }];
206
208
  }
209
+ try {
210
+ const settings = yield this.fetch.get('fleet-ops/settings/orchestrator-settings');
211
+ this.defaultEngine = settings?.allocation_engine ?? null;
212
+ } catch {
213
+ this.defaultEngine = null;
214
+ }
207
215
  }
208
216
 
209
217
  @task *loadCardFields() {
@@ -270,7 +278,8 @@ export default class OrchestratorWorkbenchComponent extends Component {
270
278
  mode: phase.mode,
271
279
  order_statuses: phase.orderStatuses ?? ['created'],
272
280
  options: {
273
- engine: phase.engine ?? 'greedy',
281
+ // Without an explicit engine the server runs the one selected in Orchestrator settings.
282
+ engine: phase.engine ?? null,
274
283
  allocation_strategy: phase.allocationStrategy ?? 'route_aware',
275
284
  vehicle_packing: phase.vehiclePacking ?? 'minimize_vehicles',
276
285
  balance_workload: phase.balanceWorkload ?? false,
@@ -311,6 +320,11 @@ export default class OrchestratorWorkbenchComponent extends Component {
311
320
  if (result.message) {
312
321
  this.orchestratorRunMessage = result.message;
313
322
  }
323
+ // The server substituted another engine (e.g. VROOM unavailable) — say so.
324
+ if (result.warning) {
325
+ this.orchestratorRunMessage = result.warning;
326
+ this.notifications.warning(result.warning, { autoClear: true, clearDuration: 10000 });
327
+ }
314
328
  // If the run returned zero assignments, surface the error/message to
315
329
  // the user immediately — otherwise the right panel stays blank with
316
330
  // no feedback (PlanViewer is only rendered when hasProposedPlan).
@@ -401,7 +415,7 @@ export default class OrchestratorWorkbenchComponent extends Component {
401
415
  id: 'legacy',
402
416
  mode: 'allocate',
403
417
  label: 'Allocate',
404
- engine: 'greedy',
418
+ engine: null,
405
419
  allocationStrategy: 'route_aware',
406
420
  orderStatuses: ['created'],
407
421
  balanceWorkload: false,
@@ -151,7 +151,7 @@
151
151
  <div class="flex items-center justify-end">
152
152
  <div class="flex-row space-x-1 pr-0.5">
153
153
  {{#if @resource.payload.pickup}}
154
- <a href="javascript:;" {{on "click" (fn this.editPlace @resource.payload.pickup)}}>
154
+ <a href="javascript:;" data-test-edit-place="pickup" {{on "click" (fn this.editPlace @resource.payload.pickup)}}>
155
155
  <FaIcon @icon="edit" />
156
156
  </a>
157
157
  <a href="javascript:;" {{on "click" (fn this.setPayloadPlace "pickup" null)}}>
@@ -206,7 +206,19 @@ export default class OrderFormRouteComponent extends Component {
206
206
  }
207
207
 
208
208
  @action editPlace(place) {
209
- this.placeActions.modal.edit(place);
209
+ // The place is edited in place, so the form already shows the change; there is no list
210
+ // to refresh, only the route preview and quote that depend on its location.
211
+ this.placeActions.modal.edit(
212
+ place,
213
+ {},
214
+ {
215
+ refresh: false,
216
+ callback: () => {
217
+ this.previewRoute();
218
+ this.requestServiceQuoteRefresh('route.place.updated');
219
+ },
220
+ }
221
+ );
210
222
  }
211
223
 
212
224
  @action async previewRoute() {
@@ -214,7 +214,7 @@
214
214
  <div class="dark:text-gray-100">
215
215
  <div class="grid grid-cols-7">
216
216
  <div class="flex flex-col items-start justify-start pt-3">
217
- <img src="/engines-dist/images/boxes/{{parcelFee.size}}.png" alt="parcel size {{parcelFee.size}}" width="44" class="w-11" />
217
+ <img src={{parcel-box-image parcelFee.size}} alt="parcel size {{parcelFee.size}}" width="44" class="w-11" />
218
218
  <div class="mt-2 text-sm font-medium">
219
219
  {{humanize parcelFee.size}}
220
220
  </div>
@@ -423,7 +423,7 @@
423
423
  <div class="dark:text-gray-100">
424
424
  <div class="grid grid-cols-7">
425
425
  <div class="flex flex-col items-start justify-start pt-3">
426
- <img src="/engines-dist/images/boxes/{{parcelFee.size}}.png" alt="parcel size {{parcelFee.size}}" width="44" class="w-11" />
426
+ <img src={{parcel-box-image parcelFee.size}} alt="parcel size {{parcelFee.size}}" width="44" class="w-11" />
427
427
  <label class="mt-2">{{humanize parcelFee.size}}</label>
428
428
  </div>
429
429
  <div class="col-span-6 dimensions-box space-x-2">