@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
@@ -13,12 +13,15 @@ use Fleetbase\FleetOps\Models\Payload;
13
13
  use Fleetbase\FleetOps\Models\Place;
14
14
  use Fleetbase\FleetOps\Models\Vehicle;
15
15
  use Fleetbase\FleetOps\Models\Vendor;
16
+ use Fleetbase\FleetOps\Models\Waypoint;
17
+ use Fleetbase\FleetOps\Orchestration\Contracts\OrchestrationEngineInterface;
16
18
  use Fleetbase\FleetOps\Orchestration\Engines\DriverAssignmentEngine;
17
19
  use Fleetbase\FleetOps\Orchestration\Engines\RouteSequencingEngine;
18
20
  use Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry;
21
+ use Fleetbase\FleetOps\Orchestration\Support\OrchestratorSettings;
22
+ use Fleetbase\FleetOps\Orchestration\Support\RouteMetrics;
19
23
  use Fleetbase\FleetOps\Traits\AuthorizesMethods;
20
24
  use Fleetbase\Http\Controllers\Controller;
21
- use Fleetbase\Models\Setting;
22
25
  use Illuminate\Http\JsonResponse;
23
26
  use Illuminate\Http\Request;
24
27
  use Illuminate\Support\Carbon;
@@ -42,6 +45,11 @@ class OrchestrationController extends Controller
42
45
  {
43
46
  use AuthorizesMethods;
44
47
 
48
+ /**
49
+ * Engines that assign orders to vehicles without producing a stop sequence.
50
+ */
51
+ protected const ASSIGNMENT_ONLY_ENGINES = ['greedy', 'capacity'];
52
+
45
53
  public function __construct(protected OrchestrationEngineRegistry $registry)
46
54
  {
47
55
  $this->authorizeMethods([
@@ -281,7 +289,7 @@ class OrchestrationController extends Controller
281
289
  // ── Run engine ────────────────────────────────────────────────────────
282
290
  $engineId = $mode === 'assign_drivers'
283
291
  ? 'driver_assignment'
284
- : ($request->input('options.engine') ?? $this->orchestratorEngineSetting());
292
+ : ($request->input('options.engine') ?: $this->orchestratorEngineSetting());
285
293
 
286
294
  try {
287
295
  if ($mode === 'assign_drivers') {
@@ -308,14 +316,13 @@ class OrchestrationController extends Controller
308
316
  }
309
317
  }
310
318
  }
311
- $engine = $this->routeSequencingEngine();
312
- $result = $engine->sequence($orders, $options);
319
+ $result = $this->optimizeRoutes($engineId, $orders, $options);
313
320
  } else {
314
- $engine = $this->registry->resolve($engineId);
315
- $result = $engine->allocate($orders, $vehicles, $options);
321
+ $result = $this->registry->allocateWithFallback($engineId, $orders, $vehicles, $options);
316
322
  }
317
323
  } catch (\RuntimeException $e) {
318
- // Engine is unavailable (e.g. VROOM not reachable).
324
+ // Engine is unavailable (e.g. VROOM not reachable) and there is no
325
+ // built-in engine to fall back to.
319
326
  // Return a structured JSON 503 so the frontend can display a
320
327
  // user-friendly message instead of an unhandled exception page.
321
328
  return response()->json([
@@ -325,6 +332,10 @@ class OrchestrationController extends Controller
325
332
  ], 503);
326
333
  }
327
334
 
335
+ if ($mode !== 'assign_drivers') {
336
+ $result = RouteMetrics::annotate($result, $orders, $vehicles);
337
+ }
338
+
328
339
  return response()->json($result);
329
340
  }
330
341
 
@@ -384,7 +395,71 @@ class OrchestrationController extends Controller
384
395
 
385
396
  protected function orchestratorEngineSetting(): string
