@fleetbase/fleetops-engine 0.6.38 → 0.6.40

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 (432) hide show
  1. package/addon/components/activity/form.hbs +2 -12
  2. package/addon/components/activity/logic-builder.hbs +1 -1
  3. package/addon/components/custom-entity/form.hbs +4 -20
  4. package/addon/components/device/form.hbs +3 -15
  5. package/addon/components/device/panel-header.hbs +13 -2
  6. package/addon/components/driver/details.hbs +33 -2
  7. package/addon/components/driver/form.hbs +42 -0
  8. package/addon/components/driver/panel-header.hbs +8 -1
  9. package/addon/components/driver/schedule.hbs +115 -76
  10. package/addon/components/driver/schedule.js +349 -157
  11. package/addon/components/driver-onboard-settings.hbs +2 -8
  12. package/addon/components/entity-field-editing-settings.hbs +2 -5
  13. package/addon/components/equipment/card.hbs +49 -0
  14. package/addon/components/equipment/card.js +6 -0
  15. package/addon/components/equipment/details.hbs +83 -44
  16. package/addon/components/equipment/form.hbs +111 -41
  17. package/addon/components/equipment/form.js +78 -10
  18. package/addon/components/equipment/panel-header.hbs +36 -0
  19. package/addon/components/equipment/panel-header.js +2 -0
  20. package/addon/components/fleet/driver-listing.hbs +3 -1
  21. package/addon/components/fleet/vehicle-listing.hbs +4 -7
  22. package/addon/components/fleet-panel/vehicle-listing.hbs +1 -6
  23. package/addon/components/fuel-report/form.hbs +1 -5
  24. package/addon/components/layout/fleet-ops-sidebar.hbs +43 -38
  25. package/addon/components/layout/fleet-ops-sidebar.js +103 -24
  26. package/addon/components/maintenance/cost-panel.hbs +176 -0
  27. package/addon/components/maintenance/cost-panel.js +241 -0
  28. package/addon/components/maintenance/details.hbs +123 -60
  29. package/addon/components/maintenance/form.hbs +138 -78
  30. package/addon/components/maintenance/form.js +131 -6
  31. package/addon/components/maintenance/panel-header.hbs +31 -0
  32. package/addon/components/maintenance/panel-header.js +2 -0
  33. package/addon/components/maintenance-schedule/details.hbs +260 -0
  34. package/addon/components/maintenance-schedule/details.js +158 -0
  35. package/addon/components/maintenance-schedule/form.hbs +287 -0
  36. package/addon/components/maintenance-schedule/form.js +199 -0
  37. package/addon/components/map/container.hbs +1 -1
  38. package/addon/components/map/drawer/device-event-listing.hbs +1 -1
  39. package/addon/components/map/drawer/driver-listing.hbs +1 -6
  40. package/addon/components/map/drawer/place-listing.hbs +1 -6
  41. package/addon/components/map/drawer/vehicle-listing.hbs +1 -6
  42. package/addon/components/map/drawer.hbs +8 -1
  43. package/addon/components/map/leaflet-live-map.hbs +2 -1
  44. package/addon/components/map/toolbar/visibility-control-panel.hbs +1 -1
  45. package/addon/components/map/toolbar.hbs +4 -29
  46. package/addon/components/modals/add-driver-shift.hbs +155 -0
  47. package/addon/components/modals/add-driver-shift.js +210 -0
  48. package/addon/components/modals/bulk-assign-orders.hbs +67 -0
  49. package/addon/components/modals/driver-shift.hbs +43 -0
  50. package/addon/components/modals/driver-shift.js +56 -0
  51. package/addon/components/modals/entity-form.hbs +5 -28
  52. package/addon/components/modals/orchestrator-import.hbs +351 -0
  53. package/addon/components/modals/orchestrator-import.js +807 -0
  54. package/addon/components/modals/order-config-new-status.hbs +1 -5
  55. package/addon/components/modals/scheduling-conflict.hbs +47 -0
  56. package/addon/components/modals/scheduling-conflict.js +53 -0
  57. package/addon/components/modals/send-work-order.hbs +91 -0
  58. package/addon/components/modals/send-work-order.js +3 -0
  59. package/addon/components/modals/service-area-form.hbs +1 -6
  60. package/addon/components/modals/set-driver-availability.hbs +50 -0
  61. package/addon/components/modals/set-driver-availability.js +57 -0
  62. package/addon/components/modals/update-order-activity.hbs +13 -9
  63. package/addon/components/modals/user-form.hbs +1 -5
  64. package/addon/components/modals/vehicle-form.hbs +17 -102
  65. package/addon/components/modals/vendor-form.hbs +15 -82
  66. package/addon/components/orchestrator/card-fields-settings.hbs +76 -0
  67. package/addon/components/orchestrator/card-fields-settings.js +134 -0
  68. package/addon/components/orchestrator/order-pool.hbs +264 -0
  69. package/addon/components/orchestrator/order-pool.js +394 -0
  70. package/addon/components/orchestrator/phase-builder.hbs +162 -0
  71. package/addon/components/orchestrator/phase-builder.js +162 -0
  72. package/addon/components/orchestrator/plan-viewer.hbs +278 -0
  73. package/addon/components/orchestrator/plan-viewer.js +342 -0
  74. package/addon/components/orchestrator/resource-panel.hbs +301 -0
  75. package/addon/components/orchestrator/resource-panel.js +106 -0
  76. package/addon/components/orchestrator-workbench.hbs +318 -0
  77. package/addon/components/orchestrator-workbench.js +1088 -0
  78. package/addon/components/order/details/custom-fields.hbs +10 -1
  79. package/addon/components/order/details/detail.hbs +37 -2
  80. package/addon/components/order/details/detail.js +0 -1
  81. package/addon/components/order/details/integrated-vendor-details.hbs +4 -4
  82. package/addon/components/order/details/notes.hbs +1 -7
  83. package/addon/components/order/details/route.hbs +1 -5
  84. package/addon/components/order/form/details.hbs +44 -10
  85. package/addon/components/order/form/details.js +57 -0
  86. package/addon/components/order/form/notes.hbs +1 -7
  87. package/addon/components/order/form/payload.hbs +1 -7
  88. package/addon/components/order/form/route.hbs +3 -15
  89. package/addon/components/order/header.hbs +1 -7
  90. package/addon/components/order/route-editor.hbs +4 -25
  91. package/addon/components/order/schedule-card.hbs +102 -95
  92. package/addon/components/order/schedule-card.js +8 -3
  93. package/addon/components/order-config-manager/details.hbs +2 -10
  94. package/addon/components/order-config-manager/entities.hbs +1 -7
  95. package/addon/components/order-config-manager.hbs +1 -6
  96. package/addon/components/part/card.hbs +49 -0
  97. package/addon/components/part/card.js +6 -0
  98. package/addon/components/part/details.hbs +102 -56
  99. package/addon/components/part/form.hbs +131 -56
  100. package/addon/components/part/form.js +78 -11
  101. package/addon/components/part/panel-header.hbs +36 -0
  102. package/addon/components/part/panel-header.js +2 -0
  103. package/addon/components/place/form.hbs +1 -7
  104. package/addon/components/sensor/details.hbs +1 -1
  105. package/addon/components/sensor/form.hbs +5 -3
  106. package/addon/components/sensor/panel-header.hbs +8 -1
  107. package/addon/components/service-area/form.hbs +1 -6
  108. package/addon/components/service-rate/form.hbs +12 -60
  109. package/addon/components/telematic/form.hbs +6 -2
  110. package/addon/components/vehicle/details/maintenance-history.hbs +42 -0
  111. package/addon/components/vehicle/details/maintenance-history.js +32 -0
  112. package/addon/components/vehicle/details/schedules.hbs +40 -0
  113. package/addon/components/vehicle/details/schedules.js +32 -0
  114. package/addon/components/vehicle/details/work-orders.hbs +42 -0
  115. package/addon/components/vehicle/details/work-orders.js +32 -0
  116. package/addon/components/vehicle/details.hbs +52 -0
  117. package/addon/components/vehicle/form.hbs +51 -0
  118. package/addon/components/vehicle/panel-header.hbs +19 -49
  119. package/addon/components/warranty/details.hbs +3 -2
  120. package/addon/components/warranty/form.hbs +3 -17
  121. package/addon/components/work-order/details.hbs +135 -40
  122. package/addon/components/work-order/form.hbs +178 -45
  123. package/addon/components/work-order/form.js +197 -4
  124. package/addon/components/work-order/panel-header.hbs +31 -0
  125. package/addon/components/work-order/panel-header.js +2 -0
  126. package/addon/controllers/connectivity/devices/index/details.js +1 -1
  127. package/addon/controllers/maintenance/equipment/index/details/index.js +0 -1
  128. package/addon/controllers/maintenance/equipment/index/details.js +36 -1
  129. package/addon/controllers/maintenance/equipment/index/edit.js +56 -1
  130. package/addon/controllers/maintenance/equipment/index/new.js +32 -1
  131. package/addon/controllers/maintenance/equipment/index.js +127 -113
  132. package/addon/controllers/maintenance/maintenances/index/details/index.js +3 -0
  133. package/addon/controllers/maintenance/maintenances/index/details.js +54 -0
  134. package/addon/controllers/maintenance/maintenances/index/edit.js +68 -0
  135. package/addon/controllers/maintenance/maintenances/index/new.js +34 -0
  136. package/addon/controllers/maintenance/maintenances/index.js +191 -0
  137. package/addon/controllers/maintenance/parts/index/details/index.js +0 -1
  138. package/addon/controllers/maintenance/parts/index/details.js +36 -1
  139. package/addon/controllers/maintenance/parts/index/edit.js +56 -1
  140. package/addon/controllers/maintenance/parts/index/new.js +32 -1
  141. package/addon/controllers/maintenance/parts/index.js +135 -113
  142. package/addon/controllers/maintenance/schedules/index/details.js +115 -0
  143. package/addon/controllers/maintenance/schedules/index/edit.js +41 -0
  144. package/addon/controllers/maintenance/schedules/index/new.js +33 -0
  145. package/addon/controllers/maintenance/schedules/index.js +280 -0
  146. package/addon/controllers/maintenance/work-orders/index/details.js +41 -1
  147. package/addon/controllers/maintenance/work-orders/index/edit.js +67 -1
  148. package/addon/controllers/maintenance/work-orders/index/new.js +43 -1
  149. package/addon/controllers/maintenance/work-orders/index.js +105 -113
  150. package/addon/controllers/management/drivers/index/details.js +6 -1
  151. package/addon/controllers/management/vehicles/index/details.js +65 -0
  152. package/addon/controllers/management/vehicles/index.js +18 -0
  153. package/addon/controllers/operations/orchestrator.js +10 -0
  154. package/addon/controllers/operations/orders/index.js +6 -0
  155. package/addon/controllers/operations/scheduler/fleet-schedule.js +341 -0
  156. package/addon/controllers/operations/scheduler/index.js +799 -275
  157. package/addon/controllers/operations/scheduler.js +21 -0
  158. package/addon/controllers/settings/orchestrator.js +70 -0
  159. package/addon/controllers/settings/scheduling.js +155 -0
  160. package/addon/extension.js +19 -0
  161. package/addon/instance-initializers/register-vroom-allocation.js +27 -0
  162. package/addon/models/maintenance-schedule.js +61 -0
  163. package/addon/routes/maintenance/equipment/index/details.js +27 -1
  164. package/addon/routes/maintenance/equipment/index/edit.js +27 -1
  165. package/addon/routes/maintenance/maintenances/index/details/index.js +3 -0
  166. package/addon/routes/maintenance/maintenances/index/details.js +29 -0
  167. package/addon/routes/maintenance/maintenances/index/edit.js +29 -0
  168. package/addon/routes/maintenance/maintenances/index/new.js +3 -0
  169. package/addon/routes/maintenance/maintenances/index.js +23 -0
  170. package/addon/routes/maintenance/maintenances.js +3 -0
  171. package/addon/routes/maintenance/parts/index/details.js +27 -1
  172. package/addon/routes/maintenance/parts/index/edit.js +27 -1
  173. package/addon/routes/maintenance/schedules/index/details/index.js +2 -0
  174. package/addon/routes/maintenance/schedules/index/details/work-orders.js +11 -0
  175. package/addon/routes/maintenance/schedules/index/details.js +25 -0
  176. package/addon/routes/maintenance/schedules/index/edit.js +25 -0
  177. package/addon/routes/maintenance/schedules/index/new.js +2 -0
  178. package/addon/routes/maintenance/schedules/index.js +21 -0
  179. package/addon/routes/maintenance/schedules.js +2 -0
  180. package/addon/routes/maintenance/work-orders/index/details.js +27 -1
  181. package/addon/routes/maintenance/work-orders/index/edit.js +27 -1
  182. package/addon/routes/management/vehicles/index/details/maintenance-history.js +3 -0
  183. package/addon/routes/management/vehicles/index/details/schedules.js +3 -0
  184. package/addon/routes/management/vehicles/index/details/work-orders.js +3 -0
  185. package/addon/routes/operations/orchestrator.js +23 -0
  186. package/addon/routes/operations/scheduler/fleet-schedule.js +28 -0
  187. package/addon/routes/operations/scheduler/index.js +48 -26
  188. package/addon/routes/operations/scheduler.js +14 -1
  189. package/addon/routes/settings/orchestrator.js +27 -0
  190. package/addon/routes/settings/scheduling.js +3 -0
  191. package/addon/routes.js +31 -1
  192. package/addon/services/driver-actions.js +40 -7
  193. package/addon/services/driver-scheduling.js +4 -1
  194. package/addon/services/equipment-actions.js +15 -5
  195. package/addon/services/leaflet-map-manager.js +14 -6
  196. package/addon/services/maintenance-actions.js +17 -14
  197. package/addon/services/maintenance-schedule-actions.js +118 -0
  198. package/addon/services/orchestration-engine-interface.js +49 -0
  199. package/addon/services/orchestration-engine.js +74 -0
  200. package/addon/services/order-actions.js +15 -0
  201. package/addon/services/order-allocation.js +116 -0
  202. package/addon/services/part-actions.js +12 -2
  203. package/addon/services/scheduling.js +316 -0
  204. package/addon/services/vehicle-actions.js +70 -7
  205. package/addon/services/vroom-allocation-engine.js +45 -0
  206. package/addon/services/work-order-actions.js +79 -0
  207. package/addon/styles/fleetops-engine.css +1658 -0
  208. package/addon/templates/analytics/reports/index/edit.hbs +1 -1
  209. package/addon/templates/analytics/reports/index/new.hbs +1 -1
  210. package/addon/templates/application.hbs +6 -1
  211. package/addon/templates/connectivity/devices/index/details/events.hbs +0 -1
  212. package/addon/templates/connectivity/devices.hbs +0 -1
  213. package/addon/templates/connectivity/events/index/details.hbs +0 -1
  214. package/addon/templates/connectivity/events.hbs +0 -1
  215. package/addon/templates/connectivity/sensors.hbs +0 -1
  216. package/addon/templates/connectivity/telematics/index/details/devices.hbs +0 -1
  217. package/addon/templates/connectivity/telematics/index/details/events.hbs +0 -1
  218. package/addon/templates/connectivity/telematics/index/details/sensors.hbs +0 -1
  219. package/addon/templates/connectivity/telematics.hbs +0 -1
  220. package/addon/templates/connectivity/tracking.hbs +0 -1
  221. package/addon/templates/connectivity.hbs +0 -1
  222. package/addon/templates/maintenance/equipment/index/details/index.hbs +1 -2
  223. package/addon/templates/maintenance/equipment/index/details.hbs +15 -2
  224. package/addon/templates/maintenance/equipment/index/edit.hbs +12 -2
  225. package/addon/templates/maintenance/equipment/index/new.hbs +1 -2
  226. package/addon/templates/maintenance/equipment/index.hbs +48 -13
  227. package/addon/templates/maintenance/equipment.hbs +0 -1
  228. package/addon/templates/maintenance/maintenances/index/details/index.hbs +1 -0
  229. package/addon/templates/maintenance/maintenances/index/details.hbs +15 -0
  230. package/addon/templates/maintenance/maintenances/index/edit.hbs +12 -0
  231. package/addon/templates/maintenance/maintenances/index/new.hbs +11 -0
  232. package/addon/templates/maintenance/maintenances/index.hbs +14 -0
  233. package/addon/templates/maintenance/maintenances.hbs +1 -0
  234. package/addon/templates/maintenance/parts/index/details/index.hbs +1 -2
  235. package/addon/templates/maintenance/parts/index/details.hbs +15 -2
  236. package/addon/templates/maintenance/parts/index/edit.hbs +12 -2
  237. package/addon/templates/maintenance/parts/index/new.hbs +1 -2
  238. package/addon/templates/maintenance/parts/index.hbs +48 -13
  239. package/addon/templates/maintenance/parts.hbs +0 -1
  240. package/addon/templates/maintenance/schedules/index/details/index.hbs +1 -0
  241. package/addon/templates/maintenance/schedules/index/details/work-orders.hbs +39 -0
  242. package/addon/templates/maintenance/schedules/index/details.hbs +14 -0
  243. package/addon/templates/maintenance/schedules/index/edit.hbs +12 -0
  244. package/addon/templates/maintenance/schedules/index/new.hbs +11 -0
  245. package/addon/templates/maintenance/schedules/index.hbs +40 -0
  246. package/addon/templates/maintenance/schedules.hbs +1 -0
  247. package/addon/templates/maintenance/work-orders/index/details.hbs +2 -1
  248. package/addon/templates/maintenance/work-orders/index/edit.hbs +2 -4
  249. package/addon/templates/maintenance/work-orders/index/new.hbs +1 -2
  250. package/addon/templates/maintenance/work-orders.hbs +0 -1
  251. package/addon/templates/management/contacts/customers/edit.hbs +1 -2
  252. package/addon/templates/management/contacts/customers/new.hbs +1 -2
  253. package/addon/templates/management/contacts/customers.hbs +1 -1
  254. package/addon/templates/management/contacts/index/edit.hbs +1 -2
  255. package/addon/templates/management/contacts/index/new.hbs +1 -2
  256. package/addon/templates/management/drivers/index/details/orders.hbs +0 -1
  257. package/addon/templates/management/drivers/index/edit.hbs +1 -2
  258. package/addon/templates/management/drivers/index/new.hbs +1 -2
  259. package/addon/templates/management/fleets/index/edit.hbs +1 -2
  260. package/addon/templates/management/fleets/index/new.hbs +1 -2
  261. package/addon/templates/management/fleets/index.hbs +1 -2
  262. package/addon/templates/management/fuel-reports/index/edit.hbs +1 -2
  263. package/addon/templates/management/fuel-reports/index/new.hbs +1 -2
  264. package/addon/templates/management/fuel-reports/index.hbs +1 -2
  265. package/addon/templates/management/issues/index/edit.hbs +1 -2
  266. package/addon/templates/management/issues/index/new.hbs +1 -2
  267. package/addon/templates/management/issues/index.hbs +1 -2
  268. package/addon/templates/management/places/index/details/activity.hbs +0 -1
  269. package/addon/templates/management/places/index/details/comments.hbs +0 -1
  270. package/addon/templates/management/places/index/details/documents.hbs +0 -1
  271. package/addon/templates/management/places/index/details/map.hbs +0 -1
  272. package/addon/templates/management/places/index/details/operations.hbs +0 -1
  273. package/addon/templates/management/places/index/details/performance.hbs +0 -1
  274. package/addon/templates/management/places/index/details/rules.hbs +0 -1
  275. package/addon/templates/management/vehicles/index/details/equipment.hbs +0 -1
  276. package/addon/templates/management/vehicles/index/details/maintenance-history.hbs +2 -0
  277. package/addon/templates/management/vehicles/index/details/schedules.hbs +2 -0
  278. package/addon/templates/management/vehicles/index/details/work-orders.hbs +2 -0
  279. package/addon/templates/management/vehicles/index/details.hbs +1 -1
  280. package/addon/templates/management/vehicles/index/edit.hbs +1 -2
  281. package/addon/templates/management/vehicles/index/new.hbs +1 -2
  282. package/addon/templates/management/vendors/index/edit.hbs +1 -2
  283. package/addon/templates/management/vendors/index/new.hbs +1 -2
  284. package/addon/templates/management/vendors/index.hbs +1 -2
  285. package/addon/templates/management/vendors/integrated.hbs +1 -2
  286. package/addon/templates/operations/orchestrator.hbs +1 -0
  287. package/addon/templates/operations/orders/index.hbs +6 -1
  288. package/addon/templates/operations/scheduler/fleet-schedule.hbs +41 -0
  289. package/addon/templates/operations/scheduler/index.hbs +147 -88
  290. package/addon/templates/operations/scheduler.hbs +7 -1
  291. package/addon/templates/settings/avatars.hbs +1 -1
  292. package/addon/templates/settings/orchestrator.hbs +65 -0
  293. package/addon/templates/settings/payments/index.hbs +1 -5
  294. package/addon/templates/settings/scheduling.hbs +82 -0
  295. package/addon/utils/create-full-calendar-event-from-order.js +52 -14
  296. package/addon/utils/create-full-calendar-event-from-schedule-item.js +50 -0
  297. package/addon/utils/fleet-ops-options.js +254 -0
  298. package/addon/utils/route-colors.js +99 -0
  299. package/addon/utils/to-calendar-date.js +70 -0
  300. package/app/components/driver/schedule.js +1 -0
  301. package/app/components/maintenance/cost-panel.js +1 -0
  302. package/app/components/maintenance/panel-header.js +1 -0
  303. package/app/components/maintenance-schedule/details.js +1 -0
  304. package/app/components/maintenance-schedule/form.js +1 -0
  305. package/app/components/modals/add-driver-shift.js +1 -0
  306. package/app/components/modals/bulk-assign-orders.js +1 -0
  307. package/app/components/modals/driver-shift.js +1 -0
  308. package/app/components/modals/orchestrator-import.js +1 -0
  309. package/app/components/modals/scheduling-conflict.js +1 -0
  310. package/app/components/modals/send-work-order.js +1 -0
  311. package/app/components/modals/set-driver-availability.js +1 -0
  312. package/app/components/orchestrator/card-fields-settings.js +1 -0
  313. package/app/components/orchestrator/order-pool.js +1 -0
  314. package/app/components/orchestrator/phase-builder.js +1 -0
  315. package/app/components/orchestrator/plan-viewer.js +1 -0
  316. package/app/components/orchestrator/resource-panel.js +1 -0
  317. package/app/components/orchestrator-workbench.js +1 -0
  318. package/app/components/vehicle/details/maintenance-history.js +1 -0
  319. package/app/components/vehicle/details/schedules.js +1 -0
  320. package/app/components/vehicle/details/work-orders.js +1 -0
  321. package/app/controllers/operations/orchestrator.js +1 -0
  322. package/app/controllers/settings/orchestrator.js +1 -0
  323. package/app/controllers/settings/scheduling.js +1 -0
  324. package/app/routes/operations/orchestrator.js +1 -0
  325. package/app/routes/settings/orchestrator.js +1 -0
  326. package/app/routes/settings/scheduling.js +1 -0
  327. package/app/services/maintenance-schedule-actions.js +1 -0
  328. package/app/services/orchestration-engine-interface.js +1 -0
  329. package/app/services/orchestration-engine.js +1 -0
  330. package/app/services/order-allocation.js +1 -0
  331. package/app/services/scheduling.js +1 -0
  332. package/app/services/vroom-allocation-engine.js +1 -0
  333. package/app/templates/settings/scheduling.js +1 -0
  334. package/app/utils/create-full-calendar-event-from-schedule-item.js +1 -0
  335. package/app/utils/route-colors.js +1 -0
  336. package/composer.json +4 -2
  337. package/extension.json +1 -1
  338. package/package.json +6 -5
  339. package/server/config/fleetops.php +20 -1
  340. package/server/migrations/2025_08_28_054927_create_parts_table.php +2 -2
  341. package/server/migrations/2025_08_28_054932_add_public_id_to_maintenance_tables.php +45 -0
  342. package/server/migrations/2025_09_01_000001_create_maintenance_schedules_table.php +88 -0
  343. package/server/migrations/2026_04_01_000001_fix_monetary_columns_in_parts_table.php +48 -0
  344. package/server/migrations/2026_04_01_000003_add_photo_uuid_to_equipment_and_parts_tables.php +61 -0
  345. package/server/migrations/2026_04_01_000004_add_public_id_to_equipments_table.php +38 -0
  346. package/server/migrations/2026_04_01_000005_add_missing_columns_to_parts_table.php +67 -0
  347. package/server/migrations/2026_04_04_000001_add_reminder_offsets_to_maintenance_schedules.php +44 -0
  348. package/server/migrations/2026_04_08_000001_add_orchestrator_columns_to_vehicles_table.php +53 -0
  349. package/server/migrations/2026_04_08_000002_add_orchestrator_columns_to_drivers_table.php +41 -0
  350. package/server/migrations/2026_04_08_000003_add_orchestrator_columns_to_orders_table.php +38 -0
  351. package/server/migrations/2026_04_08_000004_add_orchestrator_columns_to_payloads_table.php +38 -0
  352. package/server/migrations/2026_04_08_000005_add_orchestrator_columns_to_waypoints_table.php +38 -0
  353. package/server/migrations/2026_04_09_000001_create_manifests_table.php +48 -0
  354. package/server/migrations/2026_04_09_000002_create_manifest_stops_table.php +48 -0
  355. package/server/migrations/2026_04_09_000003_add_manifest_uuid_to_orders_table.php +28 -0
  356. package/server/migrations/2026_04_13_000001_add_pod_notes_columns_to_waypoints_table.php +39 -0
  357. package/server/migrations/2026_04_14_000001_drop_redundant_capacity_columns_from_payloads_table.php +42 -0
  358. package/server/migrations/2026_04_14_000002_rename_capacity_columns_on_vehicles_table.php +48 -0
  359. package/server/resources/views/mail/maintenance-schedule-reminder.blade.php +59 -0
  360. package/server/resources/views/mail/work-order-dispatched.blade.php +67 -0
  361. package/server/src/Auth/Schemas/FleetOps.php +44 -0
  362. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +150 -0
  363. package/server/src/Console/Commands/SendMaintenanceReminders.php +128 -0
  364. package/server/src/Http/Controllers/Api/v1/OrderController.php +15 -2
  365. package/server/src/Http/Controllers/Api/v1/VehicleController.php +18 -2
  366. package/server/src/Http/Controllers/Internal/v1/DriverController.php +1 -0
  367. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +27 -0
  368. package/server/src/Http/Controllers/Internal/v1/LiveController.php +9 -2
  369. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +165 -0
  370. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +304 -0
  371. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +138 -0
  372. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +975 -0
  373. package/server/src/Http/Controllers/Internal/v1/OrderController.php +42 -0
  374. package/server/src/Http/Controllers/Internal/v1/PartController.php +27 -0
  375. package/server/src/Http/Controllers/Internal/v1/SettingController.php +118 -0
  376. package/server/src/Http/Controllers/Internal/v1/Traits/DriverSchedulingTrait.php +214 -0
  377. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +68 -0
  378. package/server/src/Http/Resources/v1/Driver.php +1 -0
  379. package/server/src/Http/Resources/v1/Maintenance.php +138 -0
  380. package/server/src/Http/Resources/v1/MaintenanceSchedule.php +137 -0
  381. package/server/src/Http/Resources/v1/Orchestrator/Order.php +116 -0
  382. package/server/src/Http/Resources/v1/Order.php +7 -4
  383. package/server/src/Http/Resources/v1/Waypoint.php +7 -0
  384. package/server/src/Http/Resources/v1/WorkOrder.php +136 -0
  385. package/server/src/Imports/EquipmentImport.php +32 -0
  386. package/server/src/Imports/MaintenanceImport.php +32 -0
  387. package/server/src/Imports/MaintenanceScheduleImport.php +32 -0
  388. package/server/src/Imports/PartImport.php +32 -0
  389. package/server/src/Imports/WorkOrderImport.php +32 -0
  390. package/server/src/Jobs/ProcessAllocationJob.php +119 -0
  391. package/server/src/Listeners/HandleDeliveryCompletion.php +47 -0
  392. package/server/src/Listeners/NotifyDriverOnShiftChange.php +63 -0
  393. package/server/src/Mail/MaintenanceScheduleReminder.php +68 -0
  394. package/server/src/Mail/WorkOrderDispatched.php +58 -0
  395. package/server/src/Models/Asset.php +2 -2
  396. package/server/src/Models/Device.php +1 -1
  397. package/server/src/Models/Driver.php +82 -4
  398. package/server/src/Models/Equipment.php +62 -2
  399. package/server/src/Models/Maintenance.php +127 -9
  400. package/server/src/Models/MaintenanceSchedule.php +353 -0
  401. package/server/src/Models/Manifest.php +214 -0
  402. package/server/src/Models/ManifestStop.php +162 -0
  403. package/server/src/Models/Order.php +70 -0
  404. package/server/src/Models/OrderConfig.php +5 -2
  405. package/server/src/Models/Part.php +69 -3
  406. package/server/src/Models/Place.php +1 -1
  407. package/server/src/Models/Sensor.php +1 -1
  408. package/server/src/Models/ServiceQuote.php +1 -1
  409. package/server/src/Models/Vehicle.php +18 -1
  410. package/server/src/Models/Warranty.php +1 -1
  411. package/server/src/Models/Waypoint.php +7 -1
  412. package/server/src/Models/WorkOrder.php +122 -12
  413. package/server/src/Notifications/DriverShiftChanged.php +110 -0
  414. package/server/src/Observers/WorkOrderObserver.php +107 -0
  415. package/server/src/Orchestration/Contracts/OrchestrationEngineInterface.php +63 -0
  416. package/server/src/Orchestration/Engines/DriverAssignmentEngine.php +265 -0
  417. package/server/src/Orchestration/Engines/GreedyOrchestrationEngine.php +155 -0
  418. package/server/src/Orchestration/Engines/RouteSequencingEngine.php +272 -0
  419. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +192 -0
  420. package/server/src/Orchestration/OrchestrationEngineRegistry.php +83 -0
  421. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +380 -0
  422. package/server/src/Providers/EventServiceProvider.php +7 -1
  423. package/server/src/Providers/FleetOpsServiceProvider.php +42 -15
  424. package/server/src/routes.php +65 -4
  425. package/translations/ar-ae.yml +44 -12
  426. package/translations/bg-bg.yaml +51 -10
  427. package/translations/en-us.yaml +444 -1
  428. package/translations/fr-fr.yaml +51 -10
  429. package/translations/mn-mn.yaml +51 -10
  430. package/translations/pt-br.yaml +51 -10
  431. package/translations/ru-ru.yaml +51 -10
  432. package/translations/vi-vn.yaml +48 -12
