@fleetbase/fleetops-engine 0.6.68 → 0.6.70

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 (294) hide show
  1. package/RELEASE.md +44 -9
  2. package/addon/components/admin/telematics-settings.hbs +143 -0
  3. package/addon/components/admin/telematics-settings.js +145 -0
  4. package/addon/components/contact/form.hbs +13 -2
  5. package/addon/components/customer/details.hbs +17 -17
  6. package/addon/components/customer/form.hbs +13 -2
  7. package/addon/components/customer/form.js +0 -56
  8. package/addon/components/driver/details.hbs +13 -19
  9. package/addon/components/driver/form.hbs +20 -35
  10. package/addon/components/driver/form.js +0 -58
  11. package/addon/components/fuel-integration/hub.hbs +1 -1
  12. package/addon/components/layout/fleet-ops-sidebar.js +37 -17
  13. package/addon/components/map/leaflet-live-map.hbs +4 -242
  14. package/addon/components/map/leaflet-live-map.js +2 -1
  15. package/addon/components/map/marker-card/driver.hbs +57 -0
  16. package/addon/components/map/marker-card/driver.js +3 -0
  17. package/addon/components/map/marker-card/vehicle.hbs +78 -0
  18. package/addon/components/map/marker-card/vehicle.js +3 -0
  19. package/addon/components/modals/driver-form.hbs +2 -2
  20. package/addon/components/modals/reset-profile-credentials.hbs +23 -0
  21. package/addon/components/modals/reset-profile-credentials.js +70 -0
  22. package/addon/components/profile-identity-hint.hbs +13 -0
  23. package/addon/components/profile-identity-hint.js +49 -0
  24. package/addon/components/telematic/hub.hbs +1 -1
  25. package/addon/components/widget/live-fleet.hbs +8 -40
  26. package/addon/controllers/analytics/reports/index.js +5 -0
  27. package/addon/controllers/connectivity/devices/index.js +4 -0
  28. package/addon/controllers/connectivity/fuel-providers/index.js +6 -4
  29. package/addon/controllers/connectivity/sensors/index.js +4 -0
  30. package/addon/controllers/connectivity/telematics/index.js +4 -0
  31. package/addon/controllers/maintenance/equipment/index.js +11 -4
  32. package/addon/controllers/maintenance/inspection-forms/index.js +2 -2
  33. package/addon/controllers/maintenance/inspection-submissions/index.js +8 -2
  34. package/addon/controllers/maintenance/maintenances/index.js +4 -0
  35. package/addon/controllers/maintenance/parts/index.js +11 -4
  36. package/addon/controllers/maintenance/schedules/index.js +17 -4
  37. package/addon/controllers/maintenance/work-orders/index.js +11 -4
  38. package/addon/controllers/management/contacts/index.js +4 -0
  39. package/addon/controllers/management/drivers/index.js +8 -0
  40. package/addon/controllers/management/fleets/index.js +4 -0
  41. package/addon/controllers/management/fuel-reports/index.js +4 -0
  42. package/addon/controllers/management/fuel-transactions/index.js +9 -7
  43. package/addon/controllers/management/issues/index.js +4 -0
  44. package/addon/controllers/management/places/index.js +4 -0
  45. package/addon/controllers/management/vehicles/index.js +4 -0
  46. package/addon/controllers/management/vendors/index.js +4 -0
  47. package/addon/controllers/operations/orders/index/details.js +30 -1
  48. package/addon/controllers/operations/orders/index.js +7 -0
  49. package/addon/controllers/operations/service-rates/index.js +3 -0
  50. package/addon/controllers/settings/index.js +13 -0
  51. package/addon/controllers/settings/telematics.js +113 -0
  52. package/addon/extension.js +42 -8
  53. package/addon/routes/analytics/index.js +14 -1
  54. package/addon/routes/analytics/reports/index/details.js +1 -1
  55. package/addon/routes/analytics/reports/index/edit.js +1 -1
  56. package/addon/routes/analytics/reports/index/new.js +14 -1
  57. package/addon/routes/analytics/reports.js +14 -1
  58. package/addon/routes/connectivity/devices/index/new.js +14 -1
  59. package/addon/routes/connectivity/devices.js +14 -1
  60. package/addon/routes/connectivity/events.js +14 -1
  61. package/addon/routes/connectivity/fuel-providers/details/transactions.js +12 -0
  62. package/addon/routes/connectivity/fuel-providers/details.js +9 -0
  63. package/addon/routes/connectivity/fuel-providers/edit.js +9 -0
  64. package/addon/routes/connectivity/fuel-providers/new.js +14 -1
  65. package/addon/routes/connectivity/fuel-providers.js +14 -1
  66. package/addon/routes/connectivity/sensors/index/new.js +14 -1
  67. package/addon/routes/connectivity/sensors.js +14 -1
  68. package/addon/routes/connectivity/telematics/new.js +14 -1
  69. package/addon/routes/connectivity/telematics.js +14 -1
  70. package/addon/routes/connectivity/tracking.js +14 -1
  71. package/addon/routes/maintenance/equipment/index/details.js +2 -2
  72. package/addon/routes/maintenance/equipment/index/edit.js +2 -2
  73. package/addon/routes/maintenance/equipment/index/new.js +14 -1
  74. package/addon/routes/maintenance/equipment.js +14 -1
  75. package/addon/routes/maintenance/index.js +14 -1
  76. package/addon/routes/maintenance/inspection-forms/index/details.js +10 -1
  77. package/addon/routes/maintenance/inspection-forms/index/edit.js +10 -1
  78. package/addon/routes/maintenance/inspection-forms/index/new.js +14 -1
  79. package/addon/routes/maintenance/inspection-forms.js +14 -1
  80. package/addon/routes/maintenance/inspection-submissions/index/details.js +10 -1
  81. package/addon/routes/maintenance/inspection-submissions/index/edit.js +10 -1
  82. package/addon/routes/maintenance/inspection-submissions/index/new.js +14 -1
  83. package/addon/routes/maintenance/inspection-submissions.js +14 -1
  84. package/addon/routes/maintenance/maintenances/index/details.js +2 -2
  85. package/addon/routes/maintenance/maintenances/index/edit.js +2 -2
  86. package/addon/routes/maintenance/maintenances/index/new.js +14 -1
  87. package/addon/routes/maintenance/maintenances.js +14 -1
  88. package/addon/routes/maintenance/parts/index/details.js +2 -2
  89. package/addon/routes/maintenance/parts/index/edit.js +2 -2
  90. package/addon/routes/maintenance/parts/index/new.js +14 -1
  91. package/addon/routes/maintenance/parts.js +14 -1
  92. package/addon/routes/maintenance/schedules/index/details.js +2 -2
  93. package/addon/routes/maintenance/schedules/index/edit.js +2 -2
  94. package/addon/routes/maintenance/schedules/index/new.js +15 -1
  95. package/addon/routes/maintenance/schedules.js +15 -1
  96. package/addon/routes/maintenance/work-orders/index/details.js +2 -2
  97. package/addon/routes/maintenance/work-orders/index/edit.js +2 -2
  98. package/addon/routes/maintenance/work-orders/index/new.js +14 -1
  99. package/addon/routes/maintenance/work-orders.js +14 -1
  100. package/addon/routes/management/fuel-transactions/index/details.js +9 -0
  101. package/addon/routes/management/fuel-transactions.js +14 -1
  102. package/addon/routes/management/index.js +13 -0
  103. package/addon/routes/management/trailers.js +16 -0
  104. package/addon/routes/management/vendors/integrated/details.js +14 -1
  105. package/addon/routes/management/vendors/integrated/edit.js +14 -1
  106. package/addon/routes/management/vendors/integrated/new.js +14 -1
  107. package/addon/routes/management/vendors/integrated.js +14 -1
  108. package/addon/routes/operations/orders.js +40 -1
  109. package/addon/routes/operations/routes/index/details.js +14 -1
  110. package/addon/routes/operations/routes/index/new.js +13 -0
  111. package/addon/routes/operations/routes.js +14 -1
  112. package/addon/routes/operations/service-rates/index/new.js +11 -0
  113. package/addon/routes/operations/service-rates.js +14 -1
  114. package/addon/routes/settings/avatars.js +14 -1
  115. package/addon/routes/settings/custom-fields.js +14 -1
  116. package/addon/routes/settings/map.js +14 -1
  117. package/addon/routes/settings/notifications.js +11 -0
  118. package/addon/routes/settings/orchestrator.js +1 -1
  119. package/addon/routes/settings/payments/index.js +1 -1
  120. package/addon/routes/settings/routing.js +14 -1
  121. package/addon/routes/settings/scheduling.js +14 -1
  122. package/addon/routes/settings/telematics.js +21 -0
  123. package/addon/routes/virtual.js +11 -0
  124. package/addon/routes.js +1 -0
  125. package/addon/services/contact-actions.js +50 -89
  126. package/addon/services/driver-actions.js +98 -0
  127. package/addon/templates/settings/telematics.hbs +70 -0
  128. package/addon/utils/profile-login-actions.js +172 -0
  129. package/app/components/admin/telematics-settings.js +1 -0
  130. package/app/components/{modals/reset-customer-credentials.js → map/marker-card/driver.js} +1 -1
  131. package/app/components/map/marker-card/vehicle.js +1 -0
  132. package/app/components/modals/reset-profile-credentials.js +1 -0
  133. package/app/components/profile-identity-hint.js +1 -0
  134. package/app/controllers/settings/telematics.js +1 -0
  135. package/app/routes/settings/telematics.js +1 -0
  136. package/app/templates/settings/telematics.js +1 -0
  137. package/composer.json +2 -2
  138. package/docs/AFAQY.md +1 -1
  139. package/docs/TELEMATICS_QUEUES.md +1 -1
  140. package/docs/TELEMETRY_ARCHITECTURE.md +72 -1
  141. package/extension.json +1 -1
  142. package/package.json +3 -3
  143. package/scripts/pest-bootstrap.php +12 -0
  144. package/server/config/telemetry.php +12 -0
  145. package/server/migrations/2026_09_21_000001_convert_profile_only_users_to_managed_accounts.php +132 -0
  146. package/server/migrations/2026_09_23_000001_add_retention_index_to_device_events.php +40 -0
  147. package/server/migrations/2026_09_23_000002_add_retention_index_to_positions.php +40 -0
  148. package/server/migrations/2026_09_23_000003_add_retention_index_to_telematic_sync_runs.php +40 -0
  149. package/server/resources/views/mail/driver-credentials.blade.php +21 -0
  150. package/server/src/Auth/Schemas/FleetOps.php +104 -5
  151. package/server/src/Console/Commands/DrainTelematicInbox.php +3 -13
  152. package/server/src/Console/Commands/FixCustomerCompanies.php +1 -1
  153. package/server/src/Console/Commands/FixDriverCompanies.php +1 -1
  154. package/server/src/Console/Commands/ProcessOperationalAlerts.php +3 -3
  155. package/server/src/Console/Commands/PruneTelematicsData.php +318 -0
  156. package/server/src/Exceptions/ProfileIdentityConflictException.php +34 -0
  157. package/server/src/Http/Controllers/Api/v1/CustomerController.php +14 -0
  158. package/server/src/Http/Controllers/Api/v1/DriverController.php +56 -39
  159. package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +9 -0
  160. package/server/src/Http/Controllers/FleetOpsController.php +21 -0
  161. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +11 -0
  162. package/server/src/Http/Controllers/Internal/v1/ContactController.php +16 -17
  163. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +54 -4
  164. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +13 -0
  165. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +11 -0
  166. package/server/src/Http/Controllers/Internal/v1/DriverController.php +204 -162
  167. package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +47 -0
  168. package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +13 -0
  169. package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +15 -0
  170. package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +9 -3
  171. package/server/src/Http/Controllers/Internal/v1/InspectionFormController.php +15 -0
  172. package/server/src/Http/Controllers/Internal/v1/LiveController.php +16 -0
  173. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +15 -0
  174. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +15 -0
  175. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +15 -0
  176. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +11 -0
  177. package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +42 -1
  178. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +12 -0
  179. package/server/src/Http/Controllers/Internal/v1/OrderController.php +178 -27
  180. package/server/src/Http/Controllers/Internal/v1/PaymentController.php +13 -0
  181. package/server/src/Http/Controllers/Internal/v1/RadarController.php +19 -0
  182. package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +9 -3
  183. package/server/src/Http/Controllers/Internal/v1/SettingController.php +465 -14
  184. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +21 -0
  185. package/server/src/Http/Controllers/Internal/v1/TrailerController.php +21 -0
  186. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +21 -0
  187. package/server/src/Http/Controllers/Internal/v1/VendorController.php +17 -0
  188. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +11 -0
  189. package/server/src/Http/Requests/CreateDriverRequest.php +6 -2
  190. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +9 -18
  191. package/server/src/Http/Resources/v1/Contact.php +3 -0
  192. package/server/src/Http/Resources/v1/Driver.php +3 -0
  193. package/server/src/Jobs/DispatchTelematicsRetentionJobs.php +70 -0
  194. package/server/src/Jobs/PruneTelematicsDataJob.php +74 -0
  195. package/server/src/Listeners/NotifyDriverOnShiftChange.php +3 -3
  196. package/server/src/Mail/CustomerCredentialsMail.php +1 -1
  197. package/server/src/Mail/DriverCredentialsMail.php +55 -0
  198. package/server/src/Models/Contact.php +55 -90
  199. package/server/src/Models/Sensor.php +3 -5
  200. package/server/src/Observers/ContactObserver.php +2 -22
  201. package/server/src/Observers/DriverObserver.php +5 -6
  202. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +9 -1
  203. package/server/src/Providers/FleetOpsServiceProvider.php +18 -0
  204. package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +62 -5
  205. package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +24 -5
  206. package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +6 -2
  207. package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +40 -7
  208. package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +86 -24
  209. package/server/src/Support/Ai/FleetOpsAiConsoleCommands.php +122 -0
  210. package/server/src/Support/Ai/Tools/CreateOrderTool.php +86 -0
  211. package/server/src/Support/Ai/Tools/ImportOrdersTool.php +53 -0
  212. package/server/src/Support/Ai/Tools/OptimizeOrderRouteTool.php +70 -0
  213. package/server/src/Support/Ai/Tools/SearchResourcesTool.php +73 -0
  214. package/server/src/Support/Analytics/LiveFleet.php +18 -2
  215. package/server/src/Support/Authorization.php +103 -0
  216. package/server/src/Support/Database/TableIndexes.php +66 -0
  217. package/server/src/Support/OrderTracker.php +2 -2
  218. package/server/src/Support/ProfileAccountManager.php +405 -0
  219. package/server/src/Support/Reporting/FleetOpsReportSchema.php +476 -116
  220. package/server/src/Support/Telematics/Retention/RetentionPolicy.php +300 -0
  221. package/server/src/Support/Telematics/Retention/TelemetryActivity.php +40 -0
  222. package/server/src/Support/Telematics/TelematicService.php +12 -2
  223. package/server/src/Support/Telematics/Telemetry/Ingestor.php +4 -2
  224. package/server/src/Tracking/TrackingIntelligenceService.php +1 -1
  225. package/server/src/Tracking/TrackingOptions.php +2 -2
  226. package/server/src/Traits/AuthorizesMethods.php +37 -0
  227. package/server/src/routes.php +14 -1
  228. package/server/tests/AiOperationalQueryCapabilityTest.php +1 -0
  229. package/server/tests/AnalyticsRoutesTest.php +29 -2
  230. package/server/tests/ApiCustomerControllerContractsTest.php +21 -0
  231. package/server/tests/ApiDriverControllerContractsTest.php +102 -20
  232. package/server/tests/CommandContractsTest.php +4 -4
  233. package/server/tests/ControllerHelperContractsTest.php +3 -11
  234. package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +21 -1
  235. package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +36 -0
  236. package/server/tests/Feature/Http/Api/DriverControllerSessionHelpersTest.php +19 -6
  237. package/server/tests/Feature/Http/Api/ProfileOnlyUserConversionMigrationTest.php +201 -0
  238. package/server/tests/Feature/Http/Api/TelematicsRetentionIndexMigrationTest.php +115 -0
  239. package/server/tests/Feature/Http/Internal/ContactControllerEndpointsTest.php +6 -4
  240. package/server/tests/Feature/Http/Internal/CustomerControllerCredentialsTest.php +6 -2
  241. package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +23 -0
  242. package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +42 -17
  243. package/server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php +168 -277
  244. package/server/tests/Feature/Http/Internal/DriverControllerLoginManagementTest.php +716 -0
  245. package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +10 -3
  246. package/server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php +18 -0
  247. package/server/tests/Feature/Http/Internal/OrderControllerTenantScopingTest.php +411 -0
  248. package/server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php +8 -20
  249. package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +71 -0
  250. package/server/tests/Feature/Http/Internal/TelematicsStorageDeadlinesTest.php +119 -0
  251. package/server/tests/InternalCustomerControllerContractsTest.php +32 -1
  252. package/server/tests/InternalImportExportControllerContractsTest.php +5 -8
  253. package/server/tests/ModelAccessorContractsTest.php +82 -20
  254. package/server/tests/NavigatorControllerContractsTest.php +36 -4
  255. package/server/tests/NotificationAndMailContractsTest.php +31 -0
  256. package/server/tests/ObserverContractsTest.php +28 -3
  257. package/server/tests/OrchestratorConsumableApiTest.php +42 -0
  258. package/server/tests/ProviderContractsTest.php +10 -0
  259. package/server/tests/ReportSchemaContractsTest.php +120 -6
  260. package/server/tests/RequestContractsTest.php +19 -11
  261. package/server/tests/SettingControllerContractsTest.php +429 -18
  262. package/server/tests/SmallControllerContractsTest.php +8 -5
  263. package/server/tests/Support/PruneTelematicsDataProbe.php +55 -0
  264. package/server/tests/SupportJobAndAiCoverageTest.php +3 -3
  265. package/server/tests/TelematicControllerContractsTest.php +22 -4
  266. package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +2 -2
  267. package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +353 -0
  268. package/server/tests/Unit/Jobs/DispatchTelematicsRetentionJobsTest.php +114 -0
  269. package/server/tests/Unit/Jobs/PruneTelematicsDataJobTest.php +60 -0
  270. package/server/tests/Unit/Models/ContactCustomerUserTest.php +90 -14
  271. package/server/tests/Unit/Models/ContactTest.php +48 -5
  272. package/server/tests/Unit/Models/SensorTest.php +2 -2
  273. package/server/tests/Unit/Models/SmallModelSurfacesTest.php +31 -32
  274. package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +7 -2
  275. package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +20 -0
  276. package/server/tests/Unit/Providers/FleetOpsAiRegistrationTest.php +15 -2
  277. package/server/tests/Unit/Support/Ai/AssetStatusCapabilityQueriesTest.php +42 -0
  278. package/server/tests/Unit/Support/Ai/Capabilities/OrderInsightsCapabilityTest.php +19 -1
  279. package/server/tests/Unit/Support/Ai/Capabilities/SearchResourcesCapabilityTest.php +85 -1
  280. package/server/tests/Unit/Support/Ai/FleetOpsAiConsoleCommandsTest.php +31 -0
  281. package/server/tests/Unit/Support/Ai/Tools/FleetOpsAiToolsTest.php +246 -0
  282. package/server/tests/Unit/Support/AiCapabilityPermissionsTest.php +3 -3
  283. package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +6 -0
  284. package/server/tests/Unit/Support/AuthorizationTest.php +142 -0
  285. package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +12 -1
  286. package/server/tests/Unit/Support/MoreQuerySeamsTest.php +11 -2
  287. package/server/tests/Unit/Support/ProfileAccountManagerTest.php +643 -0
  288. package/server/tests/Unit/Support/Telematics/Retention/RetentionPolicyTest.php +190 -0
  289. package/server/tests/Unit/Support/Telematics/Retention/TelemetryActivityTest.php +68 -0
  290. package/server/tests/Unit/Tracking/TrackingOptionsCompanySettingsTest.php +109 -0
  291. package/translations/en-us.yaml +85 -2
  292. package/translations/uk-ua.yaml +3475 -0
  293. package/addon/components/modals/reset-customer-credentials.hbs +0 -18
  294. package/addon/components/modals/reset-customer-credentials.js +0 -55
