@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
@@ -0,0 +1,167 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\FuelProviders\Providers;
4
+
5
+ use Fleetbase\FleetOps\Models\FuelProviderConnection;
6
+ use Illuminate\Support\Carbon;
7
+ use Illuminate\Support\Collection;
8
+
9
+ class PetroAppFuelProvider extends AbstractFuelProvider
10
+ {
11
+ public function key(): string
12
+ {
13
+ return 'petroapp';
14
+ }
15
+
16
+ public function name(): string
17
+ {
18
+ return 'PetroApp';
19
+ }
20
+
21
+ public function testConnection(FuelProviderConnection $connection): array
22
+ {
23
+ try {
24
+ $response = $this->client($connection)->get('vehicles', ['page' => 1]);
25
+
26
+ if (!$response->successful()) {
27
+ return [
28
+ 'success' => false,
29
+ 'message' => $response->json('message') ?? 'Unable to connect to PetroApp.',
30
+ 'metadata' => ['status' => $response->status()],
31
+ ];
32
+ }
33
+
34
+ return [
35
+ 'success' => true,
36
+ 'message' => 'PetroApp connection successful.',
37
+ 'metadata' => [
38
+ 'total_vehicles' => $response->json('data.total'),
39
+ ],
40
+ ];
41
+ } catch (\Throwable $e) {
42
+ return ['success' => false, 'message' => $e->getMessage(), 'metadata' => []];
43
+ }
44
+ }
45
+
46
+ public function listVehicles(FuelProviderConnection $connection, array $options = []): Collection
47
+ {
48
+ return $this->paginatedGet($connection, 'vehicles', $options);
49
+ }
50
+
51
+ public function listStations(FuelProviderConnection $connection, array $options = []): Collection
52
+ {
53
+ return $this->paginatedGet($connection, 'petroapp_locations', $options, 'locs');
54
+ }
55
+
56
+ public function listTransactions(FuelProviderConnection $connection, Carbon $from, Carbon $to, array $options = []): Collection
57
+ {
58
+ $params = array_merge($options, [
59
+ 'from' => $from->toDateString(),
60
+ 'to' => $to->toDateString(),
61
+ 'lang' => data_get($options, 'lang', 'en'),
62
+ ]);
63
+
64
+ return $this->paginatedGet($connection, 'bills', $params)
65
+ ->map(fn ($payload) => $this->normalizeBill($payload));
66
+ }
67
+
68
+ protected function baseUrl(FuelProviderConnection $connection): string
69
+ {
70
+ $credentials = (array) $connection->credentials;
71
+
72
+ return rtrim(data_get($credentials, 'base_url', 'https://app-public.staging.petroapp.app/webservice'), '/');
73
+ }
74
+
75
+ protected function headers(FuelProviderConnection $connection): array
76
+ {
77
+ $credentials = (array) $connection->credentials;
78
+ $token = data_get($credentials, 'api_token') ?? data_get($credentials, 'api_key');
79
+
80
+ if (data_get($credentials, 'auth_type') === 'ws_sk_header') {
81
+ return [
82
+ 'WS-Version' => data_get($credentials, 'version', 'v2.0'),
83
+ 'WS-SK' => $token,
84
+ ];
85
+ }
86
+
87
+ return [
88
+ 'WS-Version' => data_get($credentials, 'version', 'v2.0'),
89
+ 'Authorization' => 'Bearer ' . $token,
90
+ ];
91
+ }
92
+
93
+ protected function paginatedGet(FuelProviderConnection $connection, string $endpoint, array $params = [], ?string $dataKey = null): Collection
94
+ {
95
+ $page = (int) data_get($params, 'page', 1);
96
+ $maxPages = (int) data_get($params, 'max_pages', 100);
97
+ unset($params['max_pages']);
98
+
99
+ $items = collect();
100
+
101
+ do {
102
+ $response = $this->client($connection)->get($endpoint, array_merge($params, ['page' => $page]));
103
+
104
+ if (!$response->successful()) {
105
+ throw new \RuntimeException($response->json('message') ?? "PetroApp {$endpoint} request failed.");
106
+ }
107
+
108
+ $payload = $response->json();
109
+ $data = $dataKey ? data_get($payload, $dataKey, []) : data_get($payload, 'data.data', []);
110
+ $items = $items->merge($data);
111
+
112
+ $lastPage = (int) data_get($payload, 'data.last_page', $page);
113
+ $hasMore = data_get($payload, 'data.next_page_url') || $page < $lastPage;
114
+ $page++;
115
+ } while ($hasMore && $page <= $maxPages);
116
+
117
+ return $items;
118
+ }
119
+
120
+ protected function normalizeBill(array $bill): array
121
+ {
122
+ $transactionAt = $this->dateFrom($bill['bill_date'] ?? $bill['billDate'] ?? null);
123
+ $providerId = $bill['id'] ?? $bill['bill_id'] ?? null;
124
+ $fallbackId = $this->transactionHash([
125
+ $bill['bill_date'] ?? $bill['billDate'] ?? null,
126
+ $bill['trip_number'] ?? null,
127
+ $bill['internal_number'] ?? null,
128
+ $bill['structure_number'] ?? null,
129
+ $bill['plate_snum'] ?? null,
130
+ $bill['station_name'] ?? null,
131
+ $bill['num_of_liters'] ?? null,
132
+ $bill['cost'] ?? null,
133
+ ]);
134
+
135
+ return [
136
+ 'provider' => $this->key(),
137
+ 'provider_transaction_id' => (string) ($providerId ?: $fallbackId),
138
+ 'provider_vehicle_id' => $this->compactIdentifier($bill['vehicle_id'] ?? null),
139
+ 'vehicle_card_id' => $this->compactIdentifier($bill['vehicle_card_id'] ?? $bill['fuel_card_number'] ?? $bill['card_number'] ?? null),
140
+ 'internal_number' => $this->compactIdentifier($bill['internal_number'] ?? null),
141
+ 'structure_number' => $this->compactIdentifier($bill['structure_number'] ?? null),
142
+ 'plate_number' => $this->compactIdentifier($bill['plate_snum'] ?? null),
143
+ 'vin' => $this->compactIdentifier($bill['vin'] ?? $bill['vin_number'] ?? null),
144
+ 'serial_number' => $this->compactIdentifier($bill['serial_number'] ?? null),
145
+ 'call_sign' => $this->compactIdentifier($bill['call_sign'] ?? null),
146
+ 'trip_number' => $this->compactIdentifier($bill['trip_number'] ?? null),
147
+ 'station_name' => $this->compactIdentifier($bill['station_name'] ?? null),
148
+ 'station_latitude' => $bill['station_lat'] ?? null,
149
+ 'station_longitude' => $bill['station_lng'] ?? null,
150
+ 'transaction_at' => $transactionAt,
151
+ 'volume' => $bill['num_of_liters'] ?? null,
152
+ 'metric_unit' => 'l',
153
+ 'amount' => $this->minorCurrencyUnit($bill['cost'] ?? null),
154
+ 'currency' => 'SAR',
155
+ 'odometer' => $bill['odometer'] ?? null,
156
+ 'normalized_payload' => [
157
+ 'payment_method' => $bill['payment_method'] ?? null,
158
+ 'payment_method_text' => $bill['payment_method_text'] ?? null,
159
+ 'branch_name' => $bill['branch_name'] ?? null,
160
+ 'city' => $bill['city'] ?? null,
161
+ 'district' => $bill['district'] ?? null,
162
+ 'delegate_name' => $bill['delegate_name'] ?? null,
163
+ ],
164
+ 'raw_payload' => $bill,
165
+ ];
166
+ }
167
+ }
@@ -20,7 +20,7 @@ use Illuminate\Support\Str;
20
20
  */