386
397
  {
387
- return Setting::lookup('fleetops.orchestrator_engine', 'greedy');
398
+ return OrchestratorSettings::engineForCompany($this->companyUuid());
399
+ }
400
+
401
+ /**
402
+ * Re-sequence each vehicle's already-assigned orders.
403
+ *
404
+ * A solver engine (e.g. VROOM) is run once per vehicle, restricted to
405
+ * that vehicle and its orders, so the stop order, arrival times and
406
+ * distances come from the selected engine. Engines that only assign
407
+ * orders (greedy, capacity) cannot sequence stops, and a solver that is
408
+ * missing or unavailable is replaced; in both cases the built-in route
409
+ * sequencer runs and the result says which engine ran and why.
410
+ */
411
+ protected function optimizeRoutes(string $engineId, $orders, array $options): array
412
+ {
413
+ $warn = true;
414
+ if (in_array($engineId, self::ASSIGNMENT_ONLY_ENGINES, true)) {
415
+ $warn = false;
416
+ $reason = sprintf('The "%s" engine assigns orders but does not sequence stops; stops were sequenced by the built-in route sequencer.', $engineId);
417
+ } elseif (!$this->registry->has($engineId)) {
418
+ $reason = sprintf('No orchestration engine is registered as "%s".', $engineId);
419
+ } else {
420
+ try {
421
+ return $this->sequenceWithEngine($this->registry->resolve($engineId), $orders, $options);
422
+ } catch (\RuntimeException $e) {
423
+ $reason = $e->getMessage();
424
+ }
425
+ }
426
+
427
+ $result = $this->routeSequencingEngine()->sequence($orders, $options);
428
+
429
+ return OrchestrationEngineRegistry::markFallback($result, $result['summary']['engine'] ?? 'route_sequencing', $engineId, $reason, $warn);
430
+ }
431
+
432
+ /**
433
+ * Run a solver engine once per vehicle group, keeping every order on its vehicle.
434
+ */
435
+ protected function sequenceWithEngine(OrchestrationEngineInterface $engine, $orders, array $options): array
436
+ {
437
+ $options = array_merge($options, ['allocation_strategy' => 'route_aware']);
438
+ $assignments = [];
439
+ $unassigned = [];
440
+
441
+ foreach ($orders->groupBy(fn ($order) => $order->vehicle_assigned_uuid ?? '') as $vehicleUuid => $vehicleOrders) {
442
+ $vehicle = $vehicleUuid !== '' && $vehicleOrders->first()->relationLoaded('vehicle') ? $vehicleOrders->first()->vehicle : null;
443
+ if (!$vehicle) {
444
+ // Without an assigned vehicle there is no route to sequence.
445
+ array_push($unassigned, ...$vehicleOrders->pluck('public_id')->all());
446
+ continue;
447
+ }
448
+
449
+ $result = $engine->allocate($vehicleOrders->values(), collect([$vehicle]), $options);
450
+ array_push($assignments, ...($result['assignments'] ?? []));
451
+ array_push($unassigned, ...($result['unassigned'] ?? []));
452
+ }
453
+
454
+ return [
455
+ 'assignments' => $assignments,
456
+ 'unassigned' => array_values(array_unique($unassigned)),
457
+ 'summary' => [
458
+ 'engine' => $engine->getIdentifier(),
459
+ 'assigned' => count($assignments),
460
+ 'unassigned' => count(array_unique($unassigned)),
461
+ ],
462
+ ];
388
463
  }
389
464
 
390
465
  protected function driverAssignmentEngine(): DriverAssignmentEngine
@@ -415,9 +490,10 @@ class OrchestrationController extends Controller
415
490
  return response()->json(['error' => 'No assignments provided.'], 422);
416
491
  }
417
492
 
418
- $committed = [];
419
- $failed = [];
420
- $manifests = [];
493
+ $committed = [];
494
+ $failed = [];
495
+ $manifests = [];
496
+ $resequenced = false;
421
497
 
422
498
  $this->beginOrchestrationTransaction();
423
499
  try {
@@ -502,6 +578,7 @@ class OrchestrationController extends Controller
502
578
  if (!empty($assignment['waypoint_sequence']) && $order->payload) {
503
579
  foreach ($assignment['waypoint_sequence'] as $seq => $waypointId) {
504
580
  $this->updateWaypointSequence($order->payload_uuid, $waypointId, $seq);
581
+ $resequenced = true;
505
582
  }
506
583
  }
507
584
 
@@ -523,6 +600,11 @@ class OrchestrationController extends Controller
523
600
  return response()->json(['error' => 'Commit failed: ' . $e->getMessage()], 500);
524
601
  }
