@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,316 @@
1
+ import Service from '@ember/service';
2
+ import { inject as service } from '@ember/service';
3
+ import { tracked } from '@glimmer/tracking';
4
+ import { action } from '@ember/object';
5
+ import { isNone } from '@ember/utils';
6
+ import { addMinutes, areIntervalsOverlapping } from 'date-fns';
7
+
8
+ /**
9
+ * SchedulingService
10
+ *
11
+ * Central domain-logic service for the unified order scheduler.
12
+ * Encapsulates conflict detection, best-fit insertion, single/bulk order
13
+ * assignment, and a client-side undo/redo history stack.
14
+ *
15
+ * Usage in a controller:
16
+ * @service scheduling;
17
+ *
18
+ * const result = await this.scheduling.assignOrder(order, driverId, scheduledAt);
19
+ * if (result.hasConflict) { ... }
20
+ */
21
+ export default class SchedulingService extends Service {
22
+ @service store;
23
+ @service fetch;
24
+ @service notifications;
25
+
26
+ // -------------------------------------------------------------------------
27
+ // Undo / Redo History Stack
28
+ // -------------------------------------------------------------------------
29
+
30
+ /** @type {Array<{orderId, previousDate, newDate, previousDriverId, newDriverId}>} */
31
+ @tracked _history = [];
32
+
33
+ /** @type {Array<{orderId, previousDate, newDate, previousDriverId, newDriverId}>} */
34
+ @tracked _future = [];
35
+
36
+ /** True when there is at least one action to undo. */
37
+ get canUndo() {
38
+ return this._history.length > 0;
39
+ }
40
+
41
+ /** True when there is at least one action to redo. */
42
+ get canRedo() {
43
+ return this._future.length > 0;
44
+ }
45
+
46
+ /**
47
+ * Push a new action onto the history stack.
48
+ * Clears the redo stack — a new action invalidates any future states.
49
+ * Capped at 20 entries to prevent unbounded memory growth.
50
+ *
51
+ * @param {Object} entry
52
+ */
53
+ _pushHistory(entry) {
54
+ const capped = [...this._history, entry].slice(-20);
55
+ this._history = capped;
56
+ this._future = [];
57
+ }
58
+
59
+ /**
60
+ * Revert the most recent scheduling action.
61
+ */
62
+ @action async undo() {
63
+ if (!this.canUndo) return;
64
+ const history = [...this._history];
65
+ const entry = history.pop();
66
+ this._history = history;
67
+ this._future = [entry, ...this._future];
68
+ await this._applyHistoryEntry(entry, true);
69
+ }
70
+
71
+ /**
72
+ * Re-apply the most recently undone action.
73
+ */
74
+ @action async redo() {
75
+ if (!this.canRedo) return;
76
+ const [entry, ...rest] = this._future;
77
+ this._future = rest;
78
+ this._history = [...this._history, entry];
79
+ await this._applyHistoryEntry(entry, false);
80
+ }
81
+
82
+ /**
83
+ * Apply a history entry in either the undo (reverse) or redo (forward) direction.
84
+ *
85
+ * @param {Object} entry
86
+ * @param {boolean} isUndo true = restore previous state, false = re-apply new state
87
+ */
88
+ async _applyHistoryEntry(entry, isUndo) {
89
+ const order = this.store.peekRecord('order', entry.orderId);
90
+ if (!order) return;
91
+ const targetDate = isUndo ? entry.previousDate : entry.newDate;
92
+ const targetDriver = isUndo ? entry.previousDriverId : entry.newDriverId;
93
+ const payload = {
94
+ order: order.id,
95
+ scheduled_at: targetDate instanceof Date ? targetDate.toISOString() : targetDate,
96
+ };
97
+ if (targetDriver !== undefined) {
98
+ payload.driver_id = targetDriver;
99
+ }
100
+ try {
101
+ await this.fetch.patch('orders/schedule', payload);
102
+ order.set('scheduled_at', targetDate);
103
+ if (targetDriver !== undefined) {
104
+ order.set('driver_assigned_uuid', targetDriver);
105
+ }
106
+ } catch (error) {
107
+ this.notifications.serverError(error);
108
+ }
109
+ }
110
+
111
+ // -------------------------------------------------------------------------
112
+ // Conflict Detection
113
+ // -------------------------------------------------------------------------
114
+
115
+ /**
116
+ * Returns all orders assigned to `driverId` whose time window overlaps
117
+ * with the proposed [start, end] interval.
118
+ *
119
+ * The `excludeOrderId` parameter allows the caller to exclude the order
120
+ * being rescheduled from its own conflict check.
121
+ *
122
+ * @param {string} driverId
123
+ * @param {Date} start
124
+ * @param {Date} end
125
+ * @param {string} [excludeOrderId]
126
+ * @returns {Array<Model>} Conflicting order records
127
+ */
128
+ checkConflicts(driverId, start, end, excludeOrderId = null) {
129
+ return this.store.peekAll('order').filter((o) => {
130
+ if (o.id === excludeOrderId) return false;
131
+ if (o.driver_assigned_uuid !== driverId) return false;
132
+ if (isNone(o.scheduled_at)) return false;
133
+ const oStart = new Date(o.scheduled_at);
134
+ const oEnd = addMinutes(oStart, o.estimated_duration ?? 60);
135
+ return areIntervalsOverlapping({ start, end }, { start: oStart, end: oEnd }, { inclusive: false });
136
+ });
137
+ }
138
+
139
+ // -------------------------------------------------------------------------
140
+ // Best-Fit Insertion
141
+ // -------------------------------------------------------------------------
142
+
143
+ /**
144
+ * Calculates the optimal `scheduled_at` time for `order` when dropped
145
+ * onto a driver's row without a specific time target.
146
+ *
147
+ * Primary strategy: POST to the Fleetbase routing API for a travel-time-
148
+ * aware recommendation.
149
+ * Fallback: Append the order after the last existing order for that driver.
150
+ *
151
+ * @param {string} driverId
152
+ * @param {Model} order
153
+ * @returns {Promise<Date>}
154
+ */
155
+ async findBestFit(driverId, order) {
156
+ // Try the server-side routing API first
157
+ try {
158
+ const response = await this.fetch.post('orders/best-fit', {
159
+ driver_id: driverId,
160
+ order_id: order.id,
161
+ });
162
+ if (response?.recommended_scheduled_at) {
163
+ return new Date(response.recommended_scheduled_at);
164
+ }
165
+ } catch {
166
+ // Non-critical — fall through to heuristic
167
+ }
168
+
169
+ // Heuristic fallback: find the end time of the last order for this driver
170
+ const driverOrders = this.store
171
+ .peekAll('order')
172
+ .filter((o) => o.driver_assigned_uuid === driverId && !isNone(o.scheduled_at))
173
+ .sortBy('scheduled_at');
174
+
175
+ if (driverOrders.length === 0) {
176
+ // No existing orders — use now as the starting point
177
+ return new Date();
178
+ }
179
+
180
+ const lastOrder = driverOrders[driverOrders.length - 1];
181
+ return addMinutes(new Date(lastOrder.scheduled_at), lastOrder.estimated_duration ?? 60);
182
+ }
183
+
184
+ // -------------------------------------------------------------------------
185
+ // Single Order Assignment
186
+ // -------------------------------------------------------------------------
187
+
188
+ /**
189
+ * Assigns a single order to a driver at a given datetime.
190
+ *
191
+ * Flow:
192
+ * 1. Conflict check (unless `options.skipConflictCheck` is true)
193
+ * 2. Persist via a targeted PATCH to the schedule endpoint
194
+ * (does NOT trigger dispatch or change the order status)
195
+ * 3. Reflect the change in the local Ember Data store
196
+ * 4. On failure: notify and return error
197
+ *
198
+ * @param {Model} order
199
+ * @param {string} driverId
200
+ * @param {Date} scheduledAt
201
+ * @param {Object} [options]
202
+ * @param {boolean} [options.skipConflictCheck=false]
203
+ * @returns {Promise<{hasConflict: boolean, conflicts?: Array, error?: Error}>}
204
+ */
205
+ async assignOrder(order, driverId, scheduledAt, options = {}) {
206
+ const duration = order.estimated_duration ?? 60;
207
+ const end = addMinutes(scheduledAt, duration);
208
+ const previousDate = order.scheduled_at;
209
+ const previousDriverId = order.driver_assigned_uuid;
210
+
211
+ if (!options.skipConflictCheck && driverId) {
212
+ const conflicts = this.checkConflicts(driverId, scheduledAt, end, order.id);
213
+ if (conflicts.length > 0) {
214
+ return { hasConflict: true, conflicts };
215
+ }
216
+ }
217
+
218
+ const payload = {
219
+ order: order.id,
220
+ scheduled_at: scheduledAt instanceof Date ? scheduledAt.toISOString() : scheduledAt,
221
+ };
222
+ if (driverId) {
223
+ payload.driver_id = driverId;
224
+ }
225
+
226
+ try {
227
+ // Use the dedicated schedule endpoint so the backend only sets
228
+ // scheduled_at / driver_assigned_uuid without triggering dispatch.
229
+ await this.fetch.patch('orders/schedule', payload);
230
+
231
+ // Reflect the change locally without triggering another save
232
+ order.set('scheduled_at', scheduledAt);
233
+ if (driverId) {
234
+ order.set('driver_assigned_uuid', driverId);
235
+ }
236
+
237
+ this._pushHistory({
238
+ orderId: order.id,
239
+ previousDate,
240
+ newDate: scheduledAt,
241
+ previousDriverId,
242
+ newDriverId: driverId,
243
+ });
244
+ return { hasConflict: false };
245
+ } catch (error) {
246
+ this.notifications.serverError(error);
247
+ return { hasConflict: false, error };
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Removes the schedule assignment from an order (sets `scheduled_at` to null).
253
+ *
254
+ * @param {Model} order
255
+ */
256
+ async unscheduleOrder(order) {
257
+ const previousDate = order.scheduled_at;
258
+ const previousDriverId = order.driver_assigned_uuid;
259
+ try {
260
+ await this.fetch.patch('orders/schedule', { order: order.id, scheduled_at: null });
261
+ order.set('scheduled_at', null);
262
+ this._pushHistory({
263
+ orderId: order.id,
264
+ previousDate,
265
+ newDate: null,
266
+ previousDriverId,
267
+ newDriverId: null,
268
+ });
269
+ } catch (error) {
270
+ this.notifications.serverError(error);
271
+ }
272
+ }
273
+
274
+ // -------------------------------------------------------------------------
275
+ // Bulk Assignment
276
+ // -------------------------------------------------------------------------
277
+
278
+ /**
279
+ * Assigns multiple orders to a driver on a given date in a single operation.
280
+ *
281
+ * Attempts a batch API call first. On failure, falls back to sequential saves.
282
+ *
283
+ * @param {Array<Model>} orders
284
+ * @param {string} driverId
285
+ * @param {Date} date
286
+ */
287
+ async bulkAssign(orders, driverId, date) {
288
+ const orderIds = orders.map((o) => o.id);
289
+ try {
290
+ await this.fetch.post('orders/bulk-schedule', {
291
+ order_ids: orderIds,
292
+ driver_id: driverId,
293
+ scheduled_at: date instanceof Date ? date.toISOString() : date,
294
+ });
295
+ } catch {
296
+ // Fallback: sequential saves
297
+ for (const order of orders) {
298
+ try {
299
+ order.set('scheduled_at', date);
300
+ order.set('driver_assigned_uuid', driverId);
301
+ await order.save();
302
+ } catch (error) {
303
+ order.rollbackAttributes();
304
+ this.notifications.serverError(error);
305
+ }
306
+ }
307
+ return;
308
+ }
309
+
310
+ // Reflect the bulk change in the local store
311
+ orders.forEach((order) => {
312
+ order.set('scheduled_at', date);
313
+ order.set('driver_assigned_uuid', driverId);
314
+ });
315
+ }
316
+ }
@@ -1,9 +1,65 @@
1
- import ResourceActionService from '@fleetbase/ember-core/services/resource-action';
1
+ import ResourceActionService, { inject as service } from '@fleetbase/ember-core/services/resource-action';
2
2
  import leafletIcon from '@fleetbase/ember-core/utils/leaflet-icon';
3
3
  import config from 'ember-get-config';
4
4
  import { action } from '@ember/object';
5
+ import { isArray } from '@ember/array';
6
+ import { dasherize } from '@ember/string';
5
7
 
6
8
  export default class VehicleActionsService extends ResourceActionService {
9
+ @service('universe/menu-service') menuService;
10
+ @service maintenanceScheduleActions;
11
+ @service workOrderActions;
12
+ @service maintenanceActions;
13
+
14
+ get registeredTabs() {
15
+ const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:vehicle:details');
16
+ return (isArray(registeredTabs) ? registeredTabs : [])
17
+ .map((tab) => {
18
+ delete tab.route;
19
+ if (!tab.key) {
20
+ tab.key = tab.id ?? dasherize(tab.label ?? tab.title);
21
+ }
22
+ return tab;
23
+ })
24
+ .filter((tab) => !tab.component);
25
+ }
26
+
27
+ get panelTabs() {
28
+ return [
29
+ {
30
+ key: 'overview',
31
+ label: 'Overview',
32
+ component: 'vehicle/details',
33
+ },
34
+ {
35
+ key: 'positions',
36
+ label: 'Positions',
37
+ component: 'positions-replay',
38
+ },
39
+ {
40
+ key: 'devices',
41
+ label: 'Devices',
42
+ component: 'device/manager',
43
+ },
44
+ {
45
+ key: 'schedules',
46
+ label: 'Schedules',
47
+ component: 'vehicle/details/schedules',
48
+ },
49
+ {
50
+ key: 'work-orders',
51
+ label: 'Work Orders',
52
+ component: 'vehicle/details/work-orders',
53
+ },
54
+ {
55
+ key: 'maintenance-history',
56
+ label: 'Maintenance',
57
+ component: 'vehicle/details/maintenance-history',
58
+ },
59
+ ...this.registeredTabs,
60
+ ];
61
+ }
62
+
7
63
  constructor() {
8
64
  super(...arguments);
9
65
  this.initialize('vehicle', { status: 'active' });
@@ -68,12 +124,7 @@ export default class VehicleActionsService extends ResourceActionService {
68
124
  },
69
125
  },
70
126
  ],
71
- tabs: [
72
- {
73
- label: this.intl.t('common.overview'),
74
- component: 'vehicle/details',
75
- },
76
- ],
127
+ tabs: this.panelTabs,
77
128
  ...options,
78
129
  });
