@fleetbase/fleetops-engine 0.6.48 → 0.6.50

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 (266) hide show
  1. package/addon/components/customer/create-order-form.hbs +2 -2
  2. package/addon/components/customer/form.hbs +2 -2
  3. package/addon/components/customer/order-form.hbs +2 -2
  4. package/addon/components/customer/orders.hbs +2 -2
  5. package/addon/components/display-place.hbs +2 -2
  6. package/addon/components/driver/details.hbs +5 -1
  7. package/addon/components/driver/form.hbs +11 -1
  8. package/addon/components/fleet-listing-panel.hbs +2 -2
  9. package/addon/components/fleet-listing-panel.js +10 -0
  10. package/addon/components/map/google-live-map.js +18 -75
  11. package/addon/components/map/leaflet-live-map.hbs +187 -37
  12. package/addon/components/map/leaflet-live-map.js +184 -20
  13. package/addon/components/modals/order-change-destination.hbs +46 -0
  14. package/addon/components/modals/order-change-destination.js +106 -0
  15. package/addon/components/modals/stop-activity.hbs +28 -0
  16. package/addon/components/modals/stop-activity.js +52 -0
  17. package/addon/components/modals/update-order-activity.hbs +45 -3
  18. package/addon/components/modals/update-order-activity.js +143 -5
  19. package/addon/components/orchestrator/phase-builder.hbs +21 -0
  20. package/addon/components/orchestrator/phase-builder.js +13 -0
  21. package/addon/components/orchestrator/resource-panel.hbs +0 -10
  22. package/addon/components/orchestrator/resource-panel.js +2 -4
  23. package/addon/components/orchestrator-workbench.js +1 -0
  24. package/addon/components/order/activity-list.hbs +2 -2
  25. package/addon/components/order/activity-timeline.hbs +31 -29
  26. package/addon/components/order/details/activity.hbs +3 -3
  27. package/addon/components/order/details/activity.js +11 -0
  28. package/addon/components/order/details/documents.js +2 -0
  29. package/addon/components/order/details/purchase-rate.hbs +2 -2
  30. package/addon/components/order/details/route.hbs +11 -3
  31. package/addon/components/order/details/route.js +91 -14
  32. package/addon/components/order/details/tracking.hbs +101 -72
  33. package/addon/components/order/details/tracking.js +149 -2
  34. package/addon/components/order/form/details.hbs +3 -3
  35. package/addon/components/order/form/documents.js +11 -4
  36. package/addon/components/order/form/route.hbs +108 -104
  37. package/addon/components/order/form/route.js +37 -1
  38. package/addon/components/order/form/service-rate.hbs +2 -2
  39. package/addon/components/order/form.hbs +2 -2
  40. package/addon/components/order-config-manager/activity-flow.hbs +4 -1
  41. package/addon/components/order-config-manager/activity-flow.js +178 -12
  42. package/addon/components/place/address.js +1 -1
  43. package/addon/components/route-list.hbs +17 -17
  44. package/addon/components/route-list.js +42 -10
  45. package/addon/components/service-rate/details.hbs +41 -0
  46. package/addon/components/service-rate/form.hbs +118 -0
  47. package/addon/components/service-rate/form.js +55 -0
  48. package/addon/components/vehicle/form.js +1 -1
  49. package/addon/components/vehicle/pill.hbs +1 -1
  50. package/addon/components/widget/fleet-ops-key-metrics.hbs +19 -17
  51. package/addon/components/widget/fleet-ops-key-metrics.js +26 -0
  52. package/addon/components/widget/fuel-efficiency.hbs +26 -0
  53. package/addon/components/widget/fuel-efficiency.js +43 -0
  54. package/addon/components/widget/geofence-violations.hbs +44 -0
  55. package/addon/components/widget/geofence-violations.js +55 -0
  56. package/addon/components/widget/issues-insights.hbs +43 -0
  57. package/addon/components/widget/issues-insights.js +62 -0
  58. package/addon/components/widget/kpi-active-orders.hbs +10 -0
  59. package/addon/components/widget/kpi-active-orders.js +5 -0
  60. package/addon/components/widget/kpi-aov.hbs +10 -0
  61. package/addon/components/widget/kpi-aov.js +5 -0
  62. package/addon/components/widget/kpi-distance.hbs +10 -0
  63. package/addon/components/widget/kpi-distance.js +5 -0
  64. package/addon/components/widget/kpi-drivers-online.hbs +10 -0
  65. package/addon/components/widget/kpi-drivers-online.js +5 -0
  66. package/addon/components/widget/kpi-earnings.hbs +10 -0
  67. package/addon/components/widget/kpi-earnings.js +5 -0
  68. package/addon/components/widget/kpi-open-issues.hbs +11 -0
  69. package/addon/components/widget/kpi-open-issues.js +5 -0
  70. package/addon/components/widget/kpi-tile.hbs +59 -0
  71. package/addon/components/widget/kpi-tile.js +168 -0
  72. package/addon/components/widget/live-fleet.hbs +122 -0
  73. package/addon/components/widget/live-fleet.js +365 -0
  74. package/addon/components/widget/maintenance-overview.hbs +45 -0
  75. package/addon/components/widget/maintenance-overview.js +40 -0
  76. package/addon/components/widget/on-time-delivery.hbs +27 -0
  77. package/addon/components/widget/on-time-delivery.js +73 -0
  78. package/addon/components/widget/operations-pulse.hbs +51 -0
  79. package/addon/components/widget/operations-pulse.js +173 -0
  80. package/addon/components/widget/orders-by-status.hbs +26 -0
  81. package/addon/components/widget/orders-by-status.js +54 -0
  82. package/addon/components/widget/revenue-trend.hbs +34 -0
  83. package/addon/components/widget/revenue-trend.js +67 -0
  84. package/addon/components/widget/top-drivers.hbs +44 -0
  85. package/addon/components/widget/top-drivers.js +54 -0
  86. package/addon/controllers/management/vehicles/index/new.js +1 -1
  87. package/addon/controllers/operations/orders/index/details.js +4 -4
  88. package/addon/controllers/operations/orders/index.js +14 -0
  89. package/addon/controllers/settings/routing.js +26 -0
  90. package/addon/extension.js +172 -3
  91. package/addon/helpers/place-address-html.js +2 -2
  92. package/addon/routes/operations/service-rates/index.js +1 -1
  93. package/addon/services/geofence.js +163 -19
  94. package/addon/services/leaflet-contextmenu-manager.js +27 -0
  95. package/addon/services/map-adapter/google.js +13 -2
  96. package/addon/services/vehicle-actions.js +1 -1
  97. package/addon/styles/fleetops-engine.css +205 -88
  98. package/addon/templates/maintenance/equipment/index.hbs +1 -1
  99. package/addon/templates/maintenance/parts/index.hbs +1 -1
  100. package/addon/templates/maintenance/schedules/index.hbs +1 -1
  101. package/addon/templates/management/vehicles/index.hbs +1 -1
  102. package/addon/templates/settings/avatars.hbs +1 -1
  103. package/addon/templates/settings/custom-fields.hbs +1 -1
  104. package/addon/templates/settings/map.hbs +1 -1
  105. package/addon/templates/settings/navigator-app.hbs +1 -1
  106. package/addon/templates/settings/notifications.hbs +1 -1
  107. package/addon/templates/settings/orchestrator.hbs +1 -1
  108. package/addon/templates/settings/payments/index.hbs +1 -1
  109. package/addon/templates/settings/payments/onboard.hbs +1 -1
  110. package/addon/templates/settings/routing.hbs +22 -2
  111. package/addon/templates/settings/scheduling.hbs +1 -1
  112. package/addon/utils/fleet-ops-options.js +2 -1
  113. package/addon/utils/live-map-card-content.js +171 -0
  114. package/addon/utils/normalize-order-config-flow.js +41 -0
  115. package/addon/utils/place-address-html.js +3 -2
  116. package/addon/utils/route-visualization.js +54 -7
  117. package/app/components/modals/order-change-destination.js +1 -0
  118. package/app/components/modals/stop-activity.js +1 -0
  119. package/app/components/widget/fuel-efficiency.js +1 -0
  120. package/app/components/widget/geofence-violations.js +1 -0
  121. package/app/components/widget/issues-insights.js +1 -0
  122. package/app/components/widget/kpi-active-orders.js +1 -0
  123. package/app/components/widget/kpi-aov.js +1 -0
  124. package/app/components/widget/kpi-distance.js +1 -0
  125. package/app/components/widget/kpi-drivers-online.js +1 -0
  126. package/app/components/widget/kpi-earnings.js +1 -0
  127. package/app/components/widget/kpi-open-issues.js +1 -0
  128. package/app/components/widget/kpi-tile.js +1 -0
  129. package/app/components/widget/live-fleet.js +1 -0
  130. package/app/components/widget/maintenance-overview.js +1 -0
  131. package/app/components/widget/on-time-delivery.js +1 -0
  132. package/app/components/widget/operations-pulse.js +1 -0
  133. package/app/components/widget/orders-by-status.js +1 -0
  134. package/app/components/widget/revenue-trend.js +1 -0
  135. package/app/components/widget/top-drivers.js +1 -0
  136. package/app/utils/live-map-card-content.js +1 -0
  137. package/composer.json +1 -1
  138. package/extension.json +1 -1
  139. package/package.json +3 -3
  140. package/pnpm-workspace.yaml +1 -0
  141. package/server/config/telematics.php +111 -1
  142. package/server/migrations/2026_05_18_000001_add_license_expiry_to_drivers_table.php +25 -0
  143. package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
  144. package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
  145. package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
  146. package/server/resources/views/labels/default.php +57 -49
  147. package/server/resources/views/labels/waypoint-label.php +27 -20
  148. package/server/seeders/Testing/Concerns/SeedsTestingData.php +183 -0
  149. package/server/seeders/Testing/ConnectivitySeeder.php +213 -0
  150. package/server/seeders/Testing/FleetSeeder.php +341 -0
  151. package/server/seeders/Testing/MaintenanceSeeder.php +238 -0
  152. package/server/seeders/Testing/NetworkSeeder.php +250 -0
  153. package/server/seeders/Testing/OrdersSeeder.php +198 -0
  154. package/server/seeders/Testing/TestingSeeder.php +40 -0
  155. package/server/seeders/{VroomOrchestrationSeeder.php → Testing/VroomOrchestrationSeeder.php} +29 -22
  156. package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
  157. package/server/src/Console/Commands/ProcessOperationalAlerts.php +279 -0
  158. package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
  159. package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
  160. package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
  161. package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
  162. package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
  163. package/server/src/Http/Controllers/Api/v1/OrderController.php +98 -70
  164. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
  165. package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
  166. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +119 -0
  167. package/server/src/Http/Controllers/Internal/v1/ContactController.php +19 -0
  168. package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
  169. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +82 -3
  170. package/server/src/Http/Controllers/Internal/v1/OrderController.php +441 -32
  171. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
  172. package/server/src/Http/Controllers/Internal/v1/SettingController.php +46 -1
  173. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
  174. package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
  175. package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
  176. package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
  177. package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
  178. package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
  179. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
  180. package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
  181. package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
  182. package/server/src/Http/Resources/v1/Customer.php +79 -0
  183. package/server/src/Http/Resources/v1/Driver.php +2 -0
  184. package/server/src/Http/Resources/v1/Fleet.php +4 -0
  185. package/server/src/Http/Resources/v1/Index/Driver.php +48 -0
  186. package/server/src/Http/Resources/v1/Index/Vehicle.php +74 -1
  187. package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
  188. package/server/src/Http/Resources/v1/Payload.php +23 -20
  189. package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
  190. package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
  191. package/server/src/Listeners/HandleOrderDispatched.php +2 -2
  192. package/server/src/Models/Contact.php +92 -15
  193. package/server/src/Models/Customer.php +64 -0
  194. package/server/src/Models/Driver.php +14 -3
  195. package/server/src/Models/Entity.php +2 -2
  196. package/server/src/Models/Order.php +29 -4
  197. package/server/src/Models/OrderConfig.php +14 -11
  198. package/server/src/Models/Payload.php +17 -1
  199. package/server/src/Models/ServiceRate.php +273 -1
  200. package/server/src/Models/ServiceRateFee.php +39 -9
  201. package/server/src/Models/TrackingNumber.php +8 -2
  202. package/server/src/Models/Vehicle.php +18 -2
  203. package/server/src/Models/Waypoint.php +2 -2
  204. package/server/src/Notifications/LateDeparture.php +48 -0
  205. package/server/src/Notifications/ProlongedStoppage.php +48 -0
  206. package/server/src/Notifications/RouteDeviation.php +48 -0
  207. package/server/src/Observers/ContactObserver.php +2 -0
  208. package/server/src/Observers/ServiceRateObserver.php +6 -6
  209. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +29 -2
  210. package/server/src/Providers/FleetOpsServiceProvider.php +6 -0
  211. package/server/src/Providers/NotificationServiceProvider.php +3 -0
  212. package/server/src/Support/Analytics/AbstractAnalytics.php +77 -0
  213. package/server/src/Support/Analytics/FuelEfficiency.php +83 -0
  214. package/server/src/Support/Analytics/GeofenceViolations.php +63 -0
  215. package/server/src/Support/Analytics/IssuesInsights.php +67 -0
  216. package/server/src/Support/Analytics/LiveFleet.php +127 -0
  217. package/server/src/Support/Analytics/MaintenanceOverview.php +70 -0
  218. package/server/src/Support/Analytics/OnTimeDelivery.php +75 -0
  219. package/server/src/Support/Analytics/OperationsPulse.php +86 -0
  220. package/server/src/Support/Analytics/OrdersByStatus.php +64 -0
  221. package/server/src/Support/Analytics/RevenueTrend.php +78 -0
  222. package/server/src/Support/Analytics/TopDrivers.php +92 -0
  223. package/server/src/Support/CustomerAuth.php +98 -0
  224. package/server/src/Support/LabelPdf.php +52 -0
  225. package/server/src/Support/Metrics/AbstractMetric.php +170 -0
  226. package/server/src/Support/Metrics/ActiveLiveOrdersMetric.php +28 -0
  227. package/server/src/Support/Metrics/AvgOrderValueMetric.php +53 -0
  228. package/server/src/Support/Metrics/DriversOnlineMetric.php +30 -0
  229. package/server/src/Support/Metrics/EarningsMetric.php +30 -0
  230. package/server/src/Support/Metrics/FuelCostsMetric.php +30 -0
  231. package/server/src/Support/Metrics/MoneyMetric.php +30 -0
  232. package/server/src/Support/Metrics/OpenIssuesMetric.php +35 -0
  233. package/server/src/Support/Metrics/OrdersCanceledMetric.php +35 -0
  234. package/server/src/Support/Metrics/OrdersCompletedMetric.php +35 -0
  235. package/server/src/Support/Metrics/OrdersInProgressMetric.php +51 -0
  236. package/server/src/Support/Metrics/OrdersScheduledMetric.php +37 -0
  237. package/server/src/Support/Metrics/Registry.php +46 -0
  238. package/server/src/Support/Metrics/ResolvedIssuesMetric.php +35 -0
  239. package/server/src/Support/Metrics/TotalCustomersMetric.php +29 -0
  240. package/server/src/Support/Metrics/TotalDistanceTraveledMetric.php +35 -0
  241. package/server/src/Support/Metrics/TotalDriversMetric.php +28 -0
  242. package/server/src/Support/Metrics/TotalTimeTraveledMetric.php +35 -0
  243. package/server/src/Support/Metrics.php +46 -249
  244. package/server/src/Support/ResolvesOrderServiceStops.php +512 -0
  245. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +290 -0
  246. package/server/src/Support/Telematics/Providers/SafeeProvider.php +333 -0
  247. package/server/src/Tracking/TrackingContextBuilder.php +41 -38
  248. package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
  249. package/server/src/Tracking/TrackingStop.php +13 -11
  250. package/server/src/routes.php +50 -0
  251. package/server/tests/AnalyticsRoutesTest.php +41 -0
  252. package/server/tests/CapacityAllocationEngineTest.php +10 -10
  253. package/server/tests/CustomerContactIdentitySafetyTest.php +54 -0
  254. package/server/tests/CustomerEndpointTest.php +199 -0
  255. package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
  256. package/server/tests/LabelPdfRenderingTest.php +23 -0
  257. package/server/tests/LiveControllerViewportTest.php +60 -0
  258. package/server/tests/MetricsRegistryTest.php +84 -0
  259. package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
  260. package/server/tests/OrderConfigEndpointTest.php +86 -0
  261. package/server/tests/OrderFileAttachmentTest.php +20 -0
  262. package/server/tests/ServiceAreaZoneApiShapeTest.php +34 -0
  263. package/server/tests/VehicleDriverAssignmentTest.php +18 -0
  264. package/server/tests/VroomOrchestrationEngineTest.php +91 -0
  265. package/translations/en-us.yaml +38 -0
  266. package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
