@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
@@ -5,6 +5,7 @@ namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
5
5
  use Fleetbase\FleetOps\Events\OrderDispatchFailed;
6
6
  use Fleetbase\FleetOps\Events\OrderStarted;
7
7
  use Fleetbase\FleetOps\Exceptions\CustomerUserConflictException;
8
+ use Fleetbase\FleetOps\Exceptions\OrderEditLockedException;
8
9
  use Fleetbase\FleetOps\Exceptions\UserAlreadyExistsException;
9
10
  use Fleetbase\FleetOps\Flow\Activity;
10
11
  use Fleetbase\FleetOps\Http\Requests\CreateOrderRequest;
@@ -24,7 +25,9 @@ use Fleetbase\FleetOps\Models\Place;
24
25
  use Fleetbase\FleetOps\Models\Proof;
25
26
  use Fleetbase\FleetOps\Models\ServiceQuote;
26
27
  use Fleetbase\FleetOps\Models\Waypoint;
28
+ use Fleetbase\FleetOps\Support\OrderEdit;
27
29
  use Fleetbase\FleetOps\Support\ResolvesOrderServiceStops;
30
+ use Fleetbase\FleetOps\Support\TrackingCode;
28
31
  use Fleetbase\FleetOps\Support\Utils;
29
32
  use Fleetbase\Http\Controllers\Controller;
30
33
  use Fleetbase\Http\Resources\Comment as CommentResource;
@@ -352,6 +355,19 @@ class OrderController extends Controller
352
355
  // get request input
353
356
  $input = $this->orderUpdateInputFromRequest($request);
354
357
 
358
+ // `entities_mode=replace` makes the entities sent the payload's full list
359
+ // (update by id, create new, delete missing); by default they are appended
360
+ $entitiesMode = OrderEdit::entitiesMode($request->input('entities_mode', $request->input('payload.entities_mode')));
361
+ $codAmount = $this->codAmountFromRequest($request);
362
+
363
+ // refuse changes the order's status locks before anything is written
364
+ $edit = $this->beginOrderEdit($order);
365
+ try {
366
+ $edit->guard($edit->requestedChanges($this->orderEditInputFromRequest($request, $codAmount), $entitiesMode));
367
+ } catch (OrderEditLockedException $exception) {
368
+ return $this->lockedEditResponse($exception);
369
+ }
370
+
355
371
  // update payload if new input or change payload by id
356
372
  if ($request->isArray('payload')) {
357
373
  $payload = data_get($order, 'payload', $this->newPayload());
@@ -383,6 +399,10 @@ class OrderController extends Controller
383
399
  $payload->setReturn($return);
384
400
  }
385
401
 
402
+ if ($codAmount !== null) {
403
+ $payload->cod_amount = $codAmount;
404
+ }
405
+
386
406
  $payload->save();
387
407
 
388
408
  // set waypoints and entities after payload is saved
@@ -392,14 +412,10 @@ class OrderController extends Controller
392
412
  $payload->removeWaypoints();
393
413
  }
394
414
 
395
- if ($entities) {
396
- $payload->setEntities($entities);
397
- }
415
+ $this->applyPayloadEntities($payload, $entities, $entitiesMode);
398
416
 
399
- $firstWaypoint = $payload->getPickupOrFirstWaypoint();
400
- if ($firstWaypoint instanceof Place) {
401
- $payload->setCurrentWaypoint($firstWaypoint);
402
- }
417
+ // the current waypoint is reconciled once the edit is committed: it is only
418
+ // moved when the route changed, and an order under way keeps its stop
403
419
 
404
420
  $input['payload_uuid'] = $payload->uuid;
405
421
  } elseif ($request->has('payload')) {
@@ -408,6 +424,11 @@ class OrderController extends Controller
408
424
  'company_uuid' => $this->sessionCompany(),
409
425
  ]);
410
426
  unset($input['payload']);
427
+
428
+ if ($codAmount !== null && is_string($input['payload_uuid']) && ($switchedPayload = $this->findPayloadByUuid($input['payload_uuid']))) {
429
+ $switchedPayload->cod_amount = $codAmount;
430
+ $switchedPayload->save();
431
+ }
411
432
  }
412
433
 
413
434
  // create a payload if missing payload[] but has pickup/dropoff/etc
