@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
@@ -0,0 +1,261 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support;
4
+
5
+ use Fleetbase\FleetOps\Jobs\PublishTrackingUpdate;
6
+ use Fleetbase\FleetOps\Models\Driver;
7
+ use Fleetbase\FleetOps\Models\Entity;
8
+ use Fleetbase\FleetOps\Models\Order;
9
+ use Fleetbase\FleetOps\Models\Vehicle;
10
+ use Fleetbase\FleetOps\Models\Waypoint;
11
+ use Fleetbase\FleetOps\Tracking\TrackingContextBuilder;
12
+ use Fleetbase\FleetOps\Tracking\TrackingIntelligenceService;
13
+ use Fleetbase\FleetOps\Tracking\TrackingOptions;
14
+ use Fleetbase\FleetOps\Tracking\TrackingStop;
15
+ use Fleetbase\Support\SocketCluster\SocketClusterService;
16
+ use Fleetbase\Support\SocketCluster\SocketToken;
17
+ use Illuminate\Support\Collection;
18
+ use Illuminate\Support\Facades\Cache;
19
+
20
+ /**
21
+ * Publishes per-customer updates to public tracking channels.
22
+ *
23
+ * The request-path hooks (orderChanged, stopChanged, driverMoved, vehicleMoved) do almost
24
+ * nothing: they bail out when socket authentication is off, throttle location changes through
25
+ * the cache, and queue a PublishTrackingUpdate job. The job builds and publishes the updates.
26
+ */
27
+ class TrackingPublisher
28
+ {
29
+ /**
30
+ * Seconds between position updates for one order (and between order lookups for one driver or vehicle).
31
+ */
32
+ public const LOCATION_INTERVAL = 5;
33
+
34
+ /**
35
+ * Seconds between ETA refreshes for one order driven by position changes.
36
+ */
37
+ public const ETA_INTERVAL = 60;
38
+
39
+ /**
40
+ * Most active orders a single driver or vehicle position change fans out to.
41
+ */
42
+ public const MAX_ORDERS_PER_SUBJECT = 25;
43
+
44
+ /**
45
+ * Order attributes whose change is published, with the reason reported.
46
+ */
47
+ public const ORDER_ATTRIBUTES = [
48
+ 'status' => TrackingUpdate::REASON_STATUS,
49
+ 'started' => TrackingUpdate::REASON_STATUS,
50
+ 'dispatched' => TrackingUpdate::REASON_STATUS,
51
+ 'driver_assigned_uuid' => TrackingUpdate::REASON_ASSIGNMENT,
52
+ 'vehicle_assigned_uuid' => TrackingUpdate::REASON_ASSIGNMENT,
53
+ 'time' => TrackingUpdate::REASON_ETA,
54
+ 'distance' => TrackingUpdate::REASON_ETA,
55
+ ];
56
+
57
+ public static function enabled(): bool
58
+ {
59
+ return SocketToken::enabled();
60
+ }
61
+
62
+ /**
63
+ * An order was saved: queue an update when a published attribute changed.
64
+ */
65
+ public function orderChanged(Order $order): bool
66
+ {
67
+ if (!static::enabled() || !$order->uuid) {
68
+ return false;
69
+ }
70
+
71
+ $changed = array_intersect_key(self::ORDER_ATTRIBUTES, $order->getChanges());
72
+ if ($changed === []) {
73
+ return false;
74
+ }
75
+
76
+ $reasons = array_values($changed);
77
+ $reason = in_array(TrackingUpdate::REASON_STATUS, $reasons, true) ? TrackingUpdate::REASON_STATUS : $reasons[0];
78
+
79
+ return $this->queue($order->uuid, $reason, $order->getConnectionName());
80
+ }
81
+
82
+ /**
83
+ * An order's stops were edited: queue an update so customers see the new route and ETAs.
84
+ */
85
+ public function routeChanged(Order $order): bool
86
+ {
87
+ if (!static::enabled() || !$order->uuid) {
88
+ return false;
89
+ }
90
+
91
+ return $this->queue($order->uuid, TrackingUpdate::REASON_ROUTE, $order->getConnectionName());
92
+ }
93
+
94
+ /**
95
+ * A waypoint or entity activity changed: queue an update for its order.
96
+ */
97
+ public function stopChanged(Waypoint|Entity|null $subject): bool
98
+ {
99
+ if (!static::enabled() || !$subject || !$subject->payload_uuid) {
100
+ return false;
101
+ }
102
+
103
+ $connection = $subject->getConnectionName();
104
+ $orderUuid = Order::on($connection)->where('payload_uuid', $subject->payload_uuid)->value('uuid');
105
+
106
+ return $orderUuid ? $this->queue($orderUuid, TrackingUpdate::REASON_STOP, $connection) : false;
107
+ }
108
+
109
+ /**
110
+ * A driver reported a position: queue a location update for each of their en-route orders.
111
+ */
112
+ public function driverMoved(Driver $driver): int
113
+ {
114
+ if (!static::enabled() || !$driver->uuid || !Cache::add('fleetops:tracking:driver:' . $driver->uuid, 1, self::LOCATION_INTERVAL)) {
115
+ return 0;
116
+ }
117
+
118
+ $connection = $driver->getConnectionName();
119
+
120
+ return $this->queueLocations($this->enRouteOrders($connection)->where('driver_assigned_uuid', $driver->uuid)->limit(self::MAX_ORDERS_PER_SUBJECT)->pluck('uuid'), $connection);
121
+ }
122
+
123
+ /**
124
+ * A vehicle reported a position: queue a location update for each en-route order it serves.
125
+ */
126
+ public function vehicleMoved(Vehicle $vehicle): int
127
+ {
128
+ if (!static::enabled() || !$vehicle->uuid || !Cache::add('fleetops:tracking:vehicle:' . $vehicle->uuid, 1, self::LOCATION_INTERVAL)) {
129
+ return 0;
130
+ }
131
+
132
+ $connection = $vehicle->getConnectionName();
133
+ $drivers = Driver::on($connection)->where('vehicle_uuid', $vehicle->uuid)->select('uuid')->toBase();
134
+ $orders = $this->enRouteOrders($connection)
135
+ ->where(function ($query) use ($vehicle, $drivers) {
136
+ $query->where('vehicle_assigned_uuid', $vehicle->uuid)->orWhereIn('driver_assigned_uuid', $drivers);
137
+ })
138
+ ->limit(self::MAX_ORDERS_PER_SUBJECT)
139
+ ->pluck('uuid');
140
+
141
+ return $this->queueLocations($orders, $connection);
142
+ }
143
+
144
+ /**
145
+ * Publish the full update to every customer of the order. Returns the channels published to.
146
+ */
147
+ public function publishOrder(Order $order, string $reason = TrackingUpdate::REASON_STATUS): int
148
+ {
149
+ if (!static::enabled()) {
150
+ return 0;
151
+ }
152
+
153
+ $stops = $this->stopsFor($order);
154
+ $tracker = $this->trackerFor($order);
155
+ $sent = 0;
156
+
157
+ foreach (TrackingScope::allForOrder($order, $this->waypointsOf($stops)) as $scope) {
158
+ $update = TrackingUpdate::for($order, $scope, $stops, $tracker);
159
+ $sent += (int) $this->send(TrackingChannel::name($scope), $update->toArray($reason));
160
+ }
161
+
162
+ return $sent;
163
+ }
164
+
165
+ /**
166
+ * Publish the position to every customer of the order who may see it. Returns the channels published to.
167
+ */
168
+ public function publishLocation(Order $order): int
169
+ {
170
+ if (!static::enabled()) {
171
+ return 0;
172
+ }
173
+
174
+ $stops = $this->stopsFor($order);
175
+ $sent = 0;
176
+
177
+ foreach (TrackingScope::allForOrder($order, $this->waypointsOf($stops)) as $scope) {
178
+ $payload = (new TrackingUpdate($order, $scope, $stops))->locationPayload();
179
+ if ($payload !== null) {
180
+ $sent += (int) $this->send(TrackingChannel::name($scope), $payload);
181
+ }
182
+ }
183
+
184
+ return $sent;
185
+ }
186
+
187
+ /**
188
+ * Every stop of the order in route order, with completion state.
189
+ *
190
+ * @return Collection<int, TrackingStop>
191
+ */
192
+ protected function stopsFor(Order $order): Collection
193
+ {
194
+ return app(TrackingContextBuilder::class)->build($order, TrackingOptions::fromArray([], $order->company_uuid))->stops;
195
+ }
196
+
197
+ /**
198
+ * The tracking intelligence result for the order's ETAs; empty when it cannot be computed.
199
+ */
200
+ protected function trackerFor(Order $order): array
201
+ {
202
+ if (in_array(strtolower((string) $order->status), TrackingUpdate::TERMINAL_STATUSES, true)) {
203
+ return [];
204
+ }
205
+
206
+ try {
207
+ return app(TrackingIntelligenceService::class)->track($order);
208
+ } catch (\Throwable) {
209
+ return [];
210
+ }
211
+ }
212
+
213
+ protected function send(string $channel, array $payload): bool
214
+ {
215
+ return SocketClusterService::publish($channel, $payload);
216
+ }
217
+
218
+ protected function queue(string $orderUuid, string $reason, ?string $connection = null): bool
219
+ {
220
+ PublishTrackingUpdate::dispatch($orderUuid, $reason, $connection);
221
+
222
+ return true;
223
+ }
224
+
225
+ /**
226
+ * Queue a location update for each order not updated within the location interval.
227
+ */
228
+ protected function queueLocations(iterable $orderUuids, ?string $connection): int
229
+ {
230
+ $queued = 0;
231
+ foreach ($orderUuids as $orderUuid) {
232
+ if (Cache::add('fleetops:tracking:location:' . $orderUuid, 1, self::LOCATION_INTERVAL)) {
233
+ $queued += (int) $this->queue($orderUuid, TrackingUpdate::REASON_LOCATION, $connection);
234
+ }
235
+ }
236
+
237
+ return $queued;
238
+ }
239
+
240
+ /**
241
+ * Orders that have started and are not yet completed or canceled.
242
+ */
243
+ protected function enRouteOrders(?string $connection)
244
+ {
245
+ return Order::on($connection)
246
+ ->whereNotIn('status', TrackingUpdate::TERMINAL_STATUSES)
247
+ ->where(function ($query) {
248
+ $query->where('started', 1)->orWhereNotNull('started_at')->orWhere('status', 'started');
249
+ });
250
+ }
251
+
252
+ /**
253
+ * @param Collection<int, TrackingStop> $stops
254
+ *
255
+ * @return array<int, Waypoint>
256
+ */
257
+ protected function waypointsOf(Collection $stops): array
258
+ {
259
+ return $stops->map(fn (TrackingStop $stop) => $stop->waypoint)->filter()->values()->all();
260
+ }
261
+ }
@@ -0,0 +1,182 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support;
4
+
5
+ use Fleetbase\FleetOps\Models\Order;
6
+ use Fleetbase\FleetOps\Models\Waypoint;
7
+ use Illuminate\Database\Eloquent\Model;
8
+ use Illuminate\Database\Eloquent\Relations\Relation;
9
+
10
+ /**
11
+ * One customer's view of one order, the unit a public tracking channel is scoped to.
12
+ *
13
+ * A stop's customer is the waypoint's `customer` morph (a Contact or a Vendor); stops without
14
+ * one belong to the order's customer. Every customer of an order therefore gets their own
15
+ * scope, their own `tracking.{opaque}` channel and their own sanitized updates.
16
+ *
17
+ * Identity is (order_uuid, customer_type, customer_uuid). customer_type is the morph class as
18
+ * stored on the order or waypoint (a fully qualified model class), normalized without a
19
+ * leading backslash so that every caller derives the same channel.
20
+ */
21
+ final class TrackingScope implements \JsonSerializable
22
+ {
23
+ public readonly string $order_uuid;
24
+
25
+ public readonly string $customer_type;
26
+
27
+ public readonly string $customer_uuid;
28
+
29
+ /**
30
+ * The order's company, when known. Not part of the scope's identity.
31
+ */
32
+ public readonly ?string $company_uuid;
33
+
34
+ private ?Order $order;
35
+
36
+ public function __construct(string $orderUuid, string $customerType, string $customerUuid, ?Order $order = null)
37
+ {
38
+ $customerType = static::normalizeType($customerType);
39
+
40
+ if ($orderUuid === '' || $customerType === '' || $customerUuid === '') {
41
+ throw new \InvalidArgumentException('A tracking scope requires an order, a customer type and a customer.');
42
+ }
43
+
44
+ $this->order_uuid = $orderUuid;
45
+ $this->customer_type = $customerType;
46
+ $this->customer_uuid = $customerUuid;
47
+ $this->order = $order && $order->uuid === $orderUuid ? $order : null;
48
+ $this->company_uuid = $this->order?->company_uuid ?: null;
49
+ }
50
+
51
+ /**
52
+ * The scope of the order's own customer, or null when the order has none.
53
+ */
54
+ public static function forOrder(Order $order): ?self
55
+ {
56
+ return static::make($order, $order->customer_type, $order->customer_uuid);
57
+ }
58
+
59
+ /**
60
+ * The scope a stop belongs to: the waypoint's customer, else the order's customer.
61
+ */
62
+ public static function forStop(Order $order, ?Waypoint $waypoint = null): ?self
63
+ {
64
+ return static::make($order, $waypoint?->customer_type, $waypoint?->customer_uuid) ?? static::forOrder($order);
65
+ }
66
+
67
+ /**
68
+ * The scope of an explicit customer on an order.
69
+ */
70
+ public static function forCustomer(Order $order, Model $customer): self
71
+ {
72
+ return new self((string) $order->uuid, $customer->getMorphClass(), (string) $customer->uuid, $order);
73
+ }
74
+
75
+ /**
76
+ * Every customer scope of an order: the order's customer first, then each distinct waypoint customer.
77
+ *
78
+ * @param iterable<Waypoint>|null $waypoints the order's waypoints; loaded from the payload when null
79
+ *
80
+ * @return array<int, self>
81
+ */
82
+ public static function allForOrder(Order $order, ?iterable $waypoints = null): array
83
+ {
84
+ if ($waypoints === null) {
85
+ $waypoints = $order->payload_uuid ? Waypoint::on($order->getConnectionName())->where('payload_uuid', $order->payload_uuid)->without(['place'])->get(['uuid', 'payload_uuid', 'customer_uuid', 'customer_type']) : [];
86
+ }
87
+
88
+ $scopes = [];
89
+ if ($scope = static::forOrder($order)) {
90
+ $scopes[$scope->key()] = $scope;
91
+ }
92
+
93
+ foreach ($waypoints as $waypoint) {
94
+ $scope = $waypoint instanceof Waypoint ? static::make($order, $waypoint->customer_type, $waypoint->customer_uuid) : null;
95
+ if ($scope && !isset($scopes[$scope->key()])) {
96
+ $scopes[$scope->key()] = $scope;
97
+ }
98
+ }
99
+
100
+ return array_values($scopes);
101
+ }
102
+
103
+ /**
104
+ * The message the channel id is derived from: "{order_uuid}:{customer_type}:{customer_uuid}".
105
+ */
106
+ public function key(): string
107
+ {
108
+ return $this->order_uuid . ':' . $this->customer_type . ':' . $this->customer_uuid;
109
+ }
110
+
111
+ /**
112
+ * Whether this scope is the given customer (type and uuid).
113
+ */
114
+ public function is(?string $customerType, ?string $customerUuid): bool
115
+ {
116
+ return $customerUuid !== null
117
+ && $customerUuid !== ''
118
+ && $customerUuid === $this->customer_uuid
119
+ && static::normalizeType((string) $customerType) === $this->customer_type;
120
+ }
121
+
122
+ /**
123
+ * Whether this scope is the order's own customer.
124
+ */
125
+ public function isOrderCustomer(Order $order): bool
126
+ {
127
+ return $order->uuid === $this->order_uuid && $this->is($order->customer_type, $order->customer_uuid);
128
+ }
129
+
130
+ /**
131
+ * Whether a stop belongs to this scope: its waypoint's customer, else the order's customer.
132
+ */
133
+ public function ownsStop(Order $order, ?Waypoint $waypoint = null): bool
134
+ {
135
+ if ($waypoint && $waypoint->customer_uuid && $waypoint->customer_type) {
136
+ return $this->is($waypoint->customer_type, $waypoint->customer_uuid);
137
+ }
138
+
139
+ return $this->isOrderCustomer($order);
140
+ }
141
+
142
+ /**
143
+ * The order this scope is about, loaded once when not given.
144
+ */
145
+ public function order(): ?Order
146
+ {
147
+ return $this->order ??= Order::where('uuid', $this->order_uuid)->first();
148
+ }
149
+
150
+ public function toArray(): array
151
+ {
152
+ return [
153
+ 'order_uuid' => $this->order_uuid,
154
+ 'customer_type' => $this->customer_type,
155
+ 'customer_uuid' => $this->customer_uuid,
156
+ ];
157
+ }
158
+
159
+ public function jsonSerialize(): array
160
+ {
161
+ return $this->toArray();
162
+ }
163
+
164
+ /**
165
+ * A morph type as a model class without a leading backslash; morph map aliases resolve to their class.
166
+ */
167
+ public static function normalizeType(string $type): string
168
+ {
169
+ $type = ltrim(trim($type), '\\');
170
+
171
+ return ltrim((string) (Relation::getMorphedModel($type) ?? $type), '\\');
172
+ }
173
+
174
+ private static function make(Order $order, ?string $customerType, ?string $customerUuid): ?self
175
+ {
176
+ if (!$order->uuid || !$customerType || !$customerUuid) {
177
+ return null;
178
+ }
179
+
180
+ return new self((string) $order->uuid, $customerType, $customerUuid, $order);
181
+ }
182
+ }