@@ -5,10 +5,12 @@ namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
5
5
  use Fleetbase\FleetOps\Exports\VehicleExport;
6
6
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
7
7
  use Fleetbase\FleetOps\Imports\VehicleImport;
8
+ use Fleetbase\FleetOps\Models\Driver;
8
9
  use Fleetbase\FleetOps\Models\Vehicle;
9
10
  use Fleetbase\Http\Requests\ExportRequest;
10
11
  use Fleetbase\Http\Requests\ImportRequest;
11
12
  use Illuminate\Http\Request;
13
+ use Illuminate\Support\Arr;
12
14
  use Illuminate\Support\Facades\DB;
13
15
  use Illuminate\Support\Str;
14
16
  use Maatwebsite\Excel\Facades\Excel;
@@ -25,14 +27,65 @@ class VehicleController extends FleetOpsController
25
27
  /**
26
28
  * Handle post save transactions.
27
29
  */
28
- public function afterSave(Request $request, Vehicle $vehicle)
30
+ public function afterSave(Request $request, Vehicle $vehicle, array $input = [])
29
31
  {
32
+ if ($this->hasDriverInput($request)) {
33
+ $this->syncDriverAssignment($vehicle, $this->driverIdentifierFromRequest($request));
34
+ }
35
+
30
36
  $customFieldValues = $request->array('vehicle.custom_field_values');
31
37
  if ($customFieldValues) {
32
38
  $vehicle->syncCustomFieldValues($customFieldValues);
33
39
  }
34
40
  }
