@fleetbase/fleetops-engine 0.6.51 → 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 (360) 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.js +20 -2
  20. package/addon/components/map/drawer/device-event-listing.hbs +12 -2
  21. package/addon/components/map/drawer/driver-listing.hbs +14 -1
  22. package/addon/components/map/drawer/place-listing.hbs +14 -1
  23. package/addon/components/map/drawer/position-listing.hbs +12 -2
  24. package/addon/components/map/drawer/vehicle-listing.hbs +14 -1
  25. package/addon/components/modals/assigned-orders.hbs +40 -0
  26. package/addon/components/modals/attach-device.hbs +4 -3
  27. package/addon/components/modals/attach-telematic-device.hbs +25 -0
  28. package/addon/components/modals/attach-telematic-device.js +3 -0
  29. package/addon/components/modals/driver-assign-order.hbs +7 -58
  30. package/addon/components/modals/driver-assign-vehicle.hbs +10 -2
  31. package/addon/components/modals/fleet-assign-driver.hbs +18 -0
  32. package/addon/components/modals/fleet-assign-driver.js +3 -0
  33. package/addon/components/modals/fleet-assign-vehicle.hbs +18 -0
  34. package/addon/components/modals/fleet-assign-vehicle.js +3 -0
  35. package/addon/components/modals/telematic-connection-diagnostics.hbs +75 -0
  36. package/addon/components/modals/telematic-connection-diagnostics.js +287 -0
  37. package/addon/components/modals/vehicle-details.hbs +6 -1
  38. package/addon/components/modals/vehicle-form.hbs +8 -1
  39. package/addon/components/order/card.hbs +106 -0
  40. package/addon/components/order/card.js +257 -0
  41. package/addon/components/order/form/service-rate.hbs +2 -0
  42. package/addon/components/service-rate/form.hbs +3 -2
  43. package/addon/components/telematic/details.hbs +98 -87
  44. package/addon/components/telematic/details.js +230 -1
  45. package/addon/components/telematic/form.hbs +303 -72
  46. package/addon/components/telematic/form.js +519 -3
  47. package/addon/components/telematic/hub.hbs +149 -0
  48. package/addon/components/telematic/hub.js +142 -0
  49. package/addon/components/telematic/settings.hbs +152 -0
  50. package/addon/components/telematic/settings.js +45 -0
  51. package/addon/components/telematic/tab-empty-state.hbs +29 -0
  52. package/addon/components/telematic/tab-empty-state.js +33 -0
  53. package/addon/components/vehicle/details.hbs +6 -1
  54. package/addon/components/vehicle/form.hbs +2 -1
  55. package/addon/components/widget/fuel-providers.hbs +43 -0
  56. package/addon/components/widget/fuel-providers.js +27 -0
  57. package/addon/controllers/connectivity/devices/index/details.js +27 -1
  58. package/addon/controllers/connectivity/devices/index.js +14 -0
  59. package/addon/controllers/connectivity/fuel-providers/details/sync.js +18 -0
  60. package/addon/controllers/connectivity/fuel-providers/details/transactions.js +24 -0
  61. package/addon/controllers/connectivity/fuel-providers/details.js +69 -0
  62. package/addon/controllers/connectivity/fuel-providers/edit.js +24 -0
  63. package/addon/controllers/connectivity/fuel-providers/index.js +177 -0
  64. package/addon/controllers/connectivity/fuel-providers/new.js +37 -0
  65. package/addon/controllers/connectivity/telematics/details/attachments.js +247 -0
  66. package/addon/controllers/connectivity/telematics/details/devices.js +368 -0
  67. package/addon/controllers/connectivity/telematics/details/events.js +244 -0
  68. package/addon/controllers/connectivity/telematics/details/index.js +3 -0
  69. package/addon/controllers/connectivity/telematics/details/logs.js +43 -0
  70. package/addon/controllers/connectivity/telematics/details/sensors.js +270 -0
  71. package/addon/controllers/connectivity/telematics/details.js +149 -0
  72. package/addon/controllers/connectivity/telematics/{index/edit.js → edit.js} +15 -4
  73. package/addon/controllers/connectivity/telematics/index.js +25 -5
  74. package/addon/controllers/connectivity/telematics/{index/new.js → new.js} +8 -2
  75. package/addon/controllers/management/drivers/index/details.js +78 -1
  76. package/addon/controllers/management/drivers/index.js +39 -0
  77. package/addon/controllers/management/fleets/index/details.js +23 -1
  78. package/addon/controllers/management/fleets/index.js +6 -1
  79. package/addon/controllers/management/fuel-reports/index.js +37 -1
  80. package/addon/controllers/management/fuel-transactions/index/details.js +70 -0
  81. package/addon/controllers/management/fuel-transactions/index.js +240 -0
  82. package/addon/controllers/management/vehicles/index/details.js +32 -0
  83. package/addon/controllers/management/vehicles/index.js +26 -0
  84. package/addon/controllers/operations/orders/index.js +4 -0
  85. package/addon/controllers/settings/payments/index.js +5 -0
  86. package/addon/extension.js +10 -0
  87. package/addon/routes/connectivity/fuel-providers/details/index.js +7 -0
  88. package/addon/routes/connectivity/fuel-providers/details/matching.js +7 -0
  89. package/addon/routes/connectivity/fuel-providers/details/settings.js +7 -0
  90. package/addon/routes/connectivity/fuel-providers/details/sync.js +18 -0
  91. package/addon/routes/connectivity/fuel-providers/details/transactions.js +14 -0
  92. package/addon/routes/connectivity/fuel-providers/details.js +18 -0
  93. package/addon/routes/connectivity/fuel-providers/edit.js +18 -0
  94. package/addon/routes/connectivity/fuel-providers/index.js +20 -0
  95. package/addon/routes/connectivity/fuel-providers/new.js +3 -0
  96. package/addon/routes/connectivity/fuel-providers.js +3 -0
  97. package/addon/routes/connectivity/telematics/details/attachments.js +28 -0
  98. package/addon/routes/connectivity/telematics/details/devices.js +30 -0
  99. package/addon/routes/connectivity/telematics/details/events.js +33 -0
  100. package/addon/routes/connectivity/telematics/details/index.js +3 -0
  101. package/addon/routes/connectivity/telematics/details/logs.js +17 -0
  102. package/addon/routes/connectivity/telematics/details/sensors.js +29 -0
  103. package/addon/routes/connectivity/telematics/{index/details.js → details.js} +1 -1
  104. package/addon/routes/connectivity/telematics/{index/edit.js → edit.js} +1 -1
  105. package/addon/routes/connectivity/telematics/new.js +3 -0
  106. package/addon/routes/management/drivers/index/details/activity.js +3 -0
  107. package/addon/routes/management/fuel-reports/index.js +2 -0
  108. package/addon/routes/management/fuel-transactions/index/details.js +18 -0
  109. package/addon/routes/management/fuel-transactions/index.js +21 -0
  110. package/addon/routes/management/fuel-transactions.js +3 -0
  111. package/addon/routes.js +28 -9
  112. package/addon/services/device-actions.js +64 -1
  113. package/addon/services/device-event-actions.js +12 -1
  114. package/addon/services/driver-actions.js +147 -11
  115. package/addon/services/fleet-actions.js +66 -1
  116. package/addon/services/fuel-integration-actions.js +31 -0
  117. package/addon/services/issue-actions.js +1 -1
  118. package/addon/services/telematic-actions.js +9 -3
  119. package/addon/services/vehicle-actions.js +125 -0
  120. package/addon/styles/fleetops-engine.css +310 -34
  121. package/addon/templates/analytics/reports/index.hbs +19 -1
  122. package/addon/templates/connectivity/devices/index.hbs +19 -1
  123. package/addon/templates/connectivity/events/index.hbs +16 -1
  124. package/addon/templates/connectivity/fuel-providers/details/index.hbs +38 -0
  125. package/addon/templates/connectivity/fuel-providers/details/matching.hbs +13 -0
  126. package/addon/templates/connectivity/fuel-providers/details/settings.hbs +11 -0
  127. package/addon/templates/connectivity/fuel-providers/details/sync.hbs +48 -0
  128. package/addon/templates/connectivity/fuel-providers/details/transactions.hbs +21 -0
  129. package/addon/templates/connectivity/fuel-providers/details.hbs +51 -0
  130. package/addon/templates/connectivity/fuel-providers/edit.hbs +24 -0
  131. package/addon/templates/connectivity/fuel-providers/index.hbs +12 -0
  132. package/addon/templates/connectivity/fuel-providers/new.hbs +22 -0
  133. package/addon/templates/connectivity/fuel-providers.hbs +1 -0
  134. package/addon/templates/connectivity/sensors/index.hbs +19 -1
  135. package/addon/templates/connectivity/telematics/details/attachments.hbs +163 -0
  136. package/addon/templates/connectivity/telematics/details/devices.hbs +88 -0
  137. package/addon/templates/connectivity/telematics/details/events.hbs +101 -0
  138. package/addon/templates/connectivity/telematics/{index/details → details}/index.hbs +0 -1
  139. package/addon/templates/connectivity/telematics/details/logs.hbs +69 -0
  140. package/addon/templates/connectivity/telematics/details/sensors.hbs +88 -0
  141. package/addon/templates/connectivity/telematics/details.hbs +53 -0
  142. package/addon/templates/connectivity/telematics/edit.hbs +27 -0
  143. package/addon/templates/connectivity/telematics/index.hbs +6 -8
  144. package/addon/templates/connectivity/telematics/new.hbs +24 -0
  145. package/addon/templates/maintenance/equipment/index.hbs +19 -1
  146. package/addon/templates/maintenance/maintenances/index.hbs +15 -1
  147. package/addon/templates/maintenance/parts/index.hbs +19 -1
  148. package/addon/templates/maintenance/schedules/index.hbs +19 -1
  149. package/addon/templates/maintenance/work-orders/index.hbs +19 -1
  150. package/addon/templates/management/contacts/customers.hbs +15 -1
  151. package/addon/templates/management/contacts/index.hbs +19 -1
  152. package/addon/templates/management/drivers/index/details/activity.hbs +1 -0
  153. package/addon/templates/management/drivers/index.hbs +19 -1
  154. package/addon/templates/management/fleets/index.hbs +19 -1
  155. package/addon/templates/management/fuel-reports/index.hbs +19 -1
  156. package/addon/templates/management/fuel-transactions/index/details.hbs +73 -0
  157. package/addon/templates/management/fuel-transactions/index.hbs +58 -0
  158. package/addon/templates/management/fuel-transactions.hbs +1 -0
  159. package/addon/templates/management/issues/index.hbs +19 -1
  160. package/addon/templates/management/places/index.hbs +19 -1
  161. package/addon/templates/management/vehicles/index.hbs +19 -1
  162. package/addon/templates/management/vendors/index.hbs +19 -1
  163. package/addon/templates/management/vendors/integrated.hbs +13 -1
  164. package/addon/templates/operations/orders/index.hbs +19 -1
  165. package/addon/templates/operations/service-rates/index.hbs +19 -1
  166. package/addon/templates/settings/payments/index.hbs +15 -1
  167. package/addon/utils/fleet-ops-options.js +3 -0
  168. package/app/components/cell/telematic-provider.js +1 -0
  169. package/app/components/cell/telematic-status.js +1 -0
  170. package/app/components/fuel-integration/form.js +1 -0
  171. package/app/components/fuel-integration/hub.js +1 -0
  172. package/app/components/fuel-integration/matching-priority.js +1 -0
  173. package/app/components/modals/assigned-orders.js +1 -0
  174. package/app/components/modals/attach-telematic-device.js +1 -0
  175. package/app/components/modals/fleet-assign-driver.js +1 -0
  176. package/app/components/modals/fleet-assign-vehicle.js +1 -0
  177. package/app/components/modals/telematic-connection-diagnostics.js +1 -0
  178. package/app/components/order/card.js +1 -0
  179. package/app/components/telematic/hub.js +1 -0
  180. package/app/components/telematic/settings.js +1 -0
  181. package/app/components/telematic/tab-empty-state.js +1 -0
  182. package/app/components/widget/fuel-providers.js +1 -0
  183. package/app/controllers/connectivity/fuel-providers/details/sync.js +1 -0
  184. package/app/controllers/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  185. package/app/controllers/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  186. package/app/controllers/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  187. package/app/controllers/connectivity/{telematics/index/details → fuel-providers}/index.js +1 -1
  188. package/app/controllers/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  189. package/app/controllers/connectivity/telematics/details/attachments.js +1 -0
  190. package/app/controllers/connectivity/telematics/details/devices.js +1 -0
  191. package/app/controllers/connectivity/telematics/details/events.js +1 -0
  192. package/app/controllers/connectivity/telematics/details/index.js +1 -0
  193. package/app/controllers/connectivity/telematics/details/logs.js +1 -0
  194. package/app/controllers/connectivity/telematics/details/sensors.js +1 -0
  195. package/app/controllers/connectivity/telematics/details.js +1 -0
  196. package/app/controllers/connectivity/telematics/edit.js +1 -0
  197. package/app/controllers/connectivity/telematics/new.js +1 -0
  198. package/app/controllers/management/fuel-transactions/index/details.js +1 -0
  199. package/app/controllers/management/fuel-transactions/index.js +1 -0
  200. package/app/routes/connectivity/{telematics/index → fuel-providers}/details/index.js +1 -1
  201. package/app/routes/connectivity/{telematics/index/details/events.js → fuel-providers/details/matching.js} +1 -1
  202. package/app/routes/connectivity/fuel-providers/details/settings.js +1 -0
  203. package/app/routes/connectivity/fuel-providers/details/sync.js +1 -0
  204. package/app/routes/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  205. package/app/routes/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  206. package/app/routes/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  207. package/app/routes/connectivity/fuel-providers/index.js +1 -0
  208. package/app/routes/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  209. package/app/routes/connectivity/fuel-providers.js +1 -0
  210. package/app/routes/connectivity/telematics/details/attachments.js +1 -0
  211. package/app/routes/connectivity/telematics/details/devices.js +1 -0
  212. package/app/routes/connectivity/telematics/details/events.js +1 -0
  213. package/app/routes/connectivity/telematics/details/index.js +1 -0
  214. package/app/routes/connectivity/telematics/details/logs.js +1 -0
  215. package/app/routes/connectivity/telematics/details/sensors.js +1 -0
  216. package/app/routes/connectivity/telematics/details.js +1 -0
  217. package/app/routes/connectivity/telematics/edit.js +1 -0
  218. package/app/routes/connectivity/telematics/new.js +1 -0
  219. package/app/routes/management/drivers/index/details/activity.js +1 -0
  220. package/app/routes/management/fuel-transactions/index/details.js +1 -0
  221. package/app/routes/management/fuel-transactions/index.js +1 -0
  222. package/app/routes/management/fuel-transactions.js +1 -0
  223. package/app/services/fuel-integration-actions.js +1 -0
  224. package/app/templates/connectivity/{telematics/index → fuel-providers}/details/index.js +1 -1
  225. package/app/templates/connectivity/{telematics/index/details/events.js → fuel-providers/details/matching.js} +1 -1
  226. package/app/templates/connectivity/fuel-providers/details/settings.js +1 -0
  227. package/app/templates/connectivity/fuel-providers/details/sync.js +1 -0
  228. package/app/templates/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
  229. package/app/templates/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
  230. package/app/templates/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
  231. package/app/templates/connectivity/fuel-providers/index.js +1 -0
  232. package/app/templates/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
  233. package/app/templates/connectivity/fuel-providers.js +1 -0
  234. package/app/templates/connectivity/telematics/details/attachments.js +1 -0
  235. package/app/templates/connectivity/telematics/details/devices.js +1 -0
  236. package/app/templates/connectivity/telematics/details/events.js +1 -0
  237. package/app/templates/connectivity/telematics/details/index.js +1 -0
  238. package/app/templates/connectivity/telematics/details/logs.js +1 -0
  239. package/app/templates/connectivity/telematics/details/sensors.js +1 -0
  240. package/app/templates/connectivity/telematics/details.js +1 -0
  241. package/app/templates/connectivity/telematics/edit.js +1 -0
  242. package/app/templates/connectivity/telematics/new.js +1 -0
  243. package/app/templates/management/drivers/index/details/activity.js +1 -0
  244. package/app/templates/management/fuel-transactions/index/details.js +1 -0
  245. package/app/templates/management/fuel-transactions/index.js +1 -0
  246. package/app/templates/management/fuel-transactions.js +1 -0
  247. package/assets/images/telematics/providers/afaqy.webp +0 -0
  248. package/assets/images/telematics/providers/default.webp +0 -0
  249. package/assets/images/telematics/providers/flespi.webp +0 -0
  250. package/assets/images/telematics/providers/geotab.webp +0 -0
  251. package/assets/images/telematics/providers/safee.webp +0 -0
  252. package/assets/images/telematics/providers/samsara.webp +0 -0
  253. package/composer.json +1 -1
  254. package/extension.json +1 -1
  255. package/package.json +4 -4
  256. package/server/config/fuel-providers.php +42 -0
  257. package/server/config/telematics.php +139 -130
  258. package/server/migrations/2026_06_06_000001_harden_device_events_telematics_contract.php +59 -0
  259. package/server/migrations/2026_06_08_000001_create_fuel_provider_connections_table.php +37 -0
  260. package/server/migrations/2026_06_08_000002_create_fuel_provider_transactions_table.php +53 -0
  261. package/server/migrations/2026_06_08_000003_create_fuel_provider_sync_runs_table.php +40 -0
  262. package/server/migrations/2026_06_10_000001_add_fuel_card_number_to_vehicles_table.php +25 -0
  263. package/server/migrations/2026_06_10_000002_add_vehicle_identity_columns_to_fuel_provider_transactions_table.php +39 -0
  264. package/server/src/Contracts/FuelProvider.php +30 -0
  265. package/server/src/Contracts/TelematicProviderDescriptor.php +3 -1
  266. package/server/src/Events/FuelProviderTransactionImported.php +17 -0
  267. package/server/src/Events/FuelProviderTransactionMatched.php +17 -0
  268. package/server/src/Events/FuelProviderTransactionUnmatched.php +17 -0
  269. package/server/src/Events/FuelReportCreatedFromProvider.php +18 -0
  270. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +6 -0
  271. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +76 -2
  272. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +44 -0
  273. package/server/src/Http/Controllers/Internal/v1/DriverController.php +158 -7
  274. package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +124 -0
  275. package/server/src/Http/Controllers/Internal/v1/FuelProviderSyncRunController.php +10 -0
  276. package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +76 -0
  277. package/server/src/Http/Controllers/Internal/v1/SensorController.php +1 -1
  278. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +178 -15
  279. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +165 -0
  280. package/server/src/Http/Controllers/TelematicWebhookController.php +103 -19
  281. package/server/src/Http/Filter/FuelProviderConnectionFilter.php +33 -0
  282. package/server/src/Http/Filter/FuelProviderSyncRunFilter.php +28 -0
  283. package/server/src/Http/Filter/FuelProviderTransactionFilter.php +59 -0
  284. package/server/src/Http/Filter/FuelReportFilter.php +10 -0
  285. package/server/src/Http/Resources/v1/Driver.php +15 -0
  286. package/server/src/Http/Resources/v1/FuelProviderConnection.php +30 -0
  287. package/server/src/Http/Resources/v1/FuelProviderSyncRun.php +36 -0
  288. package/server/src/Http/Resources/v1/FuelProviderTransaction.php +54 -0
  289. package/server/src/Http/Resources/v1/FuelReport.php +28 -22
  290. package/server/src/Http/Resources/v1/Index/Driver.php +26 -20
  291. package/server/src/Http/Resources/v1/Index/Vehicle.php +29 -22
  292. package/server/src/Http/Resources/v1/Vehicle.php +17 -0
  293. package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +2 -0
  294. package/server/src/Jobs/SyncFuelProviderTransactionsJob.php +57 -0
  295. package/server/src/Jobs/SyncTelematicDevicesJob.php +36 -6
  296. package/server/src/Jobs/TestTelematicConnectionJob.php +9 -22
  297. package/server/src/Models/Asset.php +2 -1
  298. package/server/src/Models/Contact.php +2 -1
  299. package/server/src/Models/Device.php +27 -4
  300. package/server/src/Models/DeviceEvent.php +40 -3
  301. package/server/src/Models/Driver.php +2 -1
  302. package/server/src/Models/Equipment.php +2 -1
  303. package/server/src/Models/Fleet.php +2 -1
  304. package/server/src/Models/FuelProviderConnection.php +60 -0
  305. package/server/src/Models/FuelProviderSyncRun.php +58 -0
  306. package/server/src/Models/FuelProviderTransaction.php +119 -0
  307. package/server/src/Models/FuelReport.php +16 -1
  308. package/server/src/Models/Part.php +2 -1
  309. package/server/src/Models/Sensor.php +21 -4
  310. package/server/src/Models/ServiceRate.php +39 -2
  311. package/server/src/Models/Vehicle.php +14 -2
  312. package/server/src/Models/Vendor.php +2 -1
  313. package/server/src/Providers/FleetOpsServiceProvider.php +10 -0
  314. package/server/src/Support/Analytics/FuelProviderSummary.php +42 -0
  315. package/server/src/Support/FuelProviders/FuelProviderDescriptor.php +55 -0
  316. package/server/src/Support/FuelProviders/FuelProviderRegistry.php +75 -0
  317. package/server/src/Support/FuelProviders/FuelProviderService.php +447 -0
  318. package/server/src/Support/FuelProviders/Providers/AbstractFuelProvider.php +98 -0
  319. package/server/src/Support/FuelProviders/Providers/PetroAppFuelProvider.php +167 -0
  320. package/server/src/Support/Telematics/Providers/AbstractProvider.php +27 -6
  321. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +55 -26
  322. package/server/src/Support/Telematics/Providers/FlespiProvider.php +1 -0
  323. package/server/src/Support/Telematics/Providers/GeotabProvider.php +1 -0
  324. package/server/src/Support/Telematics/Providers/SafeeProvider.php +32 -22
  325. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +3 -1
  326. package/server/src/Support/Telematics/TelematicService.php +340 -21
  327. package/server/src/routes.php +41 -4
  328. package/server/tests/FuelProviderRegistryTest.php +416 -0
  329. package/server/tests/ServiceRateAlgorithmWeightVariablesTest.php +44 -0
  330. package/server/tests/TelematicsHardeningTest.php +268 -0
  331. package/server/tests/VehicleDriverAssignmentTest.php +51 -0
  332. package/translations/ar-ae.yml +22 -0
  333. package/translations/bg-bg.yaml +72 -0
  334. package/translations/en-us.yaml +83 -0
  335. package/translations/es-pa.yaml +72 -0
  336. package/translations/fr-fr.yaml +72 -0
  337. package/translations/mn-mn.yaml +72 -0
  338. package/translations/pt-br.yaml +72 -0
  339. package/translations/ru-ru.yaml +72 -0
  340. package/translations/vi-vn.yaml +97 -0
  341. package/addon/controllers/connectivity/telematics/index/details/devices.js +0 -3
  342. package/addon/controllers/connectivity/telematics/index/details/events.js +0 -3
  343. package/addon/controllers/connectivity/telematics/index/details/index.js +0 -3
  344. package/addon/controllers/connectivity/telematics/index/details/sensors.js +0 -3
  345. package/addon/controllers/connectivity/telematics/index/details.js +0 -24
  346. package/addon/routes/connectivity/telematics/index/details/devices.js +0 -3
  347. package/addon/routes/connectivity/telematics/index/details/events.js +0 -3
  348. package/addon/routes/connectivity/telematics/index/details/index.js +0 -3
  349. package/addon/routes/connectivity/telematics/index/details/sensors.js +0 -3
  350. package/addon/routes/connectivity/telematics/index/new.js +0 -3
  351. package/addon/templates/connectivity/telematics/index/details/devices.hbs +0 -1
  352. package/addon/templates/connectivity/telematics/index/details/events.hbs +0 -1
  353. package/addon/templates/connectivity/telematics/index/details/sensors.hbs +0 -1
  354. package/addon/templates/connectivity/telematics/index/details.hbs +0 -14
  355. package/addon/templates/connectivity/telematics/index/edit.hbs +0 -12
  356. package/addon/templates/connectivity/telematics/index/new.hbs +0 -12
  357. package/app/controllers/connectivity/telematics/index/details/events.js +0 -1
  358. package/app/controllers/connectivity/telematics/index/details/sensors.js +0 -1
  359. package/app/routes/connectivity/telematics/index/details/sensors.js +0 -1
  360. 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,9 +107,7 @@ 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