21
21
  abstract class AbstractProvider implements TelematicProviderInterface
22
22
  {
23
- protected Telematic $telematic;
23
+ protected ?Telematic $telematic = null;
24
24
  protected array $credentials = [];
25
25
  protected array $headers = [];
26
26
  protected string $baseUrl = '';
@@ -33,7 +33,7 @@ abstract class AbstractProvider implements TelematicProviderInterface
33
33
  public function connect(Telematic $telematic): void
34
34
  {
35
35
  $this->telematic = $telematic;
36
- $this->credentials = json_decode(Crypt::decryptString($telematic->credentials), true);
36
+ $this->credentials = $this->resolveCredentials($telematic);
37
37
  $this->prepareAuthentication();
38
38
  }
39
39
 
@@ -72,10 +72,9 @@ abstract class AbstractProvider implements TelematicProviderInterface
72
72
  Log::error('Provider API request failed', [
73
73
  'correlation_id' => $correlationId,
74
74
  'status' => $response->status(),
75
- 'body' => $response->body(),
76
75
  ]);
77
76
 
78
- throw new \Exception('API request failed: ' . $response->body());
77
+ throw new \Exception('Provider API request failed with status ' . $response->status());
79
78
  }
80
79
 
81
80
  return $response->json();
@@ -88,7 +87,7 @@ abstract class AbstractProvider implements TelematicProviderInterface
88
87
  */