@@ -34,17 +34,20 @@ class OrderInsightsCapability extends AbstractFleetOpsAICapability
34
34
  return ['authorized' => false, 'message' => 'Current user cannot access Fleet-Ops orders.'];
35
35
  }
36
36
 
37
- $prompt = $this->prompt($task);
38
- $window = $this->dateWindow($prompt);
39
- $amount = $this->amountThreshold($prompt);
40
- $query = $this->orderQuery(session('company'));
37
+ $prompt = $this->prompt($task);
38
+ $window = $this->dateWindow($prompt);
39
+ $amount = $this->amountThreshold($prompt);
40
+ $currency = $this->currency();
41
+ $minor = $amount !== null ? $this->toMinorUnits($amount, $currency) : null;
42
+ $query = $this->orderQuery(session('company'));
41
43
 
42
44
  if ($window) {
43
45
  $query->whereBetween('created_at', [$window['start'], $window['end']]);
44
46
  }
45
47
 
46
- if ($amount !== null) {
47
- $query->whereHas('transaction', fn ($transaction) => $transaction->where('amount', '>', $amount));
48
+ if ($minor !== null) {
49
+ // Transaction amounts are stored in the currency's minor unit (e.g. cents).
50
+ $query->whereHas('transaction', fn ($transaction) => $transaction->where('amount', '>', $minor));
48
51
  }
49
52
 
50
53
  $total = (clone $query)->count();
@@ -59,6 +62,7 @@ class OrderInsightsCapability extends AbstractFleetOpsAICapability
59
62
  'end' => $window['end']->toIso8601String(),
60
63
  ] : null,
