@fleetbase/fleetops-engine 0.6.58 → 0.6.59

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 (685) hide show
  1. package/README.md +3 -0
  2. package/addon/components/customer/form.hbs +0 -49
  3. package/addon/components/entity/card.hbs +1 -0
  4. package/addon/components/modals/order-label.hbs +1 -1
  5. package/addon/components/place/form.hbs +1 -1
  6. package/addon/services/entity-actions.js +32 -0
  7. package/addon/styles/fleetops-engine.css +0 -1
  8. package/composer.json +8 -3
  9. package/extension.json +1 -1
  10. package/index.js +7 -1
  11. package/package.json +2 -1
  12. package/scripts/coverage-file-runner.php +240 -0
  13. package/scripts/coverage-runner.php +2 -1
  14. package/scripts/coverage-summary.php +41 -2
  15. package/scripts/pest-bootstrap.php +237 -3
  16. package/scripts/pest-file-runner.php +13 -1
  17. package/scripts/pest-runner.php +57 -2
  18. package/server/resources/views/labels/entity-label.php +4 -4
  19. package/server/src/Casts/MultiPolygon.php +8 -3
  20. package/server/src/Casts/OrderConfigEntities.php +10 -3
  21. package/server/src/Casts/Point.php +10 -6
  22. package/server/src/Casts/Polygon.php +5 -2
  23. package/server/src/Console/Commands/AssignCustomerRoles.php +28 -6
  24. package/server/src/Console/Commands/AssignDriverRoles.php +36 -6
  25. package/server/src/Console/Commands/AuditCustomerUserConflicts.php +6 -1
  26. package/server/src/Console/Commands/DispatchAdhocOrders.php +12 -2
  27. package/server/src/Console/Commands/FixCustomerCompanies.php +46 -11
  28. package/server/src/Console/Commands/FixDriverCompanies.php +18 -3
  29. package/server/src/Console/Commands/FixLegacyOrderConfigs.php +31 -6
  30. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +94 -42
  31. package/server/src/Console/Commands/PurgeUnpurchasedServiceQuotes.php +42 -12
  32. package/server/src/Console/Commands/ReplayVehicleLocations.php +120 -55
  33. package/server/src/Console/Commands/SendDriverNotification.php +25 -4
  34. package/server/src/Console/Commands/SendMaintenanceReminders.php +43 -23
  35. package/server/src/Console/Commands/SimulateOrderRouteNavigation.php +36 -6
  36. package/server/src/Console/Commands/TrackOrderDistanceAndTime.php +6 -1
  37. package/server/src/Events/DriverLocationChanged.php +1 -1
  38. package/server/src/Events/DriverSimulatedLocationChanged.php +1 -1
  39. package/server/src/Events/EntityActivityChanged.php +1 -1
  40. package/server/src/Events/EntityCompleted.php +1 -1
  41. package/server/src/Events/EntityDriverAssigned.php +1 -1
  42. package/server/src/Events/FuelProviderTransactionImported.php +1 -1
  43. package/server/src/Events/FuelProviderTransactionMatched.php +1 -1
  44. package/server/src/Events/FuelProviderTransactionUnmatched.php +1 -1
  45. package/server/src/Events/FuelReportCreatedFromProvider.php +1 -1
  46. package/server/src/Events/GeofenceDwelled.php +1 -1
  47. package/server/src/Events/GeofenceEntered.php +1 -1
  48. package/server/src/Events/GeofenceExited.php +1 -1
  49. package/server/src/Events/VehicleLocationChanged.php +1 -1
  50. package/server/src/Events/WaypointActivityChanged.php +1 -1
  51. package/server/src/Events/WaypointCompleted.php +1 -1
  52. package/server/src/Exceptions/IntegratedVendorException.php +2 -2
  53. package/server/src/Exceptions/TelematicRateLimitExceededException.php +1 -1
  54. package/server/src/Http/Controllers/Api/v1/ContactController.php +89 -22
  55. package/server/src/Http/Controllers/Api/v1/CustomerController.php +296 -97
  56. package/server/src/Http/Controllers/Api/v1/DeviceController.php +48 -14
  57. package/server/src/Http/Controllers/Api/v1/DriverController.php +168 -63
  58. package/server/src/Http/Controllers/Api/v1/EntityController.php +112 -66
  59. package/server/src/Http/Controllers/Api/v1/EquipmentController.php +40 -10
  60. package/server/src/Http/Controllers/Api/v1/FleetController.php +54 -14
  61. package/server/src/Http/Controllers/Api/v1/FuelReportController.php +54 -14
  62. package/server/src/Http/Controllers/Api/v1/FuelTransactionController.php +36 -11
  63. package/server/src/Http/Controllers/Api/v1/GeofenceController.php +39 -21
  64. package/server/src/Http/Controllers/Api/v1/IssueController.php +54 -14
  65. package/server/src/Http/Controllers/Api/v1/LabelController.php +61 -22
  66. package/server/src/Http/Controllers/Api/v1/NavigatorController.php +21 -5
  67. package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +35 -5
  68. package/server/src/Http/Controllers/Api/v1/OrderController.php +388 -203
  69. package/server/src/Http/Controllers/Api/v1/PartController.php +40 -10
  70. package/server/src/Http/Controllers/Api/v1/PayloadController.php +90 -30
  71. package/server/src/Http/Controllers/Api/v1/PlaceController.php +188 -91
  72. package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +100 -36
  73. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +96 -26
  74. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +169 -81
  75. package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +104 -75
  76. package/server/src/Http/Controllers/Api/v1/TrackingNumberController.php +83 -14
  77. package/server/src/Http/Controllers/Api/v1/TrackingStatusController.php +66 -16
  78. package/server/src/Http/Controllers/Api/v1/VehicleController.php +121 -67
  79. package/server/src/Http/Controllers/Api/v1/VendorController.php +54 -14
  80. package/server/src/Http/Controllers/Api/v1/WorkOrderController.php +47 -12
  81. package/server/src/Http/Controllers/Api/v1/ZoneController.php +84 -24
  82. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +15 -5
  83. package/server/src/Http/Controllers/Internal/v1/ContactController.php +113 -28
  84. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +36 -11
  85. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +9 -4
  86. package/server/src/Http/Controllers/Internal/v1/DriverController.php +167 -69
  87. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +18 -3
  88. package/server/src/Http/Controllers/Internal/v1/FleetController.php +137 -60
  89. package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +24 -9
  90. package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +18 -3
  91. package/server/src/Http/Controllers/Internal/v1/GeocoderController.php +26 -8
  92. package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +12 -2
  93. package/server/src/Http/Controllers/Internal/v1/IntegratedVendorController.php +27 -6
  94. package/server/src/Http/Controllers/Internal/v1/IssueController.php +82 -29
  95. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +34 -26
  96. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +141 -97
  97. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +28 -8
  98. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +21 -12
  99. package/server/src/Http/Controllers/Internal/v1/MorphController.php +90 -18
  100. package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +88 -35
  101. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +149 -39
  102. package/server/src/Http/Controllers/Internal/v1/OrderConfigController.php +54 -18
  103. package/server/src/Http/Controllers/Internal/v1/OrderController.php +298 -86
  104. package/server/src/Http/Controllers/Internal/v1/PartController.php +18 -3
  105. package/server/src/Http/Controllers/Internal/v1/PaymentController.php +46 -14
  106. package/server/src/Http/Controllers/Internal/v1/PlaceController.php +31 -5
  107. package/server/src/Http/Controllers/Internal/v1/ProofController.php +88 -48
  108. package/server/src/Http/Controllers/Internal/v1/SearchController.php +1 -1
  109. package/server/src/Http/Controllers/Internal/v1/SensorController.php +6 -1
  110. package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +6 -1
  111. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +255 -105
  112. package/server/src/Http/Controllers/Internal/v1/SettingController.php +88 -35
  113. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -4
  114. package/server/src/Http/Controllers/Internal/v1/Traits/DriverSchedulingTrait.php +42 -14
  115. package/server/src/Http/Controllers/Internal/v1/VendorController.php +103 -33
  116. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +42 -12
  117. package/server/src/Http/Filter/DriverFilter.php +2 -2
  118. package/server/src/Http/Filter/OrderFilter.php +1 -1
  119. package/server/src/Http/Filter/VehicleFilter.php +21 -7
  120. package/server/src/Http/Middleware/AuthenticateCustomerToken.php +12 -2
  121. package/server/src/Http/Requests/BulkDispatchRequest.php +3 -9
  122. package/server/src/Http/Requests/CancelOrderRequest.php +2 -6
  123. package/server/src/Http/Requests/CreateContactRequest.php +2 -6
  124. package/server/src/Http/Requests/CreateDeviceRequest.php +2 -2
  125. package/server/src/Http/Requests/CreateDriverRequest.php +3 -9
  126. package/server/src/Http/Requests/CreateEntityRequest.php +2 -6
  127. package/server/src/Http/Requests/CreateEquipmentRequest.php +2 -2
  128. package/server/src/Http/Requests/CreateFleetRequest.php +2 -6
  129. package/server/src/Http/Requests/CreateFuelReportRequest.php +2 -6
  130. package/server/src/Http/Requests/CreateFuelTransactionRequest.php +2 -2
  131. package/server/src/Http/Requests/CreateIssueRequest.php +2 -6
  132. package/server/src/Http/Requests/CreateOrderRequest.php +7 -8
  133. package/server/src/Http/Requests/CreatePartRequest.php +2 -2
  134. package/server/src/Http/Requests/CreatePayloadRequest.php +7 -9
  135. package/server/src/Http/Requests/CreatePlaceRequest.php +2 -6
  136. package/server/src/Http/Requests/CreatePurchaseRateRequest.php +2 -6
  137. package/server/src/Http/Requests/CreateSensorRequest.php +2 -2
  138. package/server/src/Http/Requests/CreateServiceAreaRequest.php +2 -6
  139. package/server/src/Http/Requests/CreateServiceQuoteRequest.php +2 -6
  140. package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -6
  141. package/server/src/Http/Requests/CreateTrackingNumberRequest.php +2 -6
  142. package/server/src/Http/Requests/CreateTrackingStatusRequest.php +2 -6
  143. package/server/src/Http/Requests/CreateVehicleRequest.php +2 -6
  144. package/server/src/Http/Requests/CreateVendorRequest.php +2 -6
  145. package/server/src/Http/Requests/CreateWorkOrderRequest.php +2 -2
  146. package/server/src/Http/Requests/CreateZoneRequest.php +2 -6
  147. package/server/src/Http/Requests/DecodeTrackingNumberQR.php +2 -6
  148. package/server/src/Http/Requests/DriverSimulationRequest.php +2 -6
  149. package/server/src/Http/Requests/Internal/AssignOrderRequest.php +3 -7
  150. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +4 -12
  151. package/server/src/Http/Requests/Internal/CreateOrderConfigRequest.php +2 -6
  152. package/server/src/Http/Requests/Internal/CreateOrderRequest.php +7 -8
  153. package/server/src/Http/Requests/Internal/FleetActionRequest.php +18 -12
  154. package/server/src/Http/Requests/Internal/UpdateDriverRequest.php +6 -3
  155. package/server/src/Http/Requests/QueryServiceQuotesRequest.php +2 -6
  156. package/server/src/Http/Requests/ScheduleOrderRequest.php +2 -6
  157. package/server/src/Http/Requests/UpdateIssueRequest.php +1 -3
  158. package/server/src/Http/Resources/v1/Customer.php +2 -2
  159. package/server/src/Http/Resources/v1/Maintenance.php +3 -4
  160. package/server/src/Http/Resources/v1/MaintenanceSchedule.php +3 -4
  161. package/server/src/Http/Resources/v1/Order.php +3 -7
  162. package/server/src/Http/Resources/v1/PurchaseRate.php +4 -6
  163. package/server/src/Http/Resources/v1/TrackingStatus.php +4 -6
  164. package/server/src/Http/Resources/v1/WorkOrder.php +3 -4
  165. package/server/src/Imports/ContactImport.php +10 -1
  166. package/server/src/Imports/DriverImport.php +10 -1
  167. package/server/src/Imports/EquipmentImport.php +6 -1
  168. package/server/src/Imports/FleetImport.php +10 -1
  169. package/server/src/Imports/FuelReportImport.php +10 -1
  170. package/server/src/Imports/IssueImport.php +10 -1
  171. package/server/src/Imports/MaintenanceImport.php +6 -1
  172. package/server/src/Imports/MaintenanceScheduleImport.php +6 -1
  173. package/server/src/Imports/PartImport.php +6 -1
  174. package/server/src/Imports/PlaceImport.php +10 -1
  175. package/server/src/Imports/VehicleImport.php +10 -1
  176. package/server/src/Imports/VendorImport.php +10 -1
  177. package/server/src/Imports/WorkOrderImport.php +6 -1
  178. package/server/src/Integrations/Lalamove/Lalamove.php +3 -7
  179. package/server/src/Integrations/Lalamove/LalamoveServiceType.php +5 -8
  180. package/server/src/Jobs/CheckGeofenceDwell.php +41 -16
  181. package/server/src/Jobs/FinalizeApiOrderCreation.php +17 -2
  182. package/server/src/Jobs/FinalizeInternalOrderCreation.php +11 -1
  183. package/server/src/Jobs/NotifyBulkAssignedDriver.php +29 -16
  184. package/server/src/Jobs/ProcessAllocationJob.php +59 -25
  185. package/server/src/Jobs/ReplayPositions.php +17 -10
  186. package/server/src/Jobs/SendPositionReplay.php +18 -7
  187. package/server/src/Jobs/SimulateDrivingRoute.php +14 -3
  188. package/server/src/Jobs/SyncFuelProviderTransactionsJob.php +12 -2
  189. package/server/src/Listeners/HandleDeliveryCompletion.php +11 -1
  190. package/server/src/Listeners/HandleGeofenceDwelled.php +6 -1
  191. package/server/src/Listeners/HandleGeofenceEntered.php +4 -2
  192. package/server/src/Listeners/HandleGeofenceExited.php +6 -1
  193. package/server/src/Listeners/HandleOrderCanceled.php +18 -3
  194. package/server/src/Listeners/HandleOrderDispatchFailed.php +12 -2
  195. package/server/src/Listeners/HandleOrderDispatched.php +61 -26
  196. package/server/src/Listeners/HandleOrderDriverAssigned.php +12 -2
  197. package/server/src/Listeners/HandleUserRemovedFromCompany.php +19 -6
  198. package/server/src/Listeners/NotifyDriverOnShiftChange.php +24 -4
  199. package/server/src/Listeners/NotifyOrderEvent.php +11 -6
  200. package/server/src/Models/Contact.php +6 -7
  201. package/server/src/Models/Device.php +26 -3
  202. package/server/src/Models/Driver.php +0 -6
  203. package/server/src/Models/Entity.php +4 -4
  204. package/server/src/Models/Equipment.php +1 -1
  205. package/server/src/Models/FuelReport.php +5 -1
  206. package/server/src/Models/IntegratedVendor.php +2 -2
  207. package/server/src/Models/Issue.php +5 -1
  208. package/server/src/Models/MaintenanceSchedule.php +25 -10
  209. package/server/src/Models/Order.php +18 -15
  210. package/server/src/Models/OrderConfig.php +1 -1
  211. package/server/src/Models/Payload.php +9 -16
  212. package/server/src/Models/Place.php +20 -13
  213. package/server/src/Models/ServiceArea.php +3 -6
  214. package/server/src/Models/ServiceQuote.php +5 -0
  215. package/server/src/Models/ServiceRate.php +13 -6
  216. package/server/src/Models/Telematic.php +2 -2
  217. package/server/src/Models/TrackingNumber.php +55 -10
  218. package/server/src/Models/TrackingStatus.php +24 -4
  219. package/server/src/Models/Waypoint.php +44 -10
  220. package/server/src/Models/Zone.php +3 -6
  221. package/server/src/Notifications/DriverArrivedAtGeofence.php +1 -1
  222. package/server/src/Notifications/OrderDispatched.php +3 -9
  223. package/server/src/Notifications/OrderPing.php +3 -9
  224. package/server/src/Observers/CategoryObserver.php +6 -1
  225. package/server/src/Observers/CompanyObserver.php +5 -0
  226. package/server/src/Observers/CompanyUserObserver.php +12 -2
  227. package/server/src/Observers/DriverObserver.php +19 -4
  228. package/server/src/Observers/FleetObserver.php +6 -1
  229. package/server/src/Observers/PurchaseRateObserver.php +66 -11
  230. package/server/src/Observers/ServiceAreaObserver.php +12 -2
  231. package/server/src/Observers/ServiceRateObserver.php +21 -6
  232. package/server/src/Observers/TrackingNumberObserver.php +19 -4
  233. package/server/src/Observers/UserObserver.php +6 -1
  234. package/server/src/Observers/VehicleObserver.php +27 -7
  235. package/server/src/Observers/WorkOrderObserver.php +27 -7
  236. package/server/src/Orchestration/Engines/DriverAssignmentEngine.php +8 -3
  237. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +2 -2
  238. package/server/src/Providers/FleetOpsServiceProvider.php +12 -0
  239. package/server/src/Providers/NotificationServiceProvider.php +6 -0
  240. package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +36 -26
  241. package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +18 -5
  242. package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +6 -1
  243. package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +26 -6
  244. package/server/src/Support/Analytics/FuelEfficiency.php +9 -3
  245. package/server/src/Support/Analytics/FuelProviderSummary.php +14 -5
  246. package/server/src/Support/Analytics/GeofenceViolations.php +40 -20
  247. package/server/src/Support/Analytics/IssuesInsights.php +38 -18
  248. package/server/src/Support/Analytics/MaintenanceOverview.php +51 -21
  249. package/server/src/Support/Analytics/OnTimeDelivery.php +3 -2
  250. package/server/src/Support/Analytics/OperationsPulse.php +59 -35
  251. package/server/src/Support/Analytics/OrdersByStatus.php +12 -7
  252. package/server/src/Support/Analytics/RevenueTrend.php +16 -6
  253. package/server/src/Support/Analytics/TopDrivers.php +3 -2
  254. package/server/src/Support/FleetOps.php +109 -104
  255. package/server/src/Support/Geocoding.php +52 -14
  256. package/server/src/Support/GeofenceIntersectionService.php +47 -27
  257. package/server/src/Support/LiveOrderQuery.php +6 -1
  258. package/server/src/Support/Metrics/ActiveRevenueQuery.php +3 -2
  259. package/server/src/Support/Metrics/FuelCostsMetric.php +6 -1
  260. package/server/src/Support/ResolvesOrderServiceStops.php +10 -0
  261. package/server/src/Support/Telematics/Providers/GeotabProvider.php +9 -4
  262. package/server/src/Support/Utils.php +122 -16
  263. package/server/src/Tracking/Providers/GoogleRoutesTrackingProvider.php +6 -1
  264. package/server/src/Tracking/Providers/OsrmTrackingProvider.php +11 -6
  265. package/server/src/Tracking/TrackingProviderManager.php +2 -3
  266. package/server/src/Tracking/TrackingProviderRegistry.php +8 -4
  267. package/server/src/Traits/PayloadAccessors.php +6 -1
  268. package/server/src/routes.php +23 -16
  269. package/server/tests/AiOperationalQueryCapabilityTest.php +457 -1
  270. package/server/tests/AiPreviewCapabilityContractsTest.php +180 -0
  271. package/server/tests/AnalyticsRoutesTest.php +380 -1
  272. package/server/tests/AnalyticsWidgetContractsTest.php +740 -0
  273. package/server/tests/ApiContactControllerContractsTest.php +284 -0
  274. package/server/tests/ApiCustomerControllerContractsTest.php +962 -0
  275. package/server/tests/ApiDriverControllerContractsTest.php +571 -0
  276. package/server/tests/ApiEntityControllerContractsTest.php +329 -0
  277. package/server/tests/ApiEquipmentControllerContractsTest.php +239 -0
  278. package/server/tests/ApiFleetControllerContractsTest.php +176 -0
  279. package/server/tests/ApiFuelReportControllerContractsTest.php +217 -0
  280. package/server/tests/ApiIssueControllerContractsTest.php +217 -0
  281. package/server/tests/ApiOrderControllerContractsTest.php +1030 -0
  282. package/server/tests/ApiPartControllerContractsTest.php +251 -0
  283. package/server/tests/ApiPayloadControllerContractsTest.php +314 -0
  284. package/server/tests/ApiPlaceControllerContractsTest.php +541 -0
  285. package/server/tests/ApiPurchaseRateControllerContractsTest.php +283 -0
  286. package/server/tests/ApiServiceAreaZoneControllerContractsTest.php +442 -0
  287. package/server/tests/ApiServiceQuoteControllerContractsTest.php +322 -0
  288. package/server/tests/ApiTrackingNumberControllerContractsTest.php +225 -0
  289. package/server/tests/ApiTrackingStatusControllerContractsTest.php +254 -0
  290. package/server/tests/ApiVehicleControllerContractsTest.php +368 -0
  291. package/server/tests/ApiVendorControllerContractsTest.php +199 -0
  292. package/server/tests/AssetAndPlaceSearchContractsTest.php +220 -0
  293. package/server/tests/BackendSmallModelContractsTest.php +354 -0
  294. package/server/tests/CapacityAllocationEngineTest.php +56 -0
  295. package/server/tests/CommandContractsTest.php +2695 -0
  296. package/server/tests/CompactResourceSerializationTest.php +2745 -0
  297. package/server/tests/ControllerFilterContractsTest.php +1273 -0
  298. package/server/tests/ControllerHelperContractsTest.php +1740 -0
  299. package/server/tests/ControllerSerializationContractsTest.php +141 -0
  300. package/server/tests/CoverageSummaryScriptTest.php +93 -0
  301. package/server/tests/CustomerContactIdentitySafetyTest.php +1 -6
  302. package/server/tests/CustomerEndpointTest.php +165 -1
  303. package/server/tests/DriverFilterExecutionTest.php +263 -0
  304. package/server/tests/DriverGeofenceProcessingTest.php +153 -0
  305. package/server/tests/DriverResourceSerializationTest.php +245 -0
  306. package/server/tests/DriverSchedulingTraitContractsTest.php +259 -0
  307. package/server/tests/EventContractsTest.php +1259 -0
  308. package/server/tests/ExceptionContractsTest.php +81 -0
  309. package/server/tests/ExpansionContractsTest.php +242 -0
  310. package/server/tests/ExportCoverageTest.php +116 -0
  311. package/server/tests/Feature/Http/Api/ContactControllerPhotoAndDeleteTest.php +199 -0
  312. package/server/tests/Feature/Http/Api/CustomerControllerHelperSeamsTest.php +831 -0
  313. package/server/tests/Feature/Http/Api/DeviceControllerContractsTest.php +347 -0
  314. package/server/tests/Feature/Http/Api/DeviceControllerCrudTest.php +176 -0
  315. package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +451 -0
  316. package/server/tests/Feature/Http/Api/DriverControllerHelpersTest.php +240 -0
  317. package/server/tests/Feature/Http/Api/DriverControllerLookupTest.php +186 -0
  318. package/server/tests/Feature/Http/Api/DriverControllerOrganizationsTest.php +112 -0
  319. package/server/tests/Feature/Http/Api/DriverControllerSessionHelpersTest.php +207 -0
  320. package/server/tests/Feature/Http/Api/DriverControllerSwitchOrgAndGeofenceTest.php +329 -0
  321. package/server/tests/Feature/Http/Api/DriverControllerTrackTest.php +383 -0
  322. package/server/tests/Feature/Http/Api/EntityControllerHelpersTest.php +150 -0
  323. package/server/tests/Feature/Http/Api/FuelTransactionControllerContractsTest.php +352 -0
  324. package/server/tests/Feature/Http/Api/GeofenceControllerContractsTest.php +364 -0
  325. package/server/tests/Feature/Http/Api/LabelControllerSubjectsTest.php +148 -0
  326. package/server/tests/Feature/Http/Api/OrderConfigControllerTest.php +112 -0
  327. package/server/tests/Feature/Http/Api/OrderControllerCreateTest.php +279 -0
  328. package/server/tests/Feature/Http/Api/OrderControllerIntegratedVendorTest.php +295 -0
  329. package/server/tests/Feature/Http/Api/OrderControllerLifecycleActionsTest.php +279 -0
  330. package/server/tests/Feature/Http/Api/OrderControllerLookupHelpersTest.php +207 -0
  331. package/server/tests/Feature/Http/Api/OrderControllerNearbyFiltersTest.php +282 -0
  332. package/server/tests/Feature/Http/Api/OrderControllerPersistenceHelpersTest.php +216 -0
  333. package/server/tests/Feature/Http/Api/OrderControllerQueryFiltersTest.php +184 -0
  334. package/server/tests/Feature/Http/Api/OrderControllerStartActivityTest.php +663 -0
  335. package/server/tests/Feature/Http/Api/PayloadControllerHelpersTest.php +134 -0
  336. package/server/tests/Feature/Http/Api/PlaceAndServiceQuoteSeamsTest.php +479 -0
  337. package/server/tests/Feature/Http/Api/PurchaseRateControllerHelpersTest.php +176 -0
  338. package/server/tests/Feature/Http/Api/PurchaseRateControllerOrderCreationTest.php +268 -0
  339. package/server/tests/Feature/Http/Api/SensorControllerCrudTest.php +239 -0
  340. package/server/tests/Feature/Http/Api/ServiceAreaControllerBorderAndSeamsTest.php +187 -0
  341. package/server/tests/Feature/Http/Api/ServiceQuoteControllerQueryTest.php +405 -0
  342. package/server/tests/Feature/Http/Api/ServiceQuoteControllerVendorBridgeTest.php +212 -0
  343. package/server/tests/Feature/Http/Api/ServiceRateControllerContractsTest.php +263 -0
  344. package/server/tests/Feature/Http/Api/SmallApiControllerHelpersTest.php +272 -0
  345. package/server/tests/Feature/Http/Api/TrackingNumberControllerHelpersTest.php +154 -0
  346. package/server/tests/Feature/Http/Api/TrackingStatusControllerHelpersTest.php +165 -0
  347. package/server/tests/Feature/Http/Api/VehicleControllerTrackingTest.php +483 -0
  348. package/server/tests/Feature/Http/Api/WorkOrderControllerContractsTest.php +326 -0
  349. package/server/tests/Feature/Http/Api/WorkOrderControllerHelpersTest.php +130 -0
  350. package/server/tests/Feature/Http/Api/ZoneControllerBordersAndSeamsTest.php +294 -0
  351. package/server/tests/Feature/Http/FleetOpsRouteRegistrationTest.php +189 -0
  352. package/server/tests/Feature/Http/Internal/AnalyticsControllerSeamsTest.php +53 -0
  353. package/server/tests/Feature/Http/Internal/ContactControllerContractsTest.php +240 -0
  354. package/server/tests/Feature/Http/Internal/ContactControllerCustomerGuardsTest.php +128 -0
  355. package/server/tests/Feature/Http/Internal/ContactControllerEndpointsTest.php +294 -0
  356. package/server/tests/Feature/Http/Internal/CustomerControllerCredentialsTest.php +196 -0
  357. package/server/tests/Feature/Http/Internal/DeviceControllerContractsTest.php +370 -0
  358. package/server/tests/Feature/Http/Internal/DeviceControllerExportAndFiltersTest.php +151 -0
  359. package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +1109 -0
  360. package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +233 -0
  361. package/server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php +710 -0
  362. package/server/tests/Feature/Http/Internal/DriverControllerHelpersTest.php +218 -0
  363. package/server/tests/Feature/Http/Internal/FleetAssignmentsAndTriggersTest.php +198 -0
  364. package/server/tests/Feature/Http/Internal/FleetOpsLookupControllerDbTest.php +90 -0
  365. package/server/tests/Feature/Http/Internal/HubControllerTest.php +206 -0
  366. package/server/tests/Feature/Http/Internal/IssueControllerContractsTest.php +404 -0
  367. package/server/tests/Feature/Http/Internal/IssueControllerExportImportTest.php +118 -0
  368. package/server/tests/Feature/Http/Internal/LiveControllerEndpointsTest.php +246 -0
  369. package/server/tests/Feature/Http/Internal/LiveControllerTest.php +778 -0
  370. package/server/tests/Feature/Http/Internal/MaintenanceScheduleControllerContractsTest.php +263 -0
  371. package/server/tests/Feature/Http/Internal/MaintenanceScheduleControllerSurfacesTest.php +214 -0
  372. package/server/tests/Feature/Http/Internal/MetricsControllerContractsTest.php +249 -0
  373. package/server/tests/Feature/Http/Internal/MetricsControllerHelpersTest.php +66 -0
  374. package/server/tests/Feature/Http/Internal/MorphControllerHelpersTest.php +125 -0
  375. package/server/tests/Feature/Http/Internal/NavigatorControllerHelpersTest.php +201 -0
  376. package/server/tests/Feature/Http/Internal/OrchestrationControllerContractsTest.php +928 -0
  377. package/server/tests/Feature/Http/Internal/OrchestrationControllerHelpersTest.php +214 -0
  378. package/server/tests/Feature/Http/Internal/OrchestrationControllerImportOrdersTest.php +340 -0
  379. package/server/tests/Feature/Http/Internal/OrderConfigControllerContractsTest.php +314 -0
  380. package/server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php +663 -0
  381. package/server/tests/Feature/Http/Internal/OrderControllerCapturePhotoTest.php +507 -0
  382. package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +1742 -0
  383. package/server/tests/Feature/Http/Internal/OrderControllerCreateRecordTest.php +488 -0
  384. package/server/tests/Feature/Http/Internal/OrderControllerHelperSeamsTest.php +298 -0
  385. package/server/tests/Feature/Http/Internal/OrderControllerHelpersTest.php +267 -0
  386. package/server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php +179 -0
  387. package/server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php +80 -0
  388. package/server/tests/Feature/Http/Internal/PaymentControllerPaymentsTest.php +154 -0
  389. package/server/tests/Feature/Http/Internal/PlaceControllerTest.php +252 -0
  390. package/server/tests/Feature/Http/Internal/PositionControllerReplayMetricsTest.php +163 -0
  391. package/server/tests/Feature/Http/Internal/ProofControllerContractsTest.php +219 -0
  392. package/server/tests/Feature/Http/Internal/SearchControllerEndpointTest.php +151 -0
  393. package/server/tests/Feature/Http/Internal/ServiceQuoteControllerHelpersTest.php +208 -0
  394. package/server/tests/Feature/Http/Internal/ServiceQuoteControllerQueryRecordTest.php +217 -0
  395. package/server/tests/Feature/Http/Internal/ServiceQuoteControllerVendorBridgeTest.php +239 -0
  396. package/server/tests/Feature/Http/Internal/ServiceQuotePreliminaryQueryTest.php +271 -0
  397. package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +133 -0
  398. package/server/tests/Feature/Http/Internal/SmallInternalControllersTest.php +180 -0
  399. package/server/tests/Feature/Http/Internal/TelematicControllerEndpointsTest.php +417 -0
  400. package/server/tests/Feature/Http/Internal/VehicleControllerEndpointsTest.php +361 -0
  401. package/server/tests/Feature/Http/Internal/VendorControllerEndpointsTest.php +203 -0
  402. package/server/tests/Feature/Http/Internal/WorkOrderAndConfigSurfacesTest.php +289 -0
  403. package/server/tests/Feature/Http/Internal/WorkOrderControllerContractsTest.php +199 -0
  404. package/server/tests/Feature/Http/TelematicWebhookIngestTest.php +343 -0
  405. package/server/tests/Feature/Http/Telematics/TelematicWebhookControllerProcessingTest.php +254 -0
  406. package/server/tests/FleetOpsSupportContractsTest.php +187 -0
  407. package/server/tests/FlespiProviderContractsTest.php +244 -0
  408. package/server/tests/FlowResourceTest.php +295 -0
  409. package/server/tests/FuelProviderConnectionControllerContractsTest.php +303 -0
  410. package/server/tests/FuelProviderImplementationTest.php +753 -0
  411. package/server/tests/FuelProviderResourceContractsTest.php +171 -0
  412. package/server/tests/FuelProviderTransactionControllerContractsTest.php +181 -0
  413. package/server/tests/GeofenceIntersectionServiceContractsTest.php +164 -0
  414. package/server/tests/HOSConstraintContractsTest.php +128 -0
  415. package/server/tests/HubAndGettingStartedContractsTest.php +158 -0
  416. package/server/tests/ImportContractsTest.php +191 -0
  417. package/server/tests/InternalCustomerControllerContractsTest.php +238 -0
  418. package/server/tests/InternalFleetControllerContractsTest.php +389 -0
  419. package/server/tests/InternalImportExportControllerContractsTest.php +289 -0
  420. package/server/tests/InternalMetricsControllerContractsTest.php +69 -0
  421. package/server/tests/InternalPlaceWorkOrderControllerContractsTest.php +207 -0
  422. package/server/tests/InternalServiceQuoteCheckoutControllerContractsTest.php +470 -0
  423. package/server/tests/InternalVendorControllerContractsTest.php +331 -0
  424. package/server/tests/InventoryTelemetryModelContractsTest.php +516 -0
  425. package/server/tests/IssueTimelineContractsTest.php +191 -0
  426. package/server/tests/LabelPdfRenderingTest.php +57 -0
  427. package/server/tests/LalamoveIntegrationContractsTest.php +167 -0
  428. package/server/tests/LiveOrderQueryTest.php +145 -18
  429. package/server/tests/LookupAndGeocoderControllerContractsTest.php +357 -0
  430. package/server/tests/MaintainableTraitContractsTest.php +245 -0
  431. package/server/tests/MaintenanceScheduleControllerContractsTest.php +101 -0
  432. package/server/tests/MaintenanceWarrantyContractsTest.php +237 -0
  433. package/server/tests/ManifestControllerContractsTest.php +310 -0
  434. package/server/tests/MetricsRegistryTest.php +596 -10
  435. package/server/tests/MiddlewareContractsTest.php +150 -0
  436. package/server/tests/ModelAccessorContractsTest.php +3201 -0
  437. package/server/tests/MorphControllerContractsTest.php +410 -0
  438. package/server/tests/NavigatorControllerContractsTest.php +206 -0
  439. package/server/tests/NotificationAndMailContractsTest.php +677 -0
  440. package/server/tests/ObserverContractsTest.php +1414 -0
  441. package/server/tests/OperationalAlgorithmContractsTest.php +852 -0
  442. package/server/tests/OperationsMonitorEndpointTest.php +2 -1
  443. package/server/tests/OrchestrationPayloadBuilderTest.php +87 -0
  444. package/server/tests/OrderActivityFlowRegressionTest.php +354 -7
  445. package/server/tests/OrderDispatchListenerContractsTest.php +577 -0
  446. package/server/tests/OrderFilterExecutionTest.php +244 -0
  447. package/server/tests/OrderQueuedClosureRegressionTest.php +2 -1
  448. package/server/tests/PaymentControllerContractsTest.php +199 -0
  449. package/server/tests/PointResolutionTest.php +54 -0
  450. package/server/tests/ProcessAllocationJobContractsTest.php +224 -0
  451. package/server/tests/ProofAndSensorControllerContractsTest.php +44 -0
  452. package/server/tests/ProviderContractsTest.php +409 -0
  453. package/server/tests/PublicApiControllerInputContractsTest.php +294 -0
  454. package/server/tests/QueueJobContractsTest.php +764 -0
  455. package/server/tests/ReportSchemaContractsTest.php +271 -0
  456. package/server/tests/RequestContractsTest.php +966 -0
  457. package/server/tests/RouteRegistrationExecutionTest.php +178 -0
  458. package/server/tests/RouteSequencingEngineTest.php +179 -0
  459. package/server/tests/RulesAndCastsTest.php +246 -0
  460. package/server/tests/SamsaraProviderContractsTest.php +272 -0
  461. package/server/tests/SearchControllerContractsTest.php +311 -0
  462. package/server/tests/ServiceRateRouteValidationTest.php +76 -0
  463. package/server/tests/SettingControllerContractsTest.php +410 -0
  464. package/server/tests/SmallControllerContractsTest.php +456 -0
  465. package/server/tests/SupportAnalyticsCustomerAuthContractsTest.php +221 -0
  466. package/server/tests/SupportJobAndAiCoverageTest.php +812 -0
  467. package/server/tests/SupportServiceContractsTest.php +287 -0
  468. package/server/tests/SupportValueObjectsTest.php +155 -0
  469. package/server/tests/TelematicControllerContractsTest.php +185 -0
  470. package/server/tests/TelematicProviderRegistryTest.php +167 -0
  471. package/server/tests/TelematicServiceHelperContractsTest.php +315 -0
  472. package/server/tests/TelematicWebhookControllerContractsTest.php +253 -0
  473. package/server/tests/TelematicsHardeningTest.php +642 -86
  474. package/server/tests/TrackingIntelligenceTest.php +644 -1
  475. package/server/tests/TrackingNumberTraitContractsTest.php +106 -0
  476. package/server/tests/Unit/Casts/SpatialCastBranchesTest.php +75 -0
  477. package/server/tests/Unit/Console/AssignCustomerRolesCommandTest.php +102 -0
  478. package/server/tests/Unit/Console/AssignDriverRolesCommandTest.php +113 -0
  479. package/server/tests/Unit/Console/Commands/FixLegacyOrderConfigsHelpersTest.php +108 -0
  480. package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +265 -0
  481. package/server/tests/Unit/Console/Commands/ReplayVehicleLocationsCommandTest.php +320 -0
  482. package/server/tests/Unit/Console/Commands/SimulateGeofenceEventsCommandTest.php +207 -0
  483. package/server/tests/Unit/Console/FixCommandsAndMaintenanceControllerTest.php +256 -0
  484. package/server/tests/Unit/Console/FleetOpsCommandsTest.php +198 -0
  485. package/server/tests/Unit/Console/ReplayVehicleLocationsCommandTest.php +252 -0
  486. package/server/tests/Unit/Console/SendMaintenanceRemindersTest.php +212 -0
  487. package/server/tests/Unit/Console/SimulateGeofenceEventsCommandTest.php +232 -0
  488. package/server/tests/Unit/Console/SyncTelematicsCommandTest.php +136 -0
  489. package/server/tests/Unit/Constraints/HOSConstraintTest.php +158 -0
  490. package/server/tests/Unit/Events/EventModelRecordTest.php +93 -0
  491. package/server/tests/Unit/Events/OrderDispatchFailedTest.php +34 -0
  492. package/server/tests/Unit/Exports/ExportCollectionsTest.php +142 -0
  493. package/server/tests/Unit/Exports/OperationalExportMappingTest.php +651 -0
  494. package/server/tests/Unit/Exports/VehicleExportTest.php +284 -0
  495. package/server/tests/Unit/Flow/EventFiringTest.php +95 -0
  496. package/server/tests/Unit/Http/Api/QueryWithRequestSeamsTest.php +198 -0
  497. package/server/tests/Unit/Http/Api/SensorInputTest.php +95 -0
  498. package/server/tests/Unit/Http/Filter/DeviceFilterTest.php +199 -0
  499. package/server/tests/Unit/Http/Filter/EmptyValueGuardsTest.php +66 -0
  500. package/server/tests/Unit/Http/Filter/FilterDateRangesTest.php +72 -0
  501. package/server/tests/Unit/Http/Filter/FuelProviderTransactionFilterTest.php +172 -0
  502. package/server/tests/Unit/Http/Filter/InternalRelationUuidLookupTest.php +135 -0
  503. package/server/tests/Unit/Http/Filter/IssueFilterTest.php +129 -0
  504. package/server/tests/Unit/Http/Filter/PartFilterTest.php +173 -0
  505. package/server/tests/Unit/Http/Internal/ControllerDelegationSeamsTest.php +136 -0
  506. package/server/tests/Unit/Http/Internal/ControllerExcelSeamsTest.php +89 -0
  507. package/server/tests/Unit/Http/Middleware/SetupDriverSessionTest.php +32 -0
  508. package/server/tests/Unit/Http/Requests/CreateFuelReportRequestTest.php +47 -0
  509. package/server/tests/Unit/Http/Requests/FleetActionRequestTest.php +80 -0
  510. package/server/tests/Unit/Http/Requests/RequestAuthorizationTest.php +50 -0
  511. package/server/tests/Unit/Http/Resources/CustomerResourceTest.php +108 -0
  512. package/server/tests/Unit/Http/Resources/EntityCustomerTypeTest.php +35 -0
  513. package/server/tests/Unit/Http/Resources/FleetResourceTest.php +102 -0
  514. package/server/tests/Unit/Http/Resources/IndexOrderResourceTest.php +210 -0
  515. package/server/tests/Unit/Http/Resources/IndexVehicleResourceTest.php +194 -0
  516. package/server/tests/Unit/Http/Resources/MaintenanceResourcesTest.php +182 -0
  517. package/server/tests/Unit/Http/Resources/OrderAndDriverResourceTest.php +121 -0
  518. package/server/tests/Unit/Http/Resources/OrderConfigResourceTest.php +47 -0
  519. package/server/tests/Unit/Http/Resources/PayloadResourceTest.php +111 -0
  520. package/server/tests/Unit/Http/Resources/RelationResourceFallbacksTest.php +32 -0
  521. package/server/tests/Unit/Http/Resources/ResourceRelationResolutionTest.php +55 -0
  522. package/server/tests/Unit/Http/Resources/TrackingNumberResourceTest.php +174 -0
  523. package/server/tests/Unit/Http/Resources/WaypointResourceTest.php +295 -0
  524. package/server/tests/Unit/Http/Resources/WorkOrderResourceTest.php +164 -0
  525. package/server/tests/Unit/Imports/ImportDelegationTest.php +151 -0
  526. package/server/tests/Unit/Integrations/Lalamove/LalamoveDeliveryStopTest.php +79 -0
  527. package/server/tests/Unit/Integrations/Lalamove/LalamoveLifecycleTest.php +211 -0
  528. package/server/tests/Unit/Integrations/Lalamove/LalamoveQuoteAndServiceOrderTest.php +381 -0
  529. package/server/tests/Unit/Integrations/Lalamove/LalamoveServiceTypeTest.php +51 -0
  530. package/server/tests/Unit/Integrations/LalamoveQuotationTest.php +194 -0
  531. package/server/tests/Unit/Integrations/LalamoveResponseHandlingTest.php +58 -0
  532. package/server/tests/Unit/Jobs/GeofenceDwellAndBulkNotifyTest.php +183 -0
  533. package/server/tests/Unit/Jobs/ReplayPositionsTest.php +57 -0
  534. package/server/tests/Unit/Jobs/SimulateWaypointReachedTest.php +92 -0
  535. package/server/tests/Unit/Listeners/HandleGeofenceEnteredTest.php +256 -0
  536. package/server/tests/Unit/Listeners/HandleOrderDispatchFailedListenerTest.php +101 -0
  537. package/server/tests/Unit/Listeners/HandleOrderDispatchedConcreteTest.php +203 -0
  538. package/server/tests/Unit/Listeners/HandleOrderDispatchedHelpersTest.php +186 -0
  539. package/server/tests/Unit/Listeners/HandleUserRemovedFromCompanyTest.php +65 -0
  540. package/server/tests/Unit/Listeners/ListenerDelegationSeamsTest.php +83 -0
  541. package/server/tests/Unit/Models/AssetTest.php +469 -0
  542. package/server/tests/Unit/Models/ContactCustomerUserTest.php +370 -0
  543. package/server/tests/Unit/Models/ContactTest.php +577 -0
  544. package/server/tests/Unit/Models/DeviceEventTest.php +481 -0
  545. package/server/tests/Unit/Models/DeviceLastPositionDefaultTest.php +128 -0
  546. package/server/tests/Unit/Models/DriverModelHelpersTest.php +268 -0
  547. package/server/tests/Unit/Models/DriverTest.php +401 -0
  548. package/server/tests/Unit/Models/EntityTest.php +171 -0
  549. package/server/tests/Unit/Models/EquipmentTest.php +434 -0
  550. package/server/tests/Unit/Models/FleetTest.php +153 -0
  551. package/server/tests/Unit/Models/FuelProviderTransactionTest.php +82 -0
  552. package/server/tests/Unit/Models/FuelReportImportAndLabelsTest.php +193 -0
  553. package/server/tests/Unit/Models/ImportRowFallbackSeamsTest.php +100 -0
  554. package/server/tests/Unit/Models/IntegratedVendorLifecycleTest.php +203 -0
  555. package/server/tests/Unit/Models/IntegratedVendorTest.php +135 -0
  556. package/server/tests/Unit/Models/IssueTest.php +160 -0
  557. package/server/tests/Unit/Models/MaintenanceScheduleTest.php +319 -0
  558. package/server/tests/Unit/Models/MaintenanceTest.php +277 -0
  559. package/server/tests/Unit/Models/MaintenanceWorkOrderImportTest.php +161 -0
  560. package/server/tests/Unit/Models/ManifestStopTest.php +121 -0
  561. package/server/tests/Unit/Models/ManifestTest.php +163 -0
  562. package/server/tests/Unit/Models/ModelLookupFallbackSeamsTest.php +149 -0
  563. package/server/tests/Unit/Models/OrderConfigTest.php +341 -0
  564. package/server/tests/Unit/Models/OrderDynamicContractsTest.php +190 -0
  565. package/server/tests/Unit/Models/OrderPayloadConfigAndDistanceTest.php +426 -0
  566. package/server/tests/Unit/Models/OrderPayloadRouteTest.php +227 -0
  567. package/server/tests/Unit/Models/OrderPurchaseAndDriversTest.php +188 -0
  568. package/server/tests/Unit/Models/OrderTest.php +917 -0
  569. package/server/tests/Unit/Models/PartTest.php +399 -0
  570. package/server/tests/Unit/Models/PayloadTest.php +710 -0
  571. package/server/tests/Unit/Models/PayloadWaypointsAndEntitiesTest.php +317 -0
  572. package/server/tests/Unit/Models/PlaceCreationAndImportTest.php +384 -0
  573. package/server/tests/Unit/Models/PlaceGeocodingResultsTest.php +253 -0
  574. package/server/tests/Unit/Models/PlaceSharedAndGeocodingTest.php +271 -0
  575. package/server/tests/Unit/Models/PlaceTest.php +589 -0
  576. package/server/tests/Unit/Models/PositionTest.php +49 -0
  577. package/server/tests/Unit/Models/RouteTest.php +55 -0
  578. package/server/tests/Unit/Models/SensorReadingsTest.php +168 -0
  579. package/server/tests/Unit/Models/SensorTest.php +284 -0
  580. package/server/tests/Unit/Models/ServiceAreaGeometryTest.php +129 -0
  581. package/server/tests/Unit/Models/ServiceAreaTest.php +125 -0
  582. package/server/tests/Unit/Models/ServiceQuoteTest.php +617 -0
  583. package/server/tests/Unit/Models/ServiceRateFeeTest.php +73 -0
  584. package/server/tests/Unit/Models/ServiceRatePersistenceAndServicabilityTest.php +428 -0
  585. package/server/tests/Unit/Models/ServiceRateTest.php +533 -0
  586. package/server/tests/Unit/Models/SmallModelSurfacesTest.php +294 -0
  587. package/server/tests/Unit/Models/TrackingNumberTest.php +199 -0
  588. package/server/tests/Unit/Models/TrackingStatusTest.php +131 -0
  589. package/server/tests/Unit/Models/VehicleDeviceTest.php +115 -0
  590. package/server/tests/Unit/Models/VehiclePositionsAndImportTest.php +220 -0
  591. package/server/tests/Unit/Models/VehicleTest.php +266 -0
  592. package/server/tests/Unit/Models/VendorRelationsAndImportTest.php +107 -0
  593. package/server/tests/Unit/Models/WarrantyTest.php +187 -0
  594. package/server/tests/Unit/Models/WaypointTest.php +366 -0
  595. package/server/tests/Unit/Models/WorkOrderTest.php +272 -0
  596. package/server/tests/Unit/Models/ZoneTest.php +83 -0
  597. package/server/tests/Unit/Notifications/MonitoringNotificationsTest.php +136 -0
  598. package/server/tests/Unit/Notifications/OrderAssignedChannelsTest.php +53 -0
  599. package/server/tests/Unit/Notifications/OrderDispatchedChannelsTest.php +81 -0
  600. package/server/tests/Unit/Notifications/OrderFailedChannelsTest.php +73 -0
  601. package/server/tests/Unit/Notifications/OrderPingChannelsTest.php +69 -0
  602. package/server/tests/Unit/Notifications/TerminalOrderNotificationsTest.php +185 -0
  603. package/server/tests/Unit/Observers/FleetOpsCompanyUserAndServiceAreaObserversTest.php +133 -0
  604. package/server/tests/Unit/Observers/ObserverHelperExecutionTest.php +135 -0
  605. package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +267 -0
  606. package/server/tests/Unit/Observers/PurchaseRateObserverAndOrderJobsTest.php +169 -0
  607. package/server/tests/Unit/Orchestration/Engines/DriverAssignmentEngineTest.php +393 -0
  608. package/server/tests/Unit/Orchestration/Engines/VroomOrchestrationEngineLifecycleTest.php +397 -0
  609. package/server/tests/Unit/Orchestration/OrchestrationEngineRegistryTest.php +45 -0
  610. package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +253 -0
  611. package/server/tests/Unit/Providers/FleetOpsAiRegistrationTest.php +59 -0
  612. package/server/tests/Unit/Rules/ResolvableVehicleTest.php +85 -0
  613. package/server/tests/Unit/Support/Ai/AssetStatusCapabilityQueriesTest.php +120 -0
  614. package/server/tests/Unit/Support/Ai/Capabilities/CreateOrderPreviewCapabilityTest.php +385 -0
  615. package/server/tests/Unit/Support/Ai/Capabilities/OrderInsightsCapabilityTest.php +226 -0
  616. package/server/tests/Unit/Support/Ai/Capabilities/SearchResourcesCapabilityTest.php +237 -0
  617. package/server/tests/Unit/Support/AiCapabilityPermissionsTest.php +124 -0
  618. package/server/tests/Unit/Support/AlgoEdgeBranchesTest.php +24 -0
  619. package/server/tests/Unit/Support/Analytics/GeofenceViolationsTest.php +109 -0
  620. package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +205 -0
  621. package/server/tests/Unit/Support/Analytics/MaintenanceOverviewTest.php +236 -0
  622. package/server/tests/Unit/Support/Analytics/OnTimeDeliveryAndTopDriversTest.php +250 -0
  623. package/server/tests/Unit/Support/CreateOrderPreviewDraftingTest.php +235 -0
  624. package/server/tests/Unit/Support/CustomerAuthTest.php +132 -0
  625. package/server/tests/Unit/Support/FuelEfficiencyAndCanceledHandlersTest.php +164 -0
  626. package/server/tests/Unit/Support/FuelProviderRegistryResolutionTest.php +51 -0
  627. package/server/tests/Unit/Support/FuelProviderServiceMatchingTest.php +280 -0
  628. package/server/tests/Unit/Support/GeocodingInjectedClientTest.php +268 -0
  629. package/server/tests/Unit/Support/GeocodingLocateTest.php +32 -0
  630. package/server/tests/Unit/Support/GeofenceIntersectionServiceTest.php +188 -0
  631. package/server/tests/Unit/Support/GettingStartedTest.php +164 -0
  632. package/server/tests/Unit/Support/GuardReturnSeamsTest.php +172 -0
  633. package/server/tests/Unit/Support/IntegratedVendorsResolverTest.php +250 -0
  634. package/server/tests/Unit/Support/IssuesInsightsAndAllocationJobTest.php +156 -0
  635. package/server/tests/Unit/Support/MagicAccessorSeamsTest.php +54 -0
  636. package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +215 -0
  637. package/server/tests/Unit/Support/Metrics/AvgOrderValueMetricTest.php +308 -0
  638. package/server/tests/Unit/Support/Metrics/CountMetricsTest.php +280 -0
  639. package/server/tests/Unit/Support/Metrics/FuelCostsMetricTest.php +102 -0
  640. package/server/tests/Unit/Support/Metrics/MetricQuerySeamsTest.php +129 -0
  641. package/server/tests/Unit/Support/Metrics/OrdersScheduledMetricTest.php +146 -0
  642. package/server/tests/Unit/Support/MoreQuerySeamsTest.php +410 -0
  643. package/server/tests/Unit/Support/OperationalQueryDistributionTest.php +152 -0
  644. package/server/tests/Unit/Support/OperationsPulseTest.php +130 -0
  645. package/server/tests/Unit/Support/OptimizeOrderRouteCapabilityTest.php +147 -0
  646. package/server/tests/Unit/Support/OrchestrationPayloadBuilderTest.php +135 -0
  647. package/server/tests/Unit/Support/OrchestrationRouteAndVehicleTasksTest.php +254 -0
  648. package/server/tests/Unit/Support/OsrmProofAndIssueFilterTest.php +247 -0
  649. package/server/tests/Unit/Support/PetroAppFuelProviderTest.php +165 -0
  650. package/server/tests/Unit/Support/PlaceSearchRankingTest.php +191 -0
  651. package/server/tests/Unit/Support/QuerySeamsTest.php +309 -0
  652. package/server/tests/Unit/Support/ResolvesOrderServiceStopsDbTest.php +303 -0
  653. package/server/tests/Unit/Support/ResolvesOrderServiceStopsTest.php +382 -0
  654. package/server/tests/Unit/Support/SkipAndFallbackGuardsTest.php +70 -0
  655. package/server/tests/Unit/Support/TelematicCredentialResolutionTest.php +31 -0
  656. package/server/tests/Unit/Support/TelematicServiceTest.php +256 -0
  657. package/server/tests/Unit/Support/Telematics/Providers/AbstractProviderTest.php +154 -0
  658. package/server/tests/Unit/Support/Telematics/Providers/AfaqyProviderTest.php +191 -0
  659. package/server/tests/Unit/Support/Telematics/Providers/AfaqyProviderTransportTest.php +138 -0
  660. package/server/tests/Unit/Support/Telematics/Providers/GeotabProviderTest.php +329 -0
  661. package/server/tests/Unit/Support/Telematics/Providers/ProviderTransportSeamsTest.php +133 -0
  662. package/server/tests/Unit/Support/Telematics/Providers/SafeeProviderTest.php +487 -0
  663. package/server/tests/Unit/Support/Telematics/TelematicProviderRegistryTest.php +39 -0
  664. package/server/tests/Unit/Support/Telematics/TelematicServiceLifecycleTest.php +711 -0
  665. package/server/tests/Unit/Support/Telematics/TelematicServiceTelemetryTest.php +364 -0
  666. package/server/tests/Unit/Support/UtilsAdditionalTest.php +256 -0
  667. package/server/tests/Unit/Support/UtilsGeoMatrixAndVendorTest.php +382 -0
  668. package/server/tests/Unit/Tracking/TrackingContextTest.php +114 -0
  669. package/server/tests/Unit/Tracking/TrackingIntelligenceAndContextTest.php +477 -0
  670. package/server/tests/Unit/Tracking/TrackingProviderManagerFallbacksTest.php +208 -0
  671. package/server/tests/Unit/Traits/HasTrackingNumberTest.php +303 -0
  672. package/server/tests/Unit/Traits/PayloadAccessorsTest.php +150 -0
  673. package/server/tests/UtilsContractsTest.php +244 -0
  674. package/server/tests/VehicleControllerHelperContractsTest.php +488 -0
  675. package/server/tests/VehicleDataParsingTest.php +45 -0
  676. package/server/tests/VehicleResourceContractsTest.php +313 -0
  677. package/translations/bg-bg.yaml +2 -0
  678. package/translations/en-us.yaml +2 -0
  679. package/translations/es-pa.yaml +2 -0
  680. package/translations/fr-fr.yaml +2 -0
  681. package/translations/mn-mn.yaml +2 -0
  682. package/translations/pt-br.yaml +2 -0
  683. package/translations/ru-ru.yaml +2 -0
  684. package/server/tests/DeviceFilterTest.php +0 -44
  685. 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
+ });