@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
@@ -0,0 +1,1742 @@
1
+ <?php
2
+
3
+ use Fleetbase\FleetOps\Flow\Activity;
4
+ use Fleetbase\FleetOps\Http\Controllers\Internal\v1\OrderController;
5
+ use Fleetbase\FleetOps\Http\Requests\BulkDispatchRequest;
6
+ use Fleetbase\FleetOps\Http\Requests\CancelOrderRequest;
7
+ use Fleetbase\FleetOps\Models\Driver;
8
+ use Fleetbase\FleetOps\Models\Entity;
9
+ use Fleetbase\FleetOps\Models\Order;
10
+ use Fleetbase\FleetOps\Models\OrderConfig;
11
+ use Fleetbase\FleetOps\Models\Payload;
12
+ use Fleetbase\FleetOps\Models\Place;
13
+ use Fleetbase\FleetOps\Models\Proof;
14
+ use Fleetbase\FleetOps\Models\TrackingStatus;
15
+ use Fleetbase\FleetOps\Models\Waypoint;
16
+ use Fleetbase\FleetOps\Support\OrderTracker;
17
+ use Fleetbase\Http\Requests\ExportRequest;
18
+ use Fleetbase\Models\Type;
19
+ use Illuminate\Database\ConnectionResolver;
20
+ use Illuminate\Database\Eloquent\Model as EloquentModel;
21
+ use Illuminate\Database\Eloquent\ModelNotFoundException;
22
+ use Illuminate\Database\SQLiteConnection;
23
+ use Illuminate\Http\Request;
24
+ use Illuminate\Http\UploadedFile;
25
+ use Illuminate\Support\Collection;
26
+
27
+ class FleetOpsInternalOrderLifecycleEventRecorder
28
+ {
29
+ public static array $events = [];
30
+ }
31
+
32
+ if (!function_exists('Fleetbase\FleetOps\Http\Controllers\Internal\v1\event')) {
33
+ eval('namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1; function event($event = null) { \FleetOpsInternalOrderLifecycleEventRecorder::$events[] = $event; return $event; }');
34
+ }
35
+
36
+ if (!class_exists('Fleetbase\Http\Requests\ExportRequest', false)) {
37
+ eval('namespace Fleetbase\Http\Requests; class ExportRequest extends \Illuminate\Http\Request {}');
38
+ }
39
+
40
+ class FleetOpsInternalOrderLifecycleControllerProbe extends OrderController
41
+ {
42
+ public Collection $orders;
43
+ public ?FleetOpsInternalOrderLifecycleOrderFake $order = null;
44
+ public ?FleetOpsInternalOrderLifecycleDriverFake $driver = null;
45
+ public array $trackingStatusExists = [];
46
+ public array $assignedOrderUuids = [];
47
+ public ?string $assignedDriverUuid = null;
48
+ public array $bulkNotification = [];
49
+ public int $transactions = 0;
50
+ public array $trackingNumberStatuses = [];
51
+ public ?FleetOpsInternalOrderLifecycleOrderFake $startOrder = null;
52
+ public ?FleetOpsInternalOrderLifecycleDriverFake $startDriver = null;
53
+ public ?FleetOpsInternalOrderLifecyclePayloadFake $startPayload = null;
54
+ public array $domainEvents = [];
55
+ public array $activityUpdates = [];
56
+ public bool $canPingDriver = true;
57
+ public bool $pingOrderMissing = false;
58
+ public bool $pingSendFails = false;
59
+ public ?FleetOpsInternalOrderLifecycleOrderFake $pingOrder = null;
60
+ public ?FleetOpsInternalOrderLifecycleOrderFake $labelOrder = null;
61
+ public ?FleetOpsInternalOrderLifecycleWaypointFake $labelWaypoint = null;
62
+ public ?FleetOpsInternalOrderLifecycleEntityFake $labelEntity = null;
63
+ public Collection $customTypes;
64
+ public array $defaultTypes = [];
65
+ public ?FleetOpsInternalOrderLifecycleOrderFake $proofOrder = null;
66
+ public ?FleetOpsInternalOrderLifecycleWaypointFake $proofWaypoint = null;
67
+ public ?FleetOpsInternalOrderLifecycleEntityFake $proofEntity = null;
68
+ public Collection $proofResults;
69
+ public array $exportDownloads = [];
70
+ public ?FleetOpsInternalOrderLifecycleOrderFake $trackingOrder = null;
71
+ public ?FleetOpsInternalOrderLifecycleOrderFake $scheduleOrder = null;
72
+ public ?FleetOpsInternalOrderLifecycleDriverFake $scheduleDriver = null;
73
+
74
+ public function callHelper(string $method, mixed ...$arguments): mixed
75
+ {
76
+ $reflection = new ReflectionMethod(OrderController::class, $method);
77
+ $reflection->setAccessible(true);
78
+
79
+ return $reflection->invoke($this, ...$arguments);
80
+ }
81
+
82
+ public function appendProofInputsForTest(array &$incoming, mixed $value): void
83
+ {
84
+ $this->appendProofPhotoInputs($incoming, $value);
85
+ }
86
+
87
+ protected function findOrderForStart(?string $uuid): ?Order
88
+ {
89
+ $this->startOrder?->setAttribute('start_lookup_uuid', $uuid);
90
+
91
+ return $this->startOrder;
92
+ }
93
+
94
+ protected function findDriverForStart(?string $uuid): ?Driver
95
+ {
96
+ $this->startDriver?->setAttribute('start_lookup_uuid', $uuid);
97
+
98
+ return $this->startDriver;
99
+ }
100
+
101
+ protected function findPayloadForStart(?string $uuid): ?Payload
102
+ {
103
+ $this->startPayload?->setAttribute('start_lookup_uuid', $uuid);
104
+
105
+ return $this->startPayload;
106
+ }
107
+
108
+ protected function dispatchDomainEvent(object $event): object
109
+ {
110
+ $this->domainEvents[] = $event::class;
111
+
112
+ return $event;
113
+ }
114
+
115
+ protected function orderStartedEvent(Order $order): object
116
+ {
117
+ return new FleetOpsInternalOrderLifecycleStartedEventFake($order);
118
+ }
119
+
120
+ public function updateActivity(string $id, Request $request)
121
+ {
122
+ $this->activityUpdates[] = [$id, $request->input('activity')];
123
+
124
+ return response()->json(['updated' => $id, 'activity' => data_get($request->input('activity'), 'code')]);
125
+ }
126
+
127
+ protected function canPingDriver(): bool
128
+ {
129
+ return $this->canPingDriver;
130
+ }
131
+
132
+ protected function findOrderForDriverPing(string $id): Order
133
+ {
134
+ if ($this->pingOrderMissing) {
135
+ throw new ModelNotFoundException();
136
+ }
137
+
138
+ $this->pingOrder?->setAttribute('ping_lookup_id', $id);
139
+
140
+ return $this->pingOrder ?? fleetopsInternalOrderLifecycleOrder('ping-order');
141
+ }
142
+
143
+ protected function sendDriverPing(Driver $driver, Order $order): void
144
+ {
145
+ if ($this->pingSendFails) {
146
+ throw new RuntimeException('notification failed');
147
+ }
148
+
149
+ $driver->notifications[] = Fleetbase\FleetOps\Notifications\OrderPing::class;
150
+ }
151
+
152
+ protected function defaultOrderTypeConfig(): array
153
+ {
154
+ return $this->defaultTypes;
155
+ }
156
+
157
+ protected function customOrderTypes()
158
+ {
159
+ return $this->customTypes ?? collect();
160
+ }
161
+
162
+ protected function findOrderLabelSubject(string $id): ?Order
163
+ {
164
+ $this->labelOrder?->setAttribute('label_lookup_id', $id);
165
+
166
+ return $this->labelOrder;
167
+ }
168
+
169
+ protected function findWaypointLabelSubject(string $id): ?Waypoint
170
+ {
171
+ $this->labelWaypoint?->setAttribute('label_lookup_id', $id);
172
+
173
+ return $this->labelWaypoint;
174
+ }
175
+
176
+ protected function findEntityLabelSubject(string $id): ?Entity
177
+ {
178
+ $this->labelEntity?->setAttribute('label_lookup_id', $id);
179
+
180
+ return $this->labelEntity;
181
+ }
182
+
183
+ protected function findOrderForProofs(string $id): ?Order
184
+ {
185
+ $this->proofOrder?->setAttribute('proof_lookup_id', $id);
186
+
187
+ return $this->proofOrder;
188
+ }
189
+
190
+ protected function findWaypointProofSubject(Order $order, string $subjectId): ?Waypoint
191
+ {
192
+ $this->proofWaypoint?->setAttribute('proof_lookup_id', $subjectId);
193
+
194
+ return $this->proofWaypoint;
195
+ }
196
+
197
+ protected function findEntityProofSubject(string $subjectId): ?Entity
198
+ {
199
+ $this->proofEntity?->setAttribute('proof_lookup_id', $subjectId);
200
+
201
+ return $this->proofEntity;
202
+ }
203
+
204
+ protected function proofsForSubject(Order $order, Order|Waypoint|Entity $subject)
205
+ {
206
+ $this->proofResults ??= collect();
207
+ $this->proofResults->each(fn (Proof $proof) => $proof->setAttribute('queried_subject_uuid', $subject->uuid));
208
+
209
+ return $this->proofResults;
210
+ }
211
+
212
+ protected function downloadOrderExport(array $selections, string $fileName)
213
+ {
214
+ $this->exportDownloads[] = [$selections, $fileName];
215
+
216
+ return ['download' => $fileName, 'selections' => $selections];
217
+ }
218
+
219
+ protected function findOrderByTrackingNumber(string $trackingNumber): ?Order
220
+ {
221
+ $this->trackingOrder?->setAttribute('tracking_lookup', $trackingNumber);
222
+
223
+ return $this->trackingOrder;
224
+ }
225
+
226
+ protected function findOrderForSchedule(?string $id): ?Order
227
+ {
228
+ $this->scheduleOrder?->setAttribute('schedule_lookup_id', $id);
229
+
230
+ return $this->scheduleOrder;
231
+ }
232
+
233
+ protected function findDriverForSchedule(string $id): ?Driver
234
+ {
235
+ $this->scheduleDriver?->setAttribute('schedule_lookup_id', $id);
236
+
237
+ return $this->scheduleDriver;
238
+ }
239
+
240
+ protected function payloadUsesServiceStopActivity(?Payload $payload): bool
241
+ {
242
+ return false;
243
+ }
244
+
245
+ protected function makeTextResponse(string $text)
246
+ {
247
+ return $text;
248
+ }
249
+
250
+ protected function findOrderRouteForEdit(string $uuid): ?Order
251
+ {
252
+ $this->order?->setAttribute('route_lookup_uuid', $uuid);
253
+
254
+ return $this->order;
255
+ }
256
+
257
+ protected function orderResponse(Order $order): array
258
+ {
259
+ return ['order' => $order];
260
+ }
261
+
262
+ protected function ordersByUuid(array $ids)
263
+ {
264
+ $this->orders->each(fn ($order) => $order->setAttribute('queried_ids', $ids));
265
+
266
+ return $this->orders;
267
+ }
268
+
269
+ protected function trackingStatusExists(?string $trackingNumberUuid, string $code): bool
270
+ {
271
+ return $this->trackingStatusExists[$trackingNumberUuid . ':' . $code] ?? false;
272
+ }
273
+
274
+ protected function findDriverByUuid(string $uuid): ?Driver
275
+ {
276
+ $this->driver?->setAttribute('lookup_uuid', $uuid);
277
+
278
+ return $this->driver;
279
+ }
280
+
281
+ protected function driverDisplayName(Driver $driver): string
282
+ {
283
+ return 'Ada Driver';
284
+ }
285
+
286
+ protected function validateBulkAssignDriverRequest(Request $request): array
287
+ {
288
+ return [
289
+ 'ids' => $request->input('ids', []),
290
+ 'driver' => $request->input('driver'),
291
+ ];
292
+ }
293
+
294
+ protected function findOrderByUuid(string $uuid): ?Order
295
+ {
296
+ $this->order?->setAttribute('lookup_uuid', $uuid);
297
+
298
+ return $this->order;
299
+ }
300
+
301
+ protected function findOrderById(string $id, array $with = []): ?Order
302
+ {
303
+ $this->order?->setAttribute('lookup_id', $id);
304
+ $this->order?->setAttribute('lookup_with', $with);
305
+
306
+ return $this->order;
307
+ }
308
+
309
+ protected function assignDriverToOrders($orderUuids, Driver $driver): void
310
+ {
311
+ $this->assignedOrderUuids = $orderUuids->all();
312
+ $this->assignedDriverUuid = $driver->uuid;
313
+ }
314
+
315
+ protected function dispatchBulkAssignedDriverNotification(array $orderUuids, Driver $driver): void
316
+ {
317
+ $this->bulkNotification = [$orderUuids, $driver->uuid];
318
+ }
319
+
320
+ protected function runTransaction(callable $callback): mixed
321
+ {
322
+ $this->transactions++;
323
+
324
+ return $callback();
325
+ }
326
+
327
+ protected function trackingNumberStatus(string $trackingNumberUuid): ?TrackingStatus
328
+ {
329
+ return $this->trackingNumberStatuses[$trackingNumberUuid] ?? null;
330
+ }
331
+ }
332
+
333
+ class FleetOpsInternalOrderLifecycleOrderFake extends Order
334
+ {
335
+ public bool $canceledForTest = false;
336
+ public bool $dispatchedForTest = false;
337
+ public bool $dispatchedWithActivityForTest = false;
338
+ public bool $hasDriverAssignedForTest = true;
339
+ public bool $adhocForTest = false;
340
+ public bool $dispatchedAttributeForTest = false;
341
+ public bool $hasOrderConfigForTest = true;
342
+ public ?FleetOpsInternalOrderLifecycleTrackerFake $trackerForTest = null;
343
+ public array $attachedFiles = [];
344
+ public array $customFieldValues = [];
345
+ public array $loadedMissing = [];
346
+ public array $loadedRelations = [];
347
+ public bool $savedForTest = false;
348
+ public bool $quietlySavedForTest = false;
349
+ public ?FleetOpsInternalOrderLifecycleConfigFake $configForTest = null;
350
+
351
+ public function attachFiles($files): self
352
+ {
353
+ $this->attachedFiles = $files;
354
+
355
+ return $this;
356
+ }
357
+
358
+ public function syncCustomFieldValues(array $customFieldValues, array $options = []): array
359
+ {
360
+ $this->customFieldValues = $customFieldValues;
361
+
362
+ return $customFieldValues;
363
+ }
364
+
365
+ public function loadMissing($relations)
366
+ {
367
+ $this->loadedMissing[] = $relations;
368
+
369
+ return $this;
370
+ }
371
+
372
+ public function save(array $options = [])
373
+ {
374
+ $this->savedForTest = true;
375
+
376
+ return true;
377
+ }
378
+
379
+ public function saveQuietly(array $options = [])
380
+ {
381
+ $this->quietlySavedForTest = true;
382
+
383
+ return true;
384
+ }
385
+
386
+ public function setStartedAtAttribute($value): void
387
+ {
388
+ $this->attributes['started_at'] = $value;
389
+ }
390
+
391
+ public function setScheduledAtAttribute($value): void
392
+ {
393
+ $this->attributes['scheduled_at'] = $value;
394
+ }
395
+
396
+ public function getScheduledAtAttribute($value)
397
+ {
398
+ return $this->attributes['scheduled_at'] ?? $value;
399
+ }
400
+
401
+ public function load($relations)
402
+ {
403
+ $this->loadedRelations[] = $relations;
404
+
405
+ return $this;
406
+ }
407
+
408
+ public function cancel()
409
+ {
410
+ $this->canceledForTest = true;
411
+ $this->forceFill(['status' => 'canceled']);
412
+
413
+ return $this;
414
+ }
415
+
416
+ public function dispatch(bool $save = true): Order
417
+ {
418
+ $this->dispatchedForTest = true;
419
+ $this->forceFill(['status' => 'dispatched']);
420
+
421
+ return $this;
422
+ }
423
+
424
+ public function dispatchWithActivity(): Order
425
+ {
426
+ $this->dispatchedWithActivityForTest = true;
427
+ $this->forceFill(['status' => 'dispatched']);
428
+
429
+ return $this;
430
+ }
431
+
432
+ public function ensureOrderConfig(): ?OrderConfig
433
+ {
434
+ if (!$this->hasOrderConfigForTest) {
435
+ return null;
436
+ }
437
+
438
+ $orderConfig = new OrderConfig();
439
+ $orderConfig->setRawAttributes(['uuid' => 'order-config-uuid'], true);
440
+
441
+ return $orderConfig;
442
+ }
443
+
444
+ public function getHasDriverAssignedAttribute(): bool
445
+ {
446
+ return $this->hasDriverAssignedForTest;
447
+ }
448
+
449
+ public function getAdhocAttribute(): bool
450
+ {
451
+ return $this->adhocForTest;
452
+ }
453
+
454
+ public function getDispatchedAttribute(): bool
455
+ {
456
+ return $this->dispatchedAttributeForTest;
457
+ }
458
+
459
+ public function tracker(): OrderTracker
460
+ {
461
+ return $this->trackerForTest ??= new FleetOpsInternalOrderLifecycleTrackerFake($this);
462
+ }
463
+
464
+ public function config(): ?OrderConfig
465
+ {
466
+ return $this->configForTest ??= new FleetOpsInternalOrderLifecycleConfigFake();
467
+ }
468
+
469
+ public function pdfLabelStream(): string
470
+ {
471
+ return 'stream:' . $this->uuid;
472
+ }
473
+
474
+ public function pdfLabel(): FleetOpsInternalOrderLifecyclePdfFake
475
+ {
476
+ return new FleetOpsInternalOrderLifecyclePdfFake('label:' . $this->uuid);
477
+ }
478
+ }
479
+
480
+ class FleetOpsInternalOrderLifecyclePayloadFake extends Payload
481
+ {
482
+ public array $calls = [];
483
+ public array $loadedMissing = [];
484
+ public array $loadedRelations = [];
485
+ public array $unsetRelations = [];
486
+ public ?Place $pickupOrFirstWaypointForTest = null;
487
+ public ?Place $dropoffOrLastWaypointForTest = null;
488
+ public ?Place $currentWaypointForTest = null;
489
+ public ?Collection $waypointMarkersForTest = null;
490
+
491
+ public function setPickup($place, array $options = [])
492
+ {
493
+ $this->calls[] = ['setPickup', $place, $options];
494
+
495
+ return $this;
496
+ }
497
+
498
+ public function setDropoff($place, array $options = [])
499
+ {
500
+ $this->calls[] = ['setDropoff', $place, $options];
501
+
502
+ return $this;
503
+ }
504
+
505
+ public function setReturn($place, array $options = [])
506
+ {
507
+ $this->calls[] = ['setReturn', $place, $options];
508
+
509
+ return $this;
510
+ }
511
+
512
+ public function removePlace($property, array $options = [])
513
+ {
514
+ $this->calls[] = ['removePlace', $property, $options];
515
+
516
+ return $this;
517
+ }
518
+
519
+ public function updateWaypoints($waypoints = [])
520
+ {
521
+ $this->calls[] = ['updateWaypoints', $waypoints];
522
+
523
+ return $this;
524
+ }
525
+
526
+ public function removeWaypoints()
527
+ {
528
+ $this->calls[] = ['removeWaypoints'];
529
+
530
+ return $this;
531
+ }
532
+
533
+ public function loadMissing($relations)
534
+ {
535
+ $this->loadedMissing[] = $relations;
536
+
537
+ return $this;
538
+ }
539
+
540
+ public function load($relations)
541
+ {
542
+ $this->loadedRelations[] = $relations;
543
+
544
+ if ($this->waypointMarkersForTest && in_array('waypointMarkers.trackingNumber.status', (array) $relations, true)) {
545
+ $this->setRelation('waypointMarkers', $this->waypointMarkersForTest);
546
+ }
547
+
548
+ return $this;
549
+ }
550
+
551
+ public function unsetRelation($relation)
552
+ {
553
+ $this->unsetRelations[] = $relation;
554
+
555
+ return parent::unsetRelation($relation);
556
+ }
557
+
558
+ public function getPickupOrFirstWaypoint(): ?Place
559
+ {
560
+ return $this->pickupOrFirstWaypointForTest;
561
+ }
562
+
563
+ public function getDropoffOrLastWaypoint(): ?Place
564
+ {
565
+ return $this->dropoffOrLastWaypointForTest;
566
+ }
567
+
568
+ public function setCurrentWaypoint(Place|Waypoint $destination, bool $save = true): Payload
569
+ {
570
+ $this->currentWaypointForTest = $destination instanceof Place ? $destination : null;
571
+ $this->calls[] = ['setCurrentWaypoint', $destination, $save];
572
+
573
+ return $this;
574
+ }
575
+ }
576
+
577
+ class FleetOpsInternalOrderLifecycleDriverFake extends Driver
578
+ {
579
+ public array $notifications = [];
580
+ public bool $savedForTest = false;
581
+ public bool $throwOnNotify = false;
582
+
583
+ public function save(array $options = [])
584
+ {
585
+ $this->savedForTest = true;
586
+
587
+ return true;
588
+ }
589
+
590
+ public function notify($instance)
591
+ {
592
+ if ($this->throwOnNotify) {
593
+ throw new RuntimeException('notification failed');
594
+ }
595
+
596
+ $this->notifications[] = $instance::class;
597
+ }
598
+ }
599
+
600
+ class FleetOpsInternalOrderLifecycleWaypointFake extends Waypoint
601
+ {
602
+ public array $activities = [];
603
+ public array $loadedMissing = [];
604
+
605
+ public function loadMissing($relations)
606
+ {
607
+ $this->loadedMissing[] = $relations;
608
+
609
+ return $this;
610
+ }
611
+
612
+ public function insertActivity(Activity $activity, $location = [], $proof = null): string
613
+ {
614
+ $this->activities[] = [$activity->code, $location, $proof];
615
+
616
+ return 'activity-' . count($this->activities);
617
+ }
618
+
619
+ public function getPlace(): ?Place
620
+ {
621
+ return $this->place;
622
+ }
623
+
624
+ public function pdfLabelStream(): string
625
+ {
626
+ return 'stream:' . $this->uuid;
627
+ }
628
+
629
+ public function pdfLabel(): FleetOpsInternalOrderLifecyclePdfFake
630
+ {
631
+ return new FleetOpsInternalOrderLifecyclePdfFake('label:' . $this->uuid);
632
+ }
633
+ }
634
+
635
+ class FleetOpsInternalOrderLifecycleEntityFake extends Entity
636
+ {
637
+ public array $activities = [];
638
+
639
+ public function insertActivity(Activity $activity, $location = [], $proof = null): string
640
+ {
641
+ $this->activities[] = [$activity->code, $location, $proof];
642
+
643
+ return 'activity-' . count($this->activities);
644
+ }
645
+
646
+ public function pdfLabelStream(): string
647
+ {
648
+ return 'stream:' . $this->uuid;
649
+ }
650
+
651
+ public function pdfLabel(): FleetOpsInternalOrderLifecyclePdfFake
652
+ {
653
+ return new FleetOpsInternalOrderLifecyclePdfFake('label:' . $this->uuid);
654
+ }
655
+ }
656
+
657
+ class FleetOpsInternalOrderLifecycleConfigFake extends OrderConfig
658
+ {
659
+ public function nextFirstActivity(Order|Waypoint|null $context = null): ?Activity
660
+ {
661
+ return new Activity(['code' => 'started']);
662
+ }
663
+ }
664
+
665
+ class FleetOpsInternalOrderLifecycleStartedEventFake
666
+ {
667
+ public function __construct(public Order $order)
668
+ {
669
+ }
670
+ }
671
+
672
+ class FleetOpsInternalOrderLifecyclePdfFake
673
+ {
674
+ public function __construct(private readonly string $contents)
675
+ {
676
+ }
677
+
678
+ public function output(): string
679
+ {
680
+ return $this->contents;
681
+ }
682
+ }
683
+
684
+ class FleetOpsInternalOrderLifecycleTrackerFake extends OrderTracker
685
+ {
686
+ public array $toArrayOptions = [];
687
+ public array $etaOptions = [];
688
+
689
+ public function toArray(array $options = []): array
690
+ {
691
+ $this->toArrayOptions = $options;
692
+
693
+ return ['tracker' => 'info', 'options' => $options];
694
+ }
695
+
696
+ public function eta(array $options = []): array
697
+ {
698
+ $this->etaOptions = $options;
699
+
700
+ return ['eta' => [['stop' => 'dropoff']], 'options' => $options];
701
+ }
702
+ }
703
+
704
+ class FleetOpsInternalOrderLifecycleDatabaseProbe
705
+ {
706
+ public function __construct(private SQLiteConnection $connection)
707
+ {
708
+ }
709
+
710
+ public function table(string $table)
711
+ {
712
+ return $this->connection->table($table);
713
+ }
714
+
715
+ public function raw(mixed $value): mixed
716
+ {
717
+ return $this->connection->raw($value);
718
+ }
719
+
720
+ public function connection(): SQLiteConnection
721
+ {
722
+ return $this->connection;
723
+ }
724
+
725
+ public function transaction(callable $callback): mixed
726
+ {
727
+ return $callback();
728
+ }
729
+ }
730
+
731
+ function fleetopsInternalOrderLifecycleOrder(string $uuid, string $status = 'created', ?string $trackingNumberUuid = null): FleetOpsInternalOrderLifecycleOrderFake
732
+ {
733
+ $order = new FleetOpsInternalOrderLifecycleOrderFake();
734
+ $order->setRawAttributes([
735
+ 'uuid' => $uuid,
736
+ 'status' => $status,
737
+ 'tracking_number_uuid' => $trackingNumberUuid,
738
+ ], true);
739
+
740
+ return $order;
741
+ }
742
+
743
+ function fleetopsInternalOrderLifecyclePayload(?Place $startingDestination = null, ?Place $fallbackDestination = null): FleetOpsInternalOrderLifecyclePayloadFake
744
+ {
745
+ $payload = new FleetOpsInternalOrderLifecyclePayloadFake();
746
+ $payload->pickupOrFirstWaypointForTest = $startingDestination;
747
+ $payload->dropoffOrLastWaypointForTest = $fallbackDestination;
748
+
749
+ return $payload;
750
+ }
751
+
752
+ function fleetopsInternalOrderLifecyclePlace(string $uuid = 'place-uuid'): Place
753
+ {
754
+ $place = new Place();
755
+ $place->setRawAttributes(['uuid' => $uuid, 'public_id' => $uuid], true);
756
+
757
+ return $place;
758
+ }
759
+
760
+ function fleetopsInternalOrderLifecycleController(array $orders = []): FleetOpsInternalOrderLifecycleControllerProbe
761
+ {
762
+ $controller = new FleetOpsInternalOrderLifecycleControllerProbe();
763
+ $controller->orders = new Collection($orders);
764
+ $controller->order = $orders[0] ?? fleetopsInternalOrderLifecycleOrder('order-uuid');
765
+ $controller->driver = new FleetOpsInternalOrderLifecycleDriverFake();
766
+ $controller->driver->setRawAttributes([
767
+ 'uuid' => '11111111-1111-4111-8111-111111111111',
768
+ 'name' => 'Ada Driver',
769
+ ], true);
770
+
771
+ return $controller;
772
+ }
773
+
774
+ function fleetopsInternalOrderLifecycleUseStatusesDatabase(): SQLiteConnection
775
+ {
776
+ $connection = new SQLiteConnection(new PDO('sqlite::memory:'));
777
+ $resolver = new ConnectionResolver([
778
+ 'default' => $connection,
779
+ 'mysql' => $connection,
780
+ ]);
781
+
782
+ $resolver->setDefaultConnection('mysql');
783
+ EloquentModel::setConnectionResolver($resolver);
784
+ app()->instance('db', new FleetOpsInternalOrderLifecycleDatabaseProbe($connection));
785
+
786
+ $schema = $connection->getSchemaBuilder();
787
+ $schema->create('orders', function ($table) {
788
+ $table->increments('id');
789
+ $table->string('uuid')->nullable();
790
+ $table->string('company_uuid')->nullable();
791
+ $table->string('order_config_uuid')->nullable();
792
+ $table->string('status')->nullable();
793
+ $table->timestamp('created_at')->nullable();
794
+ $table->timestamp('updated_at')->nullable();
795
+ $table->timestamp('deleted_at')->nullable();
796
+ });
797
+ $schema->create('order_configs', function ($table) {
798
+ $table->increments('id');
799
+ $table->string('uuid')->nullable();
800
+ $table->string('public_id')->nullable();
801
+ $table->string('company_uuid')->nullable();
802
+ $table->string('key')->nullable();
803
+ $table->string('name')->nullable();
804
+ $table->text('flow')->nullable();
805
+ $table->timestamp('created_at')->nullable();
806
+ $table->timestamp('updated_at')->nullable();
807
+ $table->timestamp('deleted_at')->nullable();
808
+ });
809
+
810
+ return $connection;
811
+ }
812
+
813
+ function fleetopsInternalOrderLifecycleStatusesRequest(array $input = [], ?string $companyUuid = null): Request
814
+ {
815
+ $request = new Request($input);
816
+ $request->setUserResolver(function () use ($companyUuid) {
817
+ return $companyUuid ? (object) ['company_uuid' => $companyUuid] : null;
818
+ });
819
+
820
+ return $request;
821
+ }
822
+
823
+ function fleetopsBulkActionRequest(array $payload): Request
824
+ {
825
+ return Request::create('/internal/v1/orders/bulk', 'POST', $payload);
826
+ }
827
+
828
+ function fleetopsBulkDispatchRequest(array $payload): BulkDispatchRequest
829
+ {
830
+ return BulkDispatchRequest::create('/internal/v1/orders/bulk-dispatch', 'POST', $payload);
831
+ }
832
+
833
+ function fleetopsCancelOrderRequest(array $payload): CancelOrderRequest
834
+ {
835
+ return CancelOrderRequest::create('/internal/v1/orders/cancel', 'POST', $payload);
836
+ }
837
+
838
+ function fleetopsInternalOrderExportRequest(array $payload): ExportRequest
839
+ {
840
+ return new class($payload) extends ExportRequest {
841
+ public function __construct(private readonly array $payload)
842
+ {
843
+ parent::__construct([], $payload);
844
+ }
845
+
846
+ public function input($key = null, $default = null)
847
+ {
848
+ return data_get($this->payload, $key, $default);
849
+ }
850
+
851
+ public function array($key = null, $default = [])
852
+ {
853
+ $value = $this->input($key, $default);
854
+
855
+ return is_array($value) ? $value : $default;
856
+ }
857
+ };
858
+ }
859
+
860
+ function fleetopsSuppressStrNullDeprecations(): Closure
861
+ {
862
+ set_error_handler(function (int $severity, string $message): bool {
863
+ return $severity === E_DEPRECATED && str_contains($message, 'mb_strtolower(): Passing null');
864
+ });
865
+
866
+ return function (): void {
867
+ restore_error_handler();
868
+ };
869
+ }
870
+
871
+ test('internal order controller after-update syncs files waypoints and custom fields', function () {
872
+ $order = fleetopsInternalOrderLifecycleOrder('order-route');
873
+ $payload = fleetopsInternalOrderLifecyclePayload();
874
+ $order->setRelation('payload', $payload);
875
+
876
+ $controller = fleetopsInternalOrderLifecycleController([$order]);
877
+ $controller->onAfterUpdate(new Request([
878
+ 'order' => [
879
+ 'files' => ['file-one', 'file-two'],
880
+ 'payload' => ['waypoints' => [['name' => 'Stop one']]],
881
+ 'custom_field_values' => ['fragile' => true],
882
+ ],
883
+ ]), $order);
884
+
885
+ expect($order->attachedFiles)->toBe(['file-one', 'file-two'])
886
+ ->and($order->loadedMissing)->toBe(['payload'])
887
+ ->and($payload->calls)->toContain(['updateWaypoints', [['name' => 'Stop one']]])
888
+ ->and($order->customFieldValues)->toBe(['fragile' => true]);
889
+ });
890
+
891
+ test('internal order controller edits order routes and refreshes current destination', function () {
892
+ $startingDestination = fleetopsInternalOrderLifecyclePlace('starting-place');
893
+ $payload = fleetopsInternalOrderLifecyclePayload($startingDestination);
894
+ $order = fleetopsInternalOrderLifecycleOrder('order-route');
895
+ $order->setRelation('payload', $payload);
896
+ $controller = fleetopsInternalOrderLifecycleController([$order]);
897
+
898
+ $response = $controller->editOrderRoute('order-route', new Request([
899
+ 'pickup' => ['name' => 'Pickup'],
900
+ 'dropoff' => ['name' => 'Dropoff'],
901
+ 'return' => ['name' => 'Return'],
902
+ 'waypoints' => [['name' => 'Waypoint']],
903
+ ]));
904
+
905
+ expect($response)->toBe(['order' => $order])
906
+ ->and($order->route_lookup_uuid)->toBe('order-route')
907
+ ->and($payload->calls)->toContain(['setPickup', ['name' => 'Pickup'], ['save' => true]])
908
+ ->and($payload->calls)->toContain(['setDropoff', ['name' => 'Dropoff'], ['save' => true]])
909
+ ->and($payload->calls)->toContain(['setReturn', ['name' => 'Return'], ['save' => true]])
910
+ ->and($payload->calls)->toContain(['updateWaypoints', [['name' => 'Waypoint']]])
911
+ ->and($payload->calls)->toContain(['setCurrentWaypoint', $startingDestination, true])
912
+ ->and($order->loadedRelations)->toBe([['payload.pickup', 'payload.dropoff', 'payload.return', 'payload.waypoints']]);
913
+ });
914
+
915
+ test('internal order controller clears route endpoints and falls back to dropoff destination', function () {
916
+ $fallbackDestination = fleetopsInternalOrderLifecyclePlace('fallback-place');
917
+ $payload = fleetopsInternalOrderLifecyclePayload(null, $fallbackDestination);
918
+ $order = fleetopsInternalOrderLifecycleOrder('order-route');
919
+ $order->setRelation('payload', $payload);
920
+ $controller = fleetopsInternalOrderLifecycleController([$order]);
921
+
922
+ $response = $controller->editOrderRoute('order-route', new Request([
923
+ 'pickup' => null,
924
+ 'dropoff' => null,
925
+ 'return' => null,
926
+ ]));
927
+
928
+ expect($response)->toBe(['order' => $order])
929
+ ->and($payload->calls)->toContain(['removePlace', 'pickup', ['save' => true]])
930
+ ->and($payload->calls)->toContain(['removePlace', 'dropoff', ['save' => true]])
931
+ ->and($payload->calls)->toContain(['removePlace', 'return', ['save' => true]])
932
+ ->and($payload->calls)->toContain(['removeWaypoints'])
933
+ ->and($payload->calls)->toContain(['setCurrentWaypoint', $fallbackDestination, true]);
934
+ });
935
+
936
+ test('internal order controller reports missing order route edits', function () {
937
+ $controller = fleetopsInternalOrderLifecycleController();
938
+ $controller->order = null;
939
+
940
+ expect($controller->editOrderRoute('missing-route', new Request())->getData(true))->toBe([
941
+ 'error' => 'Unable to find order to update route for.',
942
+ ]);
943
+ });
944
+
945
+ test('internal order controller bulk cancel skips already canceled and canceled tracking statuses', function () {
946
+ $created = fleetopsInternalOrderLifecycleOrder('order-created', 'created', 'tracking-created');
947
+ $alreadyCanceled = fleetopsInternalOrderLifecycleOrder('order-canceled', 'canceled', 'tracking-canceled');
948
+ $trackingCanceled = fleetopsInternalOrderLifecycleOrder('order-tracking-canceled', 'created', 'tracking-canceled-status');
949
+ $controller = fleetopsInternalOrderLifecycleController([$created, $alreadyCanceled, $trackingCanceled]);
950
+ $controller->trackingStatusExists['tracking-canceled-status:CANCELED'] = true;
951
+
952
+ $response = $controller->bulkCancel(fleetopsBulkActionRequest([
953
+ 'ids' => ['order-created', 'order-canceled', 'order-tracking-canceled'],
954
+ ]))->getData(true);
955
+
956
+ expect($response)->toBe([
957
+ 'status' => 'OK',
958
+ 'message' => 'Canceled 3 orders',
959
+ 'count' => 3,
960
+ 'failed' => ['order-canceled', 'order-tracking-canceled'],
961
+ 'successful' => ['order-created'],
962
+ ])
963
+ ->and($created->canceledForTest)->toBeTrue()
964
+ ->and($alreadyCanceled->canceledForTest)->toBeFalse()
965
+ ->and($trackingCanceled->canceledForTest)->toBeFalse()
966
+ ->and($created->queried_ids)->toBe(['order-created', 'order-canceled', 'order-tracking-canceled']);
967
+ });
968
+
969
+ test('internal order controller bulk dispatch only dispatches created uncanceled orders', function () {
970
+ $created = fleetopsInternalOrderLifecycleOrder('order-created', 'created', 'tracking-created');
971
+ $started = fleetopsInternalOrderLifecycleOrder('order-started', 'started', 'tracking-started');
972
+ $trackingCanceled = fleetopsInternalOrderLifecycleOrder('order-tracking-canceled', 'created', 'tracking-canceled-status');
973
+ $controller = fleetopsInternalOrderLifecycleController([$created, $started, $trackingCanceled]);
974
+ $controller->trackingStatusExists['tracking-canceled-status:CANCELED'] = true;
975
+
976
+ $response = $controller->bulkDispatch(fleetopsBulkDispatchRequest([
977
+ 'ids' => ['order-created', 'order-started', 'order-tracking-canceled'],
978
+ ]))->getData(true);
979
+
980
+ expect($response)->toBe([
981
+ 'status' => 'OK',
982
+ 'message' => 'Dispatched 3 orders',
983
+ 'count' => 3,
984
+ 'failed' => ['order-started', 'order-tracking-canceled'],
985
+ 'successful' => ['order-created'],
986
+ ])
987
+ ->and($created->dispatchedForTest)->toBeTrue()
988
+ ->and($started->dispatchedForTest)->toBeFalse()
989
+ ->and($trackingCanceled->dispatchedForTest)->toBeFalse();
990
+ });
991
+
992
+ test('internal order controller bulk assign driver deduplicates orders and queues notifications', function () {
993
+ $controller = fleetopsInternalOrderLifecycleController();
994
+ $driverUuid = '11111111-1111-4111-8111-111111111111';
995
+ $orderA = '22222222-2222-4222-8222-222222222222';
996
+ $orderB = '33333333-3333-4333-8333-333333333333';
997
+
998
+ $response = $controller->bulkAssignDriver(fleetopsBulkActionRequest([
999
+ 'ids' => [$orderA, $orderA, $orderB],
1000
+ 'driver' => $driverUuid,
1001
+ ]))->getData(true);
1002
+
1003
+ expect($response)->toBe([
1004
+ 'status' => 'OK',
1005
+ 'message' => 'Queued assignment of driver (Ada Driver) to 2 orders',
1006
+ 'count' => 2,
1007
+ ])
1008
+ ->and($controller->driver->lookup_uuid)->toBe($driverUuid)
1009
+ ->and($controller->transactions)->toBe(1)
1010
+ ->and($controller->assignedOrderUuids)->toBe([$orderA, $orderB])
1011
+ ->and($controller->assignedDriverUuid)->toBe($driverUuid)
1012
+ ->and($controller->bulkNotification)->toBe([[$orderA, $orderB], $driverUuid]);
1013
+
1014
+ $controller = fleetopsInternalOrderLifecycleController();
1015
+ $controller->bulkAssignDriver(fleetopsBulkActionRequest([
1016
+ 'ids' => [$orderA],
1017
+ 'driver' => $driverUuid,
1018
+ 'silent' => true,
1019
+ ]));
1020
+
1021
+ expect($controller->bulkNotification)->toBe([]);
1022
+ });
1023
+
1024
+ test('internal order controller reports invalid bulk assign driver selections', function () {
1025
+ $controller = fleetopsInternalOrderLifecycleController();
1026
+ $controller->driver = null;
1027
+
1028
+ $response = $controller->bulkAssignDriver(fleetopsBulkActionRequest([
1029
+ 'ids' => ['22222222-2222-4222-8222-222222222222'],
1030
+ 'driver' => '11111111-1111-4111-8111-111111111111',
1031
+ ]));
1032
+
1033
+ expect($response->getData(true))->toBe([
1034
+ 'error' => 'Invalid driver selected to assign orders to.',
1035
+ ]);
1036
+ });
1037
+
1038
+ test('internal order controller cancels and dispatches individual orders', function () {
1039
+ $order = fleetopsInternalOrderLifecycleOrder('order-uuid', 'created');
1040
+ $controller = fleetopsInternalOrderLifecycleController([$order]);
1041
+
1042
+ expect($controller->cancel(fleetopsCancelOrderRequest([
1043
+ 'order' => 'order-uuid',
1044
+ ]))->getData(true))->toBe([
1045
+ 'status' => 'OK',
1046
+ 'message' => 'Order was canceled',
1047
+ 'order' => 'order-uuid',
1048
+ ])
1049
+ ->and($order->lookup_uuid)->toBe('order-uuid')
1050
+ ->and($order->canceledForTest)->toBeTrue();
1051
+
1052
+ $order = fleetopsInternalOrderLifecycleOrder('order-dispatch', 'created');
1053
+ $controller = fleetopsInternalOrderLifecycleController([$order]);
1054
+
1055
+ expect($controller->dispatchOrder(new Request([
1056
+ 'order' => 'order-public',
1057
+ ]))->getData(true))->toBe([
1058
+ 'status' => 'OK',
1059
+ 'message' => 'Order was dispatched',
1060
+ 'order' => 'order-dispatch',
1061
+ ])
1062
+ ->and($order->lookup_id)->toBe('order-public')
1063
+ ->and($order->lookup_with)->toBe(['orderConfig', 'driverAssigned'])
1064
+ ->and($order->dispatchedWithActivityForTest)->toBeTrue();
1065
+ });
1066
+
1067
+ test('internal order controller reports individual dispatch validation branches', function () {
1068
+ $controller = fleetopsInternalOrderLifecycleController();
1069
+ $controller->order = null;
1070
+
1071
+ expect($controller->dispatchOrder(new Request([
1072
+ 'order' => 'missing-order',
1073
+ ]))->getData(true))->toBe([
1074
+ 'error' => 'No order found to dispatch.',
1075
+ ]);
1076
+
1077
+ $order = fleetopsInternalOrderLifecycleOrder('order-no-config', 'created');
1078
+ $order->hasOrderConfigForTest = false;
1079
+ $controller = fleetopsInternalOrderLifecycleController([$order]);
1080
+
1081
+ expect($controller->dispatchOrder(new Request([
1082
+ 'order' => 'order-no-config',
1083
+ ]))->getData(true))->toBe([
1084
+ 'error' => 'No order config found for dispatch.',
1085
+ ]);
1086
+
1087
+ $order = fleetopsInternalOrderLifecycleOrder('order-no-driver', 'created');
1088
+ $order->hasDriverAssignedForTest = false;
1089
+ $controller = fleetopsInternalOrderLifecycleController([$order]);
1090
+
1091
+ expect($controller->dispatchOrder(new Request([
1092
+ 'order' => 'order-no-driver',
1093
+ ]))->getData(true))->toBe([
1094
+ 'error' => 'No driver assigned to dispatch!',
1095
+ ]);
1096
+
1097
+ $order = fleetopsInternalOrderLifecycleOrder('order-dispatched', 'created');
1098
+ $order->dispatchedAttributeForTest = true;
1099
+ $controller = fleetopsInternalOrderLifecycleController([$order]);
1100
+
1101
+ expect($controller->dispatchOrder(new Request([
1102
+ 'order' => 'order-dispatched',
1103
+ ]))->getData(true))->toBe([
1104
+ 'error' => 'Order has already been dispatched!',
1105
+ ]);
1106
+ });
1107
+
1108
+ test('internal order controller returns tracker info and waypoint etas', function () {
1109
+ $order = fleetopsInternalOrderLifecycleOrder('order-tracker', 'started');
1110
+ $order->trackerForTest = new FleetOpsInternalOrderLifecycleTrackerFake($order);
1111
+ $controller = fleetopsInternalOrderLifecycleController([$order]);
1112
+
1113
+ $tracker = $controller->trackerInfo(new Request([
1114
+ 'provider' => 'osrm',
1115
+ 'fallbacks' => true,
1116
+ 'traffic_enabled' => false,
1117
+ 'ignored' => 'value',
1118
+ ]), 'order-tracker')->getData(true);
1119
+
1120
+ expect($tracker)->toBe([
1121
+ 'tracker' => 'info',
1122
+ 'options' => [
1123
+ 'provider' => 'osrm',
1124
+ 'fallbacks' => true,
1125
+ 'traffic_enabled' => false,
1126
+ ],
1127
+ ])
1128
+ ->and($order->trackerForTest->toArrayOptions)->toBe([
1129
+ 'provider' => 'osrm',
1130
+ 'fallbacks' => true,
1131
+ 'traffic_enabled' => false,
1132
+ ]);
1133
+
1134
+ $etas = $controller->waypointEtas(new Request([
1135
+ 'provider' => 'google',
1136
+ 'fallbacks' => false,
1137
+ 'traffic_enabled' => true,
1138
+ ]), 'order-tracker')->getData(true);
1139
+
1140
+ expect($etas)->toBe([
1141
+ 'eta' => [['stop' => 'dropoff']],
1142
+ 'options' => [
1143
+ 'provider' => 'google',
1144
+ 'fallbacks' => false,
1145
+ 'traffic_enabled' => true,
1146
+ ],
1147
+ ])
1148
+ ->and($order->trackerForTest->etaOptions)->toBe([
1149
+ 'provider' => 'google',
1150
+ 'fallbacks' => false,
1151
+ 'traffic_enabled' => true,
1152
+ ]);
1153
+
1154
+ $controller->order = null;
1155
+
1156
+ expect($controller->trackerInfo(new Request(), 'missing-order')->getData(true))->toBe([
1157
+ 'error' => 'No order found.',
1158
+ ])
1159
+ ->and($controller->waypointEtas(new Request(), 'missing-order')->getData(true))->toBe([
1160
+ 'error' => 'No order found.',
1161
+ ]);
1162
+ });
1163
+
1164
+ test('internal order controller start validates order driver and payload workflow', function () {
1165
+ $controller = fleetopsInternalOrderLifecycleController();
1166
+
1167
+ expect($controller->start(new Request(['order' => 'missing-order']))->getData(true))->toBe([
1168
+ 'error' => 'Unable to find order to start.',
1169
+ ]);
1170
+
1171
+ $startedOrder = fleetopsInternalOrderLifecycleOrder('started-order', 'started');
1172
+ $startedOrder->forceFill(['started' => true]);
1173
+ $controller->startOrder = $startedOrder;
1174
+
1175
+ expect($controller->start(new Request(['order' => 'started-order']))->getData(true))->toBe([
1176
+ 'error' => 'Order has already been started.',
1177
+ ]);
1178
+
1179
+ $order = fleetopsInternalOrderLifecycleOrder('start-order', 'dispatched');
1180
+ $order->forceFill([
1181
+ 'driver_assigned_uuid' => 'driver-start',
1182
+ 'payload_uuid' => 'payload-start',
1183
+ 'started' => false,
1184
+ ]);
1185
+ $payload = fleetopsInternalOrderLifecyclePayload();
1186
+
1187
+ $controller = fleetopsInternalOrderLifecycleController([$order]);
1188
+ $controller->startOrder = $order;
1189
+ $controller->startPayload = $payload;
1190
+
1191
+ expect($controller->start(new Request(['order' => 'start-order']))->getData(true))->toBe([
1192
+ 'error' => 'No driver assigned to order.',
1193
+ ]);
1194
+
1195
+ $driver = new FleetOpsInternalOrderLifecycleDriverFake();
1196
+ $controller->startDriver = $driver;
1197
+
1198
+ $response = $controller->start(new Request(['order' => 'start-order']))->getData(true);
1199
+
1200
+ expect($response)->toBe([
1201
+ 'updated' => 'start-order',
1202
+ 'activity' => 'started',
1203
+ ])
1204
+ ->and($order->start_lookup_uuid)->toBe('start-order')
1205
+ ->and($driver->start_lookup_uuid)->toBe('driver-start')
1206
+ ->and($payload->start_lookup_uuid)->toBe('payload-start')
1207
+ ->and($order->started)->toBeTrue()
1208
+ ->and($order->savedForTest)->toBeTrue()
1209
+ ->and($driver->current_job_uuid)->toBe('start-order')
1210
+ ->and($driver->savedForTest)->toBeTrue()
1211
+ ->and($controller->domainEvents)->toBe([FleetOpsInternalOrderLifecycleStartedEventFake::class])
1212
+ ->and($controller->activityUpdates[0][0])->toBe('start-order')
1213
+ ->and($controller->activityUpdates[0][1])->toBeInstanceOf(Activity::class)
1214
+ ->and($controller->activityUpdates[0][1]->code)->toBe('started');
1215
+ });
1216
+
1217
+ test('internal order controller ping driver covers authorization and delivery outcomes', function () {
1218
+ $controller = fleetopsInternalOrderLifecycleController();
1219
+ $controller->canPingDriver = false;
1220
+
1221
+ expect($controller->pingDriver('order-ping')->getData(true))->toBe([
1222
+ 'error' => 'Unauthorized.',
1223
+ ]);
1224
+
1225
+ $controller = fleetopsInternalOrderLifecycleController();
1226
+ $controller->pingOrderMissing = true;
1227
+
1228
+ expect($controller->pingDriver('order-ping')->getData(true))->toBe([
1229
+ 'error' => 'Order resource not found.',
1230
+ ]);
1231
+
1232
+ $order = fleetopsInternalOrderLifecycleOrder('order-ping');
1233
+ $order->setRelation('driverAssigned', null);
1234
+ $controller = fleetopsInternalOrderLifecycleController([$order]);
1235
+ $controller->pingOrder = $order;
1236
+
1237
+ expect($controller->pingDriver('order-ping')->getData(true))->toBe([
1238
+ 'error' => 'Order does not have an assigned driver.',
1239
+ ]);
1240
+
1241
+ $driver = new FleetOpsInternalOrderLifecycleDriverFake();
1242
+ $order->setRelation('driverAssigned', $driver);
1243
+
1244
+ expect($controller->pingDriver('order-ping')->getData(true))->toBe([
1245
+ 'status' => 'ok',
1246
+ 'message' => 'Driver app ping sent.',
1247
+ ])
1248
+ ->and($driver->notifications)->toBe([Fleetbase\FleetOps\Notifications\OrderPing::class]);
1249
+
1250
+ $controller->pingSendFails = true;
1251
+
1252
+ expect($controller->pingDriver('order-ping')->getData(true))->toBe([
1253
+ 'error' => 'Unable to ping driver app.',
1254
+ ]);
1255
+ });
1256
+
1257
+ test('internal order controller type list merges custom and default order types', function () {
1258
+ $controller = fleetopsInternalOrderLifecycleController();
1259
+ $custom = new Type(['key' => 'parcel', 'name' => 'Custom Parcel']);
1260
+
1261
+ $controller->customTypes = collect([$custom]);
1262
+ $controller->defaultTypes = [
1263
+ ['key' => 'parcel', 'name' => 'Default Parcel'],
1264
+ ['key' => 'freight', 'name' => 'Freight'],
1265
+ ];
1266
+
1267
+ $types = $controller->types()->getData(true);
1268
+
1269
+ expect(array_column($types, 'key'))->toBe(['parcel', 'freight'])
1270
+ ->and($types[0]['name'])->toBe('Custom Parcel')
1271
+ ->and($types[1]['name'])->toBe('Freight');
1272
+ });
1273
+
1274
+ test('internal order controller statuses merge scoped order statuses and config activities', function () {
1275
+ $connection = fleetopsInternalOrderLifecycleUseStatusesDatabase();
1276
+ $now = now();
1277
+
1278
+ session(['company' => 'company-a']);
1279
+
1280
+ $connection->table('order_configs')->insert([
1281
+ [
1282
+ 'uuid' => 'config-parcel',
1283
+ 'public_id' => 'order_config_parcel',
1284
+ 'company_uuid' => 'company-a',
1285
+ 'key' => 'parcel',
1286
+ 'name' => 'Parcel',
1287
+ 'flow' => json_encode([
1288
+ ['code' => 'created'],
1289
+ ['code' => 'driver_assigned'],
1290
+ ['code' => null],
1291
+ ]),
1292
+ 'created_at' => $now,
1293
+ 'updated_at' => $now,
1294
+ ],
1295
+ [
1296
+ 'uuid' => 'config-freight',
1297
+ 'public_id' => 'order_config_freight',
1298
+ 'company_uuid' => 'company-a',
1299
+ 'key' => 'freight',
1300
+ 'name' => 'Freight',
1301
+ 'flow' => json_encode([
1302
+ ['code' => 'loaded'],
1303
+ ['code' => 'completed'],
1304
+ ]),
1305
+ 'created_at' => $now,
1306
+ 'updated_at' => $now,
1307
+ ],
1308
+ [
1309
+ 'uuid' => 'config-other-company',
1310
+ 'public_id' => 'order_config_other_company',
1311
+ 'company_uuid' => 'company-b',
1312
+ 'key' => 'parcel',
1313
+ 'name' => 'Other Parcel',
1314
+ 'flow' => json_encode([
1315
+ ['code' => 'other_activity'],
1316
+ ]),
1317
+ 'created_at' => $now,
1318
+ 'updated_at' => $now,
1319
+ ],
1320
+ ]);
1321
+ $connection->table('orders')->insert([
1322
+ [
1323
+ 'uuid' => 'order-created',
1324
+ 'company_uuid' => 'company-a',
1325
+ 'order_config_uuid' => 'config-parcel',
1326
+ 'status' => 'created',
1327
+ 'created_at' => $now,
1328
+ 'updated_at' => $now,
1329
+ 'deleted_at' => null,
1330
+ ],
1331
+ [
1332
+ 'uuid' => 'order-dispatched',
1333
+ 'company_uuid' => 'company-a',
1334
+ 'order_config_uuid' => 'config-parcel',
1335
+ 'status' => 'dispatched',
1336
+ 'created_at' => $now,
1337
+ 'updated_at' => $now,
1338
+ 'deleted_at' => null,
1339
+ ],
1340
+ [
1341
+ 'uuid' => 'order-loaded',
1342
+ 'company_uuid' => 'company-a',
1343
+ 'order_config_uuid' => 'config-freight',
1344
+ 'status' => 'loaded',
1345
+ 'created_at' => $now,
1346
+ 'updated_at' => $now,
1347
+ 'deleted_at' => null,
1348
+ ],
1349
+ [
1350
+ 'uuid' => 'order-null-status',
1351
+ 'company_uuid' => 'company-a',
1352
+ 'order_config_uuid' => 'config-freight',
1353
+ 'status' => null,
1354
+ 'created_at' => $now,
1355
+ 'updated_at' => $now,
1356
+ 'deleted_at' => null,
1357
+ ],
1358
+ [
1359
+ 'uuid' => 'order-deleted',
1360
+ 'company_uuid' => 'company-a',
1361
+ 'order_config_uuid' => 'config-parcel',
1362
+ 'status' => 'canceled',
1363
+ 'created_at' => $now,
1364
+ 'updated_at' => $now,
1365
+ 'deleted_at' => $now,
1366
+ ],
1367
+ [
1368
+ 'uuid' => 'order-other-company',
1369
+ 'company_uuid' => 'company-b',
1370
+ 'order_config_uuid' => 'config-other-company',
1371
+ 'status' => 'other_status',
1372
+ 'created_at' => $now,
1373
+ 'updated_at' => $now,
1374
+ 'deleted_at' => null,
1375
+ ],
1376
+ ]);
1377
+
1378
+ $statuses = (new OrderController())->statuses(fleetopsInternalOrderLifecycleStatusesRequest([], 'company-a'))->getData(true);
1379
+
1380
+ expect($statuses)->toBe([
1381
+ 'created',
1382
+ 'dispatched',
1383
+ 'loaded',
1384
+ 'driver_assigned',
1385
+ 'completed',
1386
+ ]);
1387
+ });
1388
+
1389
+ test('internal order controller statuses honor config filters and activity flag', function () {
1390
+ $connection = fleetopsInternalOrderLifecycleUseStatusesDatabase();
1391
+ $now = now();
1392
+ $controller = new OrderController();
1393
+
1394
+ $connection->table('order_configs')->insert([
1395
+ [
1396
+ 'uuid' => 'config-parcel',
1397
+ 'public_id' => 'order_config_parcel',
1398
+ 'company_uuid' => 'company-a',
1399
+ 'key' => 'parcel',
1400
+ 'name' => 'Parcel',
1401
+ 'flow' => json_encode([
1402
+ ['code' => 'driver_assigned'],
1403
+ ['code' => 'completed'],
1404
+ ]),
1405
+ 'created_at' => $now,
1406
+ 'updated_at' => $now,
1407
+ ],
1408
+ [
1409
+ 'uuid' => 'config-freight',
1410
+ 'public_id' => 'order_config_freight',
1411
+ 'company_uuid' => 'company-a',
1412
+ 'key' => 'freight',
1413
+ 'name' => 'Freight',
1414
+ 'flow' => json_encode([
1415
+ ['code' => 'loaded'],
1416
+ ]),
1417
+ 'created_at' => $now,
1418
+ 'updated_at' => $now,
1419
+ ],
1420
+ ]);
1421
+ $connection->table('orders')->insert([
1422
+ [
1423
+ 'uuid' => 'order-parcel-created',
1424
+ 'company_uuid' => 'company-a',
1425
+ 'order_config_uuid' => 'config-parcel',
1426
+ 'status' => 'created',
1427
+ 'created_at' => $now,
1428
+ 'updated_at' => $now,
1429
+ ],
1430
+ [
1431
+ 'uuid' => 'order-parcel-completed',
1432
+ 'company_uuid' => 'company-a',
1433
+ 'order_config_uuid' => 'config-parcel',
1434
+ 'status' => 'completed',
1435
+ 'created_at' => $now,
1436
+ 'updated_at' => $now,
1437
+ ],
1438
+ [
1439
+ 'uuid' => 'order-freight-loaded',
1440
+ 'company_uuid' => 'company-a',
1441
+ 'order_config_uuid' => 'config-freight',
1442
+ 'status' => 'loaded',
1443
+ 'created_at' => $now,
1444
+ 'updated_at' => $now,
1445
+ ],
1446
+ ]);
1447
+
1448
+ $keyFiltered = $controller->statuses(fleetopsInternalOrderLifecycleStatusesRequest([
1449
+ 'order_config_key' => 'parcel',
1450
+ ], 'company-a'))->getData(true);
1451
+
1452
+ $uuidFilteredWithoutActivities = $controller->statuses(fleetopsInternalOrderLifecycleStatusesRequest([
1453
+ 'order_config_uuid' => 'config-freight',
1454
+ 'order_config_key' => 'parcel',
1455
+ 'include_order_config_activities' => false,
1456
+ ], 'company-a'))->getData(true);
1457
+
1458
+ expect($keyFiltered)->toBe([
1459
+ 'created',
1460
+ 'dispatched',
1461
+ 'driver_assigned',
1462
+ 'completed',
1463
+ ])
1464
+ ->and($uuidFilteredWithoutActivities)->toBe(['loaded']);
1465
+ });
1466
+
1467
+ test('internal order controller label renders supported subject formats', function () {
1468
+ $controller = fleetopsInternalOrderLifecycleController();
1469
+ $order = fleetopsInternalOrderLifecycleOrder('order-label');
1470
+ $waypoint = new FleetOpsInternalOrderLifecycleWaypointFake();
1471
+ $entity = new FleetOpsInternalOrderLifecycleEntityFake();
1472
+ $waypoint->setRawAttributes(['uuid' => 'waypoint-label'], true);
1473
+ $entity->setRawAttributes(['uuid' => 'entity-label'], true);
1474
+
1475
+ $controller->labelOrder = $order;
1476
+ $controller->labelWaypoint = $waypoint;
1477
+ $controller->labelEntity = $entity;
1478
+
1479
+ expect($controller->label('order_label', new Request()))->toBe('stream:order-label')
1480
+ ->and($controller->label('waypoint_label', new Request(['format' => 'text'])))->toBe('label:waypoint-label')
1481
+ ->and($controller->label('entity_label', new Request(['format' => 'base64']))->getData(true))->toBe([
1482
+ 'data' => base64_encode('label:entity-label'),
1483
+ ]);
1484
+
1485
+ $controller->labelOrder = null;
1486
+
1487
+ expect($controller->label('order_missing', new Request())->getData(true))->toBe([
1488
+ 'error' => 'Unable to render label.',
1489
+ ]);
1490
+ });
1491
+
1492
+ test('internal order controller proof collection resolves order waypoint and entity subjects', function () {
1493
+ $order = fleetopsInternalOrderLifecycleOrder('order-proof');
1494
+ $order->forceFill(['payload_uuid' => 'payload-proof']);
1495
+ $waypoint = new FleetOpsInternalOrderLifecycleWaypointFake();
1496
+ $waypoint->setRawAttributes(['uuid' => 'waypoint-proof'], true);
1497
+ $entity = new FleetOpsInternalOrderLifecycleEntityFake();
1498
+ $entity->setRawAttributes(['uuid' => 'entity-proof'], true);
1499
+ $proof = new Proof();
1500
+ $proof->setRawAttributes(['uuid' => 'proof-one'], true);
1501
+
1502
+ $controller = fleetopsInternalOrderLifecycleController([$order]);
1503
+ $controller->proofOrder = $order;
1504
+ $controller->proofWaypoint = $waypoint;
1505
+ $controller->proofEntity = $entity;
1506
+ $controller->proofResults = collect([$proof]);
1507
+
1508
+ $orderProofs = $controller->proofs(new Request(), 'order-proof');
1509
+
1510
+ expect($orderProofs->collection)->toHaveCount(1)
1511
+ ->and($proof->queried_subject_uuid)->toBe('order-proof');
1512
+
1513
+ $waypointProofs = $controller->proofs(new Request(), 'order-proof', 'waypoint_proof');
1514
+
1515
+ expect($waypointProofs->collection)->toHaveCount(1)
1516
+ ->and($waypoint->proof_lookup_id)->toBe('waypoint_proof')
1517
+ ->and($proof->queried_subject_uuid)->toBe('waypoint-proof');
1518
+
1519
+ $entityProofs = $controller->proofs(new Request(), 'order-proof', 'entity_proof');
1520
+
1521
+ expect($entityProofs->collection)->toHaveCount(1)
1522
+ ->and($entity->proof_lookup_id)->toBe('entity_proof')
1523
+ ->and($proof->queried_subject_uuid)->toBe('entity-proof');
1524
+
1525
+ $controller->proofWaypoint = null;
1526
+
1527
+ expect($controller->proofs(new Request(), 'order-proof', 'waypoint_missing')->getData(true))->toBe([
1528
+ 'error' => 'Unable to retrieve proof of delivery for subject.',
1529
+ ]);
1530
+ });
1531
+
1532
+ test('internal order controller export lookup and schedule endpoints use resolved resources', function () {
1533
+ $controller = fleetopsInternalOrderLifecycleController();
1534
+
1535
+ $export = $controller->export(fleetopsInternalOrderExportRequest([
1536
+ 'format' => 'csv',
1537
+ 'selections' => ['order-one', 'order-two'],
1538
+ ]));
1539
+
1540
+ expect($export['download'])->toStartWith('order-')
1541
+ ->and($export['download'])->toEndWith('.csv')
1542
+ ->and($export['selections'])->toBe(['order-one', 'order-two'])
1543
+ ->and($controller->exportDownloads[0][0])->toBe(['order-one', 'order-two']);
1544
+
1545
+ expect($controller->lookup(new Request())->getData(true))->toBe([
1546
+ 'error' => 'No tracking number provided for lookup.',
1547
+ ])
1548
+ ->and($controller->lookup(new Request(['tracking' => 'TN-404']))->getData(true))->toBe([
1549
+ 'error' => 'No order found using tracking number provided.',
1550
+ ]);
1551
+
1552
+ $trackedOrder = fleetopsInternalOrderLifecycleOrder('tracked-order');
1553
+ $trackedOrder->trackerForTest = new FleetOpsInternalOrderLifecycleTrackerFake($trackedOrder);
1554
+ $controller->trackingOrder = $trackedOrder;
1555
+
1556
+ $lookup = $controller->lookup(new Request(['tracking' => 'TN-100']));
1557
+
1558
+ expect($lookup->resource)->toBe($trackedOrder)
1559
+ ->and($trackedOrder->tracking_lookup)->toBe('TN-100')
1560
+ ->and($trackedOrder->loadedMissing)->toBe([['trackingNumber', 'payload', 'trackingStatuses']])
1561
+ ->and($trackedOrder->tracker_data)->toBe(['tracker' => 'info', 'options' => []])
1562
+ ->and($trackedOrder->eta)->toBe(['eta' => [['stop' => 'dropoff']], 'options' => []]);
1563
+
1564
+ expect($controller->scheduleOrder(new Request(['order' => 'missing-order']))->getData(true))->toBe([
1565
+ 'error' => 'No order found to schedule.',
1566
+ ]);
1567
+
1568
+ $scheduledOrder = fleetopsInternalOrderLifecycleOrder('scheduled-order');
1569
+ $driver = new FleetOpsInternalOrderLifecycleDriverFake();
1570
+ $driver->setRawAttributes(['uuid' => 'driver-scheduled'], true);
1571
+ $controller->scheduleOrder = $scheduledOrder;
1572
+ $controller->scheduleDriver = $driver;
1573
+
1574
+ $scheduleResponse = $controller->scheduleOrder(new Request([
1575
+ 'order' => 'scheduled-order',
1576
+ 'scheduled_at' => '2026-08-01 09:30:00',
1577
+ 'driver_id' => 'driver-public',
1578
+ ]))->getData(true);
1579
+
1580
+ expect($scheduleResponse['status'])->toBe('OK')
1581
+ ->and($scheduleResponse['order'])->toBe('scheduled-order')
1582
+ ->and($scheduledOrder->schedule_lookup_id)->toBe('scheduled-order')
1583
+ ->and($driver->schedule_lookup_id)->toBe('driver-public')
1584
+ ->and($scheduledOrder->driver_assigned_uuid)->toBe('driver-scheduled')
1585
+ ->and($scheduledOrder->quietlySavedForTest)->toBeTrue()
1586
+ ->and($scheduledOrder->scheduled_at->format('Y-m-d H:i:s'))->toBe('2026-08-01 09:30:00');
1587
+ });
1588
+
1589
+ test('internal order controller waypoint helpers detect waypoint state and proof objects', function () {
1590
+ $restore = fleetopsSuppressStrNullDeprecations();
1591
+
1592
+ try {
1593
+ $controller = fleetopsInternalOrderLifecycleController();
1594
+ $payload = fleetopsInternalOrderLifecyclePayload();
1595
+ $proof = new Proof();
1596
+ $proof->setRawAttributes(['uuid' => 'proof-uuid', 'public_id' => 'proof_public'], true);
1597
+ $waypoint = new FleetOpsInternalOrderLifecycleWaypointFake();
1598
+ $waypoint->setRawAttributes([
1599
+ 'uuid' => 'waypoint-uuid',
1600
+ 'public_id' => 'waypoint_public',
1601
+ 'place_uuid' => 'place-current',
1602
+ 'tracking_number_uuid' => 'tracking-complete',
1603
+ 'order' => 0,
1604
+ ], true);
1605
+ $payload->setRelation('waypoints', collect([fleetopsInternalOrderLifecyclePlace('waypoint-place')]));
1606
+ $payload->setRelation('waypointMarkers', collect([$waypoint]));
1607
+
1608
+ $completeStatus = new TrackingStatus();
1609
+ $completeStatus->setRawAttributes(['code' => 'completed', 'complete' => true], true);
1610
+ $controller->trackingNumberStatuses['tracking-complete'] = $completeStatus;
1611
+
1612
+ expect($controller->callHelper('payloadHasWaypoints', null))->toBeFalse()
1613
+ ->and($controller->callHelper('payloadHasWaypoints', $payload))->toBeTrue()
1614
+ ->and($controller->callHelper('waypointMarkerIsComplete', $waypoint))->toBeTrue()
1615
+ ->and($controller->callHelper('resolveProof', $proof))->toBe($proof)
1616
+ ->and($controller->callHelper('resolveProof', ['not' => 'proof']))->toBeNull();
1617
+
1618
+ $waypointWithoutTracking = new FleetOpsInternalOrderLifecycleWaypointFake();
1619
+ $waypointWithoutTracking->setRawAttributes(['uuid' => 'waypoint-no-tracking', 'public_id' => 'waypoint_no_tracking'], true);
1620
+
1621
+ expect($controller->callHelper('waypointMarkerIsComplete', $waypointWithoutTracking))->toBeFalse();
1622
+ } finally {
1623
+ $restore();
1624
+ }
1625
+ });
1626
+
1627
+ test('internal order controller proof photo helpers normalize aliases and base64 inputs', function () {
1628
+ $controller = fleetopsInternalOrderLifecycleController();
1629
+ $encoded = base64_encode('proof photo');
1630
+ $dataUri = 'data:image/png;base64,' . $encoded;
1631
+ $invalid = 'not-valid-base64';
1632
+ $file = UploadedFile::fake()->image('proof.png', 16, 16);
1633
+
1634
+ $request = new Request([
1635
+ 'photos' => [$dataUri, [$dataUri, 42]],
1636
+ 'photo' => $encoded,
1637
+ 'files' => [$invalid],
1638
+ 'file' => null,
1639
+ ]);
1640
+ $request->files->set('file', $file);
1641
+
1642
+ expect($controller->callHelper('collectProofPhotoInputs', $request))->toHaveCount(3)
1643
+ ->and($controller->callHelper('isValidBase64ProofPhoto', $dataUri))->toBeTrue()
1644
+ ->and($controller->callHelper('isValidBase64ProofPhoto', $encoded))->toBeTrue()
1645
+ ->and($controller->callHelper('isValidBase64ProofPhoto', ['not' => 'a string']))->toBeFalse()
1646
+ ->and($controller->callHelper('isValidBase64ProofPhoto', $invalid))->toBeFalse()
1647
+ ->and($controller->callHelper('proofPhotoInputFingerprint', $dataUri))->toBe($controller->callHelper('proofPhotoInputFingerprint', $encoded))
1648
+ ->and($controller->callHelper('proofPhotoInputFingerprint', ['unsupported']))->toBeNull();
1649
+
1650
+ $incoming = [];
1651
+ $controller->appendProofInputsForTest($incoming, [$encoded, [$file, null], 99]);
1652
+
1653
+ expect($incoming)->toBe([$encoded, $file, 99])
1654
+ ->and($controller->callHelper('dedupeProofPhotoInputs', [$dataUri, $encoded, $file, $file, ['skip']]))->toBe([$dataUri, $file]);
1655
+ });
1656
+
1657
+ test('internal order controller waypoint helpers handle empty activity branches', function () {
1658
+ $controller = fleetopsInternalOrderLifecycleController();
1659
+ $payload = fleetopsInternalOrderLifecyclePayload();
1660
+ $activity = new Activity(['code' => 'arrived']);
1661
+
1662
+ $payload->forceFill(['current_waypoint_uuid' => null]);
1663
+ $payload->waypointMarkersForTest = collect();
1664
+ $payload->setRelation('waypointMarkers', collect());
1665
+
1666
+ expect($controller->callHelper('updateCurrentWaypointActivity', null, $activity, 'point'))->toBeNull()
1667
+ ->and($controller->callHelper('updateCurrentWaypointActivity', $payload, $activity))->toBeNull()
1668
+ ->and($controller->callHelper('payloadHasCurrentWaypointActivity', null, $activity))->toBeFalse()
1669
+ ->and($controller->callHelper('payloadHasCurrentWaypointActivity', $payload, $activity))->toBeFalse()
1670
+ ->and($controller->callHelper('advanceCurrentWaypointDestination', $payload))->toBeNull();
1671
+ });
1672
+
1673
+ test('internal order controller updates current waypoint activity and advances incomplete destinations', function () {
1674
+ $restore = fleetopsSuppressStrNullDeprecations();
1675
+
1676
+ try {
1677
+ FleetOpsInternalOrderLifecycleEventRecorder::$events = [];
1678
+ $controller = fleetopsInternalOrderLifecycleController();
1679
+ $order = fleetopsInternalOrderLifecycleOrder('order-waypoint', 'started');
1680
+ $payload = fleetopsInternalOrderLifecyclePayload();
1681
+ $payload->forceFill(['current_waypoint_uuid' => 'place-current']);
1682
+ $order->setRelation('payload', $payload);
1683
+ $payload->setRelation('order', $order);
1684
+
1685
+ $currentPlace = fleetopsInternalOrderLifecyclePlace('place-current');
1686
+ $nextPlace = fleetopsInternalOrderLifecyclePlace('place-next');
1687
+ $current = new FleetOpsInternalOrderLifecycleWaypointFake();
1688
+ $current->setRawAttributes([
1689
+ 'uuid' => 'waypoint-current',
1690
+ 'public_id' => 'waypoint_current',
1691
+ 'place_uuid' => 'place-current',
1692
+ 'tracking_number_uuid' => 'tracking-current',
1693
+ 'order' => 0,
1694
+ ], true);
1695
+ $current->setRelation('place', $currentPlace);
1696
+
1697
+ $next = new FleetOpsInternalOrderLifecycleWaypointFake();
1698
+ $next->setRawAttributes([
1699
+ 'uuid' => 'waypoint-next',
1700
+ 'public_id' => 'waypoint_next',
1701
+ 'place_uuid' => 'place-next',
1702
+ 'tracking_number_uuid' => 'tracking-next',
1703
+ 'order' => 1,
1704
+ ], true);
1705
+ $next->setRelation('place', $nextPlace);
1706
+
1707
+ $entity = new FleetOpsInternalOrderLifecycleEntityFake();
1708
+ $entity->setRawAttributes(['uuid' => 'entity-current', 'destination_uuid' => 'place-current'], true);
1709
+
1710
+ $payload->waypointMarkersForTest = collect([$current, $next]);
1711
+ $payload->setRelation('waypointMarkers', collect([$current, $next]));
1712
+ $payload->setRelation('entities', collect([$entity]));
1713
+
1714
+ $completeStatus = new TrackingStatus();
1715
+ $completeStatus->setRawAttributes(['code' => 'completed', 'complete' => true], true);
1716
+ $incompleteStatus = new TrackingStatus();
1717
+ $incompleteStatus->setRawAttributes(['code' => 'arrived', 'complete' => false], true);
1718
+ $controller->trackingNumberStatuses = [
1719
+ 'tracking-current' => $completeStatus,
1720
+ 'tracking-next' => $incompleteStatus,
1721
+ ];
1722
+
1723
+ $activity = new Activity(['code' => 'arrived', 'complete' => true]);
1724
+
1725
+ expect($controller->callHelper('updateCurrentWaypointActivity', $payload, $activity, 'point', 'proof'))->toBe($current)
1726
+ ->and($current->activities)->toBe([['arrived', 'point', 'proof']])
1727
+ ->and($entity->activities)->toBe([['arrived', 'point', 'proof']])
1728
+ ->and(FleetOpsInternalOrderLifecycleEventRecorder::$events)->toHaveCount(2)
1729
+ ->and($controller->callHelper('allWaypointMarkersComplete', null))->toBeFalse()
1730
+ ->and($controller->callHelper('allWaypointMarkersComplete', $payload))->toBeFalse()
1731
+ ->and($controller->callHelper('advanceCurrentWaypointDestination', null))->toBeNull();
1732
+
1733
+ $advanced = $controller->callHelper('advanceCurrentWaypointDestination', $payload);
1734
+
1735
+ expect($advanced)->toBe($next)
1736
+ ->and($payload->calls)->toContain(['setCurrentWaypoint', $next, true])
1737
+ ->and($payload->currentWaypoint)->toBe($nextPlace)
1738
+ ->and($payload->currentWaypointMarker)->toBe($next);
1739
+ } finally {
1740
+ $restore();
1741
+ }
1742
+ });