35
41
 
42
+ protected function hasDriverInput(Request $request): bool
43
+ {
44
+ $payload = $request->all();
45
+
46
+ return Arr::has($payload, 'vehicle.driver_uuid')
47
+ || Arr::has($payload, 'vehicle.driver.uuid')
48
+ || Arr::has($payload, 'vehicle.driver.id')
49
+ || Arr::has($payload, 'driver_uuid')
50
+ || Arr::has($payload, 'driver');
51
+ }
52
+
53
+ protected function driverIdentifierFromRequest(Request $request): ?string
54
+ {
55
+ $payload = $request->all();
56
+ $driver = data_get($payload, 'vehicle.driver') ?? data_get($payload, 'driver');
57
+
58
+ return data_get($payload, 'vehicle.driver_uuid')
59
+ ?? data_get($payload, 'vehicle.driver.uuid')
60
+ ?? data_get($payload, 'vehicle.driver.id')
61
+ ?? (is_string($driver) ? $driver : null)
62
+ ?? data_get($payload, 'driver_uuid')
63
+ ?? data_get($payload, 'driver.uuid')
64
+ ?? data_get($payload, 'driver.id');
65
+ }
66
+
67
+ protected function syncDriverAssignment(Vehicle $vehicle, ?string $identifier): void
68
+ {
69
+ if (empty($identifier)) {
70
+ $vehicle->unassignDriver();
71
+
72
+ return;
73
+ }
74
+
75
+ $driver = Driver::withoutGlobalScopes()
76
+ ->whereNull('deleted_at')
77
+ ->where(function ($query) use ($identifier) {
78
+ $query->where('uuid', $identifier)->orWhere('public_id', $identifier);
79
+ })
80
+ ->when(session('company'), fn ($query, $company) => $query->where('company_uuid', $company))
81
+ ->first();
82
+
83
+ if ($driver) {
84
+ $vehicle->assignDriver($driver);
85
+ $vehicle->setRelation('driver', $driver);
86
+ }
87
+ }
88
+
36
89
  /**
37
90
  * Get all status options for an vehicle.
38
91
  *
@@ -0,0 +1,34 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Middleware;
4
+
5
+ use Fleetbase\FleetOps\Support\CustomerAuth;
6
+ use Illuminate\Http\Request;
7
+
8
+ /**
9
+ * Enforce that a Customer-Token header is present and resolves to a
10
+ * customer-type Contact that belongs to the same company as the request's
11
+ * API credential.
12
+ *
13
+ * Apply this middleware to authenticated `/v1/customers/...` routes. The
14
+ * public sign-up / login routes do NOT use it.
15
+ */
16
+ class AuthenticateCustomerToken
17
+ {
18
+ public function handle(Request $request, \Closure $next)
19
+ {
20
+ $customer = CustomerAuth::resolveFromHeader($request);
21
+ if (!$customer) {
22
+ return response()->apiError('Customer token is missing or invalid.', 401);
23
+ }
24
+
25
+ $sessionCompany = session('company');
26
+ if ($sessionCompany && $customer->company_uuid !== $sessionCompany) {
27
+ return response()->apiError('Customer does not belong to this company.', 403);
28
+ }
29
+
30
+ CustomerAuth::setCurrent($customer);
31
+
32
+ return $next($request);
33
+ }
34
+ }
@@ -0,0 +1,63 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Requests;
4
+
5
+ use Fleetbase\Http\Requests\FleetbaseRequest;
6
+
7
+ /**
8
+ * Validates the body of `POST /v1/customers/orders`.
9
+ *
10
+ * Mirrors the canonical Fleet-Ops Order create shape used by the operator
11
+ * `POST /v1/orders` endpoint: `type` / `order_config`, `scheduled_at`,
12
+ * `notes`, `meta`, and either a nested `payload` or top-level
13
+ * `pickup` / `dropoff` / `return` / `waypoints` / `entities`. Place and
14
+ * Entity sub-objects accept the standard Fleet-Ops Place / Entity field
15
+ * shapes — no client-portal aliases are recognized.
16
+ *
17
+ * `customer` is intentionally not accepted: the controller forces
18
+ * `customer_uuid` from the authenticated Customer-Token. `status`,
19
+ * `driver`, `vehicle`, `facilitator`, `dispatch` and similar operator-tier
20
+ * fields are also out of scope for the customer surface.
21
+ */
22
+ class CreateCustomerOrderRequest extends FleetbaseRequest
23
+ {
24
+ public function authorize(): bool
25
+ {
26
+ return request()->session()->has('api_credential')
27
+ || request()->session()->has('is_sanctum_token');
28
+ }
29
+
30
+ public function rules(): array
31
+ {
32
+ return [
33
+ 'type' => 'nullable|string',
34
+ 'order_config' => 'nullable|string',
35
+ 'scheduled_at' => 'nullable|date',
36
+ 'notes' => 'nullable|string|max:2000',
37
+ 'internal_id' => 'nullable|string|max:191',
38
+ 'meta' => 'nullable|array',
39
+ // Optional ServiceQuote reference (uuid or `sqte_…` public_id).
40
+ // Resolved server-side via `ServiceQuote::resolveFromRequest` and
41
+ // consumed by `$order->purchaseServiceQuote()` after creation.
42
+ 'service_quote' => 'nullable|string',
43
+
44
+ // payload may be an object OR a payload public_id string
45
+ 'payload' => 'nullable',
46
+
47
+ // Top-level alternatives when `payload` is not provided. Each
48
+ // Place sub-object accepts the standard Place fillable shape.
49
+ 'pickup' => 'nullable',
50
+ 'dropoff' => 'nullable',
51
+ 'return' => 'nullable',
52
+ 'waypoints' => 'nullable|array',
53
+ 'entities' => 'nullable|array',
54
+ 'entities.*.name' => 'nullable|string',
55
+ 'entities.*.description' => 'nullable|string',
56
+ 'entities.*.weight' => 'nullable|numeric|min:0',
57
+ 'entities.*.weight_unit' => 'nullable|string',
58
+ 'entities.*.declared_value' => 'nullable|numeric|min:0',
59
+ 'entities.*.currency' => 'nullable|string|size:3',
60
+ 'entities.*.meta' => 'nullable|array',
61
+ ];
62
+ }
63
+ }
@@ -0,0 +1,48 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Requests;
4
+
5
+ use Fleetbase\Http\Requests\FleetbaseRequest;
6
+ use Illuminate\Validation\Rule;
7
+
8
+ /**
9
+ * Validates the body of `POST /v1/customers` (final account creation step).
10
+ *
11
+ * Expects the verification code previously requested via
12
+ * `POST /v1/customers/request-creation-code`.
13
+ */
14
+ class CreateCustomerRequest extends FleetbaseRequest
15
+ {
16
+ public function authorize(): bool
17
+ {
18
+ return request()->session()->has('api_credential')
19
+ || request()->session()->has('is_sanctum_token');
20
+ }
21
+
22
+ public function rules(): array
23
+ {
24
+ return [
25
+ 'identity' => 'required|string',
26
+ 'code' => 'required|exists:verification_codes,code',
27
+ 'name' => 'required|string',
28
+ 'password' => 'required|string|min:8',
29
+ 'email' => [
30
+ 'email', 'nullable',
31
+ Rule::unique('contacts')->where(function ($query) {
32
+ $query->where('company_uuid', session('company'));
33
+
34
+ return $query->whereNull('deleted_at');
35
+ }),
36
+ ],
37
+ 'phone' => [
38
+ 'nullable', 'string',
39
+ Rule::unique('contacts')->where(function ($query) {
40
+ $query->where('company_uuid', session('company'));
41
+
42
+ return $query->whereNull('deleted_at');
43
+ }),
44
+ ],
45
+ 'meta' => 'nullable|array',
46
+ ];
47
+ }
48
+ }
@@ -35,7 +35,7 @@ class CreateDriverRequest extends FleetbaseRequest
35
35
  'country' => 'nullable|size:2',