113
  'limit' => $request->input('limit', 100),
@@ -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,11 +4,15 @@ 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;
@@ -86,6 +90,167 @@ class VehicleController extends FleetOpsController
86
90
  }
87
91
  }
88
92
 
93
+ public function assignDriver(Request $request, string $id): JsonResponse
94
+ {
95
+ $request->validate(['driver' => 'required|string']);
96
+
97
+ $vehicle = $this->findVehicle($id);
98
+ $driver = $this->findDriver($request->input('driver'));
99
+
100
+ $vehicle->assignDriver($driver);
101
+ $vehicle->load(['driver', 'devices']);
102
+
103
+ return response()->json([
104
+ 'status' => 'ok',
105
+ 'message' => 'Driver assigned to vehicle.',
106
+ 'vehicle' => $vehicle,
107
+ ]);
108
+ }
109
+
110
+ public function unassignDriver(string $id): JsonResponse
111
+ {
112
+ $vehicle = $this->findVehicle($id);
113
+ $vehicle->unassignDriver();
114
+ $vehicle->load(['driver', 'devices']);
115
+
116
+ return response()->json([
117
+ 'status' => 'ok',
118
+ 'message' => 'Driver unassigned from vehicle.',
119
+ 'vehicle' => $vehicle,
120
+ ]);
121
+ }
122
+
123
+ public function assignedOrders(string $id): JsonResponse
124
+ {
125
+ $vehicle = $this->findVehicle($id);
126
+ $orders = Order::where('vehicle_assigned_uuid', $vehicle->uuid)
127
+ ->where('company_uuid', session('company'))
128
+ ->with(['payload', 'trackingNumber', 'orderConfig', 'driverAssigned', 'vehicleAssigned'])
129
+ ->orderByRaw('uuid = ? desc', [$this->activeOrderUuid($vehicle)])
130
+ ->latest()
131
+ ->get();
132
+
133
+ return response()->json([
134
+ 'status' => 'ok',
135
+ 'vehicle' => $vehicle->fresh(['driver', 'devices']),
136
+ 'orders' => IndexOrderResource::collection($orders)->resolve(),
137
+ 'current' => $this->activeOrderUuid($vehicle),
138
+ 'count' => $orders->count(),
139
+ ]);
140
+ }
141
+
142
+ public function unassignOrders(Request $request, string $id): JsonResponse
143
+ {
144
+ $request->validate([
145
+ 'orders' => 'required|array|min:1',
146
+ 'orders.*' => 'required|string',
147
+ ]);
148
+
149
+ $vehicle = $this->findVehicle($id);
150
+ $ids = collect($request->input('orders'))->filter()->unique()->values();
151
+ $orders = Order::where('vehicle_assigned_uuid', $vehicle->uuid)
152
+ ->where('company_uuid', session('company'))
153
+ ->where(function ($query) use ($ids) {
154
+ $query->whereIn('uuid', $ids)->orWhereIn('public_id', $ids);
155
+ })
156
+ ->get();
157
+
158
+ if ($orders->isEmpty()) {
159
+ return response()->error('No assigned orders were selected for this vehicle.');
160
+ }
161
+
162
+ DB::transaction(function () use ($orders): void {
163
+ Order::whereIn('uuid', $orders->pluck('uuid'))->update([
164
+ 'vehicle_assigned_uuid' => null,
165
+ 'updated_at' => now(),
166
+ ]);
167
+ });
168
+
169
+ return response()->json([
170
+ 'status' => 'ok',
171
+ 'message' => 'Vehicle unassigned from selected orders.',
172
+ 'vehicle' => $vehicle->fresh(['driver', 'devices']),
173
+ 'orders' => IndexOrderResource::collection($orders->fresh(['driverAssigned', 'vehicleAssigned']))->resolve(),
174
+ 'count' => $orders->count(),
175
+ ]);
176
+ }
177
+
178
+ public function attachDevice(Request $request, string $id): JsonResponse
179
+ {
180
+ $request->validate(['device' => 'required|string']);
181
+
182
+ $vehicle = $this->findVehicle($id);
183
+ $device = $this->findDevice($request->input('device'));
184
+
185
+ $device->attachTo($vehicle);
186
+ $device->load(['telematic', 'warranty', 'attachable']);
187
+ $vehicle->load(['driver', 'devices']);
188
+
189
+ return response()->json([
190
+ 'status' => 'ok',
191
+ 'message' => 'Device attached to vehicle.',
192
+ 'device' => $device,
193
+ 'vehicle' => $vehicle,
194
+ ]);
195
+ }
196
+
197
+ public function detachDevice(Request $request, string $id): JsonResponse
198
+ {
199
+ $request->validate(['device' => 'required|string']);
200
+
201
+ $vehicle = $this->findVehicle($id);
202
+ $device = $this->findDevice($request->input('device'));
203
+
204
+ if ($device->attachable_uuid !== $vehicle->uuid) {
205
+ return response()->error('This device is not attached to the selected vehicle.');
206
+ }
207
+
208
+ $device->detach();
209
+ $device->load(['telematic', 'warranty', 'attachable']);
210
+ $vehicle->load(['driver', 'devices']);
211
+
212
+ return response()->json([
213
+ 'status' => 'ok',
214
+ 'message' => 'Device detached from vehicle.',
215
+ 'device' => $device,
216
+ 'vehicle' => $vehicle,
217
+ ]);
218
+ }
219
+
220
+ protected function findVehicle(string $id): Vehicle
221
+ {
222
+ return Vehicle::where(function ($query) use ($id) {
223
+ $query->where('uuid', $id)->orWhere('public_id', $id);
224
+ })
225
+ ->where('company_uuid', session('company'))
226
+ ->firstOrFail();
227
+ }
228
+
229
+ protected function findDriver(string $id): Driver
230
+ {
231
+ return Driver::where(function ($query) use ($id) {
232
+ $query->where('uuid', $id)->orWhere('public_id', $id);
233
+ })
234
+ ->where('company_uuid', session('company'))
235
+ ->firstOrFail();
236
+ }
237
+
238
+ protected function activeOrderUuid(Vehicle $vehicle): ?string
239
+ {
240
+ $vehicle->loadMissing('driver.currentOrder');
241
+
242
+ return data_get($vehicle, 'driver.currentOrder.uuid') ?? data_get($vehicle->lastKnownPosition(), 'order_uuid');
243
+ }
244
+
245
+ protected function findDevice(string $id): Device
246
+ {
247
+ return Device::where(function ($query) use ($id) {
248
+ $query->where('uuid', $id)->orWhere('public_id', $id);
249
+ })
250
+ ->where('company_uuid', session('company'))
251
+ ->firstOrFail();
252
+ }
253
+
89
254
  /**
90
255
  * Get all status options for an vehicle.
91
256
  *
@@ -41,9 +41,10 @@ class TelematicWebhookController extends Controller
41
41
  $correlationId = Str::uuid()->toString();
42
42
 
43
43
  Log::info('Webhook received', [
44
- 'correlation_id' => $correlationId,
45
- 'provider' => $providerKey,
46
- 'headers' => $request->headers->all(),
44
+ 'correlation_id' => $correlationId,
45
+ 'provider' => $providerKey,
46
+ 'has_signature' => $request->headers->has('X-Webhook-Signature'),
47
+ 'has_idempotency_key' => $request->headers->has('X-Idempotency-Key'),
47
48
  ]);
48
49
 
49
50
  // Check idempotency
@@ -60,20 +61,42 @@ class TelematicWebhookController extends Controller
60
61
  // Get provider
61
62
  $provider = $this->registry->resolve($providerKey);
62
63
 
63
- // Find telematic for this provider
64
- $telematic = Telematic::where('provider', $providerKey)->first();
64
+ // Find telematic for this provider. Native provider payloads are
65
+ // resolved by provider identifiers first; Fleetbase URL/header ids are
66
+ // optional configuration affordances where provider dashboards allow it.
67
+ $telematicId = $request->query('telematic') ?? $request->query('integration') ?? $request->header('X-Fleetbase-Telematic');
68
+ $signature = $request->header('X-Webhook-Signature');
69
+ $telematic = $this->service->resolveWebhookTelematic($providerKey, $request->all(), $request->headers->all(), $telematicId);
70
+
71
+ if (!$telematic && !$telematicId && $signature) {
72
+ $signatureMatches = Telematic::where('provider', $providerKey)
73
+ ->get()
74
+ ->filter(fn (Telematic $candidate) => $provider->validateWebhookSignature($request->getContent(), $signature, $this->service->getCredentials($candidate)));
75
+
76
+ if ($signatureMatches->count() === 1) {
77
+ $telematic = $signatureMatches->first();
78
+ } elseif ($signatureMatches->count() > 1) {
79
+ Log::warning('Ambiguous telematic webhook signature match', [
80
+ 'correlation_id' => $correlationId,
81
+ 'provider' => $providerKey,
82
+ 'matches' => $signatureMatches->count(),
83
+ ]);
84
+
85
+ return response()->json(['error' => 'Ambiguous telematic integration'], 409);
86
+ }
87
+ }
65
88
 
66
89
  if (!$telematic) {
67
- Log::warning('No telematic found for provider', [
68
- 'correlation_id' => $correlationId,
69
- 'provider' => $providerKey,
90
+ Log::warning('Unable to resolve telematic for provider webhook', [
91
+ 'correlation_id' => $correlationId,
92
+ 'provider' => $providerKey,
93
+ 'has_integration_id' => (bool) $telematicId,
70
94
  ]);
71
95
 
72
- return response()->json(['error' => 'No telematic configured'], 404);
96
+ return response()->json(['error' => 'Unable to resolve telematic integration'], 422);
73
97
  }
74
98
 
75
99
  // Validate signature
76
- $signature = $request->header('X-Webhook-Signature');
77
100
  $credentials = $this->service->getCredentials($telematic);
78
101
 
79
102
  if ($signature && !$provider->validateWebhookSignature($request->getContent(), $signature, $credentials)) {
@@ -87,15 +110,45 @@ class TelematicWebhookController extends Controller
87
110
 
88
111
  // Process webhook
89
112
  try {
90
- $result = $provider->processWebhook($request->all(), $request->headers->all());
113
+ $result = $provider->processWebhook($request->all(), $request->headers->all());
114
+ $devices = $result['devices'] ?? [];
115
+ $events = $result['events'] ?? [];
116
+ $sensors = $result['sensors'] ?? [];
117
+
118
+ $devicesByExternalId = [];
91
119
 
92
120
  // Link devices
93
- foreach ($result['devices'] as $deviceData) {
94
- $this->service->linkDevice($telematic, $deviceData);
121
+ foreach ($devices as $deviceData) {
122
+ try {
123
+ $device = $this->service->linkDevice($telematic, $deviceData);
124
+ $externalId = $deviceData['external_id'] ?? $deviceData['device_id'] ?? $deviceData['unit_id'] ?? $deviceData['vehicle_id'] ?? $deviceData['imei'] ?? null;
125
+ if ($externalId) {
126
+ $devicesByExternalId[$externalId] = $device;
127
+ }
128
+ } catch (\Illuminate\Validation\ValidationException) {
129
+ Log::warning('Skipping webhook device without provider identity', [
130
+ 'correlation_id' => $correlationId,
131
+ 'provider' => $providerKey,
132
+ ]);
133
+ }
95
134
  }
96
135
 
97
- // Store events (TODO: implement event storage)
98
- // Store sensors (TODO: implement sensor storage)
136
+ foreach ($events as $eventData) {
137
+ $externalId = $eventData['device_id'] ?? $eventData['external_id'] ?? $eventData['ident'] ?? $eventData['unit_id'] ?? $eventData['vehicle_id'] ?? $eventData['imei'] ?? null;
138
+ $this->service->storeDeviceEvent($telematic, $eventData, $externalId ? ($devicesByExternalId[$externalId] ?? null) : null);
139
+ }
140
+
141
+ foreach ($sensors as $sensorData) {
142
+ try {
143
+ $externalId = $sensorData['device_id'] ?? $sensorData['external_id'] ?? $sensorData['ident'] ?? $sensorData['unit_id'] ?? $sensorData['vehicle_id'] ?? $sensorData['imei'] ?? null;
144
+ $this->service->storeSensor($telematic, $sensorData, $externalId ? ($devicesByExternalId[$externalId] ?? null) : null);
145
+ } catch (\Illuminate\Validation\ValidationException) {
146
+ Log::warning('Skipping webhook sensor without provider or device identity', [
147
+ 'correlation_id' => $correlationId,
148
+ 'provider' => $providerKey,
149
+ ]);
150
+ }
151
+ }
99
152
 
100
153
  // Mark as processed
101
154
  if ($idempotencyKey) {
@@ -104,8 +157,9 @@ class TelematicWebhookController extends Controller
104
157
 
105
158
  Log::info('Webhook processed successfully', [
106
159
  'correlation_id' => $correlationId,
107
- 'devices_count' => count($result['devices']),
108
- 'events_count' => count($result['events']),
160
+ 'devices_count' => count($devices),
161
+ 'events_count' => count($events),
162
+ 'sensors_count' => count($sensors),
109
163
  ]);
110
164
 
111
165
  return response()->json(['status' => 'processed'], 200);
@@ -124,7 +178,7 @@ class TelematicWebhookController extends Controller
124
178
  */
