@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
@@ -8,8 +8,11 @@ use Fleetbase\FleetOps\Events\OrderCanceled;
8
8
  use Fleetbase\FleetOps\Events\OrderCompleted;
9
9
  use Fleetbase\FleetOps\Events\OrderDispatched;
10
10
  use Fleetbase\FleetOps\Events\OrderDriverAssigned;
11
+ use Fleetbase\FleetOps\Events\OrderDriverUnassigned;
11
12
  use Fleetbase\FleetOps\Flow\Activity;
13
+ use Fleetbase\FleetOps\Notifications\OrderUnassigned;
12
14
  use Fleetbase\FleetOps\Support\LabelPdf;
15
+ use Fleetbase\FleetOps\Support\OrderEditPolicy;
13
16
  use Fleetbase\FleetOps\Support\OrderTracker;
14
17
  use Fleetbase\FleetOps\Support\Utils;
15
18
  use Fleetbase\FleetOps\Traits\HasTrackingNumber;
@@ -35,6 +38,7 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
35
38
  use Illuminate\Database\Eloquent\Relations\Relation;
36
39
  use Illuminate\Support\Carbon;
37
40
  use Illuminate\Support\Collection;
41
+ use Illuminate\Support\Facades\Log;
38
42
  use Illuminate\Support\Str;
39
43
  use Spatie\Activitylog\LogOptions;
40
44
  use Spatie\Activitylog\Traits\LogsActivity;
@@ -196,6 +200,38 @@ class Order extends Model
196
200
  */
197
201
  protected $with = [];
198
202
 
203
+ /**
204
+ * Relation paths and tables the cached order lists embed, so a write to any
205
+ * of them invalidates those lists (fleetbase/core-api#301).
206
+ *
207
+ * They mirror OrderFilter::queryForInternal()'s eager loads and whereHas
208
+ * membership, the payload relation's own `with`, Payload's `$with` and the
209
+ * public query's loads. customer and facilitator are morphTo relations, so
210
+ * their tables (contacts, vendors, integrated_vendors) are named instead;
211
+ * a vendor loads its place, which payload.pickup already covers. files
212
+ * (customer photo, vendor logo) is left out: every upload writes it, and a
213
+ * new photo is set by writing the contact's or vendor's own row.
214
+ *
215
+ * @var string[]
216
+ */
217
+ protected $apiCacheDependencies = [
218
+ 'payload',
219
+ 'payload.pickup',
220
+ 'payload.dropoff',
221
+ 'payload.return',
222
+ 'payload.entities',
223
+ 'payload.waypoints',
224
+ 'payload.firstWaypointMarker.place',
225
+ 'payload.lastWaypointMarker.place',
226
+ 'trackingNumber',
227
+ 'trackingStatuses',
228
+ 'driverAssigned',
229
+ 'vehicleAssigned',
230
+ 'contacts',
231
+ 'vendors',
232
+ 'integrated_vendors',
233
+ ];
234
+
199
235
  /**
200
236
  * The attributes that should be cast to native types.
201
237
  *
@@ -244,6 +280,16 @@ class Order extends Model
244
280
  '_key',
245
281
  ];
246
282
 
283
+ /**
284
+ * Set for one save: the driver assignment being saved sends no assignment notification.
285
+ */
286
+ public bool $silentDriverAssignment = false;
287
+
288
+ /**
289
+ * Set for one save: the driver taken off the order is not sent an unassigned push (they declined it themselves).
290
+ */
291
+ public bool $quietDriverUnassignment = false;
292
+
247
293
  /**
248
294
  * Enforce a morph map.
249
295
  */
@@ -263,6 +309,31 @@ class Order extends Model
263
309
  );
264
310
  }
265
311
 