89
88
  protected function checkRateLimit(): void
90
89
  {
91
- $key = 'rate_limit:' . class_basename($this) . ':' . $this->telematic->uuid;
90
+ $key = $this->rateLimitKey();
92
91
  $tokens = Cache::get($key, $this->burstSize);
93
92
 
94
93
  if ($tokens <= 0) {
@@ -103,7 +102,7 @@ abstract class AbstractProvider implements TelematicProviderInterface
103
102
  */
104
103
  protected function recordRequest(): void
105
104
  {
106
- $key = 'rate_limit:' . class_basename($this) . ':' . $this->telematic->uuid;
105
+ $key = $this->rateLimitKey();
107
106
  $tokens = Cache::get($key, 0);
108
107
 
109
108
  // Refill tokens gradually
@@ -112,6 +111,28 @@ abstract class AbstractProvider implements TelematicProviderInterface
112
111
  }
113
112
  }
114
113
 
114
+ protected function rateLimitKey(): string
115
+ {
116
+ return 'rate_limit:' . class_basename($this) . ':' . ($this->telematic?->uuid ?? 'credential-test');
117
+ }
118
+
119
+ protected function resolveCredentials(Telematic $telematic): array
120
+ {
121
+ if (is_array($telematic->credentials)) {
122
+ return $telematic->credentials;
123
+ }
124
+
125
+ if (!$telematic->credentials) {
126
+ return [];
127
+ }
128
+
129
+ try {
130
+ return json_decode(Crypt::decryptString($telematic->credentials), true) ?? [];
131
+ } catch (\Throwable) {
132
+ return json_decode($telematic->credentials, true) ?? [];
133
+ }
134
+ }
135
+
115
136
  public function supportsWebhooks(): bool
116
137
  {
117
138
  return false;
@@ -23,8 +23,9 @@ class AfaqyProvider extends AbstractProvider
23
23
 
24
24
  $this->credentials['token'] = $token;
25
25
  $this->headers = [
26
- 'Accept' => 'application/json',
27
- 'Content-Type' => 'application/json',
26
+ 'Accept' => 'application/json',
27
+ 'Content-Type' => 'application/json',
28
+ 'Authorization' => 'Bearer ' . $token,
28
29
  ];
29
30
  }
30
31
 
@@ -78,7 +79,6 @@ class AfaqyProvider extends AbstractProvider
78
79
  'last_update',
79
80
  'counters',
80
81
  'profile',
81
- 'sensors',
82
82
  'sensors_last_val',
83
83
  ],
84
84
  ],
@@ -110,26 +110,33 @@ class AfaqyProvider extends AbstractProvider
110
110
  public function normalizeDevice(array $payload): array