525
602
 
603
+ // Waypoint sequences are written without model events
604
+ if ($resequenced) {
605
+ Waypoint::invalidateApiCacheManually($companyUuid);
606
+ }
607
+
526
608
  return response()->json([
527
609
  'committed' => $committed,
528
610
  'failed' => $failed,
@@ -10,6 +10,7 @@ use Fleetbase\FleetOps\Events\OrderDispatchFailed;
10
10
  use Fleetbase\FleetOps\Events\OrderStarted;
11
11
  use Fleetbase\FleetOps\Events\WaypointActivityChanged;
12
12
  use Fleetbase\FleetOps\Events\WaypointCompleted;
13
+ use Fleetbase\FleetOps\Exceptions\OrderEditLockedException;
13
14
  use Fleetbase\FleetOps\Exports\OrderExport;
14
15
  use Fleetbase\FleetOps\Flow\Activity;
15
16
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
@@ -19,6 +20,7 @@ use Fleetbase\FleetOps\Http\Requests\Internal\CreateOrderRequest;
19
20
  use Fleetbase\FleetOps\Http\Resources\v1\Index\Order as OrderIndexResource;
20
21
  use Fleetbase\FleetOps\Http\Resources\v1\Order as OrderResource;
21
22
  use Fleetbase\FleetOps\Http\Resources\v1\Proof as ProofResource;
23
+ use Fleetbase\FleetOps\Http\Resources\v1\PublicOrderTracking;
22
24
  use Fleetbase\FleetOps\Imports\OrdersImport;
23
25
  use Fleetbase\FleetOps\Jobs\FinalizeInternalOrderCreation;
24
26
  use Fleetbase\FleetOps\Jobs\NotifyBulkAssignedDriver;
@@ -33,6 +35,7 @@ use Fleetbase\FleetOps\Models\ServiceQuote;
33
35
  use Fleetbase\FleetOps\Models\TrackingStatus;
34
36
  use Fleetbase\FleetOps\Models\Waypoint;
35
37
  use Fleetbase\FleetOps\Notifications\OrderPing;
38
+ use Fleetbase\FleetOps\Support\OrderEdit;
36
39
  use Fleetbase\FleetOps\Support\ResolvesOrderServiceStops;
37
40
  use Fleetbase\FleetOps\Support\Utils;
38
41
  use Fleetbase\Http\Requests\ExportRequest;
@@ -54,6 +57,12 @@ use Maatwebsite\Excel\Facades\Excel;
54
57
  class OrderController extends FleetOpsController
55
58
  {
56
59
  use ResolvesOrderServiceStops;
60
+
61
+ /**
62
+ * The one answer the public tracking lookup gives for any number it cannot resolve.
63
+ */
64
+ public const TRACKING_LOOKUP_ERROR = 'No order found using tracking number provided.';
65
+
57
66
  /**
58
67
  * Permissions for methods AuthorizationGuard cannot map to a schema action (see FleetOpsController).
59
68
  *
@@ -163,8 +172,15 @@ class OrderController extends FleetOpsController
163
172
  $input['type'] = 'transport';
164
173
  }
165
174
 
175
+ // a configured lifecycle always starts at its initial activity; otherwise
166
176
  // if no status is set its default to `created`
167
- if (!isset($input['status'])) {
177
+ if ($resolvedOrderConfig && $resolvedOrderConfig->hasConfiguredLifecycle()) {
178
+ $input['status'] = $resolvedOrderConfig->getInitialStatusCode();
179
+ if (!$resolvedOrderConfig->allowsDispatch()) {
180
+ $input['dispatched'] = false;
181
+ $input['adhoc'] = false;
182
+ }
183
+ } elseif (!isset($input['status'])) {
168
184
  $input['status'] = 'created';
169
185
  }
170
186
 
@@ -185,11 +201,13 @@ class OrderController extends FleetOpsController
185
201
  $entities = Utils::get($input, 'payload.entities');
186
202
  $uploads = Utils::get($input, 'files', []);
187
203
  $customFieldValues = Utils::get($input, 'custom_field_values', []);
204
+ $orderConfig = $order->config();
205
+ $hasLifecycle = $orderConfig && $orderConfig->hasConfiguredLifecycle();
188
206
 
189
207
  // save order route & payload with request input
190
208
  $order
191
209
  ->setRoute($route)
192
- ->setStatus('created', false)
210
+ ->setStatus($hasLifecycle ? $orderConfig->getInitialStatusCode() : 'created', false)
193
211
  ->insertPayload($payload)
194
212
  ->insertWaypoints($waypoints)
195
213
  ->insertEntities($entities);
@@ -212,8 +230,12 @@ class OrderController extends FleetOpsController
212
230
  );
213
231
  }
214
232
 
215
- // Check dispatch flag with backward compatibility (default true)
233
+ // Check dispatch flag with backward compatibility (default true).
234
+ // Configs with a lifecycle that disables dispatch are never dispatched.
216
235
  $shouldDispatch = isset($input['dispatched']) ? (bool) $input['dispatched'] : true;
236
+ if ($hasLifecycle && !$orderConfig->allowsDispatch()) {
237
+ $shouldDispatch = false;
238
+ }
217
239
 
218
240
  // dispatch if flagged true, otherwise ensure order stays in created state
219
241
  if ($shouldDispatch) {
@@ -300,6 +322,19 @@ class OrderController extends FleetOpsController
300
322
  return $this->errorResponse('Unable to find order to update route for.');
301
323
  }
302
324
 
325
+ // Refuse a route edit the order's status locks (completed, canceled, expired)
326
+ $edit = $this->beginOrderEdit($order);
327
+ try {
328
+ $edit->guard($edit->requestedChanges(array_filter([
329
+ 'pickup' => $hasPickupInput ? true : null,
330
+ 'dropoff' => $hasDropoffInput ? true : null,
331
+ 'return' => $hasReturnInput ? true : null,
332
+ 'waypoints' => ($hasWaypointsInput || $hasPickupInput || $hasDropoffInput || $hasReturnInput) ? true : null,
333
+ ])));
334
+ } catch (OrderEditLockedException $exception) {
335
+ return $this->errorResponse($exception->getMessage(), 422, $exception->toResponseData());
336
+ }
337
+
303
338
  if ($hasPickupInput) {
304
339
  if ($pickup) {
305
340
  $order->payload->setPickup($pickup, ['save' => true]);
@@ -334,14 +369,9 @@ class OrderController extends FleetOpsController
334
369
  $order->payload->removeWaypoints();
335
370
  }
336
371
 
337
- $startingDestination = $order->payload->getPickupOrFirstWaypoint();
338
- if (!$startingDestination) {
339
- $startingDestination = $order->payload->getDropoffOrLastWaypoint();
340
- }
341
-
342
- if ($startingDestination instanceof Place || $startingDestination instanceof Waypoint) {
343
- $order->payload->setCurrentWaypoint($startingDestination);
344
- }
372
+ // Only a changed route moves the current waypoint, and an order under way keeps
373
+ // the driver's stop; distance, time, tracking and `order.changed` follow the change
374
+ $this->commitOrderEdit($edit);
345
375
 
346
376
  $order->load(['payload.pickup', 'payload.dropoff', 'payload.return', 'payload.waypoints']);
347
377
 
@@ -483,7 +513,7 @@ class OrderController extends FleetOpsController
483
513
  $successful = [];
484
514
 
485
515
  foreach ($orders as $order) {
486
- if ($order->status !== 'created') {
516
+ if ($order->status !== 'created' || $order->config()?->allowsDispatch() === false) {
487
517
  $failed[] = $order->uuid;
488
518
  continue;
489
519
  }
@@ -582,7 +612,12 @@ class OrderController extends FleetOpsController
582
612
  return $this->errorResponse('No order found to cancel.');
583
613
  }
584
614
 
585
- $order->cancel();
615
+ $reason = $request->input('reason');
616
+ if (is_string($reason) && trim($reason) !== '') {
617
+ $order->cancelWithReason($reason);
618
+ } else {
619
+ $order->cancel();
620
+ }
586
621
 
587
622
  return $this->jsonResponse(
588
623
  [
@@ -615,6 +650,10 @@ class OrderController extends FleetOpsController
615
650
  return $this->errorResponse('No order config found for dispatch.');
616
651
  }
617
652
 
653
+ if (!$order->config()->allowsDispatch()) {
654
+ return $this->errorResponse('Orders of this type are not dispatched.');
655
+ }
656
+
618
657
  if (!$order->hasDriverAssigned && !$order->adhoc) {
619
658
  return $this->errorResponse('No driver assigned to dispatch!');
620
659
  }
@@ -748,10 +787,21 @@ class OrderController extends FleetOpsController
748
787
 
749
788
  protected function assignDriverToOrders($orderUuids, Driver $driver): void
750
789
  {
790
+ // Drivers being replaced are told once the assignment is written
791
+ $replaced = $this->scopedToCompany(Order::whereIn('uuid', $orderUuids))
792
+ ->whereNotNull('driver_assigned_uuid')
793
+ ->where('driver_assigned_uuid', '!=', $driver->uuid)
794
+ ->get();
795
+
751
796
  $this->scopedToCompany(Order::whereIn('uuid', $orderUuids))->update([
752
797
  'driver_assigned_uuid' => $driver->uuid,
753
798
  'updated_at' => now(),
754
799
  ]);
800
+ Order::invalidateApiCacheManually(session('company'));
801
+
802
+ foreach ($replaced as $order) {
803
+ $order->notifyDriverUnassigned($order->driver_assigned_uuid);
804
+ }
755
805
  }
756
806
 
757
807
  protected function dispatchBulkAssignedDriverNotification(array $orderUuids, Driver $driver): void
@@ -769,9 +819,19 @@ class OrderController extends FleetOpsController
769
819
  return response()->json($payload);
770
820
  }
771
821
 
772
- protected function errorResponse(string $message, int $status = 400)
822
+ protected function errorResponse(string $message, int $status = 400, array $data = [])
823
+ {
824
+ return response()->error($message, $status, $data);
825
+ }
826
+
827
+ protected function beginOrderEdit(Order $order): OrderEdit
773
828
  {
774
- return response()->error($message, $status);
829
+ return OrderEdit::begin($order);
830
+ }
831
+
832
+ protected function commitOrderEdit(OrderEdit $edit): array
833
+ {
834
+ return $edit->commit();
775
835
  }
776
836
 
777
837
  /**
@@ -900,6 +960,19 @@ class OrderController extends FleetOpsController
900
960
  $activity = $request->array('activity');
901
961
  $activity = new Activity($activity, $order->getConfigFlow());
902
962
 
963
+ // Configs with strict transitions only accept a configured child of the current
964
+ // activity, and the server-side activity definition replaces the client payload.
965
+ $orderConfig = $order->config();
966
+ if ($orderConfig && $orderConfig->hasStrictTransitions()) {
967
+ $targetCode = $activity->get('code');
968
+ $configured = $targetCode ? $orderConfig->getActivityByCode($targetCode) : null;
969
+ if (!$configured || !$orderConfig->canTransition($order->status, $targetCode)) {
970
+ return response()->error('This activity is not a permitted next step for the order.', 422);
971
+ }
972
+
973
+ $activity = $configured;
974
+ }
975
+
903
976
  $requiresProof = Utils::isActivity($activity)
904
977
  && ($activity->get('require_pod') || ($activity->completesOrder() && $order->pod_required));
905
978
  if ($requiresProof && !$proof && !$bypassProof) {
@@ -1875,27 +1948,43 @@ class OrderController extends FleetOpsController
1875
1948
  return response()->json(OrderConfig::default());
1876
1949
  }
1877
1950
 
1951
+ /**
1952
+ * Look up an order for the public Track Order page.
1953
+ *
1954
+ * Answers with `PublicOrderTracking`, never the full order resource: the
1955
+ * tracking number is the only credential here. A missing, malformed or
1956
+ * unknown tracking number all get the same error, so a caller cannot tell
1957
+ * which one they hit.
1958
+ */
1878
1959
  public function lookup(Request $request)
1879
1960
  {
1880
- $trackingNumber = $request->input('tracking');
1881
- if (!$trackingNumber) {
1882
- return response()->error('No tracking number provided for lookup.');
1883
- }
1884
-
1885
- $order = $this->findOrderByTrackingNumber($trackingNumber);
1961
+ $trackingNumber = $this->normalizeTrackingLookup($request->input('tracking'));
1962
+ $order = $trackingNumber ? $this->findOrderByTrackingNumber($trackingNumber) : null;
1886
1963
 
1887
1964
  if (!$order) {
1888
- return response()->error('No order found using tracking number provided.');
1965
+ return response()->error(static::TRACKING_LOOKUP_ERROR);
1889
1966
  }
1890
1967
 
1891
- // load required relations
1892
- $order->loadMissing(['trackingNumber', 'payload', 'trackingStatuses']);
1893
-
1894
- // load tracker data
1968
+ $order->loadMissing(['trackingNumber', 'trackingStatuses', 'payload.pickup', 'payload.dropoff', 'payload.waypoints', 'payload.entities']);
1895
1969
  $order->tracker_data = $order->tracker()->toArray();
1896
- $order->eta = $order->tracker()->eta();
1897
1970
 
1898
- return new OrderResource($order);
1971
+ return new PublicOrderTracking($order);
1972
+ }
1973
+
1974
+ /**
1975
+ * The tracking number to look up, or null when the input cannot be one:
1976
+ * not a string, blank, too long, or outside the characters tracking
1977
+ * numbers use.
1978
+ */
1979
+ protected function normalizeTrackingLookup($input): ?string
1980
+ {
1981
+ if (!is_string($input)) {
1982
+ return null;
1983
+ }
1984
+
1985
+ $input = trim($input);
1986
+
1987
+ return preg_match('/^[A-Za-z0-9][A-Za-z0-9._\-]{0,99}$/', $input) === 1 ? $input : null;
1899
1988
  }
1900
1989
 
1901
1990
  /**
@@ -1952,6 +2041,7 @@ class OrderController extends FleetOpsController
1952
2041
  }
1953
2042
 
1954
2043
  $order->saveQuietly();
2044
+ $order->invalidateApiCache();
1955
2045
 
1956
2046
  return response()->json([
1957
2047
  'status' => 'OK',
@@ -6,6 +6,10 @@ use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
6
6
  use Fleetbase\FleetOps\Jobs\ReplayPositions;
7
7
  use Fleetbase\FleetOps\Models\Position;
8
8
  use Fleetbase\FleetOps\Support\Utils;
9
+ use Fleetbase\Models\User;
10
+ use Fleetbase\Support\SocketCluster\ChannelAuthorizer;
11
+ use Fleetbase\Support\SocketCluster\SocketPrincipal;
12
+ use Fleetbase\Support\SocketCluster\SocketToken;
9
13
  use Illuminate\Http\Request;
10
14
 
11
15
  class PositionController extends FleetOpsController
@@ -37,6 +41,11 @@ class PositionController extends FleetOpsController
37
41
  return response()->error('Position IDs are required');
38
42
  }
39
43
 
44
+ // With socket authentication on, the replay may only publish to a channel the user could subscribe to.
45
+ if (SocketToken::enabled() && !$this->canReplayTo($request, $channelId)) {
46
+ return response()->error('You are not allowed to replay positions to this channel.', 403);
47
+ }
48
+
40
49
  $positions = Position::whereIn('uuid', $positionIds)
41
50
  ->where('company_uuid', session('company'))
42
51
  ->orderBy('created_at')
@@ -57,6 +66,19 @@ class PositionController extends FleetOpsController
57
66
  ]);
58
67
  }
59
68
 
69
+ /**
70
+ * Whether the requesting user may subscribe to, and so receive a replay on, the channel.
71
+ */
72
+ protected function canReplayTo(Request $request, mixed $channelId): bool
73
+ {
74
+ $user = $request->user();
75
+ if (!$user instanceof User || !is_string($channelId) || $channelId === '' || strlen($channelId) > 255 || preg_match('/\s/', $channelId)) {
76
+ return false;
77
+ }
78
+
79
+ return app(ChannelAuthorizer::class)->authorize(SocketPrincipal::forUser($user, session('company')), $channelId)->allow;
80
+ }
81
+
60
82
  /**
61
83
  * Get position statistics/metrics.
62
84
  *
@@ -21,6 +21,53 @@ class ProofController extends FleetOpsController
21
21
  */
22
22
  public $resource = 'proof';
23
23
 
24
+ /**
25
+ * The most a dashboard can ask for in one request.
26
+ */
27
+ public const RECENT_LIMIT_MAX = 24;
28
+
29
+ /**
30
+ * The latest photo proofs captured for the company's orders, newest first,
31
+ * for the dashboard's proof-of-delivery tile.
32
+ */
33
+ public function recent(Request $request)
34
+ {
35
+ $limit = max(1, min(static::RECENT_LIMIT_MAX, (int) $request->input('limit', 12)));
36
+ $proofs = Proof::where('company_uuid', session('company'))
37
+ ->whereNotNull('order_uuid')
38
+ ->whereHas('file', fn ($query) => $query->where('type', 'photo'))
39
+ ->with(['file', 'order.customer'])
40
+ ->orderByDesc('created_at')
41
+ ->limit($limit)
42
+ ->get();
43
+
44
+ return $this->jsonResponse([
45
+ 'proofs' => $proofs->map(fn (Proof $proof) => $this->recentProofPayload($proof))->values()->all(),
46
+ ]);
47
+ }
48
+
49
+ protected function recentProofPayload(Proof $proof): array
50
+ {
51
+ $order = $proof->order;
52
+
53
+ return [
54
+ 'id' => $proof->public_id,
55
+ 'order_id' => $order?->public_id,
56
+ 'customer_name' => data_get($order, 'customer.name'),
57
+ 'remarks' => $proof->remarks,
58
+ 'url' => $this->photoUrl($proof->file),
59
+ 'captured_at' => $proof->created_at?->toIso8601String(),
60
+ ];
61
+ }
62
+
63
+ /**
64
+ * File::url signs the object on s3/gcs, so a private bucket still serves the photo.
65
+ */
66
+ protected function photoUrl(File $file): ?string
67
+ {
68
+ return $file->url;
69
+ }
70
+
24
71
  /**
25
72
  * Verify a QR code.
26
73
  *
@@ -83,8 +130,8 @@ class ProofController extends FleetOpsController
83
130
  // set the signature storage path
84
131
  $path = $this->signatureStoragePath($proof);
85
132
 
86
- // upload signature
87
- $this->storeSignature($path, base64_decode($signature), 'public');
133
+ // upload signature (private object; File::url serves it through a signed URL)
134
+ $this->storeSignature($path, base64_decode($signature));
88
135
 
89
136
  // create file record for upload
90
137
  $file = $this->createSignatureFile($path, $signature, $proof);
@@ -121,9 +168,13 @@ class ProofController extends FleetOpsController
121
168
  return Proof::create($attributes);
122
169
  }
123
170
 
124
- protected function storeSignature(string $path, string|false $contents, string $visibility): void
171
+ /**
172
+ * Writes without an ACL: the media bucket enforces bucket-owner object ownership, so a
173
+ * 'public' visibility (public-read ACL) makes S3 reject the PUT and put() return false.
174
+ */
175
+ protected function storeSignature(string $path, string|false $contents): void
125
176
  {
126
- Storage::disk('s3')->put($path, $contents, $visibility);
177
+ Storage::disk('s3')->put($path, $contents);
127
178
  }
128
179
 
129
180
  /**