@fleetbase/fleetops-engine 0.6.57 → 0.6.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (693) hide show
  1. package/README.md +3 -0
  2. package/addon/components/customer/form.hbs +0 -49
  3. package/addon/components/entity/card.hbs +1 -0
  4. package/addon/components/modals/order-label.hbs +1 -1
  5. package/addon/components/place/form.hbs +1 -1
  6. package/addon/services/entity-actions.js +32 -0
  7. package/addon/styles/fleetops-engine.css +0 -1
  8. package/composer.json +15 -3
  9. package/extension.json +1 -1
  10. package/index.js +7 -1
  11. package/package.json +3 -2
  12. package/phpunit.xml.dist +4 -4
  13. package/pnpm-workspace.yaml +2 -1
  14. package/scripts/coverage-file-runner.php +240 -0
  15. package/scripts/coverage-runner.php +52 -0
  16. package/scripts/coverage-summary.php +149 -0
  17. package/scripts/pest-bootstrap.php +411 -0
  18. package/scripts/pest-file-runner.php +94 -0
  19. package/scripts/pest-runner.php +119 -0
  20. package/server/resources/views/labels/entity-label.php +4 -4
  21. package/server/src/Casts/MultiPolygon.php +8 -3
  22. package/server/src/Casts/OrderConfigEntities.php +10 -3
  23. package/server/src/Casts/Point.php +10 -6
  24. package/server/src/Casts/Polygon.php +5 -2
  25. package/server/src/Console/Commands/AssignCustomerRoles.php +28 -6
  26. package/server/src/Console/Commands/AssignDriverRoles.php +36 -6
  27. package/server/src/Console/Commands/AuditCustomerUserConflicts.php +6 -1
  28. package/server/src/Console/Commands/DispatchAdhocOrders.php +12 -2
  29. package/server/src/Console/Commands/FixCustomerCompanies.php +46 -11
  30. package/server/src/Console/Commands/FixDriverCompanies.php +18 -3
  31. package/server/src/Console/Commands/FixLegacyOrderConfigs.php +31 -6
  32. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +94 -42
  33. package/server/src/Console/Commands/PurgeUnpurchasedServiceQuotes.php +42 -12
  34. package/server/src/Console/Commands/ReplayVehicleLocations.php +120 -55
  35. package/server/src/Console/Commands/SendDriverNotification.php +25 -4
  36. package/server/src/Console/Commands/SendMaintenanceReminders.php +43 -23
  37. package/server/src/Console/Commands/SimulateOrderRouteNavigation.php +36 -6
  38. package/server/src/Console/Commands/TrackOrderDistanceAndTime.php +6 -1
  39. package/server/src/Events/DriverLocationChanged.php +1 -1
  40. package/server/src/Events/DriverSimulatedLocationChanged.php +1 -1
  41. package/server/src/Events/EntityActivityChanged.php +1 -1
  42. package/server/src/Events/EntityCompleted.php +1 -1
  43. package/server/src/Events/EntityDriverAssigned.php +1 -1
  44. package/server/src/Events/FuelProviderTransactionImported.php +1 -1
  45. package/server/src/Events/FuelProviderTransactionMatched.php +1 -1
  46. package/server/src/Events/FuelProviderTransactionUnmatched.php +1 -1
  47. package/server/src/Events/FuelReportCreatedFromProvider.php +1 -1
  48. package/server/src/Events/GeofenceDwelled.php +1 -1
  49. package/server/src/Events/GeofenceEntered.php +1 -1
  50. package/server/src/Events/GeofenceExited.php +1 -1
  51. package/server/src/Events/VehicleLocationChanged.php +1 -1
  52. package/server/src/Events/WaypointActivityChanged.php +1 -1
  53. package/server/src/Events/WaypointCompleted.php +1 -1
  54. package/server/src/Exceptions/IntegratedVendorException.php +2 -2
  55. package/server/src/Exceptions/TelematicRateLimitExceededException.php +1 -1
  56. package/server/src/Http/Controllers/Api/v1/ContactController.php +89 -22
  57. package/server/src/Http/Controllers/Api/v1/CustomerController.php +296 -97
  58. package/server/src/Http/Controllers/Api/v1/DeviceController.php +48 -14
  59. package/server/src/Http/Controllers/Api/v1/DriverController.php +168 -63
  60. package/server/src/Http/Controllers/Api/v1/EntityController.php +112 -66
  61. package/server/src/Http/Controllers/Api/v1/EquipmentController.php +40 -10
  62. package/server/src/Http/Controllers/Api/v1/FleetController.php +54 -14
  63. package/server/src/Http/Controllers/Api/v1/FuelReportController.php +54 -14
  64. package/server/src/Http/Controllers/Api/v1/FuelTransactionController.php +36 -11
  65. package/server/src/Http/Controllers/Api/v1/GeofenceController.php +39 -21
  66. package/server/src/Http/Controllers/Api/v1/IssueController.php +54 -14
  67. package/server/src/Http/Controllers/Api/v1/LabelController.php +61 -22
  68. package/server/src/Http/Controllers/Api/v1/NavigatorController.php +21 -5
  69. package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +35 -5
  70. package/server/src/Http/Controllers/Api/v1/OrderController.php +388 -203
  71. package/server/src/Http/Controllers/Api/v1/PartController.php +40 -10
  72. package/server/src/Http/Controllers/Api/v1/PayloadController.php +90 -30
  73. package/server/src/Http/Controllers/Api/v1/PlaceController.php +188 -91
  74. package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +100 -36
  75. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +96 -26
  76. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +169 -81
  77. package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +104 -75
  78. package/server/src/Http/Controllers/Api/v1/TrackingNumberController.php +83 -14
  79. package/server/src/Http/Controllers/Api/v1/TrackingStatusController.php +66 -16
  80. package/server/src/Http/Controllers/Api/v1/VehicleController.php +121 -67
  81. package/server/src/Http/Controllers/Api/v1/VendorController.php +54 -14
  82. package/server/src/Http/Controllers/Api/v1/WorkOrderController.php +47 -12
  83. package/server/src/Http/Controllers/Api/v1/ZoneController.php +84 -24
  84. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +15 -5
  85. package/server/src/Http/Controllers/Internal/v1/ContactController.php +113 -28
  86. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +36 -11
  87. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +9 -4
  88. package/server/src/Http/Controllers/Internal/v1/DriverController.php +167 -69
  89. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +18 -3
  90. package/server/src/Http/Controllers/Internal/v1/FleetController.php +137 -60
  91. package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +24 -9
  92. package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +18 -3
  93. package/server/src/Http/Controllers/Internal/v1/GeocoderController.php +26 -8
  94. package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +12 -2
  95. package/server/src/Http/Controllers/Internal/v1/IntegratedVendorController.php +27 -6
  96. package/server/src/Http/Controllers/Internal/v1/IssueController.php +82 -29
  97. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +34 -26
  98. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +141 -97
  99. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +28 -8
  100. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +21 -12
  101. package/server/src/Http/Controllers/Internal/v1/MorphController.php +90 -18
  102. package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +88 -35
  103. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +149 -39
  104. package/server/src/Http/Controllers/Internal/v1/OrderConfigController.php +54 -18
  105. package/server/src/Http/Controllers/Internal/v1/OrderController.php +298 -86
  106. package/server/src/Http/Controllers/Internal/v1/PartController.php +18 -3
  107. package/server/src/Http/Controllers/Internal/v1/PaymentController.php +46 -14
  108. package/server/src/Http/Controllers/Internal/v1/PlaceController.php +31 -5
  109. package/server/src/Http/Controllers/Internal/v1/ProofController.php +88 -48
  110. package/server/src/Http/Controllers/Internal/v1/SearchController.php +1 -1
  111. package/server/src/Http/Controllers/Internal/v1/SensorController.php +6 -1
  112. package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +6 -1
  113. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +255 -105
  114. package/server/src/Http/Controllers/Internal/v1/SettingController.php +88 -35
  115. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -4
  116. package/server/src/Http/Controllers/Internal/v1/Traits/DriverSchedulingTrait.php +42 -14
  117. package/server/src/Http/Controllers/Internal/v1/VendorController.php +103 -33
  118. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +42 -12
  119. package/server/src/Http/Filter/DriverFilter.php +2 -2
  120. package/server/src/Http/Filter/OrderFilter.php +1 -1
  121. package/server/src/Http/Filter/VehicleFilter.php +21 -7
  122. package/server/src/Http/Middleware/AuthenticateCustomerToken.php +12 -2
  123. package/server/src/Http/Requests/BulkDispatchRequest.php +3 -9
  124. package/server/src/Http/Requests/CancelOrderRequest.php +2 -6
  125. package/server/src/Http/Requests/CreateContactRequest.php +2 -6
  126. package/server/src/Http/Requests/CreateDeviceRequest.php +2 -2
  127. package/server/src/Http/Requests/CreateDriverRequest.php +3 -9
  128. package/server/src/Http/Requests/CreateEntityRequest.php +2 -6
  129. package/server/src/Http/Requests/CreateEquipmentRequest.php +2 -2
  130. package/server/src/Http/Requests/CreateFleetRequest.php +2 -6
  131. package/server/src/Http/Requests/CreateFuelReportRequest.php +2 -6
  132. package/server/src/Http/Requests/CreateFuelTransactionRequest.php +2 -2
  133. package/server/src/Http/Requests/CreateIssueRequest.php +2 -6
  134. package/server/src/Http/Requests/CreateOrderRequest.php +7 -8
  135. package/server/src/Http/Requests/CreatePartRequest.php +2 -2
  136. package/server/src/Http/Requests/CreatePayloadRequest.php +7 -9
  137. package/server/src/Http/Requests/CreatePlaceRequest.php +2 -6
  138. package/server/src/Http/Requests/CreatePurchaseRateRequest.php +2 -6
  139. package/server/src/Http/Requests/CreateSensorRequest.php +2 -2
  140. package/server/src/Http/Requests/CreateServiceAreaRequest.php +2 -6
  141. package/server/src/Http/Requests/CreateServiceQuoteRequest.php +2 -6
  142. package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -6
  143. package/server/src/Http/Requests/CreateTrackingNumberRequest.php +2 -6
  144. package/server/src/Http/Requests/CreateTrackingStatusRequest.php +2 -6
  145. package/server/src/Http/Requests/CreateVehicleRequest.php +2 -6
  146. package/server/src/Http/Requests/CreateVendorRequest.php +2 -6
  147. package/server/src/Http/Requests/CreateWorkOrderRequest.php +2 -2
  148. package/server/src/Http/Requests/CreateZoneRequest.php +2 -6
  149. package/server/src/Http/Requests/DecodeTrackingNumberQR.php +2 -6
  150. package/server/src/Http/Requests/DriverSimulationRequest.php +2 -6
  151. package/server/src/Http/Requests/Internal/AssignOrderRequest.php +3 -7
  152. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +4 -12
  153. package/server/src/Http/Requests/Internal/CreateOrderConfigRequest.php +2 -6
  154. package/server/src/Http/Requests/Internal/CreateOrderRequest.php +7 -8
  155. package/server/src/Http/Requests/Internal/FleetActionRequest.php +18 -12
  156. package/server/src/Http/Requests/Internal/UpdateDriverRequest.php +6 -3
  157. package/server/src/Http/Requests/QueryServiceQuotesRequest.php +2 -6
  158. package/server/src/Http/Requests/ScheduleOrderRequest.php +2 -6
  159. package/server/src/Http/Requests/UpdateIssueRequest.php +1 -3
  160. package/server/src/Http/Resources/v1/Customer.php +2 -2
  161. package/server/src/Http/Resources/v1/Maintenance.php +3 -4
  162. package/server/src/Http/Resources/v1/MaintenanceSchedule.php +3 -4
  163. package/server/src/Http/Resources/v1/Order.php +3 -7
  164. package/server/src/Http/Resources/v1/PurchaseRate.php +4 -6
  165. package/server/src/Http/Resources/v1/TrackingStatus.php +4 -6
  166. package/server/src/Http/Resources/v1/WorkOrder.php +3 -4
  167. package/server/src/Imports/ContactImport.php +10 -1
  168. package/server/src/Imports/DriverImport.php +10 -1
  169. package/server/src/Imports/EquipmentImport.php +6 -1
  170. package/server/src/Imports/FleetImport.php +10 -1
  171. package/server/src/Imports/FuelReportImport.php +10 -1
  172. package/server/src/Imports/IssueImport.php +10 -1
  173. package/server/src/Imports/MaintenanceImport.php +6 -1
  174. package/server/src/Imports/MaintenanceScheduleImport.php +6 -1
  175. package/server/src/Imports/PartImport.php +6 -1
  176. package/server/src/Imports/PlaceImport.php +10 -1
  177. package/server/src/Imports/VehicleImport.php +10 -1
  178. package/server/src/Imports/VendorImport.php +10 -1
  179. package/server/src/Imports/WorkOrderImport.php +6 -1
  180. package/server/src/Integrations/Lalamove/Lalamove.php +3 -7
  181. package/server/src/Integrations/Lalamove/LalamoveServiceType.php +5 -8
  182. package/server/src/Jobs/CheckGeofenceDwell.php +41 -16
  183. package/server/src/Jobs/FinalizeApiOrderCreation.php +17 -2
  184. package/server/src/Jobs/FinalizeInternalOrderCreation.php +11 -1
  185. package/server/src/Jobs/NotifyBulkAssignedDriver.php +29 -16
  186. package/server/src/Jobs/ProcessAllocationJob.php +59 -25
  187. package/server/src/Jobs/ReplayPositions.php +17 -10
  188. package/server/src/Jobs/SendPositionReplay.php +18 -7
  189. package/server/src/Jobs/SimulateDrivingRoute.php +14 -3
  190. package/server/src/Jobs/SyncFuelProviderTransactionsJob.php +12 -2
  191. package/server/src/Listeners/HandleDeliveryCompletion.php +11 -1
  192. package/server/src/Listeners/HandleGeofenceDwelled.php +6 -1
  193. package/server/src/Listeners/HandleGeofenceEntered.php +4 -2
  194. package/server/src/Listeners/HandleGeofenceExited.php +6 -1
  195. package/server/src/Listeners/HandleOrderCanceled.php +18 -3
  196. package/server/src/Listeners/HandleOrderDispatchFailed.php +12 -2
  197. package/server/src/Listeners/HandleOrderDispatched.php +61 -26
  198. package/server/src/Listeners/HandleOrderDriverAssigned.php +12 -2
  199. package/server/src/Listeners/HandleUserRemovedFromCompany.php +19 -6
  200. package/server/src/Listeners/NotifyDriverOnShiftChange.php +24 -4
  201. package/server/src/Listeners/NotifyOrderEvent.php +11 -6
  202. package/server/src/Models/Contact.php +6 -7
  203. package/server/src/Models/Device.php +26 -3
  204. package/server/src/Models/Driver.php +0 -6
  205. package/server/src/Models/Entity.php +4 -4
  206. package/server/src/Models/Equipment.php +1 -1
  207. package/server/src/Models/FuelReport.php +5 -1
  208. package/server/src/Models/IntegratedVendor.php +2 -2
  209. package/server/src/Models/Issue.php +5 -1
  210. package/server/src/Models/MaintenanceSchedule.php +25 -10
  211. package/server/src/Models/Order.php +18 -15
  212. package/server/src/Models/OrderConfig.php +1 -1
  213. package/server/src/Models/Payload.php +9 -16
  214. package/server/src/Models/Place.php +20 -13
  215. package/server/src/Models/ServiceArea.php +3 -6
  216. package/server/src/Models/ServiceQuote.php +5 -0
  217. package/server/src/Models/ServiceRate.php +13 -6
  218. package/server/src/Models/Telematic.php +2 -2
  219. package/server/src/Models/TrackingNumber.php +55 -10
  220. package/server/src/Models/TrackingStatus.php +24 -4
  221. package/server/src/Models/Waypoint.php +44 -10
  222. package/server/src/Models/Zone.php +3 -6
  223. package/server/src/Notifications/DriverArrivedAtGeofence.php +1 -1
  224. package/server/src/Notifications/OrderDispatched.php +3 -9
  225. package/server/src/Notifications/OrderPing.php +3 -9
  226. package/server/src/Observers/CategoryObserver.php +6 -1
  227. package/server/src/Observers/CompanyObserver.php +5 -0
  228. package/server/src/Observers/CompanyUserObserver.php +12 -2
  229. package/server/src/Observers/DriverObserver.php +19 -4
  230. package/server/src/Observers/FleetObserver.php +6 -1
  231. package/server/src/Observers/PurchaseRateObserver.php +66 -11
  232. package/server/src/Observers/ServiceAreaObserver.php +12 -2
  233. package/server/src/Observers/ServiceRateObserver.php +21 -6
  234. package/server/src/Observers/TrackingNumberObserver.php +19 -4
  235. package/server/src/Observers/UserObserver.php +6 -1
  236. package/server/src/Observers/VehicleObserver.php +27 -7
  237. package/server/src/Observers/WorkOrderObserver.php +27 -7
  238. package/server/src/Orchestration/Engines/DriverAssignmentEngine.php +8 -3
  239. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +5 -3
  240. package/server/src/Providers/FleetOpsServiceProvider.php +12 -0
  241. package/server/src/Providers/NotificationServiceProvider.php +6 -0
  242. package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +36 -26
  243. package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +18 -5
  244. package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +6 -1
  245. package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +26 -6
  246. package/server/src/Support/Analytics/FuelEfficiency.php +9 -3
  247. package/server/src/Support/Analytics/FuelProviderSummary.php +14 -5
  248. package/server/src/Support/Analytics/GeofenceViolations.php +40 -20
  249. package/server/src/Support/Analytics/IssuesInsights.php +38 -18
  250. package/server/src/Support/Analytics/MaintenanceOverview.php +51 -21
  251. package/server/src/Support/Analytics/OnTimeDelivery.php +3 -2
  252. package/server/src/Support/Analytics/OperationsPulse.php +59 -35
  253. package/server/src/Support/Analytics/OrdersByStatus.php +12 -7
  254. package/server/src/Support/Analytics/RevenueTrend.php +16 -6
  255. package/server/src/Support/Analytics/TopDrivers.php +3 -2
  256. package/server/src/Support/FleetOps.php +109 -104
  257. package/server/src/Support/Geocoding.php +52 -14
  258. package/server/src/Support/GeofenceIntersectionService.php +47 -27
  259. package/server/src/Support/LiveOrderQuery.php +6 -1
  260. package/server/src/Support/Metrics/ActiveRevenueQuery.php +3 -2
  261. package/server/src/Support/Metrics/FuelCostsMetric.php +6 -1
  262. package/server/src/Support/ResolvesOrderServiceStops.php +10 -0
  263. package/server/src/Support/Telematics/Providers/GeotabProvider.php +9 -4
  264. package/server/src/Support/Utils.php +122 -16
  265. package/server/src/Tracking/Providers/GoogleRoutesTrackingProvider.php +6 -1
  266. package/server/src/Tracking/Providers/OsrmTrackingProvider.php +11 -6
  267. package/server/src/Tracking/TrackingOptions.php +6 -1
  268. package/server/src/Tracking/TrackingProviderManager.php +2 -3
  269. package/server/src/Tracking/TrackingProviderRegistry.php +8 -4
  270. package/server/src/Traits/PayloadAccessors.php +6 -1
  271. package/server/src/routes.php +23 -27
  272. package/server/tests/AiOperationalQueryCapabilityTest.php +457 -1
  273. package/server/tests/AiPreviewCapabilityContractsTest.php +180 -0
  274. package/server/tests/AnalyticsRoutesTest.php +384 -3
  275. package/server/tests/AnalyticsWidgetContractsTest.php +740 -0
  276. package/server/tests/ApiContactControllerContractsTest.php +284 -0
  277. package/server/tests/ApiCustomerControllerContractsTest.php +962 -0
  278. package/server/tests/ApiDriverControllerContractsTest.php +571 -0
  279. package/server/tests/ApiEntityControllerContractsTest.php +329 -0
  280. package/server/tests/ApiEquipmentControllerContractsTest.php +239 -0
  281. package/server/tests/ApiFleetControllerContractsTest.php +176 -0
  282. package/server/tests/ApiFuelReportControllerContractsTest.php +217 -0
  283. package/server/tests/ApiIssueControllerContractsTest.php +217 -0
  284. package/server/tests/ApiOrderControllerContractsTest.php +1030 -0
  285. package/server/tests/ApiPartControllerContractsTest.php +251 -0
  286. package/server/tests/ApiPayloadControllerContractsTest.php +314 -0
  287. package/server/tests/ApiPlaceControllerContractsTest.php +541 -0
  288. package/server/tests/ApiPurchaseRateControllerContractsTest.php +283 -0
  289. package/server/tests/ApiServiceAreaZoneControllerContractsTest.php +442 -0
  290. package/server/tests/ApiServiceQuoteControllerContractsTest.php +322 -0
  291. package/server/tests/ApiTrackingNumberControllerContractsTest.php +225 -0
  292. package/server/tests/ApiTrackingStatusControllerContractsTest.php +254 -0
  293. package/server/tests/ApiVehicleControllerContractsTest.php +368 -0
  294. package/server/tests/ApiVendorControllerContractsTest.php +199 -0
  295. package/server/tests/AssetAndPlaceSearchContractsTest.php +220 -0
  296. package/server/tests/BackendSmallModelContractsTest.php +354 -0
  297. package/server/tests/CapacityAllocationEngineTest.php +56 -0
  298. package/server/tests/CommandContractsTest.php +2695 -0
  299. package/server/tests/CompactResourceSerializationTest.php +2745 -0
  300. package/server/tests/ControllerFilterContractsTest.php +1273 -0
  301. package/server/tests/ControllerHelperContractsTest.php +1740 -0
  302. package/server/tests/ControllerSerializationContractsTest.php +141 -0
  303. package/server/tests/CoverageSummaryScriptTest.php +93 -0
  304. package/server/tests/CustomerContactIdentitySafetyTest.php +1 -6
  305. package/server/tests/CustomerEndpointTest.php +166 -1
  306. package/server/tests/DriverFilterExecutionTest.php +263 -0
  307. package/server/tests/DriverGeofenceProcessingTest.php +153 -0
  308. package/server/tests/DriverResourceSerializationTest.php +245 -0
  309. package/server/tests/DriverSchedulingTraitContractsTest.php +259 -0
  310. package/server/tests/EventContractsTest.php +1259 -0
  311. package/server/tests/ExceptionContractsTest.php +81 -0
  312. package/server/tests/ExpansionContractsTest.php +242 -0
  313. package/server/tests/ExportCoverageTest.php +116 -0
  314. package/server/tests/Feature/Http/Api/ContactControllerPhotoAndDeleteTest.php +199 -0
  315. package/server/tests/Feature/Http/Api/CustomerControllerHelperSeamsTest.php +831 -0
  316. package/server/tests/Feature/Http/Api/DeviceControllerContractsTest.php +347 -0
  317. package/server/tests/Feature/Http/Api/DeviceControllerCrudTest.php +176 -0
  318. package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +451 -0
  319. package/server/tests/Feature/Http/Api/DriverControllerHelpersTest.php +240 -0
  320. package/server/tests/Feature/Http/Api/DriverControllerLookupTest.php +186 -0
  321. package/server/tests/Feature/Http/Api/DriverControllerOrganizationsTest.php +112 -0
  322. package/server/tests/Feature/Http/Api/DriverControllerSessionHelpersTest.php +207 -0
  323. package/server/tests/Feature/Http/Api/DriverControllerSwitchOrgAndGeofenceTest.php +329 -0
  324. package/server/tests/Feature/Http/Api/DriverControllerTrackTest.php +383 -0
  325. package/server/tests/Feature/Http/Api/EntityControllerHelpersTest.php +150 -0
  326. package/server/tests/Feature/Http/Api/FuelTransactionControllerContractsTest.php +352 -0
  327. package/server/tests/Feature/Http/Api/GeofenceControllerContractsTest.php +364 -0
  328. package/server/tests/Feature/Http/Api/LabelControllerSubjectsTest.php +148 -0
  329. package/server/tests/Feature/Http/Api/OrderConfigControllerTest.php +112 -0
  330. package/server/tests/Feature/Http/Api/OrderControllerCreateTest.php +279 -0
  331. package/server/tests/Feature/Http/Api/OrderControllerIntegratedVendorTest.php +295 -0
  332. package/server/tests/Feature/Http/Api/OrderControllerLifecycleActionsTest.php +279 -0
  333. package/server/tests/Feature/Http/Api/OrderControllerLookupHelpersTest.php +207 -0
  334. package/server/tests/Feature/Http/Api/OrderControllerNearbyFiltersTest.php +282 -0
  335. package/server/tests/Feature/Http/Api/OrderControllerPersistenceHelpersTest.php +216 -0
  336. package/server/tests/Feature/Http/Api/OrderControllerQueryFiltersTest.php +184 -0
  337. package/server/tests/Feature/Http/Api/OrderControllerStartActivityTest.php +663 -0
  338. package/server/tests/Feature/Http/Api/PayloadControllerHelpersTest.php +134 -0
  339. package/server/tests/Feature/Http/Api/PlaceAndServiceQuoteSeamsTest.php +479 -0
  340. package/server/tests/Feature/Http/Api/PurchaseRateControllerHelpersTest.php +176 -0
  341. package/server/tests/Feature/Http/Api/PurchaseRateControllerOrderCreationTest.php +268 -0
  342. package/server/tests/Feature/Http/Api/SensorControllerCrudTest.php +239 -0
  343. package/server/tests/Feature/Http/Api/ServiceAreaControllerBorderAndSeamsTest.php +187 -0
  344. package/server/tests/Feature/Http/Api/ServiceQuoteControllerQueryTest.php +405 -0
  345. package/server/tests/Feature/Http/Api/ServiceQuoteControllerVendorBridgeTest.php +212 -0
  346. package/server/tests/Feature/Http/Api/ServiceRateControllerContractsTest.php +263 -0
  347. package/server/tests/Feature/Http/Api/SmallApiControllerHelpersTest.php +272 -0
  348. package/server/tests/Feature/Http/Api/TrackingNumberControllerHelpersTest.php +154 -0
  349. package/server/tests/Feature/Http/Api/TrackingStatusControllerHelpersTest.php +165 -0
  350. package/server/tests/Feature/Http/Api/VehicleControllerTrackingTest.php +483 -0
  351. package/server/tests/Feature/Http/Api/WorkOrderControllerContractsTest.php +326 -0
  352. package/server/tests/Feature/Http/Api/WorkOrderControllerHelpersTest.php +130 -0
  353. package/server/tests/Feature/Http/Api/ZoneControllerBordersAndSeamsTest.php +294 -0
  354. package/server/tests/Feature/Http/FleetOpsRouteRegistrationTest.php +189 -0
  355. package/server/tests/Feature/Http/Internal/AnalyticsControllerSeamsTest.php +53 -0
  356. package/server/tests/Feature/Http/Internal/ContactControllerContractsTest.php +240 -0
  357. package/server/tests/Feature/Http/Internal/ContactControllerCustomerGuardsTest.php +128 -0
  358. package/server/tests/Feature/Http/Internal/ContactControllerEndpointsTest.php +294 -0
  359. package/server/tests/Feature/Http/Internal/CustomerControllerCredentialsTest.php +196 -0
  360. package/server/tests/Feature/Http/Internal/DeviceControllerContractsTest.php +370 -0
  361. package/server/tests/Feature/Http/Internal/DeviceControllerExportAndFiltersTest.php +151 -0
  362. package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +1109 -0
  363. package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +233 -0
  364. package/server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php +710 -0
  365. package/server/tests/Feature/Http/Internal/DriverControllerHelpersTest.php +218 -0
  366. package/server/tests/Feature/Http/Internal/FleetAssignmentsAndTriggersTest.php +198 -0
  367. package/server/tests/Feature/Http/Internal/FleetOpsLookupControllerDbTest.php +90 -0
  368. package/server/tests/Feature/Http/Internal/HubControllerTest.php +206 -0
  369. package/server/tests/Feature/Http/Internal/IssueControllerContractsTest.php +404 -0
  370. package/server/tests/Feature/Http/Internal/IssueControllerExportImportTest.php +118 -0
  371. package/server/tests/Feature/Http/Internal/LiveControllerEndpointsTest.php +246 -0
  372. package/server/tests/Feature/Http/Internal/LiveControllerTest.php +778 -0
  373. package/server/tests/Feature/Http/Internal/MaintenanceScheduleControllerContractsTest.php +263 -0
  374. package/server/tests/Feature/Http/Internal/MaintenanceScheduleControllerSurfacesTest.php +214 -0
  375. package/server/tests/Feature/Http/Internal/MetricsControllerContractsTest.php +249 -0
  376. package/server/tests/Feature/Http/Internal/MetricsControllerHelpersTest.php +66 -0
  377. package/server/tests/Feature/Http/Internal/MorphControllerHelpersTest.php +125 -0
  378. package/server/tests/Feature/Http/Internal/NavigatorControllerHelpersTest.php +201 -0
  379. package/server/tests/Feature/Http/Internal/OrchestrationControllerContractsTest.php +928 -0
  380. package/server/tests/Feature/Http/Internal/OrchestrationControllerHelpersTest.php +214 -0
  381. package/server/tests/Feature/Http/Internal/OrchestrationControllerImportOrdersTest.php +340 -0
  382. package/server/tests/Feature/Http/Internal/OrderConfigControllerContractsTest.php +314 -0
  383. package/server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php +663 -0
  384. package/server/tests/Feature/Http/Internal/OrderControllerCapturePhotoTest.php +507 -0
  385. package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +1742 -0
  386. package/server/tests/Feature/Http/Internal/OrderControllerCreateRecordTest.php +488 -0
  387. package/server/tests/Feature/Http/Internal/OrderControllerHelperSeamsTest.php +298 -0
  388. package/server/tests/Feature/Http/Internal/OrderControllerHelpersTest.php +267 -0
  389. package/server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php +179 -0
  390. package/server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php +80 -0
  391. package/server/tests/Feature/Http/Internal/PaymentControllerPaymentsTest.php +154 -0
  392. package/server/tests/Feature/Http/Internal/PlaceControllerTest.php +252 -0
  393. package/server/tests/Feature/Http/Internal/PositionControllerReplayMetricsTest.php +163 -0
  394. package/server/tests/Feature/Http/Internal/ProofControllerContractsTest.php +219 -0
  395. package/server/tests/Feature/Http/Internal/SearchControllerEndpointTest.php +151 -0
  396. package/server/tests/Feature/Http/Internal/ServiceQuoteControllerHelpersTest.php +208 -0
  397. package/server/tests/Feature/Http/Internal/ServiceQuoteControllerQueryRecordTest.php +217 -0
  398. package/server/tests/Feature/Http/Internal/ServiceQuoteControllerVendorBridgeTest.php +239 -0
  399. package/server/tests/Feature/Http/Internal/ServiceQuotePreliminaryQueryTest.php +271 -0
  400. package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +133 -0
  401. package/server/tests/Feature/Http/Internal/SmallInternalControllersTest.php +180 -0
  402. package/server/tests/Feature/Http/Internal/TelematicControllerEndpointsTest.php +417 -0
  403. package/server/tests/Feature/Http/Internal/VehicleControllerEndpointsTest.php +361 -0
  404. package/server/tests/Feature/Http/Internal/VendorControllerEndpointsTest.php +203 -0
  405. package/server/tests/Feature/Http/Internal/WorkOrderAndConfigSurfacesTest.php +289 -0
  406. package/server/tests/Feature/Http/Internal/WorkOrderControllerContractsTest.php +199 -0
  407. package/server/tests/Feature/Http/TelematicWebhookIngestTest.php +343 -0
  408. package/server/tests/Feature/Http/Telematics/TelematicWebhookControllerProcessingTest.php +254 -0
  409. package/server/tests/FleetOpsSupportContractsTest.php +187 -0
  410. package/server/tests/FlespiProviderContractsTest.php +244 -0
  411. package/server/tests/FlowResourceTest.php +295 -0
  412. package/server/tests/FuelProviderConnectionControllerContractsTest.php +303 -0
  413. package/server/tests/FuelProviderImplementationTest.php +753 -0
  414. package/server/tests/FuelProviderRegistryTest.php +7 -9
  415. package/server/tests/FuelProviderResourceContractsTest.php +171 -0
  416. package/server/tests/FuelProviderTransactionControllerContractsTest.php +181 -0
  417. package/server/tests/GeofenceIntersectionServiceContractsTest.php +164 -0
  418. package/server/tests/HOSConstraintContractsTest.php +128 -0
  419. package/server/tests/HubAndGettingStartedContractsTest.php +158 -0
  420. package/server/tests/ImportContractsTest.php +191 -0
  421. package/server/tests/InternalCustomerControllerContractsTest.php +238 -0
  422. package/server/tests/InternalFleetControllerContractsTest.php +389 -0
  423. package/server/tests/InternalImportExportControllerContractsTest.php +289 -0
  424. package/server/tests/InternalMetricsControllerContractsTest.php +69 -0
  425. package/server/tests/InternalPlaceWorkOrderControllerContractsTest.php +207 -0
  426. package/server/tests/InternalServiceQuoteCheckoutControllerContractsTest.php +470 -0
  427. package/server/tests/InternalVendorControllerContractsTest.php +331 -0
  428. package/server/tests/InventoryTelemetryModelContractsTest.php +516 -0
  429. package/server/tests/IssueTimelineContractsTest.php +191 -0
  430. package/server/tests/LabelPdfRenderingTest.php +57 -0
  431. package/server/tests/LalamoveIntegrationContractsTest.php +167 -0
  432. package/server/tests/LiveOrderQueryTest.php +140 -19
  433. package/server/tests/LookupAndGeocoderControllerContractsTest.php +357 -0
  434. package/server/tests/MaintainableTraitContractsTest.php +245 -0
  435. package/server/tests/MaintenanceScheduleControllerContractsTest.php +101 -0
  436. package/server/tests/MaintenanceWarrantyContractsTest.php +237 -0
  437. package/server/tests/ManifestControllerContractsTest.php +310 -0
  438. package/server/tests/MetricsRegistryTest.php +596 -10
  439. package/server/tests/MiddlewareContractsTest.php +150 -0
  440. package/server/tests/ModelAccessorContractsTest.php +3201 -0
  441. package/server/tests/MorphControllerContractsTest.php +410 -0
  442. package/server/tests/NavigatorControllerContractsTest.php +206 -0
  443. package/server/tests/NotificationAndMailContractsTest.php +677 -0
  444. package/server/tests/ObserverContractsTest.php +1414 -0
  445. package/server/tests/OperationalAlgorithmContractsTest.php +852 -0
  446. package/server/tests/OperationsMonitorEndpointTest.php +2 -1
  447. package/server/tests/OrchestrationPayloadBuilderTest.php +87 -0
  448. package/server/tests/OrderActivityFlowRegressionTest.php +354 -7
  449. package/server/tests/OrderDispatchListenerContractsTest.php +577 -0
  450. package/server/tests/OrderFilterExecutionTest.php +244 -0
  451. package/server/tests/OrderQueuedClosureRegressionTest.php +2 -1
  452. package/server/tests/PaymentControllerContractsTest.php +199 -0
  453. package/server/tests/PointResolutionTest.php +54 -0
  454. package/server/tests/ProcessAllocationJobContractsTest.php +224 -0
  455. package/server/tests/ProofAndSensorControllerContractsTest.php +44 -0
  456. package/server/tests/ProviderContractsTest.php +409 -0
  457. package/server/tests/PublicApiControllerInputContractsTest.php +294 -0
  458. package/server/tests/QueueJobContractsTest.php +764 -0
  459. package/server/tests/ReportSchemaContractsTest.php +271 -0
  460. package/server/tests/RequestContractsTest.php +966 -0
  461. package/server/tests/RouteRegistrationExecutionTest.php +178 -0
  462. package/server/tests/RouteSequencingEngineTest.php +179 -0
  463. package/server/tests/RulesAndCastsTest.php +246 -0
  464. package/server/tests/SamsaraProviderContractsTest.php +272 -0
  465. package/server/tests/SearchControllerContractsTest.php +311 -0
  466. package/server/tests/ServiceAreaZoneApiShapeTest.php +8 -9
  467. package/server/tests/ServiceRateAlgorithmWeightVariablesTest.php +1 -1
  468. package/server/tests/ServiceRateRouteValidationTest.php +76 -0
  469. package/server/tests/SettingControllerContractsTest.php +410 -0
  470. package/server/tests/SmallControllerContractsTest.php +456 -0
  471. package/server/tests/SupportAnalyticsCustomerAuthContractsTest.php +221 -0
  472. package/server/tests/SupportJobAndAiCoverageTest.php +812 -0
  473. package/server/tests/SupportServiceContractsTest.php +287 -0
  474. package/server/tests/SupportValueObjectsTest.php +155 -0
  475. package/server/tests/TelematicControllerContractsTest.php +185 -0
  476. package/server/tests/TelematicProviderRegistryTest.php +167 -0
  477. package/server/tests/TelematicServiceHelperContractsTest.php +315 -0
  478. package/server/tests/TelematicWebhookControllerContractsTest.php +253 -0
  479. package/server/tests/TelematicsHardeningTest.php +666 -105
  480. package/server/tests/TrackingIntelligenceTest.php +709 -26
  481. package/server/tests/TrackingNumberTraitContractsTest.php +106 -0
  482. package/server/tests/Unit/Casts/SpatialCastBranchesTest.php +75 -0
  483. package/server/tests/Unit/Console/AssignCustomerRolesCommandTest.php +102 -0
  484. package/server/tests/Unit/Console/AssignDriverRolesCommandTest.php +113 -0
  485. package/server/tests/Unit/Console/Commands/FixLegacyOrderConfigsHelpersTest.php +108 -0
  486. package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +265 -0
  487. package/server/tests/Unit/Console/Commands/ReplayVehicleLocationsCommandTest.php +320 -0
  488. package/server/tests/Unit/Console/Commands/SimulateGeofenceEventsCommandTest.php +207 -0
  489. package/server/tests/Unit/Console/FixCommandsAndMaintenanceControllerTest.php +256 -0
  490. package/server/tests/Unit/Console/FleetOpsCommandsTest.php +198 -0
  491. package/server/tests/Unit/Console/ReplayVehicleLocationsCommandTest.php +252 -0
  492. package/server/tests/Unit/Console/SendMaintenanceRemindersTest.php +212 -0
  493. package/server/tests/Unit/Console/SimulateGeofenceEventsCommandTest.php +232 -0
  494. package/server/tests/Unit/Console/SyncTelematicsCommandTest.php +136 -0
  495. package/server/tests/Unit/Constraints/HOSConstraintTest.php +158 -0
  496. package/server/tests/Unit/Events/EventModelRecordTest.php +93 -0
  497. package/server/tests/Unit/Events/OrderDispatchFailedTest.php +34 -0
  498. package/server/tests/Unit/Exports/ExportCollectionsTest.php +142 -0
  499. package/server/tests/Unit/Exports/OperationalExportMappingTest.php +651 -0
  500. package/server/tests/Unit/Exports/VehicleExportTest.php +284 -0
  501. package/server/tests/Unit/Flow/EventFiringTest.php +95 -0
  502. package/server/tests/Unit/Http/Api/QueryWithRequestSeamsTest.php +198 -0
  503. package/server/tests/Unit/Http/Api/SensorInputTest.php +95 -0
  504. package/server/tests/Unit/Http/Filter/DeviceFilterTest.php +199 -0
  505. package/server/tests/Unit/Http/Filter/EmptyValueGuardsTest.php +66 -0
  506. package/server/tests/Unit/Http/Filter/FilterDateRangesTest.php +72 -0
  507. package/server/tests/Unit/Http/Filter/FuelProviderTransactionFilterTest.php +172 -0
  508. package/server/tests/Unit/Http/Filter/InternalRelationUuidLookupTest.php +135 -0
  509. package/server/tests/Unit/Http/Filter/IssueFilterTest.php +129 -0
  510. package/server/tests/Unit/Http/Filter/PartFilterTest.php +173 -0
  511. package/server/tests/Unit/Http/Internal/ControllerDelegationSeamsTest.php +136 -0
  512. package/server/tests/Unit/Http/Internal/ControllerExcelSeamsTest.php +89 -0
  513. package/server/tests/Unit/Http/Middleware/SetupDriverSessionTest.php +32 -0
  514. package/server/tests/Unit/Http/Requests/CreateFuelReportRequestTest.php +47 -0
  515. package/server/tests/Unit/Http/Requests/FleetActionRequestTest.php +80 -0
  516. package/server/tests/Unit/Http/Requests/RequestAuthorizationTest.php +50 -0
  517. package/server/tests/Unit/Http/Resources/CustomerResourceTest.php +108 -0
  518. package/server/tests/Unit/Http/Resources/EntityCustomerTypeTest.php +35 -0
  519. package/server/tests/Unit/Http/Resources/FleetResourceTest.php +102 -0
  520. package/server/tests/Unit/Http/Resources/IndexOrderResourceTest.php +210 -0
  521. package/server/tests/Unit/Http/Resources/IndexVehicleResourceTest.php +194 -0
  522. package/server/tests/Unit/Http/Resources/MaintenanceResourcesTest.php +182 -0
  523. package/server/tests/Unit/Http/Resources/OrderAndDriverResourceTest.php +121 -0
  524. package/server/tests/Unit/Http/Resources/OrderConfigResourceTest.php +47 -0
  525. package/server/tests/Unit/Http/Resources/PayloadResourceTest.php +111 -0
  526. package/server/tests/Unit/Http/Resources/RelationResourceFallbacksTest.php +32 -0
  527. package/server/tests/Unit/Http/Resources/ResourceRelationResolutionTest.php +55 -0
  528. package/server/tests/Unit/Http/Resources/TrackingNumberResourceTest.php +174 -0
  529. package/server/tests/Unit/Http/Resources/WaypointResourceTest.php +295 -0
  530. package/server/tests/Unit/Http/Resources/WorkOrderResourceTest.php +164 -0
  531. package/server/tests/Unit/Imports/ImportDelegationTest.php +151 -0
  532. package/server/tests/Unit/Integrations/Lalamove/LalamoveDeliveryStopTest.php +79 -0
  533. package/server/tests/Unit/Integrations/Lalamove/LalamoveLifecycleTest.php +211 -0
  534. package/server/tests/Unit/Integrations/Lalamove/LalamoveQuoteAndServiceOrderTest.php +381 -0
  535. package/server/tests/Unit/Integrations/Lalamove/LalamoveServiceTypeTest.php +51 -0
  536. package/server/tests/Unit/Integrations/LalamoveQuotationTest.php +194 -0
  537. package/server/tests/Unit/Integrations/LalamoveResponseHandlingTest.php +58 -0
  538. package/server/tests/Unit/Jobs/GeofenceDwellAndBulkNotifyTest.php +183 -0
  539. package/server/tests/Unit/Jobs/ReplayPositionsTest.php +57 -0
  540. package/server/tests/Unit/Jobs/SimulateWaypointReachedTest.php +92 -0
  541. package/server/tests/Unit/Listeners/HandleGeofenceEnteredTest.php +256 -0
  542. package/server/tests/Unit/Listeners/HandleOrderDispatchFailedListenerTest.php +101 -0
  543. package/server/tests/Unit/Listeners/HandleOrderDispatchedConcreteTest.php +203 -0
  544. package/server/tests/Unit/Listeners/HandleOrderDispatchedHelpersTest.php +186 -0
  545. package/server/tests/Unit/Listeners/HandleUserRemovedFromCompanyTest.php +65 -0
  546. package/server/tests/Unit/Listeners/ListenerDelegationSeamsTest.php +83 -0
  547. package/server/tests/Unit/Models/AssetTest.php +469 -0
  548. package/server/tests/Unit/Models/ContactCustomerUserTest.php +370 -0
  549. package/server/tests/Unit/Models/ContactTest.php +577 -0
  550. package/server/tests/Unit/Models/DeviceEventTest.php +481 -0
  551. package/server/tests/Unit/Models/DeviceLastPositionDefaultTest.php +128 -0
  552. package/server/tests/Unit/Models/DriverModelHelpersTest.php +268 -0
  553. package/server/tests/Unit/Models/DriverTest.php +401 -0
  554. package/server/tests/Unit/Models/EntityTest.php +171 -0
  555. package/server/tests/Unit/Models/EquipmentTest.php +434 -0
  556. package/server/tests/Unit/Models/FleetTest.php +153 -0
  557. package/server/tests/Unit/Models/FuelProviderTransactionTest.php +82 -0
  558. package/server/tests/Unit/Models/FuelReportImportAndLabelsTest.php +193 -0
  559. package/server/tests/Unit/Models/ImportRowFallbackSeamsTest.php +100 -0
  560. package/server/tests/Unit/Models/IntegratedVendorLifecycleTest.php +203 -0
  561. package/server/tests/Unit/Models/IntegratedVendorTest.php +135 -0
  562. package/server/tests/Unit/Models/IssueTest.php +160 -0
  563. package/server/tests/Unit/Models/MaintenanceScheduleTest.php +319 -0
  564. package/server/tests/Unit/Models/MaintenanceTest.php +277 -0
  565. package/server/tests/Unit/Models/MaintenanceWorkOrderImportTest.php +161 -0
  566. package/server/tests/Unit/Models/ManifestStopTest.php +121 -0
  567. package/server/tests/Unit/Models/ManifestTest.php +163 -0
  568. package/server/tests/Unit/Models/ModelLookupFallbackSeamsTest.php +149 -0
  569. package/server/tests/Unit/Models/OrderConfigTest.php +341 -0
  570. package/server/tests/Unit/Models/OrderDynamicContractsTest.php +190 -0
  571. package/server/tests/Unit/Models/OrderPayloadConfigAndDistanceTest.php +426 -0
  572. package/server/tests/Unit/Models/OrderPayloadRouteTest.php +227 -0
  573. package/server/tests/Unit/Models/OrderPurchaseAndDriversTest.php +188 -0
  574. package/server/tests/Unit/Models/OrderTest.php +917 -0
  575. package/server/tests/Unit/Models/PartTest.php +399 -0
  576. package/server/tests/Unit/Models/PayloadTest.php +710 -0
  577. package/server/tests/Unit/Models/PayloadWaypointsAndEntitiesTest.php +317 -0
  578. package/server/tests/Unit/Models/PlaceCreationAndImportTest.php +384 -0
  579. package/server/tests/Unit/Models/PlaceGeocodingResultsTest.php +253 -0
  580. package/server/tests/Unit/Models/PlaceSharedAndGeocodingTest.php +271 -0
  581. package/server/tests/Unit/Models/PlaceTest.php +589 -0
  582. package/server/tests/Unit/Models/PositionTest.php +49 -0
  583. package/server/tests/Unit/Models/RouteTest.php +55 -0
  584. package/server/tests/Unit/Models/SensorReadingsTest.php +168 -0
  585. package/server/tests/Unit/Models/SensorTest.php +284 -0
  586. package/server/tests/Unit/Models/ServiceAreaGeometryTest.php +129 -0
  587. package/server/tests/Unit/Models/ServiceAreaTest.php +125 -0
  588. package/server/tests/Unit/Models/ServiceQuoteTest.php +617 -0
  589. package/server/tests/Unit/Models/ServiceRateFeeTest.php +73 -0
  590. package/server/tests/Unit/Models/ServiceRatePersistenceAndServicabilityTest.php +428 -0
  591. package/server/tests/Unit/Models/ServiceRateTest.php +533 -0
  592. package/server/tests/Unit/Models/SmallModelSurfacesTest.php +294 -0
  593. package/server/tests/Unit/Models/TrackingNumberTest.php +199 -0
  594. package/server/tests/Unit/Models/TrackingStatusTest.php +131 -0
  595. package/server/tests/Unit/Models/VehicleDeviceTest.php +115 -0
  596. package/server/tests/Unit/Models/VehiclePositionsAndImportTest.php +220 -0
  597. package/server/tests/Unit/Models/VehicleTest.php +266 -0
  598. package/server/tests/Unit/Models/VendorRelationsAndImportTest.php +107 -0
  599. package/server/tests/Unit/Models/WarrantyTest.php +187 -0
  600. package/server/tests/Unit/Models/WaypointTest.php +366 -0
  601. package/server/tests/Unit/Models/WorkOrderTest.php +272 -0
  602. package/server/tests/Unit/Models/ZoneTest.php +83 -0
  603. package/server/tests/Unit/Notifications/MonitoringNotificationsTest.php +136 -0
  604. package/server/tests/Unit/Notifications/OrderAssignedChannelsTest.php +53 -0
  605. package/server/tests/Unit/Notifications/OrderDispatchedChannelsTest.php +81 -0
  606. package/server/tests/Unit/Notifications/OrderFailedChannelsTest.php +73 -0
  607. package/server/tests/Unit/Notifications/OrderPingChannelsTest.php +69 -0
  608. package/server/tests/Unit/Notifications/TerminalOrderNotificationsTest.php +185 -0
  609. package/server/tests/Unit/Observers/FleetOpsCompanyUserAndServiceAreaObserversTest.php +133 -0
  610. package/server/tests/Unit/Observers/ObserverHelperExecutionTest.php +135 -0
  611. package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +267 -0
  612. package/server/tests/Unit/Observers/PurchaseRateObserverAndOrderJobsTest.php +169 -0
  613. package/server/tests/Unit/Orchestration/Engines/DriverAssignmentEngineTest.php +393 -0
  614. package/server/tests/Unit/Orchestration/Engines/VroomOrchestrationEngineLifecycleTest.php +397 -0
  615. package/server/tests/Unit/Orchestration/OrchestrationEngineRegistryTest.php +45 -0
  616. package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +253 -0
  617. package/server/tests/Unit/Providers/FleetOpsAiRegistrationTest.php +59 -0
  618. package/server/tests/Unit/Rules/ResolvableVehicleTest.php +85 -0
  619. package/server/tests/Unit/Support/Ai/AssetStatusCapabilityQueriesTest.php +120 -0
  620. package/server/tests/Unit/Support/Ai/Capabilities/CreateOrderPreviewCapabilityTest.php +385 -0
  621. package/server/tests/Unit/Support/Ai/Capabilities/OrderInsightsCapabilityTest.php +226 -0
  622. package/server/tests/Unit/Support/Ai/Capabilities/SearchResourcesCapabilityTest.php +237 -0
  623. package/server/tests/Unit/Support/AiCapabilityPermissionsTest.php +124 -0
  624. package/server/tests/Unit/Support/AlgoEdgeBranchesTest.php +24 -0
  625. package/server/tests/Unit/Support/Analytics/GeofenceViolationsTest.php +109 -0
  626. package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +205 -0
  627. package/server/tests/Unit/Support/Analytics/MaintenanceOverviewTest.php +236 -0
  628. package/server/tests/Unit/Support/Analytics/OnTimeDeliveryAndTopDriversTest.php +250 -0
  629. package/server/tests/Unit/Support/CreateOrderPreviewDraftingTest.php +235 -0
  630. package/server/tests/Unit/Support/CustomerAuthTest.php +132 -0
  631. package/server/tests/Unit/Support/FuelEfficiencyAndCanceledHandlersTest.php +164 -0
  632. package/server/tests/Unit/Support/FuelProviderRegistryResolutionTest.php +51 -0
  633. package/server/tests/Unit/Support/FuelProviderServiceMatchingTest.php +280 -0
  634. package/server/tests/Unit/Support/GeocodingInjectedClientTest.php +268 -0
  635. package/server/tests/Unit/Support/GeocodingLocateTest.php +32 -0
  636. package/server/tests/Unit/Support/GeofenceIntersectionServiceTest.php +188 -0
  637. package/server/tests/Unit/Support/GettingStartedTest.php +164 -0
  638. package/server/tests/Unit/Support/GuardReturnSeamsTest.php +172 -0
  639. package/server/tests/Unit/Support/IntegratedVendorsResolverTest.php +250 -0
  640. package/server/tests/Unit/Support/IssuesInsightsAndAllocationJobTest.php +156 -0
  641. package/server/tests/Unit/Support/MagicAccessorSeamsTest.php +54 -0
  642. package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +215 -0
  643. package/server/tests/Unit/Support/Metrics/AvgOrderValueMetricTest.php +308 -0
  644. package/server/tests/Unit/Support/Metrics/CountMetricsTest.php +280 -0
  645. package/server/tests/Unit/Support/Metrics/FuelCostsMetricTest.php +102 -0
  646. package/server/tests/Unit/Support/Metrics/MetricQuerySeamsTest.php +129 -0
  647. package/server/tests/Unit/Support/Metrics/OrdersScheduledMetricTest.php +146 -0
  648. package/server/tests/Unit/Support/MoreQuerySeamsTest.php +410 -0
  649. package/server/tests/Unit/Support/OperationalQueryDistributionTest.php +152 -0
  650. package/server/tests/Unit/Support/OperationsPulseTest.php +130 -0
  651. package/server/tests/Unit/Support/OptimizeOrderRouteCapabilityTest.php +147 -0
  652. package/server/tests/Unit/Support/OrchestrationPayloadBuilderTest.php +135 -0
  653. package/server/tests/Unit/Support/OrchestrationRouteAndVehicleTasksTest.php +254 -0
  654. package/server/tests/Unit/Support/OsrmProofAndIssueFilterTest.php +247 -0
  655. package/server/tests/Unit/Support/PetroAppFuelProviderTest.php +165 -0
  656. package/server/tests/Unit/Support/PlaceSearchRankingTest.php +191 -0
  657. package/server/tests/Unit/Support/QuerySeamsTest.php +309 -0
  658. package/server/tests/Unit/Support/ResolvesOrderServiceStopsDbTest.php +303 -0
  659. package/server/tests/Unit/Support/ResolvesOrderServiceStopsTest.php +382 -0
  660. package/server/tests/Unit/Support/SkipAndFallbackGuardsTest.php +70 -0
  661. package/server/tests/Unit/Support/TelematicCredentialResolutionTest.php +31 -0
  662. package/server/tests/Unit/Support/TelematicServiceTest.php +256 -0
  663. package/server/tests/Unit/Support/Telematics/Providers/AbstractProviderTest.php +154 -0
  664. package/server/tests/Unit/Support/Telematics/Providers/AfaqyProviderTest.php +191 -0
  665. package/server/tests/Unit/Support/Telematics/Providers/AfaqyProviderTransportTest.php +138 -0
  666. package/server/tests/Unit/Support/Telematics/Providers/GeotabProviderTest.php +329 -0
  667. package/server/tests/Unit/Support/Telematics/Providers/ProviderTransportSeamsTest.php +133 -0
  668. package/server/tests/Unit/Support/Telematics/Providers/SafeeProviderTest.php +487 -0
  669. package/server/tests/Unit/Support/Telematics/TelematicProviderRegistryTest.php +39 -0
  670. package/server/tests/Unit/Support/Telematics/TelematicServiceLifecycleTest.php +711 -0
  671. package/server/tests/Unit/Support/Telematics/TelematicServiceTelemetryTest.php +364 -0
  672. package/server/tests/Unit/Support/UtilsAdditionalTest.php +256 -0
  673. package/server/tests/Unit/Support/UtilsGeoMatrixAndVendorTest.php +382 -0
  674. package/server/tests/Unit/Tracking/TrackingContextTest.php +114 -0
  675. package/server/tests/Unit/Tracking/TrackingIntelligenceAndContextTest.php +477 -0
  676. package/server/tests/Unit/Tracking/TrackingProviderManagerFallbacksTest.php +208 -0
  677. package/server/tests/Unit/Traits/HasTrackingNumberTest.php +303 -0
  678. package/server/tests/Unit/Traits/PayloadAccessorsTest.php +150 -0
  679. package/server/tests/UtilsContractsTest.php +244 -0
  680. package/server/tests/VehicleControllerHelperContractsTest.php +488 -0
  681. package/server/tests/VehicleDataParsingTest.php +45 -0
  682. package/server/tests/VehicleDriverAssignmentTest.php +8 -9
  683. package/server/tests/VehicleResourceContractsTest.php +313 -0
  684. package/translations/bg-bg.yaml +2 -0
  685. package/translations/en-us.yaml +2 -0
  686. package/translations/es-pa.yaml +2 -0
  687. package/translations/fr-fr.yaml +2 -0
  688. package/translations/mn-mn.yaml +2 -0
  689. package/translations/pt-br.yaml +2 -0
  690. package/translations/ru-ru.yaml +2 -0
  691. package/server/src/Http/Controllers/Api/v1/OrganizationController.php +0 -45
  692. package/server/tests/DeviceFilterTest.php +0 -45
  693. package/server/tests/LiveControllerViewportTest.php +0 -60
