@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
@@ -50,7 +50,7 @@ class DriverController extends Controller
50
50
  $input = $request->except(['name', 'password', 'email', 'phone', 'location', 'altitude', 'heading', 'speed', 'meta']);
51
51
 
52
52
  // Add default status
53
- $input['status'] = 'active';
53
+ $input['status'] = $request->input('status', 'available');
54
54
 
55
55
  // get user details for driver
56
56
  $userDetails = $request->only(['name', 'password', 'email', 'phone', 'timezone']);
@@ -0,0 +1,54 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
4
+
5
+ use Fleetbase\FleetOps\Http\Resources\v1\OrderConfig as OrderConfigResource;
6
+ use Fleetbase\FleetOps\Models\OrderConfig;
7
+ use Fleetbase\Http\Controllers\Controller;
8
+ use Illuminate\Database\Eloquent\ModelNotFoundException;
9
+ use Illuminate\Http\Request;
10
+
11
+ /**
12
+ * Public, read-only API surface for OrderConfigs.
13
+ *
14
+ * The internal admin console manages the full OrderConfig lifecycle through
15
+ * `/int/v1/order-configs`. This public endpoint exposes the projected,
16
+ * non-sensitive subset (flow activities + metadata) that consumers like a
17
+ * customer portal need to render status filter chips, activity labels, and
18
+ * other UI driven by the active flow.
19
+ */
20
+ class OrderConfigController extends Controller
21
+ {
22
+ /**
23
+ * List the OrderConfigs for the company resolved from the API credential.
24
+ *
25
+ * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
26
+ */
27
+ public function query(Request $request)
28
+ {
29
+ $results = OrderConfig::queryWithRequest($request);
30
+
31
+ return OrderConfigResource::collection($results);
32
+ }
33
+
34
+ /**
35
+ * Find a single OrderConfig. The `{id}` segment accepts any identifier
36
+ * supported by {@see OrderConfig::resolveFromIdentifier} — `uuid`,
37
+ * `public_id`, `namespace`, or short `key` (e.g. `transport`).
38
+ *
39
+ * @return OrderConfigResource|\Illuminate\Http\JsonResponse
40
+ */
41
+ public function find(string $id)
42
+ {
43
+ $orderConfig = OrderConfig::resolveFromIdentifier($id);
44
+ if (!$orderConfig) {
45
+ try {
46
+ $orderConfig = OrderConfig::findRecordOrFail($id);
47
+ } catch (ModelNotFoundException $e) {
48
+ return response()->apiError('Order config not found.', 404);
49
+ }
50
+ }
51
+
52
+ return new OrderConfigResource($orderConfig);
53
+ }
54
+ }
@@ -5,6 +5,7 @@ namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
5
5
  use Fleetbase\FleetOps\Events\OrderDispatchFailed;
6
6
  use Fleetbase\FleetOps\Events\OrderReady;
7
7
  use Fleetbase\FleetOps\Events\OrderStarted;
8
+ use Fleetbase\FleetOps\Exceptions\CustomerUserConflictException;
8
9
  use Fleetbase\FleetOps\Exceptions\UserAlreadyExistsException;
9
10
  use Fleetbase\FleetOps\Flow\Activity;
10
11
  use Fleetbase\FleetOps\Http\Requests\CreateOrderRequest;
@@ -23,6 +24,7 @@ use Fleetbase\FleetOps\Models\Place;
23
24
  use Fleetbase\FleetOps\Models\Proof;
24
25
  use Fleetbase\FleetOps\Models\ServiceQuote;
25
26
  use Fleetbase\FleetOps\Models\Waypoint;
27
+ use Fleetbase\FleetOps\Support\ResolvesOrderServiceStops;
26
28
  use Fleetbase\FleetOps\Support\Utils;
27
29
  use Fleetbase\Http\Controllers\Controller;
28
30
  use Fleetbase\Http\Resources\Comment as CommentResource;
@@ -43,6 +45,8 @@ use Illuminate\Validation\ValidationException;
43
45
 
44
46
  class OrderController extends Controller