61
64
  'amount_threshold' => $amount,
65
+ 'amount_currency' => $amount !== null ? $currency : null,
62
66
  'count' => $total,
63
67
  'counts_by_status' => (clone $query)
64
68
  ->selectRaw('status, count(*) as aggregate')
@@ -83,6 +87,35 @@ class OrderInsightsCapability extends AbstractFleetOpsAICapability
83
87
  return null;
84
88
  }
85
89
 
90
+ /**
91
+ * Converts a major-unit amount (e.g. 500.00) to the minor units transactions are stored in,
92
+ * honoring the currency's ISO-4217 exponent (JPY has none, BHD has three).
93
+ */
94
+ protected function toMinorUnits(float $amount, string $currency): int
95
+ {
96
+ $exponent = 2;
97
+
98
+ try {
99
+ if (class_exists(\Money\Currencies\ISOCurrencies::class)) {
100
+ $exponent = (new \Money\Currencies\ISOCurrencies())->subunitFor(new \Money\Currency(strtoupper($currency)));
101
+ }
102
+ } catch (\Throwable) {
103
+ $exponent = 2;
104
+ }
105
+
106
+ return (int) round($amount * (10 ** $exponent));
107
+ }
108
+
109
+ /**
110
+ * @codeCoverageIgnore
111
+ */
112
+ protected function currency(): string
113
+ {
114
+ $company = function_exists('session') ? \Fleetbase\Models\Company::where('uuid', session('company'))->first(['uuid', 'currency']) : null;
115
+
116
+ return strtoupper((string) ($company?->currency ?: 'USD'));
117
+ }
118
+
86
119
  protected function dateWindow(string $prompt): ?array