36
36
  'city' => 'nullable|string',
37
37
  'vehicle' => 'nullable|string|starts_with:vehicle_|exists:vehicles,public_id',
38
- 'status' => 'nullable|string|in:active,inactive',
38
+ 'status' => 'nullable|string|in:active,available,inactive',
39
39
  'vendor' => 'nullable|exists:vendors,public_id',
40
40
  'job' => 'nullable|exists:orders,public_id',
41
41
  'location' => ['nullable', new ResolvablePoint()],
@@ -4,6 +4,7 @@ namespace Fleetbase\FleetOps\Http\Requests;
4
4
 
5
5
  use Fleetbase\FleetOps\Rules\ResolvablePoint;
6
6
  use Fleetbase\Http\Requests\FleetbaseRequest;
7
+ use Illuminate\Validation\Rule;
7
8
 
8
9
  class CreateVehicleRequest extends FleetbaseRequest
9
10
  {
@@ -25,7 +26,30 @@ class CreateVehicleRequest extends FleetbaseRequest
25
26
  public function rules()
26
27
  {
27
28
  return [
28
- 'status' => 'nullable|in:operational,maintenance,decommissioned',
29
+ 'status' => [
30
+ 'nullable',
31
+ Rule::in([
32
+ 'active',
33
+ 'available',
34
+ 'in_use',
35
+ 'maintenance',
36
+ 'out_of_service',
37
+ 'reserved',
38
+ 'retired',
39
+ 'staging',
40
+ 'on_route',
41
+ 'idle',
42
+ 'cleaning',
43
+ 'awaiting_parts',
44
+ 'inspection_due',
45
+ 'inspection_failed',
46
+ 'accident',
47
+ 'compliance_hold',
48
+ 'stolen',
49
+ 'operational',
50
+ 'decommissioned',
51
+ ]),
52
+ ],
29
53
  'vendor' => 'nullable|exists:vendors,public_id',
30
54
  'driver' => 'nullable|exists:drivers,public_id',
31
55
  'location' => ['nullable', new ResolvablePoint()],
@@ -51,7 +51,7 @@ class CreateDriverRequest extends CreateDriverApiRequest
51
51
  'country' => 'nullable|string|size:2',
52
52
  'city' => 'nullable|string|max:255',
53
53
  'vehicle' => ['nullable', new ResolvableVehicle()],
54
- 'status' => 'nullable|string|in:active,inactive',
54
+ 'status' => 'nullable|string|in:active,available,inactive',
55
55
  'vendor' => 'nullable|exists:vendors,public_id',
56
56
  'job' => 'nullable|exists:orders,public_id',
57
57
  'location' => ['nullable', new ResolvablePoint()],
@@ -0,0 +1,29 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Requests;
4
+
5
+ use Fleetbase\Http\Requests\FleetbaseRequest;
6
+
7
+ /**
8
+ * Validates the body of `POST /v1/customers/request-creation-code`.
9
+ */
10
+ class VerifyCreateCustomerRequest extends FleetbaseRequest
11
+ {
12
+ public function authorize(): bool
13
+ {
14
+ return request()->session()->has('api_credential')
15
+ || request()->session()->has('is_sanctum_token');
16
+ }
17
+
18
+ public function rules(): array
19
+ {
20
+ return [
21
+ 'mode' => 'required|in:email,sms',
22
+ 'identity' => 'required|string',
23
+ // Optional profile context — when provided, used to greet the
24
+ // customer in the verification email and pre-seed the User row.
25
+ 'name' => 'nullable|string|max:255',
26
+ 'phone' => 'nullable|string|max:32',
27
+ ];
28
+ }
29
+ }
@@ -0,0 +1,41 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Resources\Internal\v1;
4
+
5
+ use Fleetbase\Http\Resources\FleetbaseResource;
6
+
7
+ class OrderConfig extends FleetbaseResource
8
+ {
9
+ /**
10
+ * Transform the resource into an array.
11
+ *
12
+ * @param \Illuminate\Http\Request $request
13
+ */
14
+ public function toArray($request): array
15
+ {
16
+ return [
17
+ 'id' => $this->id,
18
+ 'uuid' => $this->uuid,
19
+ 'public_id' => $this->public_id,
20
+ 'company_uuid' => $this->company_uuid,
21
+ 'author_uuid' => $this->author_uuid,
22
+ 'category_uuid' => $this->category_uuid,
23
+ 'icon_uuid' => $this->icon_uuid,
24
+ 'name' => $this->name,
25
+ 'namespace' => $this->namespace,
26
+ 'description' => $this->description,
27
+ 'key' => $this->key,
28
+ 'status' => $this->status,
29
+ 'version' => $this->version,
30
+ 'core_service' => (bool) $this->core_service,
31
+ 'flow' => $this->flow ?? [],
32
+ 'entities' => $this->entities ?? [],
33
+ 'tags' => $this->tags ?? [],
34
+ 'meta' => $this->meta ?? [],
35
+ 'created_at' => $this->created_at,
36
+ 'updated_at' => $this->updated_at,
37
+ 'deleted_at' => $this->deleted_at,
38
+ 'type' => 'order-config',
39
+ ];
40
+ }
41
+ }
@@ -0,0 +1,79 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Resources\v1;
4
+
5
+ use Fleetbase\FleetOps\Models\Order;
6
+ use Fleetbase\FleetOps\Support\Utils;
7
+ use Fleetbase\Http\Resources\FleetbaseResource;
8
+ use Fleetbase\Models\Company;
9
+ use Fleetbase\Support\Http;
10
+ use Illuminate\Http\Request;
11
+ use Illuminate\Support\Str;
12
+
13
+ /**
14
+ * Public API resource for FleetOps customers.
15
+ *
16
+ * Wraps a {@see \Fleetbase\FleetOps\Models\Contact} of `type='customer'` and
17
+ * exposes the auth `token` field that login/signup endpoints attach.
18
+ */
19
+ class Customer extends FleetbaseResource
20
+ {
21
+ public function toArray($request): array
22
+ {
23
+ $this->loadMissing(['place', 'places']);
24
+
25
+ return [
26
+ 'id' => $this->when(Http::isInternalRequest(), $this->id, Str::replaceFirst('contact', 'customer', $this->public_id)),
27
+ 'uuid' => $this->when(Http::isInternalRequest(), $this->uuid),
28
+ 'user_uuid' => $this->when(Http::isInternalRequest(), $this->user_uuid),
29
+ 'company_uuid' => $this->when(Http::isInternalRequest(), $this->company_uuid),
30
+ 'public_id' => $this->when(Http::isInternalRequest(), $this->public_id),
31
+ 'internal_id' => $this->internal_id,
32
+ 'name' => $this->name,
33
+ 'title' => $this->title,
34
+ 'photo_url' => $this->photo_url,
35
+ 'email' => $this->email,
36
+ 'phone' => $this->phone,
37
+ 'address' => data_get($this, 'place.address'),
38
+ 'addresses' => $this->whenLoaded('places', fn () => Place::collection($this->places)),
39
+ 'token' => $this->when($this->token, $this->token),
40
+ 'orders_count' => $this->getOrdersCount($request),
41
+ 'company' => $this->when($this->company_uuid, fn () => $this->buildCompanyPayload()),
42
+ 'meta' => data_get($this, 'meta', Utils::createObject()),
43
+ 'slug' => $this->slug,
44
+ 'created_at' => $this->created_at,
45
+ 'updated_at' => $this->updated_at,
46
+ ];
47
+ }
48
+
49
+ private function getOrdersCount(Request $request): int
50
+ {
51
+ return Order::where('customer_uuid', $this->uuid)
52
+ ->whereNull('deleted_at')
53
+ ->withoutGlobalScopes()
54
+ ->count();
55
+ }
56
+
57
+ /**
58
+ * Public-safe projection of the customer's company. Returns the same
59
+ * fields any caller could fetch through other public endpoints — name,
60
+ * resolved currency, country, phone — plus the company's public id.
61
+ * Currency falls back through company.currency → ledger base_currency
62
+ * → "USD" via {@see Utils::getCompanyTransactionCurrency}.
63
+ */
64
+ private function buildCompanyPayload(): ?array
65
+ {
66
+ $company = Company::find($this->company_uuid);
67
+ if (!$company) {
68
+ return null;
69
+ }
70
+
71
+ return [
72
+ 'id' => $company->public_id,
73
+ 'name' => $company->name,
74
+ 'currency' => Utils::getCompanyTransactionCurrency($company),
75
+ 'country' => $company->country,
76
+ 'phone' => $company->phone,
77
+ ];
78
+ }
79
+ }
@@ -38,6 +38,7 @@ class Driver extends FleetbaseResource
38
38
  'email' => $this->email,
39
39
  'phone' => $this->phone,
40
40
  'drivers_license_number' => $this->drivers_license_number,
41
+ 'license_expiry' => $this->license_expiry,
41
42
  'photo_url' => $this->photo_url,
42
43
  'avatar_url' => $this->avatar_url,
43
44
  'avatar_value' => $this->when(Http::isInternalRequest(), $this->getOriginal('avatar_url')),
@@ -94,6 +95,7 @@ class Driver extends FleetbaseResource
94
95
  'email' => $this->email,
95
96
  'phone' => $this->phone,
96
97
  'photo_url' => $this->photo_url,
98
+ 'license_expiry' => $this->license_expiry,
97
99
  'vehicle' => data_get($this, 'vehicle.public_id'),
98
100
  'current_job' => data_get($this, 'currentJob.public_id'),
99
101
  'vendor' => data_get($this, 'vendor.public_id'),
@@ -23,6 +23,10 @@ class Fleet extends FleetbaseResource
23
23
  }, $request->array('with'));
24
24
 
25
25
  $this->load($with);
26
+
27
+ if (in_array('subfleets', $with, true) && in_array('vehicles', $with, true)) {
28
+ $this->loadMissing('subFleets.vehicles');
29
+ }
26
30
  }