45
47
  {
48
+ use ResolvesOrderServiceStops;
49
+
46
50
  /**
47
51
  * Creates a new Fleetbase Order resource.
48
52
  *
@@ -251,6 +255,13 @@ class OrderController extends Controller
251
255
  $customer = Arr::only($customer, ['internal_id', 'name', 'title', 'email', 'phone', 'meta']);
252
256
 
253
257
  try {
258
+ $customerCandidate = new Contact([
259
+ ...$customer,
260
+ 'company_uuid' => session('company'),
261
+ 'type' => 'customer',
262
+ ]);
263
+ $customerCandidate->assertCustomerIdentityIsAvailable();
264
+
254
265
  $customer = Contact::firstOrCreate(
255
266
  [
256
267
  'company_uuid' => session('company'),
@@ -263,19 +274,12 @@ class OrderController extends Controller
263
274
  'type' => 'customer',
264
275
  ]
265
276
  );
277
+ } catch (CustomerUserConflictException $e) {
278
+ return response()->apiError($e->getMessage());
279
+ } catch (UserAlreadyExistsException $e) {
280
+ return response()->apiError($e->getMessage());
266
281
  } catch (\Exception $e) {
267
282
  return response()->apiError('Failed to find or create customer for order.');
268
- } catch (UserAlreadyExistsException $e) {
269
- try {
270
- // If user already exist then assign user to this customer and the company
271
- $existingUser = $e->getUser();
272
- // Assign user to customer
273
- if ($existingUser && $customer) {
274
- $customer->assignUser($existingUser);
275
- }
276
- } catch (\Exception $e) {
277
- return response()->apiError('Failed to find or create customer for order.');
278
- }
279
283
  }
280
284
 
281
285
  if (Str::isUuid($customer)) {
@@ -1043,7 +1047,17 @@ class OrderController extends Controller
1043
1047
  // if string $id
1044
1048
  if (!$order) {
1045
1049
  try {
1046
- $order = Order::findRecordOrFail($id, ['driverAssigned', 'payload.entities', 'payload.currentWaypoint', 'payload.waypoints']);
1050
+ $order = Order::findRecordOrFail($id, [
1051
+ 'driverAssigned',
1052
+ 'payload.entities',
1053
+ 'payload.pickup',
1054
+ 'payload.dropoff',
1055
+ 'payload.return',
1056
+ 'payload.currentWaypoint',
1057
+ 'payload.waypoints',
1058
+ 'payload.waypointMarkers.place',
1059
+ 'payload.waypointMarkers.trackingNumber.status',
1060
+ ]);
1047
1061
  } catch (ModelNotFoundException $exception) {
1048
1062
  return response()->apiError('Order resource not found.', 404);
1049
1063
  }
@@ -1094,37 +1108,8 @@ class OrderController extends Controller
1094
1108
  /** @var \Fleetbase\LaravelMysqlSpatial\Types\Point */
1095
1109
  $location = $order->getLastLocation();
1096
1110
 
1097
- // Check if multiple waypoint order to update activity for
1098
- $isMultipleWaypointOrder = (bool) $order->payload->isMultipleDropOrder;
1099
-
1100
- // if is multi drop order and no current destination set it
1101
- if ($isMultipleWaypointOrder && !$order->payload->current_waypoint_uuid) {
1102
- $order->payload->setFirstWaypoint($activity, $location);
1103
- }
1104
-
1105
- // Handle multiple dropoff waypoint activity
1106
- if (Utils::isActivity($activity) && $activity->completesOrder() && $isMultipleWaypointOrder) {
1107
- // Check if every waypoint is completed
1108
- $isCompleted = $order->payload->waypointMarkers->every(function ($waypoint) {
1109
- return $waypoint->complete;
1110
- });
1111
-
1112
- // only update activity for waypoint
1113
- if (!$isCompleted) {
1114
- $order->payload->updateWaypointActivity($activity, $location, $proof);
1115
- $order->payload->setNextWaypointDestination();
1116
- $order->payload->load('waypointMarkers');
1117
-
1118
- // recheck if order is completed
1119
- $isFullyCompleted = $order->payload->waypointMarkers->every(function ($waypoint) {
1120
- return $waypoint->complete;
1121
- });
1122
-
1123
- if (!$isFullyCompleted) {
1124
- return new OrderResource($order);
1125
- }
1126
- }
1127
- }
1111
+ // Check if multiple waypoint order to update activity for.
1112
+ $isMultipleWaypointOrder = $this->payloadHasWaypoints($order->payload);
1128
1113
 
1129
1114
  // also update for each order entities if not multiple drop order
1130
1115
  // all entities will share the same activity status as is one drop order
@@ -1136,23 +1121,48 @@ class OrderController extends Controller
1136
1121
  foreach ($order->payload->entities as $entity) {
1137
1122
  $entity->insertActivity($activity, $location, $proof);
1138
1123
  }
1139
- } else {
1140
- // Update parent order when status is `dispatched` or `started`
1141
- if (in_array($activity->code, ['started', 'dispatched'])) {
1142
- $order->updateActivity($activity, $proof);
1124
+ // Handle order completion
1125
+ if (Utils::isActivity($activity) && $activity->completesOrder()) {
1126
+ $order->driverAssigned?->unassignCurrentJob();
1127
+ $order->complete($this->resolveProof($proof));
1143
1128
  }
1144
1129
 
1145
- $order->payload->updateWaypointActivity($activity, $location);
1130
+ return new OrderResource($order);
1131
+ }
1132
+
1133
+ if ($activity->is('started')) {
1134
+ $order->updateActivity($activity, $proof);
1135
+
1136
+ $this->ensurePayloadCurrentServiceStop($order->payload);
1137
+ if (!$this->payloadHasCurrentServiceStopActivity($order->payload, $activity)) {
1138
+ $this->updateCurrentServiceStopActivity($order, $activity, $location, $proof);
1139
+ }
1140
+
1141
+ return new OrderResource($order->refresh());
1142
+ }
1143
+
1144
+ $currentStop = $this->ensurePayloadCurrentServiceStop($order->payload);
1145
+ $isCompletingCurrentStop = Utils::isActivity($activity) && $activity->complete();
1146
+
1147
+ $this->updateCurrentServiceStopActivity($order, $activity, $location, $proof);
1148
+
1149
+ if (Utils::isActivity($activity) && $activity->is('canceled')) {
1150
+ $order->driverAssigned?->unassignCurrentJob();
1151
+ $order->cancel();
1152
+
1153
+ return new OrderResource($order->refresh());
1146
1154
  }
1147
1155
 
1148
- // Handle order completion
1149
1156
  if (Utils::isActivity($activity) && $activity->completesOrder()) {
1150
- // unset from driver current job
1151
- $order->driverAssigned->unassignCurrentJob();
1152
- $order->complete();
1157
+ $nextStop = $isCompletingCurrentStop ? $this->advanceCurrentServiceStopDestination($order, $order->payload) : null;
1158
+
1159
+ if (!$nextStop || !$currentStop) {
1160
+ $order->driverAssigned?->unassignCurrentJob();
1161
+ $order->complete($this->resolveProof($proof));
1162
+ }
1153
1163
  }
1154
1164
 
1155
- return new OrderResource($order);
1165
+ return new OrderResource($order->refresh());
1156
1166
  }
