@fleetbase/fleetops-engine 0.6.51 → 0.6.53

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 (396) hide show
  1. package/README.md +0 -2
  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/device/manager.hbs +5 -6
  7. package/addon/components/device/manager.js +12 -15
  8. package/addon/components/driver/details.hbs +1 -1
  9. package/addon/components/fleet/vehicle-listing.js +1 -0
  10. package/addon/components/fuel-integration/form.hbs +200 -0
  11. package/addon/components/fuel-integration/form.js +242 -0
  12. package/addon/components/fuel-integration/hub.hbs +154 -0
  13. package/addon/components/fuel-integration/hub.js +165 -0
  14. package/addon/components/fuel-integration/matching-priority.hbs +76 -0
  15. package/addon/components/fuel-integration/matching-priority.js +145 -0
  16. package/addon/components/fuel-report/details.hbs +13 -1
  17. package/addon/components/issue/details.hbs +3 -57
  18. package/addon/components/issue/details.js +0 -96
  19. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.hbs +201 -0
  20. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +589 -0
  21. package/addon/components/layout/fleet-ops-sidebar.hbs +19 -233
  22. package/addon/components/layout/fleet-ops-sidebar.js +287 -376
  23. package/addon/components/maintenance-schedule/form.js +1 -0
  24. package/addon/components/map/drawer/device-event-listing.hbs +12 -2
  25. package/addon/components/map/drawer/driver-listing.hbs +14 -1
  26. package/addon/components/map/drawer/place-listing.hbs +14 -1
  27. package/addon/components/map/drawer/position-listing.hbs +12 -2
  28. package/addon/components/map/drawer/vehicle-listing.hbs +14 -1
  29. package/addon/components/modals/assigned-orders.hbs +40 -0
  30. package/addon/components/modals/attach-device.hbs +4 -3
  31. package/addon/components/modals/attach-telematic-device.hbs +25 -0
  32. package/addon/components/modals/attach-telematic-device.js +3 -0
  33. package/addon/components/modals/driver-assign-order.hbs +7 -58
  34. package/addon/components/modals/driver-assign-vehicle.hbs +10 -2
  35. package/addon/components/modals/fleet-assign-driver.hbs +18 -0
  36. package/addon/components/modals/fleet-assign-driver.js +3 -0
  37. package/addon/components/modals/fleet-assign-vehicle.hbs +18 -0
  38. package/addon/components/modals/fleet-assign-vehicle.js +3 -0
  39. package/addon/components/modals/telematic-connection-diagnostics.hbs +75 -0
  40. package/addon/components/modals/telematic-connection-diagnostics.js +287 -0
  41. package/addon/components/modals/vehicle-details.hbs +6 -1
  42. package/addon/components/modals/vehicle-form.hbs +8 -1
  43. package/addon/components/order/card.hbs +106 -0
  44. package/addon/components/order/card.js +257 -0
  45. package/addon/components/order/form/payload.js +3 -1
  46. package/addon/components/order/form/service-rate.hbs +2 -0
  47. package/addon/components/service-rate/form.hbs +3 -2
  48. package/addon/components/telematic/details.hbs +98 -87
  49. package/addon/components/telematic/details.js +230 -1
  50. package/addon/components/telematic/form.hbs +303 -72
  51. package/addon/components/telematic/form.js +519 -3
  52. package/addon/components/telematic/hub.hbs +149 -0
  53. package/addon/components/telematic/hub.js +142 -0
  54. package/addon/components/telematic/settings.hbs +152 -0
  55. package/addon/components/telematic/settings.js +45 -0
  56. package/addon/components/telematic/tab-empty-state.hbs +29 -0
  57. package/addon/components/telematic/tab-empty-state.js +33 -0
  58. package/addon/components/vehicle/details.hbs +6 -1
  59. package/addon/components/vehicle/form.hbs +2 -1
  60. package/addon/components/widget/fuel-providers.hbs +43 -0
  61. package/addon/components/widget/fuel-providers.js +27 -0
  62. package/addon/controllers/connectivity/devices/index/details.js +27 -1
  63. package/addon/controllers/connectivity/devices/index.js +18 -1
  64. package/addon/controllers/connectivity/fuel-providers/details/sync.js +18 -0
  65. package/addon/controllers/connectivity/fuel-providers/details/transactions.js +24 -0
  66. package/addon/controllers/connectivity/fuel-providers/details.js +69 -0
  67. package/addon/controllers/connectivity/fuel-providers/edit.js +24 -0
  68. package/addon/controllers/connectivity/fuel-providers/index.js +177 -0
  69. package/addon/controllers/connectivity/fuel-providers/new.js +37 -0
  70. package/addon/controllers/connectivity/telematics/details/attachments.js +247 -0
  71. package/addon/controllers/connectivity/telematics/details/devices.js +368 -0
  72. package/addon/controllers/connectivity/telematics/details/events.js +244 -0
  73. package/addon/controllers/connectivity/telematics/details/index.js +3 -0
  74. package/addon/controllers/connectivity/telematics/details/logs.js +43 -0
  75. package/addon/controllers/connectivity/telematics/details/sensors.js +270 -0
  76. package/addon/controllers/connectivity/telematics/details.js +149 -0
  77. package/addon/controllers/connectivity/telematics/{index/edit.js → edit.js} +15 -4
  78. package/addon/controllers/connectivity/telematics/index.js +25 -5
  79. package/addon/controllers/connectivity/telematics/{index/new.js → new.js} +8 -2
  80. package/addon/controllers/maintenance/index.js +100 -0
  81. package/addon/controllers/management/drivers/index/details.js +78 -1
  82. package/addon/controllers/management/drivers/index.js +39 -0
  83. package/addon/controllers/management/fleets/index/details.js +23 -1
  84. package/addon/controllers/management/fleets/index.js +6 -1
  85. package/addon/controllers/management/fuel-reports/index.js +37 -1
  86. package/addon/controllers/management/fuel-transactions/index/details.js +70 -0
  87. package/addon/controllers/management/fuel-transactions/index.js +240 -0
  88. package/addon/controllers/management/index.js +88 -0
  89. package/addon/controllers/management/vehicles/index/details.js +32 -0
  90. package/addon/controllers/management/vehicles/index.js +26 -0
  91. package/addon/controllers/operations/orders/index.js +4 -0
  92. package/addon/controllers/settings/index.js +142 -0
  93. package/addon/controllers/settings/payments/index.js +5 -0
  94. package/addon/extension.js +31 -0
  95. package/addon/routes/analytics/index.js +3 -0
  96. package/addon/routes/connectivity/devices/index.js +3 -0
  97. package/addon/routes/connectivity/fuel-providers/details/index.js +7 -0
  98. package/addon/routes/connectivity/fuel-providers/details/matching.js +7 -0
  99. package/addon/routes/connectivity/fuel-providers/details/settings.js +7 -0
  100. package/addon/routes/connectivity/fuel-providers/details/sync.js +18 -0
  101. package/addon/routes/connectivity/fuel-providers/details/transactions.js +14 -0
  102. package/addon/routes/connectivity/fuel-providers/details.js +18 -0
  103. package/addon/routes/connectivity/fuel-providers/edit.js +18 -0
  104. package/addon/routes/connectivity/fuel-providers/index.js +20 -0
  105. package/addon/routes/connectivity/fuel-providers/new.js +3 -0
  106. package/addon/routes/connectivity/fuel-providers.js +3 -0
  107. package/addon/routes/connectivity/telematics/details/attachments.js +28 -0
  108. package/addon/routes/connectivity/telematics/details/devices.js +30 -0
  109. package/addon/routes/connectivity/telematics/details/events.js +33 -0
  110. package/addon/routes/connectivity/telematics/details/index.js +3 -0
  111. package/addon/routes/connectivity/telematics/details/logs.js +17 -0
  112. package/addon/routes/connectivity/telematics/details/sensors.js +29 -0
  113. package/addon/routes/connectivity/telematics/{index/details.js → details.js} +1 -1
  114. package/addon/routes/connectivity/telematics/{index/edit.js → edit.js} +1 -1
  115. package/addon/routes/connectivity/telematics/new.js +3 -0
  116. package/addon/routes/maintenance/index.js +3 -0
  117. package/addon/routes/maintenance/work-orders/index.js +2 -0
  118. package/addon/routes/management/drivers/index/details/activity.js +3 -0
  119. package/addon/routes/management/fuel-reports/index.js +2 -0
  120. package/addon/routes/management/fuel-transactions/index/details.js +18 -0
  121. package/addon/routes/management/fuel-transactions/index.js +21 -0
  122. package/addon/routes/management/fuel-transactions.js +3 -0
  123. package/addon/routes/settings/index.js +3 -0
  124. package/addon/routes.js +28 -9
  125. package/addon/services/device-actions.js +64 -1
  126. package/addon/services/device-event-actions.js +12 -1
  127. package/addon/services/driver-actions.js +147 -11
  128. package/addon/services/fleet-actions.js +66 -1
  129. package/addon/services/fuel-integration-actions.js +31 -0
  130. package/addon/services/issue-actions.js +1 -1
  131. package/addon/services/telematic-actions.js +9 -3
  132. package/addon/services/vehicle-actions.js +125 -0
  133. package/addon/styles/fleetops-engine.css +2237 -162
  134. package/addon/templates/analytics/index.hbs +14 -0
  135. package/addon/templates/analytics/reports/index.hbs +19 -1
  136. package/addon/templates/connectivity/devices/index.hbs +19 -1
  137. package/addon/templates/connectivity/events/index.hbs +16 -1
  138. package/addon/templates/connectivity/fuel-providers/details/index.hbs +38 -0
  139. package/addon/templates/connectivity/fuel-providers/details/matching.hbs +13 -0
  140. package/addon/templates/connectivity/fuel-providers/details/settings.hbs +11 -0
  141. package/addon/templates/connectivity/fuel-providers/details/sync.hbs +48 -0
  142. package/addon/templates/connectivity/fuel-providers/details/transactions.hbs +21 -0
  143. package/addon/templates/connectivity/fuel-providers/details.hbs +51 -0
  144. package/addon/templates/connectivity/fuel-providers/edit.hbs +24 -0
  145. package/addon/templates/connectivity/fuel-providers/index.hbs +12 -0
  146. package/addon/templates/connectivity/fuel-providers/new.hbs +22 -0
  147. package/addon/templates/connectivity/fuel-providers.hbs +1 -0
  148. package/addon/templates/connectivity/sensors/index.hbs +19 -1
  149. package/addon/templates/connectivity/telematics/details/attachments.hbs +163 -0
  150. package/addon/templates/connectivity/telematics/details/devices.hbs +88 -0
  151. package/addon/templates/connectivity/telematics/details/events.hbs +101 -0
  152. package/addon/templates/connectivity/telematics/{index/details → details}/index.hbs +0 -1
  153. package/addon/templates/connectivity/telematics/details/logs.hbs +69 -0
  154. package/addon/templates/connectivity/telematics/details/sensors.hbs +88 -0
  155. package/addon/templates/connectivity/telematics/details.hbs +53 -0
  156. package/addon/templates/connectivity/telematics/edit.hbs +27 -0
  157. package/addon/templates/connectivity/telematics/index.hbs +6 -8
  158. package/addon/templates/connectivity/telematics/new.hbs +24 -0
  159. package/addon/templates/maintenance/equipment/index.hbs +19 -1
  160. package/addon/templates/maintenance/index.hbs +113 -0
  161. package/addon/templates/maintenance/maintenances/index.hbs +15 -1
  162. package/addon/templates/maintenance/parts/index.hbs +19 -1
  163. package/addon/templates/maintenance/schedules/index.hbs +19 -1
  164. package/addon/templates/maintenance/work-orders/index.hbs +19 -1
  165. package/addon/templates/management/contacts/customers.hbs +15 -1
  166. package/addon/templates/management/contacts/index.hbs +19 -1
  167. package/addon/templates/management/drivers/index/details/activity.hbs +1 -0
  168. package/addon/templates/management/drivers/index.hbs +19 -1
  169. package/addon/templates/management/fleets/index.hbs +19 -1
  170. package/addon/templates/management/fuel-reports/index.hbs +19 -1
  171. package/addon/templates/management/fuel-transactions/index/details.hbs +73 -0
  172. package/addon/templates/management/fuel-transactions/index.hbs +58 -0
  173. package/addon/templates/management/fuel-transactions.hbs +1 -0
  174. package/addon/templates/management/index.hbs +109 -73
  175. package/addon/templates/management/issues/index.hbs +19 -1
  176. package/addon/templates/management/places/index.hbs +19 -1
  177. package/addon/templates/management/vehicles/index.hbs +19 -1
  178. package/addon/templates/management/vendors/index.hbs +19 -1
  179. package/addon/templates/management/vendors/integrated.hbs +13 -1
  180. package/addon/templates/operations/orders/index.hbs +19 -1
  181. package/addon/templates/operations/service-rates/index.hbs +19 -1
  182. package/addon/templates/settings/index.hbs +83 -0
  183. package/addon/templates/settings/payments/index.hbs +15 -1
  184. package/addon/templates/settings/routing.hbs +2 -2
  185. package/addon/utils/fleet-ops-options.js +3 -0
  186. package/app/components/cell/telematic-provider.js +1 -0
  187. package/app/components/cell/telematic-status.js +1 -0
  188. package/app/components/fuel-integration/form.js +1 -0
  189. package/app/components/fuel-integration/hub.js +1 -0
  190. package/app/components/fuel-integration/matching-priority.js +1 -0
  191. package/app/components/modals/assigned-orders.js +1 -0
  192. package/app/components/modals/attach-telematic-device.js +1 -0
  193. package/app/components/modals/fleet-assign-driver.js +1 -0
  194. package/app/components/modals/fleet-assign-vehicle.js +1 -0
  195. package/app/components/modals/telematic-connection-diagnostics.js +1 -0
  196. package/app/components/order/card.js +1 -0
  197. package/app/components/telematic/hub.js +1 -0
  198. package/app/components/telematic/settings.js +1 -0
  199. package/app/components/telematic/tab-empty-state.js +1 -0
  200. package/app/components/widget/fuel-providers.js +1 -0
  201. package/app/controllers/connectivity/fuel-providers/details/sync.js +1 -0
  202. package/app/controllers/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  203. package/app/controllers/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  204. package/app/controllers/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  205. package/app/controllers/connectivity/{telematics/index/details → fuel-providers}/index.js +1 -1
  206. package/app/controllers/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  207. package/app/controllers/connectivity/telematics/details/attachments.js +1 -0
  208. package/app/controllers/connectivity/telematics/details/devices.js +1 -0
  209. package/app/controllers/connectivity/telematics/details/events.js +1 -0
  210. package/app/controllers/connectivity/telematics/details/index.js +1 -0
  211. package/app/controllers/connectivity/telematics/details/logs.js +1 -0
  212. package/app/controllers/connectivity/telematics/details/sensors.js +1 -0
  213. package/app/controllers/connectivity/telematics/details.js +1 -0
  214. package/app/controllers/connectivity/telematics/edit.js +1 -0
  215. package/app/controllers/connectivity/telematics/new.js +1 -0
  216. package/app/controllers/maintenance/index.js +1 -0
  217. package/app/controllers/management/fuel-transactions/index/details.js +1 -0
  218. package/app/controllers/management/fuel-transactions/index.js +1 -0
  219. package/app/controllers/management/index.js +1 -0
  220. package/app/controllers/settings/index.js +1 -0
  221. package/app/routes/analytics/index.js +1 -0
  222. package/app/routes/connectivity/{telematics/index → fuel-providers}/details/index.js +1 -1
  223. package/app/routes/connectivity/{telematics/index/details/events.js → fuel-providers/details/matching.js} +1 -1
  224. package/app/routes/connectivity/fuel-providers/details/settings.js +1 -0
  225. package/app/routes/connectivity/fuel-providers/details/sync.js +1 -0
  226. package/app/routes/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  227. package/app/routes/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  228. package/app/routes/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  229. package/app/routes/connectivity/fuel-providers/index.js +1 -0
  230. package/app/routes/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  231. package/app/routes/connectivity/fuel-providers.js +1 -0
  232. package/app/routes/connectivity/telematics/details/attachments.js +1 -0
  233. package/app/routes/connectivity/telematics/details/devices.js +1 -0
  234. package/app/routes/connectivity/telematics/details/events.js +1 -0
  235. package/app/routes/connectivity/telematics/details/index.js +1 -0
  236. package/app/routes/connectivity/telematics/details/logs.js +1 -0
  237. package/app/routes/connectivity/telematics/details/sensors.js +1 -0
  238. package/app/routes/connectivity/telematics/details.js +1 -0
  239. package/app/routes/connectivity/telematics/edit.js +1 -0
  240. package/app/routes/connectivity/telematics/new.js +1 -0
  241. package/app/routes/maintenance/index.js +1 -0
  242. package/app/routes/management/drivers/index/details/activity.js +1 -0
  243. package/app/routes/management/fuel-transactions/index/details.js +1 -0
  244. package/app/routes/management/fuel-transactions/index.js +1 -0
  245. package/app/routes/management/fuel-transactions.js +1 -0
  246. package/app/routes/settings/index.js +1 -0
  247. package/app/services/fuel-integration-actions.js +1 -0
  248. package/app/templates/analytics/index.js +1 -0
  249. package/app/templates/connectivity/{telematics/index → fuel-providers}/details/index.js +1 -1
  250. package/app/templates/connectivity/{telematics/index/details/events.js → fuel-providers/details/matching.js} +1 -1
  251. package/app/templates/connectivity/fuel-providers/details/settings.js +1 -0
  252. package/app/templates/connectivity/fuel-providers/details/sync.js +1 -0
  253. package/app/templates/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  254. package/app/templates/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  255. package/app/templates/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  256. package/app/templates/connectivity/fuel-providers/index.js +1 -0
  257. package/app/templates/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  258. package/app/templates/connectivity/fuel-providers.js +1 -0
  259. package/app/templates/connectivity/telematics/details/attachments.js +1 -0
  260. package/app/templates/connectivity/telematics/details/devices.js +1 -0
  261. package/app/templates/connectivity/telematics/details/events.js +1 -0
  262. package/app/templates/connectivity/telematics/details/index.js +1 -0
  263. package/app/templates/connectivity/telematics/details/logs.js +1 -0
  264. package/app/templates/connectivity/telematics/details/sensors.js +1 -0
  265. package/app/templates/connectivity/telematics/details.js +1 -0
  266. package/app/templates/connectivity/telematics/edit.js +1 -0
  267. package/app/templates/connectivity/telematics/new.js +1 -0
  268. package/app/templates/maintenance/index.js +1 -0
  269. package/app/templates/management/drivers/index/details/activity.js +1 -0
  270. package/app/templates/management/fuel-transactions/index/details.js +1 -0
  271. package/app/templates/management/fuel-transactions/index.js +1 -0
  272. package/app/templates/management/fuel-transactions.js +1 -0
  273. package/app/templates/management/index.js +1 -0
  274. package/app/templates/settings/index.js +1 -0
  275. package/assets/images/telematics/providers/afaqy.webp +0 -0
  276. package/assets/images/telematics/providers/default.webp +0 -0
  277. package/assets/images/telematics/providers/flespi.webp +0 -0
  278. package/assets/images/telematics/providers/geotab.webp +0 -0
  279. package/assets/images/telematics/providers/safee.webp +0 -0
  280. package/assets/images/telematics/providers/samsara.webp +0 -0
  281. package/composer.json +1 -1
  282. package/extension.json +1 -1
  283. package/package.json +4 -4
  284. package/server/config/fuel-providers.php +42 -0
  285. package/server/config/telematics.php +139 -130
  286. package/server/migrations/2026_06_06_000001_harden_device_events_telematics_contract.php +59 -0
  287. package/server/migrations/2026_06_08_000001_create_fuel_provider_connections_table.php +37 -0
  288. package/server/migrations/2026_06_08_000002_create_fuel_provider_transactions_table.php +53 -0
  289. package/server/migrations/2026_06_08_000003_create_fuel_provider_sync_runs_table.php +40 -0
  290. package/server/migrations/2026_06_10_000001_add_fuel_card_number_to_vehicles_table.php +25 -0
  291. package/server/migrations/2026_06_10_000002_add_vehicle_identity_columns_to_fuel_provider_transactions_table.php +39 -0
  292. package/server/migrations/2026_06_15_000001_normalize_device_attachable_vehicle_morph_types.php +30 -0
  293. package/server/src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php +1 -0
  294. package/server/src/Contracts/FuelProvider.php +30 -0
  295. package/server/src/Contracts/TelematicProviderDescriptor.php +3 -1
  296. package/server/src/Events/FuelProviderTransactionImported.php +17 -0
  297. package/server/src/Events/FuelProviderTransactionMatched.php +17 -0
  298. package/server/src/Events/FuelProviderTransactionUnmatched.php +17 -0
  299. package/server/src/Events/FuelReportCreatedFromProvider.php +18 -0
  300. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +6 -0
  301. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +140 -2
  302. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +44 -0
  303. package/server/src/Http/Controllers/Internal/v1/DriverController.php +158 -7
  304. package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +124 -0
  305. package/server/src/Http/Controllers/Internal/v1/FuelProviderSyncRunController.php +10 -0
  306. package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +76 -0
  307. package/server/src/Http/Controllers/Internal/v1/HubController.php +300 -0
  308. package/server/src/Http/Controllers/Internal/v1/SearchController.php +506 -0
  309. package/server/src/Http/Controllers/Internal/v1/SensorController.php +1 -1
  310. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +179 -16
  311. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +249 -0
  312. package/server/src/Http/Controllers/TelematicWebhookController.php +103 -19
  313. package/server/src/Http/Filter/DeviceFilter.php +74 -0
  314. package/server/src/Http/Filter/DriverFilter.php +6 -0
  315. package/server/src/Http/Filter/FuelProviderConnectionFilter.php +33 -0
  316. package/server/src/Http/Filter/FuelProviderSyncRunFilter.php +28 -0
  317. package/server/src/Http/Filter/FuelProviderTransactionFilter.php +59 -0
  318. package/server/src/Http/Filter/FuelReportFilter.php +10 -0
  319. package/server/src/Http/Filter/VehicleFilter.php +6 -0
  320. package/server/src/Http/Resources/v1/Driver.php +15 -0
  321. package/server/src/Http/Resources/v1/FuelProviderConnection.php +30 -0
  322. package/server/src/Http/Resources/v1/FuelProviderSyncRun.php +36 -0
  323. package/server/src/Http/Resources/v1/FuelProviderTransaction.php +54 -0
  324. package/server/src/Http/Resources/v1/FuelReport.php +28 -22
  325. package/server/src/Http/Resources/v1/Index/Driver.php +26 -20
  326. package/server/src/Http/Resources/v1/Index/Vehicle.php +29 -22
  327. package/server/src/Http/Resources/v1/Vehicle.php +17 -0
  328. package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +2 -0
  329. package/server/src/Jobs/SyncFuelProviderTransactionsJob.php +57 -0
  330. package/server/src/Jobs/SyncTelematicDevicesJob.php +105 -17
  331. package/server/src/Jobs/TestTelematicConnectionJob.php +9 -22
  332. package/server/src/Models/Asset.php +2 -1
  333. package/server/src/Models/Contact.php +2 -1
  334. package/server/src/Models/Device.php +27 -4
  335. package/server/src/Models/DeviceEvent.php +40 -3
  336. package/server/src/Models/Driver.php +3 -2
  337. package/server/src/Models/Equipment.php +2 -1
  338. package/server/src/Models/Fleet.php +2 -1
  339. package/server/src/Models/FuelProviderConnection.php +60 -0
  340. package/server/src/Models/FuelProviderSyncRun.php +58 -0
  341. package/server/src/Models/FuelProviderTransaction.php +119 -0
  342. package/server/src/Models/FuelReport.php +16 -1
  343. package/server/src/Models/Part.php +2 -1
  344. package/server/src/Models/Sensor.php +21 -4
  345. package/server/src/Models/ServiceRate.php +39 -2
  346. package/server/src/Models/Vehicle.php +14 -2
  347. package/server/src/Models/Vendor.php +2 -1
  348. package/server/src/Providers/FleetOpsServiceProvider.php +20 -0
  349. package/server/src/Support/Analytics/FuelProviderSummary.php +42 -0
  350. package/server/src/Support/FuelProviders/FuelProviderDescriptor.php +55 -0
  351. package/server/src/Support/FuelProviders/FuelProviderRegistry.php +75 -0
  352. package/server/src/Support/FuelProviders/FuelProviderService.php +447 -0
  353. package/server/src/Support/FuelProviders/Providers/AbstractFuelProvider.php +98 -0
  354. package/server/src/Support/FuelProviders/Providers/PetroAppFuelProvider.php +167 -0
  355. package/server/src/Support/Telematics/Providers/AbstractProvider.php +27 -6
  356. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +105 -38
  357. package/server/src/Support/Telematics/Providers/FlespiProvider.php +1 -0
  358. package/server/src/Support/Telematics/Providers/GeotabProvider.php +1 -0
  359. package/server/src/Support/Telematics/Providers/SafeeProvider.php +32 -22
  360. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +3 -1
  361. package/server/src/Support/Telematics/TelematicService.php +340 -21
  362. package/server/src/routes.php +50 -4
  363. package/server/tests/FuelProviderRegistryTest.php +416 -0
  364. package/server/tests/HubRoutesTest.php +62 -0
  365. package/server/tests/ServiceRateAlgorithmWeightVariablesTest.php +44 -0
  366. package/server/tests/TelematicsHardeningTest.php +359 -0
  367. package/server/tests/VehicleDriverAssignmentTest.php +51 -0
  368. package/translations/ar-ae.yml +23 -1
  369. package/translations/bg-bg.yaml +72 -0
  370. package/translations/en-us.yaml +83 -0
  371. package/translations/es-pa.yaml +72 -0
  372. package/translations/fr-fr.yaml +72 -0
  373. package/translations/mn-mn.yaml +72 -0
  374. package/translations/pt-br.yaml +72 -0
  375. package/translations/ru-ru.yaml +72 -0
  376. package/translations/vi-vn.yaml +98 -1
  377. package/addon/controllers/connectivity/telematics/index/details/devices.js +0 -3
  378. package/addon/controllers/connectivity/telematics/index/details/events.js +0 -3
  379. package/addon/controllers/connectivity/telematics/index/details/index.js +0 -3
  380. package/addon/controllers/connectivity/telematics/index/details/sensors.js +0 -3
  381. package/addon/controllers/connectivity/telematics/index/details.js +0 -24
  382. package/addon/routes/connectivity/telematics/index/details/devices.js +0 -3
  383. package/addon/routes/connectivity/telematics/index/details/events.js +0 -3
  384. package/addon/routes/connectivity/telematics/index/details/index.js +0 -3
  385. package/addon/routes/connectivity/telematics/index/details/sensors.js +0 -3
  386. package/addon/routes/connectivity/telematics/index/new.js +0 -3
  387. package/addon/templates/connectivity/telematics/index/details/devices.hbs +0 -1
  388. package/addon/templates/connectivity/telematics/index/details/events.hbs +0 -1
  389. package/addon/templates/connectivity/telematics/index/details/sensors.hbs +0 -1
  390. package/addon/templates/connectivity/telematics/index/details.hbs +0 -14
  391. package/addon/templates/connectivity/telematics/index/edit.hbs +0 -12
  392. package/addon/templates/connectivity/telematics/index/new.hbs +0 -12
  393. package/app/controllers/connectivity/telematics/index/details/events.js +0 -1
  394. package/app/controllers/connectivity/telematics/index/details/sensors.js +0 -1
  395. package/app/routes/connectivity/telematics/index/details/sensors.js +0 -1
  396. package/app/templates/connectivity/telematics/index/details/sensors.js +0 -1
