@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
@@ -113,6 +113,22 @@ class FleetOps
113
113
  'name' => 'issue',
114
114
  'actions' => ['export', 'import'],
115
115
  ],
116
+ [
117
+ 'name' => 'maintenance',
118
+ 'actions' => ['export', 'import'],
119
+ ],
120
+ [
121
+ 'name' => 'work-order',
122
+ 'actions' => ['export', 'import'],
123
+ ],
124
+ [
125
+ 'name' => 'equipment',
126
+ 'actions' => ['export', 'import'],
127
+ ],
128
+ [
129
+ 'name' => 'part',
130
+ 'actions' => ['export', 'import'],
131
+ ],
116
132
  [
117
133
  'name' => 'custom-field',
118
134
  'actions' => [],
@@ -264,6 +280,23 @@ class FleetOps
264
280
  'list order',
265
281
  ],
266
282
  ],
283
+ [
284
+ 'name' => 'MaintenanceManager',
285
+ 'description' => 'Policy for managing maintenance records, work orders, equipment, and parts.',
286
+ 'permissions' => [
287
+ 'see extension',
288
+ '* maintenance',
289
+ '* work-order',
290
+ '* equipment',
291
+ '* part',
292
+ 'see vehicle',
293
+ 'list vehicle',
294
+ 'view vehicle',
295
+ 'see driver',
296
+ 'list driver',
297
+ 'view driver',
298
+ ],
299
+ ],
267
300
  [
268
301
  'name' => 'OperationsAdmin',
269
302
  'description' => 'Policy for monitoring activities, issues, and fuel reports within the fleet operations.',
@@ -276,6 +309,10 @@ class FleetOps
276
309
  '* fuel-report',
277
310
  '* issue',
278
311
  '* place',
312
+ '* maintenance',
313
+ '* work-order',
314
+ '* equipment',
315
+ '* part',
279
316
  ],
280
317
  ],
281
318
  [
@@ -370,6 +407,13 @@ class FleetOps
370
407
  'OperationsAdmin',
371
408
  ],
372
409
  ],