27
31
 
28
32
  return $this->withCustomFields([
@@ -33,6 +33,7 @@ class Driver extends FleetbaseResource
33
33
  'current_job_uuid'=> $this->when($isInternal, $this->current_job_uuid),
34
34
  'name' => $this->name,
35
35
  'vehicle_name' => $this->when($isInternal, $this->vehicle_name),
36
+ 'email' => $this->email,
36
37
  'phone' => $this->phone,
37
38
  'photo_url' => $this->photo_url,
38
39
  'status' => $this->status,
@@ -41,6 +42,53 @@ class Driver extends FleetbaseResource
41
42
  'altitude' => (int) data_get($this, 'altitude', 0),
42
43
  'speed' => (int) data_get($this, 'speed', 0),
43
44
  'online' => data_get($this, 'online', false),
45
+ 'meta' => [
46
+ '_index_resource' => true,
47
+ 'location_coordinates' => $this->locationCoordinates(),
48
+ 'current_order_reference' => $this->currentOrderReference(),
49
+ 'speed_label' => $this->speedLabel(),
50
+ 'heading_label' => $this->headingLabel(),
51
+ 'status_label' => $this->statusLabel(),
52
+ ],
44
53
  ];
45
54
  }
55
+
56
+ protected function currentOrderReference(): ?string
57
+ {
58
+ $this->loadMissing('currentOrder');
59
+ $order = data_get($this, 'currentOrder');
60
+
61
+ return data_get($order, 'tracking') ?? data_get($order, 'public_id');
62
+ }
63
+
64
+ protected function locationCoordinates(): ?string
65
+ {
66
+ $location = Utils::castPoint($this->location);
67
+
68
+ return $location ? $this->formatCoordinate($location->getLat()) . ' ' . $this->formatCoordinate($location->getLng()) : null;
69
+ }
70
+
71
+ protected function speedLabel(): string
72
+ {
73
+ $speed = data_get($this, 'speed');
74
+
75
+ return is_numeric($speed) ? ((int) $speed) . ' km/h' : '-';
76
+ }
77
+
78
+ protected function headingLabel(): string
79
+ {
80
+ $heading = data_get($this, 'heading');
81
+
82
+ return is_numeric($heading) ? ((int) $heading) . ' deg' : '-';
83
+ }
84
+
85
+ protected function statusLabel(): ?string
86
+ {
87
+ return $this->status ? str($this->status)->replace(['_', '-'], ' ')->headline()->toString() : null;
88
+ }
89
+
90
+ protected function formatCoordinate(float $coordinate): string
91
+ {
92
+ return (string) round($coordinate, 4);
93
+ }
46
94
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  namespace Fleetbase\FleetOps\Http\Resources\v1\Index;
4
4
 
