@fleetbase/fleetops-engine 0.6.58 → 0.6.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (701) hide show
  1. package/README.md +3 -0
  2. package/RELEASE.md +36 -0
  3. package/addon/components/customer/form.hbs +0 -49
  4. package/addon/components/entity/card.hbs +1 -0
  5. package/addon/components/modals/order-label.hbs +1 -1
  6. package/addon/components/place/form.hbs +1 -1
  7. package/addon/components/vehicle/details/schedules.hbs +2 -2
  8. package/addon/components/vehicle/details/schedules.js +7 -1
  9. package/addon/controllers/maintenance/schedules/index/edit.js +10 -1
  10. package/addon/services/entity-actions.js +32 -0
  11. package/addon/services/vehicle-actions.js +4 -2
  12. package/addon/styles/fleetops-engine.css +0 -1
  13. package/composer.json +8 -3
  14. package/extension.json +1 -1
  15. package/index.js +7 -1
  16. package/package.json +2 -1
  17. package/scripts/coverage-file-runner.php +240 -0
  18. package/scripts/coverage-runner.php +2 -1
  19. package/scripts/coverage-summary.php +41 -2
  20. package/scripts/pest-bootstrap.php +237 -3
  21. package/scripts/pest-file-runner.php +13 -1
  22. package/scripts/pest-runner.php +57 -2
  23. package/server/config/fleetops.php +44 -0
  24. package/server/config/geocoder.php +1 -1
  25. package/server/migrations/2026_08_08_000001_make_sku_and_provider_transaction_unique_indexes_soft_delete_aware.php +152 -0
  26. package/server/resources/views/labels/entity-label.php +4 -4
  27. package/server/src/Casts/MultiPolygon.php +8 -3
  28. package/server/src/Casts/OrderConfigEntities.php +10 -3
  29. package/server/src/Casts/Point.php +10 -6
  30. package/server/src/Casts/Polygon.php +5 -2
  31. package/server/src/Console/Commands/AssignCustomerRoles.php +28 -6
  32. package/server/src/Console/Commands/AssignDriverRoles.php +36 -6
  33. package/server/src/Console/Commands/AuditCustomerUserConflicts.php +6 -1
  34. package/server/src/Console/Commands/DispatchAdhocOrders.php +12 -2
  35. package/server/src/Console/Commands/FixCustomerCompanies.php +46 -11
  36. package/server/src/Console/Commands/FixDriverCompanies.php +18 -3
  37. package/server/src/Console/Commands/FixLegacyOrderConfigs.php +31 -6
  38. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +94 -42
  39. package/server/src/Console/Commands/PurgeUnpurchasedServiceQuotes.php +42 -12
  40. package/server/src/Console/Commands/ReplayVehicleLocations.php +120 -55
  41. package/server/src/Console/Commands/SendDriverNotification.php +25 -4
  42. package/server/src/Console/Commands/SendMaintenanceReminders.php +43 -23
  43. package/server/src/Console/Commands/SimulateOrderRouteNavigation.php +36 -6
  44. package/server/src/Console/Commands/TrackOrderDistanceAndTime.php +6 -1
  45. package/server/src/Events/DriverLocationChanged.php +1 -1
  46. package/server/src/Events/DriverSimulatedLocationChanged.php +1 -1
  47. package/server/src/Events/EntityActivityChanged.php +1 -1
  48. package/server/src/Events/EntityCompleted.php +1 -1
  49. package/server/src/Events/EntityDriverAssigned.php +1 -1
  50. package/server/src/Events/FuelProviderTransactionImported.php +1 -1
  51. package/server/src/Events/FuelProviderTransactionMatched.php +1 -1
  52. package/server/src/Events/FuelProviderTransactionUnmatched.php +1 -1
  53. package/server/src/Events/FuelReportCreatedFromProvider.php +1 -1
  54. package/server/src/Events/GeofenceDwelled.php +1 -1
  55. package/server/src/Events/GeofenceEntered.php +1 -1
  56. package/server/src/Events/GeofenceExited.php +1 -1
  57. package/server/src/Events/VehicleLocationChanged.php +1 -1
  58. package/server/src/Events/WaypointActivityChanged.php +1 -1
  59. package/server/src/Events/WaypointCompleted.php +1 -1
  60. package/server/src/Exceptions/IntegratedVendorException.php +2 -2
  61. package/server/src/Exceptions/TelematicRateLimitExceededException.php +1 -1
  62. package/server/src/Http/Controllers/Api/v1/ContactController.php +89 -22
  63. package/server/src/Http/Controllers/Api/v1/CustomerController.php +346 -101
  64. package/server/src/Http/Controllers/Api/v1/DeviceController.php +48 -14
  65. package/server/src/Http/Controllers/Api/v1/DriverController.php +224 -64
  66. package/server/src/Http/Controllers/Api/v1/EntityController.php +112 -66
  67. package/server/src/Http/Controllers/Api/v1/EquipmentController.php +40 -10
  68. package/server/src/Http/Controllers/Api/v1/FleetController.php +54 -14
  69. package/server/src/Http/Controllers/Api/v1/FuelReportController.php +54 -14
  70. package/server/src/Http/Controllers/Api/v1/FuelTransactionController.php +36 -11
  71. package/server/src/Http/Controllers/Api/v1/GeofenceController.php +73 -23
  72. package/server/src/Http/Controllers/Api/v1/IssueController.php +54 -14
  73. package/server/src/Http/Controllers/Api/v1/LabelController.php +61 -22
  74. package/server/src/Http/Controllers/Api/v1/NavigatorController.php +32 -15
  75. package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +35 -5
  76. package/server/src/Http/Controllers/Api/v1/OrderController.php +388 -203
  77. package/server/src/Http/Controllers/Api/v1/PartController.php +40 -10
  78. package/server/src/Http/Controllers/Api/v1/PayloadController.php +90 -30
  79. package/server/src/Http/Controllers/Api/v1/PlaceController.php +188 -91
  80. package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +100 -36
  81. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +96 -26
  82. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +169 -81
  83. package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +104 -75
  84. package/server/src/Http/Controllers/Api/v1/TrackingNumberController.php +92 -14
  85. package/server/src/Http/Controllers/Api/v1/TrackingStatusController.php +66 -16
  86. package/server/src/Http/Controllers/Api/v1/VehicleController.php +121 -67
  87. package/server/src/Http/Controllers/Api/v1/VendorController.php +54 -14
  88. package/server/src/Http/Controllers/Api/v1/WorkOrderController.php +47 -12
  89. package/server/src/Http/Controllers/Api/v1/ZoneController.php +84 -24
  90. package/server/src/Http/Controllers/Concerns/ResolvesReviewAccountBypass.php +62 -0
  91. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +15 -5
  92. package/server/src/Http/Controllers/Internal/v1/ContactController.php +113 -28
  93. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +36 -11
  94. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +9 -4
  95. package/server/src/Http/Controllers/Internal/v1/DriverController.php +168 -70
  96. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +18 -3
  97. package/server/src/Http/Controllers/Internal/v1/FleetController.php +137 -60
  98. package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +24 -9
  99. package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +18 -3
  100. package/server/src/Http/Controllers/Internal/v1/GeocoderController.php +26 -8
  101. package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +12 -2
  102. package/server/src/Http/Controllers/Internal/v1/IntegratedVendorController.php +27 -6
  103. package/server/src/Http/Controllers/Internal/v1/IssueController.php +82 -29
  104. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +34 -26
  105. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +141 -97
  106. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +28 -8
  107. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +21 -12
  108. package/server/src/Http/Controllers/Internal/v1/MorphController.php +90 -18
  109. package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +88 -35
  110. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +149 -39
  111. package/server/src/Http/Controllers/Internal/v1/OrderConfigController.php +54 -18
  112. package/server/src/Http/Controllers/Internal/v1/OrderController.php +298 -86
  113. package/server/src/Http/Controllers/Internal/v1/PartController.php +18 -3
  114. package/server/src/Http/Controllers/Internal/v1/PaymentController.php +46 -14
  115. package/server/src/Http/Controllers/Internal/v1/PlaceController.php +31 -5
  116. package/server/src/Http/Controllers/Internal/v1/ProofController.php +88 -48
  117. package/server/src/Http/Controllers/Internal/v1/SearchController.php +1 -1
  118. package/server/src/Http/Controllers/Internal/v1/SensorController.php +6 -1
  119. package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +6 -1
  120. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +255 -105
  121. package/server/src/Http/Controllers/Internal/v1/SettingController.php +88 -35
  122. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -4
  123. package/server/src/Http/Controllers/Internal/v1/Traits/DriverSchedulingTrait.php +42 -14
  124. package/server/src/Http/Controllers/Internal/v1/VendorController.php +103 -33
  125. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +42 -12
  126. package/server/src/Http/Filter/DriverFilter.php +2 -2
  127. package/server/src/Http/Filter/MaintenanceScheduleFilter.php +40 -0
  128. package/server/src/Http/Filter/OrderFilter.php +1 -1
  129. package/server/src/Http/Filter/VehicleFilter.php +21 -7
  130. package/server/src/Http/Middleware/AuthenticateCustomerToken.php +12 -2
  131. package/server/src/Http/Requests/BulkDispatchRequest.php +3 -9
  132. package/server/src/Http/Requests/CancelOrderRequest.php +2 -6
  133. package/server/src/Http/Requests/CreateContactRequest.php +2 -6
  134. package/server/src/Http/Requests/CreateCustomerRequest.php +9 -1
  135. package/server/src/Http/Requests/CreateDeviceRequest.php +2 -2
  136. package/server/src/Http/Requests/CreateDriverRequest.php +3 -9
  137. package/server/src/Http/Requests/CreateEntityRequest.php +2 -6
  138. package/server/src/Http/Requests/CreateEquipmentRequest.php +2 -2
  139. package/server/src/Http/Requests/CreateFleetRequest.php +2 -6
  140. package/server/src/Http/Requests/CreateFuelReportRequest.php +16 -9
  141. package/server/src/Http/Requests/CreateFuelTransactionRequest.php +66 -3
  142. package/server/src/Http/Requests/CreateIssueRequest.php +2 -6
  143. package/server/src/Http/Requests/CreateOrderRequest.php +7 -8
  144. package/server/src/Http/Requests/CreatePartRequest.php +41 -3
  145. package/server/src/Http/Requests/CreatePayloadRequest.php +7 -9
  146. package/server/src/Http/Requests/CreatePlaceRequest.php +2 -6
  147. package/server/src/Http/Requests/CreatePurchaseRateRequest.php +2 -6
  148. package/server/src/Http/Requests/CreateSensorRequest.php +2 -2
  149. package/server/src/Http/Requests/CreateServiceAreaRequest.php +2 -6
  150. package/server/src/Http/Requests/CreateServiceQuoteRequest.php +2 -6
  151. package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -6
  152. package/server/src/Http/Requests/CreateTrackingNumberRequest.php +2 -6
  153. package/server/src/Http/Requests/CreateTrackingStatusRequest.php +2 -6
  154. package/server/src/Http/Requests/CreateVehicleRequest.php +2 -6
  155. package/server/src/Http/Requests/CreateVendorRequest.php +2 -6
  156. package/server/src/Http/Requests/CreateWorkOrderRequest.php +2 -2
  157. package/server/src/Http/Requests/CreateZoneRequest.php +2 -6
  158. package/server/src/Http/Requests/DecodeTrackingNumberQR.php +2 -6
  159. package/server/src/Http/Requests/DriverSimulationRequest.php +2 -6
  160. package/server/src/Http/Requests/Internal/AssignOrderRequest.php +3 -7
  161. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +4 -12
  162. package/server/src/Http/Requests/Internal/CreateOrderConfigRequest.php +2 -6
  163. package/server/src/Http/Requests/Internal/CreateOrderRequest.php +7 -8
  164. package/server/src/Http/Requests/Internal/FleetActionRequest.php +18 -12
  165. package/server/src/Http/Requests/Internal/UpdateDriverRequest.php +6 -3
  166. package/server/src/Http/Requests/QueryServiceQuotesRequest.php +2 -6
  167. package/server/src/Http/Requests/ScheduleOrderRequest.php +2 -6
  168. package/server/src/Http/Requests/UpdateIssueRequest.php +1 -3
  169. package/server/src/Http/Resources/v1/Customer.php +2 -2
  170. package/server/src/Http/Resources/v1/Maintenance.php +3 -4
  171. package/server/src/Http/Resources/v1/MaintenanceSchedule.php +3 -4
  172. package/server/src/Http/Resources/v1/Order.php +3 -7
  173. package/server/src/Http/Resources/v1/PurchaseRate.php +4 -6
  174. package/server/src/Http/Resources/v1/TrackingNumber.php +33 -0
  175. package/server/src/Http/Resources/v1/TrackingStatus.php +4 -6
  176. package/server/src/Http/Resources/v1/WorkOrder.php +3 -4
  177. package/server/src/Imports/ContactImport.php +10 -1
  178. package/server/src/Imports/DriverImport.php +10 -1
  179. package/server/src/Imports/EquipmentImport.php +6 -1
  180. package/server/src/Imports/FleetImport.php +10 -1
  181. package/server/src/Imports/FuelReportImport.php +10 -1
  182. package/server/src/Imports/IssueImport.php +10 -1
  183. package/server/src/Imports/MaintenanceImport.php +6 -1
  184. package/server/src/Imports/MaintenanceScheduleImport.php +6 -1
  185. package/server/src/Imports/PartImport.php +6 -1
  186. package/server/src/Imports/PlaceImport.php +10 -1
  187. package/server/src/Imports/VehicleImport.php +10 -1
  188. package/server/src/Imports/VendorImport.php +10 -1
  189. package/server/src/Imports/WorkOrderImport.php +6 -1
  190. package/server/src/Integrations/Lalamove/Lalamove.php +3 -7
  191. package/server/src/Integrations/Lalamove/LalamoveServiceType.php +5 -8
  192. package/server/src/Jobs/CheckGeofenceDwell.php +41 -16
  193. package/server/src/Jobs/FinalizeApiOrderCreation.php +17 -2
  194. package/server/src/Jobs/FinalizeInternalOrderCreation.php +11 -1
  195. package/server/src/Jobs/NotifyBulkAssignedDriver.php +29 -16
  196. package/server/src/Jobs/ProcessAllocationJob.php +59 -25
  197. package/server/src/Jobs/ReplayPositions.php +17 -10
  198. package/server/src/Jobs/SendPositionReplay.php +18 -7
  199. package/server/src/Jobs/SimulateDrivingRoute.php +14 -3
  200. package/server/src/Jobs/SyncFuelProviderTransactionsJob.php +12 -2
  201. package/server/src/Listeners/HandleDeliveryCompletion.php +11 -1
  202. package/server/src/Listeners/HandleGeofenceDwelled.php +6 -1
  203. package/server/src/Listeners/HandleGeofenceEntered.php +4 -2
  204. package/server/src/Listeners/HandleGeofenceExited.php +6 -1
  205. package/server/src/Listeners/HandleOrderCanceled.php +18 -3
  206. package/server/src/Listeners/HandleOrderDispatchFailed.php +12 -2
  207. package/server/src/Listeners/HandleOrderDispatched.php +61 -26
  208. package/server/src/Listeners/HandleOrderDriverAssigned.php +12 -2
  209. package/server/src/Listeners/HandleUserRemovedFromCompany.php +19 -6
  210. package/server/src/Listeners/NotifyDriverOnShiftChange.php +24 -4
  211. package/server/src/Listeners/NotifyOrderEvent.php +11 -6
  212. package/server/src/Models/Contact.php +6 -7
  213. package/server/src/Models/Device.php +26 -3
  214. package/server/src/Models/Driver.php +0 -6
  215. package/server/src/Models/Entity.php +4 -4
  216. package/server/src/Models/Equipment.php +1 -1
  217. package/server/src/Models/FuelReport.php +26 -1
  218. package/server/src/Models/IntegratedVendor.php +2 -2
  219. package/server/src/Models/Issue.php +5 -1
  220. package/server/src/Models/MaintenanceSchedule.php +25 -10
  221. package/server/src/Models/Order.php +18 -15
  222. package/server/src/Models/OrderConfig.php +1 -1
  223. package/server/src/Models/Payload.php +9 -16
  224. package/server/src/Models/Place.php +20 -13
  225. package/server/src/Models/Sensor.php +24 -0
  226. package/server/src/Models/ServiceArea.php +3 -6
  227. package/server/src/Models/ServiceQuote.php +5 -0
  228. package/server/src/Models/ServiceRate.php +13 -6
  229. package/server/src/Models/Telematic.php +2 -2
  230. package/server/src/Models/TrackingNumber.php +55 -10
  231. package/server/src/Models/TrackingStatus.php +24 -4
  232. package/server/src/Models/Waypoint.php +44 -10
  233. package/server/src/Models/Zone.php +3 -6
  234. package/server/src/Notifications/DriverArrivedAtGeofence.php +1 -1
  235. package/server/src/Notifications/OrderDispatched.php +3 -9
  236. package/server/src/Notifications/OrderPing.php +3 -9
  237. package/server/src/Observers/CategoryObserver.php +6 -1
  238. package/server/src/Observers/CompanyObserver.php +5 -0
  239. package/server/src/Observers/CompanyUserObserver.php +12 -2
  240. package/server/src/Observers/DriverObserver.php +19 -4
  241. package/server/src/Observers/FleetObserver.php +6 -1
  242. package/server/src/Observers/PurchaseRateObserver.php +66 -11
  243. package/server/src/Observers/ServiceAreaObserver.php +12 -2
  244. package/server/src/Observers/ServiceRateObserver.php +21 -6
  245. package/server/src/Observers/TrackingNumberObserver.php +19 -4
  246. package/server/src/Observers/UserObserver.php +6 -1
  247. package/server/src/Observers/VehicleObserver.php +27 -7
  248. package/server/src/Observers/WorkOrderObserver.php +27 -7
  249. package/server/src/Orchestration/Engines/DriverAssignmentEngine.php +8 -3
  250. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +2 -2
  251. package/server/src/Providers/FleetOpsServiceProvider.php +12 -0
  252. package/server/src/Providers/NotificationServiceProvider.php +6 -0
  253. package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +36 -26
  254. package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +18 -5
  255. package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +6 -1
  256. package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +26 -6
  257. package/server/src/Support/Analytics/FuelEfficiency.php +9 -3
  258. package/server/src/Support/Analytics/FuelProviderSummary.php +14 -5
  259. package/server/src/Support/Analytics/GeofenceViolations.php +40 -20
  260. package/server/src/Support/Analytics/IssuesInsights.php +38 -18
  261. package/server/src/Support/Analytics/MaintenanceOverview.php +51 -21
  262. package/server/src/Support/Analytics/OnTimeDelivery.php +3 -2
  263. package/server/src/Support/Analytics/OperationsPulse.php +59 -35
  264. package/server/src/Support/Analytics/OrdersByStatus.php +12 -7
  265. package/server/src/Support/Analytics/RevenueTrend.php +16 -6
  266. package/server/src/Support/Analytics/TopDrivers.php +3 -2
  267. package/server/src/Support/FleetOps.php +109 -104
  268. package/server/src/Support/FuelProviders/FuelProviderService.php +10 -1
  269. package/server/src/Support/Geocoding.php +52 -14
  270. package/server/src/Support/GeofenceIntersectionService.php +47 -27
  271. package/server/src/Support/LiveOrderQuery.php +6 -1
  272. package/server/src/Support/Metrics/ActiveRevenueQuery.php +3 -2
  273. package/server/src/Support/Metrics/FuelCostsMetric.php +6 -1
  274. package/server/src/Support/ResolvesOrderServiceStops.php +10 -0
  275. package/server/src/Support/Telematics/Providers/GeotabProvider.php +9 -4
  276. package/server/src/Support/Utils.php +122 -16
  277. package/server/src/Tracking/Providers/GoogleRoutesTrackingProvider.php +6 -1
  278. package/server/src/Tracking/Providers/OsrmTrackingProvider.php +11 -6
  279. package/server/src/Tracking/TrackingProviderManager.php +2 -3
  280. package/server/src/Tracking/TrackingProviderRegistry.php +8 -4
  281. package/server/src/Traits/PayloadAccessors.php +6 -1
  282. package/server/src/routes.php +24 -17
  283. package/server/tests/AiOperationalQueryCapabilityTest.php +457 -1
  284. package/server/tests/AiPreviewCapabilityContractsTest.php +180 -0
  285. package/server/tests/AnalyticsRoutesTest.php +380 -1
  286. package/server/tests/AnalyticsWidgetContractsTest.php +740 -0
  287. package/server/tests/ApiContactControllerContractsTest.php +284 -0
  288. package/server/tests/ApiCustomerControllerContractsTest.php +1130 -0
  289. package/server/tests/ApiDriverControllerContractsTest.php +603 -0
  290. package/server/tests/ApiEntityControllerContractsTest.php +329 -0
  291. package/server/tests/ApiEquipmentControllerContractsTest.php +239 -0
  292. package/server/tests/ApiFleetControllerContractsTest.php +176 -0
  293. package/server/tests/ApiFuelReportControllerContractsTest.php +217 -0
  294. package/server/tests/ApiIssueControllerContractsTest.php +217 -0
  295. package/server/tests/ApiOrderControllerContractsTest.php +1030 -0
  296. package/server/tests/ApiPartControllerContractsTest.php +251 -0
  297. package/server/tests/ApiPayloadControllerContractsTest.php +314 -0
  298. package/server/tests/ApiPlaceControllerContractsTest.php +541 -0
  299. package/server/tests/ApiPurchaseRateControllerContractsTest.php +283 -0
  300. package/server/tests/ApiServiceAreaZoneControllerContractsTest.php +442 -0
  301. package/server/tests/ApiServiceQuoteControllerContractsTest.php +322 -0
  302. package/server/tests/ApiTrackingNumberControllerContractsTest.php +225 -0
  303. package/server/tests/ApiTrackingStatusControllerContractsTest.php +254 -0
  304. package/server/tests/ApiVehicleControllerContractsTest.php +368 -0
  305. package/server/tests/ApiVendorControllerContractsTest.php +199 -0
  306. package/server/tests/AssetAndPlaceSearchContractsTest.php +220 -0
  307. package/server/tests/BackendSmallModelContractsTest.php +354 -0
  308. package/server/tests/CapacityAllocationEngineTest.php +56 -0
  309. package/server/tests/CommandContractsTest.php +2695 -0
  310. package/server/tests/CompactResourceSerializationTest.php +2838 -0
  311. package/server/tests/ControllerFilterContractsTest.php +1273 -0
  312. package/server/tests/ControllerHelperContractsTest.php +1740 -0
  313. package/server/tests/ControllerSerializationContractsTest.php +141 -0
  314. package/server/tests/CoverageSummaryScriptTest.php +93 -0
  315. package/server/tests/CustomerContactIdentitySafetyTest.php +1 -6
  316. package/server/tests/CustomerEndpointTest.php +169 -2
  317. package/server/tests/DriverFilterExecutionTest.php +263 -0
  318. package/server/tests/DriverGeofenceProcessingTest.php +153 -0
  319. package/server/tests/DriverResourceSerializationTest.php +245 -0
  320. package/server/tests/DriverSchedulingTraitContractsTest.php +259 -0
  321. package/server/tests/EventContractsTest.php +1259 -0
  322. package/server/tests/ExceptionContractsTest.php +81 -0
  323. package/server/tests/ExpansionContractsTest.php +242 -0
  324. package/server/tests/ExportCoverageTest.php +116 -0
  325. package/server/tests/Feature/Http/Api/ContactControllerPhotoAndDeleteTest.php +199 -0
  326. package/server/tests/Feature/Http/Api/CustomerControllerHelperSeamsTest.php +831 -0
  327. package/server/tests/Feature/Http/Api/DeviceControllerContractsTest.php +347 -0
  328. package/server/tests/Feature/Http/Api/DeviceControllerCrudTest.php +176 -0
  329. package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +530 -0
  330. package/server/tests/Feature/Http/Api/DriverControllerHelpersTest.php +240 -0
  331. package/server/tests/Feature/Http/Api/DriverControllerLookupTest.php +186 -0
  332. package/server/tests/Feature/Http/Api/DriverControllerOrganizationsTest.php +112 -0
  333. package/server/tests/Feature/Http/Api/DriverControllerSessionHelpersTest.php +207 -0
  334. package/server/tests/Feature/Http/Api/DriverControllerSwitchOrgAndGeofenceTest.php +329 -0
  335. package/server/tests/Feature/Http/Api/DriverControllerTrackTest.php +383 -0
  336. package/server/tests/Feature/Http/Api/EntityControllerHelpersTest.php +150 -0
  337. package/server/tests/Feature/Http/Api/FuelTransactionControllerContractsTest.php +352 -0
  338. package/server/tests/Feature/Http/Api/GeofenceControllerContractsTest.php +461 -0
  339. package/server/tests/Feature/Http/Api/LabelControllerSubjectsTest.php +148 -0
  340. package/server/tests/Feature/Http/Api/OrderConfigControllerTest.php +112 -0
  341. package/server/tests/Feature/Http/Api/OrderControllerCreateTest.php +279 -0
  342. package/server/tests/Feature/Http/Api/OrderControllerIntegratedVendorTest.php +295 -0
  343. package/server/tests/Feature/Http/Api/OrderControllerLifecycleActionsTest.php +279 -0
  344. package/server/tests/Feature/Http/Api/OrderControllerLookupHelpersTest.php +207 -0
  345. package/server/tests/Feature/Http/Api/OrderControllerNearbyFiltersTest.php +282 -0
  346. package/server/tests/Feature/Http/Api/OrderControllerPersistenceHelpersTest.php +216 -0
  347. package/server/tests/Feature/Http/Api/OrderControllerQueryFiltersTest.php +184 -0
  348. package/server/tests/Feature/Http/Api/OrderControllerStartActivityTest.php +663 -0
  349. package/server/tests/Feature/Http/Api/PayloadControllerHelpersTest.php +134 -0
  350. package/server/tests/Feature/Http/Api/PlaceAndServiceQuoteSeamsTest.php +479 -0
  351. package/server/tests/Feature/Http/Api/PurchaseRateControllerHelpersTest.php +176 -0
  352. package/server/tests/Feature/Http/Api/PurchaseRateControllerOrderCreationTest.php +268 -0
  353. package/server/tests/Feature/Http/Api/SensorControllerCrudTest.php +239 -0
  354. package/server/tests/Feature/Http/Api/ServiceAreaControllerBorderAndSeamsTest.php +187 -0
  355. package/server/tests/Feature/Http/Api/ServiceQuoteControllerQueryTest.php +405 -0
  356. package/server/tests/Feature/Http/Api/ServiceQuoteControllerVendorBridgeTest.php +212 -0
  357. package/server/tests/Feature/Http/Api/ServiceRateControllerContractsTest.php +263 -0
  358. package/server/tests/Feature/Http/Api/SmallApiControllerHelpersTest.php +272 -0
  359. package/server/tests/Feature/Http/Api/TrackingNumberControllerHelpersTest.php +160 -0
  360. package/server/tests/Feature/Http/Api/TrackingStatusControllerHelpersTest.php +165 -0
  361. package/server/tests/Feature/Http/Api/VehicleControllerTrackingTest.php +483 -0
  362. package/server/tests/Feature/Http/Api/WorkOrderControllerContractsTest.php +326 -0
  363. package/server/tests/Feature/Http/Api/WorkOrderControllerHelpersTest.php +130 -0
  364. package/server/tests/Feature/Http/Api/ZoneControllerBordersAndSeamsTest.php +294 -0
  365. package/server/tests/Feature/Http/FleetOpsRouteRegistrationTest.php +189 -0
  366. package/server/tests/Feature/Http/Internal/AnalyticsControllerSeamsTest.php +53 -0
  367. package/server/tests/Feature/Http/Internal/ContactControllerContractsTest.php +240 -0
  368. package/server/tests/Feature/Http/Internal/ContactControllerCustomerGuardsTest.php +128 -0
  369. package/server/tests/Feature/Http/Internal/ContactControllerEndpointsTest.php +294 -0
  370. package/server/tests/Feature/Http/Internal/CustomerControllerCredentialsTest.php +196 -0
  371. package/server/tests/Feature/Http/Internal/DeviceControllerContractsTest.php +370 -0
  372. package/server/tests/Feature/Http/Internal/DeviceControllerExportAndFiltersTest.php +151 -0
  373. package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +1167 -0
  374. package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +233 -0
  375. package/server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php +710 -0
  376. package/server/tests/Feature/Http/Internal/DriverControllerHelpersTest.php +218 -0
  377. package/server/tests/Feature/Http/Internal/FleetAssignmentsAndTriggersTest.php +198 -0
  378. package/server/tests/Feature/Http/Internal/FleetOpsLookupControllerDbTest.php +90 -0
  379. package/server/tests/Feature/Http/Internal/HubControllerTest.php +206 -0
  380. package/server/tests/Feature/Http/Internal/IssueControllerContractsTest.php +404 -0
  381. package/server/tests/Feature/Http/Internal/IssueControllerExportImportTest.php +118 -0
  382. package/server/tests/Feature/Http/Internal/LiveControllerEndpointsTest.php +246 -0
  383. package/server/tests/Feature/Http/Internal/LiveControllerTest.php +778 -0
  384. package/server/tests/Feature/Http/Internal/MaintenanceScheduleControllerContractsTest.php +263 -0
  385. package/server/tests/Feature/Http/Internal/MaintenanceScheduleControllerSurfacesTest.php +214 -0
  386. package/server/tests/Feature/Http/Internal/MetricsControllerContractsTest.php +249 -0
  387. package/server/tests/Feature/Http/Internal/MetricsControllerHelpersTest.php +66 -0
  388. package/server/tests/Feature/Http/Internal/MorphControllerHelpersTest.php +125 -0
  389. package/server/tests/Feature/Http/Internal/NavigatorControllerHelpersTest.php +201 -0
  390. package/server/tests/Feature/Http/Internal/OrchestrationControllerContractsTest.php +928 -0
  391. package/server/tests/Feature/Http/Internal/OrchestrationControllerHelpersTest.php +214 -0
  392. package/server/tests/Feature/Http/Internal/OrchestrationControllerImportOrdersTest.php +340 -0
  393. package/server/tests/Feature/Http/Internal/OrderConfigControllerContractsTest.php +314 -0
  394. package/server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php +663 -0
  395. package/server/tests/Feature/Http/Internal/OrderControllerCapturePhotoTest.php +507 -0
  396. package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +1742 -0
  397. package/server/tests/Feature/Http/Internal/OrderControllerCreateRecordTest.php +488 -0
  398. package/server/tests/Feature/Http/Internal/OrderControllerHelperSeamsTest.php +298 -0
  399. package/server/tests/Feature/Http/Internal/OrderControllerHelpersTest.php +267 -0
  400. package/server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php +179 -0
  401. package/server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php +80 -0
  402. package/server/tests/Feature/Http/Internal/PaymentControllerPaymentsTest.php +154 -0
  403. package/server/tests/Feature/Http/Internal/PlaceControllerTest.php +252 -0
  404. package/server/tests/Feature/Http/Internal/PositionControllerReplayMetricsTest.php +163 -0
  405. package/server/tests/Feature/Http/Internal/ProofControllerContractsTest.php +219 -0
  406. package/server/tests/Feature/Http/Internal/SearchControllerEndpointTest.php +151 -0
  407. package/server/tests/Feature/Http/Internal/ServiceQuoteControllerHelpersTest.php +208 -0
  408. package/server/tests/Feature/Http/Internal/ServiceQuoteControllerQueryRecordTest.php +217 -0
  409. package/server/tests/Feature/Http/Internal/ServiceQuoteControllerVendorBridgeTest.php +239 -0
  410. package/server/tests/Feature/Http/Internal/ServiceQuotePreliminaryQueryTest.php +271 -0
  411. package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +133 -0
  412. package/server/tests/Feature/Http/Internal/SmallInternalControllersTest.php +180 -0
  413. package/server/tests/Feature/Http/Internal/TelematicControllerEndpointsTest.php +417 -0
  414. package/server/tests/Feature/Http/Internal/VehicleControllerEndpointsTest.php +361 -0
  415. package/server/tests/Feature/Http/Internal/VendorControllerEndpointsTest.php +203 -0
  416. package/server/tests/Feature/Http/Internal/WorkOrderAndConfigSurfacesTest.php +289 -0
  417. package/server/tests/Feature/Http/Internal/WorkOrderControllerContractsTest.php +199 -0
  418. package/server/tests/Feature/Http/TelematicWebhookIngestTest.php +343 -0
  419. package/server/tests/Feature/Http/Telematics/TelematicWebhookControllerProcessingTest.php +254 -0
  420. package/server/tests/FleetOpsSupportContractsTest.php +187 -0
  421. package/server/tests/FlespiProviderContractsTest.php +244 -0
  422. package/server/tests/FlowResourceTest.php +295 -0
  423. package/server/tests/FuelProviderConnectionControllerContractsTest.php +303 -0
  424. package/server/tests/FuelProviderImplementationTest.php +753 -0
  425. package/server/tests/FuelProviderResourceContractsTest.php +171 -0
  426. package/server/tests/FuelProviderTransactionControllerContractsTest.php +198 -0
  427. package/server/tests/GeofenceIntersectionServiceContractsTest.php +164 -0
  428. package/server/tests/HOSConstraintContractsTest.php +128 -0
  429. package/server/tests/HubAndGettingStartedContractsTest.php +158 -0
  430. package/server/tests/ImportContractsTest.php +191 -0
  431. package/server/tests/InternalCustomerControllerContractsTest.php +238 -0
  432. package/server/tests/InternalFleetControllerContractsTest.php +389 -0
  433. package/server/tests/InternalImportExportControllerContractsTest.php +289 -0
  434. package/server/tests/InternalMetricsControllerContractsTest.php +69 -0
  435. package/server/tests/InternalPlaceWorkOrderControllerContractsTest.php +207 -0
  436. package/server/tests/InternalServiceQuoteCheckoutControllerContractsTest.php +470 -0
  437. package/server/tests/InternalVendorControllerContractsTest.php +331 -0
  438. package/server/tests/InventoryTelemetryModelContractsTest.php +516 -0
  439. package/server/tests/IssueTimelineContractsTest.php +191 -0
  440. package/server/tests/LabelPdfRenderingTest.php +57 -0
  441. package/server/tests/LalamoveIntegrationContractsTest.php +167 -0
  442. package/server/tests/LiveOrderQueryTest.php +145 -18
  443. package/server/tests/LookupAndGeocoderControllerContractsTest.php +357 -0
  444. package/server/tests/MaintainableTraitContractsTest.php +245 -0
  445. package/server/tests/MaintenanceScheduleControllerContractsTest.php +101 -0
  446. package/server/tests/MaintenanceWarrantyContractsTest.php +237 -0
  447. package/server/tests/ManifestControllerContractsTest.php +310 -0
  448. package/server/tests/MetricsRegistryTest.php +596 -10
  449. package/server/tests/MiddlewareContractsTest.php +150 -0
  450. package/server/tests/ModelAccessorContractsTest.php +3201 -0
  451. package/server/tests/MorphControllerContractsTest.php +410 -0
  452. package/server/tests/NavigatorControllerContractsTest.php +206 -0
  453. package/server/tests/NotificationAndMailContractsTest.php +677 -0
  454. package/server/tests/ObserverContractsTest.php +1414 -0
  455. package/server/tests/OperationalAlgorithmContractsTest.php +852 -0
  456. package/server/tests/OperationsMonitorEndpointTest.php +2 -1
  457. package/server/tests/OrchestrationPayloadBuilderTest.php +87 -0
  458. package/server/tests/OrderActivityFlowRegressionTest.php +354 -7
  459. package/server/tests/OrderDispatchListenerContractsTest.php +577 -0
  460. package/server/tests/OrderFilterExecutionTest.php +244 -0
  461. package/server/tests/OrderQueuedClosureRegressionTest.php +2 -1
  462. package/server/tests/PaymentControllerContractsTest.php +199 -0
  463. package/server/tests/PointResolutionTest.php +54 -0
  464. package/server/tests/ProcessAllocationJobContractsTest.php +224 -0
  465. package/server/tests/ProofAndSensorControllerContractsTest.php +44 -0
  466. package/server/tests/ProviderContractsTest.php +409 -0
  467. package/server/tests/PublicApiControllerInputContractsTest.php +294 -0
  468. package/server/tests/QueueJobContractsTest.php +764 -0
  469. package/server/tests/ReportSchemaContractsTest.php +271 -0
  470. package/server/tests/RequestContractsTest.php +1058 -0
  471. package/server/tests/RouteRegistrationExecutionTest.php +178 -0
  472. package/server/tests/RouteSequencingEngineTest.php +179 -0
  473. package/server/tests/RulesAndCastsTest.php +246 -0
  474. package/server/tests/SamsaraProviderContractsTest.php +272 -0
  475. package/server/tests/SearchControllerContractsTest.php +311 -0
  476. package/server/tests/ServiceRateRouteValidationTest.php +76 -0
  477. package/server/tests/SettingControllerContractsTest.php +410 -0
  478. package/server/tests/SmallControllerContractsTest.php +480 -0
  479. package/server/tests/SupportAnalyticsCustomerAuthContractsTest.php +221 -0
  480. package/server/tests/SupportJobAndAiCoverageTest.php +812 -0
  481. package/server/tests/SupportServiceContractsTest.php +287 -0
  482. package/server/tests/SupportValueObjectsTest.php +155 -0
  483. package/server/tests/TelematicControllerContractsTest.php +185 -0
  484. package/server/tests/TelematicProviderRegistryTest.php +167 -0
  485. package/server/tests/TelematicServiceHelperContractsTest.php +315 -0
  486. package/server/tests/TelematicWebhookControllerContractsTest.php +253 -0
  487. package/server/tests/TelematicsHardeningTest.php +642 -86
  488. package/server/tests/TrackingIntelligenceTest.php +644 -1
  489. package/server/tests/TrackingNumberTraitContractsTest.php +106 -0
  490. package/server/tests/Unit/Casts/SpatialCastBranchesTest.php +75 -0
  491. package/server/tests/Unit/Console/AssignCustomerRolesCommandTest.php +102 -0
  492. package/server/tests/Unit/Console/AssignDriverRolesCommandTest.php +113 -0
  493. package/server/tests/Unit/Console/Commands/FixLegacyOrderConfigsHelpersTest.php +108 -0
  494. package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +265 -0
  495. package/server/tests/Unit/Console/Commands/ReplayVehicleLocationsCommandTest.php +320 -0
  496. package/server/tests/Unit/Console/Commands/SimulateGeofenceEventsCommandTest.php +207 -0
  497. package/server/tests/Unit/Console/FixCommandsAndMaintenanceControllerTest.php +256 -0
  498. package/server/tests/Unit/Console/FleetOpsCommandsTest.php +198 -0
  499. package/server/tests/Unit/Console/ReplayVehicleLocationsCommandTest.php +252 -0
  500. package/server/tests/Unit/Console/SendMaintenanceRemindersTest.php +212 -0
  501. package/server/tests/Unit/Console/SimulateGeofenceEventsCommandTest.php +232 -0
  502. package/server/tests/Unit/Console/SyncTelematicsCommandTest.php +136 -0
  503. package/server/tests/Unit/Constraints/HOSConstraintTest.php +158 -0
  504. package/server/tests/Unit/Events/EventModelRecordTest.php +93 -0
  505. package/server/tests/Unit/Events/OrderDispatchFailedTest.php +34 -0
  506. package/server/tests/Unit/Exports/ExportCollectionsTest.php +142 -0
  507. package/server/tests/Unit/Exports/OperationalExportMappingTest.php +651 -0
  508. package/server/tests/Unit/Exports/VehicleExportTest.php +284 -0
  509. package/server/tests/Unit/Flow/EventFiringTest.php +95 -0
  510. package/server/tests/Unit/Http/Api/QueryWithRequestSeamsTest.php +198 -0
  511. package/server/tests/Unit/Http/Api/SensorInputTest.php +95 -0
  512. package/server/tests/Unit/Http/Filter/DeviceFilterTest.php +199 -0
  513. package/server/tests/Unit/Http/Filter/EmptyValueGuardsTest.php +66 -0
  514. package/server/tests/Unit/Http/Filter/FilterDateRangesTest.php +72 -0
  515. package/server/tests/Unit/Http/Filter/FuelProviderTransactionFilterTest.php +172 -0
  516. package/server/tests/Unit/Http/Filter/InternalRelationUuidLookupTest.php +135 -0
  517. package/server/tests/Unit/Http/Filter/IssueFilterTest.php +129 -0
  518. package/server/tests/Unit/Http/Filter/MaintenanceScheduleFilterTest.php +69 -0
  519. package/server/tests/Unit/Http/Filter/PartFilterTest.php +173 -0
  520. package/server/tests/Unit/Http/Internal/ControllerDelegationSeamsTest.php +136 -0
  521. package/server/tests/Unit/Http/Internal/ControllerExcelSeamsTest.php +89 -0
  522. package/server/tests/Unit/Http/Middleware/SetupDriverSessionTest.php +32 -0
  523. package/server/tests/Unit/Http/Requests/CreateFuelReportRequestTest.php +68 -0
  524. package/server/tests/Unit/Http/Requests/FleetActionRequestTest.php +80 -0
  525. package/server/tests/Unit/Http/Requests/RequestAuthorizationTest.php +50 -0
  526. package/server/tests/Unit/Http/Resources/CustomerResourceTest.php +108 -0
  527. package/server/tests/Unit/Http/Resources/EntityCustomerTypeTest.php +35 -0
  528. package/server/tests/Unit/Http/Resources/FleetResourceTest.php +102 -0
  529. package/server/tests/Unit/Http/Resources/IndexOrderResourceTest.php +210 -0
  530. package/server/tests/Unit/Http/Resources/IndexVehicleResourceTest.php +194 -0
  531. package/server/tests/Unit/Http/Resources/MaintenanceResourcesTest.php +182 -0
  532. package/server/tests/Unit/Http/Resources/OrderAndDriverResourceTest.php +121 -0
  533. package/server/tests/Unit/Http/Resources/OrderConfigResourceTest.php +47 -0
  534. package/server/tests/Unit/Http/Resources/PayloadResourceTest.php +111 -0
  535. package/server/tests/Unit/Http/Resources/RelationResourceFallbacksTest.php +32 -0
  536. package/server/tests/Unit/Http/Resources/ResourceRelationResolutionTest.php +55 -0
  537. package/server/tests/Unit/Http/Resources/TrackingNumberResourceTest.php +174 -0
  538. package/server/tests/Unit/Http/Resources/WaypointResourceTest.php +295 -0
  539. package/server/tests/Unit/Http/Resources/WorkOrderResourceTest.php +164 -0
  540. package/server/tests/Unit/Imports/ImportDelegationTest.php +151 -0
  541. package/server/tests/Unit/Integrations/Lalamove/LalamoveDeliveryStopTest.php +79 -0
  542. package/server/tests/Unit/Integrations/Lalamove/LalamoveLifecycleTest.php +211 -0
  543. package/server/tests/Unit/Integrations/Lalamove/LalamoveQuoteAndServiceOrderTest.php +381 -0
  544. package/server/tests/Unit/Integrations/Lalamove/LalamoveServiceTypeTest.php +51 -0
  545. package/server/tests/Unit/Integrations/LalamoveQuotationTest.php +194 -0
  546. package/server/tests/Unit/Integrations/LalamoveResponseHandlingTest.php +58 -0
  547. package/server/tests/Unit/Jobs/GeofenceDwellAndBulkNotifyTest.php +190 -0
  548. package/server/tests/Unit/Jobs/ReplayPositionsTest.php +57 -0
  549. package/server/tests/Unit/Jobs/SimulateWaypointReachedTest.php +92 -0
  550. package/server/tests/Unit/Listeners/HandleGeofenceEnteredTest.php +256 -0
  551. package/server/tests/Unit/Listeners/HandleOrderDispatchFailedListenerTest.php +101 -0
  552. package/server/tests/Unit/Listeners/HandleOrderDispatchedConcreteTest.php +203 -0
  553. package/server/tests/Unit/Listeners/HandleOrderDispatchedHelpersTest.php +186 -0
  554. package/server/tests/Unit/Listeners/HandleUserRemovedFromCompanyTest.php +65 -0
  555. package/server/tests/Unit/Listeners/ListenerDelegationSeamsTest.php +83 -0
  556. package/server/tests/Unit/Models/AssetTest.php +469 -0
  557. package/server/tests/Unit/Models/ContactCustomerUserTest.php +370 -0
  558. package/server/tests/Unit/Models/ContactTest.php +577 -0
  559. package/server/tests/Unit/Models/DeviceEventTest.php +481 -0
  560. package/server/tests/Unit/Models/DeviceLastPositionDefaultTest.php +128 -0
  561. package/server/tests/Unit/Models/DriverModelHelpersTest.php +268 -0
  562. package/server/tests/Unit/Models/DriverTest.php +401 -0
  563. package/server/tests/Unit/Models/EntityTest.php +171 -0
  564. package/server/tests/Unit/Models/EquipmentTest.php +434 -0
  565. package/server/tests/Unit/Models/FleetTest.php +153 -0
  566. package/server/tests/Unit/Models/FuelProviderTransactionTest.php +82 -0
  567. package/server/tests/Unit/Models/FuelReportImportAndLabelsTest.php +193 -0
  568. package/server/tests/Unit/Models/ImportRowFallbackSeamsTest.php +100 -0
  569. package/server/tests/Unit/Models/IntegratedVendorLifecycleTest.php +203 -0
  570. package/server/tests/Unit/Models/IntegratedVendorTest.php +135 -0
  571. package/server/tests/Unit/Models/IssueTest.php +160 -0
  572. package/server/tests/Unit/Models/MaintenanceScheduleTest.php +319 -0
  573. package/server/tests/Unit/Models/MaintenanceTest.php +277 -0
  574. package/server/tests/Unit/Models/MaintenanceWorkOrderImportTest.php +161 -0
  575. package/server/tests/Unit/Models/ManifestStopTest.php +121 -0
  576. package/server/tests/Unit/Models/ManifestTest.php +163 -0
  577. package/server/tests/Unit/Models/ModelLookupFallbackSeamsTest.php +149 -0
  578. package/server/tests/Unit/Models/OrderConfigTest.php +341 -0
  579. package/server/tests/Unit/Models/OrderDynamicContractsTest.php +190 -0
  580. package/server/tests/Unit/Models/OrderPayloadConfigAndDistanceTest.php +426 -0
  581. package/server/tests/Unit/Models/OrderPayloadRouteTest.php +227 -0
  582. package/server/tests/Unit/Models/OrderPurchaseAndDriversTest.php +188 -0
  583. package/server/tests/Unit/Models/OrderTest.php +917 -0
  584. package/server/tests/Unit/Models/PartTest.php +399 -0
  585. package/server/tests/Unit/Models/PayloadTest.php +710 -0
  586. package/server/tests/Unit/Models/PayloadWaypointsAndEntitiesTest.php +317 -0
  587. package/server/tests/Unit/Models/PlaceCreationAndImportTest.php +384 -0
  588. package/server/tests/Unit/Models/PlaceGeocodingResultsTest.php +253 -0
  589. package/server/tests/Unit/Models/PlaceSharedAndGeocodingTest.php +271 -0
  590. package/server/tests/Unit/Models/PlaceTest.php +589 -0
  591. package/server/tests/Unit/Models/PositionTest.php +49 -0
  592. package/server/tests/Unit/Models/RouteTest.php +55 -0
  593. package/server/tests/Unit/Models/SensorLastPositionDefaultTest.php +130 -0
  594. package/server/tests/Unit/Models/SensorReadingsTest.php +168 -0
  595. package/server/tests/Unit/Models/SensorTest.php +284 -0
  596. package/server/tests/Unit/Models/ServiceAreaGeometryTest.php +129 -0
  597. package/server/tests/Unit/Models/ServiceAreaTest.php +125 -0
  598. package/server/tests/Unit/Models/ServiceQuoteTest.php +617 -0
  599. package/server/tests/Unit/Models/ServiceRateFeeTest.php +73 -0
  600. package/server/tests/Unit/Models/ServiceRatePersistenceAndServicabilityTest.php +428 -0
  601. package/server/tests/Unit/Models/ServiceRateTest.php +533 -0
  602. package/server/tests/Unit/Models/SmallModelSurfacesTest.php +294 -0
  603. package/server/tests/Unit/Models/TrackingNumberTest.php +199 -0
  604. package/server/tests/Unit/Models/TrackingStatusTest.php +131 -0
  605. package/server/tests/Unit/Models/VehicleDeviceTest.php +115 -0
  606. package/server/tests/Unit/Models/VehiclePositionsAndImportTest.php +220 -0
  607. package/server/tests/Unit/Models/VehicleTest.php +266 -0
  608. package/server/tests/Unit/Models/VendorRelationsAndImportTest.php +107 -0
  609. package/server/tests/Unit/Models/WarrantyTest.php +187 -0
  610. package/server/tests/Unit/Models/WaypointTest.php +366 -0
  611. package/server/tests/Unit/Models/WorkOrderTest.php +272 -0
  612. package/server/tests/Unit/Models/ZoneTest.php +83 -0
  613. package/server/tests/Unit/Notifications/MonitoringNotificationsTest.php +136 -0
  614. package/server/tests/Unit/Notifications/OrderAssignedChannelsTest.php +53 -0
  615. package/server/tests/Unit/Notifications/OrderDispatchedChannelsTest.php +81 -0
  616. package/server/tests/Unit/Notifications/OrderFailedChannelsTest.php +73 -0
  617. package/server/tests/Unit/Notifications/OrderPingChannelsTest.php +69 -0
  618. package/server/tests/Unit/Notifications/TerminalOrderNotificationsTest.php +185 -0
  619. package/server/tests/Unit/Observers/FleetOpsCompanyUserAndServiceAreaObserversTest.php +133 -0
  620. package/server/tests/Unit/Observers/ObserverHelperExecutionTest.php +135 -0
  621. package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +267 -0
  622. package/server/tests/Unit/Observers/PurchaseRateObserverAndOrderJobsTest.php +169 -0
  623. package/server/tests/Unit/Orchestration/Engines/DriverAssignmentEngineTest.php +393 -0
  624. package/server/tests/Unit/Orchestration/Engines/VroomOrchestrationEngineLifecycleTest.php +397 -0
  625. package/server/tests/Unit/Orchestration/OrchestrationEngineRegistryTest.php +45 -0
  626. package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +253 -0
  627. package/server/tests/Unit/Providers/FleetOpsAiRegistrationTest.php +59 -0
  628. package/server/tests/Unit/Rules/ResolvableVehicleTest.php +85 -0
  629. package/server/tests/Unit/Support/Ai/AssetStatusCapabilityQueriesTest.php +120 -0
  630. package/server/tests/Unit/Support/Ai/Capabilities/CreateOrderPreviewCapabilityTest.php +385 -0
  631. package/server/tests/Unit/Support/Ai/Capabilities/OrderInsightsCapabilityTest.php +226 -0
  632. package/server/tests/Unit/Support/Ai/Capabilities/SearchResourcesCapabilityTest.php +237 -0
  633. package/server/tests/Unit/Support/AiCapabilityPermissionsTest.php +124 -0
  634. package/server/tests/Unit/Support/AlgoEdgeBranchesTest.php +24 -0
  635. package/server/tests/Unit/Support/Analytics/GeofenceViolationsTest.php +109 -0
  636. package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +205 -0
  637. package/server/tests/Unit/Support/Analytics/MaintenanceOverviewTest.php +236 -0
  638. package/server/tests/Unit/Support/Analytics/OnTimeDeliveryAndTopDriversTest.php +250 -0
  639. package/server/tests/Unit/Support/CreateOrderPreviewDraftingTest.php +235 -0
  640. package/server/tests/Unit/Support/CustomerAuthTest.php +132 -0
  641. package/server/tests/Unit/Support/FuelEfficiencyAndCanceledHandlersTest.php +164 -0
  642. package/server/tests/Unit/Support/FuelProviderRegistryResolutionTest.php +51 -0
  643. package/server/tests/Unit/Support/FuelProviderServiceMatchingTest.php +280 -0
  644. package/server/tests/Unit/Support/GeocodingInjectedClientTest.php +268 -0
  645. package/server/tests/Unit/Support/GeocodingLocateTest.php +32 -0
  646. package/server/tests/Unit/Support/GeofenceIntersectionServiceTest.php +188 -0
  647. package/server/tests/Unit/Support/GettingStartedTest.php +164 -0
  648. package/server/tests/Unit/Support/GuardReturnSeamsTest.php +172 -0
  649. package/server/tests/Unit/Support/IntegratedVendorsResolverTest.php +250 -0
  650. package/server/tests/Unit/Support/IssuesInsightsAndAllocationJobTest.php +156 -0
  651. package/server/tests/Unit/Support/MagicAccessorSeamsTest.php +54 -0
  652. package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +215 -0
  653. package/server/tests/Unit/Support/Metrics/AvgOrderValueMetricTest.php +308 -0
  654. package/server/tests/Unit/Support/Metrics/CountMetricsTest.php +280 -0
  655. package/server/tests/Unit/Support/Metrics/FuelCostsMetricTest.php +102 -0
  656. package/server/tests/Unit/Support/Metrics/MetricQuerySeamsTest.php +129 -0
  657. package/server/tests/Unit/Support/Metrics/OrdersScheduledMetricTest.php +146 -0
  658. package/server/tests/Unit/Support/MoreQuerySeamsTest.php +410 -0
  659. package/server/tests/Unit/Support/OperationalQueryDistributionTest.php +152 -0
  660. package/server/tests/Unit/Support/OperationsPulseTest.php +130 -0
  661. package/server/tests/Unit/Support/OptimizeOrderRouteCapabilityTest.php +147 -0
  662. package/server/tests/Unit/Support/OrchestrationPayloadBuilderTest.php +135 -0
  663. package/server/tests/Unit/Support/OrchestrationRouteAndVehicleTasksTest.php +254 -0
  664. package/server/tests/Unit/Support/OsrmProofAndIssueFilterTest.php +247 -0
  665. package/server/tests/Unit/Support/PetroAppFuelProviderTest.php +165 -0
  666. package/server/tests/Unit/Support/PlaceSearchRankingTest.php +191 -0
  667. package/server/tests/Unit/Support/QuerySeamsTest.php +309 -0
  668. package/server/tests/Unit/Support/ResolvesOrderServiceStopsDbTest.php +303 -0
  669. package/server/tests/Unit/Support/ResolvesOrderServiceStopsTest.php +382 -0
  670. package/server/tests/Unit/Support/SkipAndFallbackGuardsTest.php +70 -0
  671. package/server/tests/Unit/Support/TelematicCredentialResolutionTest.php +31 -0
  672. package/server/tests/Unit/Support/TelematicServiceTest.php +256 -0
  673. package/server/tests/Unit/Support/Telematics/Providers/AbstractProviderTest.php +154 -0
  674. package/server/tests/Unit/Support/Telematics/Providers/AfaqyProviderTest.php +191 -0
  675. package/server/tests/Unit/Support/Telematics/Providers/AfaqyProviderTransportTest.php +138 -0
  676. package/server/tests/Unit/Support/Telematics/Providers/GeotabProviderTest.php +329 -0
  677. package/server/tests/Unit/Support/Telematics/Providers/ProviderTransportSeamsTest.php +133 -0
  678. package/server/tests/Unit/Support/Telematics/Providers/SafeeProviderTest.php +487 -0
  679. package/server/tests/Unit/Support/Telematics/TelematicProviderRegistryTest.php +39 -0
  680. package/server/tests/Unit/Support/Telematics/TelematicServiceLifecycleTest.php +711 -0
  681. package/server/tests/Unit/Support/Telematics/TelematicServiceTelemetryTest.php +364 -0
  682. package/server/tests/Unit/Support/UtilsAdditionalTest.php +256 -0
  683. package/server/tests/Unit/Support/UtilsGeoMatrixAndVendorTest.php +382 -0
  684. package/server/tests/Unit/Tracking/TrackingContextTest.php +114 -0
  685. package/server/tests/Unit/Tracking/TrackingIntelligenceAndContextTest.php +477 -0
  686. package/server/tests/Unit/Tracking/TrackingProviderManagerFallbacksTest.php +208 -0
  687. package/server/tests/Unit/Traits/HasTrackingNumberTest.php +303 -0
  688. package/server/tests/Unit/Traits/PayloadAccessorsTest.php +150 -0
  689. package/server/tests/UtilsContractsTest.php +244 -0
  690. package/server/tests/VehicleControllerHelperContractsTest.php +488 -0
  691. package/server/tests/VehicleDataParsingTest.php +45 -0
  692. package/server/tests/VehicleResourceContractsTest.php +313 -0
  693. package/translations/bg-bg.yaml +2 -0
  694. package/translations/en-us.yaml +2 -0
  695. package/translations/es-pa.yaml +2 -0
  696. package/translations/fr-fr.yaml +2 -0
  697. package/translations/mn-mn.yaml +2 -0
  698. package/translations/pt-br.yaml +2 -0
  699. package/translations/ru-ru.yaml +2 -0
  700. package/server/tests/DeviceFilterTest.php +0 -44
  701. package/server/tests/LiveControllerViewportTest.php +0 -52