@@ -6,6 +6,7 @@ use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
6
6
  use Fleetbase\FleetOps\Models\Telematic;
7
7
  use Fleetbase\FleetOps\Support\Telematics\TelematicProviderRegistry;
8
8
  use Fleetbase\FleetOps\Support\Telematics\TelematicService;
9
+ use Fleetbase\Models\Activity;
9
10
  use Illuminate\Http\JsonResponse;
10
11
  use Illuminate\Http\Request;
11
12
 
@@ -54,9 +55,7 @@ class TelematicController extends FleetOpsController
54
55
  */
55
56
  public function testConnection(Request $request, string $id): JsonResponse
56
57
  {
57
- $telematic = Telematic::where('uuid', $id)
58
- ->where('company_uuid', session('company'))
59
- ->firstOrFail();
58
+ $telematic = $this->findTelematic($id);
60
59
 
61
60
  $async = $request->input('async', false);
62
61
 
@@ -82,7 +81,16 @@ class TelematicController extends FleetOpsController
82
81
  if (!$provider) {
83
82
  return response()->error('Unable to resolve telematic provider.');
84
83
  }
85
- $result = $provider->testConnection($credentials);
84
+ $result = $provider->testConnection($credentials);
85
+ $telematicId = $request->input('telematic_id');
86
+
87
+ if ($telematicId) {
88
+ $telematic = $this->findTelematic($telematicId);
89
+
90
+ if ($telematic->provider === $key) {
91
+ $this->telematicService->recordConnectionTest($telematic, $result);
92
+ }
93
+ }
86
94
  } catch (\Exception $e) {
87
95
  return response()->error($e->getMessage());
88
96
  }
