@fleetbase/fleetops-engine 0.6.58 → 0.6.60

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 (701) hide show
  1. package/README.md +3 -0
  2. package/RELEASE.md +36 -0
  3. package/addon/components/customer/form.hbs +0 -49
  4. package/addon/components/entity/card.hbs +1 -0
  5. package/addon/components/modals/order-label.hbs +1 -1
  6. package/addon/components/place/form.hbs +1 -1
  7. package/addon/components/vehicle/details/schedules.hbs +2 -2
  8. package/addon/components/vehicle/details/schedules.js +7 -1
  9. package/addon/controllers/maintenance/schedules/index/edit.js +10 -1
  10. package/addon/services/entity-actions.js +32 -0
  11. package/addon/services/vehicle-actions.js +4 -2
  12. package/addon/styles/fleetops-engine.css +0 -1
  13. package/composer.json +8 -3
  14. package/extension.json +1 -1
  15. package/index.js +7 -1
  16. package/package.json +2 -1
  17. package/scripts/coverage-file-runner.php +240 -0
  18. package/scripts/coverage-runner.php +2 -1
  19. package/scripts/coverage-summary.php +41 -2
  20. package/scripts/pest-bootstrap.php +237 -3
  21. package/scripts/pest-file-runner.php +13 -1
  22. package/scripts/pest-runner.php +57 -2
  23. package/server/config/fleetops.php +44 -0
  24. package/server/config/geocoder.php +1 -1
  25. package/server/migrations/2026_08_08_000001_make_sku_and_provider_transaction_unique_indexes_soft_delete_aware.php +152 -0
  26. package/server/resources/views/labels/entity-label.php +4 -4
  27. package/server/src/Casts/MultiPolygon.php +8 -3
  28. package/server/src/Casts/OrderConfigEntities.php +10 -3
  29. package/server/src/Casts/Point.php +10 -6
  30. package/server/src/Casts/Polygon.php +5 -2
  31. package/server/src/Console/Commands/AssignCustomerRoles.php +28 -6
  32. package/server/src/Console/Commands/AssignDriverRoles.php +36 -6
  33. package/server/src/Console/Commands/AuditCustomerUserConflicts.php +6 -1
  34. package/server/src/Console/Commands/DispatchAdhocOrders.php +12 -2
  35. package/server/src/Console/Commands/FixCustomerCompanies.php +46 -11
  36. package/server/src/Console/Commands/FixDriverCompanies.php +18 -3
  37. package/server/src/Console/Commands/FixLegacyOrderConfigs.php +31 -6
  38. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +94 -42
  39. package/server/src/Console/Commands/PurgeUnpurchasedServiceQuotes.php +42 -12
  40. package/server/src/Console/Commands/ReplayVehicleLocations.php +120 -55
  41. package/server/src/Console/Commands/SendDriverNotification.php +25 -4
  42. package/server/src/Console/Commands/SendMaintenanceReminders.php +43 -23
  43. package/server/src/Console/Commands/SimulateOrderRouteNavigation.php +36 -6
  44. package/server/src/Console/Commands/TrackOrderDistanceAndTime.php +6 -1
  45. package/server/src/Events/DriverLocationChanged.php +1 -1
  46. package/server/src/Events/DriverSimulatedLocationChanged.php +1 -1
  47. package/server/src/Events/EntityActivityChanged.php +1 -1
  48. package/server/src/Events/EntityCompleted.php +1 -1
  49. package/server/src/Events/EntityDriverAssigned.php +1 -1
  50. package/server/src/Events/FuelProviderTransactionImported.php +1 -1
  51. package/server/src/Events/FuelProviderTransactionMatched.php +1 -1
  52. package/server/src/Events/FuelProviderTransactionUnmatched.php +1 -1
  53. package/server/src/Events/FuelReportCreatedFromProvider.php +1 -1
  54. package/server/src/Events/GeofenceDwelled.php +1 -1
  55. package/server/src/Events/GeofenceEntered.php +1 -1
  56. package/server/src/Events/GeofenceExited.php +1 -1
  57. package/server/src/Events/VehicleLocationChanged.php +1 -1
  58. package/server/src/Events/WaypointActivityChanged.php +1 -1
  59. package/server/src/Events/WaypointCompleted.php +1 -1
  60. package/server/src/Exceptions/IntegratedVendorException.php +2 -2
  61. package/server/src/Exceptions/TelematicRateLimitExceededException.php +1 -1
  62. package/server/src/Http/Controllers/Api/v1/ContactController.php +89 -22
  63. package/server/src/Http/Controllers/Api/v1/CustomerController.php +346 -101
  64. package/server/src/Http/Controllers/Api/v1/DeviceController.php +48 -14
  65. package/server/src/Http/Controllers/Api/v1/DriverController.php +224 -64
  66. package/server/src/Http/Controllers/Api/v1/EntityController.php +112 -66
  67. package/server/src/Http/Controllers/Api/v1/EquipmentController.php +40 -10
  68. package/server/src/Http/Controllers/Api/v1/FleetController.php +54 -14
  69. package/server/src/Http/Controllers/Api/v1/FuelReportController.php +54 -14
  70. package/server/src/Http/Controllers/Api/v1/FuelTransactionController.php +36 -11
  71. package/server/src/Http/Controllers/Api/v1/GeofenceController.php +73 -23
  72. package/server/src/Http/Controllers/Api/v1/IssueController.php +54 -14
  73. package/server/src/Http/Controllers/Api/v1/LabelController.php +61 -22
  74. package/server/src/Http/Controllers/Api/v1/NavigatorController.php +32 -15
  75. package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +35 -5
  76. package/server/src/Http/Controllers/Api/v1/OrderController.php +388 -203
  77. package/server/src/Http/Controllers/Api/v1/PartController.php +40 -10
  78. package/server/src/Http/Controllers/Api/v1/PayloadController.php +90 -30
  79. package/server/src/Http/Controllers/Api/v1/PlaceController.php +188 -91
  80. package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +100 -36
  81. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +96 -26
  82. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +169 -81
  83. package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +104 -75
  84. package/server/src/Http/Controllers/Api/v1/TrackingNumberController.php +92 -14
  85. package/server/src/Http/Controllers/Api/v1/TrackingStatusController.php +66 -16
  86. package/server/src/Http/Controllers/Api/v1/VehicleController.php +121 -67
  87. package/server/src/Http/Controllers/Api/v1/VendorController.php +54 -14
  88. package/server/src/Http/Controllers/Api/v1/WorkOrderController.php +47 -12
  89. package/server/src/Http/Controllers/Api/v1/ZoneController.php +84 -24
  90. package/server/src/Http/Controllers/Concerns/ResolvesReviewAccountBypass.php +62 -0
  91. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +15 -5
  92. package/server/src/Http/Controllers/Internal/v1/ContactController.php +113 -28
  93. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +36 -11
  94. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +9 -4
  95. package/server/src/Http/Controllers/Internal/v1/DriverController.php +168 -70
  96. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +18 -3
  97. package/server/src/Http/Controllers/Internal/v1/FleetController.php +137 -60
  98. package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +24 -9
  99. package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +18 -3
  100. package/server/src/Http/Controllers/Internal/v1/GeocoderController.php +26 -8
  101. package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +12 -2
  102. package/server/src/Http/Controllers/Internal/v1/IntegratedVendorController.php +27 -6
  103. package/server/src/Http/Controllers/Internal/v1/IssueController.php +82 -29
  104. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +34 -26
  105. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +141 -97
  106. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +28 -8
  107. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +21 -12
  108. package/server/src/Http/Controllers/Internal/v1/MorphController.php +90 -18
  109. package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +88 -35
  110. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +149 -39
  111. package/server/src/Http/Controllers/Internal/v1/OrderConfigController.php +54 -18
  112. package/server/src/Http/Controllers/Internal/v1/OrderController.php +298 -86
  113. package/server/src/Http/Controllers/Internal/v1/PartController.php +18 -3
  114. package/server/src/Http/Controllers/Internal/v1/PaymentController.php +46 -14
  115. package/server/src/Http/Controllers/Internal/v1/PlaceController.php +31 -5
  116. package/server/src/Http/Controllers/Internal/v1/ProofController.php +88 -48
  117. package/server/src/Http/Controllers/Internal/v1/SearchController.php +1 -1
  118. package/server/src/Http/Controllers/Internal/v1/SensorController.php +6 -1
  119. package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +6 -1
  120. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +255 -105
  121. package/server/src/Http/Controllers/Internal/v1/SettingController.php +88 -35
  122. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -4
  123. package/server/src/Http/Controllers/Internal/v1/Traits/DriverSchedulingTrait.php +42 -14
  124. package/server/src/Http/Controllers/Internal/v1/VendorController.php +103 -33
  125. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +42 -12
  126. package/server/src/Http/Filter/DriverFilter.php +2 -2
  127. package/server/src/Http/Filter/MaintenanceScheduleFilter.php +40 -0
  128. package/server/src/Http/Filter/OrderFilter.php +1 -1
  129. package/server/src/Http/Filter/VehicleFilter.php +21 -7
  130. package/server/src/Http/Middleware/AuthenticateCustomerToken.php +12 -2
  131. package/server/src/Http/Requests/BulkDispatchRequest.php +3 -9
  132. package/server/src/Http/Requests/CancelOrderRequest.php +2 -6
  133. package/server/src/Http/Requests/CreateContactRequest.php +2 -6
  134. package/server/src/Http/Requests/CreateCustomerRequest.php +9 -1
  135. package/server/src/Http/Requests/CreateDeviceRequest.php +2 -2
  136. package/server/src/Http/Requests/CreateDriverRequest.php +3 -9
  137. package/server/src/Http/Requests/CreateEntityRequest.php +2 -6
  138. package/server/src/Http/Requests/CreateEquipmentRequest.php +2 -2
  139. package/server/src/Http/Requests/CreateFleetRequest.php +2 -6
  140. package/server/src/Http/Requests/CreateFuelReportRequest.php +16 -9
  141. package/server/src/Http/Requests/CreateFuelTransactionRequest.php +66 -3
  142. package/server/src/Http/Requests/CreateIssueRequest.php +2 -6
  143. package/server/src/Http/Requests/CreateOrderRequest.php +7 -8
  144. package/server/src/Http/Requests/CreatePartRequest.php +41 -3
  145. package/server/src/Http/Requests/CreatePayloadRequest.php +7 -9
  146. package/server/src/Http/Requests/CreatePlaceRequest.php +2 -6
  147. package/server/src/Http/Requests/CreatePurchaseRateRequest.php +2 -6
  148. package/server/src/Http/Requests/CreateSensorRequest.php +2 -2
  149. package/server/src/Http/Requests/CreateServiceAreaRequest.php +2 -6
  150. package/server/src/Http/Requests/CreateServiceQuoteRequest.php +2 -6
  151. package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -6
  152. package/server/src/Http/Requests/CreateTrackingNumberRequest.php +2 -6
  153. package/server/src/Http/Requests/CreateTrackingStatusRequest.php +2 -6
  154. package/server/src/Http/Requests/CreateVehicleRequest.php +2 -6
  155. package/server/src/Http/Requests/CreateVendorRequest.php +2 -6
  156. package/server/src/Http/Requests/CreateWorkOrderRequest.php +2 -2
  157. package/server/src/Http/Requests/CreateZoneRequest.php +2 -6
  158. package/server/src/Http/Requests/DecodeTrackingNumberQR.php +2 -6
  159. package/server/src/Http/Requests/DriverSimulationRequest.php +2 -6
  160. package/server/src/Http/Requests/Internal/AssignOrderRequest.php +3 -7
  161. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +4 -12
  162. package/server/src/Http/Requests/Internal/CreateOrderConfigRequest.php +2 -6
  163. package/server/src/Http/Requests/Internal/CreateOrderRequest.php +7 -8
  164. package/server/src/Http/Requests/Internal/FleetActionRequest.php +18 -12
  165. package/server/src/Http/Requests/Internal/UpdateDriverRequest.php +6 -3
  166. package/server/src/Http/Requests/QueryServiceQuotesRequest.php +2 -6
  167. package/server/src/Http/Requests/ScheduleOrderRequest.php +2 -6
  168. package/server/src/Http/Requests/UpdateIssueRequest.php +1 -3
  169. package/server/src/Http/Resources/v1/Customer.php +2 -2
  170. package/server/src/Http/Resources/v1/Maintenance.php +3 -4
  171. package/server/src/Http/Resources/v1/MaintenanceSchedule.php +3 -4
  172. package/server/src/Http/Resources/v1/Order.php +3 -7
  173. package/server/src/Http/Resources/v1/PurchaseRate.php +4 -6
  174. package/server/src/Http/Resources/v1/TrackingNumber.php +33 -0
  175. package/server/src/Http/Resources/v1/TrackingStatus.php +4 -6
  176. package/server/src/Http/Resources/v1/WorkOrder.php +3 -4
  177. package/server/src/Imports/ContactImport.php +10 -1
  178. package/server/src/Imports/DriverImport.php +10 -1
  179. package/server/src/Imports/EquipmentImport.php +6 -1
  180. package/server/src/Imports/FleetImport.php +10 -1
  181. package/server/src/Imports/FuelReportImport.php +10 -1
  182. package/server/src/Imports/IssueImport.php +10 -1
  183. package/server/src/Imports/MaintenanceImport.php +6 -1
  184. package/server/src/Imports/MaintenanceScheduleImport.php +6 -1
  185. package/server/src/Imports/PartImport.php +6 -1
  186. package/server/src/Imports/PlaceImport.php +10 -1
  187. package/server/src/Imports/VehicleImport.php +10 -1
  188. package/server/src/Imports/VendorImport.php +10 -1
  189. package/server/src/Imports/WorkOrderImport.php +6 -1
  190. package/server/src/Integrations/Lalamove/Lalamove.php +3 -7
  191. package/server/src/Integrations/Lalamove/LalamoveServiceType.php +5 -8
  192. package/server/src/Jobs/CheckGeofenceDwell.php +41 -16
  193. package/server/src/Jobs/FinalizeApiOrderCreation.php +17 -2
  194. package/server/src/Jobs/FinalizeInternalOrderCreation.php +11 -1
  195. package/server/src/Jobs/NotifyBulkAssignedDriver.php +29 -16
  196. package/server/src/Jobs/ProcessAllocationJob.php +59 -25
  197. package/server/src/Jobs/ReplayPositions.php +17 -10
  198. package/server/src/Jobs/SendPositionReplay.php +18 -7
  199. package/server/src/Jobs/SimulateDrivingRoute.php +14 -3
  200. package/server/src/Jobs/SyncFuelProviderTransactionsJob.php +12 -2
  201. package/server/src/Listeners/HandleDeliveryCompletion.php +11 -1
  202. package/server/src/Listeners/HandleGeofenceDwelled.php +6 -1
  203. package/server/src/Listeners/HandleGeofenceEntered.php +4 -2
  204. package/server/src/Listeners/HandleGeofenceExited.php +6 -1
  205. package/server/src/Listeners/HandleOrderCanceled.php +18 -3
  206. package/server/src/Listeners/HandleOrderDispatchFailed.php +12 -2
  207. package/server/src/Listeners/HandleOrderDispatched.php +61 -26
  208. package/server/src/Listeners/HandleOrderDriverAssigned.php +12 -2
  209. package/server/src/Listeners/HandleUserRemovedFromCompany.php +19 -6
  210. package/server/src/Listeners/NotifyDriverOnShiftChange.php +24 -4
  211. package/server/src/Listeners/NotifyOrderEvent.php +11 -6
  212. package/server/src/Models/Contact.php +6 -7
  213. package/server/src/Models/Device.php +26 -3
  214. package/server/src/Models/Driver.php +0 -6
  215. package/server/src/Models/Entity.php +4 -4
  216. package/server/src/Models/Equipment.php +1 -1
  217. package/server/src/Models/FuelReport.php +26 -1
  218. package/server/src/Models/IntegratedVendor.php +2 -2
  219. package/server/src/Models/Issue.php +5 -1
  220. package/server/src/Models/MaintenanceSchedule.php +25 -10
  221. package/server/src/Models/Order.php +18 -15
  222. package/server/src/Models/OrderConfig.php +1 -1
  223. package/server/src/Models/Payload.php +9 -16
  224. package/server/src/Models/Place.php +20 -13
  225. package/server/src/Models/Sensor.php +24 -0
  226. package/server/src/Models/ServiceArea.php +3 -6
  227. package/server/src/Models/ServiceQuote.php +5 -0
  228. package/server/src/Models/ServiceRate.php +13 -6
  229. package/server/src/Models/Telematic.php +2 -2
  230. package/server/src/Models/TrackingNumber.php +55 -10
  231. package/server/src/Models/TrackingStatus.php +24 -4
  232. package/server/src/Models/Waypoint.php +44 -10
  233. package/server/src/Models/Zone.php +3 -6
  234. package/server/src/Notifications/DriverArrivedAtGeofence.php +1 -1
  235. package/server/src/Notifications/OrderDispatched.php +3 -9
  236. package/server/src/Notifications/OrderPing.php +3 -9
  237. package/server/src/Observers/CategoryObserver.php +6 -1
  238. package/server/src/Observers/CompanyObserver.php +5 -0
  239. package/server/src/Observers/CompanyUserObserver.php +12 -2
  240. package/server/src/Observers/DriverObserver.php +19 -4
  241. package/server/src/Observers/FleetObserver.php +6 -1
  242. package/server/src/Observers/PurchaseRateObserver.php +66 -11
  243. package/server/src/Observers/ServiceAreaObserver.php +12 -2
  244. package/server/src/Observers/ServiceRateObserver.php +21 -6
  245. package/server/src/Observers/TrackingNumberObserver.php +19 -4
  246. package/server/src/Observers/UserObserver.php +6 -1
  247. package/server/src/Observers/VehicleObserver.php +27 -7
  248. package/server/src/Observers/WorkOrderObserver.php +27 -7
  249. package/server/src/Orchestration/Engines/DriverAssignmentEngine.php +8 -3
  250. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +2 -2
  251. package/server/src/Providers/FleetOpsServiceProvider.php +12 -0
  252. package/server/src/Providers/NotificationServiceProvider.php +6 -0
  253. package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +36 -26
  254. package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +18 -5
  255. package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +6 -1
  256. package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +26 -6
  257. package/server/src/Support/Analytics/FuelEfficiency.php +9 -3
  258. package/server/src/Support/Analytics/FuelProviderSummary.php +14 -5
  259. package/server/src/Support/Analytics/GeofenceViolations.php +40 -20
  260. package/server/src/Support/Analytics/IssuesInsights.php +38 -18
  261. package/server/src/Support/Analytics/MaintenanceOverview.php +51 -21
  262. package/server/src/Support/Analytics/OnTimeDelivery.php +3 -2
  263. package/server/src/Support/Analytics/OperationsPulse.php +59 -35
  264. package/server/src/Support/Analytics/OrdersByStatus.php +12 -7
  265. package/server/src/Support/Analytics/RevenueTrend.php +16 -6
  266. package/server/src/Support/Analytics/TopDrivers.php +3 -2
  267. package/server/src/Support/FleetOps.php +109 -104
  268. package/server/src/Support/FuelProviders/FuelProviderService.php +10 -1
  269. package/server/src/Support/Geocoding.php +52 -14
  270. package/server/src/Support/GeofenceIntersectionService.php +47 -27
  271. package/server/src/Support/LiveOrderQuery.php +6 -1
  272. package/server/src/Support/Metrics/ActiveRevenueQuery.php +3 -2
  273. package/server/src/Support/Metrics/FuelCostsMetric.php +6 -1
  274. package/server/src/Support/ResolvesOrderServiceStops.php +10 -0
  275. package/server/src/Support/Telematics/Providers/GeotabProvider.php +9 -4
  276. package/server/src/Support/Utils.php +122 -16
  277. package/server/src/Tracking/Providers/GoogleRoutesTrackingProvider.php +6 -1
  278. package/server/src/Tracking/Providers/OsrmTrackingProvider.php +11 -6
  279. package/server/src/Tracking/TrackingProviderManager.php +2 -3
  280. package/server/src/Tracking/TrackingProviderRegistry.php +8 -4
  281. package/server/src/Traits/PayloadAccessors.php +6 -1
  282. package/server/src/routes.php +24 -17
  283. package/server/tests/AiOperationalQueryCapabilityTest.php +457 -1
  284. package/server/tests/AiPreviewCapabilityContractsTest.php +180 -0
  285. package/server/tests/AnalyticsRoutesTest.php +380 -1
  286. package/server/tests/AnalyticsWidgetContractsTest.php +740 -0
  287. package/server/tests/ApiContactControllerContractsTest.php +284 -0
  288. package/server/tests/ApiCustomerControllerContractsTest.php +1130 -0
  289. package/server/tests/ApiDriverControllerContractsTest.php +603 -0
  290. package/server/tests/ApiEntityControllerContractsTest.php +329 -0
  291. package/server/tests/ApiEquipmentControllerContractsTest.php +239 -0
  292. package/server/tests/ApiFleetControllerContractsTest.php +176 -0
  293. package/server/tests/ApiFuelReportControllerContractsTest.php +217 -0
  294. package/server/tests/ApiIssueControllerContractsTest.php +217 -0
  295. package/server/tests/ApiOrderControllerContractsTest.php +1030 -0
  296. package/server/tests/ApiPartControllerContractsTest.php +251 -0
  297. package/server/tests/ApiPayloadControllerContractsTest.php +314 -0
  298. package/server/tests/ApiPlaceControllerContractsTest.php +541 -0
  299. package/server/tests/ApiPurchaseRateControllerContractsTest.php +283 -0
  300. package/server/tests/ApiServiceAreaZoneControllerContractsTest.php +442 -0
  301. package/server/tests/ApiServiceQuoteControllerContractsTest.php +322 -0
  302. package/server/tests/ApiTrackingNumberControllerContractsTest.php +225 -0
  303. package/server/tests/ApiTrackingStatusControllerContractsTest.php +254 -0
  304. package/server/tests/ApiVehicleControllerContractsTest.php +368 -0
  305. package/server/tests/ApiVendorControllerContractsTest.php +199 -0
  306. package/server/tests/AssetAndPlaceSearchContractsTest.php +220 -0
  307. package/server/tests/BackendSmallModelContractsTest.php +354 -0
  308. package/server/tests/CapacityAllocationEngineTest.php +56 -0
  309. package/server/tests/CommandContractsTest.php +2695 -0
  310. package/server/tests/CompactResourceSerializationTest.php +2838 -0
  311. package/server/tests/ControllerFilterContractsTest.php +1273 -0
  312. package/server/tests/ControllerHelperContractsTest.php +1740 -0
  313. package/server/tests/ControllerSerializationContractsTest.php +141 -0
  314. package/server/tests/CoverageSummaryScriptTest.php +93 -0
  315. package/server/tests/CustomerContactIdentitySafetyTest.php +1 -6
  316. package/server/tests/CustomerEndpointTest.php +169 -2
  317. package/server/tests/DriverFilterExecutionTest.php +263 -0
  318. package/server/tests/DriverGeofenceProcessingTest.php +153 -0
  319. package/server/tests/DriverResourceSerializationTest.php +245 -0
  320. package/server/tests/DriverSchedulingTraitContractsTest.php +259 -0
  321. package/server/tests/EventContractsTest.php +1259 -0
  322. package/server/tests/ExceptionContractsTest.php +81 -0
  323. package/server/tests/ExpansionContractsTest.php +242 -0
  324. package/server/tests/ExportCoverageTest.php +116 -0
  325. package/server/tests/Feature/Http/Api/ContactControllerPhotoAndDeleteTest.php +199 -0
  326. package/server/tests/Feature/Http/Api/CustomerControllerHelperSeamsTest.php +831 -0
  327. package/server/tests/Feature/Http/Api/DeviceControllerContractsTest.php +347 -0
  328. package/server/tests/Feature/Http/Api/DeviceControllerCrudTest.php +176 -0
  329. package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +530 -0
  330. package/server/tests/Feature/Http/Api/DriverControllerHelpersTest.php +240 -0
  331. package/server/tests/Feature/Http/Api/DriverControllerLookupTest.php +186 -0
  332. package/server/tests/Feature/Http/Api/DriverControllerOrganizationsTest.php +112 -0
  333. package/server/tests/Feature/Http/Api/DriverControllerSessionHelpersTest.php +207 -0
  334. package/server/tests/Feature/Http/Api/DriverControllerSwitchOrgAndGeofenceTest.php +329 -0
  335. package/server/tests/Feature/Http/Api/DriverControllerTrackTest.php +383 -0
  336. package/server/tests/Feature/Http/Api/EntityControllerHelpersTest.php +150 -0
  337. package/server/tests/Feature/Http/Api/FuelTransactionControllerContractsTest.php +352 -0
  338. package/server/tests/Feature/Http/Api/GeofenceControllerContractsTest.php +461 -0
  339. package/server/tests/Feature/Http/Api/LabelControllerSubjectsTest.php +148 -0
  340. package/server/tests/Feature/Http/Api/OrderConfigControllerTest.php +112 -0
  341. package/server/tests/Feature/Http/Api/OrderControllerCreateTest.php +279 -0
  342. package/server/tests/Feature/Http/Api/OrderControllerIntegratedVendorTest.php +295 -0
  343. package/server/tests/Feature/Http/Api/OrderControllerLifecycleActionsTest.php +279 -0
  344. package/server/tests/Feature/Http/Api/OrderControllerLookupHelpersTest.php +207 -0
  345. package/server/tests/Feature/Http/Api/OrderControllerNearbyFiltersTest.php +282 -0
  346. package/server/tests/Feature/Http/Api/OrderControllerPersistenceHelpersTest.php +216 -0
  347. package/server/tests/Feature/Http/Api/OrderControllerQueryFiltersTest.php +184 -0
  348. package/server/tests/Feature/Http/Api/OrderControllerStartActivityTest.php +663 -0
  349. package/server/tests/Feature/Http/Api/PayloadControllerHelpersTest.php +134 -0
  350. package/server/tests/Feature/Http/Api/PlaceAndServiceQuoteSeamsTest.php +479 -0
  351. package/server/tests/Feature/Http/Api/PurchaseRateControllerHelpersTest.php +176 -0
  352. package/server/tests/Feature/Http/Api/PurchaseRateControllerOrderCreationTest.php +268 -0
  353. package/server/tests/Feature/Http/Api/SensorControllerCrudTest.php +239 -0
  354. package/server/tests/Feature/Http/Api/ServiceAreaControllerBorderAndSeamsTest.php +187 -0
  355. package/server/tests/Feature/Http/Api/ServiceQuoteControllerQueryTest.php +405 -0
  356. package/server/tests/Feature/Http/Api/ServiceQuoteControllerVendorBridgeTest.php +212 -0
  357. package/server/tests/Feature/Http/Api/ServiceRateControllerContractsTest.php +263 -0
  358. package/server/tests/Feature/Http/Api/SmallApiControllerHelpersTest.php +272 -0
  359. package/server/tests/Feature/Http/Api/TrackingNumberControllerHelpersTest.php +160 -0
  360. package/server/tests/Feature/Http/Api/TrackingStatusControllerHelpersTest.php +165 -0
  361. package/server/tests/Feature/Http/Api/VehicleControllerTrackingTest.php +483 -0
  362. package/server/tests/Feature/Http/Api/WorkOrderControllerContractsTest.php +326 -0
  363. package/server/tests/Feature/Http/Api/WorkOrderControllerHelpersTest.php +130 -0
  364. package/server/tests/Feature/Http/Api/ZoneControllerBordersAndSeamsTest.php +294 -0
  365. package/server/tests/Feature/Http/FleetOpsRouteRegistrationTest.php +189 -0
  366. package/server/tests/Feature/Http/Internal/AnalyticsControllerSeamsTest.php +53 -0
  367. package/server/tests/Feature/Http/Internal/ContactControllerContractsTest.php +240 -0
  368. package/server/tests/Feature/Http/Internal/ContactControllerCustomerGuardsTest.php +128 -0
  369. package/server/tests/Feature/Http/Internal/ContactControllerEndpointsTest.php +294 -0
  370. package/server/tests/Feature/Http/Internal/CustomerControllerCredentialsTest.php +196 -0
  371. package/server/tests/Feature/Http/Internal/DeviceControllerContractsTest.php +370 -0
  372. package/server/tests/Feature/Http/Internal/DeviceControllerExportAndFiltersTest.php +151 -0
  373. package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +1167 -0
  374. package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +233 -0
  375. package/server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php +710 -0
  376. package/server/tests/Feature/Http/Internal/DriverControllerHelpersTest.php +218 -0
  377. package/server/tests/Feature/Http/Internal/FleetAssignmentsAndTriggersTest.php +198 -0
  378. package/server/tests/Feature/Http/Internal/FleetOpsLookupControllerDbTest.php +90 -0
  379. package/server/tests/Feature/Http/Internal/HubControllerTest.php +206 -0
  380. package/server/tests/Feature/Http/Internal/IssueControllerContractsTest.php +404 -0
  381. package/server/tests/Feature/Http/Internal/IssueControllerExportImportTest.php +118 -0
  382. package/server/tests/Feature/Http/Internal/LiveControllerEndpointsTest.php +246 -0
  383. package/server/tests/Feature/Http/Internal/LiveControllerTest.php +778 -0
  384. package/server/tests/Feature/Http/Internal/MaintenanceScheduleControllerContractsTest.php +263 -0
  385. package/server/tests/Feature/Http/Internal/MaintenanceScheduleControllerSurfacesTest.php +214 -0
  386. package/server/tests/Feature/Http/Internal/MetricsControllerContractsTest.php +249 -0
  387. package/server/tests/Feature/Http/Internal/MetricsControllerHelpersTest.php +66 -0
  388. package/server/tests/Feature/Http/Internal/MorphControllerHelpersTest.php +125 -0
  389. package/server/tests/Feature/Http/Internal/NavigatorControllerHelpersTest.php +201 -0
  390. package/server/tests/Feature/Http/Internal/OrchestrationControllerContractsTest.php +928 -0
  391. package/server/tests/Feature/Http/Internal/OrchestrationControllerHelpersTest.php +214 -0
  392. package/server/tests/Feature/Http/Internal/OrchestrationControllerImportOrdersTest.php +340 -0
  393. package/server/tests/Feature/Http/Internal/OrderConfigControllerContractsTest.php +314 -0
  394. package/server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php +663 -0
  395. package/server/tests/Feature/Http/Internal/OrderControllerCapturePhotoTest.php +507 -0
  396. package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +1742 -0
  397. package/server/tests/Feature/Http/Internal/OrderControllerCreateRecordTest.php +488 -0
  398. package/server/tests/Feature/Http/Internal/OrderControllerHelperSeamsTest.php +298 -0
  399. package/server/tests/Feature/Http/Internal/OrderControllerHelpersTest.php +267 -0
  400. package/server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php +179 -0
  401. package/server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php +80 -0
  402. package/server/tests/Feature/Http/Internal/PaymentControllerPaymentsTest.php +154 -0
  403. package/server/tests/Feature/Http/Internal/PlaceControllerTest.php +252 -0
  404. package/server/tests/Feature/Http/Internal/PositionControllerReplayMetricsTest.php +163 -0
  405. package/server/tests/Feature/Http/Internal/ProofControllerContractsTest.php +219 -0
  406. package/server/tests/Feature/Http/Internal/SearchControllerEndpointTest.php +151 -0
  407. package/server/tests/Feature/Http/Internal/ServiceQuoteControllerHelpersTest.php +208 -0
  408. package/server/tests/Feature/Http/Internal/ServiceQuoteControllerQueryRecordTest.php +217 -0
  409. package/server/tests/Feature/Http/Internal/ServiceQuoteControllerVendorBridgeTest.php +239 -0
  410. package/server/tests/Feature/Http/Internal/ServiceQuotePreliminaryQueryTest.php +271 -0
  411. package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +133 -0
  412. package/server/tests/Feature/Http/Internal/SmallInternalControllersTest.php +180 -0
  413. package/server/tests/Feature/Http/Internal/TelematicControllerEndpointsTest.php +417 -0
  414. package/server/tests/Feature/Http/Internal/VehicleControllerEndpointsTest.php +361 -0
  415. package/server/tests/Feature/Http/Internal/VendorControllerEndpointsTest.php +203 -0
  416. package/server/tests/Feature/Http/Internal/WorkOrderAndConfigSurfacesTest.php +289 -0
  417. package/server/tests/Feature/Http/Internal/WorkOrderControllerContractsTest.php +199 -0
  418. package/server/tests/Feature/Http/TelematicWebhookIngestTest.php +343 -0
  419. package/server/tests/Feature/Http/Telematics/TelematicWebhookControllerProcessingTest.php +254 -0
  420. package/server/tests/FleetOpsSupportContractsTest.php +187 -0
  421. package/server/tests/FlespiProviderContractsTest.php +244 -0
  422. package/server/tests/FlowResourceTest.php +295 -0
  423. package/server/tests/FuelProviderConnectionControllerContractsTest.php +303 -0
  424. package/server/tests/FuelProviderImplementationTest.php +753 -0
  425. package/server/tests/FuelProviderResourceContractsTest.php +171 -0
  426. package/server/tests/FuelProviderTransactionControllerContractsTest.php +198 -0
  427. package/server/tests/GeofenceIntersectionServiceContractsTest.php +164 -0
  428. package/server/tests/HOSConstraintContractsTest.php +128 -0
  429. package/server/tests/HubAndGettingStartedContractsTest.php +158 -0
  430. package/server/tests/ImportContractsTest.php +191 -0
  431. package/server/tests/InternalCustomerControllerContractsTest.php +238 -0
  432. package/server/tests/InternalFleetControllerContractsTest.php +389 -0
  433. package/server/tests/InternalImportExportControllerContractsTest.php +289 -0
  434. package/server/tests/InternalMetricsControllerContractsTest.php +69 -0
  435. package/server/tests/InternalPlaceWorkOrderControllerContractsTest.php +207 -0
  436. package/server/tests/InternalServiceQuoteCheckoutControllerContractsTest.php +470 -0
  437. package/server/tests/InternalVendorControllerContractsTest.php +331 -0
  438. package/server/tests/InventoryTelemetryModelContractsTest.php +516 -0
  439. package/server/tests/IssueTimelineContractsTest.php +191 -0
  440. package/server/tests/LabelPdfRenderingTest.php +57 -0
  441. package/server/tests/LalamoveIntegrationContractsTest.php +167 -0
  442. package/server/tests/LiveOrderQueryTest.php +145 -18
  443. package/server/tests/LookupAndGeocoderControllerContractsTest.php +357 -0
  444. package/server/tests/MaintainableTraitContractsTest.php +245 -0
  445. package/server/tests/MaintenanceScheduleControllerContractsTest.php +101 -0
  446. package/server/tests/MaintenanceWarrantyContractsTest.php +237 -0
  447. package/server/tests/ManifestControllerContractsTest.php +310 -0
  448. package/server/tests/MetricsRegistryTest.php +596 -10
  449. package/server/tests/MiddlewareContractsTest.php +150 -0
  450. package/server/tests/ModelAccessorContractsTest.php +3201 -0
  451. package/server/tests/MorphControllerContractsTest.php +410 -0
  452. package/server/tests/NavigatorControllerContractsTest.php +206 -0
  453. package/server/tests/NotificationAndMailContractsTest.php +677 -0
  454. package/server/tests/ObserverContractsTest.php +1414 -0
  455. package/server/tests/OperationalAlgorithmContractsTest.php +852 -0
  456. package/server/tests/OperationsMonitorEndpointTest.php +2 -1
  457. package/server/tests/OrchestrationPayloadBuilderTest.php +87 -0
  458. package/server/tests/OrderActivityFlowRegressionTest.php +354 -7
  459. package/server/tests/OrderDispatchListenerContractsTest.php +577 -0
  460. package/server/tests/OrderFilterExecutionTest.php +244 -0
  461. package/server/tests/OrderQueuedClosureRegressionTest.php +2 -1
  462. package/server/tests/PaymentControllerContractsTest.php +199 -0
  463. package/server/tests/PointResolutionTest.php +54 -0
  464. package/server/tests/ProcessAllocationJobContractsTest.php +224 -0
  465. package/server/tests/ProofAndSensorControllerContractsTest.php +44 -0
  466. package/server/tests/ProviderContractsTest.php +409 -0
  467. package/server/tests/PublicApiControllerInputContractsTest.php +294 -0
  468. package/server/tests/QueueJobContractsTest.php +764 -0
  469. package/server/tests/ReportSchemaContractsTest.php +271 -0
  470. package/server/tests/RequestContractsTest.php +1058 -0
  471. package/server/tests/RouteRegistrationExecutionTest.php +178 -0
  472. package/server/tests/RouteSequencingEngineTest.php +179 -0
  473. package/server/tests/RulesAndCastsTest.php +246 -0
  474. package/server/tests/SamsaraProviderContractsTest.php +272 -0
  475. package/server/tests/SearchControllerContractsTest.php +311 -0
  476. package/server/tests/ServiceRateRouteValidationTest.php +76 -0
  477. package/server/tests/SettingControllerContractsTest.php +410 -0
  478. package/server/tests/SmallControllerContractsTest.php +480 -0
  479. package/server/tests/SupportAnalyticsCustomerAuthContractsTest.php +221 -0
  480. package/server/tests/SupportJobAndAiCoverageTest.php +812 -0
  481. package/server/tests/SupportServiceContractsTest.php +287 -0
  482. package/server/tests/SupportValueObjectsTest.php +155 -0
  483. package/server/tests/TelematicControllerContractsTest.php +185 -0
  484. package/server/tests/TelematicProviderRegistryTest.php +167 -0
  485. package/server/tests/TelematicServiceHelperContractsTest.php +315 -0
  486. package/server/tests/TelematicWebhookControllerContractsTest.php +253 -0
  487. package/server/tests/TelematicsHardeningTest.php +642 -86
  488. package/server/tests/TrackingIntelligenceTest.php +644 -1
  489. package/server/tests/TrackingNumberTraitContractsTest.php +106 -0
  490. package/server/tests/Unit/Casts/SpatialCastBranchesTest.php +75 -0
  491. package/server/tests/Unit/Console/AssignCustomerRolesCommandTest.php +102 -0
  492. package/server/tests/Unit/Console/AssignDriverRolesCommandTest.php +113 -0
  493. package/server/tests/Unit/Console/Commands/FixLegacyOrderConfigsHelpersTest.php +108 -0
  494. package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +265 -0
  495. package/server/tests/Unit/Console/Commands/ReplayVehicleLocationsCommandTest.php +320 -0
  496. package/server/tests/Unit/Console/Commands/SimulateGeofenceEventsCommandTest.php +207 -0
  497. package/server/tests/Unit/Console/FixCommandsAndMaintenanceControllerTest.php +256 -0
  498. package/server/tests/Unit/Console/FleetOpsCommandsTest.php +198 -0
  499. package/server/tests/Unit/Console/ReplayVehicleLocationsCommandTest.php +252 -0
  500. package/server/tests/Unit/Console/SendMaintenanceRemindersTest.php +212 -0
  501. package/server/tests/Unit/Console/SimulateGeofenceEventsCommandTest.php +232 -0
  502. package/server/tests/Unit/Console/SyncTelematicsCommandTest.php +136 -0
  503. package/server/tests/Unit/Constraints/HOSConstraintTest.php +158 -0
  504. package/server/tests/Unit/Events/EventModelRecordTest.php +93 -0
  505. package/server/tests/Unit/Events/OrderDispatchFailedTest.php +34 -0
  506. package/server/tests/Unit/Exports/ExportCollectionsTest.php +142 -0
  507. package/server/tests/Unit/Exports/OperationalExportMappingTest.php +651 -0
  508. package/server/tests/Unit/Exports/VehicleExportTest.php +284 -0
  509. package/server/tests/Unit/Flow/EventFiringTest.php +95 -0
  510. package/server/tests/Unit/Http/Api/QueryWithRequestSeamsTest.php +198 -0
  511. package/server/tests/Unit/Http/Api/SensorInputTest.php +95 -0
  512. package/server/tests/Unit/Http/Filter/DeviceFilterTest.php +199 -0
  513. package/server/tests/Unit/Http/Filter/EmptyValueGuardsTest.php +66 -0
  514. package/server/tests/Unit/Http/Filter/FilterDateRangesTest.php +72 -0
  515. package/server/tests/Unit/Http/Filter/FuelProviderTransactionFilterTest.php +172 -0
  516. package/server/tests/Unit/Http/Filter/InternalRelationUuidLookupTest.php +135 -0
  517. package/server/tests/Unit/Http/Filter/IssueFilterTest.php +129 -0
  518. package/server/tests/Unit/Http/Filter/MaintenanceScheduleFilterTest.php +69 -0
  519. package/server/tests/Unit/Http/Filter/PartFilterTest.php +173 -0
  520. package/server/tests/Unit/Http/Internal/ControllerDelegationSeamsTest.php +136 -0
  521. package/server/tests/Unit/Http/Internal/ControllerExcelSeamsTest.php +89 -0
  522. package/server/tests/Unit/Http/Middleware/SetupDriverSessionTest.php +32 -0
  523. package/server/tests/Unit/Http/Requests/CreateFuelReportRequestTest.php +68 -0
  524. package/server/tests/Unit/Http/Requests/FleetActionRequestTest.php +80 -0
  525. package/server/tests/Unit/Http/Requests/RequestAuthorizationTest.php +50 -0
  526. package/server/tests/Unit/Http/Resources/CustomerResourceTest.php +108 -0
  527. package/server/tests/Unit/Http/Resources/EntityCustomerTypeTest.php +35 -0
  528. package/server/tests/Unit/Http/Resources/FleetResourceTest.php +102 -0
  529. package/server/tests/Unit/Http/Resources/IndexOrderResourceTest.php +210 -0
  530. package/server/tests/Unit/Http/Resources/IndexVehicleResourceTest.php +194 -0
  531. package/server/tests/Unit/Http/Resources/MaintenanceResourcesTest.php +182 -0
  532. package/server/tests/Unit/Http/Resources/OrderAndDriverResourceTest.php +121 -0
  533. package/server/tests/Unit/Http/Resources/OrderConfigResourceTest.php +47 -0
  534. package/server/tests/Unit/Http/Resources/PayloadResourceTest.php +111 -0
  535. package/server/tests/Unit/Http/Resources/RelationResourceFallbacksTest.php +32 -0
  536. package/server/tests/Unit/Http/Resources/ResourceRelationResolutionTest.php +55 -0
  537. package/server/tests/Unit/Http/Resources/TrackingNumberResourceTest.php +174 -0
  538. package/server/tests/Unit/Http/Resources/WaypointResourceTest.php +295 -0
  539. package/server/tests/Unit/Http/Resources/WorkOrderResourceTest.php +164 -0
  540. package/server/tests/Unit/Imports/ImportDelegationTest.php +151 -0
  541. package/server/tests/Unit/Integrations/Lalamove/LalamoveDeliveryStopTest.php +79 -0
  542. package/server/tests/Unit/Integrations/Lalamove/LalamoveLifecycleTest.php +211 -0
  543. package/server/tests/Unit/Integrations/Lalamove/LalamoveQuoteAndServiceOrderTest.php +381 -0
  544. package/server/tests/Unit/Integrations/Lalamove/LalamoveServiceTypeTest.php +51 -0
  545. package/server/tests/Unit/Integrations/LalamoveQuotationTest.php +194 -0
  546. package/server/tests/Unit/Integrations/LalamoveResponseHandlingTest.php +58 -0
  547. package/server/tests/Unit/Jobs/GeofenceDwellAndBulkNotifyTest.php +190 -0
  548. package/server/tests/Unit/Jobs/ReplayPositionsTest.php +57 -0
  549. package/server/tests/Unit/Jobs/SimulateWaypointReachedTest.php +92 -0
  550. package/server/tests/Unit/Listeners/HandleGeofenceEnteredTest.php +256 -0
  551. package/server/tests/Unit/Listeners/HandleOrderDispatchFailedListenerTest.php +101 -0
  552. package/server/tests/Unit/Listeners/HandleOrderDispatchedConcreteTest.php +203 -0
  553. package/server/tests/Unit/Listeners/HandleOrderDispatchedHelpersTest.php +186 -0
  554. package/server/tests/Unit/Listeners/HandleUserRemovedFromCompanyTest.php +65 -0
  555. package/server/tests/Unit/Listeners/ListenerDelegationSeamsTest.php +83 -0
  556. package/server/tests/Unit/Models/AssetTest.php +469 -0
  557. package/server/tests/Unit/Models/ContactCustomerUserTest.php +370 -0
  558. package/server/tests/Unit/Models/ContactTest.php +577 -0
  559. package/server/tests/Unit/Models/DeviceEventTest.php +481 -0
  560. package/server/tests/Unit/Models/DeviceLastPositionDefaultTest.php +128 -0
  561. package/server/tests/Unit/Models/DriverModelHelpersTest.php +268 -0
  562. package/server/tests/Unit/Models/DriverTest.php +401 -0
  563. package/server/tests/Unit/Models/EntityTest.php +171 -0
  564. package/server/tests/Unit/Models/EquipmentTest.php +434 -0
  565. package/server/tests/Unit/Models/FleetTest.php +153 -0
  566. package/server/tests/Unit/Models/FuelProviderTransactionTest.php +82 -0
  567. package/server/tests/Unit/Models/FuelReportImportAndLabelsTest.php +193 -0
  568. package/server/tests/Unit/Models/ImportRowFallbackSeamsTest.php +100 -0
  569. package/server/tests/Unit/Models/IntegratedVendorLifecycleTest.php +203 -0
  570. package/server/tests/Unit/Models/IntegratedVendorTest.php +135 -0
  571. package/server/tests/Unit/Models/IssueTest.php +160 -0
  572. package/server/tests/Unit/Models/MaintenanceScheduleTest.php +319 -0
  573. package/server/tests/Unit/Models/MaintenanceTest.php +277 -0
  574. package/server/tests/Unit/Models/MaintenanceWorkOrderImportTest.php +161 -0
  575. package/server/tests/Unit/Models/ManifestStopTest.php +121 -0
  576. package/server/tests/Unit/Models/ManifestTest.php +163 -0
  577. package/server/tests/Unit/Models/ModelLookupFallbackSeamsTest.php +149 -0
  578. package/server/tests/Unit/Models/OrderConfigTest.php +341 -0
  579. package/server/tests/Unit/Models/OrderDynamicContractsTest.php +190 -0
  580. package/server/tests/Unit/Models/OrderPayloadConfigAndDistanceTest.php +426 -0
  581. package/server/tests/Unit/Models/OrderPayloadRouteTest.php +227 -0
  582. package/server/tests/Unit/Models/OrderPurchaseAndDriversTest.php +188 -0
  583. package/server/tests/Unit/Models/OrderTest.php +917 -0
  584. package/server/tests/Unit/Models/PartTest.php +399 -0
  585. package/server/tests/Unit/Models/PayloadTest.php +710 -0
  586. package/server/tests/Unit/Models/PayloadWaypointsAndEntitiesTest.php +317 -0
  587. package/server/tests/Unit/Models/PlaceCreationAndImportTest.php +384 -0
  588. package/server/tests/Unit/Models/PlaceGeocodingResultsTest.php +253 -0
  589. package/server/tests/Unit/Models/PlaceSharedAndGeocodingTest.php +271 -0
  590. package/server/tests/Unit/Models/PlaceTest.php +589 -0
  591. package/server/tests/Unit/Models/PositionTest.php +49 -0
  592. package/server/tests/Unit/Models/RouteTest.php +55 -0
  593. package/server/tests/Unit/Models/SensorLastPositionDefaultTest.php +130 -0
  594. package/server/tests/Unit/Models/SensorReadingsTest.php +168 -0
  595. package/server/tests/Unit/Models/SensorTest.php +284 -0
  596. package/server/tests/Unit/Models/ServiceAreaGeometryTest.php +129 -0
  597. package/server/tests/Unit/Models/ServiceAreaTest.php +125 -0
  598. package/server/tests/Unit/Models/ServiceQuoteTest.php +617 -0
  599. package/server/tests/Unit/Models/ServiceRateFeeTest.php +73 -0
  600. package/server/tests/Unit/Models/ServiceRatePersistenceAndServicabilityTest.php +428 -0
  601. package/server/tests/Unit/Models/ServiceRateTest.php +533 -0
  602. package/server/tests/Unit/Models/SmallModelSurfacesTest.php +294 -0
  603. package/server/tests/Unit/Models/TrackingNumberTest.php +199 -0
  604. package/server/tests/Unit/Models/TrackingStatusTest.php +131 -0
  605. package/server/tests/Unit/Models/VehicleDeviceTest.php +115 -0
  606. package/server/tests/Unit/Models/VehiclePositionsAndImportTest.php +220 -0
  607. package/server/tests/Unit/Models/VehicleTest.php +266 -0
  608. package/server/tests/Unit/Models/VendorRelationsAndImportTest.php +107 -0
  609. package/server/tests/Unit/Models/WarrantyTest.php +187 -0
  610. package/server/tests/Unit/Models/WaypointTest.php +366 -0
  611. package/server/tests/Unit/Models/WorkOrderTest.php +272 -0
  612. package/server/tests/Unit/Models/ZoneTest.php +83 -0
  613. package/server/tests/Unit/Notifications/MonitoringNotificationsTest.php +136 -0
  614. package/server/tests/Unit/Notifications/OrderAssignedChannelsTest.php +53 -0
  615. package/server/tests/Unit/Notifications/OrderDispatchedChannelsTest.php +81 -0
  616. package/server/tests/Unit/Notifications/OrderFailedChannelsTest.php +73 -0
  617. package/server/tests/Unit/Notifications/OrderPingChannelsTest.php +69 -0
  618. package/server/tests/Unit/Notifications/TerminalOrderNotificationsTest.php +185 -0
  619. package/server/tests/Unit/Observers/FleetOpsCompanyUserAndServiceAreaObserversTest.php +133 -0
  620. package/server/tests/Unit/Observers/ObserverHelperExecutionTest.php +135 -0
  621. package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +267 -0
  622. package/server/tests/Unit/Observers/PurchaseRateObserverAndOrderJobsTest.php +169 -0
  623. package/server/tests/Unit/Orchestration/Engines/DriverAssignmentEngineTest.php +393 -0
  624. package/server/tests/Unit/Orchestration/Engines/VroomOrchestrationEngineLifecycleTest.php +397 -0
  625. package/server/tests/Unit/Orchestration/OrchestrationEngineRegistryTest.php +45 -0
  626. package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +253 -0
  627. package/server/tests/Unit/Providers/FleetOpsAiRegistrationTest.php +59 -0
  628. package/server/tests/Unit/Rules/ResolvableVehicleTest.php +85 -0
  629. package/server/tests/Unit/Support/Ai/AssetStatusCapabilityQueriesTest.php +120 -0
  630. package/server/tests/Unit/Support/Ai/Capabilities/CreateOrderPreviewCapabilityTest.php +385 -0
  631. package/server/tests/Unit/Support/Ai/Capabilities/OrderInsightsCapabilityTest.php +226 -0
  632. package/server/tests/Unit/Support/Ai/Capabilities/SearchResourcesCapabilityTest.php +237 -0
  633. package/server/tests/Unit/Support/AiCapabilityPermissionsTest.php +124 -0
  634. package/server/tests/Unit/Support/AlgoEdgeBranchesTest.php +24 -0
  635. package/server/tests/Unit/Support/Analytics/GeofenceViolationsTest.php +109 -0
  636. package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +205 -0
  637. package/server/tests/Unit/Support/Analytics/MaintenanceOverviewTest.php +236 -0
  638. package/server/tests/Unit/Support/Analytics/OnTimeDeliveryAndTopDriversTest.php +250 -0
  639. package/server/tests/Unit/Support/CreateOrderPreviewDraftingTest.php +235 -0
  640. package/server/tests/Unit/Support/CustomerAuthTest.php +132 -0
  641. package/server/tests/Unit/Support/FuelEfficiencyAndCanceledHandlersTest.php +164 -0
  642. package/server/tests/Unit/Support/FuelProviderRegistryResolutionTest.php +51 -0
  643. package/server/tests/Unit/Support/FuelProviderServiceMatchingTest.php +280 -0
  644. package/server/tests/Unit/Support/GeocodingInjectedClientTest.php +268 -0
  645. package/server/tests/Unit/Support/GeocodingLocateTest.php +32 -0
  646. package/server/tests/Unit/Support/GeofenceIntersectionServiceTest.php +188 -0
  647. package/server/tests/Unit/Support/GettingStartedTest.php +164 -0
  648. package/server/tests/Unit/Support/GuardReturnSeamsTest.php +172 -0
  649. package/server/tests/Unit/Support/IntegratedVendorsResolverTest.php +250 -0
  650. package/server/tests/Unit/Support/IssuesInsightsAndAllocationJobTest.php +156 -0
  651. package/server/tests/Unit/Support/MagicAccessorSeamsTest.php +54 -0
  652. package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +215 -0
  653. package/server/tests/Unit/Support/Metrics/AvgOrderValueMetricTest.php +308 -0
  654. package/server/tests/Unit/Support/Metrics/CountMetricsTest.php +280 -0
  655. package/server/tests/Unit/Support/Metrics/FuelCostsMetricTest.php +102 -0
  656. package/server/tests/Unit/Support/Metrics/MetricQuerySeamsTest.php +129 -0
  657. package/server/tests/Unit/Support/Metrics/OrdersScheduledMetricTest.php +146 -0
  658. package/server/tests/Unit/Support/MoreQuerySeamsTest.php +410 -0
  659. package/server/tests/Unit/Support/OperationalQueryDistributionTest.php +152 -0
  660. package/server/tests/Unit/Support/OperationsPulseTest.php +130 -0
  661. package/server/tests/Unit/Support/OptimizeOrderRouteCapabilityTest.php +147 -0
  662. package/server/tests/Unit/Support/OrchestrationPayloadBuilderTest.php +135 -0
  663. package/server/tests/Unit/Support/OrchestrationRouteAndVehicleTasksTest.php +254 -0
  664. package/server/tests/Unit/Support/OsrmProofAndIssueFilterTest.php +247 -0
  665. package/server/tests/Unit/Support/PetroAppFuelProviderTest.php +165 -0
  666. package/server/tests/Unit/Support/PlaceSearchRankingTest.php +191 -0
  667. package/server/tests/Unit/Support/QuerySeamsTest.php +309 -0
  668. package/server/tests/Unit/Support/ResolvesOrderServiceStopsDbTest.php +303 -0
  669. package/server/tests/Unit/Support/ResolvesOrderServiceStopsTest.php +382 -0
  670. package/server/tests/Unit/Support/SkipAndFallbackGuardsTest.php +70 -0
  671. package/server/tests/Unit/Support/TelematicCredentialResolutionTest.php +31 -0
  672. package/server/tests/Unit/Support/TelematicServiceTest.php +256 -0
  673. package/server/tests/Unit/Support/Telematics/Providers/AbstractProviderTest.php +154 -0
  674. package/server/tests/Unit/Support/Telematics/Providers/AfaqyProviderTest.php +191 -0
  675. package/server/tests/Unit/Support/Telematics/Providers/AfaqyProviderTransportTest.php +138 -0
  676. package/server/tests/Unit/Support/Telematics/Providers/GeotabProviderTest.php +329 -0
  677. package/server/tests/Unit/Support/Telematics/Providers/ProviderTransportSeamsTest.php +133 -0
  678. package/server/tests/Unit/Support/Telematics/Providers/SafeeProviderTest.php +487 -0
  679. package/server/tests/Unit/Support/Telematics/TelematicProviderRegistryTest.php +39 -0
  680. package/server/tests/Unit/Support/Telematics/TelematicServiceLifecycleTest.php +711 -0
  681. package/server/tests/Unit/Support/Telematics/TelematicServiceTelemetryTest.php +364 -0
  682. package/server/tests/Unit/Support/UtilsAdditionalTest.php +256 -0
  683. package/server/tests/Unit/Support/UtilsGeoMatrixAndVendorTest.php +382 -0
  684. package/server/tests/Unit/Tracking/TrackingContextTest.php +114 -0
  685. package/server/tests/Unit/Tracking/TrackingIntelligenceAndContextTest.php +477 -0
  686. package/server/tests/Unit/Tracking/TrackingProviderManagerFallbacksTest.php +208 -0
  687. package/server/tests/Unit/Traits/HasTrackingNumberTest.php +303 -0
  688. package/server/tests/Unit/Traits/PayloadAccessorsTest.php +150 -0
  689. package/server/tests/UtilsContractsTest.php +244 -0
  690. package/server/tests/VehicleControllerHelperContractsTest.php +488 -0
  691. package/server/tests/VehicleDataParsingTest.php +45 -0
  692. package/server/tests/VehicleResourceContractsTest.php +313 -0
  693. package/translations/bg-bg.yaml +2 -0
  694. package/translations/en-us.yaml +2 -0
  695. package/translations/es-pa.yaml +2 -0
  696. package/translations/fr-fr.yaml +2 -0
  697. package/translations/mn-mn.yaml +2 -0
  698. package/translations/pt-br.yaml +2 -0
  699. package/translations/ru-ru.yaml +2 -0
  700. package/server/tests/DeviceFilterTest.php +0 -44
  701. package/server/tests/LiveControllerViewportTest.php +0 -52