@@ -0,0 +1,2745 @@
1
+ <?php
2
+
3
+ use Fleetbase\FleetOps\Http\Resources\Internal\v1\IntegratedVendorFacilitator as InternalIntegratedVendorFacilitatorResource;
4
+ use Fleetbase\FleetOps\Http\Resources\Internal\v1\OrderConfig as InternalOrderConfigResource;
5
+ use Fleetbase\FleetOps\Http\Resources\Internal\v1\Vehicle as InternalVehicleResource;
6
+ use Fleetbase\FleetOps\Http\Resources\Internal\v1\Waypoint as InternalWaypointResource;
7
+ use Fleetbase\FleetOps\Http\Resources\v1\Contact as ContactResource;
8
+ use Fleetbase\FleetOps\Http\Resources\v1\CurrentJob as CurrentJobResource;
9
+ use Fleetbase\FleetOps\Http\Resources\v1\Customer as CustomerResource;
10
+ use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
11
+ use Fleetbase\FleetOps\Http\Resources\v1\Device as DeviceResource;
12
+ use Fleetbase\FleetOps\Http\Resources\v1\Entity as EntityResource;
13
+ use Fleetbase\FleetOps\Http\Resources\v1\Fleet as FleetResource;
14
+ use Fleetbase\FleetOps\Http\Resources\v1\Index\Customer as IndexCustomerResource;
15
+ use Fleetbase\FleetOps\Http\Resources\v1\Index\Driver as IndexDriverResource;
16
+ use Fleetbase\FleetOps\Http\Resources\v1\Index\Facilitator as IndexFacilitatorResource;
17
+ use Fleetbase\FleetOps\Http\Resources\v1\Index\Order as IndexOrderResource;
18
+ use Fleetbase\FleetOps\Http\Resources\v1\Index\Payload as IndexPayloadResource;
19
+ use Fleetbase\FleetOps\Http\Resources\v1\Index\Place as IndexPlaceResource;
20
+ use Fleetbase\FleetOps\Http\Resources\v1\Index\TrackingNumber as IndexTrackingNumberResource;
21
+ use Fleetbase\FleetOps\Http\Resources\v1\Index\Vehicle as IndexVehicleResource;
22
+ use Fleetbase\FleetOps\Http\Resources\v1\Issue as IssueResource;
23
+ use Fleetbase\FleetOps\Http\Resources\v1\Maintenance as MaintenanceResource;
24
+ use Fleetbase\FleetOps\Http\Resources\v1\MaintenanceSchedule as MaintenanceScheduleResource;
25
+ use Fleetbase\FleetOps\Http\Resources\v1\Orchestrator\Order as OrchestratorOrderResource;
26
+ use Fleetbase\FleetOps\Http\Resources\v1\Order as OrderResource;
27
+ use Fleetbase\FleetOps\Http\Resources\v1\OrderConfig as OrderConfigResource;
28
+ use Fleetbase\FleetOps\Http\Resources\v1\ParentFleet as ParentFleetResource;
29
+ use Fleetbase\FleetOps\Http\Resources\v1\Payload as PayloadResource;
30
+ use Fleetbase\FleetOps\Http\Resources\v1\Place as PlaceResource;
31
+ use Fleetbase\FleetOps\Http\Resources\v1\Proof as ProofResource;
32
+ use Fleetbase\FleetOps\Http\Resources\v1\PurchaseRate as PurchaseRateResource;
33
+ use Fleetbase\FleetOps\Http\Resources\v1\Sensor as SensorResource;
34
+ use Fleetbase\FleetOps\Http\Resources\v1\ServiceArea as ServiceAreaResource;
35
+ use Fleetbase\FleetOps\Http\Resources\v1\ServiceQuote as ServiceQuoteResource;
36
+ use Fleetbase\FleetOps\Http\Resources\v1\ServiceQuoteItem as ServiceQuoteItemResource;
37
+ use Fleetbase\FleetOps\Http\Resources\v1\ServiceRate as ServiceRateResource;
38
+ use Fleetbase\FleetOps\Http\Resources\v1\SubFleet as SubFleetResource;
39
+ use Fleetbase\FleetOps\Http\Resources\v1\TrackingNumber as TrackingNumberResource;
40
+ use Fleetbase\FleetOps\Http\Resources\v1\TrackingStatus as TrackingStatusResource;
41
+ use Fleetbase\FleetOps\Http\Resources\v1\VehicleWithoutDriver as VehicleWithoutDriverResource;
42
+ use Fleetbase\FleetOps\Http\Resources\v1\Vendor as VendorResource;
43
+ use Fleetbase\FleetOps\Http\Resources\v1\Waypoint as WaypointResource;
44
+ use Fleetbase\FleetOps\Http\Resources\v1\WorkOrder as WorkOrderResource;
45
+ use Fleetbase\FleetOps\Http\Resources\v1\Zone as ZoneResource;
46
+ use Fleetbase\FleetOps\Models\Contact as ContactModel;
47
+ use Illuminate\Http\Request;
48
+ use Illuminate\Support\Arr;
49
+ use Illuminate\Support\Collection;
50
+
51
+ class FleetOpsCompactResourceRouteFixture
52
+ {
53
+ public function __construct(private string $uri)
54
+ {
55
+ }
56
+
57
+ public function uri(): string
58
+ {
59
+ return $this->uri;
60
+ }
61
+ }
62
+
63
+ class FleetOpsCompactResourceRequest extends Request
64
+ {
65
+ public function isArray(string $key): bool
66
+ {
67
+ return is_array($this->input($key));
68
+ }
69
+
70
+ public function array(string $key): array
71
+ {
72
+ $value = $this->input($key);
73
+
74
+ return is_array($value) ? $value : [];
75
+ }
76
+ }
77
+
78
+ class FleetOpsCompactResourceFixture implements ArrayAccess
79
+ {
80
+ public bool $wasRecentlyCreated = false;
81
+
82
+ public function __construct(private array $attributes, private array $loaded = [])
83
+ {
84
+ }
85
+
86
+ public function __get(string $key): mixed
87
+ {
88
+ return $this->attributes[$key] ?? $this->loaded[$key] ?? null;
89
+ }
90
+
91
+ public function __isset(string $key): bool
92
+ {
93
+ return array_key_exists($key, $this->attributes) || array_key_exists($key, $this->loaded);
94
+ }
95
+
96
+ public function offsetExists(mixed $offset): bool
97
+ {
98
+ return array_key_exists((string) $offset, $this->attributes);
99
+ }
100
+
101
+ public function offsetGet(mixed $offset): mixed
102
+ {
103
+ return $this->attributes[(string) $offset] ?? null;
104
+ }
105
+
106
+ public function offsetSet(mixed $offset, mixed $value): void
107
+ {
108
+ $this->attributes[(string) $offset] = $value;
109
+ }
110
+
111
+ public function offsetUnset(mixed $offset): void
112
+ {
113
+ unset($this->attributes[(string) $offset]);
114
+ }
115
+
116
+ public function relationLoaded(string $relationship): bool
117
+ {
118
+ return array_key_exists($relationship, $this->loaded);
119
+ }
120
+
121
+ public function loadMissing(string|array $relationship): self
122
+ {
123
+ return $this;
124
+ }
125
+
126
+ public function load(string|array $relationship): self
127
+ {
128
+ return $this;
129
+ }
130
+
131
+ public function getOriginal(string $key): mixed
132
+ {
133
+ return $this->attributes['original'][$key] ?? $this->attributes[$key] ?? null;
134
+ }
135
+
136
+ public function getRelation(string $relationship): mixed
137
+ {
138
+ return $this->loaded[$relationship] ?? null;
139
+ }
140
+
141
+ public function setRelation(string $relationship, mixed $value): self
142
+ {
143
+ $this->loaded[$relationship] = $value;
144
+
145
+ return $this;
146
+ }
147
+
148
+ public function withCustomFields(array $payload): array
149
+ {
150
+ return $payload;
151
+ }
152
+
153
+ public function __call(string $method, array $arguments): mixed
154
+ {
155
+ if (array_key_exists($method, $this->loaded) && $this->loaded[$method] instanceof Collection) {
156
+ return new class($this->loaded[$method]) {
157
+ public function __construct(private Collection $collection)
158
+ {
159
+ }
160
+
161
+ public function count(): int
162
+ {
163
+ return $this->collection->count();
164
+ }
165
+ };
166
+ }
167
+
168
+ if (array_key_exists($method, $this->loaded)) {
169
+ return $this->loaded[$method];
170
+ }
171
+
172
+ if ($method === 'getAdhocDistance') {
173
+ return $this->attributes['adhoc_distance'] ?? 0;
174
+ }
175
+
176
+ if (array_key_exists($method, $this->attributes)) {
177
+ return $this->attributes[$method];
178
+ }
179
+
180
+ return null;
181
+ }
182
+ }
183
+
184
+ class FleetOpsCompactOrderTrackerFixture
185
+ {
186
+ public function toArray(): array
187
+ {
188
+ return ['state' => 'enroute', 'distance' => 4200];
189
+ }
190
+
191
+ public function eta(): array
192
+ {
193
+ return ['seconds' => 540, 'formatted' => '9 minutes'];
194
+ }
195
+ }
196
+
197
+ class TestFleetOpsIndexVehicleResource extends IndexVehicleResource
198
+ {
199
+ protected function assignedOrdersCount(): int
200
+ {
201
+ return 6;
202
+ }
203
+
204
+ protected function currentOrderReference(): ?string
205
+ {
206
+ return 'TRK-INDEX';
207
+ }
208
+
209
+ protected function speedLabel(): string
210
+ {
211
+ return '54 km/h';
212
+ }
213
+
214
+ protected function headingLabel(): string
215
+ {
216
+ return '270 deg';
217
+ }
218
+ }
219
+
220
+ class TestFleetOpsIndexDriverResource extends IndexDriverResource
221
+ {
222
+ protected function assignedOrdersCount(): int
223
+ {
224
+ return 5;
225
+ }
226
+
227
+ protected function currentOrderReference(): ?string
228
+ {
229
+ return 'TRK-DRIVER';
230
+ }
231
+ }
232
+
233
+ class TestFleetOpsCustomerResource extends CustomerResource
234
+ {
235
+ protected function getOrdersCount(Request $request): int
236
+ {
237
+ return $request->boolean('has_orders') ? 7 : 0;
238
+ }
239
+
240
+ protected function buildCompanyPayload(): ?array
241
+ {
242
+ return [
243
+ 'id' => 'company_public',
244
+ 'name' => 'Acme Logistics',
245
+ 'currency' => 'USD',
246
+ 'country' => 'US',
247
+ 'phone' => '+15550000000',
248
+ ];
249
+ }
250
+ }
251
+
252
+ function fleetopsCompactResourceRequest(bool $internal): Request
253
+ {
254
+ $uri = $internal ? 'api/int/v1/fleet-ops/resources/resource_123' : 'api/v1/fleet-ops/resources/resource_123';
255
+ $request = FleetOpsCompactResourceRequest::create('/' . $uri, 'GET');
256
+
257
+ $request->setRouteResolver(fn () => new FleetOpsCompactResourceRouteFixture($uri));
258
+ app()->instance('request', $request);
259
+
260
+ return $request;
261
+ }
262
+
263
+ function fleetopsCompactResourceFixture(array $attributes = [], array $loaded = []): FleetOpsCompactResourceFixture
264
+ {
265
+ return new FleetOpsCompactResourceFixture(array_merge([
266
+ 'id' => 101,
267
+ 'uuid' => 'fixture-uuid',
268
+ 'public_id' => 'fixture_public',
269
+ 'updated_at' => '2026-07-02 10:00:00',
270
+ 'created_at' => '2026-01-02 09:00:00',
271
+ ], $attributes), $loaded);
272
+ }
273
+
274
+ test('customer resource serializes public and internal customer payloads', function () {
275
+ $place = fleetopsCompactResourceFixture([
276
+ 'public_id' => 'place_public',
277
+ 'address' => '123 Harbor Way',
278
+ ]);
279
+ $addresses = collect([
280
+ fleetopsCompactResourceFixture([
281
+ 'public_id' => 'place_home',
282
+ 'address' => '123 Harbor Way',
283
+ ]),
284
+ ]);
285
+ $fixture = fleetopsCompactResourceFixture([
286
+ 'id' => 55,
287
+ 'uuid' => 'customer-uuid',
288
+ 'user_uuid' => 'user-uuid',
289
+ 'company_uuid' => 'company-uuid',
290
+ 'public_id' => 'contact_public',
291
+ 'internal_id' => 'CUST-55',
292
+ 'name' => 'Jane Customer',
293
+ 'title' => 'Operations Lead',
294
+ 'photo_url' => 'https://cdn.test/customer.png',
295
+ 'email' => 'jane@example.test',
296
+ 'phone' => '+15551234567',
297
+ 'token' => 'customer-token',
298
+ 'meta' => ['tier' => 'gold'],
299
+ 'slug' => 'jane-customer',
300
+ 'created_at' => '2026-01-01 10:00:00',
301
+ 'updated_at' => '2026-07-01 10:00:00',
302
+ ], [
303
+ 'place' => $place,
304
+ 'places' => $addresses,
305
+ ]);
306
+
307
+ $publicRequest = fleetopsCompactResourceRequest(false);
308
+ $publicRequest->merge(['has_orders' => true]);
309
+
310
+ $publicPayload = (new TestFleetOpsCustomerResource($fixture))->resolve($publicRequest);
311
+ $internalPayload = (new TestFleetOpsCustomerResource($fixture))->resolve(fleetopsCompactResourceRequest(true));
312
+
313
+ expect($publicPayload)->toMatchArray([
314
+ 'id' => 'customer_public',
315
+ 'internal_id' => 'CUST-55',
316
+ 'name' => 'Jane Customer',
317
+ 'title' => 'Operations Lead',
318
+ 'photo_url' => 'https://cdn.test/customer.png',
319
+ 'email' => 'jane@example.test',
320
+ 'phone' => '+15551234567',
321
+ 'address' => '123 Harbor Way',
322
+ 'token' => 'customer-token',
323
+ 'orders_count' => 7,
324
+ 'company' => [
325
+ 'id' => 'company_public',
326
+ 'name' => 'Acme Logistics',
327
+ 'currency' => 'USD',
328
+ 'country' => 'US',
329
+ 'phone' => '+15550000000',
330
+ ],
331
+ 'meta' => ['tier' => 'gold'],
332
+ 'slug' => 'jane-customer',
333
+ ])
334
+ ->and($publicPayload)->not->toHaveKeys(['uuid', 'user_uuid', 'company_uuid', 'public_id'])
335
+ ->and($publicPayload['addresses'])->toHaveCount(1)
336
+ ->and($internalPayload)->toMatchArray([
337
+ 'id' => 55,
338
+ 'uuid' => 'customer-uuid',
339
+ 'user_uuid' => 'user-uuid',
340
+ 'company_uuid' => 'company-uuid',
341
+ 'public_id' => 'contact_public',
342
+ 'orders_count' => 0,
343
+ ]);
344
+ });
345
+
346
+ test('service rate resource serializes pricing rules for internal and webhook consumers', function () {
347
+ $request = fleetopsCompactResourceRequest(true);
348
+ $rate = fleetopsCompactResourceFixture([
349
+ 'service_area_uuid' => 'area-uuid',
350
+ 'zone_uuid' => 'zone-uuid',
351
+ 'order_config_uuid' => 'config-uuid',
352
+ 'serviceArea' => (object) ['name' => 'Central'],
353
+ 'zone' => (object) ['name' => 'Downtown'],
354
+ 'service_name' => 'Same Day',
355
+ 'service_type' => 'delivery',
356
+ 'base_fee' => 12.5,
357
+ 'rate_calculation_method' => 'flat',
358
+ 'per_meter_flat_rate_fee' => 0.02,
359
+ 'per_meter_unit' => 1000,
360
+ 'per_km_flat_rate_fee' => 2.5,
361
+ 'max_distance_unit' => 'km',
362
+ 'max_distance' => 50,
363
+ 'rateFees' => [],
364
+ 'parcelFees' => [],
365
+ 'algorithm' => 'distance',
366
+ 'has_cod_fee' => 1,
367
+ 'cod_calculation_method' => 'flat',
368
+ 'cod_flat_fee' => 3,
369
+ 'cod_percent' => 0,
370
+ 'has_peak_hours_fee' => 'true',
371
+ 'peak_hours_calculation_method' => 'percent',
372
+ 'peak_hours_flat_fee' => 0,
373
+ 'peak_hours_percent' => 15,
374
+ 'peak_hours_start' => '17:00',
375
+ 'peak_hours_end' => '20:00',
376
+ 'currency' => 'SGD',
377
+ 'duration_terms' => 'same_day',
378
+ 'estimated_days' => 1,
379
+ ], [
380
+ // Loaded rather than a plain attribute: internal consumers get the full
381
+ // order config through whenLoaded, and only the public id otherwise.
382
+ 'orderConfig' => (object) ['public_id' => 'config_public'],
383
+ ]);
384
+
385
+ $payload = (new ServiceRateResource($rate))->resolve($request);
386
+ $webhook = (new ServiceRateResource($rate))->toWebhookPayload();
387
+
388
+ expect($payload)->toMatchArray([
389
+ 'id' => 101,
390
+ 'uuid' => 'fixture-uuid',
391
+ 'service_area_uuid' => 'area-uuid',
392
+ 'zone_uuid' => 'zone-uuid',
393
+ 'order_config_uuid' => 'config-uuid',
394
+ 'public_id' => 'fixture_public',
395
+ 'service_area_name' => 'Central',
396
+ 'zone_name' => 'Downtown',
397
+ 'service_name' => 'Same Day',
398
+ 'service_type' => 'delivery',
399
+ 'base_fee' => 12.5,
400
+ 'rate_calculation_method' => 'flat',
401
+ 'per_meter_flat_rate_fee' => 0.02,
402
+ 'per_meter_unit' => 1000,
403
+ 'max_distance_unit' => 'km',
404
+ 'max_distance' => 50,
405
+ 'algorithm' => 'distance',
406
+ 'has_cod_fee' => true,
407
+ 'cod_calculation_method' => 'flat',
408
+ 'cod_flat_fee' => 3,
409
+ 'has_peak_hours_fee' => true,
410
+ 'peak_hours_calculation_method' => 'percent',
411
+ 'peak_hours_percent' => 15,
412
+ 'currency' => 'SGD',
413
+ 'duration_terms' => 'same_day',
414
+ 'estimated_days' => 1,
415
+ ])
416
+ ->and($payload['order_config']->public_id)->toBe('config_public')
417
+ ->and($webhook)->toMatchArray([
418
+ 'id' => 'fixture_public',
419
+ 'service_name' => 'Same Day',
420
+ 'service_type' => 'delivery',
421
+ 'base_fee' => 12.5,
422
+ 'per_km_flat_rate_fee' => 2.5,
423
+ 'has_cod_fee' => true,
424
+ 'has_peak_hours_fee' => true,
425
+ 'peak_hours_start' => '17:00',
426
+ 'peak_hours_end' => '20:00',
427
+ 'currency' => 'SGD',
428
+ 'estimated_days' => 1,
429
+ ]);
430
+ });
431
+
432
+ test('entity resource exposes package details and webhook payloads', function () {
433
+ $request = fleetopsCompactResourceRequest(true);
434
+ $entity = fleetopsCompactResourceFixture([
435
+ 'photo_uuid' => 'photo-uuid',
436
+ 'customer_uuid' => null,
437
+ 'customer_type' => null,
438
+ 'supplier_uuid' => 'supplier-uuid',
439
+ 'destination_uuid' => 'place-uuid',
440
+ 'payload_uuid' => 'payload-uuid',
441
+ 'internal_id' => 'ENT-101',
442
+ 'name' => 'Parcel 101',
443
+ 'type' => 'parcel',
444
+ 'trackingNumber' => (object) [
445
+ 'tracking_number' => 'TN-101',
446
+ 'barcode' => 'barcode-data',
447
+ 'qr_code' => 'qr-data',
448
+ ],
449
+ 'description' => 'Fragile parcel',
450
+ 'photo_url' => 'https://cdn.test/entity.png',
451
+ 'length' => 10,
452
+ 'width' => 8,
453
+ 'height' => 6,
454
+ 'dimensions_unit' => 'cm',
455
+ 'weight' => 2.4,
456
+ 'weight_unit' => 'kg',
457
+ 'declared_value' => 75,
458
+ 'price' => 80,
459
+ 'sale_price' => 70,
460
+ 'sku' => 'SKU-101',
461
+ 'currency' => 'SGD',
462
+ 'meta' => ['fragile' => true],
463
+ 'destination' => (object) ['public_id' => 'place_public'],
464
+ ]);
465
+
466
+ $payload = (new EntityResource($entity))->resolve($request);
467
+ $webhook = (new EntityResource($entity))->toWebhookPayload();
468
+
469
+ expect($payload)->toMatchArray([
470
+ 'id' => 101,
471
+ 'uuid' => 'fixture-uuid',
472
+ 'photo_uuid' => 'photo-uuid',
473
+ 'public_id' => 'fixture_public',
474
+ 'supplier_uuid' => 'supplier-uuid',
475
+ 'destination_uuid' => 'place-uuid',
476
+ 'payload_uuid' => 'payload-uuid',
477
+ 'internal_id' => 'ENT-101',
478
+ 'name' => 'Parcel 101',
479
+ 'type' => 'parcel',
480
+ 'description' => 'Fragile parcel',
481
+ 'photo_url' => 'https://cdn.test/entity.png',
482
+ 'dimensions_unit' => 'cm',
483
+ 'weight_unit' => 'kg',
484
+ 'currency' => 'SGD',
485
+ 'meta' => ['fragile' => true],
486
+ ])
487
+ ->and($webhook)->toMatchArray([
488
+ 'id' => 'fixture_public',
489
+ 'internal_id' => 'ENT-101',
490
+ 'name' => 'Parcel 101',
491
+ 'type' => 'parcel',
492
+ 'destination' => 'place_public',
493
+ 'description' => 'Fragile parcel',
494
+ 'photo_url' => 'https://cdn.test/entity.png',
495
+ 'length' => 10,
496
+ 'width' => 8,
497
+ 'height' => 6,
498
+ 'declared_value' => 75,
499
+ 'sale_price' => 70,
500
+ 'sku' => 'SKU-101',
501
+ 'currency' => 'SGD',
502
+ 'meta' => ['fragile' => true],
503
+ ]);
504
+ });
505
+
506
+ test('index vehicle resource returns compact fleet state metadata', function () {
507
+ $request = fleetopsCompactResourceRequest(true);
508
+ $vehicle = fleetopsCompactResourceFixture([
509
+ 'company_uuid' => 'company-uuid',
510
+ 'vendor_uuid' => 'vendor-uuid',
511
+ 'photo_uuid' => 'photo-uuid',
512
+ 'internal_id' => 'VEH-INDEX',
513
+ 'display_name' => 'Index Truck',
514
+ 'driver_name' => 'Jane Driver',
515
+ 'plate_number' => 'IDX-101',
516
+ 'serial_number' => 'SER-101',
517
+ 'fuel_card_number' => 'FUEL-101',
518
+ 'vin' => 'VIN-101',
519
+ 'make' => 'Ford',
520
+ 'model' => 'Transit',
521
+ 'year' => 2026,
522
+ 'photo_url' => 'https://cdn.test/index-truck.png',
523
+ 'status' => 'in_service',
524
+ 'location' => null,
525
+ 'heading' => 270,
526
+ 'altitude' => 20,
527
+ 'speed' => 54,
528
+ 'online' => 1,
529
+ ]);
530
+
531
+ $payload = (new TestFleetOpsIndexVehicleResource($vehicle))->resolve($request);
532
+
533
+ expect($payload)->toMatchArray([
534
+ 'id' => 101,
535
+ 'uuid' => 'fixture-uuid',
536
+ 'public_id' => 'fixture_public',
537
+ 'company_uuid' => 'company-uuid',
538
+ 'vendor_uuid' => 'vendor-uuid',
539
+ 'photo_uuid' => 'photo-uuid',
540
+ 'internal_id' => 'VEH-INDEX',
541
+ 'display_name' => 'Index Truck',
542
+ 'driver_name' => 'Jane Driver',
543
+ 'plate_number' => 'IDX-101',
544
+ 'status' => 'in_service',
545
+ 'heading' => 270,
546
+ 'altitude' => 20,
547
+ 'speed' => 54,
548
+ 'online' => true,
549
+ 'assigned_orders_count' => 6,
550
+ 'meta' => [
551
+ '_index_resource' => true,
552
+ 'current_order_reference' => 'TRK-INDEX',
553
+ 'location_coordinates' => '0 0',
554
+ 'speed_label' => '54 km/h',
555
+ 'heading_label' => '270 deg',
556
+ 'status_label' => 'In Service',
557
+ ],
558
+ ]);
559
+ });
560
+
561
+ test('index order resource returns compact order table payloads', function () {
562
+ $request = fleetopsCompactResourceRequest(true);
563
+ $order = fleetopsCompactResourceFixture([
564
+ 'internal_id' => 'ORD-INDEX',
565
+ 'company_uuid' => 'company-uuid',
566
+ 'payload_uuid' => 'payload-uuid',
567
+ 'driver_assigned_uuid' => 'driver-uuid',
568
+ 'vehicle_assigned_uuid' => 'vehicle-uuid',
569
+ 'customer_uuid' => 'customer-uuid',
570
+ 'customer_type' => 'Fleetbase\\Models\\Contact',
571
+ 'facilitator_uuid' => 'facilitator-uuid',
572
+ 'facilitator_type' => 'Fleetbase\\Models\\Vendor',
573
+ 'tracking_number_uuid' => 'tracking-uuid',
574
+ 'order_config_uuid' => 'config-uuid',
575
+ 'trackingNumber' => (object) ['tracking_number' => 'TN-INDEX'],
576
+ 'type' => 'transport',
577
+ 'status' => 'created',
578
+ 'adhoc' => 1,
579
+ 'dispatched' => 0,
580
+ 'has_driver_assigned' => true,
581
+ 'is_scheduled' => false,
582
+ 'transaction_amount' => 99.95,
583
+ 'transaction_currency' => 'SGD',
584
+ 'scheduled_at' => '2026-07-27 09:00:00',
585
+ 'dispatched_at' => null,
586
+ 'started_at' => null,
587
+ ], [
588
+ 'orderConfig' => fleetopsCompactResourceFixture([
589
+ 'uuid' => 'config-uuid',
590
+ 'public_id' => 'config_public',
591
+ 'name' => 'Delivery',
592
+ 'key' => 'delivery',
593
+ ]),
594
+ 'trackingStatuses' => new Collection([
595
+ (object) ['status' => 'Created', 'code' => 'created'],
596
+ ]),
597
+ ]);
598
+
599
+ $payload = (new IndexOrderResource($order))->resolve($request);
600
+
601
+ expect($payload)->toMatchArray([
602
+ 'id' => 101,
603
+ 'uuid' => 'fixture-uuid',
604
+ 'public_id' => 'fixture_public',
605
+ 'internal_id' => 'ORD-INDEX',
606
+ 'company_uuid' => 'company-uuid',
607
+ 'payload_uuid' => 'payload-uuid',
608
+ 'driver_assigned_uuid' => 'driver-uuid',
609
+ 'vehicle_assigned_uuid' => 'vehicle-uuid',
610
+ 'customer_uuid' => 'customer-uuid',
611
+ 'tracking_number_uuid' => 'tracking-uuid',
612
+ 'order_config_uuid' => 'config-uuid',
613
+ 'tracking' => 'TN-INDEX',
614
+ 'order_config' => [
615
+ 'uuid' => 'config-uuid',
616
+ 'public_id' => 'config_public',
617
+ 'name' => 'Delivery',
618
+ 'key' => 'delivery',
619
+ ],
620
+ 'latest_status' => 'Created',
621
+ 'latest_status_code' => 'created',
622
+ 'type' => 'transport',
623
+ 'status' => 'created',
624
+ 'adhoc' => true,
625
+ 'dispatched' => false,
626
+ 'has_driver_assigned' => true,
627
+ 'is_scheduled' => false,
628
+ 'transaction_amount' => 99.95,
629
+ 'currency' => 'SGD',
630
+ 'scheduled_at' => '2026-07-27 09:00:00',
631
+ 'meta' => ['_index_resource' => true],
632
+ ]);
633
+ });
634
+
635
+ test('order resource serializes internal order identifiers tracker data and timing state', function () {
636
+ $request = fleetopsCompactResourceRequest(true);
637
+ $request->query->set('with_tracker_data', '1');
638
+ $request->query->set('with_eta', '1');
639
+
640
+ $trackingNumber = fleetopsCompactResourceFixture([
641
+ 'tracking_number' => 'TN-ORDER',
642
+ 'barcode' => 'barcode-order',
643
+ 'qr_code' => 'qr-order',
644
+ ]);
645
+ $order = fleetopsCompactResourceFixture([
646
+ 'internal_id' => 'ORD-RESOURCE',
647
+ 'company_uuid' => 'company-uuid',
648
+ 'transaction_uuid' => 'transaction-uuid',
649
+ 'customer_uuid' => 'customer-uuid',
650
+ 'customer_type' => ContactModel::class,
651
+ 'facilitator_uuid' => 'facilitator-uuid',
652
+ 'facilitator_type' => 'Fleetbase\\FleetOps\\Models\\Vendor',
653
+ 'payload_uuid' => 'payload-uuid',
654
+ 'route_uuid' => 'route-uuid',
655
+ 'purchase_rate_uuid' => 'purchase-rate-uuid',
656
+ 'tracking_number_uuid' => 'tracking-number-uuid',
657
+ 'driver_assigned_uuid' => 'driver-uuid',
658
+ 'vehicle_assigned_uuid' => 'vehicle-uuid',
659
+ 'has_driver_assigned' => true,
660
+ 'is_scheduled' => true,
661
+ 'order_config_uuid' => 'order-config-uuid',
662
+ 'orderConfig' => fleetopsCompactResourceFixture(['public_id' => 'order_config_public']),
663
+ 'payload' => null,
664
+ 'trackingNumber' => $trackingNumber,
665
+ 'comments' => collect(),
666
+ 'files' => collect(),
667
+ 'purchaseRate' => null,
668
+ 'notes' => 'Internal resource order.',
669
+ 'type' => 'transport',
670
+ 'status' => 'dispatched',
671
+ 'pod_method' => 'qr_scan',
672
+ 'pod_required' => 1,
673
+ 'dispatched' => 1,
674
+ 'started' => 0,
675
+ 'adhoc' => 1,
676
+ 'adhoc_distance' => 1234,
677
+ 'distance' => 9876,
678
+ 'time' => 654,
679
+ 'transaction_amount' => 199.95,
680
+ 'transaction_currency' => 'SGD',
681
+ 'tracker' => new FleetOpsCompactOrderTrackerFixture(),
682
+ 'meta' => ['priority' => 'high'],
683
+ 'dispatched_at' => '2026-07-26 10:00:00',
684
+ 'started_at' => null,
685
+ 'scheduled_at' => '2026-07-27 09:00:00',
686
+ ], [
687
+ 'orderConfig' => fleetopsCompactResourceFixture(['public_id' => 'order_config_public']),
688
+ ]);
689
+
690
+ $payload = (new OrderResource($order))->resolve($request);
691
+
692
+ expect($payload)->toMatchArray([
693
+ 'id' => 101,
694
+ 'uuid' => 'fixture-uuid',
695
+ 'public_id' => 'fixture_public',
696
+ 'internal_id' => 'ORD-RESOURCE',
697
+ 'company_uuid' => 'company-uuid',
698
+ 'transaction_uuid' => 'transaction-uuid',
699
+ 'customer_uuid' => 'customer-uuid',
700
+ 'facilitator_uuid' => 'facilitator-uuid',
701
+ 'payload_uuid' => 'payload-uuid',
702
+ 'route_uuid' => 'route-uuid',
703
+ 'purchase_rate_uuid' => 'purchase-rate-uuid',
704
+ 'tracking_number_uuid' => 'tracking-number-uuid',
705
+ 'driver_assigned_uuid' => 'driver-uuid',
706
+ 'vehicle_assigned_uuid' => 'vehicle-uuid',
707
+ 'has_driver_assigned' => true,
708
+ 'is_scheduled' => true,
709
+ 'order_config_uuid' => 'order-config-uuid',
710
+ 'tracking' => 'TN-ORDER',
711
+ 'barcode' => 'barcode-order',
712
+ 'qr_code' => 'qr-order',
713
+ 'notes' => 'Internal resource order.',
714
+ 'type' => 'transport',
715
+ 'status' => 'dispatched',
716
+ 'pod_method' => 'qr_scan',
717
+ 'pod_required' => true,
718
+ 'dispatched' => true,
719
+ 'started' => false,
720
+ 'adhoc' => true,
721
+ 'adhoc_distance' => 1234,
722
+ 'distance' => 9876,
723
+ 'time' => 654,
724
+ 'transaction_amount' => 199.95,
725
+ 'currency' => 'SGD',
726
+ 'tracker_data' => ['state' => 'enroute', 'distance' => 4200],
727
+ 'eta' => ['seconds' => 540, 'formatted' => '9 minutes'],
728
+ 'meta' => ['priority' => 'high'],
729
+ 'dispatched_at' => '2026-07-26 10:00:00',
730
+ 'started_at' => null,
731
+ 'scheduled_at' => '2026-07-27 09:00:00',
732
+ ]);
733
+ });
734
+
735
+ test('order resource keeps public payload on public identifiers and applies customer facilitator type labels', function () {
736
+ $request = fleetopsCompactResourceRequest(false);
737
+ $order = fleetopsCompactResourceFixture([
738
+ 'internal_id' => 'ORD-PUBLIC',
739
+ 'customer_type' => ContactModel::class,
740
+ 'facilitator_type' => 'Fleetbase\\FleetOps\\Models\\Vendor',
741
+ 'orderConfig' => fleetopsCompactResourceFixture(['public_id' => 'config_public']),
742
+ 'payload' => null,
743
+ 'trackingNumber' => null,
744
+ 'trackingStatuses' => collect(),
745
+ 'comments' => collect(),
746
+ 'files' => collect(),
747
+ 'purchaseRate' => null,
748
+ 'notes' => 'Public resource order.',
749
+ 'type' => 'delivery',
750
+ 'status' => 'created',
751
+ 'pod_required' => false,
752
+ 'dispatched' => false,
753
+ 'started' => false,
754
+ 'adhoc' => false,
755
+ 'distance' => 0,
756
+ 'time' => 0,
757
+ 'transaction_amount' => null,
758
+ 'transaction_currency' => null,
759
+ 'meta' => ['channel' => 'public-api'],
760
+ ]);
761
+ $resource = new OrderResource($order);
762
+ $payload = $resource->resolve($request);
763
+
764
+ expect($payload['id'])->toBe('fixture_public')
765
+ ->and($payload)->not->toHaveKeys(['uuid', 'public_id', 'company_uuid', 'transaction_uuid', 'tracking', 'barcode', 'qr_code'])
766
+ ->and($payload)->toMatchArray([
767
+ 'internal_id' => 'ORD-PUBLIC',
768
+ 'order_config' => 'config_public',
769
+ 'notes' => 'Public resource order.',
770
+ 'type' => 'delivery',
771
+ 'status' => 'created',
772
+ 'pod_required' => false,
773
+ 'dispatched' => false,
774
+ 'started' => false,
775
+ 'adhoc' => false,
776
+ 'meta' => ['channel' => 'public-api'],
777
+ ])
778
+ ->and($resource->setCustomerType(['id' => 'customer_public']))->toMatchArray([
779
+ 'id' => 'customer_public',
780
+ 'type' => 'customer-contact',
781
+ 'customer_type' => 'customer-contact',
782
+ ])
783
+ ->and($resource->setFacilitatorType(['id' => 'vendor_public']))->toMatchArray([
784
+ 'id' => 'vendor_public',
785
+ 'type' => 'facilitator-vendor',
786
+ 'facilitator_type' => 'facilitator-vendor',
787
+ ])
788
+ ->and($resource->setCustomerType([]))->toBe([])
789
+ ->and($resource->setFacilitatorType(null))->toBeNull();
790
+ });
791
+
792
+ test('issue resource serializes internal issue details and webhook identifiers', function () {
793
+ $request = fleetopsCompactResourceRequest(true);
794
+ $issue = fleetopsCompactResourceFixture([
795
+ 'driver_uuid' => 'driver-uuid',
796
+ 'company_uuid' => 'company-uuid',
797
+ 'vehicle_uuid' => 'vehicle-uuid',
798
+ 'order_uuid' => null,
799
+ 'assigned_to_uuid' => 'assignee-uuid',
800
+ 'reported_by_uuid' => 'reporter-uuid',
801
+ 'driver_name' => 'Jane Driver',
802
+ 'vehicle_name' => 'Truck 101',
803
+ 'vehicle_id' => 'VEH-101',
804
+ 'assignee_name' => 'Dispatcher',
805
+ 'assignee_id' => 'USR-101',
806
+ 'reporter_name' => 'Operator',
807
+ 'reporter_id' => 'USR-102',
808
+ 'issue_id' => 'ISS-101',
809
+ 'title' => 'Door latch',
810
+ 'report' => 'Rear door latch sticks.',
811
+ 'priority' => 'high',
812
+ 'meta' => ['source' => 'inspection'],
813
+ 'type' => 'vehicle',
814
+ 'category' => 'body',
815
+ 'tags' => ['safety'],
816
+ 'status' => 'open',
817
+ 'location' => null,
818
+ 'resolved_at' => null,
819
+ 'reportedBy' => (object) ['public_id' => 'reporter_public'],
820
+ 'assignedTo' => (object) ['public_id' => 'assignee_public'],
821
+ 'driver' => (object) ['public_id' => 'driver_public'],
822
+ 'vehicle' => (object) ['public_id' => 'vehicle_public'],
823
+ ]);
824
+
825
+ $payload = (new IssueResource($issue))->resolve($request);
826
+ $webhook = (new IssueResource($issue))->toWebhookPayload();
827
+
828
+ expect($payload)->toMatchArray([
829
+ 'id' => 101,
830
+ 'uuid' => 'fixture-uuid',
831
+ 'public_id' => 'fixture_public',
832
+ 'driver_uuid' => 'driver-uuid',
833
+ 'company_uuid' => 'company-uuid',
834
+ 'vehicle_uuid' => 'vehicle-uuid',
835
+ 'assigned_to_uuid' => 'assignee-uuid',
836
+ 'reported_by_uuid' => 'reporter-uuid',
837
+ 'driver_name' => 'Jane Driver',
838
+ 'vehicle_name' => 'Truck 101',
839
+ 'issue_id' => 'ISS-101',
840
+ 'title' => 'Door latch',
841
+ 'report' => 'Rear door latch sticks.',
842
+ 'priority' => 'high',
843
+ 'meta' => ['source' => 'inspection'],
844
+ 'type' => 'vehicle',
845
+ 'category' => 'body',
846
+ 'tags' => ['safety'],
847
+ 'status' => 'open',
848
+ ])
849
+ ->and($webhook)->toMatchArray([
850
+ 'id' => 'fixture_public',
851
+ 'reporter' => 'reporter_public',
852
+ 'assignee' => 'assignee_public',
853
+ 'driver' => 'driver_public',
854
+ 'vehicle' => 'vehicle_public',
855
+ 'issue_id' => 'ISS-101',
856
+ 'priority' => 'high',
857
+ 'category' => 'body',
858
+ 'status' => 'open',
859
+ ]);
860
+ });
861
+
862
+ test('maintenance resource serializes work order costs and schedule state', function () {
863
+ $request = fleetopsCompactResourceRequest(true);
864
+ $maintenance = fleetopsCompactResourceFixture([
865
+ 'company_uuid' => 'company-uuid',
866
+ 'work_order_uuid' => 'work-order-uuid',
867
+ 'created_by_uuid' => 'creator-uuid',
868
+ 'updated_by_uuid' => 'updater-uuid',
869
+ 'maintainable_uuid' => 'vehicle-uuid',
870
+ 'maintainable_type' => 'Fleetbase\\FleetOps\\Models\\Vehicle',
871
+ 'performed_by_uuid' => 'vendor-uuid',
872
+ 'performed_by_type' => 'Fleetbase\\FleetOps\\Models\\Vendor',
873
+ 'type' => 'repair',
874
+ 'status' => 'completed',
875
+ 'priority' => 'medium',
876
+ 'odometer' => 10000,
877
+ 'engine_hours' => 525,
878
+ 'summary' => 'Brake service',
879
+ 'notes' => 'Replaced pads.',
880
+ 'line_items' => [['name' => 'Pads', 'amount' => 120]],
881
+ 'labor_cost' => 80,
882
+ 'parts_cost' => 120,
883
+ 'tax' => 14,
884
+ 'total_cost' => 214,
885
+ 'currency' => 'SGD',
886
+ 'attachments' => ['invoice.pdf'],
887
+ 'meta' => ['shop' => 'North'],
888
+ 'slug' => 'brake-service',
889
+ 'maintainable_name' => 'Truck 101',
890
+ 'work_order_subject' => 'WO-101',
891
+ 'performed_by_name' => 'Vendor One',
892
+ 'duration_hours' => 2,
893
+ 'is_overdue' => false,
894
+ 'days_until_due' => 0,
895
+ 'cost_breakdown' => ['labor' => 80, 'parts' => 120],
896
+ 'scheduled_at' => '2026-07-20 09:00:00',
897
+ 'started_at' => '2026-07-20 10:00:00',
898
+ 'completed_at' => '2026-07-20 12:00:00',
899
+ ]);
900
+
901
+ $payload = (new MaintenanceResource($maintenance))->resolve($request);
902
+
903
+ expect($payload)->toMatchArray([
904
+ 'id' => 101,
905
+ 'uuid' => 'fixture-uuid',
906
+ 'company_uuid' => 'company-uuid',
907
+ 'work_order_uuid' => 'work-order-uuid',
908
+ 'maintainable_uuid' => 'vehicle-uuid',
909
+ 'maintainable_type' => 'fleet-ops:vehicle',
910
+ 'performed_by_uuid' => 'vendor-uuid',
911
+ 'performed_by_type' => 'fleet-ops:vendor',
912
+ 'type' => 'repair',
913
+ 'status' => 'completed',
914
+ 'priority' => 'medium',
915
+ 'summary' => 'Brake service',
916
+ 'line_items' => [['name' => 'Pads', 'amount' => 120]],
917
+ 'labor_cost' => 80,
918
+ 'parts_cost' => 120,
919
+ 'tax' => 14,
920
+ 'total_cost' => 214,
921
+ 'currency' => 'SGD',
922
+ 'attachments' => ['invoice.pdf'],
923
+ 'maintainable_name' => 'Truck 101',
924
+ 'work_order_subject' => 'WO-101',
925
+ 'performed_by_name' => 'Vendor One',
926
+ 'duration_hours' => 2,
927
+ 'is_overdue' => false,
928
+ 'cost_breakdown' => ['labor' => 80, 'parts' => 120],
929
+ 'completed_at' => '2026-07-20 12:00:00',
930
+ ]);
931
+ });
932
+
933
+ test('maintenance schedule resource serializes interval and next due thresholds', function () {
934
+ $request = fleetopsCompactResourceRequest(true);
935
+ $schedule = fleetopsCompactResourceFixture([
936
+ 'company_uuid' => 'company-uuid',
937
+ 'created_by_uuid' => 'creator-uuid',
938
+ 'updated_by_uuid' => 'updater-uuid',
939
+ 'subject_uuid' => 'vehicle-uuid',
940
+ 'subject_type' => 'Fleetbase\\FleetOps\\Models\\Vehicle',
941
+ 'default_assignee_uuid' => 'vendor-uuid',
942
+ 'default_assignee_type' => 'Fleetbase\\FleetOps\\Models\\Vendor',
943
+ 'name' => 'Oil Change',
944
+ 'type' => 'preventive',
945
+ 'status' => 'active',
946
+ 'interval_method' => 'distance',
947
+ 'interval_type' => 'recurring',
948
+ 'interval_value' => 5000,
949
+ 'interval_unit' => 'km',
950
+ 'interval_distance' => 5000,
951
+ 'interval_engine_hours' => 250,
952
+ 'last_service_odometer' => 10000,
953
+ 'last_service_engine_hours' => 400,
954
+ 'last_service_date' => '2026-06-01',
955
+ 'next_due_date' => '2026-08-01',
956
+ 'next_due_odometer' => 15000,
957
+ 'next_due_engine_hours' => 650,
958
+ 'default_priority' => 'medium',
959
+ 'instructions' => 'Replace oil and filter.',
960
+ 'reminder_offsets' => [7, 1],
961
+ 'meta' => ['template' => true],
962
+ 'slug' => 'oil-change',
963
+ 'subject_name' => 'Truck 101',
964
+ 'default_assignee_name' => 'Vendor One',
965
+ 'last_triggered_at' => '2026-06-01 12:00:00',
966
+ ]);
967
+
968
+ $payload = (new MaintenanceScheduleResource($schedule))->resolve($request);
969
+
970
+ expect($payload)->toMatchArray([
971
+ 'id' => 101,
972
+ 'uuid' => 'fixture-uuid',
973
+ 'company_uuid' => 'company-uuid',
974
+ 'subject_uuid' => 'vehicle-uuid',
975
+ 'subject_type' => 'fleet-ops:vehicle',
976
+ 'default_assignee_uuid' => 'vendor-uuid',
977
+ 'default_assignee_type' => 'fleet-ops:vendor',
978
+ 'name' => 'Oil Change',
979
+ 'type' => 'preventive',
980
+ 'status' => 'active',
981
+ 'interval_method' => 'distance',
982
+ 'interval_type' => 'recurring',
983
+ 'interval_value' => 5000,
984
+ 'next_due_odometer' => 15000,
985
+ 'default_priority' => 'medium',
986
+ 'instructions' => 'Replace oil and filter.',
987
+ 'reminder_offsets' => [7, 1],
988
+ 'subject_name' => 'Truck 101',
989
+ 'default_assignee_name' => 'Vendor One',
990
+ 'last_triggered_at' => '2026-06-01 12:00:00',
991
+ ]);
992
+ });
993
+
994
+ test('place resource serializes address data for resources and webhooks', function () {
995
+ $request = fleetopsCompactResourceRequest(true);
996
+ $place = fleetopsCompactResourceFixture([
997
+ 'company_uuid' => 'company-uuid',
998
+ 'owner_uuid' => null,
999
+ 'owner_type' => null,
1000
+ 'internal_id' => 'PLC-101',
1001
+ 'name' => 'Warehouse',
1002
+ 'location' => null,
1003
+ 'address' => '1 Fleet Way',
1004
+ 'address_html' => '<p>1 Fleet Way</p>',
1005
+ 'avatar_url' => 'https://cdn.test/place.png',
1006
+ 'original' => ['avatar_url' => 'avatar-token'],
1007
+ 'street1' => '1 Fleet Way',
1008
+ 'street2' => 'Dock 4',
1009
+ 'city' => 'Singapore',
1010
+ 'province' => 'Central',
1011
+ 'postal_code' => '100001',
1012
+ 'neighborhood' => 'Marina',
1013
+ 'district' => 'Downtown',
1014
+ 'building' => 'Tower',
1015
+ 'security_access_code' => '1234',
1016
+ 'country' => 'SG',
1017
+ 'country_name' => 'Singapore',
1018
+ 'phone' => '+6555550000',
1019
+ 'type' => 'warehouse',
1020
+ 'meta' => ['dock' => 4],
1021
+ 'eta' => '10 minutes',
1022
+ 'latitude' => 1.29,
1023
+ 'longitude' => 103.85,
1024
+ ]);
1025
+
1026
+ $payload = (new PlaceResource($place))->resolve($request);
1027
+ $webhook = (new PlaceResource($place))->toWebhookPayload();
1028
+
1029
+ expect($payload)->toMatchArray([
1030
+ 'id' => 101,
1031
+ 'uuid' => 'fixture-uuid',
1032
+ 'company_uuid' => 'company-uuid',
1033
+ 'name' => 'Warehouse',
1034
+ 'address' => '1 Fleet Way',
1035
+ 'address_html' => '<p>1 Fleet Way</p>',
1036
+ 'avatar_value' => 'avatar-token',
1037
+ 'street1' => '1 Fleet Way',
1038
+ 'city' => 'Singapore',
1039
+ 'country_name' => 'Singapore',
1040
+ 'phone' => '+6555550000',
1041
+ 'type' => 'warehouse',
1042
+ 'meta' => ['dock' => 4],
1043
+ 'eta' => '10 minutes',
1044
+ ])
1045
+ ->and($webhook)->toMatchArray([
1046
+ 'id' => 'fixture_public',
1047
+ 'internal_id' => 'PLC-101',
1048
+ 'name' => 'Warehouse',
1049
+ 'latitude' => 1.29,
1050
+ 'longitude' => 103.85,
1051
+ 'street1' => '1 Fleet Way',
1052
+ 'street2' => 'Dock 4',
1053
+ 'city' => 'Singapore',
1054
+ 'country' => 'SG',
1055
+ 'phone' => '+6555550000',
1056
+ 'type' => 'warehouse',
1057
+ 'meta' => ['dock' => 4],
1058
+ ]);
1059
+ });
1060
+
1061
+ test('device resource serializes telematics device status fields', function () {
1062
+ $request = fleetopsCompactResourceRequest(true);
1063
+ $device = fleetopsCompactResourceFixture([
1064
+ 'company_uuid' => 'company-uuid',
1065
+ 'telematic_uuid' => 'telematic-uuid',
1066
+ 'attachable_uuid' => 'vehicle-uuid',
1067
+ 'attachable_type' => 'Fleetbase\\FleetOps\\Models\\Vehicle',
1068
+ 'warranty_uuid' => 'warranty-uuid',
1069
+ 'photo_uuid' => 'photo-uuid',
1070
+ 'type' => 'gps',
1071
+ 'device_id' => 'DEV-101',
1072
+ 'internal_id' => 'INT-DEV-101',
1073
+ 'imei' => 'imei-101',
1074
+ 'imsi' => 'imsi-101',
1075
+ 'firmware_version' => '1.2.3',
1076
+ 'provider' => 'safee',
1077
+ 'name' => 'Tracker 101',
1078
+ 'model' => 'T100',
1079
+ 'location' => ['lat' => 1.29, 'lng' => 103.85],
1080
+ 'manufacturer' => 'TrackerCo',
1081
+ 'serial_number' => 'SER-DEV-101',
1082
+ 'last_position' => ['speed' => 42],
1083
+ 'installation_date' => '2026-01-01',
1084
+ 'last_maintenance_date' => '2026-06-01',
1085
+ 'meta' => ['vehicle' => 'Truck 101'],
1086
+ 'data' => ['battery' => 87],
1087
+ 'options' => ['interval' => 60],
1088
+ 'online' => true,
1089
+ 'status' => 'active',
1090
+ 'data_frequency' => 60,
1091
+ 'notes' => 'Mounted under dash.',
1092
+ 'last_online_at' => '2026-07-02 10:00:00',
1093
+ 'warranty_name' => 'Standard',
1094
+ 'telematic_name' => 'Safee',
1095
+ 'is_online' => true,
1096
+ 'attached_to_name' => 'Truck 101',
1097
+ 'connection_status' => 'connected',
1098
+ 'photo_url' => 'https://cdn.test/device.png',
1099
+ 'sensors_count' => 2,
1100
+ 'slug' => 'tracker-101',
1101
+ ]);
1102
+
1103
+ $payload = (new DeviceResource($device))->resolve($request);
1104
+
1105
+ expect($payload)->toMatchArray([
1106
+ 'id' => 101,
1107
+ 'uuid' => 'fixture-uuid',
1108
+ 'company_uuid' => 'company-uuid',
1109
+ 'telematic_uuid' => 'telematic-uuid',
1110
+ 'attachable_uuid' => 'vehicle-uuid',
1111
+ 'attachable_type' => 'fleet-ops:vehicle',
1112
+ 'type' => 'gps',
1113
+ 'device_id' => 'DEV-101',
1114
+ 'internal_id' => 'INT-DEV-101',
1115
+ 'imei' => 'imei-101',
1116
+ 'firmware_version' => '1.2.3',
1117
+ 'provider' => 'safee',
1118
+ 'name' => 'Tracker 101',
1119
+ 'model' => 'T100',
1120
+ 'manufacturer' => 'TrackerCo',
1121
+ 'serial_number' => 'SER-DEV-101',
1122
+ 'meta' => ['vehicle' => 'Truck 101'],
1123
+ 'data' => ['battery' => 87],
1124
+ 'options' => ['interval' => 60],
1125
+ 'online' => true,
1126
+ 'status' => 'active',
1127
+ 'sensors_count' => 2,
1128
+ 'slug' => 'tracker-101',
1129
+ ]);
1130
+ });
1131
+
1132
+ test('vendor resource serializes vendor identity and webhook address fields', function () {
1133
+ $request = fleetopsCompactResourceRequest(true);
1134
+ $place = fleetopsCompactResourceFixture([
1135
+ 'address' => '1 Vendor Way',
1136
+ 'street1' => '1 Vendor Way',
1137
+ ]);
1138
+ $vendor = fleetopsCompactResourceFixture([
1139
+ 'place_uuid' => 'place-uuid',
1140
+ 'connect_company_uuid' => 'connect-company-uuid',
1141
+ 'logo_uuid' => 'logo-uuid',
1142
+ 'type_uuid' => 'type-uuid',
1143
+ 'internal_id' => 'VEN-101',
1144
+ 'business_id' => 'BRN-101',
1145
+ 'name' => 'Vendor One',
1146
+ 'email' => 'vendor@example.test',
1147
+ 'phone' => '+6555551111',
1148
+ 'logo_url' => 'https://cdn.test/logo.png',
1149
+ 'place' => $place,
1150
+ 'places' => new Collection(),
1151
+ 'personnels' => new Collection(),
1152
+ 'country' => 'SG',
1153
+ 'type' => 'maintenance',
1154
+ 'meta' => ['tier' => 'gold'],
1155
+ 'status' => 'active',
1156
+ 'slug' => 'vendor-one',
1157
+ 'website_url' => 'https://vendor.test',
1158
+ ]);
1159
+
1160
+ $payload = (new VendorResource($vendor))->resolve($request);
1161
+ $webhook = (new VendorResource($vendor))->toWebhookPayload();
1162
+
1163
+ expect($payload)->toMatchArray([
1164
+ 'id' => 101,
1165
+ 'uuid' => 'fixture-uuid',
1166
+ 'public_id' => 'fixture_public',
1167
+ 'place_uuid' => 'place-uuid',
1168
+ 'connect_company_uuid' => 'connect-company-uuid',
1169
+ 'logo_uuid' => 'logo-uuid',
1170
+ 'type_uuid' => 'type-uuid',
1171
+ 'internal_id' => 'VEN-101',
1172
+ 'business_id' => 'BRN-101',
1173
+ 'name' => 'Vendor One',
1174
+ 'email' => 'vendor@example.test',
1175
+ 'phone' => '+6555551111',
1176
+ 'photo_url' => 'https://cdn.test/logo.png',
1177
+ 'address' => '1 Vendor Way',
1178
+ 'address_street' => '1 Vendor Way',
1179
+ 'country' => 'SG',
1180
+ 'type' => 'maintenance',
1181
+ 'status' => 'active',
1182
+ 'website_url' => 'https://vendor.test',
1183
+ ])
1184
+ ->and($webhook)->toMatchArray([
1185
+ 'id' => 'fixture_public',
1186
+ 'internal_id' => 'VEN-101',
1187
+ 'name' => 'Vendor One',
1188
+ 'email' => 'vendor@example.test',
1189
+ 'phone' => '+6555551111',
1190
+ 'photo_url' => 'https://cdn.test/logo.png',
1191
+ 'address' => '1 Vendor Way',
1192
+ 'address_street' => '1 Vendor Way',
1193
+ 'country' => 'SG',
1194
+ 'type' => 'maintenance',
1195
+ 'status' => 'active',
1196
+ 'website_url' => 'https://vendor.test',
1197
+ ]);
1198
+ });
1199
+
1200
+ test('contact resource serializes contact identity and webhook payloads', function () {
1201
+ $request = fleetopsCompactResourceRequest(true);
1202
+ $place = fleetopsCompactResourceFixture([
1203
+ 'address' => '1 Contact Way',
1204
+ 'street1' => '1 Contact Way',
1205
+ ]);
1206
+ $contact = fleetopsCompactResourceFixture([
1207
+ 'company_uuid' => 'company-uuid',
1208
+ 'user_uuid' => 'user-uuid',
1209
+ 'place_uuid' => 'place-uuid',
1210
+ 'photo_uuid' => 'photo-uuid',
1211
+ 'internal_id' => 'CON-101',
1212
+ 'name' => 'Customer Contact',
1213
+ 'title' => 'Manager',
1214
+ 'email' => 'contact@example.test',
1215
+ 'phone' => '+6555552222',
1216
+ 'photo_url' => 'https://cdn.test/contact.png',
1217
+ 'place' => $place,
1218
+ 'places' => new Collection(),
1219
+ 'type' => 'customer',
1220
+ 'customer_type' => 'Fleetbase\\FleetOps\\Models\\Contact',
1221
+ 'facilitator_type' => 'Fleetbase\\FleetOps\\Models\\Vendor',
1222
+ 'meta' => ['segment' => 'priority'],
1223
+ 'slug' => 'customer-contact',
1224
+ ]);
1225
+
1226
+ $payload = (new ContactResource($contact))->resolve($request);
1227
+ $webhook = (new ContactResource($contact))->toWebhookPayload();
1228
+
1229
+ expect($payload)->toMatchArray([
1230
+ 'id' => 101,
1231
+ 'uuid' => 'fixture-uuid',
1232
+ 'company_uuid' => 'company-uuid',
1233
+ 'user_uuid' => 'user-uuid',
1234
+ 'place_uuid' => 'place-uuid',
1235
+ 'photo_uuid' => 'photo-uuid',
1236
+ 'public_id' => 'fixture_public',
1237
+ 'internal_id' => 'CON-101',
1238
+ 'name' => 'Customer Contact',
1239
+ 'title' => 'Manager',
1240
+ 'email' => 'contact@example.test',
1241
+ 'phone' => '+6555552222',
1242
+ 'address' => '1 Contact Way',
1243
+ 'address_street' => '1 Contact Way',
1244
+ 'type' => 'customer',
1245
+ 'customer_type' => 'fleet-ops:contact',
1246
+ 'facilitator_type' => 'fleet-ops:vendor',
1247
+ 'meta' => ['segment' => 'priority'],
1248
+ 'slug' => 'customer-contact',
1249
+ ])
1250
+ ->and($webhook)->toMatchArray([
1251
+ 'id' => 'fixture_public',
1252
+ 'internal_id' => 'CON-101',
1253
+ 'name' => 'Customer Contact',
1254
+ 'title' => 'Manager',
1255
+ 'email' => 'contact@example.test',
1256
+ 'phone' => '+6555552222',
1257
+ 'photo_url' => 'https://cdn.test/contact.png',
1258
+ 'type' => 'customer',
1259
+ 'meta' => ['segment' => 'priority'],
1260
+ 'slug' => 'customer-contact',
1261
+ ]);
1262
+ });
1263
+
1264
+ test('fleet resources serialize counters and webhook fleet identifiers', function () {
1265
+ $request = fleetopsCompactResourceRequest(true);
1266
+ $fleet = fleetopsCompactResourceFixture([
1267
+ 'name' => 'Downtown Fleet',
1268
+ 'task' => 'delivery',
1269
+ 'status' => 'active',
1270
+ 'drivers_count' => 5,
1271
+ 'drivers_online_count' => 3,
1272
+ 'vehicles_count' => 4,
1273
+ 'vehicles_online_count' => 2,
1274
+ 'serviceArea' => (object) ['public_id' => 'area_public'],
1275
+ 'zone' => (object) ['public_id' => 'zone_public'],
1276
+ 'parentFleet' => (object) ['public_id' => 'parent_public'],
1277
+ ]);
1278
+
1279
+ foreach ([FleetResource::class, ParentFleetResource::class, SubFleetResource::class] as $resourceClass) {
1280
+ $payload = (new $resourceClass($fleet))->resolve($request);
1281
+ $webhook = (new $resourceClass($fleet))->toWebhookPayload();
1282
+
1283
+ expect($payload)->toMatchArray([
1284
+ 'id' => 101,
1285
+ 'uuid' => 'fixture-uuid',
1286
+ 'public_id' => 'fixture_public',
1287
+ 'name' => 'Downtown Fleet',
1288
+ 'task' => 'delivery',
1289
+ 'status' => 'active',
1290
+ 'drivers_count' => 5,
1291
+ 'drivers_online_count' => 3,
1292
+ 'vehicles_count' => 4,
1293
+ 'vehicles_online_count' => 2,
1294
+ ])
1295
+ ->and($webhook)->toMatchArray([
1296
+ 'id' => 'fixture_public',
1297
+ 'name' => 'Downtown Fleet',
1298
+ 'task' => 'delivery',
1299
+ 'status' => 'active',
1300
+ 'service_area' => 'area_public',
1301
+ 'zone' => 'zone_public',
1302
+ ]);
1303
+ }
1304
+ });
1305
+
1306
+ test('tracking status resource serializes current scan and tracking number details', function () {
1307
+ $request = fleetopsCompactResourceRequest(true);
1308
+ $status = fleetopsCompactResourceFixture([
1309
+ 'tracking_number_uuid' => 'tracking-uuid',
1310
+ 'proof_uuid' => 'proof-uuid',
1311
+ 'status' => 'Out for delivery',
1312
+ 'details' => 'Driver departed hub.',
1313
+ 'code' => 'out_for_delivery',
1314
+ 'complete' => false,
1315
+ 'trackingNumber' => fleetopsCompactResourceFixture([
1316
+ 'tracking_number' => 'TN-101',
1317
+ 'region' => 'SG',
1318
+ 'last_status' => 'Out for delivery',
1319
+ 'last_status_code' => 'out_for_delivery',
1320
+ 'owner_type' => null,
1321
+ ]),
1322
+ 'city' => 'Singapore',
1323
+ 'province' => 'Central',
1324
+ 'postal_code' => '100001',
1325
+ 'country' => 'SG',
1326
+ 'location' => null,
1327
+ ]);
1328
+
1329
+ $payload = (new TrackingStatusResource($status))->resolve($request);
1330
+ $webhook = (new TrackingStatusResource($status))->toWebhookPayload();
1331
+
1332
+ expect($payload)->toMatchArray([
1333
+ 'id' => 101,
1334
+ 'uuid' => 'fixture-uuid',
1335
+ 'public_id' => 'fixture_public',
1336
+ 'tracking_number_uuid' => 'tracking-uuid',
1337
+ 'proof_uuid' => 'proof-uuid',
1338
+ 'status' => 'Out for delivery',
1339
+ 'details' => 'Driver departed hub.',
1340
+ 'code' => 'out_for_delivery',
1341
+ 'complete' => false,
1342
+ 'city' => 'Singapore',
1343
+ 'country' => 'SG',
1344
+ ])
1345
+ ->and($webhook)->toMatchArray([
1346
+ 'id' => 'fixture_public',
1347
+ 'status' => 'Out for delivery',
1348
+ 'details' => 'Driver departed hub.',
1349
+ 'code' => 'out_for_delivery',
1350
+ 'city' => 'Singapore',
1351
+ 'country' => 'SG',
1352
+ ]);
1353
+ });
1354
+
1355
+ test('order config resource projects public flow activities', function () {
1356
+ $request = fleetopsCompactResourceRequest(true);
1357
+ $config = fleetopsCompactResourceFixture([
1358
+ 'company_uuid' => 'company-uuid',
1359
+ 'key' => 'delivery',
1360
+ 'name' => 'Delivery',
1361
+ 'namespace' => 'fleet-ops',
1362
+ 'description' => 'Delivery order flow.',
1363
+ 'tags' => ['last-mile'],
1364
+ 'status' => 'active',
1365
+ 'version' => 2,
1366
+ 'flow' => [
1367
+ [
1368
+ 'key' => 'created',
1369
+ 'status' => 'Created',
1370
+ 'details' => 'Order created.',
1371
+ 'color' => '#00ff00',
1372
+ 'complete' => false,
1373
+ 'pod_method' => 'photo',
1374
+ 'require_pod' => true,
1375
+ ],
1376
+ 'skip-me',
1377
+ ],
1378
+ ]);
1379
+
1380
+ $payload = (new OrderConfigResource($config))->resolve($request);
1381
+
1382
+ expect($payload)->toMatchArray([
1383
+ 'id' => 101,
1384
+ 'uuid' => 'fixture-uuid',
1385
+ 'public_id' => 'fixture_public',
1386
+ 'company_uuid' => 'company-uuid',
1387
+ 'key' => 'delivery',
1388
+ 'name' => 'Delivery',
1389
+ 'namespace' => 'fleet-ops',
1390
+ 'description' => 'Delivery order flow.',
1391
+ 'tags' => ['last-mile'],
1392
+ 'status' => 'active',
1393
+ 'version' => 2,
1394
+ 'flow' => [
1395
+ [
1396
+ 'code' => 'created',
1397
+ 'status' => 'Created',
1398
+ 'details' => 'Order created.',
1399
+ 'color' => '#00ff00',
1400
+ 'complete' => false,
1401
+ 'pod_method' => 'photo',
1402
+ 'require_pod' => true,
1403
+ ],
1404
+ ],
1405
+ ]);
1406
+ });
1407
+
1408
+ test('purchase rate resource serializes transaction references', function () {
1409
+ $request = fleetopsCompactResourceRequest(true);
1410
+ $rate = fleetopsCompactResourceFixture([
1411
+ 'service_quote_id' => 'quote_public',
1412
+ 'order_id' => 'order_public',
1413
+ 'customer_id' => 'customer_public',
1414
+ 'transaction_id' => 'txn_public',
1415
+ 'amount' => 25.75,
1416
+ 'currency' => 'SGD',
1417
+ 'status' => 'quoted',
1418
+ ]);
1419
+
1420
+ $payload = (new PurchaseRateResource($rate))->resolve($request);
1421
+ $webhook = (new PurchaseRateResource($rate))->toWebhookPayload();
1422
+
1423
+ expect($payload)->toMatchArray([
1424
+ 'id' => 101,
1425
+ 'uuid' => 'fixture-uuid',
1426
+ 'public_id' => 'fixture_public',
1427
+ 'order' => 'order_public',
1428
+ 'customer' => 'customer_public',
1429
+ 'transaction' => 'txn_public',
1430
+ 'amount' => 25.75,
1431
+ 'currency' => 'SGD',
1432
+ 'status' => 'quoted',
1433
+ ])
1434
+ ->and($webhook)->toMatchArray([
1435
+ 'id' => 'fixture_public',
1436
+ 'service_quote' => 'quote_public',
1437
+ 'order' => 'order_public',
1438
+ 'customer' => 'customer_public',
1439
+ 'transaction' => 'txn_public',
1440
+ 'amount' => 25.75,
1441
+ 'currency' => 'SGD',
1442
+ 'status' => 'quoted',
1443
+ ]);
1444
+ });
1445
+
1446
+ test('service area and zone resources serialize geofence display fields', function () {
1447
+ $request = fleetopsCompactResourceRequest(true);
1448
+ $area = fleetopsCompactResourceFixture([
1449
+ 'name' => 'Central',
1450
+ 'type' => 'service_area',
1451
+ 'location' => null,
1452
+ 'border' => ['type' => 'Polygon'],
1453
+ 'color' => '#00ff00',
1454
+ 'stroke_color' => '#004400',
1455
+ 'trigger_on_entry' => true,
1456
+ 'trigger_on_exit' => false,
1457
+ 'dwell_threshold_minutes' => 15,
1458
+ 'speed_limit_kmh' => 50,
1459
+ 'country' => 'SG',
1460
+ 'status' => 'active',
1461
+ ]);
1462
+ $zone = fleetopsCompactResourceFixture([
1463
+ 'service_area_uuid' => 'area-uuid',
1464
+ 'name' => 'Downtown',
1465
+ 'description' => 'Downtown service zone.',
1466
+ 'location' => null,
1467
+ 'border' => ['type' => 'Polygon'],
1468
+ 'color' => '#0000ff',
1469
+ 'stroke_color' => '#000044',
1470
+ 'status' => 'active',
1471
+ ]);
1472
+
1473
+ expect((new ServiceAreaResource($area))->resolve($request))->toMatchArray([
1474
+ 'id' => 101,
1475
+ 'uuid' => 'fixture-uuid',
1476
+ 'public_id' => 'fixture_public',
1477
+ 'name' => 'Central',
1478
+ 'type' => 'service_area',
1479
+ 'border' => ['type' => 'Polygon'],
1480
+ 'trigger_on_entry' => true,
1481
+ 'trigger_on_exit' => false,
1482
+ 'dwell_threshold_minutes' => 15,
1483
+ 'speed_limit_kmh' => 50,
1484
+ 'country' => 'SG',
1485
+ 'status' => 'active',
1486
+ ])
1487
+ ->and((new ServiceAreaResource($area))->toWebhookPayload())->toMatchArray([
1488
+ 'id' => 'fixture_public',
1489
+ 'name' => 'Central',
1490
+ 'type' => 'service_area',
1491
+ 'country' => 'SG',
1492
+ 'status' => 'active',
1493
+ ])
1494
+ ->and((new ZoneResource($zone))->resolve($request))->toMatchArray([
1495
+ 'id' => 101,
1496
+ 'public_id' => 'fixture_public',
1497
+ 'uuid' => 'fixture-uuid',
1498
+ 'service_area_uuid' => 'area-uuid',
1499
+ 'name' => 'Downtown',
1500
+ 'description' => 'Downtown service zone.',
1501
+ 'border' => ['type' => 'Polygon'],
1502
+ 'color' => '#0000ff',
1503
+ 'stroke_color' => '#000044',
1504
+ 'status' => 'active',
1505
+ ])
1506
+ ->and((new ZoneResource($zone))->toWebhookPayload())->toMatchArray([
1507
+ 'id' => 'fixture_public',
1508
+ 'name' => 'Downtown',
1509
+ 'description' => 'Downtown service zone.',
1510
+ 'status' => 'active',
1511
+ ]);
1512
+ });
1513
+
1514
+ test('payload resource serializes waypoint references and payment metadata', function () {
1515
+ $request = fleetopsCompactResourceRequest(true);
1516
+ $payload = fleetopsCompactResourceFixture([
1517
+ 'current_waypoint_uuid' => 'waypoint-current-uuid',
1518
+ 'pickup_uuid' => 'pickup-uuid',
1519
+ 'pickup_tracking_number_uuid' => 'pickup-tracking-uuid',
1520
+ 'dropoff_uuid' => 'dropoff-uuid',
1521
+ 'dropoff_tracking_number_uuid' => 'dropoff-tracking-uuid',
1522
+ 'return_uuid' => 'return-uuid',
1523
+ 'return_tracking_number_uuid' => 'return-tracking-uuid',
1524
+ 'currentWaypoint' => (object) ['public_id' => 'waypoint_current'],
1525
+ 'pickup' => null,
1526
+ 'dropoff' => null,
1527
+ 'return' => null,
1528
+ 'waypoints' => new Collection(),
1529
+ 'entities' => new Collection(),
1530
+ 'cod_amount' => 19.99,
1531
+ 'cod_currency' => 'SGD',
1532
+ 'cod_payment_method' => 'cash',
1533
+ 'payment_method' => 'card',
1534
+ 'meta' => ['fragile' => true],
1535
+ ]);
1536
+
1537
+ $resolved = (new PayloadResource($payload))->resolve($request);
1538
+
1539
+ expect($resolved)->toMatchArray([
1540
+ 'id' => 101,
1541
+ 'uuid' => 'fixture-uuid',
1542
+ 'public_id' => 'fixture_public',
1543
+ 'current_waypoint_uuid' => 'waypoint-current-uuid',
1544
+ 'pickup_uuid' => 'pickup-uuid',
1545
+ 'pickup_tracking_number_uuid' => 'pickup-tracking-uuid',
1546
+ 'dropoff_uuid' => 'dropoff-uuid',
1547
+ 'dropoff_tracking_number_uuid' => 'dropoff-tracking-uuid',
1548
+ 'return_uuid' => 'return-uuid',
1549
+ 'return_tracking_number_uuid' => 'return-tracking-uuid',
1550
+ 'cod_amount' => 19.99,
1551
+ 'cod_currency' => 'SGD',
1552
+ 'cod_payment_method' => 'cash',
1553
+ 'payment_method' => 'card',
1554
+ 'meta' => ['fragile' => true],
1555
+ ]);
1556
+ });
1557
+
1558
+ test('work order resource serializes assignment and completion state', function () {
1559
+ $request = fleetopsCompactResourceRequest(true);
1560
+ $workOrder = fleetopsCompactResourceFixture([
1561
+ 'company_uuid' => 'company-uuid',
1562
+ 'schedule_uuid' => 'schedule-uuid',
1563
+ 'created_by_uuid' => 'creator-uuid',
1564
+ 'updated_by_uuid' => 'updater-uuid',
1565
+ 'target_uuid' => 'vehicle-uuid',
1566
+ 'target_type' => 'Fleetbase\\FleetOps\\Models\\Vehicle',
1567
+ 'assignee_uuid' => 'vendor-uuid',
1568
+ 'assignee_type' => 'Fleetbase\\FleetOps\\Models\\Vendor',
1569
+ 'code' => 'WO-101',
1570
+ 'subject' => 'Inspect brakes',
1571
+ 'category' => 'safety',
1572
+ 'status' => 'open',
1573
+ 'priority' => 'high',
1574
+ 'instructions' => 'Inspect and report.',
1575
+ 'checklist' => [['label' => 'Pads', 'done' => false]],
1576
+ 'meta' => ['source' => 'schedule'],
1577
+ 'slug' => 'inspect-brakes',
1578
+ 'target_name' => 'Truck 101',
1579
+ 'assignee_name' => 'Vendor One',
1580
+ 'is_overdue' => true,
1581
+ 'days_until_due' => -1,
1582
+ 'completion_percentage' => 25,
1583
+ 'opened_at' => '2026-07-01 09:00:00',
1584
+ 'due_at' => '2026-07-05 09:00:00',
1585
+ 'closed_at' => null,
1586
+ ]);
1587
+
1588
+ expect((new WorkOrderResource($workOrder))->resolve($request))->toMatchArray([
1589
+ 'id' => 101,
1590
+ 'uuid' => 'fixture-uuid',
1591
+ 'public_id' => 'fixture_public',
1592
+ 'company_uuid' => 'company-uuid',
1593
+ 'schedule_uuid' => 'schedule-uuid',
1594
+ 'target_uuid' => 'vehicle-uuid',
1595
+ 'target_type' => 'fleet-ops:vehicle',
1596
+ 'assignee_uuid' => 'vendor-uuid',
1597
+ 'assignee_type' => 'fleet-ops:vendor',
1598
+ 'code' => 'WO-101',
1599
+ 'subject' => 'Inspect brakes',
1600
+ 'category' => 'safety',
1601
+ 'status' => 'open',
1602
+ 'priority' => 'high',
1603
+ 'instructions' => 'Inspect and report.',
1604
+ 'checklist' => [['label' => 'Pads', 'done' => false]],
1605
+ 'target_name' => 'Truck 101',
1606
+ 'assignee_name' => 'Vendor One',
1607
+ 'is_overdue' => true,
1608
+ 'days_until_due' => -1,
1609
+ 'completion_percentage' => 25,
1610
+ 'opened_at' => '2026-07-01 09:00:00',
1611
+ 'due_at' => '2026-07-05 09:00:00',
1612
+ ]);
1613
+ });
1614
+
1615
+ test('fuel transaction resource serializes provider transaction state', function () {
1616
+ $request = fleetopsCompactResourceRequest(true);
1617
+ $transaction = fleetopsCompactResourceFixture([
1618
+ 'fuel_provider_connection_uuid' => 'connection-uuid',
1619
+ 'fuel_report_uuid' => 'report-uuid',
1620
+ 'vehicle_uuid' => 'vehicle-uuid',
1621
+ 'driver_uuid' => 'driver-uuid',
1622
+ 'order_uuid' => 'order-uuid',
1623
+ 'provider' => 'wex',
1624
+ 'provider_transaction_id' => 'provider-txn-101',
1625
+ 'provider_vehicle_id' => 'provider-vehicle-101',
1626
+ 'vehicle_card_id' => 'card-101',
1627
+ 'internal_number' => 'INT-101',
1628
+ 'structure_number' => 'STRUCT-101',
1629
+ 'plate_number' => 'ABC-101',
1630
+ 'vin' => 'VIN-101',
1631
+ 'serial_number' => 'SER-101',
1632
+ 'call_sign' => 'TRUCK-101',
1633
+ 'trip_number' => 'TRIP-101',
1634
+ 'station_name' => 'Fuel Stop',
1635
+ 'station_latitude' => 1.29,
1636
+ 'station_longitude' => 103.85,
1637
+ 'station_location' => ['lat' => 1.29, 'lng' => 103.85],
1638
+ 'transaction_at' => '2026-07-01 11:00:00',
1639
+ 'volume' => 30.5,
1640
+ 'metric_unit' => 'liters',
1641
+ 'amount' => 92.25,
1642
+ 'currency' => 'SGD',
1643
+ 'odometer' => 12000,
1644
+ 'sync_status' => 'matched',
1645
+ 'matched_at' => '2026-07-01 11:05:00',
1646
+ 'vehicle_name' => 'Truck 101',
1647
+ 'driver_name' => 'Jane Driver',
1648
+ 'fuel_report_id' => 'report_public',
1649
+ 'normalized_payload' => ['amount' => 92.25],
1650
+ 'raw_payload' => ['raw' => true],
1651
+ 'meta' => ['matched_by' => 'vin'],
1652
+ ]);
1653
+
1654
+ expect((new Fleetbase\FleetOps\Http\Resources\v1\FuelTransaction($transaction))->resolve($request))->toMatchArray([
1655
+ 'id' => 101,
1656
+ 'uuid' => 'fixture-uuid',
1657
+ 'public_id' => 'fixture_public',
1658
+ 'fuel_provider_connection_uuid' => 'connection-uuid',
1659
+ 'fuel_report_uuid' => 'report-uuid',
1660
+ 'vehicle_uuid' => 'vehicle-uuid',
1661
+ 'driver_uuid' => 'driver-uuid',
1662
+ 'order_uuid' => 'order-uuid',
1663
+ 'provider' => 'wex',
1664
+ 'provider_transaction_id' => 'provider-txn-101',
1665
+ 'plate_number' => 'ABC-101',
1666
+ 'station_name' => 'Fuel Stop',
1667
+ 'volume' => 30.5,
1668
+ 'amount' => 92.25,
1669
+ 'currency' => 'SGD',
1670
+ 'sync_status' => 'matched',
1671
+ 'vehicle_name' => 'Truck 101',
1672
+ 'driver_name' => 'Jane Driver',
1673
+ 'normalized_payload' => ['amount' => 92.25],
1674
+ 'raw_payload' => ['raw' => true],
1675
+ 'meta' => ['matched_by' => 'vin'],
1676
+ ]);
1677
+ });
1678
+
1679
+ test('sensor resource serializes device sensor thresholds and status', function () {
1680
+ $request = fleetopsCompactResourceRequest(true);
1681
+ $sensor = fleetopsCompactResourceFixture([
1682
+ 'company_uuid' => 'company-uuid',
1683
+ 'device_uuid' => 'device-uuid',
1684
+ 'warranty_uuid' => 'warranty-uuid',
1685
+ 'telematic_uuid' => 'telematic-uuid',
1686
+ 'photo_uuid' => 'photo-uuid',
1687
+ 'sensorable_uuid' => 'vehicle-uuid',
1688
+ 'sensorable_type' => 'Fleetbase\\FleetOps\\Models\\Vehicle',
1689
+ 'name' => 'Temperature Sensor',
1690
+ 'type' => 'temperature',
1691
+ 'internal_id' => 'SEN-101',
1692
+ 'imei' => 'imei-sensor',
1693
+ 'imsi' => 'imsi-sensor',
1694
+ 'firmware_version' => '4.5.6',
1695
+ 'serial_number' => 'SER-SEN-101',
1696
+ 'last_position' => ['lat' => 1.29],
1697
+ 'unit' => 'celsius',
1698
+ 'min_threshold' => 2,
1699
+ 'max_threshold' => 8,
1700
+ 'threshold_inclusive' => true,
1701
+ 'last_reading_at' => '2026-07-01 12:00:00',
1702
+ 'last_value' => 4.2,
1703
+ 'calibration' => ['offset' => 0.1],
1704
+ 'report_frequency_sec' => 60,
1705
+ 'status' => 'active',
1706
+ 'meta' => ['cargo' => 'chilled'],
1707
+ 'is_active' => true,
1708
+ 'threshold_status' => 'normal',
1709
+ 'photo_url' => 'https://cdn.test/sensor.png',
1710
+ 'device_name' => 'Tracker 101',
1711
+ 'warranty_name' => 'Standard',
1712
+ 'attached_to_name' => 'Truck 101',
1713
+ 'slug' => 'temperature-sensor',
1714
+ ]);
1715
+
1716
+ expect((new SensorResource($sensor))->resolve($request))->toMatchArray([
1717
+ 'id' => 101,
1718
+ 'uuid' => 'fixture-uuid',
1719
+ 'public_id' => 'fixture_public',
1720
+ 'company_uuid' => 'company-uuid',
1721
+ 'device_uuid' => 'device-uuid',
1722
+ 'sensorable_uuid' => 'vehicle-uuid',
1723
+ 'sensorable_type' => 'fleet-ops:vehicle',
1724
+ 'name' => 'Temperature Sensor',
1725
+ 'type' => 'temperature',
1726
+ 'internal_id' => 'SEN-101',
1727
+ 'unit' => 'celsius',
1728
+ 'min_threshold' => 2,
1729
+ 'max_threshold' => 8,
1730
+ 'threshold_inclusive' => true,
1731
+ 'last_value' => 4.2,
1732
+ 'calibration' => ['offset' => 0.1],
1733
+ 'report_frequency_sec' => 60,
1734
+ 'status' => 'active',
1735
+ 'meta' => ['cargo' => 'chilled'],
1736
+ 'is_active' => true,
1737
+ 'threshold_status' => 'normal',
1738
+ 'slug' => 'temperature-sensor',
1739
+ ]);
1740
+ });
1741
+
1742
+ test('inventory and rate resources serialize scalar operational fields', function () {
1743
+ $request = fleetopsCompactResourceRequest(true);
1744
+
1745
+ $rateFee = fleetopsCompactResourceFixture([
1746
+ 'service_rate_uuid' => 'rate-uuid',
1747
+ 'service_area_uuid' => 'area-uuid',
1748
+ 'zone_uuid' => 'zone-uuid',
1749
+ 'label' => 'Distance band',
1750
+ 'priority' => 10,
1751
+ 'is_fallback' => 1,
1752
+ 'fee' => 12.5,
1753
+ 'currency' => 'SGD',
1754
+ 'min' => 0,
1755
+ 'max' => 10,
1756
+ 'unit' => 'km',
1757
+ 'distance' => 10,
1758
+ 'distance_unit' => 'km',
1759
+ 'size' => 'medium',
1760
+ 'length' => 10,
1761
+ 'height' => 8,
1762
+ 'dimensions_unit' => 'cm',
1763
+ 'weight' => 2,
1764
+ 'weight_unit' => 'kg',
1765
+ ]);
1766
+ $parcelFee = fleetopsCompactResourceFixture([
1767
+ 'service_rate_uuid' => 'rate-uuid',
1768
+ 'fee' => 9.5,
1769
+ 'currency' => 'SGD',
1770
+ 'size' => 'small',
1771
+ 'length' => 8,
1772
+ 'width' => 4,
1773
+ 'height' => 3,
1774
+ 'dimensions_unit' => 'cm',
1775
+ 'weight' => 1.5,
1776
+ 'weight_unit' => 'kg',
1777
+ 'distance' => 5,
1778
+ ]);
1779
+ $part = fleetopsCompactResourceFixture([
1780
+ 'company_uuid' => 'company-uuid',
1781
+ 'vendor_uuid' => 'vendor-uuid',
1782
+ 'warranty_uuid' => 'warranty-uuid',
1783
+ 'photo_uuid' => 'photo-uuid',
1784
+ 'asset_uuid' => 'vehicle-uuid',
1785
+ 'asset_type' => 'Fleetbase\\FleetOps\\Models\\Vehicle',
1786
+ 'sku' => 'PART-101',
1787
+ 'name' => 'Brake Pad',
1788
+ 'manufacturer' => 'PartsCo',
1789
+ 'model' => 'BP-1',
1790
+ 'serial_number' => 'SER-PART-101',
1791
+ 'barcode' => 'BAR-PART-101',
1792
+ 'description' => 'Front brake pad.',
1793
+ 'quantity_on_hand' => 4,
1794
+ 'unit_cost' => 25,
1795
+ 'msrp' => 35,
1796
+ 'currency' => 'SGD',
1797
+ 'type' => 'brake',
1798
+ 'status' => 'active',
1799
+ 'specs' => ['axle' => 'front'],
1800
+ 'meta' => ['shelf' => 'A1'],
1801
+ 'vendor_name' => 'Vendor One',
1802
+ 'warranty_name' => 'Standard',
1803
+ 'photo_url' => 'https://cdn.test/part.png',
1804
+ 'total_value' => 100,
1805
+ 'is_in_stock' => true,
1806
+ 'is_low_stock' => false,
1807
+ 'asset_name' => 'Truck 101',
1808
+ 'slug' => 'brake-pad',
1809
+ ]);
1810
+ $equipment = fleetopsCompactResourceFixture([
1811
+ 'company_uuid' => 'company-uuid',
1812
+ 'warranty_uuid' => 'warranty-uuid',
1813
+ 'photo_uuid' => 'photo-uuid',
1814
+ 'equipable_uuid' => 'vehicle-uuid',
1815
+ 'equipable_type' => 'Fleetbase\\FleetOps\\Models\\Vehicle',
1816
+ 'name' => 'Lift Gate',
1817
+ 'code' => 'EQ-101',
1818
+ 'type' => 'liftgate',
1819
+ 'status' => 'active',
1820
+ 'serial_number' => 'SER-EQ-101',
1821
+ 'manufacturer' => 'EquipmentCo',
1822
+ 'model' => 'LG-1',
1823
+ 'purchased_at' => '2026-01-01',
1824
+ 'purchase_price' => 1200,
1825
+ 'currency' => 'SGD',
1826
+ 'warranty_name' => 'Standard',
1827
+ 'photo_url' => 'https://cdn.test/equipment.png',
1828
+ 'equipped_to_name' => 'Truck 101',
1829
+ 'is_equipped' => true,
1830
+ 'age_in_days' => 30,
1831
+ 'depreciated_value' => 1100,
1832
+ 'meta' => ['bay' => 'north'],
1833
+ 'slug' => 'lift-gate',
1834
+ ]);
1835
+
1836
+ expect((new Fleetbase\FleetOps\Http\Resources\v1\ServiceRateFee($rateFee))->resolve($request))->toMatchArray([
1837
+ 'id' => 101,
1838
+ 'uuid' => 'fixture-uuid',
1839
+ 'service_rate_uuid' => 'rate-uuid',
1840
+ 'service_area_uuid' => 'area-uuid',
1841
+ 'zone_uuid' => 'zone-uuid',
1842
+ 'label' => 'Distance band',
1843
+ 'priority' => 10,
1844
+ 'is_fallback' => true,
1845
+ 'fee' => 12.5,
1846
+ 'currency' => 'SGD',
1847
+ 'distance_unit' => 'km',
1848
+ ])
1849
+ ->and((new Fleetbase\FleetOps\Http\Resources\v1\ServiceRateFee($rateFee))->toWebhookPayload())->toMatchArray([
1850
+ 'fee' => 12.5,
1851
+ 'currency' => 'SGD',
1852
+ 'size' => 'medium',
1853
+ 'dimensions_unit' => 'cm',
1854
+ 'weight_unit' => 'kg',
1855
+ ])
1856
+ ->and((new Fleetbase\FleetOps\Http\Resources\v1\ServiceRateParcelFee($parcelFee))->resolve($request))->toMatchArray([
1857
+ 'id' => 101,
1858
+ 'uuid' => 'fixture-uuid',
1859
+ 'service_rate_uuid' => 'rate-uuid',
1860
+ 'fee' => 9.5,
1861
+ 'currency' => 'SGD',
1862
+ 'size' => 'small',
1863
+ 'dimensions_unit' => 'cm',
1864
+ 'weight_unit' => 'kg',
1865
+ ])
1866
+ ->and((new Fleetbase\FleetOps\Http\Resources\v1\ServiceRateParcelFee($parcelFee))->toWebhookPayload())->toMatchArray([
1867
+ 'fee' => 9.5,
1868
+ 'currency' => 'SGD',
1869
+ 'distance' => 5,
1870
+ ])
1871
+ ->and((new Fleetbase\FleetOps\Http\Resources\v1\Part($part))->resolve($request))->toMatchArray([
1872
+ 'id' => 101,
1873
+ 'uuid' => 'fixture-uuid',
1874
+ 'company_uuid' => 'company-uuid',
1875
+ 'asset_type' => 'fleet-ops:vehicle',
1876
+ 'sku' => 'PART-101',
1877
+ 'name' => 'Brake Pad',
1878
+ 'quantity_on_hand' => 4,
1879
+ 'unit_cost' => 25,
1880
+ 'total_value' => 100,
1881
+ 'is_in_stock' => true,
1882
+ 'is_low_stock' => false,
1883
+ 'asset_name' => 'Truck 101',
1884
+ ])
1885
+ ->and((new Fleetbase\FleetOps\Http\Resources\v1\Equipment($equipment))->resolve($request))->toMatchArray([
1886
+ 'id' => 101,
1887
+ 'uuid' => 'fixture-uuid',
1888
+ 'company_uuid' => 'company-uuid',
1889
+ 'equipable_type' => 'fleet-ops:vehicle',
1890
+ 'name' => 'Lift Gate',
1891
+ 'code' => 'EQ-101',
1892
+ 'purchase_price' => 1200,
1893
+ 'currency' => 'SGD',
1894
+ 'equipped_to_name' => 'Truck 101',
1895
+ 'is_equipped' => true,
1896
+ 'age_in_days' => 30,
1897
+ 'depreciated_value' => 1100,
1898
+ ]);
1899
+ });
1900
+
1901
+ test('position fuel report and vehicle device resources serialize telemetry fields', function () {
1902
+ $request = fleetopsCompactResourceRequest(true);
1903
+
1904
+ $position = fleetopsCompactResourceFixture([
1905
+ 'order_uuid' => 'order-uuid',
1906
+ 'company_uuid' => 'company-uuid',
1907
+ 'destination_uuid' => 'place-uuid',
1908
+ 'subject_uuid' => 'vehicle-uuid',
1909
+ 'subject_type' => 'Fleetbase\\FleetOps\\Models\\Vehicle',
1910
+ 'heading' => 45,
1911
+ 'bearing' => 90,
1912
+ 'speed' => 55,
1913
+ 'altitude' => 12,
1914
+ 'latitude' => 1.29,
1915
+ 'longitude' => 103.85,
1916
+ 'coordinates' => null,
1917
+ ]);
1918
+ $fuelReport = fleetopsCompactResourceFixture([
1919
+ 'reported_by_uuid' => 'reporter-uuid',
1920
+ 'driver_uuid' => 'driver-uuid',
1921
+ 'vehicle_uuid' => 'vehicle-uuid',
1922
+ 'reporter_name' => 'Dispatcher',
1923
+ 'driver_name' => 'Jane Driver',
1924
+ 'vehicle_name' => 'Truck 101',
1925
+ 'odometer' => 12000,
1926
+ 'amount' => 92.25,
1927
+ 'currency' => 'SGD',
1928
+ 'volume' => 30.5,
1929
+ 'metric_unit' => 'liters',
1930
+ 'type' => 'fuel',
1931
+ 'status' => 'submitted',
1932
+ 'source' => 'manual',
1933
+ 'provider' => 'wex',
1934
+ 'fuel_provider_transaction_uuid' => 'txn-uuid',
1935
+ 'meta' => ['receipt' => true],
1936
+ 'location' => null,
1937
+ 'reportedBy' => (object) ['public_id' => 'reporter_public'],
1938
+ 'driver' => (object) ['public_id' => 'driver_public'],
1939
+ 'vehicle' => (object) ['public_id' => 'vehicle_public'],
1940
+ 'report' => 'Fuel Stop',
1941
+ ]);
1942
+ $vehicleDevice = fleetopsCompactResourceFixture([
1943
+ 'vehicle_uuid' => 'vehicle-uuid',
1944
+ 'device_id' => 'device-101',
1945
+ 'device_provider' => 'safee',
1946
+ 'device_type' => 'gps',
1947
+ 'device_name' => 'Tracker 101',
1948
+ 'device_model' => 'T100',
1949
+ 'device_location' => ['lat' => 1.29],
1950
+ 'manufacturer' => 'TrackerCo',
1951
+ 'serial_number' => 'SER-DEV-101',
1952
+ 'installation_date' => '2026-01-01',
1953
+ 'last_maintenance_date' => '2026-06-01',
1954
+ 'meta' => ['mounted' => true],
1955
+ 'data' => ['battery' => 87],
1956
+ 'status' => 'active',
1957
+ 'data_frequency' => 60,
1958
+ 'notes' => 'Mounted under dash.',
1959
+ ]);
1960
+
1961
+ expect((new Fleetbase\FleetOps\Http\Resources\v1\Position($position))->resolve($request))->toMatchArray([
1962
+ 'id' => 101,
1963
+ 'uuid' => 'fixture-uuid',
1964
+ 'public_id' => 'fixture_public',
1965
+ 'order_uuid' => 'order-uuid',
1966
+ 'company_uuid' => 'company-uuid',
1967
+ 'destination_uuid' => 'place-uuid',
1968
+ 'subject_uuid' => 'vehicle-uuid',
1969
+ 'subject_type' => 'fleet-ops:vehicle',
1970
+ 'heading' => 45,
1971
+ 'bearing' => 90,
1972
+ 'speed' => 55,
1973
+ 'altitude' => 12,
1974
+ 'latitude' => 1.29,
1975
+ 'longitude' => 103.85,
1976
+ ])
1977
+ ->and((new Fleetbase\FleetOps\Http\Resources\v1\FuelReport($fuelReport))->resolve($request))->toMatchArray([
1978
+ 'id' => 101,
1979
+ 'uuid' => 'fixture-uuid',
1980
+ 'reported_by_uuid' => 'reporter-uuid',
1981
+ 'driver_uuid' => 'driver-uuid',
1982
+ 'vehicle_uuid' => 'vehicle-uuid',
1983
+ 'reporter_name' => 'Dispatcher',
1984
+ 'driver_name' => 'Jane Driver',
1985
+ 'vehicle_name' => 'Truck 101',
1986
+ 'odometer' => 12000,
1987
+ 'amount' => 92.25,
1988
+ 'currency' => 'SGD',
1989
+ 'volume' => 30.5,
1990
+ 'metric_unit' => 'liters',
1991
+ 'source' => 'manual',
1992
+ 'provider' => 'wex',
1993
+ 'fuel_provider_transaction_uuid' => 'txn-uuid',
1994
+ ])
1995
+ ->and((new Fleetbase\FleetOps\Http\Resources\v1\FuelReport($fuelReport))->toWebhookPayload())->toMatchArray([
1996
+ 'id' => 'fixture_public',
1997
+ 'reporter' => 'reporter_public',
1998
+ 'driver' => 'driver_public',
1999
+ 'vehicle' => 'vehicle_public',
2000
+ 'report_name' => 'Fuel Stop',
2001
+ 'amount' => 92.25,
2002
+ 'currency' => 'SGD',
2003
+ 'provider' => 'wex',
2004
+ ])
2005
+ ->and((new Fleetbase\FleetOps\Http\Resources\v1\VehicleDevice($vehicleDevice))->resolve($request))->toMatchArray([
2006
+ 'id' => 101,
2007
+ 'uuid' => 'fixture-uuid',
2008
+ 'vehicle_uuid' => 'vehicle-uuid',
2009
+ 'device_id' => 'device-101',
2010
+ 'device_provider' => 'safee',
2011
+ 'device_type' => 'gps',
2012
+ 'device_name' => 'Tracker 101',
2013
+ 'device_model' => 'T100',
2014
+ 'manufacturer' => 'TrackerCo',
2015
+ 'serial_number' => 'SER-DEV-101',
2016
+ 'installation_date' => '2026-01-01',
2017
+ 'last_maintenance_date' => '2026-06-01',
2018
+ 'meta' => ['mounted' => true],
2019
+ 'data' => ['battery' => 87],
2020
+ 'status' => 'active',
2021
+ 'data_frequency' => 60,
2022
+ 'notes' => 'Mounted under dash.',
2023
+ ]);
2024
+ });
2025
+
2026
+ test('index driver payload and place resources serialize compact table fields', function () {
2027
+ $request = fleetopsCompactResourceRequest(true);
2028
+ $driver = fleetopsCompactResourceFixture([
2029
+ 'company_uuid' => 'company-uuid',
2030
+ 'user_uuid' => 'user-uuid',
2031
+ 'vehicle_uuid' => 'vehicle-uuid',
2032
+ 'vendor_uuid' => 'vendor-uuid',
2033
+ 'current_job_uuid' => 'order-uuid',
2034
+ 'name' => 'Jane Driver',
2035
+ 'vehicle_name' => 'Truck 101',
2036
+ 'email' => 'jane@example.test',
2037
+ 'phone' => '+6555553333',
2038
+ 'photo_url' => 'https://cdn.test/driver.png',
2039
+ 'status' => 'on_duty',
2040
+ 'location' => null,
2041
+ 'heading' => 135,
2042
+ 'altitude' => 9,
2043
+ 'speed' => 48,
2044
+ 'online' => true,
2045
+ ]);
2046
+ $payload = fleetopsCompactResourceFixture([
2047
+ 'company_uuid' => 'company-uuid',
2048
+ 'pickup_uuid' => 'pickup-uuid',
2049
+ 'dropoff_uuid' => 'dropoff-uuid',
2050
+ 'return_uuid' => 'return-uuid',
2051
+ 'index_pickup_place' => null,
2052
+ 'index_dropoff_place' => null,
2053
+ 'type' => 'parcel',
2054
+ ], [
2055
+ 'entities' => new Collection([(object) ['public_id' => 'entity_public']]),
2056
+ 'waypoints' => new Collection([(object) ['public_id' => 'waypoint_public']]),
2057
+ ]);
2058
+ $place = fleetopsCompactResourceFixture([
2059
+ 'company_uuid' => 'company-uuid',
2060
+ 'owner_uuid' => 'owner-uuid',
2061
+ 'owner_type' => 'Fleetbase\\FleetOps\\Models\\Contact',
2062
+ 'name' => 'Warehouse',
2063
+ 'address' => '1 Index Way',
2064
+ 'street1' => '1 Index Way',
2065
+ 'city' => 'Singapore',
2066
+ 'country' => 'SG',
2067
+ 'avatar_url' => 'https://cdn.test/place.png',
2068
+ 'location' => null,
2069
+ ]);
2070
+
2071
+ expect((new TestFleetOpsIndexDriverResource($driver))->resolve($request))->toMatchArray([
2072
+ 'id' => 101,
2073
+ 'uuid' => 'fixture-uuid',
2074
+ 'public_id' => 'fixture_public',
2075
+ 'company_uuid' => 'company-uuid',
2076
+ 'user_uuid' => 'user-uuid',
2077
+ 'vehicle_uuid' => 'vehicle-uuid',
2078
+ 'vendor_uuid' => 'vendor-uuid',
2079
+ 'current_job_uuid' => 'order-uuid',
2080
+ 'assigned_orders_count' => 5,
2081
+ 'name' => 'Jane Driver',
2082
+ 'vehicle_name' => 'Truck 101',
2083
+ 'email' => 'jane@example.test',
2084
+ 'status' => 'on_duty',
2085
+ 'heading' => 135,
2086
+ 'altitude' => 9,
2087
+ 'speed' => 48,
2088
+ 'online' => true,
2089
+ 'meta' => [
2090
+ '_index_resource' => true,
2091
+ 'location_coordinates' => '0 0',
2092
+ 'current_order_reference' => 'TRK-DRIVER',
2093
+ 'speed_label' => '48 km/h',
2094
+ 'heading_label' => '135 deg',
2095
+ 'status_label' => 'On Duty',
2096
+ ],
2097
+ ])
2098
+ ->and((new IndexPayloadResource($payload))->resolve($request))->toMatchArray([
2099
+ 'id' => 101,
2100
+ 'uuid' => 'fixture-uuid',
2101
+ 'public_id' => 'fixture_public',
2102
+ 'company_uuid' => 'company-uuid',
2103
+ 'pickup_uuid' => 'pickup-uuid',
2104
+ 'dropoff_uuid' => 'dropoff-uuid',
2105
+ 'return_uuid' => 'return-uuid',
2106
+ 'entities_count' => 1,
2107
+ 'waypoints_count' => 1,
2108
+ 'type' => 'parcel',
2109
+ ])
2110
+ ->and((new IndexPlaceResource($place))->resolve($request))->toMatchArray([
2111
+ 'id' => 101,
2112
+ 'uuid' => 'fixture-uuid',
2113
+ 'public_id' => 'fixture_public',
2114
+ 'company_uuid' => 'company-uuid',
2115
+ 'owner_uuid' => 'owner-uuid',
2116
+ 'owner_type' => 'Fleetbase\\FleetOps\\Models\\Contact',
2117
+ 'name' => 'Warehouse',
2118
+ 'address' => '1 Index Way',
2119
+ 'street1' => '1 Index Way',
2120
+ 'city' => 'Singapore',
2121
+ 'country' => 'SG',
2122
+ 'avatar_url' => 'https://cdn.test/place.png',
2123
+ 'meta' => ['_index_resource' => true],
2124
+ ]);
2125
+ });
2126
+
2127
+ test('deleted resource marks internal and webhook payloads as deleted', function () {
2128
+ $request = fleetopsCompactResourceRequest(true);
2129
+ $model = new class(['id' => 101, 'uuid' => 'fixture-uuid', 'public_id' => 'fixture_public', 'deleted_at' => '2026-07-01 00:00:00']) extends FleetOpsCompactResourceFixture {
2130
+ };
2131
+
2132
+ $resource = new DeletedResource($model);
2133
+ $object = $resource->getObjectType();
2134
+
2135
+ expect($resource->resolve($request))->toMatchArray([
2136
+ 'id' => 101,
2137
+ 'uuid' => 'fixture-uuid',
2138
+ 'public_id' => 'fixture_public',
2139
+ 'object' => $object,
2140
+ 'time' => '2026-07-01 00:00:00',
2141
+ 'deleted' => true,
2142
+ ])
2143
+ ->and($resource->toWebhookPayload())->toMatchArray([
2144
+ 'id' => 'fixture_public',
2145
+ 'object' => $object,
2146
+ 'time' => '2026-07-01 00:00:00',
2147
+ 'deleted' => true,
2148
+ ])
2149
+ ->and($object)->toBeString()->not->toBe('');
2150
+ });
2151
+
2152
+ test('quote proof current job and tiny index resources serialize simple payloads', function () {
2153
+ $request = fleetopsCompactResourceRequest(true);
2154
+
2155
+ $quoteItem = fleetopsCompactResourceFixture([
2156
+ 'service_quote_uuid' => 'quote-uuid',
2157
+ 'amount' => 7.5,
2158
+ 'currency' => 'SGD',
2159
+ 'details' => 'Handling fee',
2160
+ 'code' => 'handling',
2161
+ ]);
2162
+ $quote = fleetopsCompactResourceFixture([
2163
+ 'service_rate_uuid' => 'rate-uuid',
2164
+ 'payload_uuid' => 'payload-uuid',
2165
+ 'serviceRate' => (object) ['service_name' => 'Same Day', 'public_id' => 'rate_public'],
2166
+ 'integratedVendor' => (object) ['public_id' => 'vendor_public'],
2167
+ 'items' => new Collection([$quoteItem]),
2168
+ 'request_id' => 'REQ-101',
2169
+ 'amount' => 25.5,
2170
+ 'currency' => 'SGD',
2171
+ 'meta' => ['quoted' => true],
2172
+ ]);
2173
+ $currentJob = fleetopsCompactResourceFixture([
2174
+ 'company_uuid' => 'company-uuid',
2175
+ 'internal_id' => 'ORD-101',
2176
+ 'payload' => null,
2177
+ 'type' => 'transport',
2178
+ 'status' => 'dispatched',
2179
+ 'meta' => ['priority' => 'high'],
2180
+ ]);
2181
+ $proof = fleetopsCompactResourceFixture([
2182
+ 'subject' => (object) ['public_id' => 'subject_public'],
2183
+ 'order' => (object) ['public_id' => 'order_public'],
2184
+ 'file_url' => 'https://cdn.test/proof.png',
2185
+ 'remarks' => 'Delivered at reception.',
2186
+ 'raw_data' => ['signature' => true],
2187
+ 'data' => ['name' => 'Receiver'],
2188
+ ]);
2189
+ $person = fleetopsCompactResourceFixture([
2190
+ 'company_uuid' => 'company-uuid',
2191
+ 'name' => 'Compact Person',
2192
+ 'phone' => '+6555554444',
2193
+ 'email' => 'person@example.test',
2194
+ ]);
2195
+ $trackingNumber = fleetopsCompactResourceFixture([
2196
+ 'tracking_number' => 'TN-101',
2197
+ 'qr_code' => 'qr-data',
2198
+ ]);
2199
+ $internalConfig = fleetopsCompactResourceFixture([
2200
+ 'company_uuid' => 'company-uuid',
2201
+ 'author_uuid' => 'author-uuid',
2202
+ 'category_uuid' => 'category-uuid',
2203
+ 'icon_uuid' => 'icon-uuid',
2204
+ 'name' => 'Delivery',
2205
+ 'namespace' => 'fleet-ops',
2206
+ 'description' => 'Delivery config.',
2207
+ 'key' => 'delivery',
2208
+ 'status' => 'active',
2209
+ 'version' => 3,
2210
+ 'core_service' => 1,
2211
+ 'flow' => [['code' => 'created']],
2212
+ 'entities' => [['type' => 'parcel']],
2213
+ 'tags' => ['last-mile'],
2214
+ 'meta' => ['default' => true],
2215
+ 'deleted_at' => null,
2216
+ ]);
2217
+
2218
+ expect((new ServiceQuoteItemResource($quoteItem))->resolve($request))->toMatchArray([
2219
+ 'id' => 101,
2220
+ 'uuid' => 'fixture-uuid',
2221
+ 'service_quote_uuid' => 'quote-uuid',
2222
+ 'amount' => 7.5,
2223
+ 'currency' => 'SGD',
2224
+ 'details' => 'Handling fee',
2225
+ 'code' => 'handling',
2226
+ ])
2227
+ ->and((new ServiceQuoteResource($quote))->resolve($request))->toMatchArray([
2228
+ 'id' => 101,
2229
+ 'uuid' => 'fixture-uuid',
2230
+ 'public_id' => 'fixture_public',
2231
+ 'service_rate_uuid' => 'rate-uuid',
2232
+ 'payload_uuid' => 'payload-uuid',
2233
+ 'service_rate_name' => 'Same Day',
2234
+ 'service_name' => 'Same Day',
2235
+ 'request_id' => 'REQ-101',
2236
+ 'amount' => 25.5,
2237
+ 'currency' => 'SGD',
2238
+ 'meta' => ['quoted' => true],
2239
+ ])
2240
+ ->and((new ServiceQuoteResource($quote))->toWebhookPayload())->toMatchArray([
2241
+ 'id' => 'fixture_public',
2242
+ 'service_rate' => 'rate_public',
2243
+ 'facilitator' => 'vendor_public',
2244
+ 'request_id' => 'REQ-101',
2245
+ 'amount' => 25.5,
2246
+ 'currency' => 'SGD',
2247
+ ])
2248
+ ->and((new CurrentJobResource($currentJob))->resolve($request))->toMatchArray([
2249
+ 'id' => 101,
2250
+ 'uuid' => 'fixture-uuid',
2251
+ 'public_id' => 'fixture_public',
2252
+ 'company_uuid' => 'company-uuid',
2253
+ 'internal_id' => 'ORD-101',
2254
+ 'type' => 'transport',
2255
+ 'status' => 'dispatched',
2256
+ 'meta' => ['priority' => 'high'],
2257
+ ])
2258
+ ->and((new ProofResource($proof))->resolve($request))->toMatchArray([
2259
+ 'id' => 101,
2260
+ 'uuid' => 'fixture-uuid',
2261
+ 'public_id' => 'fixture_public',
2262
+ 'subject_id' => 'subject_public',
2263
+ 'order_id' => 'order_public',
2264
+ 'url' => 'https://cdn.test/proof.png',
2265
+ 'remarks' => 'Delivered at reception.',
2266
+ 'raw' => ['signature' => true],
2267
+ 'data' => ['name' => 'Receiver'],
2268
+ ])
2269
+ ->and((new IndexCustomerResource($person))->resolve($request))->toMatchArray([
2270
+ 'id' => 101,
2271
+ 'uuid' => 'fixture-uuid',
2272
+ 'public_id' => 'fixture_public',
2273
+ 'company_uuid' => 'company-uuid',
2274
+ 'name' => 'Compact Person',
2275
+ 'phone' => '+6555554444',
2276
+ 'email' => 'person@example.test',
2277
+ ])
2278
+ ->and((new IndexFacilitatorResource($person))->resolve($request))->toMatchArray([
2279
+ 'id' => 101,
2280
+ 'uuid' => 'fixture-uuid',
2281
+ 'public_id' => 'fixture_public',
2282
+ 'company_uuid' => 'company-uuid',
2283
+ 'name' => 'Compact Person',
2284
+ 'phone' => '+6555554444',
2285
+ 'email' => 'person@example.test',
2286
+ ])
2287
+ ->and((new IndexTrackingNumberResource($trackingNumber))->resolve($request))->toMatchArray([
2288
+ 'id' => 101,
2289
+ 'uuid' => 'fixture-uuid',
2290
+ 'tracking_number' => 'TN-101',
2291
+ 'qr_code' => 'qr-data',
2292
+ ])
2293
+ ->and((new InternalOrderConfigResource($internalConfig))->resolve($request))->toMatchArray([
2294
+ 'id' => 101,
2295
+ 'uuid' => 'fixture-uuid',
2296
+ 'public_id' => 'fixture_public',
2297
+ 'company_uuid' => 'company-uuid',
2298
+ 'author_uuid' => 'author-uuid',
2299
+ 'category_uuid' => 'category-uuid',
2300
+ 'icon_uuid' => 'icon-uuid',
2301
+ 'name' => 'Delivery',
2302
+ 'namespace' => 'fleet-ops',
2303
+ 'description' => 'Delivery config.',
2304
+ 'key' => 'delivery',
2305
+ 'status' => 'active',
2306
+ 'version' => 3,
2307
+ 'core_service' => true,
2308
+ 'flow' => [['code' => 'created']],
2309
+ 'entities' => [['type' => 'parcel']],
2310
+ 'tags' => ['last-mile'],
2311
+ 'meta' => ['default' => true],
2312
+ 'type' => 'order-config',
2313
+ ]);
2314
+ });
2315
+
2316
+ test('orchestrator order resource serializes loaded workbench relationships', function () {
2317
+ $request = fleetopsCompactResourceRequest(true);
2318
+
2319
+ $customFieldValue = fleetopsCompactResourceFixture([
2320
+ 'uuid' => 'custom-value-uuid',
2321
+ 'custom_field_uuid' => 'custom-field-uuid',
2322
+ 'value' => 'dock-door-4',
2323
+ 'value_type' => 'string',
2324
+ ], [
2325
+ 'customField' => fleetopsCompactResourceFixture([
2326
+ 'uuid' => 'custom-field-uuid',
2327
+ 'name' => 'dock_door',
2328
+ 'label' => 'Dock Door',
2329
+ 'type' => 'text',
2330
+ 'required' => 1,
2331
+ ]),
2332
+ ]);
2333
+ $order = fleetopsCompactResourceFixture([
2334
+ 'internal_id' => 'ORCH-101',
2335
+ 'company_uuid' => 'company-uuid',
2336
+ 'payload_uuid' => 'payload-uuid',
2337
+ 'order_config_uuid' => 'config-uuid',
2338
+ 'driver_assigned_uuid' => 'driver-uuid',
2339
+ 'vehicle_assigned_uuid' => 'vehicle-uuid',
2340
+ 'trackingNumber' => (object) ['tracking_number' => 'TN-ORCH'],
2341
+ 'type' => 'transport',
2342
+ 'status' => 'assigned',
2343
+ 'notes' => 'Workbench card.',
2344
+ 'adhoc' => 1,
2345
+ 'dispatched' => 1,
2346
+ 'has_driver_assigned' => true,
2347
+ 'is_scheduled' => true,
2348
+ 'orchestrator_priority' => 7,
2349
+ 'time_window_start' => '09:00',
2350
+ 'time_window_end' => '11:00',
2351
+ 'required_skills' => ['liftgate'],
2352
+ 'scheduled_at' => '2026-07-28 09:00:00',
2353
+ 'dispatched_at' => '2026-07-28 08:45:00',
2354
+ 'started_at' => null,
2355
+ 'meta' => ['lane' => 'north'],
2356
+ ], [
2357
+ 'orderConfig' => fleetopsCompactResourceFixture([
2358
+ 'public_id' => 'config_public',
2359
+ 'name' => 'Delivery',
2360
+ 'key' => 'delivery',
2361
+ ]),
2362
+ 'payload' => fleetopsCompactResourceFixture([
2363
+ 'company_uuid' => 'company-uuid',
2364
+ 'pickup_uuid' => 'pickup-uuid',
2365
+ 'dropoff_uuid' => 'dropoff-uuid',
2366
+ 'return_uuid' => 'return-uuid',
2367
+ 'index_pickup_place' => null,
2368
+ 'index_dropoff_place' => null,
2369
+ 'type' => 'parcel',
2370
+ ], [
2371
+ 'entities' => new Collection([(object) ['public_id' => 'entity_public']]),
2372
+ 'waypoints' => new Collection([(object) ['public_id' => 'waypoint_public']]),
2373
+ ]),
2374
+ 'driverAssigned' => fleetopsCompactResourceFixture([
2375
+ 'company_uuid' => 'company-uuid',
2376
+ 'user_uuid' => 'user-uuid',
2377
+ 'vehicle_uuid' => 'vehicle-uuid',
2378
+ 'vendor_uuid' => 'vendor-uuid',
2379
+ 'current_job_uuid' => 'order-uuid',
2380
+ 'name' => 'Jane Driver',
2381
+ 'vehicle_name' => 'Truck 101',
2382
+ 'email' => 'jane@example.test',
2383
+ 'status' => 'on_duty',
2384
+ 'location' => null,
2385
+ 'heading' => 180,
2386
+ 'altitude' => 12,
2387
+ 'speed' => 42,
2388
+ 'online' => true,
2389
+ ]),
2390
+ 'vehicleAssigned' => fleetopsCompactResourceFixture([
2391
+ 'company_uuid' => 'company-uuid',
2392
+ 'vendor_uuid' => 'vendor-uuid',
2393
+ 'photo_uuid' => 'photo-uuid',
2394
+ 'internal_id' => 'VEH-ORCH',
2395
+ 'display_name' => 'Orchestrator Truck',
2396
+ 'driver_name' => 'Jane Driver',
2397
+ 'plate_number' => 'ORCH-101',
2398
+ 'serial_number' => 'SER-ORCH',
2399
+ 'fuel_card_number' => 'FUEL-ORCH',
2400
+ 'vin' => 'VIN-ORCH',
2401
+ 'make' => 'Ford',
2402
+ 'model' => 'Transit',
2403
+ 'year' => 2026,
2404
+ 'photo_url' => 'https://cdn.test/orch-truck.png',
2405
+ 'status' => 'assigned',
2406
+ 'location' => null,
2407
+ 'heading' => 90,
2408
+ 'altitude' => 10,
2409
+ 'speed' => 38,
2410
+ 'online' => true,
2411
+ ]),
2412
+ 'customFieldValues' => new Collection([$customFieldValue]),
2413
+ ]);
2414
+
2415
+ $payload = (new OrchestratorOrderResource($order))->resolve($request);
2416
+
2417
+ expect($payload)->toMatchArray([
2418
+ 'id' => 101,
2419
+ 'uuid' => 'fixture-uuid',
2420
+ 'public_id' => 'fixture_public',
2421
+ 'internal_id' => 'ORCH-101',
2422
+ 'company_uuid' => 'company-uuid',
2423
+ 'payload_uuid' => 'payload-uuid',
2424
+ 'order_config_uuid' => 'config-uuid',
2425
+ 'driver_assigned_uuid' => 'driver-uuid',
2426
+ 'vehicle_assigned_uuid' => 'vehicle-uuid',
2427
+ 'tracking' => 'TN-ORCH',
2428
+ 'order_config' => [
2429
+ 'id' => 'config_public',
2430
+ 'name' => 'Delivery',
2431
+ 'key' => 'delivery',
2432
+ ],
2433
+ 'type' => 'transport',
2434
+ 'status' => 'assigned',
2435
+ 'notes' => 'Workbench card.',
2436
+ 'adhoc' => true,
2437
+ 'dispatched' => true,
2438
+ 'has_driver_assigned' => true,
2439
+ 'is_scheduled' => true,
2440
+ 'orchestrator_priority' => 7,
2441
+ 'time_window_start' => '09:00',
2442
+ 'time_window_end' => '11:00',
2443
+ 'required_skills' => ['liftgate'],
2444
+ 'meta' => ['lane' => 'north'],
2445
+ ])
2446
+ ->and($payload['payload']->resolve($request))->toMatchArray([
2447
+ 'id' => 101,
2448
+ 'uuid' => 'fixture-uuid',
2449
+ 'public_id' => 'fixture_public',
2450
+ 'company_uuid' => 'company-uuid',
2451
+ 'pickup_uuid' => 'pickup-uuid',
2452
+ 'dropoff_uuid' => 'dropoff-uuid',
2453
+ 'return_uuid' => 'return-uuid',
2454
+ 'entities_count' => 1,
2455
+ 'waypoints_count' => 1,
2456
+ 'type' => 'parcel',
2457
+ ])
2458
+ ->and($payload['custom_field_values']->all())->toMatchArray([
2459
+ [
2460
+ 'id' => 'custom-value-uuid',
2461
+ 'uuid' => 'custom-value-uuid',
2462
+ 'custom_field_uuid' => 'custom-field-uuid',
2463
+ 'value' => 'dock-door-4',
2464
+ 'value_type' => 'string',
2465
+ 'custom_field' => [
2466
+ 'id' => 'custom-field-uuid',
2467
+ 'uuid' => 'custom-field-uuid',
2468
+ 'name' => 'dock_door',
2469
+ 'label' => 'Dock Door',
2470
+ 'type' => 'text',
2471
+ 'required' => true,
2472
+ ],
2473
+ ],
2474
+ ]);
2475
+ });
2476
+
2477
+ test('internal vehicle vendor and waypoint resources expose internal compact fields', function () {
2478
+ $request = fleetopsCompactResourceRequest(true);
2479
+
2480
+ if (!Arr::hasMacro('insertAfterKey')) {
2481
+ Arr::macro('insertAfterKey', function (array $array, array $insert, string $afterKey): array {
2482
+ $offset = array_search($afterKey, array_keys($array), true);
2483
+
2484
+ if ($offset === false) {
2485
+ return array_merge($array, $insert);
2486
+ }
2487
+
2488
+ return array_slice($array, 0, $offset + 1, true) + $insert + array_slice($array, $offset + 1, null, true);
2489
+ });
2490
+ }
2491
+
2492
+ $vehicle = fleetopsCompactResourceFixture([
2493
+ 'internal_id' => 'VEH-INTERNAL',
2494
+ 'company_uuid' => 'company-uuid',
2495
+ 'vendor_uuid' => 'vendor-uuid',
2496
+ 'category_uuid' => 'category-uuid',
2497
+ 'warranty_uuid' => 'warranty-uuid',
2498
+ 'telematic_uuid' => 'telematic-uuid',
2499
+ 'photo_uuid' => 'photo-uuid',
2500
+ 'photo_url' => 'https://cdn.test/vehicle.png',
2501
+ 'avatar_url' => 'https://cdn.test/avatar.png',
2502
+ 'name' => 'Internal Vehicle',
2503
+ 'display_name' => 'Internal Van',
2504
+ 'driver_name' => 'Jane Driver',
2505
+ 'vendor_name' => 'Vendor One',
2506
+ 'description' => 'Internal fleet vehicle.',
2507
+ 'make' => 'Ford',
2508
+ 'model' => 'Transit',
2509
+ 'year' => 2026,
2510
+ 'plate_number' => 'INT-101',
2511
+ 'serial_number' => 'SER-INT',
2512
+ 'fuel_card_number' => 'FUEL-INT',
2513
+ 'vin' => 'VIN-INT',
2514
+ 'status' => 'available',
2515
+ 'online' => true,
2516
+ 'location' => null,
2517
+ 'heading' => 15,
2518
+ 'altitude' => 4,
2519
+ 'speed' => 18,
2520
+ 'measurement_system' => 'metric',
2521
+ 'odometer' => 1200,
2522
+ 'odometer_unit' => 'km',
2523
+ 'fuel_type' => 'diesel',
2524
+ 'currency' => 'SGD',
2525
+ 'meta' => ['yard' => 'east'],
2526
+ 'notes' => 'Washed weekly.',
2527
+ ], [
2528
+ 'devices' => new Collection([(object) ['uuid' => 'device-uuid']]),
2529
+ ]);
2530
+ $vendor = fleetopsCompactResourceFixture([
2531
+ 'name' => 'Fuel Vendor',
2532
+ 'photo_url' => 'https://cdn.test/vendor.png',
2533
+ 'provider' => 'shell',
2534
+ 'options' => ['region' => 'sg'],
2535
+ 'sandbox' => true,
2536
+ 'type' => 'fuel',
2537
+ 'status' => 'active',
2538
+ ]);
2539
+ $place = fleetopsCompactResourceFixture([
2540
+ 'company_uuid' => 'company-uuid',
2541
+ 'owner_uuid' => 'owner-uuid',
2542
+ 'owner_type' => 'Fleetbase\\FleetOps\\Models\\Contact',
2543
+ 'name' => 'Waypoint Place',
2544
+ 'address' => '12 Route Way',
2545
+ 'street1' => '12 Route Way',
2546
+ 'city' => 'Singapore',
2547
+ 'country' => 'SG',
2548
+ 'avatar_url' => 'https://cdn.test/waypoint.png',
2549
+ 'location' => null,
2550
+ 'meta' => ['dock' => 'A'],
2551
+ ]);
2552
+ $waypoint = fleetopsCompactResourceFixture([
2553
+ 'place' => $place,
2554
+ 'tracking_number_uuid' => 'tracking-uuid',
2555
+ 'tracking' => 'TN-WAYPOINT',
2556
+ 'status' => 'arrived',
2557
+ 'status_code' => 'arrived',
2558
+ ]);
2559
+
2560
+ expect((new InternalVehicleResource($vehicle))->resolve($request))->toMatchArray([
2561
+ 'id' => 101,
2562
+ 'uuid' => 'fixture-uuid',
2563
+ 'public_id' => 'fixture_public',
2564
+ 'internal_id' => 'VEH-INTERNAL',
2565
+ 'company_uuid' => 'company-uuid',
2566
+ 'vendor_uuid' => 'vendor-uuid',
2567
+ 'category_uuid' => 'category-uuid',
2568
+ 'warranty_uuid' => 'warranty-uuid',
2569
+ 'telematic_uuid' => 'telematic-uuid',
2570
+ 'photo_uuid' => 'photo-uuid',
2571
+ 'display_name' => 'Internal Van',
2572
+ 'driver_name' => 'Jane Driver',
2573
+ 'vendor_name' => 'Vendor One',
2574
+ 'make' => 'Ford',
2575
+ 'model' => 'Transit',
2576
+ 'year' => 2026,
2577
+ 'plate_number' => 'INT-101',
2578
+ 'status' => 'available',
2579
+ 'online' => true,
2580
+ 'measurement_system' => 'metric',
2581
+ 'odometer' => 1200,
2582
+ 'fuel_type' => 'diesel',
2583
+ 'currency' => 'SGD',
2584
+ 'heading' => 15,
2585
+ 'altitude' => 4,
2586
+ 'speed' => 18,
2587
+ 'meta' => ['yard' => 'east'],
2588
+ ])
2589
+ ->and((new VehicleWithoutDriverResource($vehicle))->toWebhookPayload())->toMatchArray([
2590
+ 'id' => 'fixture_public',
2591
+ 'internal_id' => 'VEH-INTERNAL',
2592
+ 'name' => 'Internal Vehicle',
2593
+ 'display_name' => 'Internal Van',
2594
+ 'description' => 'Internal fleet vehicle.',
2595
+ 'vin' => 'VIN-INT',
2596
+ 'plate_number' => 'INT-101',
2597
+ 'serial_number' => 'SER-INT',
2598
+ 'fuel_card_number' => 'FUEL-INT',
2599
+ 'make' => 'Ford',
2600
+ 'model' => 'Transit',
2601
+ 'year' => 2026,
2602
+ 'photo_url' => 'https://cdn.test/vehicle.png',
2603
+ 'avatar_url' => 'https://cdn.test/avatar.png',
2604
+ 'status' => 'available',
2605
+ 'online' => true,
2606
+ 'measurement_system' => 'metric',
2607
+ 'odometer' => 1200,
2608
+ 'odometer_unit' => 'km',
2609
+ 'fuel_type' => 'diesel',
2610
+ 'currency' => 'SGD',
2611
+ 'heading' => 15,
2612
+ 'altitude' => 4,
2613
+ 'speed' => 18,
2614
+ 'notes' => 'Washed weekly.',
2615
+ 'meta' => ['yard' => 'east'],
2616
+ ])
2617
+ ->and((new InternalIntegratedVendorFacilitatorResource($vendor))->resolve($request))->toMatchArray([
2618
+ 'id' => 101,
2619
+ 'uuid' => 'fixture-uuid',
2620
+ 'public_id' => 'fixture_public',
2621
+ 'name' => 'Fuel Vendor',
2622
+ 'photo_url' => 'https://cdn.test/vendor.png',
2623
+ 'provider' => 'shell',
2624
+ 'options' => ['region' => 'sg'],
2625
+ 'sandbox' => true,
2626
+ 'facilitator_type' => 'fuel',
2627
+ 'type' => 'facilitator',
2628
+ 'status' => 'active',
2629
+ ])
2630
+ ->and((new InternalWaypointResource($waypoint))->resolve($request))->toMatchArray([
2631
+ 'uuid' => 'fixture-uuid',
2632
+ 'waypoint_uuid' => 'fixture-uuid',
2633
+ 'waypoint_public_id' => 'fixture_public',
2634
+ 'tracking_number_uuid' => 'tracking-uuid',
2635
+ 'tracking' => 'TN-WAYPOINT',
2636
+ 'status' => 'arrived',
2637
+ 'status_code' => 'arrived',
2638
+ 'name' => 'Waypoint Place',
2639
+ 'address' => '12 Route Way',
2640
+ 'city' => 'Singapore',
2641
+ 'country' => 'SG',
2642
+ 'meta' => ['dock' => 'A'],
2643
+ ]);
2644
+ });
2645
+
2646
+ test('tracking number and waypoint webhook resources serialize tracking contracts', function () {
2647
+ $trackingNumber = fleetopsCompactResourceFixture([
2648
+ 'status_uuid' => 'status-uuid',
2649
+ 'owner_uuid' => 'owner-uuid',
2650
+ 'owner_type' => 'Fleetbase\\FleetOps\\Models\\Order',
2651
+ 'owner' => (object) ['public_id' => 'order_public'],
2652
+ 'tracking_number' => 'TN-ZERO',
2653
+ 'region' => 'sg',
2654
+ 'last_status' => 'Created',
2655
+ 'last_status_code' => 'created',
2656
+ 'qr_code' => 'qr-data',
2657
+ 'barcode' => 'barcode-data',
2658
+ ]);
2659
+ $waypoint = fleetopsCompactResourceFixture([
2660
+ 'internal_id' => 'WAYPOINT-101',
2661
+ 'name' => 'Waypoint Stop',
2662
+ 'type' => 'dropoff',
2663
+ 'destination' => (object) ['public_id' => 'place_public'],
2664
+ 'customer_type' => null,
2665
+ 'customer_uuid' => null,
2666
+ 'trackingNumber' => $trackingNumber,
2667
+ 'description' => 'Dropoff at reception.',
2668
+ 'photo_url' => 'https://cdn.test/waypoint-proof.png',
2669
+ 'length' => 10,
2670
+ 'width' => 8,
2671
+ 'height' => 6,
2672
+ 'dimensions_unit' => 'cm',
2673
+ 'weight' => 2.5,
2674
+ 'weight_unit' => 'kg',
2675
+ 'declared_value' => 125,
2676
+ 'price' => 140,
2677
+ 'sale_price' => 120,
2678
+ 'sku' => 'WAY-SKU',
2679
+ 'currency' => 'SGD',
2680
+ 'meta' => ['sequence' => 2],
2681
+ ]);
2682
+ $order = fleetopsCompactResourceFixture([
2683
+ 'internal_id' => 'ORDER-WEBHOOK',
2684
+ 'customer' => null,
2685
+ 'payload' => null,
2686
+ 'facilitator' => null,
2687
+ 'driverAssigned' => null,
2688
+ 'trackingNumber' => $trackingNumber,
2689
+ 'purchaseRate' => null,
2690
+ 'notes' => 'Webhook order.',
2691
+ 'type' => 'transport',
2692
+ 'status' => 'created',
2693
+ 'adhoc' => true,
2694
+ 'meta' => ['channel' => 'api'],
2695
+ 'dispatched_at' => null,
2696
+ 'started_at' => null,
2697
+ 'scheduled_at' => '2026-07-30 09:00:00',
2698
+ ]);
2699
+
2700
+ expect((new TrackingNumberResource($trackingNumber))->toWebhookPayload())->toMatchArray([
2701
+ 'id' => 'fixture_public',
2702
+ 'tracking_number' => 'TN-ZERO',
2703
+ 'subject' => 'order_public',
2704
+ 'region' => 'sg',
2705
+ 'qr_code' => 'qr-data',
2706
+ 'barcode' => 'barcode-data',
2707
+ 'type' => 'order',
2708
+ ])
2709
+ ->and((new OrderResource($order))->toWebhookPayload())->toMatchArray([
2710
+ 'id' => 'fixture_public',
2711
+ 'internal_id' => 'ORDER-WEBHOOK',
2712
+ 'customer' => null,
2713
+ 'facilitator' => null,
2714
+ 'notes' => 'Webhook order.',
2715
+ 'type' => 'transport',
2716
+ 'status' => 'created',
2717
+ 'adhoc' => true,
2718
+ 'meta' => ['channel' => 'api'],
2719
+ 'dispatched_at' => null,
2720
+ 'started_at' => null,
2721
+ 'scheduled_at' => '2026-07-30 09:00:00',
2722
+ ])
2723
+ ->and((new WaypointResource($waypoint))->toWebhookPayload())->toMatchArray([
2724
+ 'id' => 'fixture_public',
2725
+ 'internal_id' => 'WAYPOINT-101',
2726
+ 'name' => 'Waypoint Stop',
2727
+ 'type' => 'dropoff',
2728
+ 'destination' => 'place_public',
2729
+ 'customer' => null,
2730
+ 'description' => 'Dropoff at reception.',
2731
+ 'photo_url' => 'https://cdn.test/waypoint-proof.png',
2732
+ 'length' => 10,
2733
+ 'width' => 8,
2734
+ 'height' => 6,
2735
+ 'dimensions_unit' => 'cm',
2736
+ 'weight' => 2.5,
2737
+ 'weight_unit' => 'kg',
2738
+ 'declared_value' => 125,
2739
+ 'price' => 140,
2740
+ 'sale_price' => 120,
2741
+ 'sku' => 'WAY-SKU',
2742
+ 'currency' => 'SGD',
2743
+ 'meta' => ['sequence' => 2],
2744
+ ]);
2745
+ });