@@ -99,12 +107,10 @@ class TelematicController extends FleetOpsController
99
107
  */
100
108
  public function discover(Request $request, string $id): JsonResponse
101
109
  {
102
- $telematic = Telematic::where('uuid', $id)
103
- ->where('company_uuid', session('company'))
104
- ->firstOrFail();
110
+ $telematic = $this->findTelematic($id);
105
111
 
106
112
  $jobId = $this->telematicService->discoverDevices($telematic, [
107
- 'limit' => $request->input('limit', 100),
113
+ 'limit' => $request->input('limit'),
108
114
  'filters' => $request->input('filters', []),
109
115
  ]);
110
116
 
@@ -119,9 +125,7 @@ class TelematicController extends FleetOpsController
119
125
  */
120
126
  public function devices(Request $request, string $id): JsonResponse
121
127
  {
122
- $telematic = Telematic::where('uuid', $id)
123
- ->where('company_uuid', session('company'))
124
- ->firstOrFail();
128
+ $telematic = $this->findTelematic($id);
125
129
 
126
130
  $devices = $this->telematicService->getDevices($telematic, [
127
131
  'status' => $request->input('status'),
@@ -133,18 +137,44 @@ class TelematicController extends FleetOpsController
133
137
  ]);
134
138
  }
135
139
 
140
+ /**
141
+ * Get persisted logs and audit entries for a telematic.
142
+ */
143
+ public function logs(Request $request, string $id): JsonResponse
144
+ {
145
+ $telematic = $this->findTelematic($id);
146
+ $limit = min((int) $request->input('limit', 50), 100);
147
+
148
+ $activityLogs = Activity::with(['causer'])
149
+ ->where('subject_type', Telematic::class)
150
+ ->where('subject_id', $telematic->uuid)
151
+ ->latest()
152
+ ->limit($limit)
153
+ ->get()
154
+ ->map(fn (Activity $activity) => $this->makeActivityLogEntry($activity));
155
+
156
+ $logs = $this->makeTelematicMetadataLogs($telematic)
157
+ ->merge($activityLogs)
158
+ ->sortByDesc(fn ($log) => $log['created_at'] ?? '')
159
+ ->values();
160
+
161
+ return response()->json([
162
+ 'logs' => $logs,
163
+ ]);
164
+ }
165
+
136
166
  /**
137
167
  * Link a device to a telematic.
138
168
  */
139
169
  public function linkDevice(Request $request, string $id): JsonResponse
140
170
  {
141
- $telematic = Telematic::where('uuid', $id)
142
- ->where('company_uuid', session('company'))
143
- ->firstOrFail();
171
+ $telematic = $this->findTelematic($id);
144
172
 
145
173
  $request->validate([
146
- 'external_id' => 'required|string',
147
- 'device_name' => 'required|string',
174
+ 'external_id' => 'required_without:device_id|nullable|string',
175
+ 'device_id' => 'required_without:external_id|nullable|string',
176
+ 'device_name' => 'nullable|string',
177
+ 'name' => 'nullable|string',
148
178
  ]);
149
179
 
150
180
  $device = $this->telematicService->linkDevice($telematic, $request->all());
@@ -153,4 +183,137 @@ class TelematicController extends FleetOpsController
153
183
  'device' => $device,
154
184
  ], 201);