312
+ /**
313
+ * Bulk removal deletes the matched orders one at a time instead of with a single
314
+ * query, so the "deleted" event fires for each and OrderObserver cascades the soft
315
+ * delete to the order's payload, entities and tracking data. Matching, the tenant
316
+ * constraint (GHSA-3wj9-hh56-7fw7) and the returned count follow core-api's version.
317
+ */
318
+ public function bulkRemove($ids = [])
319
+ {
320
+ $orders = static::where(function ($query) use ($ids) {
321
+ $query->whereIn($this->getQualifiedKeyName(), $ids)->orWhereIn($this->getQualifiedPublicId(), $ids);
322
+ });
323
+
324
+ // Tenant constraint: never delete another company's orders.
325
+ $companyUuid = session('company');
326
+ if ($companyUuid) {
327
+ $orders->where($this->qualifyColumn('company_uuid'), $companyUuid);
328
+ }
329
+
330
+ $orders = $orders->get();
331
+ $orders->each->delete();
332
+ $this->invalidateApiCacheOnChange();
333
+
334
+ return $orders->count();
335
+ }
336
+
266
337
  /**
267
338
  * Get the activity log options for the model.
268
339
  */
@@ -1034,6 +1105,7 @@ class Order extends Model
1034
1105
  $route->save();
1035
1106
 
1036
1107
  $this->updateQuietly(['route_uuid' => $route->uuid]);
1108
+ $this->invalidateApiCache();
1037
1109
 
1038
1110
  return $this;
1039
1111
  }
@@ -1169,6 +1241,7 @@ class Order extends Model
1169
1241
  ]);
1170
1242
 
1171
1243
  if ($attached) {
1244
+ $this->invalidateApiCache();
1172
1245
  $this->setRelation('purchaseRate', $purchaseRate);
1173
1246
 
1174
1247
  if ($purchaseRate->transaction_uuid) {
@@ -1231,6 +1304,7 @@ class Order extends Model
1231
1304
  ]);
1232
1305
 
1233
1306
  $this->updateQuietly(['transaction_uuid' => $transaction->uuid]);
1307
+ $this->invalidateApiCache();
1234
1308
  } catch (\Throwable $e) {
1235
1309
  }
1236
1310
 
@@ -1270,6 +1344,7 @@ class Order extends Model
1270
1344
  if ($save) {
1271
1345
  $this->saveQuietly();
1272
1346
  $this->flushAttributesCache();
1347
+ $this->invalidateApiCache();
1273
1348
  }
1274
1349
 
1275
1350
  dispatch(fn () => event(new OrderDispatched($this)))->afterCommit();
@@ -1308,6 +1383,29 @@ class Order extends Model
1308
1383
  return $this;
1309
1384
  }
1310
1385
 
1386
+ /**
1387
+ * Determine whether the order is still waiting to be dispatched.
1388
+ *
1389
+ * A deferred dispatch, such as the queued finalize job for orders created with
1390
+ * `dispatch: true`, can run after the order was already dispatched or started
1391
+ * through another path. Dispatching it then would move the status back to
1392
+ * "dispatched", so only an order that still sits at its initial status with no
1393
+ * dispatch or start recorded qualifies.
1394
+ */
1395
+ public function isAwaitingDispatch(): bool
1396
+ {
1397
+ if ($this->dispatched || $this->dispatched_at || $this->started || $this->started_at) {
1398
+ return false;
1399
+ }
1400
+
1401
+ $status = $this->status ?? 'created';
1402
+ if ($status !== 'created' && $status !== $this->config()?->getInitialStatusCode()) {
1403
+ return false;
1404
+ }
1405
+
1406
+ return !$this->hasDispatchedStatus();
1407
+ }
1408
+
1311
1409
  /**
1312
1410
  * Dispatches the order only if it hasn't been dispatched yet.
1313
1411
  * A check is performed before dispatching the order.
@@ -1346,10 +1444,33 @@ class Order extends Model
1346
1444
  */
1347
1445
  public function cancel()
1348
1446
  {
1349
- $this->status = 'canceled';
1447
+ return $this->cancelWithReason(null);
1448
+ }
1350
1449
 