79
130
  },
@@ -120,6 +171,18 @@ export default class VehicleActionsService extends ResourceActionService {
120
171
  },
121
172
  };
122
173
 
174
+ @action scheduleMaintenance(vehicle) {
175
+ this.maintenanceScheduleActions.modal.create({ subject: vehicle });
176
+ }
177
+
178
+ @action createWorkOrder(vehicle) {
179
+ this.workOrderActions.modal.create({ target: vehicle });
180
+ }
181
+
182
+ @action logMaintenance(vehicle) {
183
+ this.maintenanceActions.modal.create({ maintainable: vehicle });
184
+ }
185
+
123
186
  @action locate(vehicle, options = {}) {
124
187
  const { latitude, longitude, location } = vehicle;
125
188
 
@@ -0,0 +1,45 @@
1
+ import { inject as service } from '@ember/service';
2
+ import OrchestrationEngineInterfaceService from './orchestration-engine-interface';
3
+
4
+ /**
5
+ * VroomOrchestrationEngineService
6
+ *
7
+ * Frontend adapter for the VROOM orchestration engine. Delegates all computation
8
+ * to the backend OrchestrationController — the frontend adapter's role is to
9
+ * call the correct API endpoint and return the normalized result.
10
+ *
11
+ * This service is registered into the allocation-engine registry via the
12
+ * register-vroom-allocation instance initializer.
13
+ */
14
+ export default class VroomOrchestrationEngineService extends OrchestrationEngineInterfaceService {
15
+ @service fetch;
16
+ @service notifications;
17
+
18
+ name = 'VROOM';
19
+ identifier = 'vroom';
20
+
21
+ /**
22
+ * Run the VROOM orchestration via the backend API.
23
+ *
24
+ * @param {Array} orders Array of order records (or order public_ids).
25
+ * @param {Array} vehicles Array of vehicle records (or vehicle public_ids).
26
+ * @param {Object} options Options forwarded to the backend engine.
27
+ * @returns {Promise<{assignments: Array, unassigned: Array, summary: Object}>}
28
+ */
29
+ async allocate(orders = [], vehicles = [], options = {}) {
30
+ const orderIds = orders.map((o) => (typeof o === 'string' ? o : o.public_id));
31
+ const vehicleIds = vehicles.map((v) => (typeof v === 'string' ? v : v.public_id));
32
+
33
+ try {
34
+ const result = await this.fetch.post('fleet-ops/orchestrator/run', {
35
+ order_ids: orderIds,
36
+ vehicle_ids: vehicleIds,
37
+ options: { ...options, engine: 'vroom' },
38
+ });
39
+ return result;
40
+ } catch (error) {
41
+ this.notifications.serverError(error);
42
+ throw error;
43
+ }
44
+ }
45
+ }
@@ -1,6 +1,10 @@
1
1
  import ResourceActionService from '@fleetbase/ember-core/services/resource-action';
2
+ import { action } from '@ember/object';
3
+ import { inject as service } from '@ember/service';
2
4
 
3
5
  export default class WorkOrderActionsService extends ResourceActionService {
6
+ @service fetch;
7
+ @service notifications;
4
8
  constructor() {
5
9
  super(...arguments);
6
10
  this.initialize('work-order');
@@ -46,6 +50,82 @@ export default class WorkOrderActionsService extends ResourceActionService {
46
50
  },
47
51
  };
48
52
 
53
+ /**
54
+ * Packs completion data into workOrder.meta.completion_data before saving.
55
+ *
56
+ * Monetary values (laborCost, partsCost, tax) come from the MoneyInput
57
+ * component's @onChange callback, which already emits **cents** (integers).
58
+ * No further conversion is needed — values are stored as-is.
59
+ *
60
+ * Called by the edit/new controllers before workOrder.save().
61
+ *
62
+ * @param {WorkOrderModel} workOrder
63
+ * @param {Object} completionData — plain object from the form component's @onCompletionChange
64
+ */
65
+ prepareForSave(workOrder, completionData = {}) {
66
+ if (workOrder.status !== 'closed' || !completionData) {
67
+ return;
68
+ }
69
+
70
+ // Ensure values are integers (MoneyInput emits cents already)
71
+ const toIntCents = (value) => {
72
+ if (value === null || value === undefined) return null;
73
+ const parsed = parseInt(value, 10);
74
+ return isNaN(parsed) ? null : parsed;
75
+ };
76
+
77
+ const laborCostCents = toIntCents(completionData.laborCost);
78
+ const partsCostCents = toIntCents(completionData.partsCost);
79
+ const taxCents = toIntCents(completionData.tax);
80
+ const totalCostCents = laborCostCents !== null || partsCostCents !== null || taxCents !== null ? (laborCostCents ?? 0) + (partsCostCents ?? 0) + (taxCents ?? 0) : null;
81
+
82
+ workOrder.meta = {
83
+ ...(workOrder.meta ?? {}),
84
+ completion_data: {
85
+ odometer: completionData.odometer ? parseFloat(completionData.odometer) : null,
86
+ engine_hours: completionData.engineHours ? parseFloat(completionData.engineHours) : null,
87
+ labor_cost: laborCostCents,
88
+ parts_cost: partsCostCents,
89
+ tax: taxCents,
90
+ total_cost: totalCostCents,
91
+ currency: workOrder.currency ?? 'USD',
92
+ notes: completionData.notes ?? null,
93
+ },
94
+ };
95
+ }
96
+
97
+ @action sendEmail(workOrder, options = {}) {
98
+ const assignee = workOrder.assignee;
99
+ console.log('Assignee:', assignee);
100
+ const vendorName = assignee?.name ?? workOrder.assignee_name ?? null;
101
+ const vendorEmail = assignee?.email ?? null;
102
+ const vendorPhone = assignee?.phone ?? null;
103
+
104
+ this.modalsManager.show('modals/send-work-order', {
105
+ title: 'Send Work Order to Vendor',
106
+ acceptButtonText: 'Send Work Order',
107
+ acceptButtonIcon: 'paper-plane',
108
+ acceptButtonDisabled: !vendorEmail,
109
+ declineButtonText: 'Cancel',
110
+ workOrder,
111
+ vendorName,
112
+ vendorEmail,
113
+ vendorPhone,
114
+ confirm: async (modal) => {
115
+ modal.startLoading();
116
+ try {
117
+ const response = await this.fetch.post(`work-orders/${workOrder.id}/send`);
118
+ this.notifications.success(response?.message ?? `Work order sent to ${vendorName ?? 'vendor'} successfully.`);
119
+ modal.done();
120
+ } catch (error) {
121
+ this.notifications.serverError(error);
122
+ modal.stopLoading();
123
+ }
124
+ },
125
+ ...options,
126
+ });
127
+ }
128
+
49
129
  modal = {
50
130
  create: (attributes = {}, options = {}, saveOptions = {}) => {
51
131
  const workOrder = this.createNewInstance(attributes);