@@ -441,6 +462,10 @@ class OrderController extends Controller
441
462
  $payload->setReturn($return);
442
463
  }
443
464
 
465
+ if ($codAmount !== null) {
466
+ $payload->cod_amount = $codAmount;
467
+ }
468
+
444
469
  $payload->save();
445
470
 
446
471
  // set waypoints and entities after payload is saved
@@ -450,14 +475,10 @@ class OrderController extends Controller
450
475
  $payload->removeWaypoints();
451
476
  }
452
477
 
453
- if ($entities) {
454
- $payload->setEntities($entities);
455
- }
478
+ $this->applyPayloadEntities($payload, $entities, $entitiesMode);
456
479
 
457
- $firstWaypoint = $payload->getPickupOrFirstWaypoint();
458
- if ($firstWaypoint instanceof Place) {
459
- $payload->setCurrentWaypoint($firstWaypoint);
460
- }
480
+ // the current waypoint is reconciled once the edit is committed: it is only
481
+ // moved when the route changed, and an order under way keeps its stop
461
482
 
462
483
  $input['payload_uuid'] = $payload->uuid;
463
484
  }
@@ -537,6 +558,10 @@ class OrderController extends Controller
537
558
  $order->update($input);
538
559
  $order->flushAttributesCache();
539
560
 
561
+ // reroute, recompute distance and time, republish tracking and announce
562
+ // `order.changed` for whatever this update changed
563
+ $this->commitOrderEdit($edit);
564
+
540
565
  // load required relations
541
566
  $order->load(['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate.serviceQuote.items', 'customer', 'facilitator']);
542
567
 
@@ -544,6 +569,79 @@ class OrderController extends Controller
544
569
  return $this->orderResource($order);
545
570
  }
546
571
 
572
+ /**
573
+ * The cash to collect sent with an order update, as `payload.cod_amount` or a
574
+ * top-level `cod_amount`. Zero is a valid amount; anything not numeric is ignored.
575
+ */
576
+ protected function codAmountFromRequest(Request $request): int|float|string|null
577
+ {
578
+ $value = null;
579
+ if ($request->isArray('payload') && Arr::has((array) $request->input('payload'), 'cod_amount')) {
580
+ $value = data_get($request->input('payload'), 'cod_amount');
581
+ } elseif ($request->has('cod_amount')) {
582
+ $value = $request->input('cod_amount');
583
+ }
584
+
585
+ return is_numeric($value) ? $value : null;
586
+ }
587
+
588
+ /**
589
+ * The parts of an order update the edit policy looks at.
590
+ */
591
+ protected function orderEditInputFromRequest(Request $request, mixed $codAmount = null): array
592
+ {
593
+ $routeKeys = ['pickup', 'dropoff', 'return', 'waypoints', 'entities'];
594
+
595
+ if ($request->isArray('payload')) {
596
+ $edit = Arr::only((array) $request->input('payload'), $routeKeys);
597
+ } elseif ($request->has('payload')) {
598
+ // switching to another payload replaces the whole route
599
+ $edit = ['pickup' => true, 'dropoff' => true, 'waypoints' => true];
600
+ } else {
601
+ $edit = $request->only($routeKeys);
602
+ }
603
+
604
+ foreach (['scheduled_at', 'notes', 'customer'] as $key) {
605
+ if ($request->has($key)) {
606
+ $edit[$key] = $request->input($key);
607
+ }
608
+ }
609
+
610
+ if ($codAmount !== null) {
611
+ $edit['cod_amount'] = $codAmount;
612
+ }
613
+
614
+ return $edit;
615
+ }
616
+
617
+ protected function applyPayloadEntities(Payload $payload, mixed $entities, string $mode): void
618
+ {
619
+ if ($mode === OrderEdit::ENTITIES_REPLACE && is_array($entities)) {
620
+ $payload->syncEntities($entities);
621
+
622
+ return;
623
+ }
624
+
625
+ if ($entities) {
626
+ $payload->setEntities($entities);
627
+ }
628
+ }
629
+
630
+ protected function beginOrderEdit(Order $order): OrderEdit
631
+ {
632
+ return OrderEdit::begin($order);
633
+ }
634
+
635
+ protected function commitOrderEdit(OrderEdit $edit): array
636
+ {
637
+ return $edit->commit();
638
+ }
639
+
640
+ protected function lockedEditResponse(OrderEditLockedException $exception)
641
+ {
642
+ return response()->apiError($exception->getMessage(), 422, $exception->toResponseData());
643
+ }
644
+
547
645
  protected function orderCreateInputFromRequest(Request $request): array