155
185
  }
186
+
187
+ protected function findTelematic(string $id): Telematic
188
+ {
189
+ return Telematic::where(function ($query) use ($id) {
190
+ $query->where('uuid', $id)->orWhere('public_id', $id);
191
+ })
192
+ ->where('company_uuid', session('company'))
193
+ ->firstOrFail();
194
+ }
195
+
196
+ protected function makeTelematicMetadataLogs(Telematic $telematic)
197
+ {
198
+ $meta = $telematic->meta ?? [];
199
+ $logs = collect();
200
+
201
+ if (data_get($meta, 'last_sync_result')) {
202
+ $result = data_get($meta, 'last_sync_result');
203
+ $failed = $result === 'failed';
204
+
205
+ $logs->push([
206
+ 'id' => 'sync-' . (data_get($meta, 'last_sync_job_id') ?? data_get($meta, 'last_sync_completed_at') ?? data_get($meta, 'last_sync_failed_at') ?? $telematic->uuid),
207
+ 'type' => 'sync_' . $result,
208
+ 'label' => $failed ? 'Device sync failed' : ($result === 'queued' ? 'Device sync queued' : 'Device sync completed'),
209
+ 'description' => $failed
210
+ ? $this->userFacingIssueMessage(data_get($meta, 'last_sync_error'), 'Device sync failed. Review the provider connection and server logs, then try again.')
211
+ : $this->syncSuccessDescription($meta),
212
+ 'status' => $failed ? 'warning' : ($result === 'queued' ? 'info' : 'success'),
213
+ 'icon' => $failed ? 'circle-exclamation' : 'satellite-dish',
214
+ 'created_at' => data_get($meta, 'last_sync_failed_at') ?? data_get($meta, 'last_sync_completed_at') ?? data_get($meta, 'last_sync_started_at'),
215
+ 'actor_name' => null,
216
+ 'metadata' => [
217
+ 'job_id' => data_get($meta, 'last_sync_job_id'),
218
+ 'result' => $result,
219
+ 'total' => data_get($meta, 'last_sync_total'),
220
+ 'error_type' => data_get($meta, 'last_sync_error_type'),
221
+ 'started_at' => data_get($meta, 'last_sync_started_at'),
222
+ 'completed_at' => data_get($meta, 'last_sync_completed_at'),
223
+ 'failed_at' => data_get($meta, 'last_sync_failed_at'),
224
+ ],
225
+ ]);
226
+ }
227
+
228
+ if (data_get($meta, 'last_test_result')) {
229
+ $result = data_get($meta, 'last_test_result');
230
+ $failed = $result === 'failed';
231
+
232
+ $logs->push([
233
+ 'id' => 'connection-test-' . (data_get($meta, 'last_connection_test') ?? $telematic->uuid),
234
+ 'type' => 'connection_test_' . $result,
235
+ 'label' => $failed ? 'Connection test failed' : 'Connection test verified',
236
+ 'description' => $failed
237
+ ? $this->userFacingIssueMessage(data_get($meta, 'last_error'), 'Connection test failed. Review the provider credentials and try again.')
238
+ : 'Provider credentials were verified successfully.',
239
+ 'status' => $failed ? 'warning' : 'success',
240
+ 'icon' => 'plug',
241
+ 'created_at' => data_get($meta, 'last_connection_test'),
242
+ 'actor_name' => null,
243
+ 'metadata' => [
244
+ 'result' => $result,
245
+ ],
246
+ ]);
247
+ }
248
+
249
+ return $logs;
250
+ }
251
+
252
+ protected function makeActivityLogEntry(Activity $activity): array
253
+ {
254
+ return [
255
+ 'id' => $activity->uuid ?? $activity->id,
256
+ 'type' => 'activity_' . ($activity->event ?? 'updated'),
257
+ 'label' => $this->activityLogLabel($activity),
258
+ 'description' => $this->activityLogDescription($activity),
259
+ 'status' => $activity->event === 'deleted' ? 'warning' : 'default',
260
+ 'icon' => $activity->event === 'created' ? 'plus' : 'history',
261
+ 'created_at' => $activity->created_at,
262
+ 'actor_name' => data_get($activity, 'causer.name'),
263
+ 'metadata' => [
264
+ 'event' => $activity->event,
265
+ ],
266
+ ];
267
+ }
268
+
269
+ protected function activityLogLabel(Activity $activity): string
270
+ {
271
+ return match ($activity->event) {
272
+ 'created' => 'Provider connection created',
273
+ 'deleted' => 'Provider connection deleted',
274
+ default => 'Provider connection updated',
275
+ };
276
+ }
277
+
278
+ protected function activityLogDescription(Activity $activity): string
279
+ {
280
+ return match ($activity->event) {
281
+ 'created' => 'Provider connection details were created.',
282
+ 'deleted' => 'Provider connection details were removed.',
283
+ default => 'Provider connection details were updated.',
284
+ };
285
+ }
286
+
287
+ protected function syncSuccessDescription(array $meta): string
288
+ {
289
+ $total = data_get($meta, 'last_sync_total');
290
+
291
+ if (is_numeric($total)) {
292
+ return "{$total} provider devices were synced.";
293
+ }
294
+
295
+ return 'Provider device sync completed successfully.';
296
+ }
297
+
298
+ protected function userFacingIssueMessage($message, string $fallback): string
299
+ {
300
+ if (!$message || $this->isSensitiveIssueMessage($message)) {
301
+ return $fallback;
302
+ }
303
+
304
+ return (string) $message;
305
+ }
306
+
307
+ protected function isSensitiveIssueMessage($message): bool
308
+ {
309
+ $value = strtolower((string) $message);
310
+
311
+ foreach (['sqlstate', 'insert into', 'update `', 'select ', 'schema', 'stack trace', 'connection:', 'pdoexception'] as $fragment) {
312
+ if (str_contains($value, $fragment)) {
313
+ return true;
314
+ }
315
+ }
316
+
317
+ return false;
318
+ }
156
319
  }