@@ -0,0 +1,192 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Orchestration\Engines;
4
+
5
+ use Fleetbase\FleetOps\Orchestration\Contracts\OrchestrationEngineInterface;
6
+ use Fleetbase\FleetOps\Orchestration\Support\OrchestrationPayloadBuilder;
7
+ use Fleetbase\Models\Setting;
8
+ use Illuminate\Support\Collection;
9
+ use Illuminate\Support\Facades\Http;
10
+ use Illuminate\Support\Facades\Log;
11
+
12
+ /**
13
+ * VroomOrchestrationEngine.
14
+ *
15
+ * Self-contained VROOM integration for the FleetOps Orchestrator Workbench.
16
+ * This engine does NOT depend on the optional `fleetbase/vroom` extension —
17
+ * it communicates directly with the VROOM HTTP API using the same base URI
18
+ * and API-key convention that the extension uses, so the two are compatible
19
+ * when both are installed.
20
+ *
21
+ * Resolution order for configuration:
22
+ *
23
+ * 1. Company-level setting — `Setting::lookupCompany('vroom', 'api_key')`
24
+ * (written by the fleetbase/vroom extension settings page, if installed)
25
+ * 2. Environment variable — `VROOM_API_KEY`
26
+ * 3. No key — requests are sent without an api_key parameter
27
+ * (works for self-hosted VROOM instances that do not require auth)
28
+ *
29
+ * Base URI: `config('vroom.base_uri')` → `VROOM_HOST` env → default production URL
30
+ * Timeout: `VROOM_TIMEOUT` env (default 30 s)
31
+ *
32
+ * The VROOM HTTP API endpoint used is `POST {base_uri}/solve`, matching the
33
+ * verso-optim.com hosted service and the fleetbase/vroom extension.
34
+ */
35
+ class VroomOrchestrationEngine implements OrchestrationEngineInterface
36
+ {
37
+ public function getName(): string
38
+ {
39
+ return 'VROOM';
40
+ }
41
+
42
+ public function getIdentifier(): string
43
+ {
44
+ return 'vroom';
45
+ }
46
+
47
+ /**
48
+ * Run the VROOM VRP solver.
49
+ *
50
+ * Constructs a VROOM-format payload from the normalized jobs/vehicles
51
+ * produced by OrchestrationPayloadBuilder, calls the VROOM HTTP API, and
52
+ * maps the response back to the standard OrchestrationEngineInterface result
53
+ * shape.
54
+ *
55
+ * @throws \RuntimeException if the VROOM API is unreachable or returns an error
56
+ */
57
+ public function allocate(Collection $orders, Collection $vehicles, array $options = []): array
58
+ {
59
+ $jobs = OrchestrationPayloadBuilder::buildJobs($orders);
60
+ $vroomVehicles = OrchestrationPayloadBuilder::buildVehicles($vehicles);
61
+
62
+ if (empty($jobs)) {
63
+ return ['assignments' => [], 'unassigned' => [], 'summary' => []];
64
+ }
65
+
66
+ // Map normalized vehicle entries to VROOM vehicle objects.
67
+ // The 'driver_id' key is not part of the VROOM spec — we carry it in
68
+ // a description field and use it when mapping results back.
69
+ $vroomPayload = [
70
+ 'jobs' => $jobs,
71
+ 'vehicles' => array_map(function (array $v) {
72
+ $vehicle = [
73
+ 'id' => crc32($v['id']), // VROOM requires integer IDs
74
+ 'description' => json_encode(['vehicle_id' => $v['id'], 'driver_id' => $v['driver_id']]),
75
+ 'start' => $v['start'],
76
+ 'capacity' => $v['capacity'],
77
+ ];
78
+ if (isset($v['time_window'])) {
79
+ $vehicle['time_window'] = $v['time_window'];
80
+ }
81
+ if (isset($v['skills'])) {
82
+ $vehicle['skills'] = $v['skills'];
83
+ }
84
+
85
+ return $vehicle;
86
+ }, $vroomVehicles),
87
+ 'options' => [
88
+ 'g' => $options['geometry'] ?? false,
89
+ ],
90
+ ];
91
+
92
+ // Map job IDs to integer IDs for VROOM, keeping a reverse lookup
93
+ $jobIdMap = [];
94
+ $jobIdReverse = [];
95
+ foreach ($vroomPayload['jobs'] as &$job) {
96
+ $intId = crc32($job['id']);
97
+ $jobIdReverse[$intId] = $job['id'];
98
+ $job['id'] = $intId;
99
+ $jobIdMap[$job['id']] = true;
100
+ }
101
+ unset($job);
102
+
103
+ // ── Resolve connection config ─────────────────────────────────────────
104
+ // Base URI: prefer the vroom extension config, then VROOM_HOST env, then
105
+ // the production verso-optim.com hosted service.
106
+ $baseUri = config('vroom.base_uri', env('VROOM_HOST', 'https://api.verso-optim.com/vrp/v1'));
107
+ $timeout = (int) env('VROOM_TIMEOUT', 30);
108
+
109
+ // ── Resolve API key ───────────────────────────────────────────────────
110
+ // 1. Company-level setting (written by fleetbase/vroom extension UI)
111
+ // 2. VROOM_API_KEY env var
112
+ // 3. null — no key appended (for self-hosted instances)
113
+ $apiKey = null;
114
+ try {
115
+ $apiKey = Setting::lookupCompany('vroom', ['api_key' => null])['api_key'] ?? null;
116
+ } catch (\Throwable) {
117
+ // Setting table may not exist in minimal installs — ignore
118
+ }
119
+ if (!$apiKey) {
120
+ $apiKey = env('VROOM_API_KEY');
121
+ }
122
+
123
+ // Build the solve URL — append api_key as query param when present
124
+ $solveUrl = rtrim($baseUri, '/') . '/solve';
125
+ if ($apiKey) {
126
+ $solveUrl .= '?api_key=' . urlencode($apiKey);
127
+ }
128
+
129
+ // ── Call VROOM ────────────────────────────────────────────────────────
130
+ try {
131
+ $response = Http::timeout($timeout)->post($solveUrl, $vroomPayload);
132
+ } catch (\Exception $e) {
133
+ Log::error('[VroomOrchestrationEngine] HTTP request failed: ' . $e->getMessage());
134
+ throw new \RuntimeException('VROOM allocation engine is unavailable: ' . $e->getMessage() . ' — ensure VROOM_HOST is reachable or switch to the built-in greedy engine.', 0, $e);
135
+ }
136
+
137
+ if (!$response->successful()) {
138
+ throw new \RuntimeException('VROOM returned an error: HTTP ' . $response->status() . ' — ' . $response->body());
139
+ }
140
+
141
+ $result = $response->json();
142
+
143
+ return $this->mapVroomResponse($result, $jobIdReverse);
144
+ }
145
+
146
+ /**
147
+ * Map a VROOM API response to the standard OrchestrationEngineInterface shape.
148
+ *
149
+ * @param array $jobIdReverse Map of VROOM integer job ID → order public_id
150
+ */
151
+ protected function mapVroomResponse(array $vroomResult, array $jobIdReverse): array
152
+ {
153
+ $assignments = [];
154
+
155
+ foreach ($vroomResult['routes'] ?? [] as $route) {
156
+ $vehicleDesc = json_decode($route['description'] ?? '{}', true);
157
+ $vehicleId = $vehicleDesc['vehicle_id'] ?? null;
158
+ $driverId = $vehicleDesc['driver_id'] ?? null;
159
+ $sequence = 0;
160
+
161
+ foreach ($route['steps'] ?? [] as $step) {
162
+ if ($step['type'] !== 'job') {
163
+ continue;
164
+ }
165
+ $orderId = $jobIdReverse[$step['id']] ?? null;
166
+ if (!$orderId) {
167
+ continue;
168
+ }
169
+ $assignments[] = [
170
+ 'order_id' => $orderId,
171
+ 'vehicle_id' => $vehicleId,
172
+ 'driver_id' => $driverId,
173
+ 'sequence' => ++$sequence,
174
+ 'arrival' => $step['arrival'] ?? null,
175
+ 'duration' => $step['duration'] ?? null,
176
+ 'distance' => $step['distance'] ?? null,
177
+ ];
178
+ }
179
+ }
180
+
181
+ $unassigned = array_map(
182
+ fn ($u) => $jobIdReverse[$u['id']] ?? $u['id'],
183
+ $vroomResult['unassigned'] ?? []
184
+ );
185
+
186
+ return [
187
+ 'assignments' => $assignments,
188
+ 'unassigned' => array_values($unassigned),
189
+ 'summary' => $vroomResult['summary'] ?? [],
190
+ ];
191
+ }
192
+ }
@@ -0,0 +1,83 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Orchestration;
4
+
5
+ use Fleetbase\FleetOps\Orchestration\Contracts\OrchestrationEngineInterface;
6
+
7
+ /**
8
+ * OrchestrationEngineRegistry.
9
+ *
10
+ * A simple service-locator for orchestration engines. Engines register
11
+ * themselves (typically from a service provider) and the
12
+ * OrchestrationController resolves the active engine at runtime from the
13
+ * FleetOps setting key 'fleetops.orchestrator_engine'.
14
+ *
15
+ * Third-party extensions can register their own engines by resolving this
16
+ * registry in their service provider:
17
+ *
18
+ * $this->app->resolving(OrchestrationEngineRegistry::class, function ($registry) {
19
+ * $registry->register(new MyCustomEngine());
20
+ * });
21
+ */
22
+ class OrchestrationEngineRegistry
23
+ {
24
+ /**
25
+ * Registered engines keyed by their identifier.
26
+ *
27
+ * @var array<string, OrchestrationEngineInterface>
28
+ */
29
+ protected array $engines = [];
30
+
31
+ /**
32
+ * Register an orchestration engine.
33
+ *
34
+ * @throws \InvalidArgumentException if an engine with the same identifier is already registered
35
+ */
36
+ public function register(OrchestrationEngineInterface $engine): void
37
+ {
38
+ $id = $engine->getIdentifier();
39
+ if (isset($this->engines[$id])) {
40
+ throw new \InvalidArgumentException("An orchestration engine with identifier '{$id}' is already registered.");
41
+ }
42
+ $this->engines[$id] = $engine;
43
+ }
44
+
45
+ /**
46
+ * Resolve an engine by identifier.
47
+ *
48
+ * @throws \RuntimeException if no engine with the given identifier is registered
49
+ */
50
+ public function resolve(string $identifier): OrchestrationEngineInterface
51
+ {
52
+ if (!isset($this->engines[$identifier])) {
53
+ throw new \RuntimeException("No orchestration engine registered with identifier '{$identifier}'. " . 'Available engines: ' . implode(', ', array_keys($this->engines)));
54
+ }
55
+
56
+ return $this->engines[$identifier];
57
+ }
58
+
59
+ /**
60
+ * Return all registered engines as an array of {id, name} pairs.
61
+ * Used by the settings API to populate the engine selector dropdown.
62
+ *
63
+ * @return array<array{id: string, name: string}>
64
+ */
65
+ public function available(): array
66
+ {
67
+ return array_values(array_map(
68
+ fn (OrchestrationEngineInterface $engine) => [
69
+ 'id' => $engine->getIdentifier(),
70
+ 'name' => $engine->getName(),
71
+ ],
72
+ $this->engines
73
+ ));
74
+ }
75
+
76
+ /**
77
+ * Check whether an engine with the given identifier is registered.
78
+ */
79
+ public function has(string $identifier): bool
80
+ {
81
+ return isset($this->engines[$identifier]);
82
+ }
83
+ }
@@ -0,0 +1,380 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Orchestration\Support;
4
+
5
+ use Fleetbase\FleetOps\Models\Driver;
6
+ use Fleetbase\FleetOps\Models\Order;
7
+ use Fleetbase\FleetOps\Models\Vehicle;
8
+ use Illuminate\Support\Collection;
9
+
10
+ /**
11
+ * OrchestrationPayloadBuilder.
12
+ *
13
+ * Transforms FleetOps Order and Vehicle/Driver records into a normalized
14
+ * intermediate representation that engine adapters can consume. This class
15
+ * is engine-agnostic — it does not produce VROOM JSON directly. Each engine
16
+ * adapter (e.g. VroomOrchestrationEngine) calls the builder and then maps the
17
+ * normalized output to its own wire format.
18
+ *
19
+ * Vehicle capacity is read from the existing first-class columns on the
20
+ * vehicles table (payload_capacity, payload_capacity_volume, etc.).
21
+ *
22
+ * Order/payload capacity demand is computed dynamically by aggregating the
23
+ * weight and dimensions of the payload's entities — no denormalised cache
24
+ * columns are needed or used on the payloads table.
25
+ */
26
+ class OrchestrationPayloadBuilder
27
+ {
28
+ /**
29
+ * Safely read a meta key from a model, returning $default on any error.
30
+ *
31
+ * Vehicle::getAllMeta() has a strict array return type but some rows store
32
+ * the meta column as a raw JSON string rather than a decoded array, causing
33
+ * a TypeError at runtime. This wrapper catches that and any other meta
34
+ * read failure so a single bad vehicle row does not abort the whole run.
35
+ *
36
+ * @param \Illuminate\Database\Eloquent\Model $model
37
+ */
38
+ protected static function safeMeta($model, string $key, $default = null)
39
+ {
40
+ try {
41
+ return $model->getMeta($key) ?? $default;
42
+ } catch (\Throwable $e) {
43
+ return $default;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Compute the multi-dimensional capacity demand for a payload by aggregating
49
+ * its entities' weight and volume values.
50
+ *
51
+ * Returns a 4-element integer array matching the vehicle capacity array:
52
+ * [weight_kg, volume_litres, pallets, parcels]
53
+ *
54
+ * Weight is normalised to kg from entity.weight_unit.
55
+ * Volume is derived from entity length × width × height (dimensions_unit normalised to metres)
56
+ * and expressed in litres (×1000) so it can be stored as an integer for VROOM.
57
+ *
58
+ * Falls back to order meta keys (weight_kg, volume_m3, pallets, parcels) for
59
+ * backwards compatibility with orders that were created before entity-level
60
+ * dimension data was captured.
61
+ */
62
+ protected static function computePayloadDemand(Order $order): array
63
+ {
64
+ $payload = $order->payload;
65
+
66
+ if ($payload && $payload->entities && $payload->entities->isNotEmpty()) {
67
+ $totalWeightKg = 0.0;
68
+ $totalVolumeLit = 0.0;
69
+ $totalParcels = 0;
70
+
71
+ foreach ($payload->entities as $entity) {
72
+ // --- Weight ---
73
+ $rawWeight = (float) ($entity->weight ?? 0);
74
+ $weightUnit = strtolower($entity->weight_unit ?? 'kg');
75
+ $weightKg = match ($weightUnit) {
76
+ 'g', 'gram', 'grams' => $rawWeight / 1000,
77
+ 'lb', 'lbs', 'pound', 'pounds' => $rawWeight * 0.453592,
78
+ 'oz', 'ounce', 'ounces' => $rawWeight * 0.0283495,
79
+ 't', 'ton', 'tonne', 'tonnes' => $rawWeight * 1000,
80
+ default => $rawWeight, // kg assumed
81
+ };
82
+ $totalWeightKg += $weightKg;
83
+
84
+ // --- Volume (L × W × H → m³ → litres) ---
85
+ $l = (float) ($entity->length ?? 0);
86
+ $w = (float) ($entity->width ?? 0);
87
+ $h = (float) ($entity->height ?? 0);
88
+ $unit = strtolower($entity->dimensions_unit ?? 'm');
89
+
90
+ if ($l > 0 && $w > 0 && $h > 0) {
91
+ // Normalise to metres
92
+ $factor = match ($unit) {
93
+ 'cm', 'centimeter', 'centimetre' => 0.01,
94
+ 'mm', 'millimeter', 'millimetre' => 0.001,
95
+ 'in', 'inch', 'inches' => 0.0254,
96
+ 'ft', 'foot', 'feet' => 0.3048,
97
+ default => 1.0, // metres assumed
98
+ };
99
+ $volumeM3 = ($l * $factor) * ($w * $factor) * ($h * $factor);
100
+ $totalVolumeLit += $volumeM3 * 1000; // m³ → litres
101
+ }
102
+
103
+ $totalParcels++;
104
+ }
105
+
106
+ return [
107
+ (int) round($totalWeightKg),
108
+ (int) round($totalVolumeLit),
109
+ 0, // pallet count not tracked at entity level
110
+ $totalParcels,
111
+ ];
112
+ }
113
+
114
+ // Fallback: order meta keys for backwards compatibility
115
+ return [
116
+ (int) round((float) ($order->getMeta('weight_kg') ?? 0)),
117
+ (int) round((float) ($order->getMeta('volume_m3') ?? 0) * 1000),
118
+ (int) ($order->getMeta('pallets') ?? 0),
119
+ (int) ($order->getMeta('parcels') ?? 1),
120
+ ];
121
+ }
122
+
123
+ /**
124
+ * Build the vehicle capacity array from the vehicle's first-class columns.
125
+ *
126
+ * Returns a 4-element integer array:
127
+ * [weight_kg, volume_litres, pallets, parcels]
128
+ *
129
+ * payload_capacity — existing column, weight in kg
130
+ * payload_capacity_volume — new column, volume in m³ (stored as litres for VROOM)
131
+ * payload_capacity_pallets / payload_capacity_parcels — new columns
132
+ */
133
+ protected static function buildVehicleCapacity(Vehicle $vehicle): array
134
+ {
135
+ return [
136
+ (int) round((float) ($vehicle->payload_capacity ?? static::safeMeta($vehicle, 'max_weight_kg', 0))),
137
+ (int) round((float) ($vehicle->payload_capacity_volume ?? static::safeMeta($vehicle, 'max_volume_m3', 0)) * 1000),
138
+ (int) ($vehicle->payload_capacity_pallets ?? static::safeMeta($vehicle, 'max_pallets', 0)),
139
+ (int) ($vehicle->payload_capacity_parcels ?? static::safeMeta($vehicle, 'max_parcels', 100)),
140
+ ];
141
+ }
142
+
143
+ /**
144
+ * Build the normalized job list from a collection of Orders.
145
+ *
146
+ * Each job contains:
147
+ * - id: order public_id
148
+ * - location: [longitude, latitude] of the delivery destination
149
+ * - pickup: [longitude, latitude] of the pickup (for PDPTW shipments)
150
+ * - service: service time in seconds (waypoint.service_time → order meta → default 300)
151
+ * - time_windows: [[earliest_unix, latest_unix]] from order.time_window_start/end or scheduled_at
152
+ * - skills: integer skill codes from order.required_skills or custom fields
153
+ * - amount: multi-dimensional capacity demand [weight_kg, volume_litres, pallets, parcels]
154
+ * - priority: orchestrator_priority (0–100, higher = more important)
155
+ * - description: human-readable label for debugging
156
+ */
157
+ public static function buildJobs(Collection $orders): array
158
+ {
159
+ return $orders->map(function (Order $order) {
160
+ $payload = $order->payload;
161
+ $destination = $payload?->dropoff ?? $payload?->waypoints?->last();
162
+
163
+ if (!$destination || !$destination->location) {
164
+ return null;
165
+ }
166
+
167
+ // --- Location ---
168
+ $job = [
169
+ 'id' => $order->public_id,
170
+ 'location' => [$destination->location->getLng(), $destination->location->getLat()],
171
+ 'description' => $order->public_id,
172
+ ];
173
+
174
+ // Pickup location (for pickup-and-delivery problems)
175
+ $pickup = $payload?->pickup;
176
+ if ($pickup && $pickup->location) {
177
+ $job['pickup'] = [$pickup->location->getLng(), $pickup->location->getLat()];
178
+ }
179
+
180
+ // --- Service time ---
181
+ // Prefer waypoint-level service_time, then order meta, then default 300s
182
+ $waypointMarker = $payload?->waypointMarkers?->last();
183
+ $job['service'] = (int) (
184
+ $waypointMarker?->service_time
185
+ ?? $order->getMeta('service_time_seconds')
186
+ ?? 300
187
+ );
188
+
189
+ // --- Capacity demand ---
190
+ // Aggregated dynamically from payload entities; falls back to order meta.
191
+ $job['amount'] = static::computePayloadDemand($order);
192
+
193
+ // --- Time windows ---
194
+ // Prefer explicit orchestrator time_window columns, fall back to scheduled_at
195
+ if ($order->time_window_start && $order->time_window_end) {
196
+ $job['time_windows'] = [[
197
+ $order->time_window_start->timestamp,
198
+ $order->time_window_end->timestamp,
199
+ ]];
200
+ } elseif ($order->scheduled_at) {
201
+ $start = $order->scheduled_at->timestamp;
202
+ $end = $order->scheduled_at->copy()->addHours(4)->timestamp;
203
+ $job['time_windows'] = [[$start, $end]];
204
+ }
205
+
206
+ // --- Skills ---
207
+ // Prefer first-class required_skills JSON column, fall back to custom fields
208
+ $skills = static::resolveSkills(
209
+ $order->required_skills ?? [],
210
+ $order->custom_fields ?? []
211
+ );
212
+ if (!empty($skills)) {
213
+ $job['skills'] = $skills;
214
+ }
215
+
216
+ // --- Priority ---
217
+ if ($order->orchestrator_priority !== null && $order->orchestrator_priority > 0) {
218
+ $job['priority'] = (int) $order->orchestrator_priority;
219
+ }
220
+
221
+ return $job;
222
+ })->filter()->values()->toArray();
223
+ }
224
+
225
+ /**
226
+ * Build vehicles for vehicle-only allocation mode — no driver required.
227
+ *
228
+ * Uses vehicle.location as the start position. Skills and capacity are
229
+ * read from the vehicle only. Used by the 'assign_vehicles' mode.
230
+ */
231
+ public static function buildVehiclesOnly(Collection $vehicles): array
232
+ {
233
+ return $vehicles->map(function (Vehicle $vehicle) {
234
+ if (!$vehicle->location) {
235
+ return null;
236
+ }
237
+ $entry = [
238
+ 'id' => $vehicle->public_id,
239
+ 'driver_id' => null,
240
+ 'start' => [$vehicle->location->getLng(), $vehicle->location->getLat()],
241
+ ];
242
+ if ($vehicle->return_to_depot && $vehicle->location) {
243
+ $entry['end'] = [$vehicle->location->getLng(), $vehicle->location->getLat()];
244
+ }
245
+ $entry['capacity'] = static::buildVehicleCapacity($vehicle);
246
+ if ($vehicle->max_tasks !== null && $vehicle->max_tasks > 0) {
247
+ $entry['max_tasks'] = (int) $vehicle->max_tasks;
248
+ }
249
+ if ($vehicle->time_window_start && $vehicle->time_window_end) {
250
+ $entry['time_window'] = [
251
+ $vehicle->time_window_start->timestamp,
252
+ $vehicle->time_window_end->timestamp,
253
+ ];
254
+ }
255
+ $skills = static::resolveSkills($vehicle->skills ?? [], $vehicle->custom_fields ?? []);
256
+ if (!empty($skills)) {
257
+ $entry['skills'] = $skills;
258
+ }
259
+
260
+ return $entry;
261
+ })->filter()->values()->toArray();
262
+ }
263
+
264
+ /**
265
+ * Build vehicles for driver+vehicle allocation mode.
266
+ *
267
+ * Uses driver.location as the start position, falling back to vehicle.location.
268
+ * Capacity is read from the vehicle. Time windows prefer the driver's explicit
269
+ * window, then the driver's active shift, then the vehicle's window.
270
+ */
271
+ public static function buildVehicles(Collection $vehicles): array
272
+ {
273
+ return $vehicles->map(function (Vehicle $vehicle) {
274
+ $driver = $vehicle->driver;
275
+ // For vehicle-only mode, fall back to vehicle.location when no driver
276
+ $startLocation = $driver?->location ?? $vehicle->location;
277
+ if (!$startLocation) {
278
+ return null;
279
+ }
280
+ // --- Start position ---
281
+ $entry = [
282
+ 'id' => $vehicle->public_id,
283
+ 'driver_id' => $driver?->public_id,
284
+ 'start' => [$startLocation->getLng(), $startLocation->getLat()],
285
+ ];
286
+ // --- Return to depot ---
287
+ if ($vehicle->return_to_depot && $startLocation) {
288
+ $entry['end'] = [$startLocation->getLng(), $startLocation->getLat()];
289
+ }
290
+
291
+ // --- Multi-dimensional capacity ---
292
+ $entry['capacity'] = static::buildVehicleCapacity($vehicle);
293
+
294
+ // --- Max tasks ---
295
+ if ($vehicle->max_tasks !== null && $vehicle->max_tasks > 0) {
296
+ $entry['max_tasks'] = (int) $vehicle->max_tasks;
297
+ }
298
+
299
+ // --- Max travel time (seconds) ---
300
+ $maxTravel = $driver?->max_travel_time ?? static::safeMeta($vehicle, 'max_travel_time_seconds');
301
+ if ($maxTravel) {
302
+ $entry['max_travel_time'] = (int) $maxTravel;
303
+ }
304
+
305
+ // --- Time window ---
306
+ // Priority: driver.time_window_start/end columns → active shift → vehicle.time_window columns
307
+ if ($driver?->time_window_start && $driver?->time_window_end) {
308
+ $entry['time_window'] = [
309
+ $driver->time_window_start->timestamp,
310
+ $driver->time_window_end->timestamp,
311
+ ];
312
+ } else {
313
+ $activeShift = $driver?->activeShiftFor(now());
314
+ if ($activeShift) {
315
+ $entry['time_window'] = [
316
+ $activeShift->start_at->timestamp,
317
+ $activeShift->end_at->timestamp,
318
+ ];
319
+ } elseif ($vehicle->time_window_start && $vehicle->time_window_end) {
320
+ $entry['time_window'] = [
321
+ $vehicle->time_window_start->timestamp,
322
+ $vehicle->time_window_end->timestamp,
323
+ ];
324
+ }
325
+ }
326
+
327
+ // --- Skills ---
328
+ $skills = array_values(array_unique(array_merge(
329
+ static::resolveSkills($vehicle->skills ?? [], $vehicle->custom_fields ?? []),
330
+ static::resolveSkills($driver?->skills ?? [], $driver?->custom_fields ?? [])
331
+ )));
332
+ if (!empty($skills)) {
333
+ $entry['skills'] = $skills;
334
+ }
335
+
336
+ return $entry;
337
+ })->filter()->values()->toArray();
338
+ }
339
+
340
+ /**
341
+ * Resolve integer skill codes from a first-class skills array and/or custom fields.
342
+ *
343
+ * The skills array (from the new JSON column) contains string identifiers like
344
+ * "cold_chain", "hazmat", "fragile". These are hashed to stable positive integers.
345
+ * Custom fields that are boolean-true are also included for backwards compatibility.
346
+ *
347
+ * @param array $skills String skill identifiers from the skills JSON column
348
+ * @param array $customFields Custom fields array from HasCustomFields trait
349
+ *
350
+ * @return array<int>
351
+ */
352
+ public static function resolveSkills(array $skills, array $customFields = []): array
353
+ {
354
+ $codes = [];
355
+
356
+ // First-class skills column: hash each string identifier to a stable integer
357
+ foreach ($skills as $skill) {
358
+ if (is_string($skill) && !empty($skill)) {
359
+ $codes[] = abs(crc32($skill)) % 2147483647 + 1;
360
+ }
361
+ }
362
+
363
+ // Backwards-compatible custom fields: boolean-true fields treated as skills
364
+ foreach ($customFields as $key => $value) {
365
+ if ($value === true || $value === 'true' || $value === '1' || $value === 1) {
366
+ $codes[] = abs(crc32($key)) % 2147483647 + 1;
367
+ }
368
+ }
369
+
370
+ return array_values(array_unique($codes));
371
+ }
372
+
373
+ /**
374
+ * @deprecated use resolveSkills() instead
375
+ */
376
+ protected static function extractSkillsFromCustomFields(array $customFields): array
377
+ {
378
+ return static::resolveSkills([], $customFields);
379
+ }
380
+ }
@@ -19,7 +19,7 @@ class EventServiceProvider extends ServiceProvider
19
19
  \Fleetbase\FleetOps\Events\OrderDispatched::class => [\Fleetbase\FleetOps\Listeners\HandleOrderDispatched::class, \Fleetbase\Listeners\SendResourceLifecycleWebhook::class, \Fleetbase\FleetOps\Listeners\NotifyOrderEvent::class],
20
20
  \Fleetbase\FleetOps\Events\OrderDispatchFailed::class => [\Fleetbase\FleetOps\Listeners\HandleOrderDispatchFailed::class, \Fleetbase\Listeners\SendResourceLifecycleWebhook::class, \Fleetbase\FleetOps\Listeners\NotifyOrderEvent::class],
21
21
  \Fleetbase\FleetOps\Events\OrderDriverAssigned::class => [\Fleetbase\FleetOps\Listeners\HandleOrderDriverAssigned::class, \Fleetbase\Listeners\SendResourceLifecycleWebhook::class, \Fleetbase\FleetOps\Listeners\NotifyOrderEvent::class],
22
- \Fleetbase\FleetOps\Events\OrderCompleted::class => [\Fleetbase\Listeners\SendResourceLifecycleWebhook::class, \Fleetbase\FleetOps\Listeners\NotifyOrderEvent::class],
22
+ \Fleetbase\FleetOps\Events\OrderCompleted::class => [\Fleetbase\Listeners\SendResourceLifecycleWebhook::class, \Fleetbase\FleetOps\Listeners\NotifyOrderEvent::class, \Fleetbase\FleetOps\Listeners\HandleDeliveryCompletion::class],
23
23
  \Fleetbase\FleetOps\Events\OrderFailed::class => [\Fleetbase\Listeners\SendResourceLifecycleWebhook::class, \Fleetbase\FleetOps\Listeners\NotifyOrderEvent::class],
24
24
  \Fleetbase\FleetOps\Events\OrderReady::class => [\Fleetbase\FleetOps\Listeners\HandleOrderReady::class],
25
25
 
@@ -27,5 +27,11 @@ class EventServiceProvider extends ServiceProvider
27
27
  * Core Events
28
28
  */
29
29
  \Fleetbase\Events\UserRemovedFromCompany::class => [\Fleetbase\FleetOps\Listeners\HandleUserRemovedFromCompany::class],
30
+
31
+ /*
32
+ * Scheduling Events
33
+ */
34
+ \Fleetbase\Events\ScheduleItemCreated::class => [\Fleetbase\FleetOps\Listeners\NotifyDriverOnShiftChange::class],
35
+ \Fleetbase\Events\ScheduleItemUpdated::class => [\Fleetbase\FleetOps\Listeners\NotifyDriverOnShiftChange::class],
30
36
  ];
31
37
  }