410
+ [
411
+ 'name' => 'Maintenance Technician',
412
+ 'description' => 'Role for technicians responsible for managing maintenance records, work orders, equipment, and parts inventory.',
413
+ 'policies' => [
414
+ 'MaintenanceManager',
415
+ ],
416
+ ],
373
417
  [
374
418
  'name' => 'Driver Coordinator',
375
419
  'description' => 'Role responsible for coordinating drivers, vehicles, and orders.',
@@ -0,0 +1,150 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Console\Commands;
4
+
5
+ use Carbon\Carbon;
6
+ use Fleetbase\FleetOps\Models\MaintenanceSchedule;
7
+ use Fleetbase\FleetOps\Models\Vehicle;
8
+ use Fleetbase\FleetOps\Models\WorkOrder;
9
+ use Illuminate\Console\Command;
10
+
11
+ /**
12
+ * ProcessMaintenanceTriggers.
13
+ *
14
+ * Scans all active MaintenanceSchedule records and determines whether a
15
+ * preventive maintenance work order should be created based on:
16
+ *
17
+ * - Time/date thresholds (next_due_date)
18
+ * - Odometer thresholds (next_due_odometer vs. current vehicle odometer)
19
+ * - Engine-hour thresholds (next_due_engine_hours vs. current engine hours)
20
+ *
21
+ * When a schedule is triggered, a WorkOrder is automatically created from the
22
+ * schedule's default settings and a `maintenance.triggered` event is dispatched.
23
+ *
24
+ * Designed to run on a scheduled basis (e.g. daily via the Laravel scheduler).
25
+ */
26
+ class ProcessMaintenanceTriggers extends Command
27
+ {
28
+ /**
29
+ * The name and signature of the console command.
30
+ *
31
+ * @var string
32
+ */
33
+ protected $signature = 'fleetops:process-maintenance-triggers
34
+ {--sandbox : Run in sandbox database mode}
35
+ {--dry-run : Report triggers without creating work orders}';
36
+
37
+ /**
38
+ * The console command description.
39
+ *
40
+ * @var string
41
+ */
42
+ protected $description = 'Process preventive maintenance schedule triggers and auto-create work orders';
43
+
44
+ /**
45
+ * Execute the console command.
46
+ */
47
+ public function handle(): void
48
+ {
49
+ date_default_timezone_set('UTC');
50
+
51
+ $sandbox = (bool) $this->option('sandbox');
52
+ $dryRun = (bool) $this->option('dry-run');
53
+ $conn = $sandbox ? 'sandbox' : 'mysql';
54
+
55
+ $this->info('Processing maintenance schedule triggers' . ($dryRun ? ' [DRY RUN]' : '') . ' at ' . Carbon::now()->toDateTimeString());
56
+
57
+ $triggered = 0;
58
+
59
+ // ------------------------------------------------------------------
60
+ // Load all active schedules that have at least one threshold set
61
+ // ------------------------------------------------------------------
62
+ $schedules = MaintenanceSchedule::on($conn)
63
+ ->withoutGlobalScopes()
64
+ ->where('status', 'active')
65
+ ->where(function ($q) {
66
+ $q->whereNotNull('next_due_date')
67
+ ->orWhereNotNull('next_due_odometer')
68
+ ->orWhereNotNull('next_due_engine_hours');
69
+ })
70
+ ->whereNull('deleted_at')
71
+ ->with('subject')
72
+ ->get();
73
+
74
+ foreach ($schedules as $schedule) {
75
+ $subject = $schedule->subject;
76
+ $currentOdometer = null;
77
+ $currentEngHours = null;
78
+
79
+ // Resolve current readings from the subject asset if it is a vehicle
80
+ if ($subject instanceof Vehicle) {
81
+ $currentOdometer = $subject->odometer;
82
+ $currentEngHours = $subject->engine_hours;
83
+ }
84
+
85
+ if (!$schedule->isDue($currentOdometer, $currentEngHours)) {
86
+ continue;
87
+ }
88
+
89
+ // Build a human-readable reason string for logging
90
+ $reasons = [];
91
+ if ($schedule->next_due_date && now()->gte($schedule->next_due_date)) {
92
+ $reasons[] = 'date due ' . $schedule->next_due_date->toDateString();
93
+ }
94
+ if ($schedule->next_due_odometer && $currentOdometer !== null && $currentOdometer >= $schedule->next_due_odometer) {
95
+ $reasons[] = "odometer {$currentOdometer} >= {$schedule->next_due_odometer}";
96
+ }
97
+ if ($schedule->next_due_engine_hours && $currentEngHours !== null && $currentEngHours >= $schedule->next_due_engine_hours) {
98
+ $reasons[] = "engine hours {$currentEngHours} >= {$schedule->next_due_engine_hours}";
99
+ }
100
+
101
+ $this->line("Triggered: schedule {$schedule->public_id} ({$schedule->name}) — " . implode(', ', $reasons));
102
+
103
+ if (!$dryRun) {
104
+ // Check if a pending work order already exists for this schedule
105
+ // to avoid duplicates on repeated runs before the WO is completed
106
+ $existingOpen = WorkOrder::on($conn)
107
+ ->withoutGlobalScopes()
108
+ ->where('schedule_uuid', $schedule->uuid)
109
+ ->whereIn('status', ['open', 'in_progress'])
110
+ ->whereNull('deleted_at')
111
+ ->exists();
112
+
113
+ if ($existingOpen) {
114
+ $this->line(' → Skipped: open work order already exists for this schedule.');
115
+ continue;
116
+ }
117
+
118
+ // Auto-generate a work order from the schedule defaults
119
+ // Generate a sequential WO code: WO-YYYYMMDD-XXXX
120
+ $woCount = WorkOrder::on($conn)->withoutGlobalScopes()->whereNull('deleted_at')->count() + 1;
121
+ $woCode = 'WO-' . now()->format('Ymd') . '-' . str_pad($woCount, 4, '0', STR_PAD_LEFT);
122
+
123
+ $workOrder = WorkOrder::on($conn)->create([
124
+ 'company_uuid' => $schedule->company_uuid,
125
+ 'schedule_uuid' => $schedule->uuid,
126
+ 'subject' => $schedule->name,
127
+ 'code' => $woCode,
128
+ 'status' => 'open',
129
+ 'priority' => $schedule->default_priority ?? 'normal',
130
+ 'target_type' => $schedule->subject_type,
131
+ 'target_uuid' => $schedule->subject_uuid,
132
+ 'assignee_type' => $schedule->default_assignee_type,
133
+ 'assignee_uuid' => $schedule->default_assignee_uuid,
134
+ 'instructions' => $schedule->instructions,
135
+ 'due_at' => $schedule->next_due_date,
136
+ 'opened_at' => now(),
137
+ 'created_by_uuid' => null, // system-generated
138
+ ]);
139
+
140
+ $this->line(" → Created work order {$workOrder->public_id}");
141
+
142
+ event('maintenance.triggered', [$schedule, $workOrder]);
143
+ }
144
+
145
+ $triggered++;
146
+ }
147
+
148
+ $this->info("Processed {$triggered} schedule trigger(s)" . ($dryRun ? ' (dry run — no work orders created)' : '.'));
149
+ }
150
+ }
@@ -0,0 +1,128 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Console\Commands;
4
+
5
+ use Fleetbase\FleetOps\Mail\MaintenanceScheduleReminder;
6
+ use Fleetbase\FleetOps\Models\MaintenanceSchedule;
7
+ use Illuminate\Console\Command;
8
+ use Illuminate\Support\Carbon;
9
+ use Illuminate\Support\Facades\DB;
10
+ use Illuminate\Support\Facades\Mail;
11
+
12
+ /**
13
+ * Sends reminder emails for upcoming maintenance schedules.
14
+ *
15
+ * Runs daily. For each active schedule that has reminder_offsets configured,
16
+ * it checks whether any offset threshold has been crossed (i.e. today is within
17
+ * N days of next_due_date) and — if no reminder has already been sent for that
18
+ * offset + due-date combination — sends a MaintenanceScheduleReminder email to
19
+ * the default assignee and records the send in maintenance_schedule_reminders.
20
+ */
21
+ class SendMaintenanceReminders extends Command
22
+ {
23
+ /**
24
+ * The name and signature of the console command.
25
+ *
26
+ * @var string
27
+ */
28
+ protected $signature = 'fleetops:send-maintenance-reminders
29
+ {--sandbox : Run against the sandbox database connection}
30
+ {--dry-run : Log which emails would be sent without actually sending}';
31
+
32
+ /**
33
+ * The console command description.
34
+ *
35
+ * @var string
36
+ */
37
+ protected $description = 'Send reminder emails for maintenance schedules that are approaching their due date';
38
+
39
+ /**
40
+ * Execute the console command.
41
+ */
42
+ public function handle(): void
43
+ {
44
+ date_default_timezone_set('UTC');
45
+ $sandbox = (bool) $this->option('sandbox');
46
+ $dryRun = (bool) $this->option('dry-run');
47
+ $conn = $sandbox ? 'sandbox' : 'mysql';
48
+
49
+ $this->info('Sending maintenance schedule reminders' . ($dryRun ? ' [DRY RUN]' : '') . ' at ' . Carbon::now()->toDateTimeString());
50
+
51
+ $sent = 0;
52
+
53
+ // Load all active schedules that have:
54
+ // - a non-null next_due_date (reminders are date-based only)
55
+ // - a non-null reminder_offsets JSON array
56
+ // - a default_assignee configured
57
+ $schedules = MaintenanceSchedule::on($conn)
58
+ ->withoutGlobalScopes()
59
+ ->where('status', 'active')
60
+ ->whereNotNull('next_due_date')
61
+ ->whereNotNull('reminder_offsets')
62
+ ->whereNotNull('default_assignee_uuid')
63
+ ->whereNull('deleted_at')
64
+ ->with(['subject', 'defaultAssignee'])
65
+ ->get();
66
+
67
+ foreach ($schedules as $schedule) {
68
+ $offsets = $schedule->reminder_offsets;
69
+ $nextDueDate = $schedule->next_due_date;
70
+
71
+ if (empty($offsets) || !$nextDueDate) {
72
+ continue;
73
+ }
74
+
75
+ // Resolve the recipient email from the polymorphic defaultAssignee
76
+ $assignee = $schedule->defaultAssignee;
77
+ $email = $assignee?->email ?? null;
78
+
79
+ if (!$email) {
80
+ $this->line(" → Skipped schedule {$schedule->public_id}: no email on default assignee.");
81
+ continue;
82
+ }
83
+
84
+ $dueDateSnapshot = $nextDueDate->toDateString();
85
+
86
+ foreach ($offsets as $offsetDays) {
87
+ $offsetDays = (int) $offsetDays;
88
+
89
+ // The reminder window: today must be on or after (due_date - offset_days)
90
+ $reminderDate = $nextDueDate->copy()->subDays($offsetDays);
91
+
92
+ if (Carbon::today()->lt($reminderDate)) {
93
+ // Not yet within the reminder window for this offset
94
+ continue;
95
+ }
96
+
97
+ // Check whether we already sent this reminder for this cycle
98
+ $alreadySent = DB::connection($conn)
99
+ ->table('maintenance_schedule_reminders')
100
+ ->where('schedule_uuid', $schedule->uuid)
101
+ ->where('offset_days', $offsetDays)
102
+ ->where('due_date_snapshot', $dueDateSnapshot)
103
+ ->exists();
104
+
105
+ if ($alreadySent) {
106
+ continue;
107
+ }
108
+
109
+ $this->line("Sending reminder: schedule {$schedule->public_id} ({$schedule->name}) — {$offsetDays} days before {$dueDateSnapshot} → {$email}");
110
+
111
+ if (!$dryRun) {
112
+ Mail::to($email)->send(new MaintenanceScheduleReminder($schedule, $offsetDays));
113
+
114
+ DB::connection($conn)->table('maintenance_schedule_reminders')->insert([
115
+ 'schedule_uuid' => $schedule->uuid,
116
+ 'offset_days' => $offsetDays,
117
+ 'due_date_snapshot' => $dueDateSnapshot,
118
+ 'sent_at' => Carbon::now(),
119
+ ]);
120
+ }
121
+
122
+ $sent++;
123
+ }
124
+ }
125
+
126
+ $this->info("Sent {$sent} reminder(s)" . ($dryRun ? ' (dry run — no emails sent)' : '.'));
127
+ }
128
+ }
@@ -29,6 +29,7 @@ use Maatwebsite\Excel\Facades\Excel;
29
29
 
30
30
  class DriverController extends FleetOpsController
31
31
  {
32
+ use Traits\DriverSchedulingTrait;
32
33
  /**
33
34
  * The resource to query.
34
35
  *
@@ -3,6 +3,9 @@
3
3
  namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
4
 
5
5
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
6
+ use Fleetbase\FleetOps\Imports\EquipmentImport;
7
+ use Fleetbase\Http\Requests\ImportRequest;
8
+ use Maatwebsite\Excel\Facades\Excel;
6
9
 
7
10
  class EquipmentController extends FleetOpsController
8
11
  {
@@ -12,4 +15,28 @@ class EquipmentController extends FleetOpsController
12
15
  * @var string
13
16
  */
14
17
  public $resource = 'equipment';
18
+
19
+ /**
20
+ * Process import files (excel, csv) into Equipment records.
21
+ *
22
+ * @return \Illuminate\Http\Response
23
+ */
24
+ public function import(ImportRequest $request)
25
+ {
26
+ $disk = $request->input('disk', config('filesystems.default'));
27
+ $files = $request->resolveFilesFromIds();
28
+ $importedCount = 0;
29
+
30
+ foreach ($files as $file) {
31
+ try {
32
+ $import = new EquipmentImport();
33
+ Excel::import($import, $file->path, $disk);
34
+ $importedCount += $import->imported;
35
+ } catch (\Throwable $e) {
36
+ return response()->error('Invalid file, unable to process.');
37
+ }
38
+ }
39
+
40
+ return response()->json(['status' => 'ok', 'message' => 'Import completed', 'imported' => $importedCount]);
41
+ }
15
42
  }
@@ -38,9 +38,16 @@ class LiveController extends Controller
38
38
  ->applyDirectivesForPermissions('fleet-ops list order')
39
39
  ->get();
40
40
 
41
- // Loop through each order to get its current destination location
41
+ // Loop through each order to get its current destination location.
42
+ // Filter out null and 0,0 fallback coordinates — these are returned
43
+ // by getCurrentDestinationLocation() when an order has no dropoff or
44
+ // waypoint set, and would cause the location service to resolve to
45
+ // the ocean (0,0 = Gulf of Guinea).
42
46
  foreach ($orders as $order) {
43
- $coordinates[] = $order->getCurrentDestinationLocation();
47
+ $location = $order->getCurrentDestinationLocation();
48
+ if ($location && !($location->getLat() == 0 && $location->getLng() == 0)) {
49
+ $coordinates[] = $location;
50
+ }
44
51
  }
45
52
 
46
53
  return response()->json($coordinates);
@@ -3,6 +3,12 @@
3
3
  namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
4
 
5
5
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
6
+ use Fleetbase\FleetOps\Imports\MaintenanceImport;
7
+ use Fleetbase\FleetOps\Models\Maintenance;
8
+ use Fleetbase\Http\Requests\ImportRequest;
9
+ use Illuminate\Http\JsonResponse;
10
+ use Illuminate\Http\Request;
11
+ use Maatwebsite\Excel\Facades\Excel;
6
12
 
7
13
  class MaintenanceController extends FleetOpsController
8
14
  {
@@ -12,4 +18,163 @@ class MaintenanceController extends FleetOpsController
12
18
  * @var string
13
19
  */
14
20
  public $resource = 'maintenance';
21
+
22
+ /**
23
+ * Eager-load polymorphic relationships after create so they appear in the API response.
24
+ * Called automatically by HasApiControllerBehavior::createRecord() via getControllerCallback.
25
+ */
26
+ public function onAfterCreate($request, Maintenance $record, array $input): void
27
+ {
28
+ $record->load(['maintainable', 'performedBy']);
29
+ }
30
+
31
+ /**
32
+ * Eager-load polymorphic relationships after update so they appear in the API response.
33
+ * Called automatically by HasApiControllerBehavior::updateRecord() via getControllerCallback.
34
+ */
35
+ public function onAfterUpdate($request, Maintenance $record, array $input): void
36
+ {
37
+ $record->load(['maintainable', 'performedBy']);
38
+ }
39
+
40
+ /**
41
+ * Eager-load polymorphic relationships when finding record so they appear in the API response.
42
+ * Called automatically by HasApiControllerBehavior::findRecord() via getControllerCallback.
43
+ */
44
+ public function onFindRecord($builder, $request): void
45
+ {
46
+ $builder->with(['maintainable', 'performedBy']);
47
+ }
48
+
49
+ /**
50
+ * Add a cost line item to a maintenance record.
51
+ * POST /maintenances/{id}/line-items.
52
+ */
53
+ public function addLineItem(string $id, Request $request): JsonResponse
54
+ {
55
+ $maintenance = Maintenance::where('uuid', $id)
56
+ ->orWhere('public_id', $id)
57
+ ->firstOrFail();
58
+
59
+ $validated = $request->validate([
60
+ 'description' => 'required|string|max:255',
61
+ 'quantity' => 'required|numeric|min:0',
62
+ 'unit_cost' => 'required|integer|min:0',
63
+ 'currency' => 'nullable|string|size:3',
64
+ ]);
65
+
66
+ $maintenance->addLineItem($validated);
67
+ $maintenance->refresh();
68
+ $this->recalculateCosts($maintenance);
69
+
70
+ return response()->json([
71
+ 'status' => 'ok',
72
+ 'line_items' => $maintenance->line_items,
73
+ 'total_cost' => $maintenance->total_cost,
74
+ ]);
75
+ }
76
+
77
+ /**
78
+ * Update a cost line item on a maintenance record.
79
+ * PUT /maintenances/{id}/line-items/{index}.
80
+ */
81
+ public function updateLineItem(string $id, int $index, Request $request): JsonResponse
82
+ {
83
+ $maintenance = Maintenance::where('uuid', $id)
84
+ ->orWhere('public_id', $id)
85
+ ->firstOrFail();
86
+
87
+ $validated = $request->validate([
88
+ 'description' => 'required|string|max:255',
89
+ 'quantity' => 'required|numeric|min:0',
90
+ 'unit_cost' => 'required|integer|min:0',
91
+ 'currency' => 'nullable|string|size:3',
92
+ ]);
93
+
94
+ $lineItems = $maintenance->line_items ?? [];
95
+
96
+ if (!isset($lineItems[$index])) {
97
+ return response()->json(['error' => 'Line item not found.'], 404);
98
+ }
99
+
100
+ $lineItems[$index] = array_merge($lineItems[$index], $validated, [
101
+ 'updated_at' => now(),
102
+ ]);
103
+
104
+ $maintenance->update(['line_items' => $lineItems]);
105
+ $maintenance->refresh();
106
+ $this->recalculateCosts($maintenance);
107
+
108
+ return response()->json([
109
+ 'status' => 'ok',
110
+ 'line_items' => $maintenance->line_items,
111
+ 'total_cost' => $maintenance->total_cost,
112
+ ]);
113
+ }
114
+
115
+ /**
116
+ * Remove a cost line item from a maintenance record.
117
+ * DELETE /maintenances/{id}/line-items/{index}.
118
+ */
119
+ public function removeLineItem(string $id, int $index): JsonResponse
120
+ {
121
+ $maintenance = Maintenance::where('uuid', $id)
122
+ ->orWhere('public_id', $id)
123
+ ->firstOrFail();
124
+
125
+ if (!$maintenance->removeLineItem($index)) {
126
+ return response()->json(['error' => 'Line item not found.'], 404);
127
+ }
128
+
129
+ $maintenance->refresh();
130
+ $this->recalculateCosts($maintenance);
131
+
132
+ return response()->json([
133
+ 'status' => 'ok',
134
+ 'line_items' => $maintenance->line_items,
135
+ 'total_cost' => $maintenance->total_cost,
136
+ ]);
137
+ }
138
+
139
+ /**
140
+ * Process import files (excel, csv) into Maintenance records.
141
+ *
142
+ * @return \Illuminate\Http\Response
143
+ */
144
+ public function import(ImportRequest $request)
145
+ {
146
+ $disk = $request->input('disk', config('filesystems.default'));
147
+ $files = $request->resolveFilesFromIds();
148
+ $importedCount = 0;
149
+
150
+ foreach ($files as $file) {
151
+ try {
152
+ $import = new MaintenanceImport();
153
+ Excel::import($import, $file->path, $disk);
154
+ $importedCount += $import->imported;
155
+ } catch (\Throwable $e) {
156
+ return response()->error('Invalid file, unable to process.');
157
+ }
158
+ }
159
+
160
+ return response()->json(['status' => 'ok', 'message' => 'Import completed', 'imported' => $importedCount]);
161
+ }
162
+
163
+ /**
164
+ * Recalculate and persist parts_cost and total_cost derived from line_items.
165
+ * All monetary values are stored as integers (smallest currency unit, e.g. cents).
166
+ */
167
+ protected function recalculateCosts(Maintenance $maintenance): void
168
+ {
169
+ $lineItems = $maintenance->line_items ?? [];
170
+ $partsCost = (int) collect($lineItems)->sum(fn ($item) => ($item['quantity'] ?? 0) * ($item['unit_cost'] ?? 0));
171
+ $laborCost = (int) ($maintenance->labor_cost ?? 0);
172
+ $tax = (int) ($maintenance->tax ?? 0);
173
+ $totalCost = $laborCost + $partsCost + $tax;
174
+
175
+ $maintenance->update([
176
+ 'parts_cost' => $partsCost,
177
+ 'total_cost' => $totalCost,
178
+ ]);
179
+ }
15
180
  }