111
111
  {
112
112
  $lastUpdate = $payload['last_update'] ?? [];
113
+ $profile = $payload['profile'] ?? [];
113
114
 
114
115
  return [
115
- 'external_id' => $payload['_id'] ?? $payload['id'] ?? null,
116
- 'device_name' => $payload['name'] ?? data_get($payload, 'profile.plate_number') ?? 'Unknown Unit',
117
- 'device_provider' => 'afaqy',
118
- 'device_model' => data_get($payload, 'profile.model') ?? $payload['device'] ?? null,
119
- 'imei' => $payload['imei'] ?? null,
120
- 'phone' => $payload['sim_number'] ?? null,
121
- 'vin' => data_get($payload, 'profile.vin'),
122
- 'status' => ($payload['active'] ?? false) ? 'active' : 'inactive',
123
- 'location' => [
116
+ 'device_id' => $payload['_id'] ?? $payload['id'] ?? null,
117
+ 'name' => $payload['name'] ?? data_get($payload, 'profile.plate_number') ?? 'Unknown Unit',
118
+ 'provider' => 'afaqy',
119
+ 'model' => data_get($payload, 'profile.model') ?? $payload['device'] ?? null,
120
+ 'imei' => $payload['imei'] ?? null,
121
+ 'phone' => $payload['sim_number'] ?? null,
122
+ 'vin' => data_get($payload, 'profile.vin'),
123
+ 'status' => ($payload['active'] ?? false) ? 'active' : 'inactive',
124
+ 'online' => $payload['active'] ?? null,
125
+ 'last_seen_at' => $this->parseTimestamp($lastUpdate['dtt'] ?? $lastUpdate['dts'] ?? null),
126
+ 'location' => [
124
127
  'lat' => $lastUpdate['lat'] ?? null,
125
128
  'lng' => $lastUpdate['lng'] ?? null,
126
129
  ],
127
130
  'meta' => [
128
- 'raw' => $payload,
129
- 'plate_number' => data_get($payload, 'profile.plate_number'),
130
- 'driver_id' => $payload['driver_id'] ?? null,
131
- 'last_update' => $this->normalizeEvent($payload),
132
- 'capabilities' => [
131
+ 'provider_unit_id' => $payload['_id'] ?? $payload['id'] ?? null,
132
+ 'plate_number' => data_get($profile, 'plate_number'),
133
+ 'vehicle_type' => data_get($profile, 'vehicle_type'),
134
+ 'fuel_type' => data_get($profile, 'fuel_type'),
135
+ 'device_serial' => $payload['device_serial'] ?? null,
136
+ 'sim_number' => $payload['sim_number'] ?? null,
137
+ 'driver_id' => $payload['driver_id'] ?? null,
138
+ 'last_update' => $this->compactLastUpdate($lastUpdate),
139
+ 'capabilities' => [
133
140
  'tracking' => isset($lastUpdate['lat'], $lastUpdate['lng']),
134
141
  'odometer' => data_get($payload, 'counters.odometer') !== null,
135
142
  'fuel_level' => $this->extractFuelLevel($payload) !== null,
@@ -145,7 +152,9 @@ class AfaqyProvider extends AbstractProvider
145
152
 
146
153
  return [
147
154
  'external_id' => $payload['_id'] ?? $payload['id'] ?? null,
155
+ 'device_id' => $payload['_id'] ?? $payload['id'] ?? null,
148
156
  'event_type' => $payload['event'] ?? $payload['event_type'] ?? 'telemetry_update',
157
+ 'message' => $payload['message'] ?? $payload['event'] ?? null,
149
158
  'occurred_at' => $this->parseTimestamp($lastUpdate['dtt'] ?? $lastUpdate['dts'] ?? null),
150
159
  'location' => [
151
160
  'lat' => $lastUpdate['lat'] ?? data_get($lastUpdate, 'loc.coordinates.1'),
@@ -175,12 +184,16 @@ class AfaqyProvider extends AbstractProvider
175
184
  {
176
185
  return [
177
186
  [
178
- 'name' => 'base_url',
179
- 'label' => 'Base URL',
180
- 'type' => 'text',
181
- 'placeholder' => 'https://api.afaqy.sa',
182
- 'required' => false,
183
- 'validation' => 'nullable|url',
187
+ 'name' => 'base_url',
188
+ 'label' => 'Base URL',
189
+ 'type' => 'text',
190
+ 'placeholder' => 'https://api.afaqy.sa',
191
+ 'required' => false,
192
+ 'advanced' => true,
193
+ 'is_endpoint' => true,
194
+ 'default_value' => 'https://api.afaqy.sa',
195
+ 'help_text' => 'Optional override. Leave blank to use the default AFAQY API host.',
196
+ 'validation' => 'nullable|url',
184
197
  ],
185
198
  [
186
199
  'name' => 'username',
@@ -227,7 +240,7 @@ class AfaqyProvider extends AbstractProvider
227
240
  ]);
228
241
 
229
242
  if ($response->failed()) {
230
- throw new \RuntimeException('AFAQY authentication failed: ' . $response->body());
243
+ throw new \RuntimeException('AFAQY authentication failed with status ' . $response->status());
231
244
  }
232
245
 
233
246
  $token = data_get($response->json(), 'data.token');
@@ -243,15 +256,31 @@ class AfaqyProvider extends AbstractProvider
243
256
  {
244
257
  $response = Http::withHeaders($this->headers)
245
258
  ->timeout(30)
246
- ->post($this->baseUrl . $endpoint . '?token=' . urlencode($this->credentials['token']), $payload);
259
+ ->post($this->baseUrl . $endpoint, array_merge(['token' => $this->credentials['token']], $payload));
247
260
 
248
261
  if ($response->failed()) {
249
- throw new \RuntimeException('AFAQY API request failed: ' . $response->body());
262
+ throw new \RuntimeException('AFAQY API request failed with status ' . $response->status());
250
263
  }
251
264
 
252
265
  return $response->json() ?? [];
253
266
  }
254
267
 
268
+ protected function compactLastUpdate(array $lastUpdate): array
269
+ {
270
+ $params = $lastUpdate['params'] ?? $lastUpdate['prms'] ?? [];
271
+
272
+ return array_filter([
273
+ 'occurred_at' => $this->parseTimestamp($lastUpdate['dtt'] ?? $lastUpdate['dts'] ?? null),
274
+ 'lat' => $lastUpdate['lat'] ?? null,
275
+ 'lng' => $lastUpdate['lng'] ?? null,
276
+ 'speed' => $lastUpdate['speed'] ?? $lastUpdate['spd'] ?? null,
277
+ 'heading' => $lastUpdate['angle'] ?? $lastUpdate['ang'] ?? null,
278
+ 'altitude' => $lastUpdate['alt'] ?? null,
279
+ 'satellites' => $params['sat'] ?? null,
280
+ 'protocol' => $params['protocol'] ?? null,
281
+ ], fn ($value) => $value !== null);
282
+ }
283
+
255
284
  protected function parseTimestamp($value): ?string
256
285
  {
257
286
  if (!$value) {
@@ -97,6 +97,7 @@ class FlespiProvider extends AbstractProvider
97
97
  {
98
98
  return [
99
99
  'external_id' => $payload['id'] ?? null,
100
+ 'device_id' => $payload['device.id'] ?? null,
100
101
  'event_type' => $payload['event.enum'] ?? 'telemetry_update',
101
102
  'occurred_at' => isset($payload['timestamp']) ? date('Y-m-d H:i:s', $payload['timestamp']) : now(),
102
103
  'location' => [
@@ -131,6 +131,7 @@ class GeotabProvider extends AbstractProvider
131
131
  {
132
132
  return [
133
133
  'external_id' => $payload['id'] ?? null,
134
+ 'device_id' => $payload['device']['id'] ?? $payload['deviceId'] ?? null,
134
135
  'event_type' => $payload['type'] ?? 'status_data',
135
136
  'occurred_at' => $payload['dateTime'] ?? now(),
136
137
  'meta' => $payload,
@@ -13,13 +13,13 @@ use Illuminate\Support\Facades\Http;
13
13
  */
14
14
  class SafeeProvider extends AbstractProvider
15
15
  {
16
- protected string $baseUrl = '';
16
+ protected string $baseUrl = 'https://api.safee.com';
17
17
  protected int $requestsPerMinute = 3000;
18
18
  protected ?string $accessToken = null;
19
19
 
20
20
  protected function prepareAuthentication(): void
21
21
  {
22
- $this->baseUrl = rtrim($this->credentials['api_base_url'] ?? $this->credentials['server_uri'] ?? '', '/');
22
+ $this->baseUrl = rtrim($this->credentials['api_base_url'] ?? $this->credentials['server_uri'] ?? $this->baseUrl, '/');
23
23
  $this->accessToken = $this->credentials['access_token'] ?? $this->authenticate();
24
24
  $scheme = $this->credentials['authorization_scheme'] ?? 'Bearer';
25
25
 
@@ -90,17 +90,21 @@ class SafeeProvider extends AbstractProvider
90
90
 
91
91
  public function normalizeDevice(array $payload): array
92
92
  {
93
- $position = $this->extractPosition($payload);
93
+ $position = $this->extractPosition($payload);
94
+ $rawStatus = $payload['status'] ?? $payload['vehicleStatus'] ?? null;
95
+ $status = $this->normalizeVehicleStatus($rawStatus);
94
96
 
95
97
  return [
96
- 'external_id' => $payload['id'] ?? data_get($payload, 'vehicle.id') ?? null,
97
- 'device_name' => $payload['name'] ?? $payload['plateNumber'] ?? data_get($payload, 'vehicle.name') ?? 'Unknown Vehicle',
98
- 'device_provider' => 'safee',
99
- 'device_model' => $payload['model'] ?? data_get($payload, 'device.model') ?? null,
100
- 'imei' => data_get($payload, 'device.imei') ?? data_get($payload, 'device.serial') ?? null,
101
- 'vin' => $payload['vin'] ?? null,
102
- 'status' => $this->normalizeVehicleStatus($payload['status'] ?? $payload['vehicleStatus'] ?? null),
103
- 'location' => [
98
+ 'device_id' => $payload['id'] ?? data_get($payload, 'vehicle.id') ?? null,
99
+ 'name' => $payload['name'] ?? $payload['plateNumber'] ?? data_get($payload, 'vehicle.name') ?? 'Unknown Vehicle',
100
+ 'provider' => 'safee',
101
+ 'model' => $payload['model'] ?? data_get($payload, 'device.model') ?? null,
102
+ 'imei' => data_get($payload, 'device.imei') ?? data_get($payload, 'device.serial') ?? null,
103
+ 'vin' => $payload['vin'] ?? null,
104
+ 'status' => $status,
105
+ 'online' => $rawStatus ? $status === 'active' : null,
106
+ 'last_seen_at' => $this->parseTimestamp($payload['date'] ?? $payload['deviceTime'] ?? $payload['time'] ?? null),
107
+ 'location' => [
104
108
  'lat' => $position['lat'] ?? null,
105
109
  'lng' => $position['lng'] ?? null,
106
110
  ],
@@ -126,7 +130,9 @@ class SafeeProvider extends AbstractProvider
126
130
 
127
131
  return [
128
132
  'external_id' => $payload['id'] ?? data_get($payload, 'vehicle.id') ?? null,
133
+ 'device_id' => $payload['id'] ?? data_get($payload, 'vehicle.id') ?? null,
129
134
  'event_type' => data_get($payload, 'event.code') ?? data_get($payload, 'event.name') ?? 'telemetry_update',
135
+ 'message' => data_get($payload, 'event.name') ?? data_get($payload, 'event.message') ?? null,
130
136
  'occurred_at' => $this->parseTimestamp($payload['date'] ?? $payload['deviceTime'] ?? $payload['time'] ?? null),
131
137
  'location' => [
132
138
  'lat' => $position['lat'] ?? null,
@@ -156,12 +162,16 @@ class SafeeProvider extends AbstractProvider
156
162
  {
157
163
  return [
158
164
  [
159
- 'name' => 'server_uri',
160
- 'label' => 'Server URI',
161
- 'type' => 'text',
162
- 'placeholder' => 'https://tracking.example.com',
163
- 'required' => true,
164
- 'validation' => 'required|url',
165
+ 'name' => 'server_uri',
166
+ 'label' => 'Server URI',
167
+ 'type' => 'text',
168
+ 'placeholder' => 'https://api.safee.com',
169
+ 'required' => false,
170
+ 'advanced' => true,
171
+ 'is_endpoint' => true,
172
+ 'default_value' => 'https://api.safee.com',
173
+ 'help_text' => 'Optional override. Leave blank to use the default Safee API host.',
174
+ 'validation' => 'nullable|url',
165
175
  ],
166
176
  [
167
177
  'name' => 'realm_id',
@@ -203,13 +213,13 @@ class SafeeProvider extends AbstractProvider
203
213
 
204
214
  protected function authenticate(): string
205
215
  {
206
- foreach (['server_uri', 'realm_id', 'client_id', 'client_secret', 'username', 'password'] as $field) {
216
+ foreach (['realm_id', 'client_id', 'client_secret', 'username', 'password'] as $field) {
207
217
  if (empty($this->credentials[$field])) {
208
218
  throw new \InvalidArgumentException("Safee credential '{$field}' is required.");
209
219
  }
210
220
  }
211
221
 
212
- $tokenUrl = rtrim($this->credentials['server_uri'], '/') . '/auth/realms/' . $this->credentials['realm_id'] . '/protocol/openid-connect/token';
222
+ $tokenUrl = $this->baseUrl . '/auth/realms/' . $this->credentials['realm_id'] . '/protocol/openid-connect/token';
213
223
 
214
224
  $response = Http::asForm()
215
225
  ->acceptJson()
@@ -223,7 +233,7 @@ class SafeeProvider extends AbstractProvider
223
233
  ]);
224
234
 
225
235
  if ($response->failed()) {
226
- throw new \RuntimeException('Safee authentication failed: ' . $response->body());
236
+ throw new \RuntimeException('Safee authentication failed with status ' . $response->status());
227
237
  }
228
238
 
229
239
  $token = $response->json('access_token');
@@ -242,7 +252,7 @@ class SafeeProvider extends AbstractProvider
242
252
  ->get($this->baseUrl . $endpoint);
243
253
 
244
254
  if ($response->failed()) {
245
- throw new \RuntimeException('Safee API request failed: ' . $response->body());
255
+ throw new \RuntimeException('Safee API request failed with status ' . $response->status());
246
256
  }
247
257
 
248
258
  return $response->json() ?? [];
@@ -255,7 +265,7 @@ class SafeeProvider extends AbstractProvider
255
265
  ->post($this->baseUrl . $endpoint, $payload);
256
266
 
257
267
  if ($response->failed()) {
258
- throw new \RuntimeException('Safee API request failed: ' . $response->body());
268
+ throw new \RuntimeException('Safee API request failed with status ' . $response->status());
259
269
  }
260
270
 
261
271
  return $response->json() ?? [];
@@ -94,6 +94,7 @@ class SamsaraProvider extends AbstractProvider
94
94
  {
95
95
  return [
96
96
  'external_id' => $payload['id'] ?? null,
97
+ 'device_id' => $payload['vehicle']['id'] ?? $payload['vehicleId'] ?? null,
97
98
  'event_type' => $payload['eventType'] ?? 'vehicle_update',
98
99
  'occurred_at' => $payload['time'] ?? now(),
99
100
  'location' => [
@@ -135,7 +136,8 @@ class SamsaraProvider extends AbstractProvider
135
136
  if (isset($payload['data'])) {
136
137
  foreach ($payload['data'] as $item) {
137
138
  if (isset($item['vehicle'])) {
138
- $devices[] = $this->normalizeDevice($item['vehicle']);
139
+ $devices[] = $this->normalizeDevice($item['vehicle']);
140
+ $item['vehicleId'] = $item['vehicle']['id'] ?? null;
139
141
  }
140
142
 
141
143
  $events[] = $this->normalizeEvent($item);