125
179
  public function ingest(Request $request, string $id): JsonResponse
126
180
  {
127
- $telematic = Telematic::where('uuid', $id)->orWhere('public_id', $id)->firstOrFail();
181
+ $telematic = Telematic::where('public_id', $id)->orWhere('uuid', $id)->firstOrFail();
128
182
  $correlationId = Str::uuid()->toString();
129
183
 
130
184
  Log::info('Custom ingest received', [
@@ -139,10 +193,38 @@ class TelematicWebhookController extends Controller
139
193
  }
140
194
 
141
195
  try {
196
+ $devicesByExternalId = [];
197
+
142
198
  // Process devices
143
199
  if ($request->has('devices')) {
144
200
  foreach ($request->input('devices') as $deviceData) {
145
- $this->service->linkDevice($telematic, $deviceData);
201
+ try {
202
+ $device = $this->service->linkDevice($telematic, $deviceData);
203
+ $externalId = $deviceData['external_id'] ?? $deviceData['device_id'] ?? $deviceData['unit_id'] ?? $deviceData['vehicle_id'] ?? $deviceData['imei'] ?? null;
204
+ if ($externalId) {
205
+ $devicesByExternalId[$externalId] = $device;
206
+ }
207
+ } catch (\Illuminate\Validation\ValidationException) {
208
+ Log::warning('Skipping custom ingest device without provider identity', [
209
+ 'correlation_id' => $correlationId,
210
+ ]);
211
+ }
212
+ }
213
+ }
214
+
215
+ foreach ($request->input('events', []) as $eventData) {
216
+ $externalId = $eventData['device_id'] ?? $eventData['external_id'] ?? $eventData['ident'] ?? $eventData['unit_id'] ?? $eventData['vehicle_id'] ?? $eventData['imei'] ?? null;
217
+ $this->service->storeDeviceEvent($telematic, $eventData, $externalId ? ($devicesByExternalId[$externalId] ?? null) : null);
218
+ }
219
+
220
+ foreach ($request->input('sensors', []) as $sensorData) {
221
+ try {
222
+ $externalId = $sensorData['device_id'] ?? $sensorData['external_id'] ?? $sensorData['ident'] ?? $sensorData['unit_id'] ?? $sensorData['vehicle_id'] ?? $sensorData['imei'] ?? null;
223
+ $this->service->storeSensor($telematic, $sensorData, $externalId ? ($devicesByExternalId[$externalId] ?? null) : null);
224
+ } catch (\Illuminate\Validation\ValidationException) {
225
+ Log::warning('Skipping custom ingest sensor without provider or device identity', [
226
+ 'correlation_id' => $correlationId,
227
+ ]);
146
228
  }
147
229
  }
148
230
 
@@ -154,6 +236,8 @@ class TelematicWebhookController extends Controller
154
236
  Log::info('Custom ingest processed', [
155
237
  'correlation_id' => $correlationId,
156
238
  'devices_count' => count($request->input('devices', [])),
239
+ 'events_count' => count($request->input('events', [])),
240
+ 'sensors_count' => count($request->input('sensors', [])),
157
241
  ]);
158
242
 
159
243
  return response()->json(['status' => 'ingested'], 200);
@@ -0,0 +1,33 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Filter;
4
+
5
+ use Fleetbase\Http\Filter\Filter;
6
+
7
+ class FuelProviderConnectionFilter extends Filter
8
+ {
9
+ public function queryForInternal()
10
+ {
11
+ $this->builder->where('company_uuid', $this->session->get('company'));
12
+ }
13
+
14
+ public function query(?string $searchQuery)
15
+ {
16
+ $this->builder->search($searchQuery);
17
+ }
18
+
19
+ public function provider(?string $provider)
20
+ {
21
+ $this->builder->where('provider', $provider);
22
+ }
23
+
24
+ public function status(?string $status)
25
+ {
26
+ $this->builder->where('status', $status);
27
+ }
28
+
29
+ public function environment(?string $environment)
30
+ {
31
+ $this->builder->where('environment', $environment);
32
+ }
33
+ }
@@ -0,0 +1,28 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Filter;
4
+
5
+ use Fleetbase\Http\Filter\Filter;
6
+
7
+ class FuelProviderSyncRunFilter extends Filter
8
+ {
9
+ public function queryForInternal()
10
+ {
11
+ $this->builder->where('company_uuid', $this->session->get('company'));
12
+ }
13
+
14
+ public function provider(?string $provider)
15
+ {
16
+ $this->builder->where('provider', $provider);
17
+ }
18
+
19
+ public function status(?string $status)
20
+ {
21
+ $this->builder->where('status', $status);
22
+ }
23
+
24
+ public function connection(?string $connection)
25
+ {
26
+ $this->builder->where('fuel_provider_connection_uuid', $connection);
27
+ }
28
+ }