548
646
  {
549
647
  return $request->only([
@@ -631,24 +729,6 @@ class OrderController extends Controller
631
729
  });
632
730
  }
633
731
 
634
- if ($request->has('facilitator')) {
635
- $query->whereHas('facilitator', function ($q) use ($request) {
636
- $q->where(function ($q) use ($request) {
637
- $q->where('public_id', $request->input('facilitator'));
638
- $q->orWhere('internal_id', $request->input('facilitator'));
639
- });
640
- });
641
- }
642
-
643
- if ($request->has('customer')) {
644
- $query->whereHas('customer', function ($q) use ($request) {
645
- $q->where(function ($q) use ($request) {
646
- $q->where('public_id', $request->input('customer'));
647
- $q->orWhere('internal_id', $request->input('customer'));
648
- });
649
- });
650
- }
651
-
652
732
  if ($request->has('entity')) {
653
733
  $query->whereHas('payload.entities', function ($q) use ($request) {
654
734
  $q->where(function ($q) use ($request) {
@@ -1296,8 +1376,12 @@ class OrderController extends Controller
1296
1376
  *
1297
1377
  * @return \Fleetbase\Http\Resources\v1\Order
1298
1378
  */
1299
- public function cancelOrder(string $id)
1379
+ public function cancelOrder(string $id, ?Request $request = null)
1300
1380
  {
1381
+ // Laravel does not inject a class-typed parameter that has a default, so the
1382
+ // current request is read here; the default keeps cancelOrder($id) callable.
1383
+ $request = $request ?? request();
1384
+
1301
1385
  try {
1302
1386
  $order = $this->findOrder($id);
1303
1387
  } catch (ModelNotFoundException $exception) {
@@ -1309,11 +1393,30 @@ class OrderController extends Controller
1309
1393
  );
1310
1394
  }
1311
1395
 
1312
- $order->cancel();
1396
+ // the reason becomes the canceled status details and is sent with the canceled notifications
1397
+ $reason = $this->reasonFromRequest($request);
1398
+ if ($reason !== null) {
1399
+ $order->cancelWithReason($reason);
1400
+ } else {
1401
+ $order->cancel();
1402
+ }
1313
1403
 
1314
1404
  return $this->orderResource($order);
1315
1405
  }
1316
1406
 
1407
+ /**
1408
+ * The free-text `reason` sent with a cancel, trimmed to 1000 characters.
1409
+ */
1410
+ protected function reasonFromRequest(Request $request): ?string
1411
+ {
1412
+ $reason = $request->input('reason');
1413
+ if (!is_string($reason) || trim($reason) === '') {
1414
+ return null;
1415
+ }
1416
+
1417
+ return Str::limit(trim($reason), 1000, '');
1418
+ }
1419
+
1317
1420
  /**
1318
1421
  * Mark a specific place (waypoint, pickup, or drop-off) as the current
1319
1422
  * destination for an order payload.
@@ -1444,8 +1547,8 @@ class OrderController extends Controller
1444
1547
  return $this->apiError('Unable to capture QR code data.');
1445
1548
  }
1446
1549
 
1447
- // validate
1448
- if ($subject && $code === $subject->uuid) {
1550
+ // validate: the scan must name this subject, in any code format still in circulation
1551
+ if (TrackingCode::matches($code, $subject)) {
1449
1552
  // create verification proof
1450
1553
  $proof = $this->createProof([
1451
1554
  'company_uuid' => session('company'),
@@ -2,12 +2,15 @@
2
2
 
3
3
  namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
4
4
 
5
+ use Fleetbase\FleetOps\Exceptions\OrderEditLockedException;
5
6
  use Fleetbase\FleetOps\Http\Requests\CreatePayloadRequest;
6
7
  use Fleetbase\FleetOps\Http\Requests\UpdatePayloadRequest;
7
8
  use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
8
9
  use Fleetbase\FleetOps\Http\Resources\v1\Payload as PayloadResource;
10
+ use Fleetbase\FleetOps\Models\Order;
9
11
  use Fleetbase\FleetOps\Models\Payload;
10
12
  use Fleetbase\FleetOps\Models\Place;
13
+ use Fleetbase\FleetOps\Support\OrderEdit;
11
14
  use Fleetbase\Http\Controllers\Controller;
12
15
  use Illuminate\Http\Request;
13
16
  use Illuminate\Support\Arr;
@@ -103,6 +106,18 @@ class PayloadController extends Controller
103
106
  $return = $routeShape['return'];
104
107
  $hasWaypointsField = $routeShape['has_waypoints_field'];
105
108
  $hasRouteEndpointFields = $routeShape['has_route_endpoint_fields'];
109
+ $entitiesMode = OrderEdit::entitiesMode($input['entities_mode'] ?? null);
110
+
111
+ // when the payload belongs to an order, refuse changes the order's status locks
112
+ $order = $this->orderForPayload($payload);
113
+ $edit = $order ? $this->beginOrderEdit($order) : null;
114
+ if ($edit) {
115
+ try {
116
+ $edit->guard($edit->requestedChanges(Arr::only($input, ['pickup', 'dropoff', 'return', 'waypoints', 'entities', 'cod_amount']), $entitiesMode));
117
+ } catch (OrderEditLockedException $exception) {
118
+ return $this->lockedEditResponse($exception);
119
+ }
120
+ }
106
121
 
107
122
  // pickup assignment
108
123
  if ($pickup) {
@@ -126,21 +141,29 @@ class PayloadController extends Controller
126
141
  $payload->removeWaypoints();
127
142
  }
128
143
 
129
- // set entities
130
- if ($entities) {
144
+ // set entities: appended, or with `entities_mode=replace` the full list
145
+ if ($entitiesMode === OrderEdit::ENTITIES_REPLACE && is_array($entities)) {
146
+ $payload->syncEntities($entities);
147
+ } elseif ($entities) {
131
148
  $payload->setEntities($entities);
132
149
  }
133
150
 
134
- // update the payload
135
- $payload->fill(array_filter($this->payloadFillInputFromInput($input)));
151
+ // update the payload; empty values are skipped, but a cash-to-collect of 0 is kept
152
+ $payload->fill($this->payloadUpdateFillInput($input));
136
153
 
137
154
  // save the payload
138
155
  $payload->save();
139
156
 
140
- // set the first / current waypoint from the effective route shape
141
- $firstWaypoint = $payload->getPickupOrFirstWaypoint();
142
- if ($firstWaypoint instanceof Place) {
143
- $payload->setCurrentWaypoint($firstWaypoint);
157
+ if ($edit) {
158
+ // the order's edit moves the current waypoint only when the route changed, keeps
159
+ // an order under way on its stop and announces the change
160
+ $this->commitOrderEdit($edit);
161
+ } else {
162
+ // set the first / current waypoint from the effective route shape
163
+ $firstWaypoint = $payload->getPickupOrFirstWaypoint();
164
+ if ($firstWaypoint instanceof Place) {
165
+ $payload->setCurrentWaypoint($firstWaypoint);
166
+ }
144
167
  }
145
168
 
146
169
  // make sure entities and waypoints is loaded
@@ -237,6 +260,51 @@ class PayloadController extends Controller
237
260
  return Arr::only($input, ['type', 'provider', 'meta', 'cod_amount', 'cod_currency', 'cod_payment_method']);
238
261
  }
239
262
 
263
+ /**
264
+ * The payload attributes an update fills: empty values are skipped as before,
265
+ * except a numeric cash-to-collect, so 0 clears it.
266
+ */
267
+ protected function payloadUpdateFillInput(array $input): array
268
+ {
269
+ $fill = $this->payloadFillInputFromInput($input);
270
+ $cod = $fill['cod_amount'] ?? null;
271
+ $fill = array_filter($fill);
272
+
273
+ if (is_numeric($cod)) {
274
+ $fill['cod_amount'] = $cod;
275
+ }
276
+
277
+ return $fill;
278
+ }
279
+
280
+ protected function orderForPayload(Payload $payload): ?Order
281
+ {
282
+ if (!$payload->uuid) {
283
+ return null;
284
+ }
285
+
286
+ try {
287
+ return Order::where('payload_uuid', $payload->uuid)->first();
288
+ } catch (\Throwable) {
289
+ return null;
290
+ }
291
+ }
292
+
293
+ protected function beginOrderEdit(Order $order): OrderEdit
294
+ {
295
+ return OrderEdit::begin($order);
296
+ }
297
+
298
+ protected function commitOrderEdit(OrderEdit $edit): array
299
+ {
300
+ return $edit->commit();
301
+ }
302
+
303
+ protected function lockedEditResponse(OrderEditLockedException $exception)
304
+ {
305
+ return response()->apiError($exception->getMessage(), 422, $exception->toResponseData());
306
+ }
307
+
240
308
  protected function newPayload(array $input): Payload
241
309
  {
242
310
  return new Payload($input);
@@ -146,7 +146,7 @@ class PlaceController extends Controller
146
146
  try {
147
147
  $place = $this->findPlaceOrFail($id);
148
148
  } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
149
- return $this->apiError('Place resource not found.');
149
+ return $this->apiError('Place resource not found.', 404);
150
150
  }
151
151
 
152
152
  // get request input
@@ -253,7 +253,7 @@ class PlaceController extends Controller
253
253
  try {
254
254
  $place = $this->findPlaceOrFail($id);
255
255
  } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
256
- return $this->apiError('Place resource not found.');
256
+ return $this->apiError('Place resource not found.', 404);
257
257
  }
258
258
 
259
259
  return $this->placeResource($place);
@@ -269,7 +269,7 @@ class PlaceController extends Controller
269
269
  try {
270
270
  $place = $this->findPlaceOrFail($id);
271
271
  } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
272
- return $this->apiError('Place resource not found.');
272
+ return $this->apiError('Place resource not found.', 404);
273
273
  }
274
274
 
275
275
  $place->delete();
@@ -127,8 +127,10 @@ class TrackingNumberController extends Controller
127
127
  // validate request inputs
128
128
  $code = $request->input('code');
129
129
 
130
- // get the model of from the code
131
- $model = $this->findQrModel(['entities', 'orders'], ['uuid' => $code]);
130
+ // the order, waypoint, entity or place the code points at, in any format still in
131
+ // circulation: a tracking url, a bare tracking number or public id, or the bare
132
+ // owner uuid printed on older labels
133
+ $model = $this->findQrModel($code);
132
134
 
133
135
  // if no model response with error
134
136
  if (!$model) {
@@ -179,37 +181,9 @@ class TrackingNumberController extends Controller
179
181
  return new DeletedResource($trackingNumber);
180
182
  }
181
183
 
182
- protected function findQrModel(array $tables, array $where)
184
+ protected function findQrModel(string $code)
183
185
  {
184
- // Hydrate through the eloquent models so the resolved record can be
185
- // wrapped in its typed resource — raw rows have no model class.
186
- $modelMap = [
187
- 'entities' => \Fleetbase\FleetOps\Models\Entity::class,
188
- 'orders' => \Fleetbase\FleetOps\Models\Order::class,
189
- ];
190
-
191
- foreach ($tables as $table) {
192
- $modelClass = $modelMap[$table] ?? null;
193
- if (!$modelClass) {
194
- continue;
195
- }
196
-
197
- $model = $modelClass::where($where)->first();
198
- if ($model) {
199
- return $model;
200
- }
201
- }
202
-
203
- // No raw-row fallback. Utils::findModel() was called with the ARRAY of table names
204
- // and, when nothing matched, passed that array to DB::table() — which stringified
205
- // it to the literal table name "Array" and threw SQLSTATE[42S02]. Every
206
- // unresolvable code became a 500 instead of the 400 this method's caller already
207
- // handles.
208
- //
209
- // The fallback could not have helped anyway: qrModelResource() serializes through
210
- // a typed resource keyed on the model class, and a raw stdClass row has none. The
211
- // mapped models above are the endpoint's whole contract.
212
- return null;
186
+ return TrackingNumber::findOwnerByCode($code, session('company'));
213
187
  }
214
188
 
215
189
  protected function qrModelResource($model)
@@ -17,7 +17,9 @@ use Fleetbase\FleetOps\Models\Driver;
17
17
  use Fleetbase\FleetOps\Models\Vehicle;
18
18
  use Fleetbase\FleetOps\Models\Vendor;
19
19
  use Fleetbase\FleetOps\Models\Warranty;
20
+ use Fleetbase\FleetOps\Support\ApiCacheThrottle;
20
21
  use Fleetbase\FleetOps\Support\GeofenceIntersectionService;
22
+ use Fleetbase\FleetOps\Support\TrackingPublisher;
21
23
  use Fleetbase\FleetOps\Support\Utils;
22
24
  use Fleetbase\Http\Controllers\Controller;
23
25
  use Fleetbase\LaravelMysqlSpatial\Types\Point;
@@ -296,8 +298,11 @@ class VehicleController extends Controller
296
298
 
297
299
  $vehicle->updateQuietly($positionData);
298
300
  $vehicle->createPosition($positionData);
301
+ // Location writes are quiet and frequent: invalidate cached lists at most once a window
302
+ ApiCacheThrottle::invalidate($vehicle, $vehicle->company_uuid);
299
303
 
300
304
  broadcast(new VehicleLocationChanged($vehicle));
305
+ app(TrackingPublisher::class)->vehicleMoved($vehicle);
301
306
 
302
307
  try {
303
308
  $newLocation = new Point($latitude, $longitude);
@@ -19,6 +19,7 @@ use Fleetbase\FleetOps\Support\Utils;
19
19
  use Fleetbase\Http\Requests\ExportRequest;
20
20
  use Fleetbase\Http\Requests\ImportRequest;
21
21
  use Fleetbase\Models\User;
22
+ use Fleetbase\Support\ApiModelCache;
22
23
  use Illuminate\Http\Request;
23
24
  use Illuminate\Support\Facades\DB;
24
25
  use Illuminate\Support\Facades\Mail;
@@ -266,6 +267,7 @@ class ContactController extends FleetOpsController
266
267
  protected function bulkUpdateCustomerContext(string $modelClass, array $filter, array $replacement): void
267
268
  {
268
269
  $modelClass::where($filter)->update($replacement);
270
+ ApiModelCache::invalidateModelCache(new $modelClass(), session('company'));
269
271
  }
270
272
 
271
273
  protected function customerPortalIssuesForContact(Contact $contact)
@@ -350,6 +352,7 @@ class ContactController extends FleetOpsController
350
352
  protected function saveContactMetaQuietly(Contact $contact, array $meta): void
351
353
  {
352
354
  $contact->forceFill(['meta' => $meta])->saveQuietly();
355
+ $contact->invalidateApiCache();
353
356
  }
354
357
 
355
358
  protected function isCustomerPortalInstalled(): bool
@@ -595,6 +595,16 @@ class DriverController extends FleetOpsController
595
595
  'driver_assigned_uuid' => null,
596
596
  'updated_at' => now(),
597
597
  ]);
598
+ Order::invalidateApiCacheManually(session('company'));
599
+
600
+ // The bulk update bypasses the order observer, so the driver taken off each
601
+ // order is told here
602
+ foreach ($orders as $order) {
603
+ $previousDriverUuid = $order->driver_assigned_uuid;
604
+ if ($previousDriverUuid) {
605
+ $order->notifyDriverUnassigned($previousDriverUuid);
606
+ }
607
+ }
598
608
  }
