@fleetbase/fleetops-engine 0.6.38 → 0.6.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. package/addon/components/activity/form.hbs +2 -12
  2. package/addon/components/activity/logic-builder.hbs +1 -1
  3. package/addon/components/custom-entity/form.hbs +4 -20
  4. package/addon/components/device/form.hbs +3 -15
  5. package/addon/components/device/panel-header.hbs +13 -2
  6. package/addon/components/driver/details.hbs +33 -2
  7. package/addon/components/driver/form.hbs +42 -0
  8. package/addon/components/driver/panel-header.hbs +8 -1
  9. package/addon/components/driver/schedule.hbs +115 -76
  10. package/addon/components/driver/schedule.js +349 -157
  11. package/addon/components/driver-onboard-settings.hbs +2 -8
  12. package/addon/components/entity-field-editing-settings.hbs +2 -5
  13. package/addon/components/equipment/card.hbs +49 -0
  14. package/addon/components/equipment/card.js +6 -0
  15. package/addon/components/equipment/details.hbs +83 -44
  16. package/addon/components/equipment/form.hbs +111 -41
  17. package/addon/components/equipment/form.js +78 -10
  18. package/addon/components/equipment/panel-header.hbs +36 -0
  19. package/addon/components/equipment/panel-header.js +2 -0
  20. package/addon/components/fleet/driver-listing.hbs +3 -1
  21. package/addon/components/fleet/vehicle-listing.hbs +4 -7
  22. package/addon/components/fleet-panel/vehicle-listing.hbs +1 -6
  23. package/addon/components/fuel-report/form.hbs +1 -5
  24. package/addon/components/layout/fleet-ops-sidebar.hbs +43 -38
  25. package/addon/components/layout/fleet-ops-sidebar.js +103 -24
  26. package/addon/components/maintenance/cost-panel.hbs +176 -0
  27. package/addon/components/maintenance/cost-panel.js +241 -0
  28. package/addon/components/maintenance/details.hbs +123 -60
  29. package/addon/components/maintenance/form.hbs +138 -78
  30. package/addon/components/maintenance/form.js +131 -6
  31. package/addon/components/maintenance/panel-header.hbs +31 -0
  32. package/addon/components/maintenance/panel-header.js +2 -0
  33. package/addon/components/maintenance-schedule/details.hbs +260 -0
  34. package/addon/components/maintenance-schedule/details.js +158 -0
  35. package/addon/components/maintenance-schedule/form.hbs +287 -0
  36. package/addon/components/maintenance-schedule/form.js +199 -0
  37. package/addon/components/map/container.hbs +1 -1
  38. package/addon/components/map/drawer/device-event-listing.hbs +1 -1
  39. package/addon/components/map/drawer/driver-listing.hbs +1 -6
  40. package/addon/components/map/drawer/place-listing.hbs +1 -6
  41. package/addon/components/map/drawer/vehicle-listing.hbs +1 -6
  42. package/addon/components/map/drawer.hbs +8 -1
  43. package/addon/components/map/leaflet-live-map.hbs +2 -1
  44. package/addon/components/map/toolbar/visibility-control-panel.hbs +1 -1
  45. package/addon/components/map/toolbar.hbs +4 -29
  46. package/addon/components/modals/add-driver-shift.hbs +155 -0
  47. package/addon/components/modals/add-driver-shift.js +210 -0
  48. package/addon/components/modals/bulk-assign-orders.hbs +67 -0
  49. package/addon/components/modals/driver-shift.hbs +43 -0
  50. package/addon/components/modals/driver-shift.js +56 -0
  51. package/addon/components/modals/entity-form.hbs +5 -28
  52. package/addon/components/modals/orchestrator-import.hbs +351 -0
  53. package/addon/components/modals/orchestrator-import.js +807 -0
  54. package/addon/components/modals/order-config-new-status.hbs +1 -5
  55. package/addon/components/modals/scheduling-conflict.hbs +47 -0
  56. package/addon/components/modals/scheduling-conflict.js +53 -0
  57. package/addon/components/modals/send-work-order.hbs +91 -0
  58. package/addon/components/modals/send-work-order.js +3 -0
  59. package/addon/components/modals/service-area-form.hbs +1 -6
  60. package/addon/components/modals/set-driver-availability.hbs +50 -0
  61. package/addon/components/modals/set-driver-availability.js +57 -0
  62. package/addon/components/modals/update-order-activity.hbs +13 -9
  63. package/addon/components/modals/user-form.hbs +1 -5
  64. package/addon/components/modals/vehicle-form.hbs +17 -102
  65. package/addon/components/modals/vendor-form.hbs +15 -82
  66. package/addon/components/orchestrator/card-fields-settings.hbs +76 -0
  67. package/addon/components/orchestrator/card-fields-settings.js +134 -0
  68. package/addon/components/orchestrator/order-pool.hbs +264 -0
  69. package/addon/components/orchestrator/order-pool.js +394 -0
  70. package/addon/components/orchestrator/phase-builder.hbs +162 -0
  71. package/addon/components/orchestrator/phase-builder.js +162 -0
  72. package/addon/components/orchestrator/plan-viewer.hbs +278 -0
  73. package/addon/components/orchestrator/plan-viewer.js +342 -0
  74. package/addon/components/orchestrator/resource-panel.hbs +301 -0
  75. package/addon/components/orchestrator/resource-panel.js +106 -0
  76. package/addon/components/orchestrator-workbench.hbs +318 -0
  77. package/addon/components/orchestrator-workbench.js +1088 -0
  78. package/addon/components/order/details/custom-fields.hbs +10 -1
  79. package/addon/components/order/details/detail.hbs +37 -2
  80. package/addon/components/order/details/detail.js +0 -1
  81. package/addon/components/order/details/integrated-vendor-details.hbs +4 -4
  82. package/addon/components/order/details/notes.hbs +1 -7
  83. package/addon/components/order/details/route.hbs +1 -5
  84. package/addon/components/order/form/details.hbs +44 -10
  85. package/addon/components/order/form/details.js +57 -0
  86. package/addon/components/order/form/notes.hbs +1 -7
  87. package/addon/components/order/form/payload.hbs +1 -7
  88. package/addon/components/order/form/route.hbs +3 -15
  89. package/addon/components/order/header.hbs +1 -7
  90. package/addon/components/order/route-editor.hbs +4 -25
  91. package/addon/components/order/schedule-card.hbs +102 -95
  92. package/addon/components/order/schedule-card.js +8 -3
  93. package/addon/components/order-config-manager/details.hbs +2 -10
  94. package/addon/components/order-config-manager/entities.hbs +1 -7
  95. package/addon/components/order-config-manager.hbs +1 -6
  96. package/addon/components/part/card.hbs +49 -0
  97. package/addon/components/part/card.js +6 -0
  98. package/addon/components/part/details.hbs +102 -56
  99. package/addon/components/part/form.hbs +131 -56
  100. package/addon/components/part/form.js +78 -11
  101. package/addon/components/part/panel-header.hbs +36 -0
  102. package/addon/components/part/panel-header.js +2 -0
  103. package/addon/components/place/form.hbs +1 -7
  104. package/addon/components/sensor/details.hbs +1 -1
  105. package/addon/components/sensor/form.hbs +5 -3
  106. package/addon/components/sensor/panel-header.hbs +8 -1
  107. package/addon/components/service-area/form.hbs +1 -6
  108. package/addon/components/service-rate/form.hbs +12 -60
  109. package/addon/components/telematic/form.hbs +6 -2
  110. package/addon/components/vehicle/details/maintenance-history.hbs +42 -0
  111. package/addon/components/vehicle/details/maintenance-history.js +32 -0
  112. package/addon/components/vehicle/details/schedules.hbs +40 -0
  113. package/addon/components/vehicle/details/schedules.js +32 -0
  114. package/addon/components/vehicle/details/work-orders.hbs +42 -0
  115. package/addon/components/vehicle/details/work-orders.js +32 -0
  116. package/addon/components/vehicle/details.hbs +52 -0
  117. package/addon/components/vehicle/form.hbs +51 -0
  118. package/addon/components/vehicle/panel-header.hbs +19 -49
  119. package/addon/components/warranty/details.hbs +3 -2
  120. package/addon/components/warranty/form.hbs +3 -17
  121. package/addon/components/work-order/details.hbs +135 -40
  122. package/addon/components/work-order/form.hbs +178 -45
  123. package/addon/components/work-order/form.js +197 -4
  124. package/addon/components/work-order/panel-header.hbs +31 -0
  125. package/addon/components/work-order/panel-header.js +2 -0
  126. package/addon/controllers/connectivity/devices/index/details.js +1 -1
  127. package/addon/controllers/maintenance/equipment/index/details/index.js +0 -1
  128. package/addon/controllers/maintenance/equipment/index/details.js +36 -1
  129. package/addon/controllers/maintenance/equipment/index/edit.js +56 -1
  130. package/addon/controllers/maintenance/equipment/index/new.js +32 -1
  131. package/addon/controllers/maintenance/equipment/index.js +127 -113
  132. package/addon/controllers/maintenance/maintenances/index/details/index.js +3 -0
  133. package/addon/controllers/maintenance/maintenances/index/details.js +54 -0
  134. package/addon/controllers/maintenance/maintenances/index/edit.js +68 -0
  135. package/addon/controllers/maintenance/maintenances/index/new.js +34 -0
  136. package/addon/controllers/maintenance/maintenances/index.js +191 -0
  137. package/addon/controllers/maintenance/parts/index/details/index.js +0 -1
  138. package/addon/controllers/maintenance/parts/index/details.js +36 -1
  139. package/addon/controllers/maintenance/parts/index/edit.js +56 -1
  140. package/addon/controllers/maintenance/parts/index/new.js +32 -1
  141. package/addon/controllers/maintenance/parts/index.js +135 -113
  142. package/addon/controllers/maintenance/schedules/index/details.js +115 -0
  143. package/addon/controllers/maintenance/schedules/index/edit.js +41 -0
  144. package/addon/controllers/maintenance/schedules/index/new.js +33 -0
  145. package/addon/controllers/maintenance/schedules/index.js +280 -0
  146. package/addon/controllers/maintenance/work-orders/index/details.js +41 -1
  147. package/addon/controllers/maintenance/work-orders/index/edit.js +67 -1
  148. package/addon/controllers/maintenance/work-orders/index/new.js +43 -1
  149. package/addon/controllers/maintenance/work-orders/index.js +105 -113
  150. package/addon/controllers/management/drivers/index/details.js +6 -1
  151. package/addon/controllers/management/vehicles/index/details.js +65 -0
  152. package/addon/controllers/management/vehicles/index.js +18 -0
  153. package/addon/controllers/operations/orchestrator.js +10 -0
  154. package/addon/controllers/operations/orders/index.js +6 -0
  155. package/addon/controllers/operations/scheduler/fleet-schedule.js +341 -0
  156. package/addon/controllers/operations/scheduler/index.js +799 -275
  157. package/addon/controllers/operations/scheduler.js +21 -0
  158. package/addon/controllers/settings/orchestrator.js +70 -0
  159. package/addon/controllers/settings/scheduling.js +155 -0
  160. package/addon/extension.js +19 -0
  161. package/addon/instance-initializers/register-vroom-allocation.js +27 -0
  162. package/addon/models/maintenance-schedule.js +61 -0
  163. package/addon/routes/maintenance/equipment/index/details.js +27 -1
  164. package/addon/routes/maintenance/equipment/index/edit.js +27 -1
  165. package/addon/routes/maintenance/maintenances/index/details/index.js +3 -0
  166. package/addon/routes/maintenance/maintenances/index/details.js +29 -0
  167. package/addon/routes/maintenance/maintenances/index/edit.js +29 -0
  168. package/addon/routes/maintenance/maintenances/index/new.js +3 -0
  169. package/addon/routes/maintenance/maintenances/index.js +23 -0
  170. package/addon/routes/maintenance/maintenances.js +3 -0
  171. package/addon/routes/maintenance/parts/index/details.js +27 -1
  172. package/addon/routes/maintenance/parts/index/edit.js +27 -1
  173. package/addon/routes/maintenance/schedules/index/details/index.js +2 -0
  174. package/addon/routes/maintenance/schedules/index/details/work-orders.js +11 -0
  175. package/addon/routes/maintenance/schedules/index/details.js +25 -0
  176. package/addon/routes/maintenance/schedules/index/edit.js +25 -0
  177. package/addon/routes/maintenance/schedules/index/new.js +2 -0
  178. package/addon/routes/maintenance/schedules/index.js +21 -0
  179. package/addon/routes/maintenance/schedules.js +2 -0
  180. package/addon/routes/maintenance/work-orders/index/details.js +27 -1
  181. package/addon/routes/maintenance/work-orders/index/edit.js +27 -1
  182. package/addon/routes/management/vehicles/index/details/maintenance-history.js +3 -0
  183. package/addon/routes/management/vehicles/index/details/schedules.js +3 -0
  184. package/addon/routes/management/vehicles/index/details/work-orders.js +3 -0
  185. package/addon/routes/operations/orchestrator.js +23 -0
  186. package/addon/routes/operations/scheduler/fleet-schedule.js +28 -0
  187. package/addon/routes/operations/scheduler/index.js +48 -26
  188. package/addon/routes/operations/scheduler.js +14 -1
  189. package/addon/routes/settings/orchestrator.js +27 -0
  190. package/addon/routes/settings/scheduling.js +3 -0
  191. package/addon/routes.js +31 -1
  192. package/addon/services/driver-actions.js +40 -7
  193. package/addon/services/driver-scheduling.js +4 -1
  194. package/addon/services/equipment-actions.js +15 -5
  195. package/addon/services/leaflet-map-manager.js +14 -6
  196. package/addon/services/maintenance-actions.js +17 -14
  197. package/addon/services/maintenance-schedule-actions.js +118 -0
  198. package/addon/services/orchestration-engine-interface.js +49 -0
  199. package/addon/services/orchestration-engine.js +74 -0
  200. package/addon/services/order-actions.js +15 -0
  201. package/addon/services/order-allocation.js +116 -0
  202. package/addon/services/part-actions.js +12 -2
  203. package/addon/services/scheduling.js +316 -0
  204. package/addon/services/vehicle-actions.js +70 -7
  205. package/addon/services/vroom-allocation-engine.js +45 -0
  206. package/addon/services/work-order-actions.js +79 -0
  207. package/addon/styles/fleetops-engine.css +1658 -0
  208. package/addon/templates/analytics/reports/index/edit.hbs +1 -1
  209. package/addon/templates/analytics/reports/index/new.hbs +1 -1
  210. package/addon/templates/application.hbs +6 -1
  211. package/addon/templates/connectivity/devices/index/details/events.hbs +0 -1
  212. package/addon/templates/connectivity/devices.hbs +0 -1
  213. package/addon/templates/connectivity/events/index/details.hbs +0 -1
  214. package/addon/templates/connectivity/events.hbs +0 -1
  215. package/addon/templates/connectivity/sensors.hbs +0 -1
  216. package/addon/templates/connectivity/telematics/index/details/devices.hbs +0 -1
  217. package/addon/templates/connectivity/telematics/index/details/events.hbs +0 -1
  218. package/addon/templates/connectivity/telematics/index/details/sensors.hbs +0 -1
  219. package/addon/templates/connectivity/telematics.hbs +0 -1
  220. package/addon/templates/connectivity/tracking.hbs +0 -1
  221. package/addon/templates/connectivity.hbs +0 -1
  222. package/addon/templates/maintenance/equipment/index/details/index.hbs +1 -2
  223. package/addon/templates/maintenance/equipment/index/details.hbs +15 -2
  224. package/addon/templates/maintenance/equipment/index/edit.hbs +12 -2
  225. package/addon/templates/maintenance/equipment/index/new.hbs +1 -2
  226. package/addon/templates/maintenance/equipment/index.hbs +48 -13
  227. package/addon/templates/maintenance/equipment.hbs +0 -1
  228. package/addon/templates/maintenance/maintenances/index/details/index.hbs +1 -0
  229. package/addon/templates/maintenance/maintenances/index/details.hbs +15 -0
  230. package/addon/templates/maintenance/maintenances/index/edit.hbs +12 -0
  231. package/addon/templates/maintenance/maintenances/index/new.hbs +11 -0
  232. package/addon/templates/maintenance/maintenances/index.hbs +14 -0
  233. package/addon/templates/maintenance/maintenances.hbs +1 -0
  234. package/addon/templates/maintenance/parts/index/details/index.hbs +1 -2
  235. package/addon/templates/maintenance/parts/index/details.hbs +15 -2
  236. package/addon/templates/maintenance/parts/index/edit.hbs +12 -2
  237. package/addon/templates/maintenance/parts/index/new.hbs +1 -2
  238. package/addon/templates/maintenance/parts/index.hbs +48 -13
  239. package/addon/templates/maintenance/parts.hbs +0 -1
  240. package/addon/templates/maintenance/schedules/index/details/index.hbs +1 -0
  241. package/addon/templates/maintenance/schedules/index/details/work-orders.hbs +39 -0
  242. package/addon/templates/maintenance/schedules/index/details.hbs +14 -0
  243. package/addon/templates/maintenance/schedules/index/edit.hbs +12 -0
  244. package/addon/templates/maintenance/schedules/index/new.hbs +11 -0
  245. package/addon/templates/maintenance/schedules/index.hbs +40 -0
  246. package/addon/templates/maintenance/schedules.hbs +1 -0
  247. package/addon/templates/maintenance/work-orders/index/details.hbs +2 -1
  248. package/addon/templates/maintenance/work-orders/index/edit.hbs +2 -4
  249. package/addon/templates/maintenance/work-orders/index/new.hbs +1 -2
  250. package/addon/templates/maintenance/work-orders.hbs +0 -1
  251. package/addon/templates/management/contacts/customers/edit.hbs +1 -2
  252. package/addon/templates/management/contacts/customers/new.hbs +1 -2
  253. package/addon/templates/management/contacts/customers.hbs +1 -1
  254. package/addon/templates/management/contacts/index/edit.hbs +1 -2
  255. package/addon/templates/management/contacts/index/new.hbs +1 -2
  256. package/addon/templates/management/drivers/index/details/orders.hbs +0 -1
  257. package/addon/templates/management/drivers/index/edit.hbs +1 -2
  258. package/addon/templates/management/drivers/index/new.hbs +1 -2
  259. package/addon/templates/management/fleets/index/edit.hbs +1 -2
  260. package/addon/templates/management/fleets/index/new.hbs +1 -2
  261. package/addon/templates/management/fleets/index.hbs +1 -2
  262. package/addon/templates/management/fuel-reports/index/edit.hbs +1 -2
  263. package/addon/templates/management/fuel-reports/index/new.hbs +1 -2
  264. package/addon/templates/management/fuel-reports/index.hbs +1 -2
  265. package/addon/templates/management/issues/index/edit.hbs +1 -2
  266. package/addon/templates/management/issues/index/new.hbs +1 -2
  267. package/addon/templates/management/issues/index.hbs +1 -2
  268. package/addon/templates/management/places/index/details/activity.hbs +0 -1
  269. package/addon/templates/management/places/index/details/comments.hbs +0 -1
  270. package/addon/templates/management/places/index/details/documents.hbs +0 -1
  271. package/addon/templates/management/places/index/details/map.hbs +0 -1
  272. package/addon/templates/management/places/index/details/operations.hbs +0 -1
  273. package/addon/templates/management/places/index/details/performance.hbs +0 -1
  274. package/addon/templates/management/places/index/details/rules.hbs +0 -1
  275. package/addon/templates/management/vehicles/index/details/equipment.hbs +0 -1
  276. package/addon/templates/management/vehicles/index/details/maintenance-history.hbs +2 -0
  277. package/addon/templates/management/vehicles/index/details/schedules.hbs +2 -0
  278. package/addon/templates/management/vehicles/index/details/work-orders.hbs +2 -0
  279. package/addon/templates/management/vehicles/index/details.hbs +1 -1
  280. package/addon/templates/management/vehicles/index/edit.hbs +1 -2
  281. package/addon/templates/management/vehicles/index/new.hbs +1 -2
  282. package/addon/templates/management/vendors/index/edit.hbs +1 -2
  283. package/addon/templates/management/vendors/index/new.hbs +1 -2
  284. package/addon/templates/management/vendors/index.hbs +1 -2
  285. package/addon/templates/management/vendors/integrated.hbs +1 -2
  286. package/addon/templates/operations/orchestrator.hbs +1 -0
  287. package/addon/templates/operations/orders/index.hbs +6 -1
  288. package/addon/templates/operations/scheduler/fleet-schedule.hbs +41 -0
  289. package/addon/templates/operations/scheduler/index.hbs +147 -88
  290. package/addon/templates/operations/scheduler.hbs +7 -1
  291. package/addon/templates/settings/avatars.hbs +1 -1
  292. package/addon/templates/settings/orchestrator.hbs +65 -0
  293. package/addon/templates/settings/payments/index.hbs +1 -5
  294. package/addon/templates/settings/scheduling.hbs +82 -0
  295. package/addon/utils/create-full-calendar-event-from-order.js +52 -14
  296. package/addon/utils/create-full-calendar-event-from-schedule-item.js +50 -0
  297. package/addon/utils/fleet-ops-options.js +254 -0
  298. package/addon/utils/route-colors.js +99 -0
  299. package/addon/utils/to-calendar-date.js +70 -0
  300. package/app/components/driver/schedule.js +1 -0
  301. package/app/components/maintenance/cost-panel.js +1 -0
  302. package/app/components/maintenance/panel-header.js +1 -0
  303. package/app/components/maintenance-schedule/details.js +1 -0
  304. package/app/components/maintenance-schedule/form.js +1 -0
  305. package/app/components/modals/add-driver-shift.js +1 -0
  306. package/app/components/modals/bulk-assign-orders.js +1 -0
  307. package/app/components/modals/driver-shift.js +1 -0
  308. package/app/components/modals/orchestrator-import.js +1 -0
  309. package/app/components/modals/scheduling-conflict.js +1 -0
  310. package/app/components/modals/send-work-order.js +1 -0
  311. package/app/components/modals/set-driver-availability.js +1 -0
  312. package/app/components/orchestrator/card-fields-settings.js +1 -0
  313. package/app/components/orchestrator/order-pool.js +1 -0
  314. package/app/components/orchestrator/phase-builder.js +1 -0
  315. package/app/components/orchestrator/plan-viewer.js +1 -0
  316. package/app/components/orchestrator/resource-panel.js +1 -0
  317. package/app/components/orchestrator-workbench.js +1 -0
  318. package/app/components/vehicle/details/maintenance-history.js +1 -0
  319. package/app/components/vehicle/details/schedules.js +1 -0
  320. package/app/components/vehicle/details/work-orders.js +1 -0
  321. package/app/controllers/operations/orchestrator.js +1 -0
  322. package/app/controllers/settings/orchestrator.js +1 -0
  323. package/app/controllers/settings/scheduling.js +1 -0
  324. package/app/routes/operations/orchestrator.js +1 -0
  325. package/app/routes/settings/orchestrator.js +1 -0
  326. package/app/routes/settings/scheduling.js +1 -0
  327. package/app/services/maintenance-schedule-actions.js +1 -0
  328. package/app/services/orchestration-engine-interface.js +1 -0
  329. package/app/services/orchestration-engine.js +1 -0
  330. package/app/services/order-allocation.js +1 -0
  331. package/app/services/scheduling.js +1 -0
  332. package/app/services/vroom-allocation-engine.js +1 -0
  333. package/app/templates/settings/scheduling.js +1 -0
  334. package/app/utils/create-full-calendar-event-from-schedule-item.js +1 -0
  335. package/app/utils/route-colors.js +1 -0
  336. package/composer.json +4 -2
  337. package/extension.json +1 -1
  338. package/package.json +6 -5
  339. package/server/config/fleetops.php +20 -1
  340. package/server/migrations/2025_08_28_054927_create_parts_table.php +2 -2
  341. package/server/migrations/2025_08_28_054932_add_public_id_to_maintenance_tables.php +45 -0
  342. package/server/migrations/2025_09_01_000001_create_maintenance_schedules_table.php +88 -0
  343. package/server/migrations/2026_04_01_000001_fix_monetary_columns_in_parts_table.php +48 -0
  344. package/server/migrations/2026_04_01_000003_add_photo_uuid_to_equipment_and_parts_tables.php +61 -0
  345. package/server/migrations/2026_04_01_000004_add_public_id_to_equipments_table.php +38 -0
  346. package/server/migrations/2026_04_01_000005_add_missing_columns_to_parts_table.php +67 -0
  347. package/server/migrations/2026_04_04_000001_add_reminder_offsets_to_maintenance_schedules.php +44 -0
  348. package/server/migrations/2026_04_08_000001_add_orchestrator_columns_to_vehicles_table.php +53 -0
  349. package/server/migrations/2026_04_08_000002_add_orchestrator_columns_to_drivers_table.php +41 -0
  350. package/server/migrations/2026_04_08_000003_add_orchestrator_columns_to_orders_table.php +38 -0
  351. package/server/migrations/2026_04_08_000004_add_orchestrator_columns_to_payloads_table.php +38 -0
  352. package/server/migrations/2026_04_08_000005_add_orchestrator_columns_to_waypoints_table.php +38 -0
  353. package/server/migrations/2026_04_09_000001_create_manifests_table.php +48 -0
  354. package/server/migrations/2026_04_09_000002_create_manifest_stops_table.php +48 -0
  355. package/server/migrations/2026_04_09_000003_add_manifest_uuid_to_orders_table.php +28 -0
  356. package/server/migrations/2026_04_13_000001_add_pod_notes_columns_to_waypoints_table.php +39 -0
  357. package/server/migrations/2026_04_14_000001_drop_redundant_capacity_columns_from_payloads_table.php +42 -0
  358. package/server/migrations/2026_04_14_000002_rename_capacity_columns_on_vehicles_table.php +48 -0
  359. package/server/resources/views/mail/maintenance-schedule-reminder.blade.php +59 -0
  360. package/server/resources/views/mail/work-order-dispatched.blade.php +67 -0
  361. package/server/src/Auth/Schemas/FleetOps.php +44 -0
  362. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +150 -0
  363. package/server/src/Console/Commands/SendMaintenanceReminders.php +128 -0
  364. package/server/src/Http/Controllers/Api/v1/OrderController.php +15 -2
  365. package/server/src/Http/Controllers/Api/v1/VehicleController.php +18 -2
  366. package/server/src/Http/Controllers/Internal/v1/DriverController.php +1 -0
  367. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +27 -0
  368. package/server/src/Http/Controllers/Internal/v1/LiveController.php +9 -2
  369. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +165 -0
  370. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +304 -0
  371. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +138 -0
  372. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +975 -0
  373. package/server/src/Http/Controllers/Internal/v1/OrderController.php +42 -0
  374. package/server/src/Http/Controllers/Internal/v1/PartController.php +27 -0
  375. package/server/src/Http/Controllers/Internal/v1/SettingController.php +118 -0
  376. package/server/src/Http/Controllers/Internal/v1/Traits/DriverSchedulingTrait.php +214 -0
  377. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +68 -0
  378. package/server/src/Http/Resources/v1/Driver.php +1 -0
  379. package/server/src/Http/Resources/v1/Maintenance.php +138 -0
  380. package/server/src/Http/Resources/v1/MaintenanceSchedule.php +137 -0
  381. package/server/src/Http/Resources/v1/Orchestrator/Order.php +116 -0
  382. package/server/src/Http/Resources/v1/Order.php +7 -4
  383. package/server/src/Http/Resources/v1/Waypoint.php +7 -0
  384. package/server/src/Http/Resources/v1/WorkOrder.php +136 -0
  385. package/server/src/Imports/EquipmentImport.php +32 -0
  386. package/server/src/Imports/MaintenanceImport.php +32 -0
  387. package/server/src/Imports/MaintenanceScheduleImport.php +32 -0
  388. package/server/src/Imports/PartImport.php +32 -0
  389. package/server/src/Imports/WorkOrderImport.php +32 -0
  390. package/server/src/Jobs/ProcessAllocationJob.php +119 -0
  391. package/server/src/Listeners/HandleDeliveryCompletion.php +47 -0
  392. package/server/src/Listeners/NotifyDriverOnShiftChange.php +63 -0
  393. package/server/src/Mail/MaintenanceScheduleReminder.php +68 -0
  394. package/server/src/Mail/WorkOrderDispatched.php +58 -0
  395. package/server/src/Models/Asset.php +2 -2
  396. package/server/src/Models/Device.php +1 -1
  397. package/server/src/Models/Driver.php +82 -4
  398. package/server/src/Models/Equipment.php +62 -2
  399. package/server/src/Models/Maintenance.php +127 -9
  400. package/server/src/Models/MaintenanceSchedule.php +353 -0
  401. package/server/src/Models/Manifest.php +214 -0
  402. package/server/src/Models/ManifestStop.php +162 -0
  403. package/server/src/Models/Order.php +70 -0
  404. package/server/src/Models/OrderConfig.php +5 -2
  405. package/server/src/Models/Part.php +69 -3
  406. package/server/src/Models/Place.php +1 -1
  407. package/server/src/Models/Sensor.php +1 -1
  408. package/server/src/Models/ServiceQuote.php +1 -1
  409. package/server/src/Models/Vehicle.php +18 -1
  410. package/server/src/Models/Warranty.php +1 -1
  411. package/server/src/Models/Waypoint.php +7 -1
  412. package/server/src/Models/WorkOrder.php +122 -12
  413. package/server/src/Notifications/DriverShiftChanged.php +110 -0
  414. package/server/src/Observers/WorkOrderObserver.php +107 -0
  415. package/server/src/Orchestration/Contracts/OrchestrationEngineInterface.php +63 -0
  416. package/server/src/Orchestration/Engines/DriverAssignmentEngine.php +265 -0
  417. package/server/src/Orchestration/Engines/GreedyOrchestrationEngine.php +155 -0
  418. package/server/src/Orchestration/Engines/RouteSequencingEngine.php +272 -0
  419. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +192 -0
  420. package/server/src/Orchestration/OrchestrationEngineRegistry.php +83 -0
  421. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +380 -0
  422. package/server/src/Providers/EventServiceProvider.php +7 -1
  423. package/server/src/Providers/FleetOpsServiceProvider.php +42 -15
  424. package/server/src/routes.php +65 -4
  425. package/translations/ar-ae.yml +44 -12
  426. package/translations/bg-bg.yaml +51 -10
  427. package/translations/en-us.yaml +444 -1
  428. package/translations/fr-fr.yaml +51 -10
  429. package/translations/mn-mn.yaml +51 -10
  430. package/translations/pt-br.yaml +51 -10
  431. package/translations/ru-ru.yaml +51 -10
  432. package/translations/vi-vn.yaml +48 -12
