@fleetbase/fleetops-engine 0.6.37 → 0.6.39

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 (434) 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 +40 -36
  25. package/addon/components/layout/fleet-ops-sidebar.js +61 -10
  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 +1087 -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 +30 -0
  117. package/addon/components/vehicle/form.hbs +39 -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/details.js +5 -0
  155. package/addon/controllers/operations/orders/index.js +6 -0
  156. package/addon/controllers/operations/scheduler/fleet-schedule.js +341 -0
  157. package/addon/controllers/operations/scheduler/index.js +799 -275
  158. package/addon/controllers/operations/scheduler.js +21 -0
  159. package/addon/controllers/settings/orchestrator.js +70 -0
  160. package/addon/controllers/settings/scheduling.js +155 -0
  161. package/addon/extension.js +74 -1
  162. package/addon/instance-initializers/register-vroom-allocation.js +27 -0
  163. package/addon/models/maintenance-schedule.js +61 -0
  164. package/addon/routes/maintenance/equipment/index/details.js +27 -1
  165. package/addon/routes/maintenance/equipment/index/edit.js +27 -1
  166. package/addon/routes/maintenance/maintenances/index/details/index.js +3 -0
  167. package/addon/routes/maintenance/maintenances/index/details.js +29 -0
  168. package/addon/routes/maintenance/maintenances/index/edit.js +29 -0
  169. package/addon/routes/maintenance/maintenances/index/new.js +3 -0
  170. package/addon/routes/maintenance/maintenances/index.js +23 -0
  171. package/addon/routes/maintenance/maintenances.js +3 -0
  172. package/addon/routes/maintenance/parts/index/details.js +27 -1
  173. package/addon/routes/maintenance/parts/index/edit.js +27 -1
  174. package/addon/routes/maintenance/schedules/index/details/index.js +2 -0
  175. package/addon/routes/maintenance/schedules/index/details/work-orders.js +11 -0
  176. package/addon/routes/maintenance/schedules/index/details.js +25 -0
  177. package/addon/routes/maintenance/schedules/index/edit.js +25 -0
  178. package/addon/routes/maintenance/schedules/index/new.js +2 -0
  179. package/addon/routes/maintenance/schedules/index.js +21 -0
  180. package/addon/routes/maintenance/schedules.js +2 -0
  181. package/addon/routes/maintenance/work-orders/index/details.js +27 -1
  182. package/addon/routes/maintenance/work-orders/index/edit.js +27 -1
  183. package/addon/routes/management/vehicles/index/details/maintenance-history.js +3 -0
  184. package/addon/routes/management/vehicles/index/details/schedules.js +3 -0
  185. package/addon/routes/management/vehicles/index/details/work-orders.js +3 -0
  186. package/addon/routes/operations/orchestrator.js +23 -0
  187. package/addon/routes/operations/orders/index/details/virtual.js +23 -0
  188. package/addon/routes/operations/scheduler/fleet-schedule.js +28 -0
  189. package/addon/routes/operations/scheduler/index.js +48 -26
  190. package/addon/routes/operations/scheduler.js +14 -1
  191. package/addon/routes/settings/orchestrator.js +27 -0
  192. package/addon/routes/settings/scheduling.js +3 -0
  193. package/addon/routes.js +33 -1
  194. package/addon/services/driver-actions.js +40 -7
  195. package/addon/services/driver-scheduling.js +4 -1
  196. package/addon/services/equipment-actions.js +15 -5
  197. package/addon/services/leaflet-map-manager.js +14 -6
  198. package/addon/services/maintenance-actions.js +17 -14
  199. package/addon/services/maintenance-schedule-actions.js +118 -0
  200. package/addon/services/orchestration-engine-interface.js +49 -0
  201. package/addon/services/orchestration-engine.js +74 -0
  202. package/addon/services/order-actions.js +15 -0
  203. package/addon/services/order-allocation.js +116 -0
  204. package/addon/services/part-actions.js +12 -2
  205. package/addon/services/scheduling.js +316 -0
  206. package/addon/services/vehicle-actions.js +70 -7
  207. package/addon/services/vroom-allocation-engine.js +45 -0
  208. package/addon/services/work-order-actions.js +80 -0
  209. package/addon/styles/fleetops-engine.css +1658 -0
  210. package/addon/templates/analytics/reports/index/edit.hbs +1 -1
  211. package/addon/templates/analytics/reports/index/new.hbs +1 -1
  212. package/addon/templates/application.hbs +6 -1
  213. package/addon/templates/connectivity/devices/index/details/events.hbs +0 -1
  214. package/addon/templates/connectivity/devices.hbs +0 -1
  215. package/addon/templates/connectivity/events/index/details.hbs +0 -1
  216. package/addon/templates/connectivity/events.hbs +0 -1
  217. package/addon/templates/connectivity/sensors.hbs +0 -1
  218. package/addon/templates/connectivity/telematics/index/details/devices.hbs +0 -1
  219. package/addon/templates/connectivity/telematics/index/details/events.hbs +0 -1
  220. package/addon/templates/connectivity/telematics/index/details/sensors.hbs +0 -1
  221. package/addon/templates/connectivity/telematics.hbs +0 -1
  222. package/addon/templates/connectivity/tracking.hbs +0 -1
  223. package/addon/templates/connectivity.hbs +0 -1
  224. package/addon/templates/maintenance/equipment/index/details/index.hbs +1 -2
  225. package/addon/templates/maintenance/equipment/index/details.hbs +15 -2
  226. package/addon/templates/maintenance/equipment/index/edit.hbs +12 -2
  227. package/addon/templates/maintenance/equipment/index/new.hbs +1 -2
  228. package/addon/templates/maintenance/equipment/index.hbs +48 -13
  229. package/addon/templates/maintenance/equipment.hbs +0 -1
  230. package/addon/templates/maintenance/maintenances/index/details/index.hbs +1 -0
  231. package/addon/templates/maintenance/maintenances/index/details.hbs +15 -0
  232. package/addon/templates/maintenance/maintenances/index/edit.hbs +12 -0
  233. package/addon/templates/maintenance/maintenances/index/new.hbs +11 -0
  234. package/addon/templates/maintenance/maintenances/index.hbs +14 -0
  235. package/addon/templates/maintenance/maintenances.hbs +1 -0
  236. package/addon/templates/maintenance/parts/index/details/index.hbs +1 -2
  237. package/addon/templates/maintenance/parts/index/details.hbs +15 -2
  238. package/addon/templates/maintenance/parts/index/edit.hbs +12 -2
  239. package/addon/templates/maintenance/parts/index/new.hbs +1 -2
  240. package/addon/templates/maintenance/parts/index.hbs +48 -13
  241. package/addon/templates/maintenance/parts.hbs +0 -1
  242. package/addon/templates/maintenance/schedules/index/details/index.hbs +1 -0
  243. package/addon/templates/maintenance/schedules/index/details/work-orders.hbs +39 -0
  244. package/addon/templates/maintenance/schedules/index/details.hbs +14 -0
  245. package/addon/templates/maintenance/schedules/index/edit.hbs +12 -0
  246. package/addon/templates/maintenance/schedules/index/new.hbs +11 -0
  247. package/addon/templates/maintenance/schedules/index.hbs +40 -0
  248. package/addon/templates/maintenance/schedules.hbs +1 -0
  249. package/addon/templates/maintenance/work-orders/index/details.hbs +2 -1
  250. package/addon/templates/maintenance/work-orders/index/edit.hbs +2 -4
  251. package/addon/templates/maintenance/work-orders/index/new.hbs +1 -2
  252. package/addon/templates/maintenance/work-orders.hbs +0 -1
  253. package/addon/templates/management/contacts/customers/edit.hbs +1 -2
  254. package/addon/templates/management/contacts/customers/new.hbs +1 -2
  255. package/addon/templates/management/contacts/customers.hbs +1 -1
  256. package/addon/templates/management/contacts/index/edit.hbs +1 -2
  257. package/addon/templates/management/contacts/index/new.hbs +1 -2
  258. package/addon/templates/management/drivers/index/details/orders.hbs +0 -1
  259. package/addon/templates/management/drivers/index/edit.hbs +1 -2
  260. package/addon/templates/management/drivers/index/new.hbs +1 -2
  261. package/addon/templates/management/fleets/index/edit.hbs +1 -2
  262. package/addon/templates/management/fleets/index/new.hbs +1 -2
  263. package/addon/templates/management/fleets/index.hbs +1 -2
  264. package/addon/templates/management/fuel-reports/index/edit.hbs +1 -2
  265. package/addon/templates/management/fuel-reports/index/new.hbs +1 -2
  266. package/addon/templates/management/fuel-reports/index.hbs +1 -2
  267. package/addon/templates/management/issues/index/edit.hbs +1 -2
  268. package/addon/templates/management/issues/index/new.hbs +1 -2
  269. package/addon/templates/management/issues/index.hbs +1 -2
  270. package/addon/templates/management/places/index/details/activity.hbs +0 -1
  271. package/addon/templates/management/places/index/details/comments.hbs +0 -1
  272. package/addon/templates/management/places/index/details/documents.hbs +0 -1
  273. package/addon/templates/management/places/index/details/map.hbs +0 -1
  274. package/addon/templates/management/places/index/details/operations.hbs +0 -1
  275. package/addon/templates/management/places/index/details/performance.hbs +0 -1
  276. package/addon/templates/management/places/index/details/rules.hbs +0 -1
  277. package/addon/templates/management/vehicles/index/details/equipment.hbs +0 -1
  278. package/addon/templates/management/vehicles/index/details/maintenance-history.hbs +2 -0
  279. package/addon/templates/management/vehicles/index/details/schedules.hbs +2 -0
  280. package/addon/templates/management/vehicles/index/details/work-orders.hbs +2 -0
  281. package/addon/templates/management/vehicles/index/details.hbs +1 -1
  282. package/addon/templates/management/vehicles/index/edit.hbs +1 -2
  283. package/addon/templates/management/vehicles/index/new.hbs +1 -2
  284. package/addon/templates/management/vendors/index/edit.hbs +1 -2
  285. package/addon/templates/management/vendors/index/new.hbs +1 -2
  286. package/addon/templates/management/vendors/index.hbs +1 -2
  287. package/addon/templates/management/vendors/integrated.hbs +1 -2
  288. package/addon/templates/operations/orchestrator.hbs +1 -0
  289. package/addon/templates/operations/orders/index/details/virtual.hbs +1 -0
  290. package/addon/templates/operations/orders/index.hbs +6 -1
  291. package/addon/templates/operations/scheduler/fleet-schedule.hbs +41 -0
  292. package/addon/templates/operations/scheduler/index.hbs +147 -88
  293. package/addon/templates/operations/scheduler.hbs +7 -1
  294. package/addon/templates/settings/avatars.hbs +1 -1
  295. package/addon/templates/settings/orchestrator.hbs +65 -0
  296. package/addon/templates/settings/payments/index.hbs +1 -5
  297. package/addon/templates/settings/scheduling.hbs +82 -0
  298. package/addon/utils/create-full-calendar-event-from-order.js +52 -14
  299. package/addon/utils/create-full-calendar-event-from-schedule-item.js +50 -0
  300. package/addon/utils/fleet-ops-options.js +254 -0
  301. package/addon/utils/route-colors.js +99 -0
  302. package/addon/utils/to-calendar-date.js +70 -0
  303. package/app/components/driver/schedule.js +1 -0
  304. package/app/components/maintenance/cost-panel.js +1 -0
  305. package/app/components/maintenance/panel-header.js +1 -0
  306. package/app/components/maintenance-schedule/details.js +1 -0
  307. package/app/components/maintenance-schedule/form.js +1 -0
  308. package/app/components/modals/add-driver-shift.js +1 -0
  309. package/app/components/modals/bulk-assign-orders.js +1 -0
  310. package/app/components/modals/driver-shift.js +1 -0
  311. package/app/components/modals/orchestrator-import.js +1 -0
  312. package/app/components/modals/scheduling-conflict.js +1 -0
  313. package/app/components/modals/send-work-order.js +1 -0
  314. package/app/components/modals/set-driver-availability.js +1 -0
  315. package/app/components/orchestrator/card-fields-settings.js +1 -0
  316. package/app/components/orchestrator/order-pool.js +1 -0
  317. package/app/components/orchestrator/phase-builder.js +1 -0
  318. package/app/components/orchestrator/plan-viewer.js +1 -0
  319. package/app/components/orchestrator/resource-panel.js +1 -0
  320. package/app/components/orchestrator-workbench.js +1 -0
  321. package/app/components/vehicle/details/maintenance-history.js +1 -0
  322. package/app/components/vehicle/details/schedules.js +1 -0
  323. package/app/components/vehicle/details/work-orders.js +1 -0
  324. package/app/controllers/operations/orchestrator.js +1 -0
  325. package/app/controllers/settings/orchestrator.js +1 -0
  326. package/app/controllers/settings/scheduling.js +1 -0
  327. package/app/routes/operations/orchestrator.js +1 -0
  328. package/app/routes/operations/orders/index/details/virtual.js +1 -0
  329. package/app/routes/settings/orchestrator.js +1 -0
  330. package/app/routes/settings/scheduling.js +1 -0
  331. package/app/services/maintenance-schedule-actions.js +1 -0
  332. package/app/services/orchestration-engine-interface.js +1 -0
  333. package/app/services/orchestration-engine.js +1 -0
  334. package/app/services/order-allocation.js +1 -0
  335. package/app/services/scheduling.js +1 -0
  336. package/app/services/vroom-allocation-engine.js +1 -0
  337. package/app/templates/operations/orders/index/details/virtual.js +1 -0
  338. package/app/templates/settings/scheduling.js +1 -0
  339. package/app/utils/create-full-calendar-event-from-schedule-item.js +1 -0
  340. package/app/utils/route-colors.js +1 -0
  341. package/composer.json +5 -3
  342. package/extension.json +1 -1
  343. package/package.json +6 -5
  344. package/server/config/fleetops.php +20 -1
  345. package/server/migrations/2025_08_28_054927_create_parts_table.php +2 -2
  346. package/server/migrations/2025_08_28_054932_add_public_id_to_maintenance_tables.php +45 -0
  347. package/server/migrations/2025_09_01_000001_create_maintenance_schedules_table.php +88 -0
  348. package/server/migrations/2026_04_01_000001_fix_monetary_columns_in_parts_table.php +48 -0
  349. package/server/migrations/2026_04_01_000003_add_photo_uuid_to_equipment_and_parts_tables.php +61 -0
  350. package/server/migrations/2026_04_01_000004_add_public_id_to_equipments_table.php +38 -0
  351. package/server/migrations/2026_04_01_000005_add_missing_columns_to_parts_table.php +67 -0
  352. package/server/migrations/2026_04_04_000001_add_reminder_offsets_to_maintenance_schedules.php +44 -0
  353. package/server/migrations/2026_04_08_000001_add_orchestrator_columns_to_vehicles_table.php +58 -0
  354. package/server/migrations/2026_04_08_000002_add_orchestrator_columns_to_drivers_table.php +41 -0
  355. package/server/migrations/2026_04_08_000003_add_orchestrator_columns_to_orders_table.php +38 -0
  356. package/server/migrations/2026_04_08_000004_add_orchestrator_columns_to_payloads_table.php +41 -0
  357. package/server/migrations/2026_04_08_000005_add_orchestrator_columns_to_waypoints_table.php +38 -0
  358. package/server/migrations/2026_04_09_000001_create_manifests_table.php +48 -0
  359. package/server/migrations/2026_04_09_000002_create_manifest_stops_table.php +48 -0
  360. package/server/migrations/2026_04_09_000003_add_manifest_uuid_to_orders_table.php +28 -0
  361. package/server/migrations/2026_04_13_000001_add_pod_notes_columns_to_waypoints_table.php +39 -0
  362. package/server/resources/views/mail/maintenance-schedule-reminder.blade.php +59 -0
  363. package/server/resources/views/mail/work-order-dispatched.blade.php +67 -0
  364. package/server/src/Auth/Schemas/FleetOps.php +44 -0
  365. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +150 -0
  366. package/server/src/Console/Commands/SendMaintenanceReminders.php +128 -0
  367. package/server/src/Http/Controllers/Internal/v1/DriverController.php +1 -0
  368. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +27 -0
  369. package/server/src/Http/Controllers/Internal/v1/LiveController.php +9 -2
  370. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +165 -0
  371. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +304 -0
  372. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +138 -0
  373. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +975 -0
  374. package/server/src/Http/Controllers/Internal/v1/OrderController.php +42 -0
  375. package/server/src/Http/Controllers/Internal/v1/PartController.php +27 -0
  376. package/server/src/Http/Controllers/Internal/v1/SettingController.php +118 -0
  377. package/server/src/Http/Controllers/Internal/v1/Traits/DriverSchedulingTrait.php +214 -0
  378. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +68 -0
  379. package/server/src/Http/Resources/v1/Driver.php +1 -0
  380. package/server/src/Http/Resources/v1/Maintenance.php +138 -0
  381. package/server/src/Http/Resources/v1/MaintenanceSchedule.php +137 -0
  382. package/server/src/Http/Resources/v1/Orchestrator/Order.php +116 -0
  383. package/server/src/Http/Resources/v1/Order.php +7 -4
  384. package/server/src/Http/Resources/v1/Waypoint.php +7 -0
  385. package/server/src/Http/Resources/v1/WorkOrder.php +136 -0
  386. package/server/src/Imports/EquipmentImport.php +32 -0
  387. package/server/src/Imports/MaintenanceImport.php +32 -0
  388. package/server/src/Imports/MaintenanceScheduleImport.php +32 -0
  389. package/server/src/Imports/PartImport.php +32 -0
  390. package/server/src/Imports/WorkOrderImport.php +32 -0
  391. package/server/src/Jobs/ProcessAllocationJob.php +119 -0
  392. package/server/src/Listeners/HandleDeliveryCompletion.php +47 -0
  393. package/server/src/Listeners/NotifyDriverOnShiftChange.php +63 -0
  394. package/server/src/Mail/MaintenanceScheduleReminder.php +68 -0
  395. package/server/src/Mail/WorkOrderDispatched.php +58 -0
  396. package/server/src/Models/Asset.php +2 -2
  397. package/server/src/Models/Device.php +1 -1
  398. package/server/src/Models/Driver.php +82 -4
  399. package/server/src/Models/Equipment.php +62 -2
  400. package/server/src/Models/Maintenance.php +127 -9
  401. package/server/src/Models/MaintenanceSchedule.php +353 -0
  402. package/server/src/Models/Manifest.php +214 -0
  403. package/server/src/Models/ManifestStop.php +162 -0
  404. package/server/src/Models/Order.php +70 -0
  405. package/server/src/Models/OrderConfig.php +5 -2
  406. package/server/src/Models/Part.php +69 -3
  407. package/server/src/Models/Payload.php +7 -2
  408. package/server/src/Models/Place.php +1 -1
  409. package/server/src/Models/Sensor.php +1 -1
  410. package/server/src/Models/ServiceQuote.php +1 -1
  411. package/server/src/Models/Vehicle.php +20 -1
  412. package/server/src/Models/Warranty.php +1 -1
  413. package/server/src/Models/Waypoint.php +7 -1
  414. package/server/src/Models/WorkOrder.php +122 -12
  415. package/server/src/Notifications/DriverShiftChanged.php +110 -0
  416. package/server/src/Observers/WorkOrderObserver.php +107 -0
  417. package/server/src/Orchestration/Contracts/OrchestrationEngineInterface.php +63 -0
  418. package/server/src/Orchestration/Engines/DriverAssignmentEngine.php +265 -0
  419. package/server/src/Orchestration/Engines/GreedyOrchestrationEngine.php +155 -0
  420. package/server/src/Orchestration/Engines/RouteSequencingEngine.php +272 -0
  421. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +192 -0
  422. package/server/src/Orchestration/OrchestrationEngineRegistry.php +83 -0
  423. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +290 -0
  424. package/server/src/Providers/EventServiceProvider.php +7 -1
  425. package/server/src/Providers/FleetOpsServiceProvider.php +42 -15
  426. package/server/src/routes.php +65 -4
  427. package/translations/ar-ae.yml +44 -12
  428. package/translations/bg-bg.yaml +51 -10
  429. package/translations/en-us.yaml +444 -1
  430. package/translations/fr-fr.yaml +51 -10
  431. package/translations/mn-mn.yaml +51 -10
  432. package/translations/pt-br.yaml +51 -10
  433. package/translations/ru-ru.yaml +51 -10
  434. 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,290 @@
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
+ * The builder now reads first-class orchestrator columns (skills, capacity_*,
20
+ * time_window_*, service_time, orchestrator_priority) added by the 2026-04-08
21
+ * migrations, falling back to custom fields and meta for backwards compatibility.
22
+ */
23
+ class OrchestrationPayloadBuilder
24
+ {
25
+ /**
26
+ * Safely read a meta key from a model, returning $default on any error.
27
+ *
28
+ * Vehicle::getAllMeta() has a strict array return type but some rows store
29
+ * the meta column as a raw JSON string rather than a decoded array, causing
30
+ * a TypeError at runtime. This wrapper catches that and any other meta
31
+ * read failure so a single bad vehicle row does not abort the whole run.
32
+ *
33
+ * @param \Illuminate\Database\Eloquent\Model $model
34
+ */
35
+ protected static function safeMeta($model, string $key, $default = null)
36
+ {
37
+ try {
38
+ return $model->getMeta($key) ?? $default;
39
+ } catch (\Throwable $e) {
40
+ return $default;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Build the normalized job list from a collection of Orders.
46
+ *
47
+ * Each job contains:
48
+ * - id: order public_id
49
+ * - location: [longitude, latitude] of the delivery destination
50
+ * - pickup: [longitude, latitude] of the pickup (for PDPTW shipments)
51
+ * - service: service time in seconds (waypoint.service_time → order meta → default 300)
52
+ * - time_windows: [[earliest_unix, latest_unix]] from order.time_window_start/end or scheduled_at
53
+ * - skills: integer skill codes from order.required_skills or custom fields
54
+ * - amount: multi-dimensional capacity demand [weight_kg, volume_m3, pallets, parcels]
55
+ * - priority: orchestrator_priority (0–100, higher = more important)
56
+ * - description: human-readable label for debugging
57
+ */
58
+ public static function buildJobs(Collection $orders): array
59
+ {
60
+ return $orders->map(function (Order $order) {
61
+ $payload = $order->payload;
62
+ $destination = $payload?->dropoff ?? $payload?->waypoints?->last();
63
+
64
+ if (!$destination || !$destination->location) {
65
+ return null;
66
+ }
67
+
68
+ // --- Location ---
69
+ $job = [
70
+ 'id' => $order->public_id,
71
+ 'location' => [$destination->location->getLng(), $destination->location->getLat()],
72
+ 'description' => $order->public_id,
73
+ ];
74
+
75
+ // Pickup location (for pickup-and-delivery problems)
76
+ $pickup = $payload?->pickup;
77
+ if ($pickup && $pickup->location) {
78
+ $job['pickup'] = [$pickup->location->getLng(), $pickup->location->getLat()];
79
+ }
80
+
81
+ // --- Service time ---
82
+ // Prefer waypoint-level service_time, then order meta, then default 300s
83
+ $waypointMarker = $payload?->waypointMarkers?->last();
84
+ $job['service'] = (int) (
85
+ $waypointMarker?->service_time
86
+ ?? $order->getMeta('service_time_seconds')
87
+ ?? 300
88
+ );
89
+
90
+ // --- Capacity demand (multi-dimensional) ---
91
+ // [weight_kg, volume_m3, pallets, parcels] — must match vehicle capacity array
92
+ $job['amount'] = [
93
+ (int) round((float) ($payload?->capacity_weight_kg ?? $order->getMeta('weight_kg') ?? 0)),
94
+ (int) round((float) ($payload?->capacity_volume_m3 ?? $order->getMeta('volume_m3') ?? 0) * 1000), // store as litres for integer
95
+ (int) ($payload?->capacity_pallets ?? $order->getMeta('pallets') ?? 0),
96
+ (int) ($payload?->capacity_parcels ?? $order->getMeta('parcels') ?? 1),
97
+ ];
98
+
99
+ // --- Time windows ---
100
+ // Prefer explicit orchestrator time_window columns, fall back to scheduled_at
101
+ if ($order->time_window_start && $order->time_window_end) {
102
+ $job['time_windows'] = [[
103
+ $order->time_window_start->timestamp,
104
+ $order->time_window_end->timestamp,
105
+ ]];
106
+ } elseif ($order->scheduled_at) {
107
+ $start = $order->scheduled_at->timestamp;
108
+ $end = $order->scheduled_at->copy()->addHours(4)->timestamp;
109
+ $job['time_windows'] = [[$start, $end]];
110
+ }
111
+
112
+ // --- Skills ---
113
+ // Prefer first-class required_skills JSON column, fall back to custom fields
114
+ $skills = static::resolveSkills(
115
+ $order->required_skills ?? [],
116
+ $order->custom_fields ?? []
117
+ );
118
+ if (!empty($skills)) {
119
+ $job['skills'] = $skills;
120
+ }
121
+
122
+ // --- Priority ---
123
+ if ($order->orchestrator_priority !== null && $order->orchestrator_priority > 0) {
124
+ $job['priority'] = (int) $order->orchestrator_priority;
125
+ }
126
+
127
+ return $job;
128
+ })->filter()->values()->toArray();
129
+ }
130
+
131
+ /**
132
+ * Build vehicles for vehicle-only allocation mode — no driver required.
133
+ *
134
+ * Uses vehicle.location as the start position. Skills and capacity are
135
+ * read from the vehicle only. Used by the 'assign_vehicles' mode.
136
+ */
137
+ public static function buildVehiclesOnly(Collection $vehicles): array
138
+ {
139
+ return $vehicles->map(function (Vehicle $vehicle) {
140
+ if (!$vehicle->location) {
141
+ return null;
142
+ }
143
+ $entry = [
144
+ 'id' => $vehicle->public_id,
145
+ 'driver_id' => null,
146
+ 'start' => [$vehicle->location->getLng(), $vehicle->location->getLat()],
147
+ ];
148
+ if ($vehicle->return_to_depot && $vehicle->location) {
149
+ $entry['end'] = [$vehicle->location->getLng(), $vehicle->location->getLat()];
150
+ }
151
+ $entry['capacity'] = [
152
+ (int) round((float) ($vehicle->capacity_weight_kg ?? static::safeMeta($vehicle, 'max_weight_kg', 0))),
153
+ (int) round((float) ($vehicle->capacity_volume_m3 ?? static::safeMeta($vehicle, 'max_volume_m3', 0)) * 1000),
154
+ (int) ($vehicle->capacity_pallets ?? static::safeMeta($vehicle, 'max_pallets', 0)),
155
+ (int) ($vehicle->capacity_parcels ?? static::safeMeta($vehicle, 'max_parcels', 100)),
156
+ ];
157
+ if ($vehicle->max_tasks !== null && $vehicle->max_tasks > 0) {
158
+ $entry['max_tasks'] = (int) $vehicle->max_tasks;
159
+ }
160
+ if ($vehicle->time_window_start && $vehicle->time_window_end) {
161
+ $entry['time_window'] = [
162
+ $vehicle->time_window_start->timestamp,
163
+ $vehicle->time_window_end->timestamp,
164
+ ];
165
+ }
166
+ $skills = static::resolveSkills($vehicle->skills ?? [], $vehicle->custom_fields ?? []);
167
+ if (!empty($skills)) {
168
+ $entry['skills'] = $skills;
169
+ }
170
+
171
+ return $entry;
172
+ })->filter()->values()->toArray();
173
+ }
174
+
175
+ public static function buildVehicles(Collection $vehicles): array
176
+ {
177
+ return $vehicles->map(function (Vehicle $vehicle) {
178
+ $driver = $vehicle->driver;
179
+ // For vehicle-only mode, fall back to vehicle.location when no driver
180
+ $startLocation = $driver?->location ?? $vehicle->location;
181
+ if (!$startLocation) {
182
+ return null;
183
+ }
184
+ // --- Start position ---
185
+ $entry = [
186
+ 'id' => $vehicle->public_id,
187
+ 'driver_id' => $driver?->public_id,
188
+ 'start' => [$startLocation->getLng(), $startLocation->getLat()],
189
+ ];
190
+ // --- Return to depot ---
191
+ if ($vehicle->return_to_depot && $startLocation) {
192
+ $entry['end'] = [$startLocation->getLng(), $startLocation->getLat()];
193
+ }
194
+
195
+ // --- Multi-dimensional capacity ---
196
+ // [weight_kg, volume_l (×1000 from m3), pallets, parcels]
197
+ $entry['capacity'] = [
198
+ (int) round((float) ($vehicle->capacity_weight_kg ?? static::safeMeta($vehicle, 'max_weight_kg', 0))),
199
+ (int) round((float) ($vehicle->capacity_volume_m3 ?? static::safeMeta($vehicle, 'max_volume_m3', 0)) * 1000),
200
+ (int) ($vehicle->capacity_pallets ?? static::safeMeta($vehicle, 'max_pallets', 0)),
201
+ (int) ($vehicle->capacity_parcels ?? static::safeMeta($vehicle, 'max_parcels', 100)),
202
+ ];
203
+
204
+ // --- Max tasks ---
205
+ if ($vehicle->max_tasks !== null && $vehicle->max_tasks > 0) {
206
+ $entry['max_tasks'] = (int) $vehicle->max_tasks;
207
+ }
208
+
209
+ // --- Max travel time (seconds) ---
210
+ $maxTravel = $driver?->max_travel_time ?? static::safeMeta($vehicle, 'max_travel_time_seconds');
211
+ if ($maxTravel) {
212
+ $entry['max_travel_time'] = (int) $maxTravel;
213
+ }
214
+
215
+ // --- Time window ---
216
+ // Priority: driver.time_window_start/end columns → active shift → vehicle.time_window columns
217
+ if ($driver?->time_window_start && $driver?->time_window_end) {
218
+ $entry['time_window'] = [
219
+ $driver->time_window_start->timestamp,
220
+ $driver->time_window_end->timestamp,
221
+ ];
222
+ } else {
223
+ $activeShift = $driver?->activeShiftFor(now());
224
+ if ($activeShift) {
225
+ $entry['time_window'] = [
226
+ $activeShift->start_at->timestamp,
227
+ $activeShift->end_at->timestamp,
228
+ ];
229
+ } elseif ($vehicle->time_window_start && $vehicle->time_window_end) {
230
+ $entry['time_window'] = [
231
+ $vehicle->time_window_start->timestamp,
232
+ $vehicle->time_window_end->timestamp,
233
+ ];
234
+ }
235
+ }
236
+
237
+ // --- Skills ---
238
+ $skills = array_values(array_unique(array_merge(
239
+ static::resolveSkills($vehicle->skills ?? [], $vehicle->custom_fields ?? []),
240
+ static::resolveSkills($driver?->skills ?? [], $driver?->custom_fields ?? [])
241
+ )));
242
+ if (!empty($skills)) {
243
+ $entry['skills'] = $skills;
244
+ }
245
+
246
+ return $entry;
247
+ })->filter()->values()->toArray();
248
+ }
249
+
250
+ /**
251
+ * Resolve integer skill codes from a first-class skills array and/or custom fields.
252
+ *
253
+ * The skills array (from the new JSON column) contains string identifiers like
254
+ * "cold_chain", "hazmat", "fragile". These are hashed to stable positive integers.
255
+ * Custom fields that are boolean-true are also included for backwards compatibility.
256
+ *
257
+ * @param array $skills String skill identifiers from the skills JSON column
258
+ * @param array $customFields Custom fields array from HasCustomFields trait
259
+ *
260
+ * @return array<int>
261
+ */
262
+ public static function resolveSkills(array $skills, array $customFields = []): array
263
+ {
264
+ $codes = [];
265
+
266
+ // First-class skills column: hash each string identifier to a stable integer
267
+ foreach ($skills as $skill) {
268
+ if (is_string($skill) && !empty($skill)) {
269
+ $codes[] = abs(crc32($skill)) % 2147483647 + 1;
270
+ }
271
+ }
272
+
273
+ // Backwards-compatible custom fields: boolean-true fields treated as skills
274
+ foreach ($customFields as $key => $value) {
275
+ if ($value === true || $value === 'true' || $value === '1' || $value === 1) {
276
+ $codes[] = abs(crc32($key)) % 2147483647 + 1;
277
+ }
278
+ }
279
+
280
+ return array_values(array_unique($codes));
281
+ }
282
+
283
+ /**
284
+ * @deprecated use resolveSkills() instead
285
+ */
286
+ protected static function extractSkillsFromCustomFields(array $customFields): array
287
+ {
288
+ return static::resolveSkills([], $customFields);
289
+ }
290
+ }
@@ -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
  }
@@ -23,21 +23,22 @@ class FleetOpsServiceProvider extends CoreServiceProvider
23
23
  * @var array
24
24
  */
25
25
  public $observers = [
26
- \Fleetbase\FleetOps\Models\Order::class => \Fleetbase\FleetOps\Observers\OrderObserver::class,
27
- \Fleetbase\FleetOps\Models\Payload::class => \Fleetbase\FleetOps\Observers\PayloadObserver::class,
28
- \Fleetbase\FleetOps\Models\Place::class => \Fleetbase\FleetOps\Observers\PlaceObserver::class,
29
- \Fleetbase\FleetOps\Models\ServiceRate::class => \Fleetbase\FleetOps\Observers\ServiceRateObserver::class,
30
- \Fleetbase\FleetOps\Models\PurchaseRate::class => \Fleetbase\FleetOps\Observers\PurchaseRateObserver::class,
31
- \Fleetbase\FleetOps\Models\ServiceArea::class => \Fleetbase\FleetOps\Observers\ServiceAreaObserver::class,
32
- \Fleetbase\FleetOps\Models\TrackingNumber::class => \Fleetbase\FleetOps\Observers\TrackingNumberObserver::class,
33
- \Fleetbase\FleetOps\Models\Driver::class => \Fleetbase\FleetOps\Observers\DriverObserver::class,
34
- \Fleetbase\FleetOps\Models\Vehicle::class => \Fleetbase\FleetOps\Observers\VehicleObserver::class,
35
- \Fleetbase\FleetOps\Models\Fleet::class => \Fleetbase\FleetOps\Observers\FleetObserver::class,
36
- \Fleetbase\FleetOps\Models\Contact::class => \Fleetbase\FleetOps\Observers\ContactObserver::class,
37
- \Fleetbase\Models\User::class => \Fleetbase\FleetOps\Observers\UserObserver::class,
38
- \Fleetbase\Models\Company::class => \Fleetbase\FleetOps\Observers\CompanyObserver::class,
39
- \Fleetbase\Models\CompanyUser::class => \Fleetbase\FleetOps\Observers\CompanyUserObserver::class,
40
- \Fleetbase\Models\Category::class => \Fleetbase\FleetOps\Observers\CategoryObserver::class,
26
+ \Fleetbase\FleetOps\Models\Order::class => \Fleetbase\FleetOps\Observers\OrderObserver::class,
27
+ \Fleetbase\FleetOps\Models\Payload::class => \Fleetbase\FleetOps\Observers\PayloadObserver::class,
28
+ \Fleetbase\FleetOps\Models\Place::class => \Fleetbase\FleetOps\Observers\PlaceObserver::class,
29
+ \Fleetbase\FleetOps\Models\ServiceRate::class => \Fleetbase\FleetOps\Observers\ServiceRateObserver::class,
30
+ \Fleetbase\FleetOps\Models\PurchaseRate::class => \Fleetbase\FleetOps\Observers\PurchaseRateObserver::class,
31
+ \Fleetbase\FleetOps\Models\ServiceArea::class => \Fleetbase\FleetOps\Observers\ServiceAreaObserver::class,
32
+ \Fleetbase\FleetOps\Models\TrackingNumber::class => \Fleetbase\FleetOps\Observers\TrackingNumberObserver::class,
33
+ \Fleetbase\FleetOps\Models\Driver::class => \Fleetbase\FleetOps\Observers\DriverObserver::class,
34
+ \Fleetbase\FleetOps\Models\Vehicle::class => \Fleetbase\FleetOps\Observers\VehicleObserver::class,
35
+ \Fleetbase\FleetOps\Models\Fleet::class => \Fleetbase\FleetOps\Observers\FleetObserver::class,
36
+ \Fleetbase\FleetOps\Models\Contact::class => \Fleetbase\FleetOps\Observers\ContactObserver::class,
37
+ \Fleetbase\Models\User::class => \Fleetbase\FleetOps\Observers\UserObserver::class,
38
+ \Fleetbase\Models\Company::class => \Fleetbase\FleetOps\Observers\CompanyObserver::class,
39
+ \Fleetbase\Models\CompanyUser::class => \Fleetbase\FleetOps\Observers\CompanyUserObserver::class,
40
+ \Fleetbase\Models\Category::class => \Fleetbase\FleetOps\Observers\CategoryObserver::class,
41
+ \Fleetbase\FleetOps\Models\WorkOrder::class => \Fleetbase\FleetOps\Observers\WorkOrderObserver::class,
41
42
  ];
42
43
 
43
44
  /**
@@ -61,6 +62,8 @@ class FleetOpsServiceProvider extends CoreServiceProvider
61
62
  \Fleetbase\FleetOps\Console\Commands\SendDriverNotification::class,
62
63
  \Fleetbase\FleetOps\Console\Commands\ReplayVehicleLocations::class,
63
64
  \Fleetbase\FleetOps\Console\Commands\TestEmail::class,
65
+ \Fleetbase\FleetOps\Console\Commands\ProcessMaintenanceTriggers::class,
66
+ \Fleetbase\FleetOps\Console\Commands\SendMaintenanceReminders::class,
64
67
  ];
65
68
 
66
69
  /**
@@ -80,6 +83,13 @@ class FleetOpsServiceProvider extends CoreServiceProvider
80
83
  {
81
84
  $this->app->register(CoreServiceProvider::class);
82
85
  $this->app->register(ReportSchemaServiceProvider::class);
86
+
87
+ // Register the OrchestrationEngineRegistry as a singleton so that engines
88
+ // registered from any service provider share the same instance.
89
+ $this->app->singleton(
90
+ \Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry::class,
91
+ fn () => new \Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry()
92
+ );
83
93
  }
84
94
 
85
95
  /**
@@ -98,9 +108,26 @@ class FleetOpsServiceProvider extends CoreServiceProvider
98
108
  $schedule->command('fleetops:dispatch-adhoc')->everyMinute()->withoutOverlapping()->storeOutputInDb();
99
109
  $schedule->command('fleetops:update-estimations')->everyTenMinutes()->withoutOverlapping();
100
110
  $schedule->command('fleetops:purge-service-quotes')->daily()->withoutOverlapping();
111
+ $schedule->command('fleetops:process-maintenance-triggers')->daily()->withoutOverlapping()->storeOutputInDb();
112
+ $schedule->command('fleetops:send-maintenance-reminders')->daily()->withoutOverlapping()->storeOutputInDb();
101
113
  });
102
114
  $this->registerNotifications();
103
115
  $this->registerExpansionsFrom(__DIR__ . '/../Expansions');
116
+
117
+ // Register built-in orchestration engines.
118
+ // Third-party engines can register themselves by resolving the
119
+ // OrchestrationEngineRegistry singleton from their own service providers.
120
+ $this->app->resolving(
121
+ \Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry::class,
122
+ function (\Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry $registry) {
123
+ if (!$registry->has('vroom')) {
124
+ $registry->register(new \Fleetbase\FleetOps\Orchestration\Engines\VroomOrchestrationEngine());
125
+ }
126
+ if (!$registry->has('greedy')) {
127
+ $registry->register(new \Fleetbase\FleetOps\Orchestration\Engines\GreedyOrchestrationEngine());
128
+ }
129
+ }
130
+ );
104
131
  $this->loadRoutesFrom(__DIR__ . '/../routes.php');
105
132
  $this->loadMigrationsFrom(__DIR__ . '/../../migrations');
106
133
  $this->loadViewsFrom(__DIR__ . '/../../resources/views', 'fleetops');