1450
+ /**
1451
+ * Cancels the order, recording why.
1452
+ *
1453
+ * The reason becomes the details of the canceled tracking status and is carried on
1454
+ * the OrderCanceled event's activity, which the canceled notifications read.
1455
+ *
1456
+ * @return mixed the result of the OrderCanceled event
1457
+ */
1458
+ public function cancelWithReason(?string $reason = null)
1459
+ {
1460
+ // The canceled activity comes from the order config, which may define its own code.
1351
1461
  $this->loadMissing('orderConfig');
1352
- $canceledActivity = $this->orderConfig->getCanceledActivity();
1462
+ $orderConfig = $this->orderConfig ?? $this->config();
1463
+ $canceledActivity = $orderConfig->getCanceledActivity();
1464
+
1465
+ // A reason given for the cancellation becomes the canceled status details, which
1466
+ // the canceled notifications read. The config's activity itself is left untouched.
1467
+ $reason = is_string($reason) ? trim($reason) : null;
1468
+ $reasonActivity = null;
1469
+ if ($reason !== null && $reason !== '' && $canceledActivity instanceof Activity) {
1470
+ $canceledActivity = $reasonActivity = (clone $canceledActivity)->set('details', $reason);
1471
+ }
1472
+
1473
+ $this->status = $canceledActivity->code ?? 'canceled';
1353
1474
  $this->updateActivity($canceledActivity);
1354
1475
 
1355
1476
  if ($this->isIntegratedVendorOrder()) {
@@ -1360,7 +1481,13 @@ class Order extends Model
1360
1481
  }
1361
1482
  }
1362
1483
 
1363
- return dispatch(fn () => event(new OrderCanceled($this)))->afterCommit();
1484
+ // The event carries the activity when a reason was given, so the canceled
1485
+ // notifications (which read the activity details) include it.
1486
+ return dispatch(function () use ($reasonActivity) {
1487
+ $event = new OrderCanceled($this);
1488
+ $event->activity = $reasonActivity;
1489
+ event($event);
1490
+ })->afterCommit();
1364
1491
  }
1365
1492
 