@@ -0,0 +1,260 @@
1
+ <div class="next-content-panel-wrapper" ...attributes>
2
+
3
+ {{! ===== OVERVIEW ===== }}
4
+ <ContentPanel @title="Overview" @open={{true}} @wrapperClass="bordered-top">
5
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-2 text-xs dark:text-gray-100">
6
+
7
+ {{! — Identity — }}
8
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-1">
9
+ Identity
10
+ </div>
11
+ <div class="field-info-container">
12
+ <div class="field-name">ID</div>
13
+ <ClickToCopy @value={{@resource.public_id}} class="field-value font-mono">{{n-a @resource.public_id}}</ClickToCopy>
14
+ </div>
15
+ <div class="field-info-container">
16
+ <div class="field-name">Name</div>
17
+ <div class="field-value">{{n-a @resource.name}}</div>
18
+ </div>
19
+ <div class="field-info-container">
20
+ <div class="field-name">Type</div>
21
+ <div class="field-value">{{n-a (smart-humanize @resource.type)}}</div>
22
+ </div>
23
+ <div class="field-info-container">
24
+ <div class="field-name">Status</div>
25
+ <div class="field-value">
26
+ <Badge @status={{@resource.status}}>{{smart-humanize @resource.status}}</Badge>
27
+ </div>
28
+ </div>
29
+
30
+ {{! — Assignment — }}
31
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
32
+ Assignment
33
+ </div>
34
+ <div class="field-info-container">
35
+ <div class="field-name">Asset</div>
36
+ <div class="field-value">{{n-a (or @resource.subject.displayName @resource.subject.name @resource.subject_name)}}</div>
37
+ </div>
38
+ {{#if (or @resource.default_assignee.displayName @resource.default_assignee.name)}}
39
+ <div class="field-info-container">
40
+ <div class="field-name">Default Assignee</div>
41
+ <div class="field-value">{{n-a (or @resource.default_assignee.displayName @resource.default_assignee.name)}}</div>
42
+ </div>
43
+ {{/if}}
44
+ <div class="field-info-container">
45
+ <div class="field-name">Default Priority</div>
46
+ <div class="field-value">
47
+ {{#if (eq @resource.default_priority "critical")}}
48
+ <Badge @status="danger">Critical</Badge>
49
+ {{else if (eq @resource.default_priority "high")}}
50
+ <Badge @status="warning">High</Badge>
51
+ {{else if (eq @resource.default_priority "medium")}}
52
+ <Badge @status="info">Medium</Badge>
53
+ {{else}}
54
+ <Badge @status="default">Low</Badge>
55
+ {{/if}}
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </ContentPanel>
60
+
61
+ {{! ===== INTERVAL TRIGGERS ===== }}
62
+ <ContentPanel @title="Interval Triggers" @open={{true}} @wrapperClass="bordered-top">
63
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-2 text-xs dark:text-gray-100">
64
+
65
+ {{#if @resource.interval_value}}
66
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-1">
67
+ Time-Based
68
+ </div>
69
+ <div class="field-info-container">
70
+ <div class="field-name">Repeat Every</div>
71
+ <div class="field-value">{{@resource.interval_value}} {{@resource.interval_unit}}</div>
72
+ </div>
73
+ {{/if}}
74
+
75
+ {{#if @resource.interval_distance}}
76
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
77
+ Distance-Based
78
+ </div>
79
+ <div class="field-info-container">
80
+ <div class="field-name">Every (km)</div>
81
+ <div class="field-value">{{@resource.interval_distance}} km</div>
82
+ </div>
83
+ {{/if}}
84
+
85
+ {{#if @resource.interval_engine_hours}}
86
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
87
+ Engine Hours-Based
88
+ </div>
89
+ <div class="field-info-container">
90
+ <div class="field-name">Every (hours)</div>
91
+ <div class="field-value">{{@resource.interval_engine_hours}} hrs</div>
92
+ </div>
93
+ {{/if}}
94
+ </div>
95
+ </ContentPanel>
96
+
97
+ {{! ===== SERVICE HISTORY & NEXT DUE ===== }}
98
+ <ContentPanel @title="Service History & Next Due" @open={{true}} @wrapperClass="bordered-top">
99
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-2 text-xs dark:text-gray-100">
100
+
101
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-1">
102
+ Last Service
103
+ </div>
104
+ <div class="field-info-container">
105
+ <div class="field-name">Last Service Date</div>
106
+ <div class="field-value">{{n-a (format-date-fns @resource.last_service_date "dd MMM yyyy")}}</div>
107
+ </div>
108
+ <div class="field-info-container">
109
+ <div class="field-name">Last Odometer</div>
110
+ <div class="field-value">
111
+ {{#if @resource.last_service_odometer}}
112
+ {{@resource.last_service_odometer}}
113
+ km
114
+ {{else}}
115
+
116
+ {{/if}}
117
+ </div>
118
+ </div>
119
+ <div class="field-info-container">
120
+ <div class="field-name">Last Engine Hours</div>
121
+ <div class="field-value">{{n-a @resource.last_service_engine_hours}}</div>
122
+ </div>
123
+
124
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
125
+ Next Due
126
+ </div>
127
+ <div class="field-info-container">
128
+ <div class="field-name">Next Due Date</div>
129
+ <div class="field-value">{{n-a (format-date-fns @resource.next_due_date "dd MMM yyyy")}}</div>
130
+ </div>
131
+ <div class="field-info-container">
132
+ <div class="field-name">Next Due Odometer</div>
133
+ <div class="field-value">
134
+ {{#if @resource.next_due_odometer}}
135
+ {{@resource.next_due_odometer}}
136
+ km
137
+ {{else}}
138
+
139
+ {{/if}}
140
+ </div>
141
+ </div>
142
+ <div class="field-info-container">
143
+ <div class="field-name">Next Due Engine Hours</div>
144
+ <div class="field-value">{{n-a @resource.next_due_engine_hours}}</div>
145
+ </div>
146
+ </div>
147
+ </ContentPanel>
148
+
149
+ {{! ===== INSTRUCTIONS ===== }}
150
+ {{#if @resource.instructions}}
151
+ <ContentPanel @title="Instructions" @open={{false}} @wrapperClass="bordered-top">
152
+ <div class="text-xs text-gray-700 dark:text-gray-300 whitespace-pre-wrap">
153
+ {{@resource.instructions}}
154
+ </div>
155
+ </ContentPanel>
156
+ {{/if}}
157
+
158
+ {{! ===== UPCOMING SCHEDULE CALENDAR (time-based only) ===== }}
159
+ {{#if this.isTimeBased}}
160
+ <ContentPanel @title="Upcoming Schedule" @open={{true}} @wrapperClass="bordered-top" @panelBodyWrapperClass="px-0i">
161
+ {{! Calendar header }}
162
+ <div class="flex items-center justify-between px-6 pb-2">
163
+ <button type="button" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-500 dark:text-gray-400" {{on "click" this.prevMonth}}>
164
+ <FaIcon @icon="chevron-left" @size="xs" />
165
+ </button>
166
+ <span class="text-xs font-semibold text-gray-700 dark:text-gray-200">{{this.calendarMonthName}}</span>
167
+ <button type="button" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-500 dark:text-gray-400" {{on "click" this.nextMonth}}>
168
+ <FaIcon @icon="chevron-right" @size="xs" />
169
+ </button>
170
+ </div>
171
+
172
+ {{! Day-of-week header row }}
173
+ <div class="grid grid-cols-7 text-center pb-1">
174
+ {{#each this.dayNames as |dayName|}}
175
+ <div class="text-xs font-medium text-gray-400 dark:text-gray-500 py-1">{{dayName}}</div>
176
+ {{/each}}
177
+ </div>
178
+
179
+ {{! Calendar grid }}
180
+ <div class="pb-4">
181
+ {{#each this.calendarWeeks as |week|}}
182
+ <div class="grid grid-cols-7 text-center">
183
+ {{#each week as |cell|}}
184
+ <div class="py-1">
185
+ <span
186
+ class="inline-flex items-center justify-center w-6 h-6 rounded-full text-xs
187
+ {{if cell.isScheduled 'bg-blue-500 text-white font-semibold'}}
188
+ {{if cell.isToday (if cell.isScheduled 'ring-2 ring-offset-1 ring-blue-400' 'ring-2 ring-blue-400 text-blue-600 dark:text-blue-400 font-semibold')}}
189
+ {{unless cell.isCurrentMonth 'text-gray-300 dark:text-gray-600'}}
190
+ "
191
+ >
192
+ {{cell.day}}
193
+ </span>
194
+ </div>
195
+ {{/each}}
196
+ </div>
197
+ {{/each}}
198
+ </div>
199
+
200
+ {{! Legend }}
201
+ <div class="flex items-center gap-4 px-3 py-2 border-t border-gray-100 dark:border-gray-700">
202
+ <div class="flex items-center gap-1.5">
203
+ <span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-blue-500 text-white text-xs">1</span>
204
+ <span class="text-xs text-gray-500 dark:text-gray-400">Scheduled service</span>
205
+ </div>
206
+ <div class="flex items-center gap-1.5">
207
+ <span class="inline-flex items-center justify-center w-5 h-5 rounded-full ring-2 ring-blue-400 text-xs text-blue-600">1</span>
208
+ <span class="text-xs text-gray-500 dark:text-gray-400">Today</span>
209
+ </div>
210
+ </div>
211
+
212
+ {{! Upcoming occurrences list }}
213
+ {{#if this.upcomingOccurrences.length}}
214
+ <div class="px-3 pb-3 pt-1 border-t border-gray-100 dark:border-gray-700">
215
+ <div class="py-2">
216
+ <p class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">Next occurrences</p>
217
+ </div>
218
+ <div class="space-y-1 pl-4">
219
+ {{#each this.upcomingOccurrences as |occurrence index|}}
220
+ <div class="flex items-center gap-2">
221
+ <span
222
+ class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 text-xs font-semibold flex-shrink-0"
223
+ >
224
+ {{add index 1}}
225
+ </span>
226
+ <span class="text-xs text-gray-700 dark:text-gray-200">
227
+ {{format-date-fns occurrence "EEEE, MMMM d, yyyy"}}
228
+ </span>
229
+ </div>
230
+ {{/each}}
231
+ </div>
232
+ </div>
233
+ {{/if}}
234
+ </ContentPanel>
235
+ {{/if}}
236
+
237
+ {{! ===== REMINDER OFFSETS ===== }}
238
+ {{#if @resource.reminder_offsets.length}}
239
+ <ContentPanel @title="Reminder Emails" @open={{true}} @wrapperClass="bordered-top">
240
+ <div class="text-xs text-gray-700 dark:text-gray-300">
241
+ <p class="mb-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold">
242
+ The default assignee will receive email reminders at:
243
+ </p>
244
+ <div class="flex flex-wrap gap-1">
245
+ {{#each @resource.reminder_offsets as |offset|}}
246
+ <span class="inline-flex items-center bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 text-xs px-2 py-0.5 rounded">
247
+ {{offset}}
248
+ days before
249
+ </span>
250
+ {{/each}}
251
+ </div>
252
+ </div>
253
+ </ContentPanel>
254
+ {{/if}}
255
+
256
+ {{! ===== CUSTOM FIELDS ===== }}
257
+ <CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
258
+
259
+ <Spacer @height="200px" />
260
+ </div>
@@ -0,0 +1,158 @@
1
+ import Component from '@glimmer/component';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { action } from '@ember/object';
4
+ import { inject as service } from '@ember/service';
5
+
6
+ /**
7
+ * Compute the next N occurrences of a time-based maintenance schedule
8
+ * starting from the given `nextDueDate`, stepping by `intervalValue` `intervalUnit`.
9
+ */
10
+ function computeOccurrences(nextDueDate, intervalValue, intervalUnit, count = 12) {
11
+ if (!nextDueDate || !intervalValue || !intervalUnit) return [];
12
+ const occurrences = [];
13
+ let cursor = new Date(nextDueDate);
14
+ for (let i = 0; i < count; i++) {
15
+ occurrences.push(new Date(cursor));
16
+ switch (intervalUnit) {
17
+ case 'days':
18
+ cursor.setDate(cursor.getDate() + intervalValue);
19
+ break;
20
+ case 'weeks':
21
+ cursor.setDate(cursor.getDate() + intervalValue * 7);
22
+ break;
23
+ case 'months':
24
+ cursor.setMonth(cursor.getMonth() + intervalValue);
25
+ break;
26
+ case 'years':
27
+ cursor.setFullYear(cursor.getFullYear() + intervalValue);
28
+ break;
29
+ default:
30
+ cursor.setDate(cursor.getDate() + intervalValue);
31
+ }
32
+ }
33
+ return occurrences;
34
+ }
35
+
36
+ /**
37
+ * Build a 6-week calendar grid for a given year/month.
38
+ * Each cell: { date, day, isCurrentMonth, isToday, isScheduled }
39
+ */
40
+ function buildCalendarGrid(year, month, scheduledDates) {
41
+ const scheduledSet = new Set(scheduledDates.map((d) => `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`));
42
+ const today = new Date();
43
+ const todayKey = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`;
44
+ const firstDay = new Date(year, month, 1);
45
+ const cursor = new Date(year, month, 1 - firstDay.getDay());
46
+ const weeks = [];
47
+ for (let w = 0; w < 6; w++) {
48
+ const week = [];
49
+ for (let d = 0; d < 7; d++) {
50
+ const date = new Date(cursor);
51
+ const key = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
52
+ week.push({ date, day: date.getDate(), isCurrentMonth: date.getMonth() === month, isToday: key === todayKey, isScheduled: scheduledSet.has(key) });
53
+ cursor.setDate(cursor.getDate() + 1);
54
+ }
55
+ weeks.push(week);
56
+ }
57
+ return weeks;
58
+ }
59
+
60
+ const MONTH_NAMES = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
61
+ const DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
62
+
63
+ export default class MaintenanceScheduleDetailsComponent extends Component {
64
+ @service fetch;
65
+ @tracked calendarYear;
66
+ @tracked calendarMonth;
67
+
68
+ constructor() {
69
+ super(...arguments);
70
+ const seed = this.args.resource?.next_due_date ? new Date(this.args.resource.next_due_date) : new Date();
71
+ this.calendarYear = seed.getFullYear();
72
+ this.calendarMonth = seed.getMonth();
73
+ }
74
+
75
+ get resource() {
76
+ return this.args.resource;
77
+ }
78
+
79
+ get occurrences() {
80
+ const r = this.resource;
81
+ if (!r) return [];
82
+ return computeOccurrences(r.next_due_date, r.interval_value, r.interval_unit, 12);
83
+ }
84
+
85
+ get calendarMonthName() {
86
+ return `${MONTH_NAMES[this.calendarMonth]} ${this.calendarYear}`;
87
+ }
88
+ get dayNames() {
89
+ return DAY_NAMES;
90
+ }
91
+
92
+ get calendarWeeks() {
93
+ return buildCalendarGrid(this.calendarYear, this.calendarMonth, this.occurrences);
94
+ }
95
+
96
+ get upcomingOccurrences() {
97
+ const now = new Date();
98
+ return this.occurrences.filter((d) => d >= now).slice(0, 6);
99
+ }
100
+
101
+ get isTimeBased() {
102
+ const m = this.resource?.interval_method;
103
+ return !m || m === 'time';
104
+ }
105
+
106
+ @action prevMonth() {
107
+ if (this.calendarMonth === 0) {
108
+ this.calendarMonth = 11;
109
+ this.calendarYear = this.calendarYear - 1;
110
+ } else {
111
+ this.calendarMonth = this.calendarMonth - 1;
112
+ }
113
+ }
114
+
115
+ @action nextMonth() {
116
+ if (this.calendarMonth === 11) {
117
+ this.calendarMonth = 0;
118
+ this.calendarYear = this.calendarYear + 1;
119
+ } else {
120
+ this.calendarMonth = this.calendarMonth + 1;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Trigger a browser download of the .ics file for this schedule.
126
+ * Uses fetch.download() which automatically attaches auth headers and
127
+ * reads the Content-Disposition / Content-Type from the response.
128
+ */
129
+ @action downloadIcal(schedule) {
130
+ const id = schedule.public_id ?? schedule.id;
131
+ this.fetch
132
+ .download(
133
+ `maintenance-schedules/${id}/ical`,
134
+ {},
135
+ {
136
+ fileName: `maintenance-schedule-${id}.ics`,
137
+ mimeType: 'text/calendar',
138
+ }
139
+ )
140
+ .catch((error) => {
141
+ // eslint-disable-next-line no-console
142
+ console.error('Failed to download iCal:', error);
143
+ });
144
+ }
145
+
146
+ /**
147
+ * Open the Google Calendar "add event" URL for this schedule's next due date.
148
+ */
149
+ @action addToGoogleCalendar(schedule) {
150
+ const title = encodeURIComponent(schedule.name ?? 'Maintenance Schedule');
151
+ const dueDate = schedule.next_due_date ? new Date(schedule.next_due_date) : new Date();
152
+ const pad = (n) => String(n).padStart(2, '0');
153
+ const dateStr = `${dueDate.getFullYear()}${pad(dueDate.getMonth() + 1)}${pad(dueDate.getDate())}`;
154
+ const details = encodeURIComponent(schedule.description ?? schedule.instructions ?? '');
155
+ const url = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${title}&dates=${dateStr}/${dateStr}&details=${details}`;
156
+ window.open(url, '_blank', 'noopener,noreferrer');
157
+ }
158
+ }