1157
1167
 
1158
1168
  /**
@@ -1165,7 +1175,14 @@ class OrderController extends Controller
1165
1175
  $waypointId = $request->input('waypoint');
1166
1176
 
1167
1177
  try {
1168
- $order = Order::findRecordOrFail($id, ['payload']);
1178
+ $order = Order::findRecordOrFail($id, [
1179
+ 'payload.pickup',
1180
+ 'payload.dropoff',
1181
+ 'payload.return',
1182
+ 'payload.waypoints',
1183
+ 'payload.waypointMarkers.place',
1184
+ 'payload.waypointMarkers.trackingNumber.status',
1185
+ ]);
1169
1186
  } catch (ModelNotFoundException $exception) {
1170
1187
  return response()->json(
1171
1188
  [
@@ -1175,19 +1192,16 @@ class OrderController extends Controller
1175
1192
  );
1176
1193
  }
1177
1194
 
1178
- // Get waypoint record if available
1179
- $waypoint = null;
1195
+ $stop = null;
1180
1196
  if ($waypointId) {
1181
- $waypoint = Waypoint::where('payload_uuid', $order->payload_uuid)->whereHas('place', function ($query) use ($waypointId) {
1182
- $query->where('public_id', $waypointId);
1183
- })->first();
1197
+ $stop = $this->resolveServiceStopFromKey($order->payload, $waypointId);
1184
1198
  }
1185
1199
 
1186
1200
  $orderConfig = $order->ensureOrderConfig();
1187
1201
  if (!$orderConfig) {
1188
1202
  return response()->apiError('No order config found for order.');
1189
1203
  }
1190
- $activities = $orderConfig->nextActivity($waypoint);
1204
+ $activities = $stop ? $this->nextActivitiesForServiceStop($order, $order->payload, $stop) : $orderConfig->nextActivity();
1191
1205
 
1192
1206
  // If activity is to complete order add proof of delivery properties if required
1193
1207
  // This is a temporary fix until activity is updated to handle POD on it's own
@@ -1282,7 +1296,18 @@ class OrderController extends Controller
1282
1296
  public function setDestination(string $id, string $placeId)
1283
1297
  {
1284
1298
  try {
1285
- $order = Order::findRecordOrFail($id, ['payload.waypoints', 'payload.pickup', 'payload.dropoff', 'driverAssigned', 'vehicleAssigned', 'customer', 'facilitator']);
1299
+ $order = Order::findRecordOrFail($id, [
1300
+ 'payload.pickup',
1301
+ 'payload.dropoff',
1302
+ 'payload.return',
1303
+ 'payload.waypoints',
1304
+ 'payload.waypointMarkers.place',
1305
+ 'payload.waypointMarkers.trackingNumber.status',
1306
+ 'driverAssigned',
1307
+ 'vehicleAssigned',
1308
+ 'customer',
1309
+ 'facilitator',
1310
+ ]);
1286
1311
  } catch (ModelNotFoundException $exception) {
1287
1312
  return response()->apiError('Order resource not found.', 404);
1288
1313
  }
@@ -1290,17 +1315,13 @@ class OrderController extends Controller
1290
1315
  // Get the order payload
1291
1316
  $payload = $order->payload;
1292
1317
 
1293
- // Resolve the place
1294
- $place = $payload->waypoints->firstWhere('public_id', $placeId)
1295
- ?? (($payload->pickup && $payload->pickup->public_id === $placeId) ? $payload->pickup : null)
1296
- ?? (($payload->dropoff && $payload->dropoff->public_id === $placeId) ? $payload->dropoff : null);
1297
-
1298
- if (!$place) {
1318
+ $stop = $this->resolveServiceStopFromKey($payload, $placeId);
1319
+ if (!$stop) {
1299
1320
  return response()->apiError('Place resource is not a valid destination.', 422);
1300
1321
  }
1301
1322
 
1302
1323
  // Persist destination choice
1303
- $payload->setCurrentWaypoint($place);
1324
+ $this->setPayloadCurrentServiceStop($payload, $stop);
1304
1325
 
1305
1326
  return new OrderResource($order->refresh());
1306
1327
  }
@@ -1385,7 +1406,7 @@ class OrderController extends Controller
1385
1406
 
1386
1407
  // Load Order
1387
1408
  try {
1388
- $order = Order::findRecordOrFail($id);
1409
+ $order = Order::findRecordOrFail($id, ['payload.pickup', 'payload.dropoff', 'payload.return', 'payload.waypoints', 'payload.waypointMarkers.place']);
1389
1410
  } catch (ModelNotFoundException $e) {
1390
1411
  return response()->apiError('Order resource not found.', 404);
1391
1412
  }
@@ -1580,7 +1601,7 @@ class OrderController extends Controller
1580
1601
 
1581
1602
  // Load Order
1582
1603
  try {
1583
- $order = Order::findRecordOrFail($id);
1604
+ $order = Order::findRecordOrFail($id, ['payload.pickup', 'payload.dropoff', 'payload.return', 'payload.waypoints', 'payload.waypointMarkers.place']);
1584
1605
  } catch (ModelNotFoundException $e) {
1585
1606
  return response()->apiError('Order resource not found.', 404);
1586
1607
  }
@@ -1678,6 +1699,13 @@ class OrderController extends Controller
1678
1699
  return $order;
1679
1700
  }
1680
1701
 
1702
+ if (in_array($type, ['place', 'waypoint'], true)) {
1703
+ $stop = $this->resolveServiceStopFromKey($order->payload, $subjectId);
1704
+ if ($stop) {
1705
+ return ($stop['waypoint'] ?? null) instanceof Waypoint ? $stop['waypoint'] : ($stop['place'] ?? $order);
1706
+ }
1707
+ }
1708
+
1681
1709
  return match ($type) {
1682
1710
  'place', 'waypoint' => Waypoint::withoutGlobalScopes()
1683
1711
  ->where('payload_uuid', $order->payload_uuid)
@@ -24,7 +24,7 @@ class ServiceAreaController extends Controller
24
24
  public function create(CreateServiceAreaRequest $request)
25
25
  {
26
26
  // get request input
27
- $input = $request->only(['name', 'type', 'status', 'country', 'color', 'stroke_color', 'trigger_on_entry', 'trigger_on_exit', 'dwell_threshold_minutes', 'speed_limit_kmh']);
27
+ $input = $request->only(['name', 'type', 'status', 'country', 'border', 'color', 'stroke_color', 'trigger_on_entry', 'trigger_on_exit', 'dwell_threshold_minutes', 'speed_limit_kmh']);
28
28
 
29
29
  // get radius for creating service area border - default to 500 meters
30
30
  $radius = (int) $request->input('radius', 500);
@@ -99,7 +99,7 @@ class ServiceAreaController extends Controller
99
99
  }
100
100
 
101
101
  // get request input
102
- $input = $request->only(['name', 'type', 'status', 'country', 'color', 'stroke_color', 'trigger_on_entry', 'trigger_on_exit', 'dwell_threshold_minutes', 'speed_limit_kmh']);
102
+ $input = $request->only(['name', 'type', 'status', 'country', 'border', 'color', 'stroke_color', 'trigger_on_entry', 'trigger_on_exit', 'dwell_threshold_minutes', 'speed_limit_kmh']);
103
103
 
104
104
  // get radius for creating service area border - default to 500 meters
105
105
  $radius = $request->input('radius', 500);
@@ -67,7 +67,7 @@ class VehicleController extends Controller
67
67
  $vehicle = Vehicle::create($input);
68
68
 
69
69
  // driver assignment
70
- if ($request->has('driver')) {
70
+ if ($request->exists('driver') && !empty($request->input('driver'))) {
71
71
  // set this vehicle to the driver
72
72
  try {
73
73
  $driver = Driver::findRecordOrFail($request->input('driver'));
@@ -80,8 +80,7 @@ class VehicleController extends Controller
80
80
  );
81
81
  }
82
82
 
83
- $driver->vehicle_uuid = $vehicle->uuid;
84
- $driver->save();
83
+ $vehicle->assignDriver($driver);
85
84
  }
86
85
 
87
86
  // response the driver resource
@@ -150,6 +149,24 @@ class VehicleController extends Controller
150
149
  // save the update
151
150
  $vehicle->save();
152
151
 
152
+ if ($request->exists('driver')) {
153
+ if (empty($request->input('driver'))) {
154
+ $vehicle->unassignDriver();
155
+ } else {
156
+ try {
157
+ $driver = Driver::findRecordOrFail($request->input('driver'));
158
+ $vehicle->assignDriver($driver);
159
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
160
+ return response()->json(
161
+ [
162
+ 'error' => 'The driver attempted to assign this vehicle was not found.',
163
+ ],
164
+ 404
165
+ );
166
+ }
167
+ }
168
+ }
169
+
153
170
  // get udpated vehicle
154
171
  $vehicle = $vehicle->refresh();
155
172
 
@@ -0,0 +1,119 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
+
5
+ use Fleetbase\FleetOps\Support\Analytics\AbstractAnalytics;
6
+ use Fleetbase\FleetOps\Support\Analytics\FuelEfficiency;
7
+ use Fleetbase\FleetOps\Support\Analytics\GeofenceViolations;
8
+ use Fleetbase\FleetOps\Support\Analytics\IssuesInsights;
9
+ use Fleetbase\FleetOps\Support\Analytics\LiveFleet;
10
+ use Fleetbase\FleetOps\Support\Analytics\MaintenanceOverview;
11
+ use Fleetbase\FleetOps\Support\Analytics\OnTimeDelivery;
12
+ use Fleetbase\FleetOps\Support\Analytics\OperationsPulse;
13
+ use Fleetbase\FleetOps\Support\Analytics\OrdersByStatus;
14
+ use Fleetbase\FleetOps\Support\Analytics\RevenueTrend;
15
+ use Fleetbase\FleetOps\Support\Analytics\TopDrivers;
16
+ use Fleetbase\Http\Controllers\Controller;
17
+ use Illuminate\Http\Request;
18
+
19
+ /**
20
+ * One public method per dashboard widget. Each method:
21
+ * - Resolves a period from ?period=7d|30d|... or explicit start/end
22
+ * - Constructs the corresponding Support\Analytics\* helper
23
+ * - Returns the helper's JSON payload (Chart.js datasets, ranked rows,
24
+ * scalar tiles, etc. — shape is widget-specific)
25
+ *
26
+ * Cache headers are set per-widget by the route middleware in routes.php.
27
+ */
28
+ class AnalyticsController extends Controller
29
+ {
30
+ public function operationsPulse(Request $request)
31
+ {
32
+ return $this->run($request, OperationsPulse::class);
33
+ }
34
+
35
+ public function revenueTrend(Request $request)
36
+ {
37
+ return $this->run($request, RevenueTrend::class, function (RevenueTrend $a) use ($request) {
38
+ $a->groupBy($request->string('group_by', 'day')->toString());
39
+ });
40
+ }
41
+
42
+ public function ordersByStatus(Request $request)
43
+ {
44
+ return $this->run($request, OrdersByStatus::class, defaultDays: 14);
45
+ }
46
+
47
+ public function onTimeDelivery(Request $request)
48
+ {
49
+ return $this->run($request, OnTimeDelivery::class, function (OnTimeDelivery $a) use ($request) {
50
+ $a->slaMinutes((int) $request->input('sla_minutes', 30));
51
+ });
52
+ }
53
+
54
+ public function topDrivers(Request $request)
55
+ {
56
+ return $this->run($request, TopDrivers::class, function (TopDrivers $a) use ($request) {
57
+ $a->limit((int) $request->input('limit', 10));
58
+ $a->sortBy($request->string('sort_by', 'orders_completed')->toString());
59
+ });
60
+ }
61
+
62
+ public function fuelEfficiency(Request $request)
63
+ {
64
+ return $this->run($request, FuelEfficiency::class, defaultDays: 90);
65
+ }
66
+
67
+ public function issuesInsights(Request $request)
68
+ {
69
+ return $this->run($request, IssuesInsights::class);
70
+ }
71
+
72
+ public function maintenanceOverview(Request $request)
73
+ {
74
+ return $this->run($request, MaintenanceOverview::class);
75
+ }
76
+
77
+ public function geofenceViolations(Request $request)
78
+ {
79
+ return $this->run($request, GeofenceViolations::class, defaultDays: 7);
80
+ }
81
+
82
+ public function liveFleet(Request $request)
83
+ {
84
+ return $this->run($request, LiveFleet::class);
85
+ }
86
+
87
+ /**
88
+ * Shared boilerplate: period parsing + helper construction + JSON response.
89
+ *
90
+ * @param class-string<AbstractAnalytics> $class
91
+ */
92
+ private function run(Request $request, string $class, ?callable $configure = null, int $defaultDays = 30)
93
+ {
94
+ [$start, $end] = AbstractAnalytics::resolvePeriod(
95
+ $request->string('period')->toString() ?: null,
96
+ $request->date('start'),
97
+ $request->date('end'),
98
+ $defaultDays,
99
+ );
100
+
101
+ try {
102
+ /** @var AbstractAnalytics $analytics */
103
+ $analytics = $class::forCompany($request->user()->company)->between($start, $end);
104
+
105
+ if ($configure !== null) {
106
+ $configure($analytics);
107
+ }
108
+
109
+ return response()->json($analytics->get());
110
+ } catch (\Throwable $e) {
111
+ report($e);
112
+
113
+ return response()->json([
114
+ 'error' => $e->getMessage(),
115
+ 'widget' => $class,
116
+ ], 500);
117
+ }
118
+ }
119
+ }
@@ -28,6 +28,7 @@ class ContactController extends FleetOpsController
28
28
  public function onBeforeCreate(Request $request, array &$input)