@@ -19,6 +19,7 @@ use Fleetbase\FleetOps\Models\ServiceQuote;
19
19
  use Fleetbase\FleetOps\Support\CustomerAuth;
20
20
  use Fleetbase\FleetOps\Support\Utils;
21
21
  use Fleetbase\Http\Controllers\Controller;
22
+ use Fleetbase\LaravelMysqlSpatial\Types\Point as SpatialPoint;
22
23
  use Fleetbase\Models\File;
23
24
  use Fleetbase\Models\User;
24
25
  use Fleetbase\Models\UserDevice;
@@ -41,6 +42,8 @@ use Illuminate\Support\Str;
41
42
  */
42
43
  class CustomerController extends Controller
43
44
  {
45
+ use \Fleetbase\FleetOps\Http\Controllers\Concerns\ResolvesReviewAccountBypass;
46
+
44
47
  /* ============================================================
45
48
  | Public auth flows (API credential only, no Customer-Token)
46
49
  * ============================================================ */
@@ -54,7 +57,7 @@ class CustomerController extends Controller
54
57
  {
55
58
  $mode = $request->input('mode', 'email');
56
59
  $identity = $request->input('identity');
57
- $isEmail = Utils::isEmail($identity);
60
+ $isEmail = $this->isEmail($identity);
58
61
 
59
62
  if ($mode === 'email' && !$isEmail) {
60
63
  return response()->apiError('Invalid email provided for identity.');
@@ -64,7 +67,7 @@ class CustomerController extends Controller
64
67
  $identity = static::phone($identity);
65
68
  }
66
69
 
67
- $sessionCompany = session('company');
70
+ $sessionCompany = $this->sessionCompany();
68
71
  if (!$sessionCompany) {
69
72
  return response()->apiError('No company resolved from API credential.', 500);
70
73
  }
@@ -80,14 +83,12 @@ class CustomerController extends Controller
80
83
  // template references `$user->name`). Look up — or create — the User
81
84
  // before sending. `create()` later backfills password + remaining fields
82
85
  // on this same row when the customer confirms the code.
83
- $subject = $isEmail
84
- ? User::where('email', $identity)->whereNull('deleted_at')->withoutGlobalScopes()->first()
85
- : User::where('phone', $identity)->whereNull('deleted_at')->withoutGlobalScopes()->first();
86
+ $subject = $this->findActiveUserByIdentity($identity, $isEmail ? 'email' : 'phone');
86
87
 
87
88
  if (!$subject) {
88
89
  // `password` and `type` are guarded on User; assign type after create
89
90
  // via setUserType (saves the row).
90
- $subject = User::create([
91
+ $subject = $this->createUser([
91
92
  'company_uuid' => $sessionCompany,
92
93
  'name' => $providedName !== '' ? $providedName : $identity,
93
94
  'email' => $isEmail ? $identity : null,
@@ -108,13 +109,13 @@ class CustomerController extends Controller
108
109
 
109
110
  try {
110
111
  if ($mode === 'email') {
111
- VerificationCode::generateEmailVerificationFor($subject, 'fleetops_create_customer', [
112
+ $this->generateEmailVerification($subject, 'fleetops_create_customer', [
112
113
  'subject' => config('app.name') . ' verification code',
113
114
  'messageCallback' => fn ($verification) => 'Your ' . config('app.name') . ' verification code is ' . $verification->code,
114
115
  'meta' => $meta,
115
116
  ]);
116
117
  } else {
117
- VerificationCode::generateSmsVerificationFor($subject, 'fleetops_create_customer', [
118
+ $this->generateSmsVerification($subject, 'fleetops_create_customer', [
118
119
  'messageCallback' => fn ($verification) => 'Your ' . config('app.name') . ' verification code is ' . $verification->code,
119
120
  'meta' => $meta,
120
121
  ]);
@@ -135,22 +136,22 @@ class CustomerController extends Controller
135
136
  {
136
137
  $code = $request->input('code');
137
138
  $identity = $request->input('identity');
138
- $isEmail = Utils::isEmail($identity);
139
+ $isEmail = $this->isEmail($identity);
139
140
  if (!$isEmail) {
140
141
  $identity = static::phone($identity);
141
142
  }
142
143
 
143
144
  // Verify the code is one we sent for this identity.
144
- $verificationCode = VerificationCode::where([
145
+ $verificationCode = $this->verificationCodeExists([
145
146
  'code' => $code,
146
147
  'for' => 'fleetops_create_customer',
147
148
  'meta->identity' => $identity,
148
- ])->exists();
149
- if (!$verificationCode) {
149
+ ]);
150
+ if (!$verificationCode && !$this->verificationBypassMatches($identity, $code)) {
150
151
  return response()->apiError('Invalid verification code provided.');
151
152
  }
152
153
 
153
- $sessionCompany = session('company');
154
+ $sessionCompany = $this->sessionCompany();
154
155
  if (!$sessionCompany) {
155
156
  return response()->apiError('No company resolved from API credential.', 500);
156
157
  }
@@ -158,15 +159,15 @@ class CustomerController extends Controller
158
159
  // Attach to existing User if one matches the identity, otherwise create one.
159
160
  $user = null;
160
161
  if ($isEmail) {
161
- $user = User::where('email', $identity)->whereNull('deleted_at')->withoutGlobalScopes()->first();
162
+ $user = $this->findActiveUserByIdentity($identity, 'email');
162
163
  } elseif (Str::startsWith($identity, '+')) {
163
- $user = User::where('phone', $identity)->whereNull('deleted_at')->withoutGlobalScopes()->first();
164
+ $user = $this->findActiveUserByIdentity($identity, 'phone');
164
165
  }
165
166
 
166
167
  if (!$user) {
167
168
  // `password` and `type` are guarded on User; assign them after create
168
169
  // (setUserType saves the type, setPasswordAttribute hashes plaintext).
169
- $user = User::create([
170
+ $user = $this->createUser([
170
171
  'company_uuid' => $sessionCompany,
171
172
  'name' => $request->input('name'),
172
173
  'email' => $isEmail ? $identity : $request->input('email'),
@@ -215,15 +216,15 @@ class CustomerController extends Controller
215
216
  // Handle photo as either file id or base64 data.
216
217
  $photo = $request->input('photo');
217
218
  if ($photo) {
218
- if (Utils::isPublicId($photo)) {
219
- $file = File::where('public_id', $photo)->first();
219
+ if ($this->isPublicId($photo)) {
220
+ $file = $this->findFileByPublicId($photo);
220
221
  if ($file) {
221
222
  $input['photo_uuid'] = $file->uuid;
222
223
  }
223
224
  }
224
- if (Utils::isBase64String($photo)) {
225
+ if ($this->isBase64String($photo)) {
225
226
  $path = implode('/', ['uploads', $sessionCompany, 'customers']);
226
- $file = File::createFromBase64($photo, null, $path);
227
+ $file = $this->createFileFromBase64($photo, $path);
227
228
  if ($file) {
228
229
  $input['photo_uuid'] = $file->uuid;
229
230
  }
@@ -232,22 +233,22 @@ class CustomerController extends Controller
232
233
 
233
234
  // Reuse an existing customer-Contact for this user+company if one exists
234
235
  // (idempotent re-signup), otherwise create one.
235
- $contact = Contact::where([
236
+ $contact = $this->findCustomerContact([
236
237
  'company_uuid' => $sessionCompany,
237
238
  'user_uuid' => $user->uuid,
238
239
  'type' => 'customer',
239
- ])->first();
240
+ ]);
240
241
  if ($contact) {
241
242
  $contact->fill(array_filter($input, fn ($v) => $v !== null && $v !== ''))->save();
242
243
  } else {
243
244
  try {
244
- $contact = Contact::create($input);
245
+ $contact = $this->createContact($input);
245
246
  } catch (UserAlreadyExistsException $e) {
246
- $contact = Contact::where([
247
+ $contact = $this->findCustomerContact([
247
248
  'company_uuid' => $sessionCompany,
248
249
  'user_uuid' => $user->uuid,
249
250
  'type' => 'customer',
250
- ])->first();
251
+ ]);
251
252
  if (!$contact) {
252
253
  return response()->apiError($e->getMessage());
253
254
  }
@@ -272,10 +273,10 @@ class CustomerController extends Controller
272
273
  }
273
274
  }
274
275
 
275
- $token = $user->createToken($contact->uuid);
276
+ $token = $this->createCustomerToken($user, $contact);
276
277
  $contact->token = $token->plainTextToken;
277
278
 
278
- return new CustomerResource($contact);
279
+ return $this->customerResource($contact);
279
280
  }
280
281
 
281
282
  /**
@@ -293,7 +294,7 @@ class CustomerController extends Controller
293
294
  }
294
295
 
295
296
  if (is_string($input)) {
296
- return Place::where(['public_id' => $input, 'company_uuid' => $companyUuid])->first();
297
+ return $this->findPlaceByPublicId($input, $companyUuid);
297
298
  }
298
299
 
299
300
  if (!is_array($input)) {
@@ -309,11 +310,19 @@ class CustomerController extends Controller
309
310
  return null;
310
311
  }
311
312
 
312
- return Place::create(array_merge(
313
+ return $this->createPlace(array_merge(
313
314
  [
314
315
  'company_uuid' => $companyUuid,
315
316
  'owner_uuid' => $contact->uuid,
316
317
  'owner_type' => get_class($contact),
318
+ // `places.location` is a NOT NULL POINT column with no database default,
319
+ // and $allowed above is address-only — a caller cannot supply coordinates
320
+ // here. Without this the insert fails with SQLSTATE[HY000] 1364 ("Field
321
+ // 'location' doesn't have a default value"), turning a documented signup
322
+ // payload into a 500. Point(0, 0) is the same placeholder the geocoding
323
+ // helpers fall back to when an address cannot be resolved — see
324
+ // Place::getGoogleAddressArray and Place::findExistingSharedPlace.
325
+ 'location' => new SpatialPoint(0, 0),
317
326
  ],
318
327
  $attributes,
319
328
  ));
@@ -330,20 +339,18 @@ class CustomerController extends Controller
330
339
  return response()->apiError('Identity and password are required.', 400);
331
340
  }
332
341
 
333
- $user = User::where('email', $identity)
334
- ->orWhere('phone', static::phone($identity))
335
- ->first();
342
+ $user = $this->findUserForLogin($identity);
336
343
 
337
- if (!$user || !$user->password || !Hash::check($password, $user->password)) {
344
+ if (!$user || !$user->password || !$this->passwordMatches($password, $user->password)) {
338
345
  return response()->apiError('Authentication failed using credentials provided.', 401);
339
346
  }
340
347
 
341
- $sessionCompany = session('company');
348
+ $sessionCompany = $this->sessionCompany();
342
349
  if (!$sessionCompany) {
343
350
  return response()->apiError('No company resolved from API credential.', 500);
344
351
  }
345
352
 
346
- $contact = Contact::firstOrCreate(
353
+ $contact = $this->firstOrCreateCustomerContact(
347
354
  [
348
355
  'user_uuid' => $user->uuid,
349
356
  'company_uuid' => $sessionCompany,
@@ -356,10 +363,10 @@ class CustomerController extends Controller
356
363
  ]
357
364
  );
358
365
 
359
- $token = $user->createToken($contact->uuid);
366
+ $token = $this->createCustomerToken($user, $contact);
360
367
  $contact->token = $token->plainTextToken;
361
368
 
362
- return new CustomerResource($contact);
369
+ return $this->customerResource($contact);
363
370
  }
364
371
 
365
372
  /**
@@ -369,13 +376,13 @@ class CustomerController extends Controller
369
376
  {
370
377
  $phone = static::phone($request->input('phone') ?? $request->input('identity'));
371
378
 
372
- $user = User::where('phone', $phone)->whereNull('deleted_at')->withoutGlobalScopes()->first();
379
+ $user = $this->findActiveUserByIdentity($phone, 'phone');
373
380
  if (!$user) {
374
381
  return response()->apiError('No customer with this phone number found.');
375
382
  }
376
383
 
377
384
  try {
378
- VerificationCode::generateSmsVerificationFor($user, 'fleetops_customer_login', [
385
+ $this->generateSmsVerification($user, 'fleetops_customer_login', [
379
386
  'messageCallback' => fn ($verification) => 'Your ' . config('app.name') . ' verification code is ' . $verification->code,
380
387
  ]);
381
388
 
@@ -383,7 +390,7 @@ class CustomerController extends Controller
383
390
  } catch (\Throwable $e) {
384
391
  if ($user->email) {
385
392
  try {
386
- VerificationCode::generateEmailVerificationFor($user, 'fleetops_customer_login', [
393
+ $this->generateEmailVerification($user, 'fleetops_customer_login', [
387
394
  'subject' => config('app.name') . ' verification code',
388
395
  'messageCallback' => fn ($verification) => 'Your ' . config('app.name') . ' verification code is ' . $verification->code,
389
396
  ]);
@@ -403,30 +410,30 @@ class CustomerController extends Controller
403
410
  */
404
411
  public function verifyCode(Request $request)
405
412
  {
406
- $identity = Utils::isEmail($request->input('identity')) ? $request->input('identity') : static::phone($request->input('identity'));
413
+ $identity = $this->isEmail($request->input('identity')) ? $request->input('identity') : static::phone($request->input('identity'));
407
414
  $code = $request->input('code');
408
415
  $for = $request->input('for', 'fleetops_customer_login');
409
416
 
410
417
  if ($for === 'fleetops_create_customer') {
411
- return $this->create($request);
418
+ return $this->create(CreateCustomerRequest::createFrom($request));
412
419
  }
413
420
 
414
- $user = User::where('phone', $identity)->orWhere('email', $identity)->first();
421
+ $user = $this->findUserForVerification($identity);
415
422
  if (!$user) {
416
423
  return response()->apiError('Unable to verify code.');
417
424
  }
418
425
 
419
- $verificationCode = VerificationCode::where([
426
+ $verificationCode = $this->verificationCodeExists([
420
427
  'subject_uuid' => $user->uuid,
421
428
  'code' => $code,
422
429
  'for' => $for,
423
- ])->exists();
424
- if (!$verificationCode) {
430
+ ]);
431
+ if (!$verificationCode && !$this->verificationBypassMatches($identity, $code)) {
425
432
  return response()->apiError('Invalid verification code.');
426
433
  }
427
434
 
428
- $sessionCompany = session('company');
429
- $contact = Contact::firstOrCreate(
435
+ $sessionCompany = $this->sessionCompany();
436
+ $contact = $this->firstOrCreateCustomerContact(
430
437
  [
431
438
  'user_uuid' => $user->uuid,
432
439
  'company_uuid' => $sessionCompany,
@@ -439,10 +446,10 @@ class CustomerController extends Controller
439
446
  ]
440
447
  );
441
448
 
442
- $token = $user->createToken($contact->uuid);
449
+ $token = $this->createCustomerToken($user, $contact);
443
450
  $contact->token = $token->plainTextToken;
444
451
 
445
- return new CustomerResource($contact);
452
+ return $this->customerResource($contact);
446
453
  }
447
454
 
448
455
  /**
@@ -455,10 +462,8 @@ class CustomerController extends Controller
455
462
  return response()->apiError('Identity is required.', 400);
456
463
  }
457
464
 
458
- $isEmail = Utils::isEmail($identity);
459
- $user = $isEmail
460
- ? User::where('email', $identity)->first()
461
- : User::where('phone', static::phone($identity))->first();
465
+ $isEmail = $this->isEmail($identity);
466
+ $user = $this->findUserByIdentity($isEmail ? $identity : static::phone($identity), $isEmail ? 'email' : 'phone');
462
467
 
463
468
  if (!$user) {
464
469
  // Don't leak account existence — return success regardless.
@@ -468,13 +473,13 @@ class CustomerController extends Controller
468
473
  $meta = ['identity' => $isEmail ? $identity : static::phone($identity)];
469
474
  try {
470
475
  if ($isEmail) {
471
- VerificationCode::generateEmailVerificationFor($user, 'fleetops_customer_password_reset', [
476
+ $this->generateEmailVerification($user, 'fleetops_customer_password_reset', [
472
477
  'subject' => config('app.name') . ' password reset',
473
478
  'messageCallback' => fn ($v) => 'Your ' . config('app.name') . ' password reset code is ' . $v->code,
474
479
  'meta' => $meta,
475
480
  ]);
476
481
  } else {
477
- VerificationCode::generateSmsVerificationFor($user, 'fleetops_customer_password_reset', [
482
+ $this->generateSmsVerification($user, 'fleetops_customer_password_reset', [
478
483
  'messageCallback' => fn ($v) => 'Your ' . config('app.name') . ' password reset code is ' . $v->code,
479
484
  'meta' => $meta,
480
485
  ]);
@@ -501,21 +506,21 @@ class CustomerController extends Controller
501
506
  return response()->apiError('Password must be at least 8 characters.', 400);
502
507
  }
503
508
 
504
- $isEmail = Utils::isEmail($identity);
509
+ $isEmail = $this->isEmail($identity);
505
510
  $needle = $isEmail ? $identity : static::phone($identity);
506
511
 
507
- $verificationCode = VerificationCode::where([
512
+ $verificationCode = $this->findVerificationCode([
508
513
  'code' => $code,
509
514
  'for' => 'fleetops_customer_password_reset',
510
515
  'meta->identity' => $needle,
511
- ])->first();
512
- if (!$verificationCode) {
516
+ ]);
517
+ // $needle, not $identity: the other verify paths normalise in place, so an
518
+ // allowlisted phone must be compared in the same normalised form here too.
519
+ if (!$verificationCode && !$this->verificationBypassMatches($needle, $code)) {
513
520
  return response()->apiError('Invalid reset code.');
514
521
  }
515
522
 
516
- $user = $isEmail
517
- ? User::where('email', $needle)->first()
518
- : User::where('phone', $needle)->first();
523
+ $user = $this->findUserByIdentity($needle, $isEmail ? 'email' : 'phone');
519
524
  if (!$user) {
520
525
  return response()->apiError('Account not found.');
521
526
  }
@@ -524,8 +529,11 @@ class CustomerController extends Controller
524
529
  $user->password = $password;
525
530
  $user->save();
526
531
  // Invalidate all existing sessions for this user after a password reset.
527
- $user->tokens()->delete();
528
- $verificationCode->delete();
532
+ $this->deleteUserTokens($user);
533
+ // Null on the testing-bypass path — there is no row to consume.
534
+ if ($verificationCode) {
535
+ $verificationCode->delete();
536
+ }
529
537
 
530
538
  return response()->json(['status' => 'ok']);
531
539
  }
@@ -539,12 +547,12 @@ class CustomerController extends Controller
539
547
  */
540
548
  public function me()
541
549
  {
542
- $customer = CustomerAuth::current();
550
+ $customer = $this->currentCustomer();
543
551
  if (!$customer) {
544
552
  return response()->apiError('Not authenticated.', 401);
545
553
  }
546
554
 
547
- return new CustomerResource($customer);
555
+ return $this->customerResource($customer);
548
556
  }
549
557
 
550
558
  /**
@@ -552,7 +560,7 @@ class CustomerController extends Controller
552
560
  */
553
561
  public function updateMe(UpdateContactRequest $request)
554
562
  {
555
- $customer = CustomerAuth::current();
563
+ $customer = $this->currentCustomer();
556
564
  if (!$customer) {
557
565
  return response()->apiError('Not authenticated.', 401);
558
566
  }
@@ -565,15 +573,15 @@ class CustomerController extends Controller
565
573
  // Photo handling.
566
574
  $photo = $request->input('photo');
567
575
  if ($photo) {
568
- if (Utils::isPublicId($photo)) {
569
- $file = File::where('public_id', $photo)->first();
576
+ if ($this->isPublicId($photo)) {
577
+ $file = $this->findFileByPublicId($photo);
570
578
  if ($file) {
571
579
  $input['photo_uuid'] = $file->uuid;
572
580
  }
573
581
  }
574
- if (Utils::isBase64String($photo)) {
575
- $path = implode('/', ['uploads', session('company'), 'customers']);
576
- $file = File::createFromBase64($photo, null, $path);
582
+ if ($this->isBase64String($photo)) {
583
+ $path = implode('/', ['uploads', $this->sessionCompany(), 'customers']);
584
+ $file = $this->createFileFromBase64($photo, $path);
577
585
  if ($file) {
578
586
  $input['photo_uuid'] = $file->uuid;
579
587
  }
@@ -597,11 +605,11 @@ class CustomerController extends Controller
597
605
  'phone' => $input['phone'] ?? null,
598
606
  ], fn ($v) => $v !== null);
599
607
  if (!empty($userUpdate)) {
600
- User::where('uuid', $customer->user_uuid)->update($userUpdate);
608
+ $this->updateUserByUuid($customer->user_uuid, $userUpdate);
601
609
  }
602
610
  }
603
611
 
604
- return new CustomerResource($customer->fresh());
612
+ return $this->customerResource($customer->fresh());
605
613
  }
606
614
 
607
615
  /**
@@ -609,13 +617,13 @@ class CustomerController extends Controller
609
617
  */
610
618
  public function logout(Request $request)
611
619
  {
612
- $customer = CustomerAuth::current();
620
+ $customer = $this->currentCustomer();
613
621
  if (!$customer) {
614
622
  return response()->apiError('Not authenticated.', 401);
615
623
  }
616
624
 
617
625
  $tokenString = $request->header(CustomerAuth::HEADER);
618
- $accessToken = $tokenString ? \Laravel\Sanctum\PersonalAccessToken::findToken($tokenString) : null;
626
+ $accessToken = $tokenString ? $this->findAccessToken($tokenString) : null;
619
627
  if ($accessToken) {
620
628
  $accessToken->delete();
621
629
  }
@@ -628,14 +636,14 @@ class CustomerController extends Controller
628
636
  */
629
637
  public function logoutAll()
630
638
  {
631
- $customer = CustomerAuth::current();
639
+ $customer = $this->currentCustomer();
632
640
  if (!$customer || !$customer->user_uuid) {
633
641
  return response()->apiError('Not authenticated.', 401);
634
642
  }
635
643
 
636
- $user = User::where('uuid', $customer->user_uuid)->first();
644
+ $user = $this->findUserByUuid($customer->user_uuid);
637
645
  if ($user) {
638
- $user->tokens()->delete();
646
+ $this->deleteUserTokens($user);
639
647
  }
640
648
 
641
649
  return response()->json(['status' => 'ok']);
@@ -646,18 +654,18 @@ class CustomerController extends Controller
646
654
  */
647
655
  public function orders(Request $request)
648
656
  {
649
- $customer = CustomerAuth::current();
657
+ $customer = $this->currentCustomer();
650
658
  if (!$customer) {
651
659
  return response()->apiError('Not authenticated.', 401);
652
660
  }
653
661
 
654
- $results = Order::queryWithRequest($request, function (&$query) use ($customer) {
662
+ $results = $this->queryOrders($request, function (&$query) use ($customer) {
655
663
  $query->where('customer_uuid', $customer->uuid)
656
664
  ->whereNull('deleted_at')
657
665
  ->withoutGlobalScopes();
658
666
  });
659
667
 
660
- return OrderResource::collection($results);
668
+ return $this->orderResourceCollection($results);
661
669
  }
662
670
 
663
671
  /**
@@ -665,13 +673,13 @@ class CustomerController extends Controller
665
673
  */
666
674
  public function findOrder(string $id)
667
675
  {
668
- $customer = CustomerAuth::current();
676
+ $customer = $this->currentCustomer();
669
677
  if (!$customer) {
670
678
  return response()->apiError('Not authenticated.', 401);
671
679
  }
672
680
 
673
681
  try {
674
- $order = Order::findRecordOrFail($id);
682
+ $order = $this->findOrderOrFail($id);
675
683
  } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
676
684
  return response()->apiError('Order not found.', 404);
677
685
  }
@@ -680,7 +688,7 @@ class CustomerController extends Controller
680
688
  return response()->apiError('Order not found.', 404);
681
689
  }
682
690
 
683
- return new OrderResource($order);
691
+ return $this->orderResource($order);
684
692
  }
685
693
 
686
694
  /**
@@ -699,19 +707,18 @@ class CustomerController extends Controller
699
707
  */
700
708
  public function createOrder(CreateCustomerOrderRequest $request)
701
709
  {
702
- $customer = CustomerAuth::current();
710
+ $customer = $this->currentCustomer();
703
711
  if (!$customer) {
704
712
  return response()->apiError('Not authenticated.', 401);
705
713
  }
706
714
 
707
- $sessionCompany = session('company');
715
+ $sessionCompany = $this->sessionCompany();
708
716
  if (!$sessionCompany) {
709
717
  return response()->apiError('No company resolved from API credential.', 500);
710
718
  }
711
719
 
712
720
  // Resolve the order config for this order. Mirrors OrderController::create.
713
- $orderConfig = OrderConfig::resolveFromIdentifier($request->only(['type', 'order_config']))
714
- ?: OrderConfig::where('company_uuid', $sessionCompany)->first();
721
+ $orderConfig = $this->resolveOrderConfig($request, $sessionCompany);
715
722
  if (!$orderConfig) {
716
723
  return response()->apiError('No order config available for this company.', 422);
717
724
  }
@@ -725,7 +732,7 @@ class CustomerController extends Controller
725
732
  $payloadInput = (array) $request->input('payload');
726
733
  $payloadUuid = $this->buildPayloadFromInput($payloadInput, $sessionCompany)->uuid;
727
734
  } elseif ($request->isString('payload')) {
728
- $payloadUuid = Utils::getUuid('payloads', [
735
+ $payloadUuid = $this->getUuid('payloads', [
729
736
  'public_id' => $request->input('payload'),
730
737
  'company_uuid' => $sessionCompany,
731
738
  ]);
@@ -736,10 +743,10 @@ class CustomerController extends Controller
736
743
  }
737
744
  }
738
745
 
739
- $order = Order::create([
746
+ $order = $this->createOrderRecord([
740
747
  'company_uuid' => $sessionCompany,
741
748
  'customer_uuid' => $customer->uuid,
742
- 'customer_type' => Utils::getModelClassName('contact'),
749
+ 'customer_type' => $this->getModelClassName('contact'),
743
750
  'payload_uuid' => $payloadUuid,
744
751
  'order_config_uuid' => $orderConfig->uuid,
745
752
  'type' => $orderConfig->key,
@@ -753,12 +760,12 @@ class CustomerController extends Controller
753
760
  // If the customer picked a ServiceQuote up front, consume it now to
754
761
  // lock the pricing onto the order's PurchaseRate (mirrors how
755
762
  // OrderController::create handles `service_quote`).
756
- $serviceQuote = ServiceQuote::resolveFromRequest($request);
763
+ $serviceQuote = $this->resolveServiceQuote($request);
757
764
  if ($serviceQuote instanceof ServiceQuote) {
758
765
  $order->purchaseServiceQuote($serviceQuote);
759
766
  }
760
767
 
761
- return new OrderResource($order->fresh(['payload', 'payload.pickup', 'payload.dropoff', 'payload.entities']));
768
+ return $this->orderResource($order->fresh(['payload', 'payload.pickup', 'payload.dropoff', 'payload.entities']));
762
769
  }
763
770
 
764
771
  /**
@@ -769,7 +776,7 @@ class CustomerController extends Controller
769
776
  */
770
777
  protected function buildPayloadFromInput(array $payloadInput, string $companyUuid): Payload
771
778
  {
772
- $payload = new Payload();
779
+ $payload = $this->newPayload();
773
780
  $entities = data_get($payloadInput, 'entities', []);
774
781
  $waypoints = data_get($payloadInput, 'waypoints', []);
775
782
  $pickup = data_get($payloadInput, 'pickup');
@@ -810,16 +817,16 @@ class CustomerController extends Controller
810
817
  */
811
818
  public function places(Request $request)
812
819
  {
813
- $customer = CustomerAuth::current();
820
+ $customer = $this->currentCustomer();
814
821
  if (!$customer) {
815
822
  return response()->apiError('Not authenticated.', 401);
816
823
  }
817
824
 
818
- $results = Place::queryWithRequest($request, function (&$query) use ($customer) {
825
+ $results = $this->queryPlaces($request, function (&$query) use ($customer) {
819
826
  $query->where('owner_uuid', $customer->uuid);
820
827
  });
821
828
 
822
- return PlaceResource::collection($results);
829
+ return $this->placeResourceCollection($results);
823
830
  }
824
831
 
825
832
  /**
@@ -827,12 +834,12 @@ class CustomerController extends Controller
827
834
  */
828
835
  public function registerDevice(Request $request)
829
836
  {
830
- $customer = CustomerAuth::current();
837
+ $customer = $this->currentCustomer();
831
838
  if (!$customer) {
832
839
  return response()->apiError('Not authenticated.', 401);
833
840
  }
834
841
 
835
- $device = UserDevice::firstOrCreate(
842
+ $device = $this->firstOrCreateDevice(
836
843
  [
837
844
  'token' => $request->input('token'),
838
845
  'platform' => $request->input('platform', $request->input('os')),
@@ -855,6 +862,244 @@ class CustomerController extends Controller
855
862
  | Helpers
856
863
  * ============================================================ */
857
864
 
865
+ protected function isEmail(?string $identity): bool
866
+ {
867
+ return Utils::isEmail($identity);
868
+ }
869
+
870
+ protected function isPublicId(?string $value): bool
871
+ {
872
+ return Utils::isPublicId($value);
873
+ }
874
+
875
+ protected function isBase64String(?string $value): bool
876
+ {
877
+ return Utils::isBase64String($value);
878
+ }
879
+
880
+ protected function sessionCompany(): ?string
881
+ {
882
+ return session('company');
883
+ }
884
+
885
+ protected function currentCustomer(): ?Contact
886
+ {
887
+ return CustomerAuth::current();
888
+ }
889
+
890
+ protected function findActiveUserByIdentity(string $identity, string $column): ?User
891
+ {
892
+ return User::where($column, $identity)->whereNull('deleted_at')->withoutGlobalScopes()->first();
893
+ }
894
+
895
+ protected function findUserByIdentity(string $identity, string $column): ?User
896
+ {
897
+ return User::where($column, $identity)->first();
898
+ }
899
+
900
+ protected function findUserForLogin(string $identity): ?User
901
+ {
902
+ return User::where('email', $identity)
903
+ ->orWhere('phone', static::phone($identity))
904
+ ->first();
905
+ }
906
+
907
+ protected function findUserForVerification(string $identity): ?User
908
+ {
909
+ return User::where('phone', $identity)->orWhere('email', $identity)->first();
910
+ }
911
+
912
+ protected function findUserByUuid(string $uuid): ?User
913
+ {
914
+ return User::where('uuid', $uuid)->first();
915
+ }
916
+
917
+ protected function createUser(array $attributes): User
918
+ {
919
+ return User::create($attributes);
920
+ }
921
+
922
+ protected function passwordMatches(string $password, string $hash): bool
923
+ {
924
+ return Hash::check($password, $hash);
925
+ }
926
+
927
+ protected function generateEmailVerification(User $user, string $for, array $options): mixed
928
+ {
929
+ return VerificationCode::generateEmailVerificationFor($user, $for, $options);
930
+ }
931
+
932
+ protected function generateSmsVerification(User $user, string $for, array $options): mixed
933
+ {
934
+ return VerificationCode::generateSmsVerificationFor($user, $for, $options);
935
+ }
936
+
937
+ /**
938
+ * Whether the supplied code matches the configured testing bypass code.
939
+ *
940
+ * Three conditions, all required, mirroring the console equivalent in
941
+ * Fleetbase\Http\Controllers\Internal\v1\AuthController::authenticateWithVerificationCode:
942
+ * a bypass code must actually be configured, the app must not be running in
943
+ * production, and the comparison is constant-time.
944
+ *
945
+ * Fails safe by default: config/app.php resolves `env` to `production` when
946
+ * neither APP_ENV nor ENVIRONMENT is set, so an unconfigured install cannot
947
+ * be bypassed even accidentally.
948
+ *
949
+ * `!== null && !== ''` rather than `!empty()` — `!empty('0')` is false, so a
950
+ * configured bypass code of "0" would otherwise be silently ignored.
951
+ *
952
+ * Kept out of verificationCodeExists()/findVerificationCode() on purpose:
953
+ * those two are test seams that the controller contract tests override, so a
954
+ * policy living inside them would be stubbed away exactly where it matters.
955
+ */
956
+ protected function verificationBypassMatches(?string $identity, ?string $code): bool
957
+ {
958
+ return static::reviewAccountBypassMatches(
959
+ 'fleetops.customers.verification_bypass_code',
960
+ 'fleetops.customers.review_accounts',
961
+ $identity,
962
+ $code,
963
+ 'fleetops-customer'
964
+ );
965
+ }
966
+
967
+ protected function verificationCodeExists(array $attributes): bool
968
+ {
969
+ return VerificationCode::where($attributes)->exists();
970
+ }
971
+
972
+ protected function findVerificationCode(array $attributes): mixed
973
+ {
974
+ return VerificationCode::where($attributes)->first();
975
+ }
976
+
977
+ protected function findFileByPublicId(string $publicId): mixed
978
+ {
979
+ return File::where('public_id', $publicId)->first();
980
+ }
981
+
982
+ protected function createFileFromBase64(string $contents, string $path): mixed
983
+ {
984
+ return File::createFromBase64($contents, null, $path);
985
+ }
986
+
987
+ protected function findCustomerContact(array $attributes): ?Contact
988
+ {
989
+ return Contact::where($attributes)->first();
990
+ }
991
+
992
+ protected function createContact(array $attributes): Contact
993
+ {
994
+ return Contact::create($attributes);
995
+ }
996
+
997
+ protected function firstOrCreateCustomerContact(array $attributes, array $values): Contact
998
+ {
999
+ return Contact::firstOrCreate($attributes, $values);
1000
+ }
1001
+
1002
+ protected function createCustomerToken(User $user, Contact $contact): mixed
1003
+ {
1004
+ return $user->createToken($contact->uuid);
1005
+ }
1006
+
1007
+ protected function findPlaceByPublicId(string $publicId, string $companyUuid): ?Place
1008
+ {
1009
+ return Place::where(['public_id' => $publicId, 'company_uuid' => $companyUuid])->first();
1010
+ }
1011
+
1012
+ protected function createPlace(array $attributes): Place
1013
+ {
1014
+ return Place::create($attributes);
1015
+ }
1016
+
1017
+ protected function updateUserByUuid(string $uuid, array $attributes): mixed
1018
+ {
1019
+ return User::where('uuid', $uuid)->update($attributes);
1020
+ }
1021
+
1022
+ protected function findAccessToken(string $token): mixed
1023
+ {
1024
+ return \Laravel\Sanctum\PersonalAccessToken::findToken($token);
1025
+ }
1026
+
1027
+ protected function deleteUserTokens(User $user): void
1028
+ {
1029
+ $user->tokens()->delete();
1030
+ }
1031
+
1032
+ protected function queryOrders(Request $request, callable $callback): mixed
1033
+ {
1034
+ return Order::queryWithRequest($request, $callback);
1035
+ }
1036
+
1037
+ protected function findOrderOrFail(string $id): Order
1038
+ {
1039
+ return Order::findRecordOrFail($id);
1040
+ }
1041
+
1042
+ protected function resolveOrderConfig(CreateCustomerOrderRequest $request, string $companyUuid): ?OrderConfig
1043
+ {
1044
+ return OrderConfig::resolveFromIdentifier($request->only(['type', 'order_config']))
1045
+ ?: OrderConfig::where('company_uuid', $companyUuid)->first();
1046
+ }
1047
+
1048
+ protected function getUuid(array|string $table, array $where, array $options = []): mixed
1049
+ {
1050
+ return Utils::getUuid($table, $where, $options);
1051
+ }
1052
+
1053
+ protected function getModelClassName(?string $table): ?string
1054
+ {
1055
+ return Utils::getModelClassName($table);
1056
+ }
1057
+
1058
+ protected function createOrderRecord(array $attributes): Order
1059
+ {
1060
+ return Order::create($attributes);
1061
+ }
1062
+
1063
+ protected function resolveServiceQuote(CreateCustomerOrderRequest $request): mixed
1064
+ {
1065
+ return ServiceQuote::resolveFromRequest($request);
1066
+ }
1067
+
1068
+ protected function newPayload(): Payload
1069
+ {
1070
+ return new Payload();
1071
+ }
1072
+
1073
+ protected function queryPlaces(Request $request, callable $callback): mixed
1074
+ {
1075
+ return Place::queryWithRequest($request, $callback);
1076
+ }
1077
+
1078
+ protected function firstOrCreateDevice(array $attributes, array $values): mixed
1079
+ {
1080
+ return UserDevice::firstOrCreate($attributes, $values);
1081
+ }
1082
+
1083
+ protected function customerResource(Contact $contact): mixed
1084
+ {
1085
+ return new CustomerResource($contact);
1086
+ }
1087
+
1088
+ protected function orderResource(Order $order): mixed
1089
+ {
1090
+ return new OrderResource($order);
1091
+ }
1092
+
1093
+ protected function orderResourceCollection(mixed $results): mixed
1094
+ {
1095
+ return OrderResource::collection($results);
1096
+ }
1097
+
1098
+ protected function placeResourceCollection(mixed $results): mixed
1099
+ {
1100
+ return PlaceResource::collection($results);
1101
+ }
1102
+
858
1103
  /**
859
1104
  * Normalize a phone number to international format (with leading `+`).
860
1105
  */