87
120
  {
88
121
  return $this->relativeDateResolver()->resolveWindow($prompt);
@@ -90,7 +123,7 @@ class OrderInsightsCapability extends AbstractFleetOpsAICapability
90
123
 
91
124
  protected function orderQuery(?string $companyUuid): mixed
92
125
  {
93
- return Order::where('company_uuid', $companyUuid);
126
+ return $this->scopeToPermission(Order::where('company_uuid', $companyUuid), 'fleet-ops list order');
94
127
  }
95
128
 
96
129
  protected function relativeDateResolver(): AiRelativeDateResolver
@@ -48,19 +48,69 @@ class SearchResourcesCapability extends AbstractFleetOpsAICapability
48
48
  $prompt = (string) $task->prompt;
49
49
  $terms = $this->searchTerms($prompt);
50
50
 
51
- return [
51
+ if (empty($terms)) {
52
+ return [
53
+ 'query_terms' => [],
54
+ 'results' => [],
55
+ 'message' => 'The prompt did not reference a specific Fleet-Ops record, so no records were searched.',
56
+ ];
57
+ }
58
+
59
+ return $this->searchAll($terms);
60
+ }
61
+
62
+ /**
63
+ * Search the given resource types (all when null) for the terms. A failing search is reported
64
+ * without discarding the others.
65
+ */
66
+ protected function searchAll(array $terms, ?array $types = null): array
67
+ {
68
+ $results = [];
69
+ $failed = [];
70
+ $searches = [
71
+ 'orders' => fn () => $this->orders($terms),
72
+ 'vehicles' => fn () => $this->vehicles($terms),
73
+ 'drivers' => fn () => $this->drivers($terms),
74
+ 'work_orders' => fn () => $this->workOrders($terms),
75
+ 'maintenances' => fn () => $this->maintenances($terms),
76
+ 'devices' => fn () => $this->devices($terms),
77
+ 'sensors' => fn () => $this->sensors($terms),
78
+ 'telematics' => fn () => $this->telematics($terms),
79
+ ];
80
+
81
+ if ($types !== null) {
82
+ $searches = array_intersect_key($searches, array_flip($types));
83
+ }
84
+
85
+ foreach ($searches as $resource => $search) {
86
+ try {
87
+ $results[$resource] = $search();
88
+ } catch (\Throwable $e) {
89
+ $failed[] = $resource;
90
+ $this->reportSearchFailure($resource, $e);
91
+ }
92
+ }
93
+
94
+ $payload = [
52
95
  'query_terms' => $terms,
53
- 'results' => array_filter([
54
- 'orders' => $this->orders($terms),
55
- 'vehicles' => $this->vehicles($terms),
56
- 'drivers' => $this->drivers($terms),
57
- 'work_orders' => $this->workOrders($terms),
58
- 'maintenances' => $this->maintenances($terms),
59
- 'devices' => $this->devices($terms),
60
- 'sensors' => $this->sensors($terms),
61
- 'telematics' => $this->telematics($terms),
62
- ]),
96
+ 'results' => array_filter($results),
63
97
  ];
98
+
99
+ if (!empty($failed)) {
100
+ $payload['unavailable_search'] = $failed;
101
+ }
102
+
103
+ return $payload;
104
+ }
105
+
106
+ /**
107
+ * @codeCoverageIgnore
108
+ */
109
+ protected function reportSearchFailure(string $resource, \Throwable $e): void
110
+ {
111
+ if (function_exists('report')) {
112
+ report($e);
113
+ }
64
114
  }
65
115
 
66
116
  protected function matchesPrompt(string $prompt): bool
@@ -76,7 +126,7 @@ class SearchResourcesCapability extends AbstractFleetOpsAICapability
76
126
 
77
127
  return $this->orderSearchQuery()
78
128
  ->where(function ($query) use ($terms) {
79
- $this->whereLikeAny($query, ['public_id', 'internal_id', 'uuid', 'status', 'type'], $terms);
129
+ $this->whereLikeAny($query, ['public_id', 'internal_id'], $terms);
80
130
  $query->orWhereHas('trackingNumber', fn ($tracking) => $this->whereLikeAny($tracking, ['tracking_number', 'barcode'], $terms));
81
131
  })
82
132
  ->limit(5)
@@ -127,7 +177,7 @@ class SearchResourcesCapability extends AbstractFleetOpsAICapability
127
177
 
128
178
  return $this->driverSearchQuery()
129
179
  ->where(function ($query) use ($terms) {
130
- $this->whereLikeAny($query, ['public_id', 'uuid', 'drivers_license_number', 'status'], $terms);
180
+ $this->whereLikeAny($query, ['public_id', 'internal_id', 'drivers_license_number'], $terms);
131
181
  $query->orWhereHas('user', fn ($user) => $this->whereLikeAny($user, ['name', 'email', 'phone'], $terms));
132
182
  })
133
183
  ->limit(5)
@@ -146,27 +196,27 @@ class SearchResourcesCapability extends AbstractFleetOpsAICapability
146
196
 
147
197
  protected function workOrders(array $terms): array
148
198
  {
149
- return $this->generic(WorkOrder::class, 'fleet-ops see work-order', ['public_id', 'uuid', 'code', 'subject', 'status', 'priority'], 'console.fleet-ops.maintenance.work-orders.index.details', $terms);
199
+ return $this->generic(WorkOrder::class, 'fleet-ops see work-order', ['public_id', 'code', 'subject'], 'console.fleet-ops.maintenance.work-orders.index.details', $terms);
150
200
  }
151
201
 
152
202
  protected function maintenances(array $terms): array
153
203
  {
154
- return $this->generic(Maintenance::class, 'fleet-ops see maintenance', ['public_id', 'uuid', 'status', 'type', 'summary', 'notes'], 'console.fleet-ops.maintenance.maintenances.index.details', $terms);
204
+ return $this->generic(Maintenance::class, 'fleet-ops see maintenance', ['public_id', 'summary', 'notes'], 'console.fleet-ops.maintenance.maintenances.index.details', $terms);
155
205
  }
156
206
 
157
207
  protected function devices(array $terms): array
158
208
  {
159
- return $this->generic(Device::class, 'fleet-ops see device', ['public_id', 'uuid', 'name', 'device_id', 'imei', 'serial_number', 'status'], 'console.fleet-ops.connectivity.devices.index.details', $terms);
209
+ return $this->generic(Device::class, 'fleet-ops see device', ['public_id', 'name', 'device_id', 'imei', 'serial_number'], 'console.fleet-ops.connectivity.devices.index.details', $terms);
160
210
  }
161
211
 
162
212
  protected function sensors(array $terms): array
163
213
  {
164
- return $this->generic(Sensor::class, 'fleet-ops see sensor', ['public_id', 'uuid', 'name', 'internal_id', 'serial_number', 'imei', 'type', 'sensor_type', 'status'], 'console.fleet-ops.connectivity.sensors.index.details', $terms);
214
+ return $this->generic(Sensor::class, 'fleet-ops see sensor', ['public_id', 'name', 'internal_id', 'serial_number', 'imei'], 'console.fleet-ops.connectivity.sensors.index.details', $terms);
165
215
  }
166
216
 
167
217
  protected function telematics(array $terms): array
168
218
  {
169
- return $this->generic(Telematic::class, 'fleet-ops see telematic', ['public_id', 'uuid', 'name', 'provider', 'status'], 'console.fleet-ops.connectivity.telematics.details', $terms);
219
+ return $this->generic(Telematic::class, 'fleet-ops see telematic', ['public_id', 'name'], 'console.fleet-ops.connectivity.telematics.details', $terms);
170
220
  }
171
221
 
172
222
  protected function generic(string $modelClass, string $permission, array $columns, string $route, array $terms): array
@@ -193,23 +243,35 @@ class SearchResourcesCapability extends AbstractFleetOpsAICapability
193
243
 
194
244
  protected function orderSearchQuery()
195
245
  {
196
- return Order::with(['transaction', 'trackingNumber'])
197
- ->where('company_uuid', session('company'));
246
+ return $this->scopeToPermission(
247
+ Order::with(['transaction', 'trackingNumber'])->where('company_uuid', session('company')),
248
+ 'fleet-ops list order'
249
+ );
198
250
  }
199
251
 
200
252
  protected function vehicleSearchQuery()
201
253
  {
202
- return Vehicle::where('company_uuid', session('company'));
254
+ return $this->scopeToPermission(Vehicle::where('company_uuid', session('company')), 'fleet-ops list vehicle');
203
255
  }
204
256
 
205
257
  protected function driverSearchQuery()
206
258
  {
207
- return Driver::with('user')
208
- ->where('company_uuid', session('company'));
259
+ return $this->scopeToPermission(Driver::with('user')->where('company_uuid', session('company')), 'fleet-ops list driver');
209
260
  }
210
261
 
211
262
  protected function genericSearchQuery(string $modelClass)
212
263
  {
213
- return $modelClass::where('company_uuid', session('company'));
264
+ $permission = match ($modelClass) {
265
+ WorkOrder::class => 'fleet-ops list work-order',
266
+ Maintenance::class => 'fleet-ops list maintenance',
267
+ Device::class => 'fleet-ops list device',
268
+ Sensor::class => 'fleet-ops list sensor',
269
+ Telematic::class => 'fleet-ops list telematic',
270
+ default => null,
271
+ };
272
+
273
+ $query = $modelClass::where('company_uuid', session('company'));
274
+
275
+ return $permission ? $this->scopeToPermission($query, $permission) : $query;
214
276
  }
215
277
  }