@@ -0,0 +1,1414 @@
1
+ <?php
2
+
3
+ use Carbon\Carbon;
4
+ use Fleetbase\FleetOps\Listeners\NotifyDriverOnShiftChange;
5
+ use Fleetbase\FleetOps\Models\Contact;
6
+ use Fleetbase\FleetOps\Models\Driver;
7
+ use Fleetbase\FleetOps\Models\Fleet;
8
+ use Fleetbase\FleetOps\Models\Maintenance;
9
+ use Fleetbase\FleetOps\Models\MaintenanceSchedule;
10
+ use Fleetbase\FleetOps\Models\Order;
11
+ use Fleetbase\FleetOps\Models\Payload;
12
+ use Fleetbase\FleetOps\Models\Place;
13
+ use Fleetbase\FleetOps\Models\PurchaseRate;
14
+ use Fleetbase\FleetOps\Models\ServiceArea;
15
+ use Fleetbase\FleetOps\Models\ServiceRate;
16
+ use Fleetbase\FleetOps\Models\TrackingNumber;
17
+ use Fleetbase\FleetOps\Models\TrackingStatus;
18
+ use Fleetbase\FleetOps\Models\Vehicle;
19
+ use Fleetbase\FleetOps\Models\WorkOrder;
20
+ use Fleetbase\FleetOps\Models\Zone;
21
+ use Fleetbase\FleetOps\Notifications\DriverShiftChanged;
22
+ use Fleetbase\FleetOps\Observers\CategoryObserver;
23
+ use Fleetbase\FleetOps\Observers\CompanyObserver;
24
+ use Fleetbase\FleetOps\Observers\CompanyUserObserver;
25
+ use Fleetbase\FleetOps\Observers\ContactObserver;
26
+ use Fleetbase\FleetOps\Observers\DriverObserver;
27
+ use Fleetbase\FleetOps\Observers\FleetObserver;
28
+ use Fleetbase\FleetOps\Observers\OrderObserver;
29
+ use Fleetbase\FleetOps\Observers\PayloadObserver;
30
+ use Fleetbase\FleetOps\Observers\PlaceObserver;
31
+ use Fleetbase\FleetOps\Observers\PurchaseRateObserver;
32
+ use Fleetbase\FleetOps\Observers\ServiceAreaObserver;
33
+ use Fleetbase\FleetOps\Observers\ServiceRateObserver;
34
+ use Fleetbase\FleetOps\Observers\TrackingNumberObserver;
35
+ use Fleetbase\FleetOps\Observers\UserObserver;
36
+ use Fleetbase\FleetOps\Observers\VehicleObserver;
37
+ use Fleetbase\FleetOps\Observers\WorkOrderObserver;
38
+ use Fleetbase\FleetOps\Observers\ZoneObserver;
39
+ use Fleetbase\Models\Category;
40
+ use Fleetbase\Models\Company;
41
+ use Fleetbase\Models\CompanyUser;
42
+ use Fleetbase\Models\Schedule;
43
+ use Fleetbase\Models\ScheduleItem;
44
+ use Fleetbase\Models\Transaction;
45
+ use Fleetbase\Models\TransactionItem;
46
+ use Fleetbase\Models\User;
47
+ use Illuminate\Cache\ArrayStore;
48
+ use Illuminate\Cache\Repository;
49
+ use Illuminate\Database\Eloquent\Collection as EloquentCollection;
50
+ use Illuminate\Support\Facades\Cache;
51
+
52
+ if (!class_exists('Illuminate\Foundation\Auth\User')) {
53
+ class_alias(Illuminate\Database\Eloquent\Model::class, 'Illuminate\Foundation\Auth\User');
54
+ }
55
+
56
+ class FleetOpsWorkOrderObserverProbe extends WorkOrderObserver
57
+ {
58
+ public bool $maintenanceExists = false;
59
+ public ?MaintenanceSchedule $schedule = null;
60
+ public array $createdMaintenance = [];
61
+ public array $completedEvents = [];
62
+
63
+ protected function hasMaintenanceRecord(WorkOrder $workOrder): bool
64
+ {
65
+ return $this->maintenanceExists;
66
+ }
67
+
68
+ protected function createMaintenance(array $attributes): Maintenance
69
+ {
70
+ $this->createdMaintenance[] = $attributes;
71
+
72
+ return new Maintenance();
73
+ }
74
+
75
+ protected function findSchedule(string $uuid): ?MaintenanceSchedule
76
+ {
77
+ return $this->schedule && $this->schedule->uuid === $uuid ? $this->schedule : null;
78
+ }
79
+
80
+ protected function dispatchCompletedEvent(WorkOrder $workOrder): void
81
+ {
82
+ $this->completedEvents[] = $workOrder;
83
+ }
84
+ }
85
+
86
+ class FleetOpsWorkOrderObserverWorkOrderFake extends WorkOrder
87
+ {
88
+ public bool $statusWasChanged = true;
89
+
90
+ public function wasChanged($attributes = null): bool
91
+ {
92
+ return $attributes === 'status' ? $this->statusWasChanged : false;
93
+ }
94
+ }
95
+
96
+ class FleetOpsWorkOrderObserverScheduleFake extends MaintenanceSchedule
97
+ {
98
+ public array $resets = [];
99
+
100
+ public function resetAfterCompletion(?int $completedOdometer = null, ?int $completedEngineHours = null, ?Carbon $completedAt = null): bool
101
+ {
102
+ $this->resets[] = [$completedOdometer, $completedEngineHours, $completedAt];
103
+
104
+ return true;
105
+ }
106
+ }
107
+
108
+ class FleetOpsOrderObserverOrderFake extends Order
109
+ {
110
+ public ?string $uuid = null;
111
+ public ?string $status = null;
112
+ public bool $started = false;
113
+ public ?Carbon $started_at = null;
114
+ public bool $statusDirty = false;
115
+ public bool $driverWasChanged = false;
116
+ public bool $integratedVendor = false;
117
+ public array $originalData = [];
118
+ public array $events = [];
119
+
120
+ public function isDirty($attributes = null): bool
121
+ {
122
+ return $attributes === 'status' ? $this->statusDirty : false;
123
+ }
124
+
125
+ public function getOriginal($key = null, $default = null): mixed
126
+ {
127
+ return $key === null ? $this->originalData : ($this->originalData[$key] ?? $default);
128
+ }
129
+
130
+ public function wasChanged($attributes = null): bool
131
+ {
132
+ return $attributes === 'driver_assigned_uuid' ? $this->driverWasChanged : false;
133
+ }
134
+
135
+ public function setDriverLocationAsPickup($force = false)
136
+ {
137
+ $this->events[] = 'setDriverLocationAsPickup';
138
+ }
139
+
140
+ public function notifyDriverAssigned(): void
141
+ {
142
+ $this->events[] = 'notifyDriverAssigned';
143
+ }
144
+
145
+ public function isIntegratedVendorOrder(): bool
146
+ {
147
+ return $this->integratedVendor;
148
+ }
149
+ }
150
+
151
+ class FleetOpsContactObserverContactFake extends Contact
152
+ {
153
+ public bool $hasUser = false;
154
+ public bool $customer = false;
155
+ public bool $typeDirty = false;
156
+ public bool $emailChanged = false;
157
+ public bool $phoneChanged = false;
158
+ public array $events = [];
159
+ public array $originalData = [];
160
+
161
+ public function doesntHaveUser(): bool
162
+ {
163
+ $this->events[] = 'doesntHaveUser';
164
+
165
+ return !$this->hasUser;
166
+ }
167
+
168
+ public function createUser(bool $sendInvite = false): User
169
+ {
170
+ $this->events[] = 'createUser';
171
+ $this->hasUser = true;
172
+
173
+ return new User();
174
+ }
175
+
176
+ public function assertCustomerIdentityIsAvailable(): void
177
+ {
178
+ $this->events[] = 'assertCustomerIdentityIsAvailable';
179
+ }
180
+
181
+ public function isCustomer(): bool
182
+ {
183
+ $this->events[] = 'isCustomer';
184
+
185
+ return $this->customer;
186
+ }
187
+
188
+ public function normalizeCustomerUser(?User $user = null, bool $quiet = false): ?User
189
+ {
190
+ $this->events[] = 'normalizeCustomerUser';
191
+
192
+ return $user ?? new User();
193
+ }
194
+
195
+ public function syncWithUser(): bool
196
+ {
197
+ $this->events[] = 'syncWithUser';
198
+
199
+ return true;
200
+ }
201
+
202
+ public function deleteUser(): ?bool
203
+ {
204
+ $this->events[] = 'deleteUser';
205
+
206
+ return true;
207
+ }
208
+
209
+ public function getOriginal($key = null, $default = null): mixed
210
+ {
211
+ return $key === null ? $this->originalData : ($this->originalData[$key] ?? $default);
212
+ }
213
+
214
+ public function isDirty($attributes = null): bool
215
+ {
216
+ return $attributes === 'type' ? $this->typeDirty : false;
217
+ }
218
+
219
+ public function wasChanged($attributes = null): bool
220
+ {
221
+ return match ($attributes) {
222
+ 'email' => $this->emailChanged,
223
+ 'phone' => $this->phoneChanged,
224
+ default => false,
225
+ };
226
+ }
227
+ }
228
+
229
+ class FleetOpsDriverObserverProbe extends DriverObserver
230
+ {
231
+ public array $invalidations = [];
232
+ public array $unassigned = [];
233
+ public ?User $user = null;
234
+
235
+ protected function invalidateLiveCache(): void
236
+ {
237
+ $this->invalidations[] = ['drivers', 'operations-monitor'];
238
+ }
239
+
240
+ protected function unassignOrders(Driver $driver): int
241
+ {
242
+ $this->unassigned[] = $driver->uuid;
243
+
244
+ return 1;
245
+ }
246
+
247
+ protected function findDriverUser(Driver $driver): ?User
248
+ {
249
+ return $this->user;
250
+ }
251
+ }
252
+
253
+ class FleetOpsDriverObserverUserFake extends User
254
+ {
255
+ public bool $driverRole = true;
256
+ public bool $deleted = false;
257
+
258
+ public function hasRole($roles, ?string $guard = null): bool
259
+ {
260
+ return $roles === 'Driver' && $this->driverRole;
261
+ }
262
+
263
+ public function delete()
264
+ {
265
+ $this->deleted = true;
266
+
267
+ return true;
268
+ }
269
+ }
270
+
271
+ class FleetOpsVehicleObserverProbe extends VehicleObserver
272
+ {
273
+ public ?string $identifier = null;
274
+ public ?Driver $driver = null;
275
+ public array $deleted = [];
276
+ public array $invalidated = [];
277
+
278
+ protected function getDriverIdentifier(): ?string
279
+ {
280
+ return $this->identifier;
281
+ }
282
+
283
+ protected function findDriver(string $identifier): ?Driver
284
+ {
285
+ return $this->driver && $identifier === $this->identifier ? $this->driver : null;
286
+ }
287
+
288
+ protected function deleteDriversAssignedTo(Vehicle $vehicle): mixed
289
+ {
290
+ $this->deleted[] = $vehicle->uuid;
291
+
292
+ return 1;
293
+ }
294
+
295
+ protected function invalidateLiveCache(): void
296
+ {
297
+ $this->invalidated[] = ['vehicles', 'operations-monitor'];
298
+ }
299
+ }
300
+
301
+ class FleetOpsVehicleObserverDriverFake extends Driver
302
+ {
303
+ public array $assignments = [];
304
+
305
+ public function assignVehicle(Vehicle $vehicle): self
306
+ {
307
+ $this->assignments[] = $vehicle;
308
+
309
+ return $this;
310
+ }
311
+ }
312
+
313
+ class FleetOpsServiceRateObserverProbe extends ServiceRateObserver
314
+ {
315
+ public mixed $rateFeesInput = null;
316
+ public mixed $parcelFeesInput = null;
317
+ public array $deletedModelBatches = [];
318
+
319
+ protected function rateFeesInput(): mixed
320
+ {
321
+ return $this->rateFeesInput;
322
+ }
323
+
324
+ protected function parcelFeesInput(): mixed
325
+ {
326
+ return $this->parcelFeesInput;
327
+ }
328
+
329
+ protected function deleteModels(mixed $models): void
330
+ {
331
+ $this->deletedModelBatches[] = $models;
332
+ }
333
+ }
334
+
335
+ class FleetOpsServiceRateObserverServiceRateFake extends ServiceRate
336
+ {
337
+ public bool $fixedMeter = false;
338
+ public bool $perDrop = false;
339
+ public bool $multiZone = false;
340
+ public bool $parcelService = false;
341
+ public array $rateFeeCalls = [];
342
+ public array $parcelFeeCalls = [];
343
+ public bool $relationsLoaded = false;
344
+
345
+ public function isFixedMeter(): bool
346
+ {
347
+ return $this->fixedMeter;
348
+ }
349
+
350
+ public function isPerDrop(): bool
351
+ {
352
+ return $this->perDrop;
353
+ }
354
+
355
+ public function isMultiZoneDistance(): bool
356
+ {
357
+ return $this->multiZone;
358
+ }
359
+
360
+ public function isParcelService(): bool
361
+ {
362
+ return $this->parcelService;
363
+ }
364
+
365
+ public function setServiceRateFees(?array $serviceRateFees = [])
366
+ {
367
+ $this->rateFeeCalls[] = $serviceRateFees;
368
+
369
+ return $this;
370
+ }
371
+
372
+ public function setServiceRateParcelFees(?array $serviceRateParcelFees = [])
373
+ {
374
+ $this->parcelFeeCalls[] = $serviceRateParcelFees;
375
+
376
+ return $this;
377
+ }
378
+
379
+ public function load($relations)
380
+ {
381
+ $this->relationsLoaded = $relations === ['parcelFees', 'rateFees'];
382
+ $this->setRelation('parcelFees', new EloquentCollection(['parcel-fee']));
383
+ $this->setRelation('rateFees', new EloquentCollection(['rate-fee']));
384
+
385
+ return $this;
386
+ }
387
+ }
388
+
389
+ class FleetOpsTrackingNumberObserverProbe extends TrackingNumberObserver
390
+ {
391
+ public array $barcodes = [];
392
+ public array $statuses = [];
393
+
394
+ protected function generateTrackingNumber(TrackingNumber $trackingNumber): string
395
+ {
396
+ return 'TN-' . $trackingNumber->region;
397
+ }
398
+
399
+ protected function generateBarcode(string $ownerUuid, string $type): string
400
+ {
401
+ $this->barcodes[] = [$ownerUuid, $type];
402
+
403
+ return $type . '-png';
404
+ }
405
+
406
+ protected function createTrackingStatus(array $attributes): TrackingStatus
407
+ {
408
+ $this->statuses[] = $attributes;
409
+
410
+ $status = new TrackingStatus();
411
+ $status->setRawAttributes(array_merge(['uuid' => 'status-uuid'], $attributes), true);
412
+
413
+ return $status;
414
+ }
415
+ }
416
+
417
+ class FleetOpsTrackingNumberObserverTrackingNumberFake extends TrackingNumber
418
+ {
419
+ public array $ownerStatuses = [];
420
+
421
+ public function updateOwnerStatus(?TrackingStatus $trackingStatus = null)
422
+ {
423
+ $this->ownerStatuses[] = $trackingStatus;
424
+
425
+ return $this;
426
+ }
427
+ }
428
+
429
+ class FleetOpsZoneObserverProbe extends ZoneObserver
430
+ {
431
+ public array $invalidations = [];
432
+
433
+ protected function invalidateServiceAreaCache(Zone $zone, ?string $serviceAreaUuid = null): void
434
+ {
435
+ $serviceAreaUuid ??= $zone->service_area_uuid;
436
+ if (!$serviceAreaUuid) {
437
+ return;
438
+ }
439
+
440
+ $this->invalidations[] = [$zone->company_uuid, $serviceAreaUuid];
441
+ }
442
+ }
443
+
444
+ class FleetOpsNotifyDriverOnShiftChangeProbe extends NotifyDriverOnShiftChange
445
+ {
446
+ public ?Schedule $schedule = null;
447
+ public array $settings = [];
448
+ public bool $createdEvent = false;
449
+ public array $sent = [];
450
+
451
+ protected function getSchedule(ScheduleItem $scheduleItem): ?Schedule
452
+ {
453
+ return $this->schedule;
454
+ }
455
+
456
+ protected function getSchedulingSettings(): array
457
+ {
458
+ return $this->settings;
459
+ }
460
+
461
+ protected function isCreatedEvent(object $event): bool
462
+ {
463
+ return $this->createdEvent;
464
+ }
465
+
466
+ protected function notifyDriver(Driver $driver, DriverShiftChanged $notification): void
467
+ {
468
+ $this->sent[] = [$driver, $notification];
469
+ }
470
+ }
471
+
472
+ class FleetOpsFleetObserverProbe extends FleetObserver
473
+ {
474
+ public array $cleared = [];
475
+
476
+ protected function clearParentFleet(string $fleetUuid): void
477
+ {
478
+ $this->cleared[] = $fleetUuid;
479
+ }
480
+ }
481
+
482
+ class FleetOpsServiceAreaObserverProbe extends ServiceAreaObserver
483
+ {
484
+ public array $countries = [];
485
+ public array $deleted = [];
486
+
487
+ protected function createPolygonFromCountry(string $country): mixed
488
+ {
489
+ $this->countries[] = $country;
490
+
491
+ return 'polygon-' . $country;
492
+ }
493
+
494
+ protected function deleteModels(mixed $models): void
495
+ {
496
+ $this->deleted[] = $models;
497
+ }
498
+ }
499
+
500
+ class FleetOpsServiceAreaObserverServiceAreaFake extends ServiceArea
501
+ {
502
+ public function getAttribute($key)
503
+ {
504
+ if ($key === 'border') {
505
+ return $this->attributes['border'] ?? null;
506
+ }
507
+
508
+ return parent::getAttribute($key);
509
+ }
510
+
511
+ public function setAttribute($key, $value)
512
+ {
513
+ if ($key === 'border') {
514
+ $this->attributes['border'] = $value;
515
+
516
+ return $this;
517
+ }
518
+
519
+ return parent::setAttribute($key, $value);
520
+ }
521
+
522
+ public function load($relations)
523
+ {
524
+ return $this;
525
+ }
526
+ }
527
+
528
+ class FleetOpsCompanyUserObserverProbe extends CompanyUserObserver
529
+ {
530
+ public array $deleted = [];
531
+ public ?Driver $driver = null;
532
+
533
+ protected function deleteDrivers(string $userUuid): void
534
+ {
535
+ $this->deleted[] = $userUuid;
536
+ }
537
+
538
+ protected function findDriver(string $userUuid): ?Driver
539
+ {
540
+ return $this->driver;
541
+ }
542
+ }
543
+
544
+ class FleetOpsCompanyUserFake extends CompanyUser
545
+ {
546
+ public bool $statusChanged = true;
547
+
548
+ public function wasChanged($attributes = null): bool
549
+ {
550
+ return $attributes === 'status' ? $this->statusChanged : false;
551
+ }
552
+ }
553
+
554
+ class FleetOpsObserverDriverFake extends Driver
555
+ {
556
+ public array $updates = [];
557
+
558
+ public function update(array $attributes = [], array $options = []): bool
559
+ {
560
+ $this->updates[] = $attributes;
561
+
562
+ return true;
563
+ }
564
+ }
565
+
566
+ class FleetOpsCategoryObserverProbe extends CategoryObserver
567
+ {
568
+ public array $deleted = [];
569
+
570
+ protected function deleteCustomFields(string $categoryUuid): void
571
+ {
572
+ $this->deleted[] = $categoryUuid;
573
+ }
574
+ }
575
+
576
+ class FleetOpsCompanyObserverProbe extends CompanyObserver
577
+ {
578
+ public array $configs = [];
579
+
580
+ protected function createTransportConfig(Company $company): void
581
+ {
582
+ $this->configs[] = $company->uuid;
583
+ }
584
+ }
585
+
586
+ class FleetOpsUserObserverProbe extends UserObserver
587
+ {
588
+ public array $deleted = [];
589
+
590
+ protected function deleteDrivers(string $userUuid): void
591
+ {
592
+ $this->deleted[] = $userUuid;
593
+ }
594
+ }
595
+
596
+ class FleetOpsPayloadObserverPayloadFake extends Payload
597
+ {
598
+ public int $updates = 0;
599
+
600
+ public function updateOrderDistanceAndTime(): ?Order
601
+ {
602
+ $this->updates++;
603
+
604
+ return null;
605
+ }
606
+ }
607
+
608
+ class FleetOpsPurchaseRateObserverProbe extends PurchaseRateObserver
609
+ {
610
+ public bool $loaded = false;
611
+ public ?Company $company = null;
612
+ public ?Order $order = null;
613
+ public array $currencyInputs = [];
614
+ public array $transactions = [];
615
+ public array $items = [];
616
+ public bool $hasQuote = true;
617
+ public ?string $quoteCurrency = null;
618
+ public int|float $quoteAmount = 0;
619
+ public Illuminate\Support\Collection $quoteItems;
620
+ public string $transactionId = 'generated-transaction-id';
621
+
622
+ public function __construct()
623
+ {
624
+ $this->quoteItems = collect();
625
+ }
626
+
627
+ protected function generateUuid(): string
628
+ {
629
+ return 'purchase-rate-uuid';
630
+ }
631
+
632
+ protected function loadRelations(PurchaseRate $purchaseRate): void
633
+ {
634
+ $this->loaded = true;
635
+ }
636
+
637
+ protected function findCompany(?string $uuid): ?Company
638
+ {
639
+ $this->currencyInputs[] = ['findCompany', $uuid];
640
+
641
+ return $this->company;
642
+ }
643
+
644
+ protected function getCompanyTransactionCurrency(mixed $company): string
645
+ {
646
+ $this->currencyInputs[] = ['currency', $company instanceof Company ? $company->uuid : $company];
647
+
648
+ return 'USD';
649
+ }
650
+
651
+ protected function getServiceQuoteCurrency(PurchaseRate $purchaseRate): ?string
652
+ {
653
+ return $this->quoteCurrency;
654
+ }
655
+
656
+ protected function getServiceQuoteAmount(PurchaseRate $purchaseRate): int|float
657
+ {
658
+ return $this->quoteAmount;
659
+ }
660
+
661
+ protected function getTransactionId(PurchaseRate $purchaseRate): string
662
+ {
663
+ return $this->transactionId;
664
+ }
665
+
666
+ protected function hasServiceQuote(PurchaseRate $purchaseRate): bool
667
+ {
668
+ return $this->hasQuote;
669
+ }
670
+
671
+ protected function getServiceQuoteItems(PurchaseRate $purchaseRate): Illuminate\Support\Collection
672
+ {
673
+ return $this->quoteItems;
674
+ }
675
+
676
+ protected function createTransaction(array $attributes): Transaction
677
+ {
678
+ $this->transactions[] = $attributes;
679
+
680
+ $transaction = new Transaction();
681
+ $transaction->setRawAttributes(['uuid' => 'transaction-uuid'], true);
682
+
683
+ return $transaction;
684
+ }
685
+
686
+ protected function createTransactionItem(array $attributes): TransactionItem
687
+ {
688
+ $this->items[] = $attributes;
689
+
690
+ return new TransactionItem();
691
+ }
692
+
693
+ protected function resolveOrder(PurchaseRate $purchaseRate): ?Order
694
+ {
695
+ return $this->order;
696
+ }
697
+ }
698
+
699
+ test('work order observer creates maintenance resets schedule and dispatches completed event on close', function () {
700
+ Carbon::setTestNow(Carbon::parse('2026-04-01 12:00:00'));
701
+
702
+ $schedule = new FleetOpsWorkOrderObserverScheduleFake();
703
+ $schedule->setRawAttributes(['uuid' => 'schedule-uuid']);
704
+
705
+ $workOrder = new FleetOpsWorkOrderObserverWorkOrderFake();
706
+ $workOrder->setRawAttributes([
707
+ 'uuid' => 'work-order-uuid',
708
+ 'company_uuid' => 'company-uuid',
709
+ 'status' => 'closed',
710
+ 'target_type' => 'fleet-ops:vehicle',
711
+ 'target_uuid' => 'vehicle-uuid',
712
+ 'priority' => 'high',
713
+ 'opened_at' => Carbon::parse('2026-03-01 09:00:00'),
714
+ 'closed_at' => Carbon::parse('2026-03-05 10:30:00'),
715
+ 'assignee_type' => 'fleet-ops:contact',
716
+ 'assignee_uuid' => 'assignee-uuid',
717
+ 'subject' => 'Replace brakes',
718
+ 'created_by_uuid' => 'creator-uuid',
719
+ 'schedule_uuid' => 'schedule-uuid',
720
+ 'meta' => [
721
+ 'completion_data' => [
722
+ 'notes' => 'Completed cleanly',
723
+ 'odometer' => '12000',
724
+ 'engine_hours' => '550',
725
+ 'labor_cost' => 1000,
726
+ 'parts_cost' => 2500,
727
+ 'tax' => 300,
728
+ 'total_cost' => 3800,
729
+ 'currency' => 'USD',
730
+ 'line_items' => [['label' => 'Brake pads']],
731
+ ],
732
+ ],
733
+ ]);
734
+
735
+ $observer = new FleetOpsWorkOrderObserverProbe();
736
+ $observer->schedule = $schedule;
737
+
738
+ $observer->updated($workOrder);
739
+
740
+ expect($observer->createdMaintenance)->toHaveCount(1)
741
+ ->and($observer->createdMaintenance[0])->toMatchArray([
742
+ 'company_uuid' => 'company-uuid',
743
+ 'work_order_uuid' => 'work-order-uuid',
744
+ 'maintainable_type' => 'fleet-ops:vehicle',
745
+ 'maintainable_uuid' => 'vehicle-uuid',
746
+ 'type' => 'scheduled',
747
+ 'status' => 'done',
748
+ 'priority' => 'high',
749
+ 'performed_by_type' => 'fleet-ops:contact',
750
+ 'performed_by_uuid' => 'assignee-uuid',
751
+ 'summary' => 'Replace brakes',
752
+ 'notes' => 'Completed cleanly',
753
+ 'odometer' => '12000',
754
+ 'engine_hours' => '550',
755
+ 'total_cost' => 3800,
756
+ 'created_by_uuid' => 'creator-uuid',
757
+ ])
758
+ ->and($observer->createdMaintenance[0]['completed_at']->toDateTimeString())->toBe('2026-03-05 10:30:00')
759
+ ->and($schedule->resets)->toHaveCount(1)
760
+ ->and($schedule->resets[0][0])->toBe(12000)
761
+ ->and($schedule->resets[0][1])->toBe(550)
762
+ ->and($schedule->resets[0][2]->toDateTimeString())->toBe('2026-03-05 10:30:00')
763
+ ->and($observer->completedEvents)->toBe([$workOrder]);
764
+
765
+ Carbon::setTestNow();
766
+ });
767
+
768
+ test('purchase rate observer creates transaction items and defaults generated attributes', function () {
769
+ session(['company' => 'session-company-uuid']);
770
+
771
+ $order = new Order();
772
+ $order->setRawAttributes(['uuid' => 'order-uuid'], true);
773
+
774
+ $purchaseRate = new PurchaseRate();
775
+ $purchaseRate->setRawAttributes([
776
+ 'company_uuid' => 'purchase-company-uuid',
777
+ 'customer_uuid' => 'customer-uuid',
778
+ 'customer_type' => 'fleet-ops:contact',
779
+ 'payload_uuid' => 'payload-uuid',
780
+ 'meta' => ['transaction_id' => 'gateway-id'],
781
+ 'status' => null,
782
+ ], true);
783
+
784
+ $company = new Company();
785
+ $company->setRawAttributes(['uuid' => 'session-company-uuid'], true);
786
+
787
+ $observer = new FleetOpsPurchaseRateObserverProbe();
788
+ $observer->order = $order;
789
+ $observer->company = $company;
790
+ $observer->quoteAmount = 4550;
791
+ $observer->transactionId = 'gateway-id';
792
+ $observer->quoteItems = collect([
793
+ (object) ['amount' => 1200, 'details' => 'Base rate', 'code' => 'base'],
794
+ (object) ['amount' => null],
795
+ ]);
796
+
797
+ $observer->creating($purchaseRate);
798
+
799
+ expect($observer->loaded)->toBeTrue()
800
+ ->and($purchaseRate->uuid)->toBe('purchase-rate-uuid')
801
+ ->and($purchaseRate->transaction_uuid)->toBe('transaction-uuid')
802
+ ->and($purchaseRate->status)->toBe(Transaction::STATUS_SUCCESS)
803
+ ->and($observer->currencyInputs)->toBe([
804
+ ['findCompany', 'session-company-uuid'],
805
+ ['currency', 'session-company-uuid'],
806
+ ])
807
+ ->and($observer->transactions[0])->toMatchArray([
808
+ 'company_uuid' => 'session-company-uuid',
809
+ 'customer_uuid' => 'customer-uuid',
810
+ 'customer_type' => 'fleet-ops:contact',
811
+ 'subject_uuid' => 'order-uuid',
812
+ 'subject_type' => Order::class,
813
+ 'context_uuid' => 'purchase-rate-uuid',
814
+ 'context_type' => PurchaseRate::class,
815
+ 'gateway_transaction_id' => 'gateway-id',
816
+ 'gateway' => 'internal',
817
+ 'amount' => 4550,
818
+ 'currency' => 'USD',
819
+ 'type' => 'dispatch',
820
+ 'direction' => Transaction::DIRECTION_CREDIT,
821
+ 'status' => Transaction::STATUS_SUCCESS,
822
+ 'settlement_status' => Transaction::SETTLEMENT_STATUS_UNPAID,
823
+ ])
824
+ ->and($observer->items)->toBe([
825
+ [
826
+ 'transaction_uuid' => 'transaction-uuid',
827
+ 'amount' => 1200,
828
+ 'currency' => 'USD',
829
+ 'details' => 'Base rate',
830
+ 'code' => 'base',
831
+ ],
832
+ [
833
+ 'transaction_uuid' => 'transaction-uuid',
834
+ 'amount' => 0,
835
+ 'currency' => 'USD',
836
+ 'details' => 'Internal dispatch',
837
+ 'code' => 'internal',
838
+ ],
839
+ ]);
840
+ });
841
+
842
+ test('purchase rate observer uses service quote currency and handles missing order or quote items', function () {
843
+ session(['company' => null]);
844
+
845
+ $purchaseRate = new PurchaseRate();
846
+ $purchaseRate->setRawAttributes([
847
+ 'uuid' => 'existing-purchase-rate',
848
+ 'company_uuid' => 'purchase-company-uuid',
849
+ 'payload_uuid' => null,
850
+ 'status' => 'pending',
851
+ ], true);
852
+
853
+ $observer = new FleetOpsPurchaseRateObserverProbe();
854
+ $observer->quoteAmount = 250;
855
+ $observer->quoteCurrency = 'SGD';
856
+ $observer->quoteItems = collect();
857
+
858
+ $observer->creating($purchaseRate);
859
+
860
+ expect($purchaseRate->uuid)->toBe('existing-purchase-rate')
861
+ ->and($purchaseRate->status)->toBe('pending')
862
+ ->and($observer->currencyInputs)->toBe([['findCompany', 'purchase-company-uuid']])
863
+ ->and($observer->transactions[0])->toMatchArray([
864
+ 'company_uuid' => 'purchase-company-uuid',
865
+ 'subject_uuid' => null,
866
+ 'subject_type' => null,
867
+ 'context_uuid' => 'existing-purchase-rate',
868
+ 'amount' => 250,
869
+ 'currency' => 'SGD',
870
+ ])
871
+ ->and($observer->items)->toBe([]);
872
+ });
873
+
874
+ test('place observer uppercases address fields and invalidates live places cache', function () {
875
+ Cache::swap(new Repository(new ArrayStore()));
876
+ session(['company' => 'company-uuid']);
877
+
878
+ $place = new Place();
879
+ $place->name = 'central depot';
880
+ $place->street1 = 'main road';
881
+ $place->city = 'singapore';
882
+ $place->country = 'sg';
883
+ $place->postal_code = 12345;
884
+ $place->neighborhood = null;
885
+
886
+ $observer = new PlaceObserver();
887
+ $observer->creating($place);
888
+ $observer->created($place);
889
+ $observer->updated($place);
890
+ $observer->deleted($place);
891
+
892
+ expect($place->name)->toBe('CENTRAL DEPOT')
893
+ ->and($place->street1)->toBe('MAIN ROAD')
894
+ ->and($place->city)->toBe('SINGAPORE')
895
+ ->and($place->country)->toBe('SG')
896
+ ->and($place->postal_code)->toBe(12345)
897
+ ->and(Cache::get('live:company-uuid:places:version'))->toBe(3);
898
+ });
899
+
900
+ test('fleet service area company category user and payload observers delegate lifecycle side effects', function () {
901
+ Cache::swap(new Repository(new ArrayStore()));
902
+ session(['company' => 'company-uuid']);
903
+
904
+ $fleet = new Fleet();
905
+ $fleet->setRawAttributes(['uuid' => 'fleet-uuid'], true);
906
+
907
+ $fleetObserver = new FleetOpsFleetObserverProbe();
908
+ $fleetObserver->created($fleet);
909
+ $fleetObserver->updated($fleet);
910
+ $fleetObserver->deleted($fleet);
911
+
912
+ expect($fleetObserver->cleared)->toBe(['fleet-uuid'])
913
+ ->and(Cache::get('live:company-uuid:operations-monitor:version'))->toBe(3);
914
+
915
+ $serviceArea = new FleetOpsServiceAreaObserverServiceAreaFake();
916
+ $serviceArea->country = 'SG';
917
+ $serviceArea->setRelation('zones', collect(['zone-a', 'zone-b']));
918
+
919
+ $serviceAreaObserver = new FleetOpsServiceAreaObserverProbe();
920
+ $serviceAreaObserver->creating($serviceArea);
921
+ $serviceAreaObserver->deleted($serviceArea);
922
+
923
+ expect($serviceArea->border)->toBe('polygon-SG')
924
+ ->and($serviceAreaObserver->countries)->toBe(['SG'])
925
+ ->and($serviceAreaObserver->deleted)->toHaveCount(1);
926
+
927
+ $withBorder = new FleetOpsServiceAreaObserverServiceAreaFake();
928
+ $withBorder->border = 'existing-border';
929
+ $withBorder->country = 'MY';
930
+ $serviceAreaObserver->creating($withBorder);
931
+
932
+ expect($withBorder->border)->toBe('existing-border')
933
+ ->and($serviceAreaObserver->countries)->toBe(['SG']);
934
+
935
+ $companyUser = new FleetOpsCompanyUserFake();
936
+ $companyUser->setRawAttributes([
937
+ 'user_uuid' => 'user-uuid',
938
+ 'status' => 'inactive',
939
+ ], true);
940
+
941
+ $driver = new FleetOpsObserverDriverFake();
942
+ $companyUserObserver = new FleetOpsCompanyUserObserverProbe();
943
+ $companyUserObserver->driver = $driver;
944
+ $companyUserObserver->deleted($companyUser);
945
+ $companyUserObserver->updated($companyUser);
946
+
947
+ expect($companyUserObserver->deleted)->toBe(['user-uuid'])
948
+ ->and($driver->updates)->toBe([['status' => 'inactive']]);
949
+
950
+ $companyUser->statusChanged = false;
951
+ $companyUserObserver->updated($companyUser);
952
+
953
+ expect($driver->updates)->toHaveCount(1);
954
+
955
+ $category = new Category();
956
+ $category->setRawAttributes(['uuid' => 'category-uuid'], true);
957
+ $categoryObserver = new FleetOpsCategoryObserverProbe();
958
+ $categoryObserver->deleted($category);
959
+
960
+ $company = new Company();
961
+ $company->setRawAttributes(['uuid' => 'company-uuid'], true);
962
+ $companyObserver = new FleetOpsCompanyObserverProbe();
963
+ $companyObserver->created($company);
964
+
965
+ $user = new User();
966
+ $user->setRawAttributes(['uuid' => 'user-uuid'], true);
967
+ $userObserver = new FleetOpsUserObserverProbe();
968
+ $userObserver->deleted($user);
969
+
970
+ $payload = new FleetOpsPayloadObserverPayloadFake();
971
+ (new PayloadObserver())->created($payload);
972
+
973
+ expect($categoryObserver->deleted)->toBe(['category-uuid'])
974
+ ->and($companyObserver->configs)->toBe(['company-uuid'])
975
+ ->and($userObserver->deleted)->toBe(['user-uuid'])
976
+ ->and($payload->updates)->toBe(1);
977
+ });
978
+
979
+ test('work order observer skips non closing duplicate and missing schedule branches', function () {
980
+ $observer = new FleetOpsWorkOrderObserverProbe();
981
+
982
+ $unchanged = new FleetOpsWorkOrderObserverWorkOrderFake();
983
+ $unchanged->setRawAttributes(['status' => 'closed']);
984
+ $unchanged->statusWasChanged = false;
985
+ $observer->updated($unchanged);
986
+
987
+ $open = new FleetOpsWorkOrderObserverWorkOrderFake();
988
+ $open->setRawAttributes(['status' => 'open']);
989
+ $observer->updated($open);
990
+
991
+ expect($observer->createdMaintenance)->toBe([])
992
+ ->and($observer->completedEvents)->toBe([]);
993
+
994
+ $duplicate = new FleetOpsWorkOrderObserverWorkOrderFake();
995
+ $duplicate->setRawAttributes([
996
+ 'uuid' => 'duplicate-work-order',
997
+ 'status' => 'closed',
998
+ 'schedule_uuid' => 'missing-schedule',
999
+ ]);
1000
+ $observer->maintenanceExists = true;
1001
+ $observer->updated($duplicate);
1002
+
1003
+ expect($observer->createdMaintenance)->toBe([])
1004
+ ->and($observer->completedEvents)->toBe([$duplicate]);
1005
+ });
1006
+
1007
+ test('order observer starts dispatched orders and invalidates live cache entries', function () {
1008
+ Cache::swap(new Repository(new ArrayStore()));
1009
+ session(['company' => 'company-uuid']);
1010
+ Carbon::setTestNow(Carbon::parse('2026-04-02 08:15:00'));
1011
+
1012
+ $order = new FleetOpsOrderObserverOrderFake();
1013
+ $order->uuid = 'order-uuid';
1014
+ $order->status = 'started';
1015
+ $order->started = false;
1016
+ $order->started_at = null;
1017
+ $order->originalData = ['status' => 'dispatched'];
1018
+ $order->statusDirty = true;
1019
+
1020
+ $observer = new OrderObserver();
1021
+
1022
+ $observer->updating($order);
1023
+ $observer->created($order);
1024
+
1025
+ expect($order->started)->toBeTrue()
1026
+ ->and($order->started_at->toDateTimeString())->toBe('2026-04-02 08:15:00')
1027
+ ->and(Cache::get('live:company-uuid:orders:version'))->toBe(1)
1028
+ ->and(Cache::get('live:company-uuid:routes:version'))->toBe(1)
1029
+ ->and(Cache::get('live:company-uuid:coordinates:version'))->toBe(1);
1030
+
1031
+ Carbon::setTestNow();
1032
+ });
1033
+
1034
+ test('order observer preserves explicit start fields and notifies assigned driver on update', function () {
1035
+ Cache::swap(new Repository(new ArrayStore()));
1036
+ session(['company' => 'company-uuid']);
1037
+
1038
+ $explicitStart = Carbon::parse('2026-04-01 10:00:00');
1039
+ $order = new FleetOpsOrderObserverOrderFake();
1040
+ $order->uuid = 'order-update-uuid';
1041
+ $order->status = 'started';
1042
+ $order->started = true;
1043
+ $order->started_at = $explicitStart;
1044
+ $order->originalData = ['status' => 'dispatched'];
1045
+ $order->statusDirty = true;
1046
+ $order->driverWasChanged = true;
1047
+
1048
+ $observer = new OrderObserver();
1049
+
1050
+ $observer->updating($order);
1051
+ $observer->updated($order);
1052
+
1053
+ expect($order->started)->toBeTrue()
1054
+ ->and($order->started_at)->toBe($explicitStart)
1055
+ ->and($order->events)->toBe([
1056
+ 'setDriverLocationAsPickup',
1057
+ 'notifyDriverAssigned',
1058
+ ])
1059
+ ->and(Cache::get('live:company-uuid:orders:version'))->toBe(1);
1060
+ });
1061
+
1062
+ test('order observer ignores non dispatched start transitions', function () {
1063
+ $order = new FleetOpsOrderObserverOrderFake();
1064
+ $order->status = 'started';
1065
+ $order->started = false;
1066
+ $order->started_at = null;
1067
+ $order->originalData = ['status' => 'created'];
1068
+ $order->statusDirty = true;
1069
+
1070
+ (new OrderObserver())->updating($order);
1071
+
1072
+ expect($order->started)->toBeFalse()
1073
+ ->and($order->started_at)->toBeNull();
1074
+ });
1075
+
1076
+ test('order observer invalidates cache on delete and forwards to integrated vendors', function () {
1077
+ Cache::swap(new Repository(new ArrayStore()));
1078
+ session(['company' => 'company-uuid']);
1079
+
1080
+ $order = new FleetOpsOrderObserverOrderFake();
1081
+ $order->uuid = 'order-deleted-uuid';
1082
+
1083
+ (new OrderObserver())->deleted($order);
1084
+
1085
+ expect(Cache::get('live:company-uuid:orders:version'))->toBe(1)
1086
+ ->and(Cache::get('live:company-uuid:routes:version'))->toBe(1)
1087
+ ->and(Cache::get('live:company-uuid:coordinates:version'))->toBe(1);
1088
+
1089
+ // Integrated vendor orders additionally notify the upstream provider. The
1090
+ // provider is unreachable in the harness, so the assertion is that the
1091
+ // forwarding is attempted rather than skipped.
1092
+ $integrated = new FleetOpsOrderObserverOrderFake();
1093
+ $integrated->uuid = 'order-deleted-integrated';
1094
+ $integrated->integratedVendor = true;
1095
+
1096
+ expect(fn () => (new OrderObserver())->deleted($integrated))->toThrow(Error::class);
1097
+ });
1098
+
1099
+ test('contact observer creates syncs normalizes and deletes associated users', function () {
1100
+ $observer = new ContactObserver();
1101
+ $contact = new FleetOpsContactObserverContactFake();
1102
+ $contact->setRawAttributes(['type' => 'customer']);
1103
+ $contact->customer = true;
1104
+
1105
+ $observer->creating($contact);
1106
+ $observer->saving($contact);
1107
+ $observer->deleted($contact);
1108
+
1109
+ expect($contact->events)->toBe([
1110
+ 'doesntHaveUser',
1111
+ 'createUser',
1112
+ 'assertCustomerIdentityIsAvailable',
1113
+ 'doesntHaveUser',
1114
+ 'isCustomer',
1115
+ 'normalizeCustomerUser',
1116
+ 'syncWithUser',
1117
+ 'deleteUser',
1118
+ ]);
1119
+ });
1120
+
1121
+ test('contact observer creates the user account on save when none exists yet', function () {
1122
+ $observer = new ContactObserver();
1123
+ $contact = new FleetOpsContactObserverContactFake();
1124
+ $contact->setRawAttributes(['type' => 'contact']);
1125
+
1126
+ // Saving without a prior creating() leaves the contact userless, which is
1127
+ // the arm that provisions the account rather than reusing it.
1128
+ $observer->saving($contact);
1129
+
1130
+ expect($contact->events)->toBe([
1131
+ 'assertCustomerIdentityIsAvailable',
1132
+ 'doesntHaveUser',
1133
+ 'createUser',
1134
+ 'isCustomer',
1135
+ 'syncWithUser',
1136
+ ]);
1137
+ });
1138
+
1139
+ test('contact observer prevents changing existing customer contact type', function () {
1140
+ $observer = new ContactObserver();
1141
+ $contact = new FleetOpsContactObserverContactFake();
1142
+ $contact->exists = true;
1143
+ $contact->typeDirty = true;
1144
+ $contact->originalData = ['type' => 'customer'];
1145
+ $contact->setRawAttributes(['type' => 'driver']);
1146
+
1147
+ expect(fn () => $observer->saving($contact))
1148
+ ->toThrow(Exception::class, 'Customer contact type cannot be changed.');
1149
+ });
1150
+
1151
+ test('driver observer defaults location unassigns related records and deletes driver users', function () {
1152
+ $driver = new Driver();
1153
+ $driver->setRawAttributes([
1154
+ 'uuid' => 'driver-uuid',
1155
+ 'user_uuid' => 'user-uuid',
1156
+ ], true);
1157
+
1158
+ $observer = new FleetOpsDriverObserverProbe();
1159
+ $user = new FleetOpsDriverObserverUserFake();
1160
+
1161
+ $observer->user = $user;
1162
+ $observer->creating($driver);
1163
+ $observer->created($driver);
1164
+ $observer->updated($driver);
1165
+ $observer->deleting($driver);
1166
+ $observer->deleted($driver);
1167
+
1168
+ expect($driver->location)->toBeInstanceOf(Fleetbase\LaravelMysqlSpatial\Types\Point::class)
1169
+ ->and($driver->vehicle_uuid)->toBeNull()
1170
+ ->and($observer->invalidations)->toBe([
1171
+ ['drivers', 'operations-monitor'],
1172
+ ['drivers', 'operations-monitor'],
1173
+ ['drivers', 'operations-monitor'],
1174
+ ])
1175
+ ->and($observer->unassigned)->toBe(['driver-uuid'])
1176
+ ->and($user->deleted)->toBeTrue();
1177
+
1178
+ $driverWithLocation = new Driver();
1179
+ $location = new Fleetbase\LaravelMysqlSpatial\Types\Point(1.3, 103.8);
1180
+ $driverWithLocation->location = $location;
1181
+ $observer->creating($driverWithLocation);
1182
+
1183
+ expect($driverWithLocation->location)->toBe($location);
1184
+ });
1185
+
1186
+ test('vehicle observer assigns requested driver and invalidates caches', function () {
1187
+ $observer = new FleetOpsVehicleObserverProbe();
1188
+ $observer->identifier = 'driver-uuid';
1189
+ $observer->driver = new FleetOpsVehicleObserverDriverFake();
1190
+
1191
+ $vehicle = new Vehicle();
1192
+ $vehicle->setRawAttributes(['uuid' => 'vehicle-uuid'], true);
1193
+
1194
+ $observer->created($vehicle);
1195
+ $observer->updating($vehicle);
1196
+ $observer->deleted($vehicle);
1197
+
1198
+ expect($observer->driver->assignments)->toBe([$vehicle, $vehicle])
1199
+ ->and($vehicle->getRelation('driver'))->toBe($observer->driver)
1200
+ ->and($observer->deleted)->toBe(['vehicle-uuid'])
1201
+ ->and($observer->invalidated)->toBe([
1202
+ ['vehicles', 'operations-monitor'],
1203
+ ['vehicles', 'operations-monitor'],
1204
+ ['vehicles', 'operations-monitor'],
1205
+ ]);
1206
+
1207
+ $withoutDriver = new FleetOpsVehicleObserverProbe();
1208
+ $withoutDriver->created(new Vehicle());
1209
+
1210
+ expect($withoutDriver->invalidated)->toBe([['vehicles', 'operations-monitor']]);
1211
+ });
1212
+
1213
+ test('service rate observer syncs rate and parcel fee inputs and deletes loaded fees', function () {
1214
+ $observer = new FleetOpsServiceRateObserverProbe();
1215
+ $observer->rateFeesInput = [['fee' => 10]];
1216
+ $observer->parcelFeesInput = [['parcel_fee' => 20]];
1217
+
1218
+ $serviceRate = new FleetOpsServiceRateObserverServiceRateFake();
1219
+ $serviceRate->fixedMeter = true;
1220
+ $serviceRate->parcelService = true;
1221
+
1222
+ $observer->created($serviceRate);
1223
+
1224
+ expect($serviceRate->rateFeeCalls)->toBe([[['fee' => 10]]])
1225
+ ->and($serviceRate->parcelFeeCalls)->toBe([[['parcel_fee' => 20]]]);
1226
+
1227
+ $serviceRate->fixedMeter = false;
1228
+ $serviceRate->perDrop = true;
1229
+ $serviceRate->multiZone = false;
1230
+ $serviceRate->parcelService = false;
1231
+ $serviceRate->rateFeeCalls = [];
1232
+ $serviceRate->parcelFeeCalls = [];
1233
+ $observer->updated($serviceRate);
1234
+
1235
+ expect($serviceRate->rateFeeCalls)->toBe([[['fee' => 10]]])
1236
+ ->and($serviceRate->parcelFeeCalls)->toBe([]);
1237
+
1238
+ // Parcel services sync their parcel fees on update too, not only on create
1239
+ $serviceRate->perDrop = false;
1240
+ $serviceRate->parcelService = true;
1241
+ $serviceRate->rateFeeCalls = [];
1242
+ $serviceRate->parcelFeeCalls = [];
1243
+ $observer->updated($serviceRate);
1244
+
1245
+ expect($serviceRate->rateFeeCalls)->toBe([])
1246
+ ->and($serviceRate->parcelFeeCalls)->toBe([[['parcel_fee' => 20]]]);
1247
+
1248
+ $observer->deleted($serviceRate);
1249
+
1250
+ expect($serviceRate->relationsLoaded)->toBeTrue()
1251
+ ->and($observer->deletedModelBatches)->toHaveCount(2);
1252
+ });
1253
+
1254
+ test('tracking number observer generates codes and creates initial tracking status', function () {
1255
+ session(['company' => 'company-uuid']);
1256
+
1257
+ $trackingNumber = new FleetOpsTrackingNumberObserverTrackingNumberFake();
1258
+ $trackingNumber->setRawAttributes([
1259
+ 'uuid' => 'tracking-uuid',
1260
+ 'region' => 'sg',
1261
+ 'owner_uuid' => 'owner-uuid',
1262
+ 'owner_type' => Order::class,
1263
+ ], true);
1264
+
1265
+ $observer = new FleetOpsTrackingNumberObserverProbe();
1266
+ $observer->creating($trackingNumber);
1267
+ $observer->created($trackingNumber);
1268
+
1269
+ expect($trackingNumber->tracking_number)->toBe('TN-sg')
1270
+ ->and($trackingNumber->qr_code)->toBe('QRCODE-png')
1271
+ ->and($trackingNumber->barcode)->toBe('PDF417-png')
1272
+ ->and($observer->barcodes)->toBe([
1273
+ ['owner-uuid', 'QRCODE'],
1274
+ ['owner-uuid', 'PDF417'],
1275
+ ])
1276
+ ->and($observer->statuses[0])->toMatchArray([
1277
+ 'company_uuid' => 'company-uuid',
1278
+ 'tracking_number_uuid' => 'tracking-uuid',
1279
+ 'status' => 'Order Created',
1280
+ 'details' => 'New order created.',
1281
+ 'code' => 'CREATED',
1282
+ ])
1283
+ ->and($observer->statuses[0]['location'])->toBeInstanceOf(Fleetbase\LaravelMysqlSpatial\Types\Point::class)
1284
+ ->and($trackingNumber->ownerStatuses)->toHaveCount(1);
1285
+ });
1286
+
1287
+ test('zone observer invalidates service area cache for lifecycle events and original service area', function () {
1288
+ $zone = new Zone();
1289
+ $zone->setRawAttributes([
1290
+ 'uuid' => 'zone-uuid',
1291
+ 'company_uuid' => 'company-uuid',
1292
+ 'service_area_uuid' => 'service-area-old',
1293
+ ], true);
1294
+ $zone->service_area_uuid = 'service-area-new';
1295
+
1296
+ $observer = new FleetOpsZoneObserverProbe();
1297
+ $observer->created($zone);
1298
+ $observer->updated($zone);
1299
+ $observer->deleted($zone);
1300
+ $observer->restored($zone);
1301
+ $observer->created(new Zone());
1302
+
1303
+ expect($observer->invalidations)->toBe([
1304
+ ['company-uuid', 'service-area-new'],
1305
+ ['company-uuid', 'service-area-old'],
1306
+ ['company-uuid', 'service-area-new'],
1307
+ ['company-uuid', 'service-area-new'],
1308
+ ['company-uuid', 'service-area-new'],
1309
+ ]);
1310
+ });
1311
+
1312
+ test('notify driver on shift change exits early and sends enabled driver notifications', function () {
1313
+ $listener = new FleetOpsNotifyDriverOnShiftChangeProbe();
1314
+
1315
+ $listener->handle((object) []);
1316
+ expect($listener->sent)->toBe([]);
1317
+
1318
+ $scheduleItem = new ScheduleItem();
1319
+ $listener->handle((object) ['scheduleItem' => $scheduleItem]);
1320
+ expect($listener->sent)->toBe([]);
1321
+
1322
+ $schedule = new Schedule();
1323
+ $schedule->setRelation('subject', new User());
1324
+ $listener->schedule = $schedule;
1325
+ $listener->handle((object) ['scheduleItem' => $scheduleItem]);
1326
+ expect($listener->sent)->toBe([]);
1327
+
1328
+ $driver = new Driver();
1329
+ $schedule->setRelation('subject', $driver);
1330
+ $listener->handle((object) ['scheduleItem' => $scheduleItem]);
1331
+ expect($listener->sent)->toBe([]);
1332
+
1333
+ $listener->settings = ['notify_drivers_on_shift_change' => true];
1334
+ $listener->createdEvent = true;
1335
+ $listener->handle((object) ['scheduleItem' => $scheduleItem]);
1336
+
1337
+ expect($listener->sent)->toHaveCount(1)
1338
+ ->and($listener->sent[0][0])->toBe($driver)
1339
+ ->and($listener->sent[0][1])->toBeInstanceOf(DriverShiftChanged::class);
1340
+ });
1341
+
1342
+ if (!function_exists('Fleetbase\FleetOps\Observers\event')) {
1343
+ eval('namespace Fleetbase\FleetOps\Observers; function event($event = null, $payload = null) { $GLOBALS["fleetopsObserverEvents"][] = [$event, $payload]; return []; }');
1344
+ }
1345
+
1346
+ test('work order observer real helpers query maintenance schedules and events', function () {
1347
+ $connection = new Illuminate\Database\SQLiteConnection(new PDO('sqlite::memory:'));
1348
+ $resolver = new Illuminate\Database\ConnectionResolver(['default' => $connection, 'mysql' => $connection]);
1349
+ $resolver->setDefaultConnection('mysql');
1350
+ Illuminate\Database\Eloquent\Model::setConnectionResolver($resolver);
1351
+ if (!Illuminate\Database\Eloquent\Model::getEventDispatcher()) {
1352
+ Illuminate\Database\Eloquent\Model::setEventDispatcher(new Illuminate\Events\Dispatcher());
1353
+ }
1354
+ $schema = $connection->getSchemaBuilder();
1355
+ foreach (['maintenances' => ['uuid', 'public_id', 'company_uuid', 'work_order_uuid', 'maintainable_type', 'maintainable_uuid', 'type', 'status', 'scheduled_at', 'odometer', 'engine_hours', 'summary', '_key'], 'maintenance_schedules' => ['uuid', 'public_id', 'company_uuid', 'name', 'next_due_at', 'meta', '_key']] as $table => $columns) {
1356
+ $schema->create($table, function ($blueprint) use ($columns) {
1357
+ $blueprint->increments('id');
1358
+ foreach ($columns as $column) {
1359
+ $blueprint->string($column)->nullable();
1360
+ }
1361
+ $blueprint->timestamps();
1362
+ $blueprint->timestamp('deleted_at')->nullable();
1363
+ });
1364
+ }
1365
+ session(['company' => 'company-1']);
1366
+ app()->instance('responsecache', new class {
1367
+ public function __call($method, $arguments)
1368
+ {
1369
+ return null;
1370
+ }
1371
+ });
1372
+ config()->set('activitylog.enabled', false);
1373
+ config()->set('activitylog.default_auth_driver', 'web');
1374
+ app()->bind(Illuminate\Contracts\Config\Repository::class, fn () => config());
1375
+ $connection->table('maintenances')->insert(['uuid' => 'mnt-obs-1', 'work_order_uuid' => 'wo-obs-1', 'status' => 'completed']);
1376
+ $connection->table('maintenance_schedules')->insert(['uuid' => 'sched-obs-1', 'company_uuid' => 'company-1', 'name' => 'Quarterly']);
1377
+
1378
+ $observer = new WorkOrderObserver();
1379
+ $helper = function (string $method, ...$arguments) use ($observer) {
1380
+ $reflection = new ReflectionMethod(WorkOrderObserver::class, $method);
1381
+ $reflection->setAccessible(true);
1382
+
1383
+ return $reflection->invoke($observer, ...$arguments);
1384
+ };
1385
+
1386
+ $workOrder = new WorkOrder();
1387
+ $workOrder->setRawAttributes(['uuid' => 'wo-obs-1', 'company_uuid' => 'company-1'], true);
1388
+
1389
+ // Real queries scope by work order and schedule identity
1390
+ expect($helper('hasMaintenanceRecord', $workOrder))->toBeTrue()
1391
+ ->and($helper('findSchedule', 'sched-obs-1'))->toBeInstanceOf(MaintenanceSchedule::class)
1392
+ ->and($helper('findSchedule', 'sched-missing'))->toBeNull();
1393
+
1394
+ // Maintenance creation persists rows through the observer helper
1395
+ $created = $helper('createMaintenance', ['company_uuid' => 'company-1', 'work_order_uuid' => 'wo-obs-2', 'status' => 'scheduled', 'type' => 'inspection']);
1396
+ expect($created)->toBeInstanceOf(Maintenance::class)
1397
+ ->and($connection->table('maintenances')->count())->toBe(2);
1398
+
1399
+ // Completed events dispatch through the observer event shim
1400
+ $GLOBALS['fleetopsObserverEvents'] = [];
1401
+ $helper('dispatchCompletedEvent', $workOrder);
1402
+ expect($GLOBALS['fleetopsObserverEvents'][0][0])->toBe('work_order.completed');
1403
+
1404
+ // Schedule resets skip work orders without linked schedules
1405
+ $unlinked = new WorkOrder();
1406
+ $unlinked->setRawAttributes(['uuid' => 'wo-obs-3', 'company_uuid' => 'company-1', 'schedule_uuid' => null], true);
1407
+ $helper('resetSchedule', $unlinked);
1408
+
1409
+ // And skip quietly when the linked schedule row is missing
1410
+ $orphaned = new WorkOrder();
1411
+ $orphaned->setRawAttributes(['uuid' => 'wo-obs-4', 'company_uuid' => 'company-1', 'schedule_uuid' => 'sched-missing'], true);
1412
+ $helper('resetSchedule', $orphaned);
1413
+ expect(true)->toBeTrue();
1414
+ });