@@ -4,14 +4,19 @@ namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
4
 
5
5
  use Fleetbase\FleetOps\Exports\VehicleExport;
6
6
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
7
+ use Fleetbase\FleetOps\Http\Resources\v1\Index\Order as IndexOrderResource;
7
8
  use Fleetbase\FleetOps\Imports\VehicleImport;
9
+ use Fleetbase\FleetOps\Models\Device;
8
10
  use Fleetbase\FleetOps\Models\Driver;
11
+ use Fleetbase\FleetOps\Models\Order;
9
12
  use Fleetbase\FleetOps\Models\Vehicle;
10
13
  use Fleetbase\Http\Requests\ExportRequest;
11
14
  use Fleetbase\Http\Requests\ImportRequest;
15
+ use Illuminate\Http\JsonResponse;
12
16
  use Illuminate\Http\Request;
13
17
  use Illuminate\Support\Arr;
14
18
  use Illuminate\Support\Facades\DB;
19
+ use Illuminate\Support\Facades\Log;
15
20
  use Illuminate\Support\Str;
16
21
  use Maatwebsite\Excel\Facades\Excel;
17
22
 
@@ -86,6 +91,250 @@ class VehicleController extends FleetOpsController
86
91
  }
87
92
  }
88
93
 
94
+ public function assignDriver(Request $request, string $id): JsonResponse
95
+ {
96
+ $request->validate(['driver' => 'required|string']);
97
+
98
+ $vehicle = $this->findVehicle($id);
99
+ $driver = $this->findDriver($request->input('driver'));
100
+
101
+ $vehicle->assignDriver($driver);
102
+ $vehicle->load(['driver', 'devices']);
103
+
104
+ return response()->json([
105
+ 'status' => 'ok',
106
+ 'message' => 'Driver assigned to vehicle.',
107
+ 'vehicle' => $vehicle,
108
+ ]);
109
+ }
110
+
111
+ public function unassignDriver(string $id): JsonResponse
112
+ {
113
+ $vehicle = $this->findVehicle($id);
114
+ $vehicle->unassignDriver();
115
+ $vehicle->load(['driver', 'devices']);
116
+
117
+ return response()->json([
118
+ 'status' => 'ok',
119
+ 'message' => 'Driver unassigned from vehicle.',
120
+ 'vehicle' => $vehicle,
121
+ ]);
122
+ }
123
+
124
+ public function assignedOrders(string $id): JsonResponse
125
+ {
126
+ $vehicle = $this->findVehicle($id);
127
+ $orders = Order::where('vehicle_assigned_uuid', $vehicle->uuid)
128
+ ->where('company_uuid', session('company'))
129
+ ->with(['payload', 'trackingNumber', 'orderConfig', 'driverAssigned', 'vehicleAssigned'])
130
+ ->orderByRaw('uuid = ? desc', [$this->activeOrderUuid($vehicle)])
131
+ ->latest()
132
+ ->get();
133
+
134
+ return response()->json([
135
+ 'status' => 'ok',
136
+ 'vehicle' => $vehicle->fresh(['driver', 'devices']),
137
+ 'orders' => IndexOrderResource::collection($orders)->resolve(),
138
+ 'current' => $this->activeOrderUuid($vehicle),
139
+ 'count' => $orders->count(),
140
+ ]);
141
+ }
142
+
143
+ public function unassignOrders(Request $request, string $id): JsonResponse
144
+ {
145
+ $request->validate([
146
+ 'orders' => 'required|array|min:1',
147
+ 'orders.*' => 'required|string',
148
+ ]);
149
+
150
+ $vehicle = $this->findVehicle($id);
151
+ $ids = collect($request->input('orders'))->filter()->unique()->values();
152
+ $orders = Order::where('vehicle_assigned_uuid', $vehicle->uuid)
153
+ ->where('company_uuid', session('company'))
154
+ ->where(function ($query) use ($ids) {
155
+ $query->whereIn('uuid', $ids)->orWhereIn('public_id', $ids);
156
+ })
157
+ ->get();
158
+
159
+ if ($orders->isEmpty()) {
160
+ return response()->error('No assigned orders were selected for this vehicle.');
161
+ }
162
+
163
+ DB::transaction(function () use ($orders): void {
164
+ Order::whereIn('uuid', $orders->pluck('uuid'))->update([
165
+ 'vehicle_assigned_uuid' => null,
166
+ 'updated_at' => now(),
167
+ ]);
168
+ });
169
+
170
+ return response()->json([
171
+ 'status' => 'ok',
172
+ 'message' => 'Vehicle unassigned from selected orders.',
173
+ 'vehicle' => $vehicle->fresh(['driver', 'devices']),
174
+ 'orders' => IndexOrderResource::collection($orders->fresh(['driverAssigned', 'vehicleAssigned']))->resolve(),
175
+ 'count' => $orders->count(),
176
+ ]);
177
+ }
178
+
179
+ public function attachDevice(Request $request, string $id): JsonResponse
180
+ {
181
+ $request->validate(['device' => 'required|string']);
182
+
183
+ $deviceId = $request->input('device');
184
+ $vehicle = $this->resolveVehicle($id);
185
+ $device = $this->resolveDevice($deviceId);
186
+
187
+ if (!$vehicle) {
188
+ $this->logDeviceAttachmentLookupFailure('attach-device', 'vehicle', $id, $deviceId);
189
+
190
+ return response()->error('Vehicle not found or not available for this organization.', 404);
191
+ }
192
+
193
+ if (!$device) {
194
+ $this->logDeviceAttachmentLookupFailure('attach-device', 'device', $id, $deviceId);
195
+
196
+ return response()->error('Device not found or not available for this organization.', 404);
197
+ }
198
+
199
+ try {
200
+ $device->attachTo($vehicle);
201
+ $device->load(['telematic', 'warranty', 'attachable']);
202
+ $vehicle->load(['driver', 'devices']);
203
+ } catch (\Throwable $e) {
204
+ $this->logDeviceAttachmentFailure('attach-device', $vehicle, $device, $e);
205
+
206
+ return response()->error('Unable to attach device to vehicle. Please try again or contact support.', 500);
207
+ }
208
+
209
+ return response()->json([
210
+ 'status' => 'ok',
211
+ 'message' => 'Device attached to vehicle.',
212
+ 'device' => $device,
213
+ 'vehicle' => $vehicle,
214
+ ]);
215
+ }
216
+
217
+ public function detachDevice(Request $request, string $id): JsonResponse
218
+ {
219
+ $request->validate(['device' => 'required|string']);
220
+
221
+ $deviceId = $request->input('device');
222
+ $vehicle = $this->resolveVehicle($id);
223
+ $device = $this->resolveDevice($deviceId);
224
+
225
+ if (!$vehicle) {
226
+ $this->logDeviceAttachmentLookupFailure('detach-device', 'vehicle', $id, $deviceId);
227
+
228
+ return response()->error('Vehicle not found or not available for this organization.', 404);
229
+ }
230
+
231
+ if (!$device) {
232
+ $this->logDeviceAttachmentLookupFailure('detach-device', 'device', $id, $deviceId);
233
+
234
+ return response()->error('Device not found or not available for this organization.', 404);
235
+ }
236
+
237
+ if ($device->attachable_uuid !== $vehicle->uuid) {
238
+ return response()->error('This device is not attached to the selected vehicle.');
239
+ }
240
+
241
+ try {
242
+ $device->detach();
243
+ $device->load(['telematic', 'warranty', 'attachable']);
244
+ $vehicle->load(['driver', 'devices']);
245
+ } catch (\Throwable $e) {
246
+ $this->logDeviceAttachmentFailure('detach-device', $vehicle, $device, $e);
247
+
248
+ return response()->error('Unable to detach device from vehicle. Please try again or contact support.', 500);
249
+ }
250
+
251
+ return response()->json([
252
+ 'status' => 'ok',
253
+ 'message' => 'Device detached from vehicle.',
254
+ 'device' => $device,
255
+ 'vehicle' => $vehicle,
256
+ ]);
257
+ }
258
+
259
+ protected function findVehicle(string $id): Vehicle
260
+ {
261
+ return Vehicle::where(function ($query) use ($id) {
262
+ $query->where('uuid', $id)->orWhere('public_id', $id);
263
+ })
264
+ ->where('company_uuid', session('company'))
265
+ ->firstOrFail();
266
+ }
267
+
268
+ protected function resolveVehicle(string $id): ?Vehicle
269
+ {
270
+ return Vehicle::where(function ($query) use ($id) {
271
+ $query->where('uuid', $id)->orWhere('public_id', $id);
272
+ })
273
+ ->where('company_uuid', session('company'))
274
+ ->first();
275
+ }
276
+
277
+ protected function findDriver(string $id): Driver
278
+ {
279
+ return Driver::where(function ($query) use ($id) {
280
+ $query->where('uuid', $id)->orWhere('public_id', $id);
281
+ })
282
+ ->where('company_uuid', session('company'))
283
+ ->firstOrFail();
284
+ }
285
+
286
+ protected function resolveDevice(string $id): ?Device
287
+ {
288
+ return Device::where(function ($query) use ($id) {
289
+ $query->where('uuid', $id)->orWhere('public_id', $id);
290
+ })
291
+ ->where('company_uuid', session('company'))
292
+ ->first();
293
+ }
294
+
295
+ protected function logDeviceAttachmentLookupFailure(string $action, string $missingResource, string $vehicleId, ?string $deviceId): void
296
+ {
297
+ Log::warning('Vehicle device attachment lookup failed', [
298
+ 'action' => $action,
299
+ 'missing_resource' => $missingResource,
300
+ 'vehicle_id' => $vehicleId,
301
+ 'device_id' => $deviceId,
302
+ 'company_uuid' => session('company'),
303
+ 'request_id' => request()->headers->get('X-Request-ID') ?? request()->headers->get('X-Correlation-ID'),
304
+ ]);
305
+ }
306
+
307
+ protected function logDeviceAttachmentFailure(string $action, Vehicle $vehicle, Device $device, \Throwable $exception): void
308
+ {
309
+ Log::error('Vehicle device attachment failed', [
310
+ 'action' => $action,
311
+ 'vehicle_uuid' => $vehicle->uuid,
312
+ 'vehicle_id' => $vehicle->public_id,
313
+ 'device_uuid' => $device->uuid,
314
+ 'device_id' => $device->public_id,
315
+ 'company_uuid' => session('company'),
316
+ 'request_id' => request()->headers->get('X-Request-ID') ?? request()->headers->get('X-Correlation-ID'),
317
+ 'exception_class' => get_class($exception),
318
+ 'exception' => $exception->getMessage(),
319
+ ]);
320
+ }
321
+
322
+ protected function activeOrderUuid(Vehicle $vehicle): ?string
323
+ {
324
+ $vehicle->loadMissing('driver.currentOrder');
325
+
326
+ return data_get($vehicle, 'driver.currentOrder.uuid') ?? data_get($vehicle->lastKnownPosition(), 'order_uuid');
327
+ }
328
+
329
+ protected function findDevice(string $id): Device
330
+ {
331
+ return Device::where(function ($query) use ($id) {
332
+ $query->where('uuid', $id)->orWhere('public_id', $id);
333
+ })
334
+ ->where('company_uuid', session('company'))
335
+ ->firstOrFail();
336
+ }
337
+
89
338
  /**
90
339
  * Get all status options for an vehicle.
91
340
  *