@@ -0,0 +1,122 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Ai;
4
+
5
+ /**
6
+ * Fleet-Ops pages and dialogs Fleetbase AI may offer to open. Definitions are plain arrays so Fleet-Ops
7
+ * does not depend on the AI extension's classes; nothing runs until the user confirms it in the console.
8
+ */
9
+ class FleetOpsAiConsoleCommands
10
+ {
11
+ public const ENGINE = '@fleetbase/fleetops-engine';
12
+
13
+ protected const DOCS = 'https://fleetbase.io/docs/fleet-ops';
14
+
15
+ public static function all(): array
16
+ {
17
+ return array_merge(static::resources(), static::settings());
18
+ }
19
+
20
+ /**
21
+ * Open, create, import, and view commands for Fleet-Ops records.
22
+ */
23
+ public static function resources(): array
24
+ {
25
+ $resources = [
26
+ // key => [singular, plural, list route, new route, detail route, permission resource, actions service, supports import, docs path, keywords]
27
+ 'orders' => ['order', 'Orders', 'operations.orders.index', 'operations.orders.index.new', 'operations.orders.index.details', 'order', 'order-actions', 'importOrders', 'operations/orders', ['dispatch', 'delivery', 'shipment', 'job']],
28
+ 'drivers' => ['driver', 'Drivers', 'management.drivers.index', 'management.drivers.index.new', 'management.drivers.index.details', 'driver', 'driver-actions', 'import', 'resources/drivers', ['courier', 'navigator']],
29
+ 'vehicles' => ['vehicle', 'Vehicles', 'management.vehicles.index', 'management.vehicles.index.new', 'management.vehicles.index.details', 'vehicle', 'vehicle-actions', 'import', 'resources/vehicles', ['truck', 'van', 'car', 'fleet']],
30
+ 'contacts' => ['contact', 'Contacts', 'management.contacts.index', 'management.contacts.index.new', 'management.contacts.index.details', 'contact', 'contact-actions', 'import', 'resources/contacts', ['person']],
31
+ 'customers' => ['customer', 'Customers', 'management.contacts.customers', 'management.contacts.customers.new', 'management.contacts.customers.details', 'contact', 'customer-actions', 'import', 'resources/contacts', ['client', 'contacts']],
32
+ 'places' => ['place', 'Places', 'management.places.index', 'management.places.index.new', 'management.places.index.details', 'place', 'place-actions', 'import', 'resources/places', ['address', 'location', 'depot', 'warehouse']],
33
+ 'fleets' => ['fleet', 'Fleets', 'management.fleets.index', 'management.fleets.index.new', 'management.fleets.index.details', 'fleet', 'fleet-actions', null, 'resources/fleets', ['team']],
34
+ 'issues' => ['issue', 'Issues', 'management.issues.index', 'management.issues.index.new', 'management.issues.index.details', 'issue', 'issue-actions', null, 'resources/issues', ['problem', 'incident']],
35
+ 'work_orders' => ['work order', 'Work Orders', 'maintenance.work-orders.index', 'maintenance.work-orders.index.new', 'maintenance.work-orders.index.details', 'work-order', 'work-order-actions', null, 'maintenance/work-orders', ['maintenance', 'repair']],
36
+ 'maintenances' => ['maintenance', 'Maintenances', 'maintenance.maintenances.index', 'maintenance.maintenances.index.new', 'maintenance.maintenances.index.details', 'maintenance', 'maintenance-actions', null, 'maintenance', ['service', 'repair']],
37
+ ];
38
+
39
+ $commands = [];
40
+
41
+ foreach ($resources as $key => [$singular, $plural, $listRoute, $newRoute, $detailRoute, $permission, $service, $import, $docs, $keywords]) {
42
+ $base = [
43
+ 'breadcrumb' => "Fleet-Ops › {$plural}",
44
+ 'keywords' => array_merge(['fleet-ops', $singular], $keywords),
45
+ 'docs_url' => static::DOCS . "/{$docs}",
46
+ 'module' => 'fleet-ops',
47
+ ];
48
+
49
+ $commands[] = $base + [
50
+ 'id' => "fleet-ops.{$key}.open",
51
+ 'label' => "Open {$plural}",
52
+ 'description' => "Go to the Fleet-Ops {$plural} list.",
53
+ 'steps' => [['type' => 'navigate', 'route' => "console.fleet-ops.{$listRoute}"]],
54
+ 'permissions' => ["fleet-ops list {$permission}"],
55
+ ];
56
+
57
+ $commands[] = $base + [
58
+ 'id' => "fleet-ops.{$key}.create",
59
+ 'label' => "Create {$singular}",
60
+ 'description' => "Open the new {$singular} form in Fleet-Ops.",
61
+ 'steps' => [['type' => 'navigate', 'route' => "console.fleet-ops.{$newRoute}"]],
62
+ 'permissions' => ["fleet-ops create {$permission}"],
63
+ 'keywords' => array_merge($base['keywords'], ['new', 'add', 'create']),
64
+ ];
65
+
66
+ $commands[] = $base + [
67
+ 'id' => "fleet-ops.{$key}.view",
68
+ 'label' => "Open {$singular}",
69
+ 'description' => "Open a specific {$singular} by its public id.",
70
+ 'steps' => [['type' => 'navigate', 'route' => "console.fleet-ops.{$detailRoute}", 'models' => ['public_id']]],
71
+ 'permissions' => ["fleet-ops see {$permission}"],
72
+ 'params' => ['public_id' => ['type' => 'string', 'description' => "The {$singular} public id."]],
73
+ ];
74
+
75
+ if ($import) {
76
+ $commands[] = $base + [
77
+ 'id' => "fleet-ops.{$key}.import",
78
+ 'label' => "Import {$plural}",
79
+ 'description' => "Go to {$plural} in Fleet-Ops and open the spreadsheet import, including the template download.",
80
+ 'steps' => [
81
+ ['type' => 'navigate', 'route' => "console.fleet-ops.{$listRoute}"],
82
+ ['type' => 'service', 'engine' => static::ENGINE, 'service' => $service, 'method' => $import],
83
+ ],
84
+ 'permissions' => ["fleet-ops import {$permission}"],
85
+ 'keywords' => array_merge($base['keywords'], ['import', 'spreadsheet', 'csv', 'excel', 'xlsx', 'template', 'bulk', 'upload']),
86
+ ];
87
+ }
88
+ }
89
+
90
+ return $commands;
91
+ }
92
+
93
+ public static function settings(): array
94
+ {
95
+ $settings = [
96
+ 'map' => ['Map', 'map-settings', ['map', 'provider', 'google maps', 'leaflet', 'openstreetmap', 'tiles', 'view maps']],
97
+ 'navigator-app' => ['Navigator App', 'navigator-settings', ['navigator', 'driver app', 'mobile app', 'onboard']],
98
+ 'routing' => ['Routing', 'routing-settings', ['routing', 'route', 'optimization', 'osrm', 'distance']],
99
+ 'notifications' => ['Notifications', 'notification-settings', ['notification', 'alert', 'email', 'sms']],
100
+ 'custom-fields' => ['Custom Fields', 'custom-field', ['custom field', 'field', 'attribute']],
101
+ 'scheduling' => ['Scheduling', null, ['schedule', 'shift', 'calendar']],
102
+ ];
103
+
104
+ $commands = [];
105
+
106
+ foreach ($settings as $route => [$label, $permission, $keywords]) {
107
+ $commands[] = [
108
+ 'id' => 'fleet-ops.settings.' . str_replace('-', '_', $route) . '.open',
109
+ 'label' => "Open {$label} settings",
110
+ 'breadcrumb' => "Fleet-Ops › Settings › {$label}",
111
+ 'description' => "Go to the Fleet-Ops {$label} settings.",
112
+ 'steps' => [['type' => 'navigate', 'route' => "console.fleet-ops.settings.{$route}"]],
113
+ 'permissions' => $permission ? ["fleet-ops view {$permission}"] : [],
114
+ 'keywords' => array_merge(['fleet-ops', 'settings', 'configure'], $keywords),
115
+ 'docs_url' => static::DOCS . "/settings/{$route}",
116
+ 'module' => 'fleet-ops',
117
+ ];
118
+ }
119
+
120
+ return $commands;
121
+ }
122
+ }
@@ -0,0 +1,86 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Ai\Tools;
4
+
5
+ use Fleetbase\Ai\Contracts\AIToolCapabilityInterface;
6
+ use Fleetbase\Ai\Models\AiTask;
7
+ use Fleetbase\Ai\Support\AiToolContext;
8
+ use Fleetbase\FleetOps\Support\Ai\Capabilities\CreateOrderPreviewCapability;
9
+
10
+ /**
11
+ * Lets the model prepare a Fleet-Ops order draft from structured fields. The draft is shown to the user
12
+ * as a preview card; the order is only created when the user applies it.
13
+ */
14
+ class CreateOrderTool extends CreateOrderPreviewCapability implements AIToolCapabilityInterface
15
+ {
16
+ public function toolName(): string
17
+ {
18
+ return 'propose_create_order';
19
+ }
20
+
21
+ public function toolDescription(): string
22
+ {
23
+ return 'Prepare a Fleet-Ops order draft for the user to review. Call once per order (several calls prepare several drafts). '
24
+ . 'Each draft appears as a card the user must confirm; the order is NOT created by this tool. '
25
+ . 'Pass addresses or saved place names exactly as the user gave them. Use ISO 8601 for scheduled_at, resolved with the temporal context. '
26
+ . 'Only fill fields the user provided or explicitly asked you to make up (for example test data). After calling, tell the user to review the draft card; never claim the order exists.';
27
+ }
28
+
29
+ public function toolParameters(): array
30
+ {
31
+ return [
32
+ 'type' => 'object',
33
+ 'properties' => [
34
+ 'pickup' => ['type' => 'string', 'description' => 'Pickup address or saved place name.'],
35
+ 'dropoff' => ['type' => 'string', 'description' => 'Dropoff address or saved place name.'],
36
+ 'scheduled_at' => ['type' => 'string', 'description' => 'Optional ISO 8601 date and time.'],
37
+ 'driver' => ['type' => 'string', 'description' => 'Optional driver name or public id.'],
38
+ 'vehicle' => ['type' => 'string', 'description' => 'Optional vehicle name, plate, or public id.'],
39
+ 'order_config' => ['type' => 'string', 'description' => 'Optional order configuration key, e.g. transport.'],
40
+ 'notes' => ['type' => 'string', 'description' => 'Optional notes for the order.'],
41
+ 'dispatch' => ['type' => 'boolean', 'description' => 'Dispatch immediately. Only true when the user asked for it.'],
42
+ ],
43
+ 'required' => ['pickup', 'dropoff'],
44
+ 'additionalProperties' => false,
45
+ ];
46
+ }
47
+
48
+ public function availableFor(AiToolContext $context): bool
49
+ {
50
+ return $context->audience->canAll($this->permissions());
51
+ }
52
+
53
+ public function invoke(AiTask $task, array $arguments, AiToolContext $context): array
54
+ {
55
+ $draft = array_filter([
56
+ 'payload' => array_filter([
57
+ 'pickup_query' => $this->argument($arguments, 'pickup'),
58
+ 'dropoff_query' => $this->argument($arguments, 'dropoff'),
59
+ ]),
60
+ 'scheduled_at' => $this->argument($arguments, 'scheduled_at'),
61
+ 'driver_query' => $this->argument($arguments, 'driver'),
62
+ 'vehicle_query' => $this->argument($arguments, 'vehicle'),
63
+ 'type' => $this->argument($arguments, 'order_config'),
64
+ 'notes' => $this->argument($arguments, 'notes'),
65
+ 'dispatched' => (bool) ($arguments['dispatch'] ?? false),
66
+ ], fn ($value) => $value !== null && $value !== []);
67
+
68
+ $preview = $context->addActionPreview($this, $this->preview($task, ['source' => 'tool', 'draft' => $draft]));
69
+
70
+ return [
71
+ 'preview_id' => $preview['preview_id'],
72
+ 'ready' => $preview['ready'] ?? false,
73
+ 'missing_fields' => $preview['missing_fields'] ?? [],
74
+ 'fields' => $preview['fields'] ?? [],
75
+ 'status' => 'draft_shown_to_user',
76
+ 'message' => 'A draft order card is shown to the user. The order does not exist until the user reviews the card and clicks ' . ($preview['apply_label'] ?? 'Create order') . '.',
77
+ ];
78
+ }
79
+
80
+ protected function argument(array $arguments, string $key): ?string
81
+ {
82
+ $value = trim((string) ($arguments[$key] ?? ''));
83
+
84
+ return $value === '' ? null : $value;
85
+ }
86
+ }
@@ -0,0 +1,53 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Ai\Tools;
4
+
5
+ use Fleetbase\Ai\Contracts\AIToolCapabilityInterface;
6
+ use Fleetbase\Ai\Models\AiTask;
7
+ use Fleetbase\Ai\Support\AiToolContext;
8
+ use Fleetbase\FleetOps\Support\Ai\Capabilities\ImportOrdersPreviewCapability;
9
+
10
+ /**
11
+ * Explains what a Fleet-Ops order import needs. Fleetbase AI cannot process an uploaded spreadsheet,
12
+ * so this only reports the accepted formats and required columns for the user to act on themselves.
13
+ */
14
+ class ImportOrdersTool extends ImportOrdersPreviewCapability implements AIToolCapabilityInterface
15
+ {
16
+ public function toolName(): string
17
+ {
18
+ return 'fleetops_import_requirements';
19
+ }
20
+
21
+ public function toolDescription(): string
22
+ {
23
+ return 'Report what a Fleet-Ops order or resource import needs: accepted file formats and the columns a spreadsheet must contain. '
24
+ . 'Use it when the user asks about importing or bulk uploading orders, vehicles, or drivers from a spreadsheet. '
25
+ . 'This does NOT import anything and cannot read an uploaded file; never tell the user rows were imported.';
26
+ }
27
+
28
+ public function toolParameters(): array
29
+ {
30
+ return [
31
+ 'type' => 'object',
32
+ 'properties' => new \stdClass(),
33
+ 'additionalProperties' => false,
34
+ ];
35
+ }
36
+
37
+ public function availableFor(AiToolContext $context): bool
38
+ {
39
+ return $context->audience->canAll($this->permissions());
40
+ }
41
+
42
+ public function invoke(AiTask $task, array $arguments, AiToolContext $context): array
43
+ {
44
+ $requirements = $this->resolve($task);
45
+
46
+ return [
47
+ 'accepted_sources' => $requirements['accepted_sources'] ?? [],
48
+ 'minimum_columns' => $requirements['minimum_columns'] ?? [],
49
+ 'can_import_here' => false,
50
+ 'message' => $requirements['message'] ?? 'Fleetbase AI cannot process uploaded spreadsheets.',
51
+ ];
52
+ }
53
+ }
@@ -0,0 +1,70 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Ai\Tools;
4
+
5
+ use Fleetbase\Ai\Contracts\AIToolCapabilityInterface;
6
+ use Fleetbase\Ai\Models\AiTask;
7
+ use Fleetbase\Ai\Support\AiToolContext;
8
+ use Fleetbase\FleetOps\Support\Ai\Capabilities\OptimizeOrderRouteCapability;
9
+
10
+ /**
11
+ * Lets the model propose a resequenced waypoint order for a Fleet-Ops order. The proposal is shown as
12
+ * a preview card; the route is only changed when the user applies it.
13
+ */
14
+ class OptimizeOrderRouteTool extends OptimizeOrderRouteCapability implements AIToolCapabilityInterface
15
+ {
16
+ public function toolName(): string
17
+ {
18
+ return 'propose_optimize_order_route';
19
+ }
20
+
21
+ public function toolDescription(): string
22
+ {
23
+ return 'Prepare an optimized waypoint sequence for one Fleet-Ops order, for the user to review. '
24
+ . 'Use it when the user asks to optimize, reorder, or resequence the stops on an order. '
25
+ . 'Each proposal appears as a card the user must confirm; the route is NOT changed by this tool. '
26
+ . 'After calling, tell the user to review the card; never claim the route was changed.';
27
+ }
28
+
29
+ public function toolParameters(): array
30
+ {
31
+ return [
32
+ 'type' => 'object',
33
+ 'properties' => [
34
+ 'order' => ['type' => 'string', 'description' => 'The order public id, internal id, or UUID, exactly as the user gave it.'],
35
+ ],
36
+ 'required' => ['order'],
37
+ 'additionalProperties' => false,
38
+ ];
39
+ }
40
+
41
+ public function availableFor(AiToolContext $context): bool
42
+ {
43
+ return $context->audience->canAll($this->permissions());
44
+ }
45
+
46
+ public function invoke(AiTask $task, array $arguments, AiToolContext $context): array
47
+ {
48
+ $order = trim((string) ($arguments['order'] ?? ''));
49
+
50
+ if ($order === '') {
51
+ return ['error' => 'invalid_arguments', 'message' => 'An order id is required to optimize a route.'];
52
+ }
53
+
54
+ // The capability resolves the order from the prompt text, so hand it a copy carrying just the
55
+ // identifier the model supplied. The copy is never saved.
56
+ $probe = clone $task;
57
+ $probe->prompt = $order;
58
+
59
+ $preview = $context->addActionPreview($this, $this->preview($probe));
60
+
61
+ return [
62
+ 'preview_id' => $preview['preview_id'],
63
+ 'ready' => $preview['ready'] ?? false,
64
+ 'missing_fields' => $preview['missing_fields'] ?? [],
65
+ 'fields' => $preview['fields'] ?? [],
66
+ 'status' => 'proposal_shown_to_user',
67
+ 'message' => $preview['message'] ?? 'A route proposal card is shown to the user.',
68
+ ];
69
+ }
70
+ }