5
+ use Fleetbase\FleetOps\Models\Order;
5
6
  use Fleetbase\FleetOps\Support\Utils;
6
7
  use Fleetbase\Http\Resources\FleetbaseResource;
7
8
  use Fleetbase\Support\Http;
@@ -28,8 +29,12 @@ class Vehicle extends FleetbaseResource
28
29
  'company_uuid' => $this->when($isInternal, $this->company_uuid),
29
30
  'vendor_uuid' => $this->when($isInternal, $this->vendor_uuid),
30
31
  'photo_uuid' => $this->when($isInternal, $this->photo_uuid),
32
+ 'internal_id' => $this->internal_id,
31
33
  'display_name' => $this->display_name,
34
+ 'driver_name' => $this->driver_name,
32
35
  'plate_number' => $this->plate_number,
36
+ 'serial_number' => $this->serial_number,
37
+ 'vin' => $this->vin,
33
38
  'make' => $this->make,
34
39
  'model' => $this->model,
35
40
  'year' => $this->year,
@@ -43,8 +48,76 @@ class Vehicle extends FleetbaseResource
43
48
 
44
49
  // Meta flag to indicate this is an index resource
45
50
  'meta' => [
46
- '_index_resource' => true,
51
+ '_index_resource' => true,
52
+ 'current_order_reference' => $this->currentOrderReference(),
53
+ 'location_coordinates' => $this->locationCoordinates(),
54
+ 'speed_label' => $this->speedLabel(),
55
+ 'heading_label' => $this->headingLabel(),
56
+ 'status_label' => $this->statusLabel(),
47
57
  ],