29
29
  {
30
30
  $this->resolveUserInput($request, $input);
31
+ $this->assertCustomerIdentityIsAvailable($input);
31
32
  }
32
33
 
33
34
  /**
@@ -40,6 +41,7 @@ class ContactController extends FleetOpsController
40
41
  }
41
42
 
42
43
  $this->resolveUserInput($request, $input);
44
+ $this->assertCustomerIdentityIsAvailable($input, $contact);
43
45
  }
44
46
 
45
47
  /**
@@ -146,4 +148,21 @@ class ContactController extends FleetOpsController
146
148
  $input['user_uuid'] = User::where('uuid', $user)->orWhere('public_id', $user)->value('uuid') ?? $user;
147
149
  unset($input['user']);
148
150
  }
151
+
152
+ private function assertCustomerIdentityIsAvailable(array $input, ?Contact $contact = null): void
153
+ {
154
+ $type = data_get($input, 'type', $contact?->type);
155
+ if ($type !== 'customer') {
156
+ return;
157
+ }
158
+
159
+ $customer = $contact ? $contact->replicate() : new Contact();
160
+ if ($contact) {
161
+ $customer->forceFill($contact->getAttributes());
162
+ $customer->exists = $contact->exists;
163
+ }
164
+
165
+ $customer->forceFill($input);
166
+ $customer->assertCustomerIdentityIsAvailable();
167
+ }
149
168
  }