@fleetbase/fleetops-engine 0.6.50 → 0.6.52

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 (494) hide show
  1. package/README.md +183 -67
  2. package/addon/components/cell/telematic-provider.hbs +16 -0
  3. package/addon/components/cell/telematic-provider.js +36 -0
  4. package/addon/components/cell/telematic-status.hbs +1 -0
  5. package/addon/components/cell/telematic-status.js +39 -0
  6. package/addon/components/contact/panel-header.hbs +11 -48
  7. package/addon/components/customer/details.hbs +1 -2
  8. package/addon/components/customer/form.hbs +63 -51
  9. package/addon/components/customer/form.js +27 -1
  10. package/addon/components/customer/orders.hbs +48 -0
  11. package/addon/components/customer/orders.js +67 -5
  12. package/addon/components/device/manager.hbs +5 -6
  13. package/addon/components/device/manager.js +12 -15
  14. package/addon/components/driver/details.hbs +1 -1
  15. package/addon/components/driver/form.hbs +1 -1
  16. package/addon/components/fleet/vehicle-listing.js +1 -0
  17. package/addon/components/fuel-integration/form.hbs +200 -0
  18. package/addon/components/fuel-integration/form.js +242 -0
  19. package/addon/components/fuel-integration/hub.hbs +154 -0
  20. package/addon/components/fuel-integration/hub.js +165 -0
  21. package/addon/components/fuel-integration/matching-priority.hbs +76 -0
  22. package/addon/components/fuel-integration/matching-priority.js +145 -0
  23. package/addon/components/fuel-report/details.hbs +13 -1
  24. package/addon/components/home/getting-started-guidance.hbs +113 -0
  25. package/addon/components/home/getting-started-guidance.js +107 -0
  26. package/addon/components/issue/details/comments.hbs +3 -0
  27. package/addon/components/issue/details/comments.js +3 -0
  28. package/addon/components/issue/details/correspondence.hbs +9 -0
  29. package/addon/components/issue/details/correspondence.js +83 -0
  30. package/addon/components/issue/details/documents.hbs +69 -0
  31. package/addon/components/issue/details/documents.js +85 -0
  32. package/addon/components/issue/details.hbs +166 -59
  33. package/addon/components/issue/details.js +149 -0
  34. package/addon/components/issue/form.hbs +19 -1
  35. package/addon/components/issue/panel-header.hbs +43 -0
  36. package/addon/components/issue/panel-header.js +22 -0
  37. package/addon/components/issue/timeline.hbs +40 -0
  38. package/addon/components/issue/timeline.js +45 -0
  39. package/addon/components/layout/fleet-ops-sidebar.js +20 -2
  40. package/addon/components/leaflet-tracking-marker.js +3 -0
  41. package/addon/components/map/drawer/device-event-listing.hbs +12 -2
  42. package/addon/components/map/drawer/driver-listing.hbs +14 -1
  43. package/addon/components/map/drawer/place-listing.hbs +14 -1
  44. package/addon/components/map/drawer/position-listing.hbs +12 -2
  45. package/addon/components/map/drawer/position-listing.js +3 -0
  46. package/addon/components/map/drawer/vehicle-listing.hbs +14 -1
  47. package/addon/components/map/leaflet-live-map.hbs +2 -2
  48. package/addon/components/map/leaflet-live-map.js +17 -0
  49. package/addon/components/map/order-list-overlay.hbs +4 -4
  50. package/addon/components/modals/assigned-orders.hbs +40 -0
  51. package/addon/components/modals/attach-device.hbs +4 -3
  52. package/addon/components/modals/attach-telematic-device.hbs +25 -0
  53. package/addon/components/modals/attach-telematic-device.js +3 -0
  54. package/addon/components/modals/convert-customer-to-vendor.hbs +30 -0
  55. package/addon/components/modals/convert-customer-to-vendor.js +55 -0
  56. package/addon/components/modals/driver-assign-order.hbs +7 -58
  57. package/addon/components/modals/driver-assign-vehicle.hbs +10 -2
  58. package/addon/components/modals/fleet-assign-driver.hbs +18 -0
  59. package/addon/components/modals/fleet-assign-driver.js +3 -0
  60. package/addon/components/modals/fleet-assign-vehicle.hbs +18 -0
  61. package/addon/components/modals/fleet-assign-vehicle.js +3 -0
  62. package/addon/components/modals/issue-assign.hbs +20 -0
  63. package/addon/components/modals/issue-assign.js +49 -0
  64. package/addon/components/modals/issue-close.hbs +30 -0
  65. package/addon/components/modals/issue-close.js +73 -0
  66. package/addon/components/modals/issue-status.hbs +20 -0
  67. package/addon/components/modals/issue-status.js +43 -0
  68. package/addon/components/modals/reset-customer-credentials.js +12 -6
  69. package/addon/components/modals/telematic-connection-diagnostics.hbs +75 -0
  70. package/addon/components/modals/telematic-connection-diagnostics.js +287 -0
  71. package/addon/components/modals/update-order-activity.hbs +51 -17
  72. package/addon/components/modals/update-order-activity.js +59 -5
  73. package/addon/components/modals/vehicle-details.hbs +6 -1
  74. package/addon/components/modals/vehicle-form.hbs +8 -1
  75. package/addon/components/order/card.hbs +106 -0
  76. package/addon/components/order/card.js +257 -0
  77. package/addon/components/order/details/activity.js +2 -2
  78. package/addon/components/order/details/proof.hbs +40 -38
  79. package/addon/components/order/details/proof.js +31 -4
  80. package/addon/components/order/details.hbs +5 -5
  81. package/addon/components/order/details.js +19 -1
  82. package/addon/components/order/form/service-rate.hbs +2 -0
  83. package/addon/components/order/header.hbs +1 -1
  84. package/addon/components/route-list.hbs +0 -12
  85. package/addon/components/service-rate/details.hbs +1 -1
  86. package/addon/components/service-rate/form.hbs +4 -3
  87. package/addon/components/service-rate/form.js +4 -0
  88. package/addon/components/telematic/details.hbs +98 -87
  89. package/addon/components/telematic/details.js +230 -1
  90. package/addon/components/telematic/form.hbs +303 -72
  91. package/addon/components/telematic/form.js +519 -3
  92. package/addon/components/telematic/hub.hbs +149 -0
  93. package/addon/components/telematic/hub.js +142 -0
  94. package/addon/components/telematic/settings.hbs +152 -0
  95. package/addon/components/telematic/settings.js +45 -0
  96. package/addon/components/telematic/tab-empty-state.hbs +29 -0
  97. package/addon/components/telematic/tab-empty-state.js +33 -0
  98. package/addon/components/vehicle/details.hbs +6 -1
  99. package/addon/components/vehicle/form.hbs +2 -1
  100. package/addon/components/vendor/personnel-manager.hbs +166 -0
  101. package/addon/components/vendor/personnel-manager.js +143 -0
  102. package/addon/components/widget/fuel-providers.hbs +43 -0
  103. package/addon/components/widget/fuel-providers.js +27 -0
  104. package/addon/components/widget/live-fleet.hbs +1 -0
  105. package/addon/components/widget/live-fleet.js +2 -7
  106. package/addon/components/widget/revenue-trend.hbs +1 -1
  107. package/addon/components/widget/revenue-trend.js +75 -2
  108. package/addon/controllers/connectivity/devices/index/details.js +27 -1
  109. package/addon/controllers/connectivity/devices/index.js +14 -0
  110. package/addon/controllers/connectivity/fuel-providers/details/sync.js +18 -0
  111. package/addon/controllers/connectivity/fuel-providers/details/transactions.js +24 -0
  112. package/addon/controllers/connectivity/fuel-providers/details.js +69 -0
  113. package/addon/controllers/connectivity/fuel-providers/edit.js +24 -0
  114. package/addon/controllers/connectivity/fuel-providers/index.js +177 -0
  115. package/addon/controllers/connectivity/fuel-providers/new.js +37 -0
  116. package/addon/controllers/connectivity/telematics/details/attachments.js +247 -0
  117. package/addon/controllers/connectivity/telematics/details/devices.js +368 -0
  118. package/addon/controllers/connectivity/telematics/details/events.js +244 -0
  119. package/addon/controllers/connectivity/telematics/details/index.js +3 -0
  120. package/addon/controllers/connectivity/telematics/details/logs.js +43 -0
  121. package/addon/controllers/connectivity/telematics/details/sensors.js +270 -0
  122. package/addon/controllers/connectivity/telematics/details.js +149 -0
  123. package/addon/controllers/connectivity/telematics/{index/edit.js → edit.js} +15 -4
  124. package/addon/controllers/connectivity/telematics/index.js +25 -5
  125. package/addon/controllers/connectivity/telematics/{index/new.js → new.js} +8 -2
  126. package/addon/controllers/management/contacts/customers/details.js +31 -13
  127. package/addon/controllers/management/contacts/customers.js +16 -5
  128. package/addon/controllers/management/contacts/index/details.js +31 -13
  129. package/addon/controllers/management/contacts/index.js +7 -0
  130. package/addon/controllers/management/drivers/index/details.js +78 -1
  131. package/addon/controllers/management/drivers/index.js +39 -0
  132. package/addon/controllers/management/fleets/index/details.js +23 -1
  133. package/addon/controllers/management/fleets/index.js +6 -1
  134. package/addon/controllers/management/fuel-reports/index.js +37 -1
  135. package/addon/controllers/management/fuel-transactions/index/details.js +70 -0
  136. package/addon/controllers/management/fuel-transactions/index.js +240 -0
  137. package/addon/controllers/management/issues/index/details.js +52 -6
  138. package/addon/controllers/management/vehicles/index/details.js +32 -0
  139. package/addon/controllers/management/vehicles/index.js +26 -0
  140. package/addon/controllers/management/vendors/index/details.js +4 -0
  141. package/addon/controllers/operations/orders/index/details.js +11 -3
  142. package/addon/controllers/operations/orders/index.js +5 -1
  143. package/addon/controllers/settings/payments/index.js +5 -0
  144. package/addon/extension.js +29 -12
  145. package/addon/helpers/format-duration.js +1 -38
  146. package/addon/initializers/load-leaflet-assets.js +5 -6
  147. package/addon/initializers/patch-ember-leaflet-tooltip-layer.js +51 -0
  148. package/addon/routes/connectivity/fuel-providers/details/index.js +7 -0
  149. package/addon/routes/connectivity/fuel-providers/details/matching.js +7 -0
  150. package/addon/routes/connectivity/fuel-providers/details/settings.js +7 -0
  151. package/addon/routes/connectivity/fuel-providers/details/sync.js +18 -0
  152. package/addon/routes/connectivity/fuel-providers/details/transactions.js +14 -0
  153. package/addon/routes/connectivity/fuel-providers/details.js +18 -0
  154. package/addon/routes/connectivity/fuel-providers/edit.js +18 -0
  155. package/addon/routes/connectivity/fuel-providers/index.js +20 -0
  156. package/addon/routes/connectivity/fuel-providers/new.js +3 -0
  157. package/addon/routes/connectivity/fuel-providers.js +3 -0
  158. package/addon/routes/connectivity/telematics/details/attachments.js +28 -0
  159. package/addon/routes/connectivity/telematics/details/devices.js +30 -0
  160. package/addon/routes/connectivity/telematics/details/events.js +33 -0
  161. package/addon/routes/connectivity/telematics/details/index.js +3 -0
  162. package/addon/routes/connectivity/telematics/details/logs.js +17 -0
  163. package/addon/routes/connectivity/telematics/details/sensors.js +29 -0
  164. package/addon/routes/connectivity/telematics/{index/details.js → details.js} +1 -1
  165. package/addon/routes/connectivity/telematics/{index/edit.js → edit.js} +1 -1
  166. package/addon/routes/connectivity/telematics/new.js +3 -0
  167. package/addon/routes/management/drivers/index/details/activity.js +3 -0
  168. package/addon/routes/management/fuel-reports/index.js +2 -0
  169. package/addon/routes/management/fuel-transactions/index/details.js +18 -0
  170. package/addon/routes/management/fuel-transactions/index.js +21 -0
  171. package/addon/routes/management/fuel-transactions.js +3 -0
  172. package/addon/routes/management/issues/index/details.js +1 -1
  173. package/addon/routes.js +29 -9
  174. package/addon/services/contact-actions.js +231 -1
  175. package/addon/services/device-actions.js +64 -1
  176. package/addon/services/device-event-actions.js +12 -1
  177. package/addon/services/driver-actions.js +147 -11
  178. package/addon/services/fleet-actions.js +66 -1
  179. package/addon/services/fuel-integration-actions.js +31 -0
  180. package/addon/services/getting-started.js +44 -0
  181. package/addon/services/issue-actions.js +72 -1
  182. package/addon/services/leaflet-map-manager.js +2 -0
  183. package/addon/services/map-adapter/leaflet.js +19 -0
  184. package/addon/services/service-areas.js +3 -0
  185. package/addon/services/telematic-actions.js +9 -3
  186. package/addon/services/vehicle-actions.js +125 -0
  187. package/addon/styles/fleetops-engine.css +1313 -1
  188. package/addon/templates/analytics/reports/index.hbs +19 -1
  189. package/addon/templates/connectivity/devices/index.hbs +19 -1
  190. package/addon/templates/connectivity/events/index.hbs +16 -1
  191. package/addon/templates/connectivity/fuel-providers/details/index.hbs +38 -0
  192. package/addon/templates/connectivity/fuel-providers/details/matching.hbs +13 -0
  193. package/addon/templates/connectivity/fuel-providers/details/settings.hbs +11 -0
  194. package/addon/templates/connectivity/fuel-providers/details/sync.hbs +48 -0
  195. package/addon/templates/connectivity/fuel-providers/details/transactions.hbs +21 -0
  196. package/addon/templates/connectivity/fuel-providers/details.hbs +51 -0
  197. package/addon/templates/connectivity/fuel-providers/edit.hbs +24 -0
  198. package/addon/templates/connectivity/fuel-providers/index.hbs +12 -0
  199. package/addon/templates/connectivity/fuel-providers/new.hbs +22 -0
  200. package/addon/templates/connectivity/fuel-providers.hbs +1 -0
  201. package/addon/templates/connectivity/sensors/index.hbs +19 -1
  202. package/addon/templates/connectivity/telematics/details/attachments.hbs +163 -0
  203. package/addon/templates/connectivity/telematics/details/devices.hbs +88 -0
  204. package/addon/templates/connectivity/telematics/details/events.hbs +101 -0
  205. package/addon/templates/connectivity/telematics/{index/details → details}/index.hbs +0 -1
  206. package/addon/templates/connectivity/telematics/details/logs.hbs +69 -0
  207. package/addon/templates/connectivity/telematics/details/sensors.hbs +88 -0
  208. package/addon/templates/connectivity/telematics/details.hbs +53 -0
  209. package/addon/templates/connectivity/telematics/edit.hbs +27 -0
  210. package/addon/templates/connectivity/telematics/index.hbs +6 -8
  211. package/addon/templates/connectivity/telematics/new.hbs +24 -0
  212. package/addon/templates/maintenance/equipment/index.hbs +19 -1
  213. package/addon/templates/maintenance/maintenances/index.hbs +15 -1
  214. package/addon/templates/maintenance/parts/index.hbs +19 -1
  215. package/addon/templates/maintenance/schedules/index.hbs +19 -1
  216. package/addon/templates/maintenance/work-orders/index.hbs +19 -1
  217. package/addon/templates/management/contacts/customers/details.hbs +1 -1
  218. package/addon/templates/management/contacts/customers.hbs +15 -1
  219. package/addon/templates/management/contacts/index/details.hbs +1 -1
  220. package/addon/templates/management/contacts/index.hbs +19 -1
  221. package/addon/templates/management/contacts.hbs +2 -2
  222. package/addon/templates/management/drivers/index/details/activity.hbs +1 -0
  223. package/addon/templates/management/drivers/index.hbs +19 -1
  224. package/addon/templates/management/fleets/index.hbs +19 -1
  225. package/addon/templates/management/fuel-reports/index.hbs +19 -1
  226. package/addon/templates/management/fuel-transactions/index/details.hbs +73 -0
  227. package/addon/templates/management/fuel-transactions/index.hbs +58 -0
  228. package/addon/templates/management/fuel-transactions.hbs +1 -0
  229. package/addon/templates/management/issues/index/details/index.hbs +3 -1
  230. package/addon/templates/management/issues/index/details.hbs +3 -2
  231. package/addon/templates/management/issues/index.hbs +19 -1
  232. package/addon/templates/management/places/index.hbs +19 -1
  233. package/addon/templates/management/vehicles/index.hbs +19 -1
  234. package/addon/templates/management/vendors/index/details/personnel.hbs +1 -2
  235. package/addon/templates/management/vendors/index.hbs +19 -1
  236. package/addon/templates/management/vendors/integrated.hbs +13 -1
  237. package/addon/templates/operations/orders/index/details/index.hbs +2 -2
  238. package/addon/templates/operations/orders/index.hbs +20 -2
  239. package/addon/templates/operations/service-rates/index/edit.hbs +1 -0
  240. package/addon/templates/operations/service-rates/index/new.hbs +1 -0
  241. package/addon/templates/operations/service-rates/index.hbs +19 -1
  242. package/addon/templates/settings/payments/index.hbs +15 -1
  243. package/addon/templates/settings/payments/onboard.hbs +8 -8
  244. package/addon/utils/fleet-ops-options.js +3 -0
  245. package/addon/utils/leaflet-draw-namespace-guard.js +22 -0
  246. package/addon/utils/leaflet-plugin-loader.js +219 -0
  247. package/addon/utils/setup-customer-portal.js +4 -64
  248. package/app/components/cell/telematic-provider.js +1 -0
  249. package/app/components/cell/telematic-status.js +1 -0
  250. package/app/components/fuel-integration/form.js +1 -0
  251. package/app/components/fuel-integration/hub.js +1 -0
  252. package/app/components/fuel-integration/matching-priority.js +1 -0
  253. package/app/components/home/getting-started-guidance.js +1 -0
  254. package/app/components/issue/details/comments.js +1 -0
  255. package/app/components/issue/details/correspondence.js +1 -0
  256. package/app/components/issue/details/documents.js +1 -0
  257. package/app/components/issue/panel-header.js +1 -0
  258. package/app/components/modals/assigned-orders.js +1 -0
  259. package/app/components/modals/attach-telematic-device.js +1 -0
  260. package/app/components/modals/convert-customer-to-vendor.js +1 -0
  261. package/app/components/modals/fleet-assign-driver.js +1 -0
  262. package/app/components/modals/fleet-assign-vehicle.js +1 -0
  263. package/app/components/modals/issue-assign.js +1 -0
  264. package/app/components/modals/issue-close.js +1 -0
  265. package/app/components/modals/issue-status.js +1 -0
  266. package/app/components/modals/telematic-connection-diagnostics.js +1 -0
  267. package/app/components/order/card.js +1 -0
  268. package/app/components/telematic/hub.js +1 -0
  269. package/app/components/telematic/settings.js +1 -0
  270. package/app/components/telematic/tab-empty-state.js +1 -0
  271. package/app/components/vendor/personnel-manager.js +1 -0
  272. package/app/components/widget/fuel-providers.js +1 -0
  273. package/app/controllers/connectivity/fuel-providers/details/sync.js +1 -0
  274. package/app/controllers/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  275. package/app/controllers/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  276. package/app/controllers/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  277. package/app/controllers/connectivity/{telematics/index/details → fuel-providers}/index.js +1 -1
  278. package/app/controllers/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  279. package/app/controllers/connectivity/telematics/details/attachments.js +1 -0
  280. package/app/controllers/connectivity/telematics/details/devices.js +1 -0
  281. package/app/controllers/connectivity/telematics/details/events.js +1 -0
  282. package/app/controllers/connectivity/telematics/details/index.js +1 -0
  283. package/app/controllers/connectivity/telematics/details/logs.js +1 -0
  284. package/app/controllers/connectivity/telematics/details/sensors.js +1 -0
  285. package/app/controllers/connectivity/telematics/details.js +1 -0
  286. package/app/controllers/connectivity/telematics/edit.js +1 -0
  287. package/app/controllers/connectivity/telematics/new.js +1 -0
  288. package/app/controllers/management/fuel-transactions/index/details.js +1 -0
  289. package/app/controllers/management/fuel-transactions/index.js +1 -0
  290. package/app/helpers/format-duration.js +1 -1
  291. package/app/routes/connectivity/{telematics/index → fuel-providers}/details/index.js +1 -1
  292. package/app/routes/connectivity/{telematics/index/details/events.js → fuel-providers/details/matching.js} +1 -1
  293. package/app/routes/connectivity/fuel-providers/details/settings.js +1 -0
  294. package/app/routes/connectivity/fuel-providers/details/sync.js +1 -0
  295. package/app/routes/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  296. package/app/routes/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  297. package/app/routes/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  298. package/app/routes/connectivity/fuel-providers/index.js +1 -0
  299. package/app/routes/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  300. package/app/routes/connectivity/fuel-providers.js +1 -0
  301. package/app/routes/connectivity/telematics/details/attachments.js +1 -0
  302. package/app/routes/connectivity/telematics/details/devices.js +1 -0
  303. package/app/routes/connectivity/telematics/details/events.js +1 -0
  304. package/app/routes/connectivity/telematics/details/index.js +1 -0
  305. package/app/routes/connectivity/telematics/details/logs.js +1 -0
  306. package/app/routes/connectivity/telematics/details/sensors.js +1 -0
  307. package/app/routes/connectivity/telematics/details.js +1 -0
  308. package/app/routes/connectivity/telematics/edit.js +1 -0
  309. package/app/routes/connectivity/telematics/new.js +1 -0
  310. package/app/routes/management/drivers/index/details/activity.js +1 -0
  311. package/app/routes/management/fuel-transactions/index/details.js +1 -0
  312. package/app/routes/management/fuel-transactions/index.js +1 -0
  313. package/app/routes/management/fuel-transactions.js +1 -0
  314. package/app/services/fuel-integration-actions.js +1 -0
  315. package/app/services/getting-started.js +1 -0
  316. package/app/templates/connectivity/{telematics/index → fuel-providers}/details/index.js +1 -1
  317. package/app/templates/connectivity/{telematics/index/details/events.js → fuel-providers/details/matching.js} +1 -1
  318. package/app/templates/connectivity/fuel-providers/details/settings.js +1 -0
  319. package/app/templates/connectivity/fuel-providers/details/sync.js +1 -0
  320. package/app/templates/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  321. package/app/templates/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  322. package/app/templates/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  323. package/app/templates/connectivity/fuel-providers/index.js +1 -0
  324. package/app/templates/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  325. package/app/templates/connectivity/fuel-providers.js +1 -0
  326. package/app/templates/connectivity/telematics/details/attachments.js +1 -0
  327. package/app/templates/connectivity/telematics/details/devices.js +1 -0
  328. package/app/templates/connectivity/telematics/details/events.js +1 -0
  329. package/app/templates/connectivity/telematics/details/index.js +1 -0
  330. package/app/templates/connectivity/telematics/details/logs.js +1 -0
  331. package/app/templates/connectivity/telematics/details/sensors.js +1 -0
  332. package/app/templates/connectivity/telematics/details.js +1 -0
  333. package/app/templates/connectivity/telematics/edit.js +1 -0
  334. package/app/templates/connectivity/telematics/new.js +1 -0
  335. package/app/templates/management/drivers/index/details/activity.js +1 -0
  336. package/app/templates/management/fuel-transactions/index/details.js +1 -0
  337. package/app/templates/management/fuel-transactions/index.js +1 -0
  338. package/app/templates/management/fuel-transactions.js +1 -0
  339. package/app/utils/leaflet-draw-namespace-guard.js +1 -0
  340. package/app/utils/leaflet-plugin-loader.js +1 -0
  341. package/assets/images/telematics/providers/afaqy.webp +0 -0
  342. package/assets/images/telematics/providers/default.webp +0 -0
  343. package/assets/images/telematics/providers/flespi.webp +0 -0
  344. package/assets/images/telematics/providers/geotab.webp +0 -0
  345. package/assets/images/telematics/providers/safee.webp +0 -0
  346. package/assets/images/telematics/providers/samsara.webp +0 -0
  347. package/composer.json +1 -1
  348. package/extension.json +1 -1
  349. package/package.json +5 -5
  350. package/server/config/fuel-providers.php +42 -0
  351. package/server/config/telematics.php +139 -130
  352. package/server/migrations/2026_05_31_000001_add_portal_fields_to_vendor_personnels_table.php +30 -0
  353. package/server/migrations/2026_06_03_000000_ensure_tags_column_on_issues_table.php +31 -0
  354. package/server/migrations/2026_06_03_000001_add_order_uuid_to_issues_table.php +25 -0
  355. package/server/migrations/2026_06_04_000001_backfill_order_transaction_subjects.php +39 -0
  356. package/server/migrations/2026_06_04_000002_backfill_order_transaction_directions.php +24 -0
  357. package/server/migrations/2026_06_06_000001_harden_device_events_telematics_contract.php +59 -0
  358. package/server/migrations/2026_06_08_000001_create_fuel_provider_connections_table.php +37 -0
  359. package/server/migrations/2026_06_08_000002_create_fuel_provider_transactions_table.php +53 -0
  360. package/server/migrations/2026_06_08_000003_create_fuel_provider_sync_runs_table.php +40 -0
  361. package/server/migrations/2026_06_10_000001_add_fuel_card_number_to_vehicles_table.php +25 -0
  362. package/server/migrations/2026_06_10_000002_add_vehicle_identity_columns_to_fuel_provider_transactions_table.php +39 -0
  363. package/server/resources/views/mail/customer-credentials.blade.php +16 -13
  364. package/server/src/Contracts/FuelProvider.php +30 -0
  365. package/server/src/Contracts/TelematicProviderDescriptor.php +3 -1
  366. package/server/src/Events/FuelProviderTransactionImported.php +17 -0
  367. package/server/src/Events/FuelProviderTransactionMatched.php +17 -0
  368. package/server/src/Events/FuelProviderTransactionUnmatched.php +17 -0
  369. package/server/src/Events/FuelReportCreatedFromProvider.php +18 -0
  370. package/server/src/Expansions/UserFilterExpansion.php +28 -0
  371. package/server/src/Http/Controllers/Api/v1/IssueController.php +2 -0
  372. package/server/src/Http/Controllers/Api/v1/OrderController.php +26 -25
  373. package/server/src/Http/Controllers/Api/v1/PlaceController.php +10 -58
  374. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +6 -0
  375. package/server/src/Http/Controllers/Internal/v1/ContactController.php +135 -0
  376. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +116 -6
  377. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +76 -2
  378. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +44 -0
  379. package/server/src/Http/Controllers/Internal/v1/DriverController.php +158 -7
  380. package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +124 -0
  381. package/server/src/Http/Controllers/Internal/v1/FuelProviderSyncRunController.php +10 -0
  382. package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +76 -0
  383. package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +17 -0
  384. package/server/src/Http/Controllers/Internal/v1/IssueController.php +251 -0
  385. package/server/src/Http/Controllers/Internal/v1/OrderController.php +172 -97
  386. package/server/src/Http/Controllers/Internal/v1/PlaceController.php +10 -68
  387. package/server/src/Http/Controllers/Internal/v1/SensorController.php +1 -1
  388. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +178 -15
  389. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +165 -0
  390. package/server/src/Http/Controllers/Internal/v1/VendorController.php +89 -0
  391. package/server/src/Http/Controllers/TelematicWebhookController.php +103 -19
  392. package/server/src/Http/Filter/FuelProviderConnectionFilter.php +33 -0
  393. package/server/src/Http/Filter/FuelProviderSyncRunFilter.php +28 -0
  394. package/server/src/Http/Filter/FuelProviderTransactionFilter.php +59 -0
  395. package/server/src/Http/Filter/FuelReportFilter.php +10 -0
  396. package/server/src/Http/Requests/CreateIssueRequest.php +4 -0
  397. package/server/src/Http/Requests/UpdateIssueRequest.php +4 -0
  398. package/server/src/Http/Resources/v1/Driver.php +15 -0
  399. package/server/src/Http/Resources/v1/FuelProviderConnection.php +30 -0
  400. package/server/src/Http/Resources/v1/FuelProviderSyncRun.php +36 -0
  401. package/server/src/Http/Resources/v1/FuelProviderTransaction.php +54 -0
  402. package/server/src/Http/Resources/v1/FuelReport.php +28 -22
  403. package/server/src/Http/Resources/v1/Index/Driver.php +26 -20
  404. package/server/src/Http/Resources/v1/Index/Order.php +12 -9
  405. package/server/src/Http/Resources/v1/Index/Vehicle.php +29 -22
  406. package/server/src/Http/Resources/v1/Issue.php +25 -0
  407. package/server/src/Http/Resources/v1/Order.php +20 -18
  408. package/server/src/Http/Resources/v1/Vehicle.php +17 -0
  409. package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +2 -0
  410. package/server/src/Jobs/SyncFuelProviderTransactionsJob.php +57 -0
  411. package/server/src/Jobs/SyncTelematicDevicesJob.php +36 -6
  412. package/server/src/Jobs/TestTelematicConnectionJob.php +9 -22
  413. package/server/src/Mail/CustomerCredentialsMail.php +5 -6
  414. package/server/src/Models/Asset.php +2 -1
  415. package/server/src/Models/Contact.php +32 -52
  416. package/server/src/Models/Device.php +27 -4
  417. package/server/src/Models/DeviceEvent.php +40 -3
  418. package/server/src/Models/Driver.php +2 -1
  419. package/server/src/Models/Equipment.php +2 -1
  420. package/server/src/Models/Fleet.php +2 -1
  421. package/server/src/Models/FuelProviderConnection.php +60 -0
  422. package/server/src/Models/FuelProviderSyncRun.php +58 -0
  423. package/server/src/Models/FuelProviderTransaction.php +119 -0
  424. package/server/src/Models/FuelReport.php +16 -1
  425. package/server/src/Models/Issue.php +47 -6
  426. package/server/src/Models/Order.php +33 -6
  427. package/server/src/Models/Part.php +2 -1
  428. package/server/src/Models/Sensor.php +21 -4
  429. package/server/src/Models/ServiceArea.php +1 -1
  430. package/server/src/Models/ServiceRate.php +39 -2
  431. package/server/src/Models/Vehicle.php +14 -2
  432. package/server/src/Models/Vendor.php +2 -1
  433. package/server/src/Models/VendorPersonnel.php +10 -1
  434. package/server/src/Models/Zone.php +1 -1
  435. package/server/src/Observers/PurchaseRateObserver.php +20 -0
  436. package/server/src/Observers/ZoneObserver.php +56 -0
  437. package/server/src/Providers/FleetOpsServiceProvider.php +11 -0
  438. package/server/src/Support/Analytics/FuelProviderSummary.php +42 -0
  439. package/server/src/Support/Analytics/TopDrivers.php +9 -1
  440. package/server/src/Support/FuelProviders/FuelProviderDescriptor.php +55 -0
  441. package/server/src/Support/FuelProviders/FuelProviderRegistry.php +75 -0
  442. package/server/src/Support/FuelProviders/FuelProviderService.php +447 -0
  443. package/server/src/Support/FuelProviders/Providers/AbstractFuelProvider.php +98 -0
  444. package/server/src/Support/FuelProviders/Providers/PetroAppFuelProvider.php +167 -0
  445. package/server/src/Support/GettingStarted.php +144 -0
  446. package/server/src/Support/PlaceSearch.php +282 -0
  447. package/server/src/Support/ResolvesOrderServiceStops.php +22 -12
  448. package/server/src/Support/Telematics/Providers/AbstractProvider.php +27 -6
  449. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +55 -26
  450. package/server/src/Support/Telematics/Providers/FlespiProvider.php +1 -0
  451. package/server/src/Support/Telematics/Providers/GeotabProvider.php +1 -0
  452. package/server/src/Support/Telematics/Providers/SafeeProvider.php +32 -22
  453. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +3 -1
  454. package/server/src/Support/Telematics/TelematicService.php +340 -21
  455. package/server/src/routes.php +74 -5
  456. package/server/tests/AnalyticsRoutesTest.php +8 -0
  457. package/server/tests/CustomerContactIdentitySafetyTest.php +41 -0
  458. package/server/tests/DriverVehicleStatusDefaultTest.php +2 -2
  459. package/server/tests/FuelProviderRegistryTest.php +416 -0
  460. package/server/tests/GettingStartedStatusTest.php +21 -0
  461. package/server/tests/OrderActivityFlowRegressionTest.php +116 -0
  462. package/server/tests/ServiceAreaZoneApiShapeTest.php +44 -0
  463. package/server/tests/ServiceRateAlgorithmWeightVariablesTest.php +44 -0
  464. package/server/tests/TelematicsHardeningTest.php +268 -0
  465. package/server/tests/VehicleDriverAssignmentTest.php +51 -0
  466. package/translations/ar-ae.yml +31 -0
  467. package/translations/bg-bg.yaml +81 -0
  468. package/translations/en-us.yaml +94 -0
  469. package/translations/es-pa.yaml +81 -0
  470. package/translations/fr-fr.yaml +81 -0
  471. package/translations/mn-mn.yaml +81 -0
  472. package/translations/pt-br.yaml +81 -0
  473. package/translations/ru-ru.yaml +81 -0
  474. package/translations/vi-vn.yaml +106 -0
  475. package/addon/controllers/connectivity/telematics/index/details/devices.js +0 -3
  476. package/addon/controllers/connectivity/telematics/index/details/events.js +0 -3
  477. package/addon/controllers/connectivity/telematics/index/details/index.js +0 -3
  478. package/addon/controllers/connectivity/telematics/index/details/sensors.js +0 -3
  479. package/addon/controllers/connectivity/telematics/index/details.js +0 -24
  480. package/addon/routes/connectivity/telematics/index/details/devices.js +0 -3
  481. package/addon/routes/connectivity/telematics/index/details/events.js +0 -3
  482. package/addon/routes/connectivity/telematics/index/details/index.js +0 -3
  483. package/addon/routes/connectivity/telematics/index/details/sensors.js +0 -3
  484. package/addon/routes/connectivity/telematics/index/new.js +0 -3
  485. package/addon/templates/connectivity/telematics/index/details/devices.hbs +0 -1
  486. package/addon/templates/connectivity/telematics/index/details/events.hbs +0 -1
  487. package/addon/templates/connectivity/telematics/index/details/sensors.hbs +0 -1
  488. package/addon/templates/connectivity/telematics/index/details.hbs +0 -14
  489. package/addon/templates/connectivity/telematics/index/edit.hbs +0 -12
  490. package/addon/templates/connectivity/telematics/index/new.hbs +0 -12
  491. package/app/controllers/connectivity/telematics/index/details/events.js +0 -1
  492. package/app/controllers/connectivity/telematics/index/details/sensors.js +0 -1
  493. package/app/routes/connectivity/telematics/index/details/sensors.js +0 -1
  494. package/app/templates/connectivity/telematics/index/details/sensors.js +0 -1