599
609
 
600
610
  protected function freshOrders($orders, array $with)
@@ -250,10 +250,17 @@ class FleetController extends FleetOpsController
250
250
 
251
251
  protected static function deleteFleetDriverAssignment(string $fleetUuid, string $driverUuid): mixed
252
252
  {
253
- return FleetDriver::where([
253
+ $deleted = FleetDriver::where([
254
254
  'fleet_uuid' => $fleetUuid,
255
255
  'driver_uuid' => $driverUuid,
256
256
  ])->delete();
257
+
258
+ // Deleted without model events; fleet_drivers rows carry no company
259
+ if ($deleted) {
260
+ FleetDriver::invalidateApiCacheManually();
261
+ }
262
+
263
+ return $deleted;
257
264
  }
258
265
 
259
266
  protected static function fleetVehicleAssignmentExists(string $fleetUuid, string $vehicleUuid): bool
@@ -274,10 +281,17 @@ class FleetController extends FleetOpsController
274
281
 
275
282
  protected static function deleteFleetVehicleAssignment(string $fleetUuid, string $vehicleUuid): mixed
276
283
  {
277
- return FleetVehicle::where([
284
+ $deleted = FleetVehicle::where([
278
285
  'fleet_uuid' => $fleetUuid,
279
286
  'vehicle_uuid' => $vehicleUuid,
280
287
  ])->delete();
288
+
289
+ // Deleted without model events; fleet_vehicles rows carry no company
290
+ if ($deleted) {
291
+ FleetVehicle::invalidateApiCacheManually();
292
+ }
293
+
294
+ return $deleted;
281
295
  }
282
296
 
283
297
  protected static function invalidateOperationsMonitor(): void
@@ -111,8 +111,7 @@ class InspectionSubmissionController extends FleetOpsController
111
111
 
112
112
  public function submit(string $id): JsonResponse
113
113
  {
114
- $submission = InspectionSubmission::where('uuid', $id)
115
- ->orWhere('public_id', $id)
114
+ $submission = $this->submissionQuery($id)
116
115
  ->with(['itemResults', 'vehicle', 'driver', 'form'])
117
116
  ->firstOrFail();
118
117
 
@@ -127,8 +126,7 @@ class InspectionSubmissionController extends FleetOpsController
127
126
 
128
127
  public function createIssue(string $id): JsonResponse
129
128
  {
130
- $submission = InspectionSubmission::where('uuid', $id)
131
- ->orWhere('public_id', $id)
129
+ $submission = $this->submissionQuery($id)
132
130
  ->with(['itemResults', 'vehicle', 'driver', 'form'])
133
131
  ->firstOrFail();
134
132
 
@@ -145,8 +143,7 @@ class InspectionSubmissionController extends FleetOpsController
145
143
 
146
144
  public function createWorkOrder(string $id): JsonResponse
147
145
  {
148
- $submission = InspectionSubmission::where('uuid', $id)
149
- ->orWhere('public_id', $id)
146
+ $submission = $this->submissionQuery($id)
150
147
  ->with(['itemResults', 'vehicle', 'driver', 'form'])
151
148
  ->firstOrFail();
152
149
 
@@ -164,8 +161,7 @@ class InspectionSubmissionController extends FleetOpsController
164
161
 
165
162
  public function resolve(string $id): JsonResponse
166
163
  {
167
- $submission = InspectionSubmission::where('uuid', $id)
168
- ->orWhere('public_id', $id)
164
+ $submission = $this->submissionQuery($id)
169
165
  ->firstOrFail();
170
166
 
171
167
  $submission->update([
@@ -180,6 +176,15 @@ class InspectionSubmissionController extends FleetOpsController
180
176
  ]);
181
177
  }
182
178
 
179
+ /** A submission by uuid or public id, only ever within the session's company. */
180
+ protected function submissionQuery(string $id)
181
+ {
182
+ return InspectionSubmission::where('company_uuid', session('company'))
183
+ ->where(function ($query) use ($id) {
184
+ $query->where('uuid', $id)->orWhere('public_id', $id);
185
+ });
186
+ }
187
+
183
188
  protected function syncItemResultsFromRequest(Request $request, InspectionSubmission $submission): void
184
189
  {
185
190
  $items = static::arrayInput($request, 'inspection_submission.item_results', 'item_results');
@@ -38,6 +38,7 @@ class IssueController extends FleetOpsController
38
38
  if ($assignee && $assignee->type === 'customer') {
39
39
  $issue->assigned_to_uuid = null;
40
40
  $issue->saveQuietly();
41
+ $issue->invalidateApiCache();
41
42
  }
42
43
  }
43
44
 
@@ -58,6 +59,7 @@ class IssueController extends FleetOpsController
58
59
  if ($order) {
59
60
  $issue->order_uuid = $order->uuid;
60
61
  $issue->saveQuietly();
62
+ $issue->invalidateApiCache();
61
63
  }
62
64
  }
63
65
  }
@@ -194,8 +194,10 @@ class MaintenanceController extends FleetOpsController
194
194
 
195
195
  protected function findMaintenanceForLineItem(string $id): Maintenance
196
196
  {
197
- return Maintenance::where('uuid', $id)
198
- ->orWhere('public_id', $id)
197
+ return Maintenance::where('company_uuid', session('company'))
198
+ ->where(function ($query) use ($id) {
199
+ $query->where('uuid', $id)->orWhere('public_id', $id);
200
+ })
199
201
  ->firstOrFail();
200
202
  }
201
203