48
58
  ];
49
59
  }
60
+
61
+ protected function currentOrderReference(): ?string
62
+ {
63
+ $this->loadMissing('driver.currentOrder');
64
+ $order = data_get($this, 'driver.currentOrder') ?? $this->currentPositionOrder() ?? $this->currentVehicleOrder();
65
+
66
+ return data_get($order, 'tracking') ?? data_get($order, 'public_id');
67
+ }
68
+
69
+ protected function locationCoordinates(): ?string
70
+ {
71
+ $location = Utils::castPoint($this->location);
72
+
73
+ return $location ? $this->formatCoordinate($location->getLat()) . ' ' . $this->formatCoordinate($location->getLng()) : null;
74
+ }
75
+
76
+ protected function speedLabel(): string
77
+ {
78
+ $speed = data_get($this->lastKnownPosition(), 'speed', data_get($this, 'speed'));
79
+
80
+ return is_numeric($speed) ? ((int) $speed) . ' km/h' : '-';
81
+ }
82
+
83
+ protected function headingLabel(): string
84
+ {
85
+ $heading = data_get($this->lastKnownPosition(), 'heading', data_get($this, 'heading'));
86
+
87
+ return is_numeric($heading) ? ((int) $heading) . ' deg' : '-';
88
+ }
89
+
90
+ protected function statusLabel(): ?string
91
+ {
92
+ return $this->status ? str($this->status)->replace(['_', '-'], ' ')->headline()->toString() : null;
93
+ }
94
+
95
+ protected function currentPositionOrder(): ?Order
96
+ {
97
+ $orderUuid = data_get($this->lastKnownPosition(), 'order_uuid');
98
+
99
+ return $orderUuid ? Order::where('uuid', $orderUuid)->first() : null;
100
+ }
101
+
102
+ protected function currentVehicleOrder(): ?Order
103
+ {
104
+ return Order::where('vehicle_assigned_uuid', $this->uuid)
105
+ ->whereNotIn('status', ['completed', 'canceled', 'cancelled'])
106
+ ->latest()
107
+ ->first();
108
+ }
109
+
110
+ protected function lastKnownPosition()
111
+ {
112
+ if (!$this->resource->relationLoaded('last_known_position')) {
113
+ $this->resource->setRelation('last_known_position', $this->getLastKnownPosition());
114
+ }
115
+
116
+ return $this->resource->getRelation('last_known_position');
117
+ }
118
+
119
+ protected function formatCoordinate(float $coordinate): string
120
+ {
121
+ return (string) round($coordinate, 4);
122
+ }
50
123
  }