@@ -6,9 +6,15 @@ use Fleetbase\FleetOps\Exports\IssueExport;
6
6
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
7
7
  use Fleetbase\FleetOps\Imports\IssueImport;
8
8
  use Fleetbase\FleetOps\Models\Issue;
9
+ use Fleetbase\FleetOps\Models\Order;
9
10
  use Fleetbase\Http\Requests\ExportRequest;
10
11
  use Fleetbase\Http\Requests\ImportRequest;
12
+ use Fleetbase\Models\Activity;
13
+ use Fleetbase\Models\Comment;
14
+ use Fleetbase\Models\File;
15
+ use Fleetbase\Models\User;
11
16
  use Illuminate\Http\Request;
17
+ use Illuminate\Support\Collection;
12
18
  use Illuminate\Support\Str;
13
19
  use Maatwebsite\Excel\Facades\Excel;
14
20
 
@@ -26,10 +32,255 @@ class IssueController extends FleetOpsController
26
32
  */
27
33
  public function afterSave(Request $request, Issue $issue)
28
34
  {
35
+ if ($issue->assigned_to_uuid) {
36
+ $assignee = User::where('uuid', $issue->assigned_to_uuid)->first();
37
+
38
+ if ($assignee && $assignee->type === 'customer') {
39
+ $issue->assigned_to_uuid = null;
40
+ $issue->saveQuietly();
41
+ }
42
+ }
43
+
29
44
  $customFieldValues = $request->array('issue.custom_field_values');
30
45
  if ($customFieldValues) {
31
46
  $issue->syncCustomFieldValues($customFieldValues);
32
47
  }
48
+
49
+ $uploads = $request->array('issue.files');
50
+ if ($uploads) {
51
+ File::whereIn('uuid', $uploads)->get()->each(function (File $file) use ($issue) {
52
+ $file->setKey($issue);
53
+ });
54
+ }
55
+
56
+ if (!$issue->order_uuid && data_get($issue, 'meta.order_uuid')) {
57
+ $order = Order::where('uuid', data_get($issue, 'meta.order_uuid'))->where('company_uuid', $issue->company_uuid)->first();
58
+ if ($order) {
59
+ $issue->order_uuid = $order->uuid;
60
+ $issue->saveQuietly();
61
+ }
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Return curated activity timeline entries for an issue.
67
+ *
68
+ * @return \Illuminate\Http\JsonResponse
69
+ */
70
+ public function timeline($id)
71
+ {
72
+ $issue = Issue::findById($id, ['reporter', 'assignee']);
73
+
74
+ if (!$issue || $issue->company_uuid !== session('company')) {
75
+ return response()->error('Issue not found for this organization.', 404);
76
+ }
77
+
78
+ $events = collect([$this->makeIssueOpenedEvent($issue)])
79
+ ->merge($this->issueActivityEvents($issue))
80
+ ->merge($this->commentEvents($issue))
81
+ ->merge($this->fileEvents($issue))
82
+ ->sortByDesc('created_at')
83
+ ->values();
84
+
85
+ return response()->json(['events' => $events]);
86
+ }
87
+
88
+ protected function issueActivityEvents(Issue $issue): Collection
89
+ {
90
+ return Activity::with(['causer'])
91
+ ->where('subject_type', Issue::class)
92
+ ->where('subject_id', $issue->uuid)
93
+ ->latest()
94
+ ->get()
95
+ ->flatMap(function (Activity $activity) use ($issue) {
96
+ if ($activity->event === 'created') {
97
+ return [];
98
+ }
99
+
100
+ $events = [];
101
+ foreach (['status', 'priority', 'assigned_to_uuid', 'vehicle_uuid', 'driver_uuid', 'order_uuid', 'type', 'category', 'tags', 'resolved_at'] as $field) {
102
+ $from = data_get($activity->properties, "old.{$field}");
103
+ $to = data_get($activity->properties, "attributes.{$field}");
104
+
105
+ if ($from === $to || $to === null) {
106
+ continue;
107
+ }
108
+
109
+ $events[] = $this->makeFieldChangedEvent($activity, $issue, $field, $from, $to);
110
+ }
111
+
112
+ return $events ?: [$this->makeGenericActivityEvent($activity, $issue)];
113
+ });
114
+ }
115
+
116
+ protected function commentEvents(Issue $issue): Collection
117
+ {
118
+ return Comment::with(['author'])
119
+ ->where('subject_type', Issue::class)
120
+ ->where('subject_uuid', $issue->uuid)
121
+ ->latest()
122
+ ->get()
123
+ ->map(function (Comment $comment) {
124
+ return [
125
+ 'id' => $comment->uuid,
126
+ 'type' => 'correspondence_added',
127
+ 'label' => 'Correspondence added',
128
+ 'description' => Str::limit(strip_tags($comment->content ?? ''), 120),
129
+ 'actor_name' => data_get($comment, 'author.name', 'Someone'),
130
+ 'actor_avatar_url' => data_get($comment, 'author.avatar_url'),
131
+ 'created_at' => $comment->created_at,
132
+ 'icon' => 'comment',
133
+ 'tone' => 'blue',
134
+ 'meta' => [
135
+ 'comment_id' => $comment->public_id ?? $comment->uuid,
136
+ ],
137
+ ];
138
+ });
139
+ }
140
+
141
+ protected function fileEvents(Issue $issue): Collection
142
+ {
143
+ $currentFiles = File::with(['uploader'])
144
+ ->where('subject_type', Issue::class)
145
+ ->where('subject_uuid', $issue->uuid)
146
+ ->latest()
147
+ ->get()
148
+ ->map(function (File $file) {
149
+ return [
150
+ 'id' => $file->uuid,
151
+ 'type' => 'document_uploaded',
152
+ 'label' => 'Document uploaded',
153
+ 'description' => $file->original_filename,
154
+ 'actor_name' => data_get($file, 'uploader.name', 'Someone'),
155
+ 'actor_avatar_url' => data_get($file, 'uploader.avatar_url'),
156
+ 'created_at' => $file->created_at,
157
+ 'icon' => 'file-arrow-up',
158
+ 'tone' => 'purple',
159
+ 'meta' => [
160
+ 'file_id' => $file->public_id ?? $file->uuid,
161
+ 'file_name' => $file->original_filename,
162
+ 'file_url' => $file->url,
163
+ ],
164
+ ];
165
+ });
166
+
167
+ $fileActivities = Activity::with(['causer'])
168
+ ->where('subject_type', File::class)
169
+ ->where(function ($query) use ($issue) {
170
+ $query->where('properties->attributes->subject_uuid', $issue->uuid)
171
+ ->orWhere('properties->old->subject_uuid', $issue->uuid);
172
+ })
173
+ ->latest()
174
+ ->get()
175
+ ->filter(fn (Activity $activity) => $activity->event === 'deleted')
176
+ ->map(function (Activity $activity) {
177
+ $fileName = data_get($activity->properties, 'old.original_filename')
178
+ ?? data_get($activity->properties, 'attributes.original_filename')
179
+ ?? 'Document';
180
+
181
+ return [
182
+ 'id' => $activity->uuid ?? $activity->id,
183
+ 'type' => 'document_removed',
184
+ 'label' => 'Document removed',
185
+ 'description' => $fileName,
186
+ 'actor_name' => data_get($activity, 'causer.name', 'Someone'),
187
+ 'actor_avatar_url' => data_get($activity, 'causer.avatar_url'),
188
+ 'created_at' => $activity->created_at,
189
+ 'icon' => 'file-circle-xmark',
190
+ 'tone' => 'red',
191
+ 'meta' => [
192
+ 'file_name' => $fileName,
193
+ ],
194
+ ];
195
+ });
196
+
197
+ return $currentFiles->merge($fileActivities);
198
+ }
199
+
200
+ protected function makeIssueOpenedEvent(Issue $issue): array
201
+ {
202
+ return [
203
+ 'id' => $issue->uuid . '-opened',
204
+ 'type' => 'issue_opened',
205
+ 'label' => 'Issue opened',
206
+ 'description' => 'Reported by ' . ($issue->reporter_name ?: 'Unknown reporter'),
207
+ 'actor_name' => $issue->reporter_name ?: 'Unknown reporter',
208
+ 'actor_avatar_url' => data_get($issue, 'reporter.avatar_url'),
209
+ 'created_at' => $issue->created_at,
210
+ 'icon' => 'circle-plus',
211
+ 'tone' => 'green',
212
+ 'meta' => [
213
+ 'issue_id' => $issue->public_id,
214
+ ],
215
+ ];
216
+ }
217
+
218
+ protected function makeFieldChangedEvent(Activity $activity, Issue $issue, string $field, $from, $to): array
219
+ {
220
+ $eventMap = [
221
+ 'status' => ['status_changed', 'Status changed', 'arrows-rotate', 'blue'],
222
+ 'priority' => ['priority_changed', 'Priority changed', 'flag', 'orange'],
223
+ 'assigned_to_uuid' => ['assignee_changed', 'Assignee changed', 'user-check', 'indigo'],
224
+ 'vehicle_uuid' => ['vehicle_changed', 'Vehicle linked', 'truck', 'slate'],
225
+ 'driver_uuid' => ['driver_changed', 'Driver linked', 'id-card', 'slate'],
226
+ 'order_uuid' => ['order_changed', 'Order linked', 'box', 'slate'],
227
+ 'resolved_at' => [$to ? 'issue_closed' : 'issue_reopened', $to ? 'Issue closed' : 'Issue re-opened', $to ? 'circle-check' : 'rotate-left', $to ? 'green' : 'orange'],
228
+ ];
229
+
230
+ [$type, $label, $icon, $tone] = $eventMap[$field] ?? ['issue_updated', Str::headline(str_replace('_uuid', '', $field)) . ' changed', 'pen', 'slate'];
231
+
232
+ if ($field === 'status' && in_array($to, ['closed', 'resolved', 'completed'])) {
233
+ [$type, $label, $icon, $tone] = ['issue_closed', 'Issue closed', 'circle-check', 'green'];
234
+ }
235
+
236
+ if ($field === 'status' && $to === 're_opened') {
237
+ [$type, $label, $icon, $tone] = ['issue_reopened', 'Issue re-opened', 'rotate-left', 'orange'];
238
+ }
239
+
240
+ return [
241
+ 'id' => ($activity->uuid ?? $activity->id) . '-' . $field,
242
+ 'type' => $type,
243
+ 'label' => $label,
244
+ 'description' => $this->formatChangeDescription($field, $from, $to),
245
+ 'actor_name' => data_get($activity, 'causer.name', 'Someone'),
246
+ 'actor_avatar_url' => data_get($activity, 'causer.avatar_url'),
247
+ 'created_at' => $activity->created_at,
248
+ 'icon' => $icon,
249
+ 'tone' => $tone,
250
+ 'meta' => [
251
+ 'field' => $field,
252
+ 'from' => $from,
253
+ 'to' => $to,
254
+ 'issue_id' => $issue->public_id,
255
+ ],
256
+ ];
257
+ }
258
+
259
+ protected function makeGenericActivityEvent(Activity $activity, Issue $issue): array
260
+ {
261
+ return [
262
+ 'id' => $activity->uuid ?? $activity->id,
263
+ 'type' => 'issue_updated',
264
+ 'label' => 'Issue updated',
265
+ 'description' => $activity->description,
266
+ 'actor_name' => data_get($activity, 'causer.name', 'Someone'),
267
+ 'actor_avatar_url' => data_get($activity, 'causer.avatar_url'),
268
+ 'created_at' => $activity->created_at,
269
+ 'icon' => 'pen',
270
+ 'tone' => 'slate',
271
+ 'meta' => [
272
+ 'issue_id' => $issue->public_id,
273
+ ],
274
+ ];
275
+ }
276
+
277
+ protected function formatChangeDescription(string $field, $from, $to): string
278
+ {
279
+ $fieldLabel = Str::headline(str_replace('_uuid', '', $field));
280
+ $fromLabel = blank($from) ? 'none' : Str::headline((string) $from);
281
+ $toLabel = blank($to) ? 'none' : Str::headline((string) $to);
282
+
283
+ return "{$fieldLabel} changed from {$fromLabel} to {$toLabel}.";
33
284
  }
34
285
 
35
286
  /**
@@ -47,7 +47,6 @@ use Illuminate\Support\Facades\DB;
47
47
  use Illuminate\Support\Facades\Storage;
48
48
  use Illuminate\Support\Facades\Validator;
49
49
  use Illuminate\Support\Str;
50
- use Illuminate\Validation\ValidationException;
51
50
  use Maatwebsite\Excel\Facades\Excel;
52
51
 
53
52
  class OrderController extends FleetOpsController
@@ -676,28 +675,9 @@ class OrderController extends FleetOpsController
676
675
  // get the next order activity
677
676
  $flow = $activity = $order->config()->nextFirstActivity();
678
677
 
679
- /**
680
- * @var \Fleetbase\LaravelMysqlSpatial\Types\Point
681
- */
682
- $location = $order->getLastLocation();
683
-
684
- // if multi drop order set first destination
685
- if ($payload->isMultipleDropOrder) {
686
- $firstDestination = $payload->waypoints->first();
687
-
688
- if ($firstDestination) {
689
- $payload->current_waypoint_uuid = $firstDestination->uuid;
690
- $payload->save();
691
- }
692
-
693
- // update activity for each waypoint and entity
694
- foreach ($payload->waypointMarkers as $waypointMarker) {
695
- $waypointMarker->insertActivity($activity, $location);
696
- }
697
-
698
- foreach ($payload->entities as $entity) {
699
- $entity->insertActivity($activity, $location);
700
- }
678
+ // if multi-stop route set first service stop destination
679
+ if ($this->payloadUsesServiceStopActivity($payload)) {
680
+ $this->ensurePayloadCurrentServiceStop($payload);
701
681
  }
702
682
 
703
683
  // update order activity
@@ -729,9 +709,16 @@ class OrderController extends FleetOpsController
729
709
  return response()->error('No order found.');
730
710
  }
731
711
 
732
- $proof = $request->input('proof');
733
- $activity = $request->array('activity');
734
- $activity = new Activity($activity, $order->getConfigFlow());
712
+ $proof = $request->input('proof');
713
+ $bypassProof = $request->boolean('bypass_proof');
714
+ $activity = $request->array('activity');
715
+ $activity = new Activity($activity, $order->getConfigFlow());
716
+
717
+ $requiresProof = Utils::isActivity($activity)
718
+ && ($activity->get('require_pod') || ($activity->completesOrder() && $order->pod_required));
719
+ if ($requiresProof && !$proof && !$bypassProof) {
720
+ return response()->error('Proof of delivery is required for this activity or must be explicitly bypassed.', 422);
721
+ }
735
722
 
736
723
  // Handle pickup/dropoff order activity update as normal
737
724
  if (Utils::isActivity($activity) && $activity->is('dispatched')) {
@@ -751,15 +738,20 @@ class OrderController extends FleetOpsController
751
738
  * @var \Fleetbase\LaravelMysqlSpatial\Types\Point
752
739
  */
753
740
  $location = $order->getLastLocation();
754
- $isMultipleWaypointOrder = $this->payloadHasWaypoints($order->payload);
741
+ $usesServiceStopActivity = $this->payloadUsesServiceStopActivity($order->payload);
742
+ $isLifecycleActivity = Utils::isActivity($activity) && in_array($activity->code, ['created', 'dispatched', 'started'], true);
755
743
 
756
- if (!$isMultipleWaypointOrder) {
744
+ if (!$usesServiceStopActivity || $isLifecycleActivity) {
757
745
  $order->updateActivity($activity, $proof);
758
746
 
759
- // also update for each order entities if not multiple drop order
760
- // all entities will share the same activity status as is one drop order
761
- foreach ($order->payload->entities as $entity) {
762
- $entity->insertActivity($activity, $location, $proof);
747
+ if (!$usesServiceStopActivity) {
748
+ foreach ($order->payload->entities as $entity) {
749
+ $entity->insertActivity($activity, $location, $proof);
750
+ }
751
+ }
752
+
753
+ if ($usesServiceStopActivity && $activity->is('started')) {
754
+ $this->ensurePayloadCurrentServiceStop($order->payload);
763
755
  }
764
756
 
765
757
  // Handle order completed
@@ -772,23 +764,6 @@ class OrderController extends FleetOpsController
772
764
  return new OrderResource($order->refresh());
773
765
  }
774
766
 
775
- if ($activity->is('dispatched')) {
776
- $order->updateActivity($activity, $proof);
777
-
778
- return new OrderResource($order->refresh());
779
- }
780
-
781
- if ($activity->is('started')) {
782
- $order->updateActivity($activity, $proof);
783
-
784
- $this->ensurePayloadCurrentServiceStop($order->payload);
785
- if (!$this->payloadHasCurrentServiceStopActivity($order->payload, $activity)) {
786
- $this->updateCurrentServiceStopActivity($order, $activity, $location, $proof);
787
- }
788
-
789
- return new OrderResource($order->refresh());
790
- }
791
-
792
767
  if (!$order->started && in_array($order->status, ['created', 'dispatched'])) {
793
768
  return response()->error('Order must be started before waypoint activity can be updated.', 422);
794
769
  }
@@ -836,18 +811,21 @@ class OrderController extends FleetOpsController
836
811
  return response()->error('No order found.');
837
812
  }
838
813
 
839
- $canUpdateWaypointActivity = $order->started || $order->started_at || !in_array($order->status, ['created', 'dispatched']);
814
+ $order->loadMissing([
815
+ 'payload.pickup',
816
+ 'payload.dropoff',
817
+ 'payload.waypoints',
818
+ 'payload.waypointMarkers.place',
819
+ 'payload.waypointMarkers.trackingNumber.status',
820
+ ]);
821
+
822
+ $canUpdateWaypointActivity = $this->payloadUsesServiceStopActivity($order->payload)
823
+ && ($order->started || $order->started_at || !in_array($order->status, ['created', 'dispatched']));
840
824
  $stop = null;
841
- if ($request->filled('waypoint') && $canUpdateWaypointActivity) {
842
- $order->loadMissing([
843
- 'payload.pickup',
844
- 'payload.dropoff',
845
- 'payload.return',
846
- 'payload.waypoints',
847
- 'payload.waypointMarkers.place',
848
- 'payload.waypointMarkers.trackingNumber.status',
849
- ]);
850
- $stop = $this->resolveServiceStopFromKey($order->payload, $request->input('waypoint'));
825
+ if ($canUpdateWaypointActivity) {
826
+ $stop = $request->filled('waypoint')
827
+ ? $this->resolveServiceStopFromKey($order->payload, $request->input('waypoint'))
828
+ : $this->payloadCurrentServiceStop($order->payload);
851
829
  }
852
830
  $orderConfig = $order->ensureOrderConfig();
853
831
  if (!$orderConfig) {
@@ -896,7 +874,7 @@ class OrderController extends FleetOpsController
896
874
  return response()->error('No order found.');
897
875
  }
898
876
 
899
- if (!$this->payloadHasWaypoints($order->payload)) {
877
+ if (!$this->payloadUsesServiceStopActivity($order->payload)) {
900
878
  return response()->error('Destination can only be changed for multi-waypoint orders.', 422);
901
879
  }
902
880
 
@@ -917,35 +895,43 @@ class OrderController extends FleetOpsController
917
895
  */
918
896
  public function capturePhoto(Request $request, string $id, ?string $subjectId = null)
919
897
  {
920
- try {
921
- $request->validate([
922
- 'photos' => 'required|array|min:1',
923
- 'photos.*' => [
924
- function ($attribute, $value, $fail) {
925
- if ($value instanceof UploadedFile) {
926
- if (
927
- !$value->isValid()
928
- || !in_array($value->extension(), ['jpg', 'jpeg', 'png', 'gif', 'webp'])
929
- || $value->getSize() > 10 * 1024 * 1024
930
- ) {
931
- $fail("{$attribute} must be a valid image file <= 10 MB.");
932
- }
933
-
934
- return;
898
+ $incoming = $this->collectProofPhotoInputs($request);
899
+ $metadata = $request->input('data', []);
900
+ $metadata = is_array($metadata) ? $metadata : [];
901
+ $validator = Validator::make([
902
+ 'photos' => $incoming,
903
+ 'remarks' => $request->input('remarks'),
904
+ 'data' => $metadata,
905
+ ], [
906
+ 'photos' => 'required|array|min:1',
907
+ 'photos.*' => [
908
+ function ($attribute, $value, $fail) {
909
+ if ($value instanceof UploadedFile) {
910
+ $extension = strtolower($value->getClientOriginalExtension() ?: $value->extension());
911
+ if (
912
+ !$value->isValid()
913
+ || !in_array($extension, ['jpg', 'jpeg', 'png', 'gif', 'webp'])
914
+ || $value->getSize() > 10 * 1024 * 1024
915
+ ) {
916
+ $fail("{$attribute} must be a valid image file <= 10 MB.");
935
917
  }
936
918
 
937
- if (is_string($value) && base64_decode($value, true) !== false) {
938
- return;
939
- }
919
+ return;
920
+ }
940
921
 
941
- $fail("{$attribute} must be an image file or a valid Base64 string.");
942
- },
943
- ],
944
- 'remarks' => 'sometimes|string|max:255',
945
- 'data' => 'sometimes|array',
946
- ]);
947
- } catch (ValidationException $e) {
948
- $errorMessage = collect($e->errors())->flatten()->first();
922
+ if ($this->isValidBase64ProofPhoto($value)) {
923
+ return;
924
+ }
925
+
926
+ $fail("{$attribute} must be an image file or a valid Base64 string.");
927
+ },
928
+ ],
929
+ 'remarks' => 'sometimes|string|max:255',
930
+ 'data' => 'sometimes|array',
931
+ ]);
932
+
933
+ if ($validator->fails()) {
934
+ $errorMessage = collect($validator->errors()->all())->first();
949
935
 
950
936
  return response()->error($errorMessage, 422);
951
937
  }
@@ -960,15 +946,10 @@ class OrderController extends FleetOpsController
960
946
  return response()->error('Unable to capture photo as proof.', 422);
961
947
  }
962
948
 
963
- $disk = $request->input('disk', config('filesystems.default'));
964
- $bucket = $request->input("filesystems.disks.{$disk}.bucket", config('filesystems.disks.s3.bucket'));
965
- $rawInputs = $request->file('photos', []);
966
- $base64Inputs = array_filter($request->input('photos', []), function ($value) {
967
- return is_string($value) && base64_decode($value, true) !== false;
968
- });
969
- $incoming = array_merge($rawInputs, $base64Inputs);
970
- $remarks = $request->input('remarks', 'Verified by Photo');
971
- $data = $request->input('data', []);
949
+ $disk = $request->input('disk', config('filesystems.default'));
950
+ $bucket = $request->input("filesystems.disks.{$disk}.bucket", config('filesystems.disks.s3.bucket'));
951
+ $remarks = $request->input('remarks', 'Verified by Photo');
952
+ $data = $metadata;
972
953
 
973
954
  if (empty($incoming)) {
974
955
  return response()->error('No photo data to capture.', 422);
@@ -992,13 +973,107 @@ class OrderController extends FleetOpsController
992
973
  return new ProofResource($proof);
993
974
  }
994
975
 
976
+ /**
977
+ * Normalize console proof upload payloads into one photo list before validation.
978
+ */
979
+ protected function collectProofPhotoInputs(Request $request): array
980
+ {
981
+ $incoming = [];
982
+ foreach (['photos', 'photo', 'files', 'file'] as $key) {
983
+ $this->appendProofPhotoInputs($incoming, $request->file($key));
984
+ $this->appendProofPhotoInputs($incoming, $request->input($key));
985
+ }
986
+
987
+ $incoming = array_values(array_filter($incoming, function ($value) {
988
+ return $value instanceof UploadedFile || is_string($value);
989
+ }));
990
+
991
+ return $this->dedupeProofPhotoInputs($incoming);
992
+ }
993
+
994
+ /**
995
+ * Append a nested file/input value to the proof input list.
996
+ */
997
+ protected function appendProofPhotoInputs(array &$incoming, mixed $value): void
998
+ {
999
+ if ($value === null) {
1000
+ return;
1001
+ }
1002
+
1003
+ if (is_array($value)) {
1004
+ foreach ($value as $nested) {
1005
+ $this->appendProofPhotoInputs($incoming, $nested);
1006
+ }
1007
+
1008
+ return;
1009
+ }
1010
+
1011
+ $incoming[] = $value;
1012
+ }
1013
+
1014
+ /**
1015
+ * Remove duplicate photo inputs exposed under multiple multipart aliases.
1016
+ */
1017
+ protected function dedupeProofPhotoInputs(array $incoming): array
1018
+ {
1019
+ $seen = [];
1020
+
1021
+ return array_values(array_filter($incoming, function ($value) use (&$seen) {
1022
+ $fingerprint = $this->proofPhotoInputFingerprint($value);
1023
+ if ($fingerprint === null || isset($seen[$fingerprint])) {
1024
+ return false;
1025
+ }
1026
+
1027
+ $seen[$fingerprint] = true;
1028
+
1029
+ return true;
1030
+ }));
1031
+ }
1032
+
1033
+ /**
1034
+ * Build a stable fingerprint for an uploaded file or Base64 proof payload.
1035
+ */
1036
+ protected function proofPhotoInputFingerprint(mixed $value): ?string
1037
+ {
1038
+ if ($value instanceof UploadedFile) {
1039
+ return hash('sha256', implode('|', [
1040
+ $value->getRealPath(),
1041
+ $value->getSize(),
1042
+ $value->getClientOriginalName(),
1043
+ $value->getClientMimeType(),
1044
+ ]));
1045
+ }
1046
+
1047
+ if (is_string($value)) {
1048
+ $payload = Str::contains($value, 'base64,') ? Str::after($value, 'base64,') : $value;
1049
+
1050
+ return hash('sha256', $payload);
1051
+ }
1052
+
1053
+ return null;
1054
+ }
1055
+
1056
+ /**
1057
+ * Determine whether a proof input is a usable Base64 image payload.
1058
+ */
1059
+ protected function isValidBase64ProofPhoto(mixed $value): bool
1060
+ {
1061
+ if (!is_string($value)) {
1062
+ return false;
1063
+ }
1064
+
1065
+ $payload = Str::contains($value, 'base64,') ? Str::after($value, 'base64,') : $value;
1066
+
1067
+ return base64_decode($payload, true) !== false;
1068
+ }
1069
+
995
1070
  /**
996
1071
  * Store a proof photo and create its file record.
997
1072
  */
998
1073
  protected function storeProofPhoto(Proof $proof, UploadedFile|string $photo, string $disk, string $bucket): File
999
1074
  {
1000
1075
  $isFile = $photo instanceof UploadedFile;
1001
- $contents = $isFile ? file_get_contents($photo->getRealPath()) : base64_decode($photo);
1076
+ $contents = $isFile ? file_get_contents($photo->getRealPath()) : base64_decode(Str::contains($photo, 'base64,') ? Str::after($photo, 'base64,') : $photo);
1002
1077
  $extension = $isFile ? $photo->getClientOriginalExtension() : 'png';
1003
1078
  $contentType = $isFile ? $photo->getClientMimeType() : 'image/png';
1004
1079
  $company = session('company');