1366
1493
  /**
@@ -1376,6 +1503,52 @@ class Order extends Model
1376
1503
  }
1377
1504
  }
1378
1505
 
1506
+ /**
1507
+ * Tell a driver the order was taken off them: broadcasts `order.driver_unassigned`
1508
+ * on their channels and pushes `{ type: "order_unassigned", id }` to their devices.
1509
+ * The order is cleared as the driver's current job.
1510
+ *
1511
+ * @param Driver|string|null $driver the driver (or driver uuid) removed from the order
1512
+ */
1513
+ public function notifyDriverUnassigned($driver): bool
1514
+ {
1515
+ if (is_string($driver)) {
1516
+ $driver = Driver::where('uuid', $driver)->withoutGlobalScopes()->first();
1517
+ }
1518
+
1519
+ if (!$driver instanceof Driver) {
1520
+ return false;
1521
+ }
1522
+
1523
+ if ($driver->current_job_uuid && $driver->current_job_uuid === $this->uuid) {
1524
+ $driver->forceFill(['current_job_uuid' => null])->saveQuietly();
1525
+ }
1526
+
1527
+ try {
1528
+ event(new OrderDriverUnassigned($this, $driver));
1529
+ } catch (\Throwable $exception) {
1530
+ Log::warning('Unable to broadcast order.driver_unassigned', ['order' => $this->public_id, 'error' => $exception->getMessage()]);
1531
+ }
1532
+
1533
+ if (!$this->quietDriverUnassignment) {
1534
+ try {
1535
+ $driver->notify(new OrderUnassigned($this));
1536
+ } catch (\Throwable $exception) {
1537
+ Log::warning('Unable to notify driver of unassignment', ['order' => $this->public_id, 'error' => $exception->getMessage()]);
1538
+ }
1539
+ }
1540
+
1541
+ return true;
1542
+ }
1543
+
1544
+ /**
1545
+ * The edit policy for the order at its current status.
1546
+ */
1547
+ public function editPolicy(): OrderEditPolicy
1548
+ {
1549
+ return OrderEditPolicy::for($this);
1550
+ }
1551
+
1379
1552
  /**
1380
1553
  * Updates the activity of the order.
1381
1554
  * Updates the order status and inserts a new activity based on the provided activity details.
@@ -1633,7 +1806,7 @@ class Order extends Model
1633
1806
  return $this;
1634
1807
  }
1635
1808
 
1636
- return $this->assignDriver($driver);
1809
+ return $this->assignDriver($driver, $silent);
1637
1810
  }
1638
1811
 
1639
1812
  throw new \Exception('Invalid driver provided for assignment!');
@@ -1646,12 +1819,16 @@ class Order extends Model
1646
1819
  $this->setRelation('driverAssigned', $driver);
1647
1820
  }
1648
1821
 
1649
- if (!$silent) {
1650
- $this->notifyDriverAssigned();
1822
+ // The order observer sends OrderDriverAssigned once the assignment is saved
1823
+ // (and tells a replaced driver they were unassigned), so it is not fired here
1824
+ // as well. A silent assignment is flagged for that one save.
1825
+ $this->silentDriverAssignment = (bool) $silent;
1826
+ try {
1827
+ $this->save();
1828
+ } finally {
1829
+ $this->silentDriverAssignment = false;
1651
1830
  }
1652
1831
 
1653
- $this->save();
1654
-
1655
1832
  return $this;
1656
1833
  }
1657
1834
 
@@ -1671,7 +1848,10 @@ class Order extends Model
1671
1848
  $driverAssigned = Driver::where('uuid', $this->driver_assigned_uuid)->first();
1672
1849
  }
1673
1850
 
1674
- if ($driverAssigned instanceof Driver) {
1851
+ // A driver that has never reported a position still carries the
1852
+ // POINT(0 0) placeholder it was created with. Measuring from there
1853
+ // puts the origin at null island, so fall back to the pickup.
1854
+ if ($driverAssigned instanceof Driver && static::isKnownPosition($driverAssigned->location)) {
1675
1855
  return $driverAssigned->location;
1676
1856
  }
1677
1857
  }
@@ -1702,6 +1882,16 @@ class Order extends Model
1702
1882
  return $destination ? $destination->location : null;
1703
1883
  }
1704
1884
 
1885
+ /**
1886
+ * Determines whether a position is a real, resolved location rather than
1887
+ * missing or the POINT(0 0) placeholder stored for drivers that have not
1888
+ * reported a position yet and for places that failed to geocode.
1889
+ */
1890
+ public static function isKnownPosition($position): bool
1891
+ {
1892
+ return $position instanceof Point && ($position->getLat() != 0.0 || $position->getLng() != 0.0);
1893
+ }
1894
+
1705
1895
  /**
1706
1896
  * Sets the preliminary distance and time for the order.
1707
1897
  * Uses a utility method to calculate the distance and time between the current origin and destination positions.
@@ -1713,13 +1903,14 @@ class Order extends Model
1713
1903
  $origin = $this->getCurrentOriginPosition();
1714
1904
  $destination = $this->getDestinationPosition();
1715
1905
 
1716
- if ($origin === null || $destination === null) {
1906
+ if (!static::isKnownPosition($origin) || !static::isKnownPosition($destination)) {
1717
1907
  return $this;
1718
1908
  }
1719
1909
 
1720
1910
  $matrix = Utils::getPreliminaryDistanceMatrix($origin, $destination);
1721
1911
 
1722
1912
  $this->updateQuietly(['distance' => $matrix->distance, 'time' => $matrix->time]);
1913
+ $this->invalidateApiCache();
1723
1914
 
1724
1915
  return $this;
1725
1916
  }
@@ -1735,7 +1926,7 @@ class Order extends Model
1735
1926
  $origin = $this->getCurrentOriginPosition();
1736
1927
  $destination = $this->getDestinationPosition();
1737
1928
 
1738
- if (!$origin || !$destination) {
1929
+ if (!static::isKnownPosition($origin) || !static::isKnownPosition($destination)) {
1739
1930
  return $this;
1740
1931
  }
1741
1932
 
@@ -1757,6 +1948,7 @@ class Order extends Model
1757
1948
  'distance' => $matrix->distance,
1758
1949
  'time' => $matrix->time,
1759
1950
  ]);
1951
+ $this->invalidateApiCache();
1760
1952
  }
1761
1953
 
1762
1954
  return $this;
@@ -1783,6 +1975,37 @@ class Order extends Model
1783
1975
  return (int) Utils::get($this, 'adhoc_distance', Utils::get($this, 'company.options.fleetops.adhoc_distance', 6000));
1784
1976
  }
1785
1977
 
1978
+ /**
1979
+ * Returns the initial tracking status for a newly created order.
1980
+ *
1981
+ * Used by the tracking number on creation. Orders whose config defines its own
1982
+ * lifecycle start at the configured initial activity instead of `created`.
1983
+ *
1984
+ * @return array|null ['code', 'status', 'details'] or null to use the default created status
1985
+ */
1986
+ public function getInitialTrackingStatus(): ?array
1987
+ {
1988
+ if (!$this->order_config_uuid) {
1989
+ return null;
1990
+ }
1991
+
1992
+ $orderConfig = OrderConfig::where('uuid', $this->order_config_uuid)->first();
1993
+ if (!$orderConfig || !$orderConfig->hasConfiguredLifecycle()) {
1994
+ return null;
1995
+ }
1996
+
1997
+ $activity = $orderConfig->getInitialActivity();
1998
+ if (!$activity) {
1999
+ return null;
2000
+ }
2001
+
2002
+ return [
2003
+ 'code' => $activity->code,
2004
+ 'status' => $this->resolveActivityTemplateString((string) $activity->get('status', '')),
2005
+ 'details' => $this->resolveActivityTemplateString((string) $activity->get('details', '')),
2006
+ ];
2007
+ }
2008
+
1786
2009
  /**
1787
2010
  * Retrieves the OrderConfig associated with this order.
1788
2011
  *
@@ -380,6 +380,125 @@ class OrderConfig extends Model
380
380
  return $this->activities()->firstWhere('code', $code);
381
381
  }
382
382
 
383
+ /**
384
+ * Returns the configured lifecycle semantics stored under `meta.lifecycle`.
385
+ *
386
+ * A configured lifecycle is opt-in. When absent the default dispatch lifecycle
387
+ * (created -> dispatched -> ... -> completed / canceled) applies unchanged.
388
+ *
389
+ * Supported keys:
390
+ * - `initial` (string) activity code new orders start at
391
+ * - `completed` (string) activity code that completes an order
392
+ * - `canceled` (string) activity code used when an order is canceled
393
+ * - `terminal` (string[]) activity codes after which no transition is allowed
394
+ * - `dispatch` (bool) false disables dispatch for orders using this config
395
+ * - `strict_transitions` (bool) true only allows moving to a configured child activity
396
+ */
397
+ public function lifecycle(): array
398
+ {
399
+ $lifecycle = data_get($this->meta, 'lifecycle');
400
+
401
+ return is_array($lifecycle) ? $lifecycle : [];
402
+ }
403
+
404
+ /**
405
+ * Determines if this config defines its own lifecycle semantics.
406
+ */
407
+ public function hasConfiguredLifecycle(): bool
408
+ {
409
+ return !empty($this->lifecycle());
410
+ }
411
+
412
+ /**
413
+ * Returns the activity new orders using this config start at.
414
+ *
415
+ * Without a configured lifecycle this is the `created` activity, if present in the flow.
416
+ */
417
+ public function getInitialActivity(): ?Activity
418
+ {
419
+ $initialCode = data_get($this->lifecycle(), 'initial');
420
+ if ($initialCode) {
421
+ return $this->getActivityByCode($initialCode);
422
+ }
423
+
424
+ return $this->getCreatedActivity();
425
+ }
426
+
427
+ /**
428
+ * Returns the status code new orders using this config start at.
429
+ */
430
+ public function getInitialStatusCode(): string
431
+ {
432
+ return data_get($this->lifecycle(), 'initial') ?: 'created';
433
+ }
434
+
435
+ /**
436
+ * Determines if orders using this config may be dispatched.
437
+ */
438
+ public function allowsDispatch(): bool
439
+ {
440
+ $dispatch = data_get($this->lifecycle(), 'dispatch', true);
441
+
442
+ return !in_array($dispatch, [false, 'false', 0, '0'], true);
443
+ }
444
+
445
+ /**
446
+ * Determines if activity updates must follow the configured flow graph.
447
+ */
448
+ public function hasStrictTransitions(): bool
449
+ {
450
+ return (bool) data_get($this->lifecycle(), 'strict_transitions', false);
451
+ }
452
+
453
+ /**
454
+ * Returns the terminal activity codes of this config.
455
+ */
456
+ public function getTerminalActivityCodes(): array
457
+ {
458
+ if (!$this->hasConfiguredLifecycle()) {
459
+ return ['completed', 'canceled'];
460
+ }
461
+
462
+ $terminal = data_get($this->lifecycle(), 'terminal', []);
463
+ $terminal = is_array($terminal) ? $terminal : [];
464
+ foreach (['completed', 'canceled'] as $semantic) {
465
+ $code = data_get($this->lifecycle(), $semantic);
466
+ if ($code) {
467
+ $terminal[] = $code;
468
+ }
469
+ }
470
+
471
+ return array_values(array_unique($terminal));
472
+ }
473
+
474
+ /**
475
+ * Determines if the given activity code is terminal for this config.
476
+ */
477
+ public function isTerminalActivityCode(?string $code): bool
478
+ {
479
+ return $code !== null && in_array($code, $this->getTerminalActivityCodes(), true);
480
+ }
481
+
482
+ /**
483
+ * Determines if the configured flow permits moving from one activity code to another.
484
+ *
485
+ * Only the configured graph is consulted: the target must be listed as a child of the
486
+ * source activity, and terminal activities permit no further transition.
487
+ */
488
+ public function canTransition(?string $fromCode, string $toCode): bool
489
+ {
490
+ if ($fromCode === null || $this->isTerminalActivityCode($fromCode)) {
491
+ return false;
492
+ }
493
+
494
+ $from = $this->getActivityByCode($fromCode);
495
+ if (!$from) {
496
+ return false;
497
+ }
498
+
499
+ return $from->children()->contains(fn ($activity) => $activity->code === $toCode);
500
+ }
501
+
383
502
  /**
384
503
  * Creates an Activity instance representing a canceled order.
385
504
  *
@@ -391,6 +510,11 @@ class OrderConfig extends Model
391
510
  */
392
511
  public function getCanceledActivity()
393
512
  {
513
+ $configuredCode = data_get($this->lifecycle(), 'canceled');
514
+ if ($configuredCode && ($configuredActivity = $this->getActivityByCode($configuredCode))) {
515
+ return $configuredActivity;
516
+ }
517
+
394
518
  $canceledActivity = $this->activities()->firstWhere('code', 'canceled');
395
519
  if ($canceledActivity) {
396
520
  return $canceledActivity;
@@ -416,6 +540,11 @@ class OrderConfig extends Model
416
540
  */
417
541
  public function getCompletedActivity()
418
542
  {
543
+ $configuredCode = data_get($this->lifecycle(), 'completed');
544
+ if ($configuredCode && ($configuredActivity = $this->getActivityByCode($configuredCode))) {
545
+ return $configuredActivity;
546
+ }
547
+
419
548
  $completedActivity = $this->activities()->firstWhere('code', 'completed');
420
549
  if ($completedActivity) {
421
550
  return $completedActivity;