@fleetbase/fleetops-engine 0.6.46 → 0.6.48

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 (95) hide show
  1. package/addon/components/admin/routing-settings.hbs +71 -0
  2. package/addon/components/admin/routing-settings.js +124 -0
  3. package/addon/components/customer/orders.js +1 -1
  4. package/addon/components/display-place.hbs +1 -1
  5. package/addon/components/map/order-list-overlay/order.hbs +18 -3
  6. package/addon/components/map/toolbar.hbs +2 -2
  7. package/addon/components/map/toolbar.js +10 -2
  8. package/addon/components/orchestrator/phase-builder.hbs +28 -5
  9. package/addon/components/orchestrator/phase-builder.js +21 -1
  10. package/addon/components/orchestrator/resource-panel.hbs +19 -1
  11. package/addon/components/orchestrator/resource-panel.js +4 -0
  12. package/addon/components/orchestrator-workbench.js +3 -1
  13. package/addon/components/order/details/purchase-rate.hbs +19 -22
  14. package/addon/components/order/details/route.hbs +26 -1
  15. package/addon/components/order/details/route.js +50 -0
  16. package/addon/components/order/details/tracking.hbs +171 -27
  17. package/addon/components/order/details/tracking.js +457 -0
  18. package/addon/components/order/form/details.hbs +1 -2
  19. package/addon/components/order/form/details.js +6 -0
  20. package/addon/components/order/form/route.js +9 -1
  21. package/addon/components/order/kanban-card.hbs +18 -3
  22. package/addon/components/order-list-overlay/order.hbs +18 -3
  23. package/addon/components/order-progress-card.hbs +21 -6
  24. package/addon/components/order-tracking-lookup.hbs +7 -7
  25. package/addon/components/order-tracking-lookup.js +1 -1
  26. package/addon/components/route-list.hbs +78 -8
  27. package/addon/components/route-list.js +48 -6
  28. package/addon/components/tracking-stop-progress.hbs +45 -0
  29. package/addon/components/tracking-stop-progress.js +79 -0
  30. package/addon/controllers/settings/payments/index.js +1 -1
  31. package/addon/controllers/settings/routing.js +98 -1
  32. package/addon/helpers/format-duration.js +38 -0
  33. package/addon/routes/application.js +6 -0
  34. package/addon/services/order-list-overlay.js +2 -2
  35. package/addon/styles/fleetops-engine.css +733 -0
  36. package/addon/templates/settings/routing.hbs +60 -2
  37. package/app/components/tracking-stop-progress.js +1 -0
  38. package/app/helpers/format-duration.js +1 -0
  39. package/composer.json +1 -1
  40. package/extension.json +1 -1
  41. package/package.json +3 -3
  42. package/pnpm-workspace.yaml +7 -0
  43. package/server/config/fleetops.php +32 -11
  44. package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +86 -0
  45. package/server/seeders/VroomOrchestrationSeeder.php +402 -0
  46. package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +64 -0
  47. package/server/src/Http/Controllers/Api/v1/OrderController.php +26 -26
  48. package/server/src/Http/Controllers/Api/v1/PayloadController.php +10 -10
  49. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +1 -1
  50. package/server/src/Http/Controllers/Internal/v1/ContactController.php +41 -0
  51. package/server/src/Http/Controllers/Internal/v1/LiveController.php +6 -42
  52. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +1 -1
  53. package/server/src/Http/Controllers/Internal/v1/OrderController.php +69 -15
  54. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
  55. package/server/src/Http/Controllers/Internal/v1/SettingController.php +119 -12
  56. package/server/src/Http/Resources/v1/Index/Payload.php +2 -2
  57. package/server/src/Models/Contact.php +122 -13
  58. package/server/src/Models/Order.php +1 -1
  59. package/server/src/Models/Payload.php +8 -8
  60. package/server/src/Models/Place.php +0 -3
  61. package/server/src/Models/ServiceRate.php +1 -1
  62. package/server/src/Observers/ContactObserver.php +8 -0
  63. package/server/src/Orchestration/Engines/CapacityAllocationEngine.php +220 -0
  64. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +330 -38
  65. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +259 -51
  66. package/server/src/Providers/FleetOpsServiceProvider.php +27 -0
  67. package/server/src/Support/LiveOrderQuery.php +75 -0
  68. package/server/src/Support/Metrics.php +15 -0
  69. package/server/src/Support/OSRM.php +11 -6
  70. package/server/src/Support/OrderTracker.php +7 -582
  71. package/server/src/Support/Utils.php +1 -1
  72. package/server/src/Tracking/Contracts/TrackingProviderInterface.php +19 -0
  73. package/server/src/Tracking/Providers/CalculatedTrackingProvider.php +65 -0
  74. package/server/src/Tracking/Providers/GoogleRoutesTrackingProvider.php +118 -0
  75. package/server/src/Tracking/Providers/OsrmTrackingProvider.php +71 -0
  76. package/server/src/Tracking/README.md +25 -0
  77. package/server/src/Tracking/Support/FakeTrackingProvider.php +42 -0
  78. package/server/src/Tracking/TrackingContext.php +63 -0
  79. package/server/src/Tracking/TrackingContextBuilder.php +204 -0
  80. package/server/src/Tracking/TrackingIntelligenceService.php +180 -0
  81. package/server/src/Tracking/TrackingOptions.php +49 -0
  82. package/server/src/Tracking/TrackingProviderCapabilities.php +30 -0
  83. package/server/src/Tracking/TrackingProviderManager.php +89 -0
  84. package/server/src/Tracking/TrackingProviderRegistry.php +35 -0
  85. package/server/src/Tracking/TrackingProviderResult.php +20 -0
  86. package/server/src/Tracking/TrackingStop.php +59 -0
  87. package/server/src/routes.php +11 -0
  88. package/server/tests/CapacityAllocationEngineTest.php +108 -0
  89. package/server/tests/LiveOrderQueryTest.php +34 -0
  90. package/server/tests/OrchestrationPayloadBuilderTest.php +132 -0
  91. package/server/tests/OrchestratorConsumableApiTest.php +74 -0
  92. package/server/tests/PingDriverEndpointTest.php +19 -0
  93. package/server/tests/TrackingIntelligenceTest.php +176 -0
  94. package/server/tests/VroomOrchestrationEngineTest.php +159 -0
  95. package/translations/en-us.yaml +4 -0
@@ -141,85 +141,243 @@ class OrchestrationPayloadBuilder
141
141
  }
142
142
 
143
143
  /**
144
- * Build the normalized job list from a collection of Orders.
144
+ * Build normalized route tasks from Fleetbase orders.
145
145
  *
146
- * Each job contains:
147
- * - id: order public_id
148
- * - location: [longitude, latitude] of the delivery destination
149
- * - pickup: [longitude, latitude] of the pickup (for PDPTW shipments)
150
- * - service: service time in seconds (waypoint.service_time → order meta → default 300)
151
- * - time_windows: [[earliest_unix, latest_unix]] from order.time_window_start/end or scheduled_at
152
- * - skills: integer skill codes from order.required_skills or custom fields
153
- * - amount: multi-dimensional capacity demand [weight_kg, volume_litres, pallets, parcels]
154
- * - priority: orchestrator_priority (0–100, higher = more important)
155
- * - description: human-readable label for debugging
146
+ * Fleetbase orders must remain atomic during allocation. A task contains
147
+ * the full ordered stop list for the order, but engine adapters decide how
148
+ * much of that route shape their solver can safely model.
156
149
  */
157
- public static function buildJobs(Collection $orders): array
150
+ public static function buildRouteTasks(Collection $orders): array
158
151
  {
159
152
  return $orders->map(function (Order $order) {
160
- $payload = $order->payload;
161
- $destination = $payload?->dropoff ?? $payload?->waypoints?->last();
153
+ $candidates = static::buildRouteStopCandidates($order);
154
+ $invalidStop = collect($candidates)->first(fn (array $stop) => !static::coordinatesFromPlace($stop['place'] ?? null));
155
+ $stops = static::normalizeRouteStops($candidates);
156
+
157
+ if ($invalidStop) {
158
+ return [
159
+ 'id' => $order->public_id,
160
+ 'description' => $order->public_id,
161
+ 'invalid' => true,
162
+ 'reason' => sprintf('Order %s stop is missing valid coordinates.', $invalidStop['role'] ?? 'route'),
163
+ 'stops' => [],
164
+ ];
165
+ }
162
166
 
163
- if (!$destination || !$destination->location) {
164
- return null;
167
+ if (empty($stops)) {
168
+ return [
169
+ 'id' => $order->public_id,
170
+ 'description' => $order->public_id,
171
+ 'invalid' => true,
172
+ 'reason' => 'Order has no routable pickup, dropoff, or waypoint coordinates.',
173
+ 'stops' => [],
174
+ ];
165
175
  }
166
176
 
167
- // --- Location ---
168
- $job = [
177
+ $task = [
169
178
  'id' => $order->public_id,
170
- 'location' => [$destination->location->getLng(), $destination->location->getLat()],
171
179
  'description' => $order->public_id,
180
+ 'stops' => $stops,
181
+ 'amount' => static::computePayloadDemand($order),
182
+ 'service' => static::resolveServiceTime($order),
172
183
  ];
173
184
 
174
- // Pickup location (for pickup-and-delivery problems)
175
- $pickup = $payload?->pickup;
176
- if ($pickup && $pickup->location) {
177
- $job['pickup'] = [$pickup->location->getLng(), $pickup->location->getLat()];
178
- }
179
-
180
- // --- Service time ---
181
- // Prefer waypoint-level service_time, then order meta, then default 300s
182
- $waypointMarker = $payload?->waypointMarkers?->last();
183
- $job['service'] = (int) (
184
- $waypointMarker?->service_time
185
- ?? $order->getMeta('service_time_seconds')
186
- ?? 300
187
- );
188
-
189
- // --- Capacity demand ---
190
- // Aggregated dynamically from payload entities; falls back to order meta.
191
- $job['amount'] = static::computePayloadDemand($order);
192
-
193
- // --- Time windows ---
194
- // Prefer explicit orchestrator time_window columns, fall back to scheduled_at
195
185
  if ($order->time_window_start && $order->time_window_end) {
196
- $job['time_windows'] = [[
186
+ $task['time_windows'] = [[
197
187
  $order->time_window_start->timestamp,
198
188
  $order->time_window_end->timestamp,
199
189
  ]];
200
190
  } elseif ($order->scheduled_at) {
201
- $start = $order->scheduled_at->timestamp;
202
- $end = $order->scheduled_at->copy()->addHours(4)->timestamp;
203
- $job['time_windows'] = [[$start, $end]];
191
+ $start = $order->scheduled_at->timestamp;
192
+ $end = $order->scheduled_at->copy()->addHours(4)->timestamp;
193
+ $task['time_windows'] = [[$start, $end]];
204
194
  }
205
195
 
206
- // --- Skills ---
207
- // Prefer first-class required_skills JSON column, fall back to custom fields
208
196
  $skills = static::resolveSkills(
209
197
  $order->required_skills ?? [],
210
198
  $order->custom_fields ?? []
211
199
  );
212
200
  if (!empty($skills)) {
213
- $job['skills'] = $skills;
201
+ $task['skills'] = $skills;
214
202
  }
215
203
 
216
- // --- Priority ---
217
204
  if ($order->orchestrator_priority !== null && $order->orchestrator_priority > 0) {
218
- $job['priority'] = (int) $order->orchestrator_priority;
205
+ $task['priority'] = (int) $order->orchestrator_priority;
219
206
  }
220
207
 
221
- return $job;
222
- })->filter()->values()->toArray();
208
+ return $task;
209
+ })->values()->toArray();
210
+ }
211
+
212
+ /**
213
+ * @deprecated Use buildRouteTasks(). Kept for older engine adapters.
214
+ */
215
+ public static function buildJobs(Collection $orders): array
216
+ {
217
+ return array_values(array_filter(
218
+ static::buildRouteTasks($orders),
219
+ fn (array $task) => empty($task['invalid'])
220
+ ));
221
+ }
222
+
223
+ /**
224
+ * Build normalized capacity-only tasks from Fleetbase orders.
225
+ *
226
+ * Capacity allocation does not require route stops. It only needs an order
227
+ * demand vector plus optional skills, priority, and task limits.
228
+ */
229
+ public static function buildCapacityTasks(Collection $orders): array
230
+ {
231
+ return $orders->map(function (Order $order) {
232
+ if (!$order->payload) {
233
+ return [
234
+ 'id' => $order->public_id,
235
+ 'description' => $order->public_id,
236
+ 'invalid' => true,
237
+ 'reason' => 'Order has no payload to compute capacity demand.',
238
+ 'amount' => [0, 0, 0, 0],
239
+ ];
240
+ }
241
+
242
+ $task = [
243
+ 'id' => $order->public_id,
244
+ 'description' => $order->public_id,
245
+ 'amount' => static::computePayloadDemand($order),
246
+ 'service' => static::resolveServiceTime($order),
247
+ ];
248
+
249
+ $skills = static::resolveSkills(
250
+ $order->required_skills ?? [],
251
+ $order->custom_fields ?? []
252
+ );
253
+ if (!empty($skills)) {
254
+ $task['skills'] = $skills;
255
+ }
256
+
257
+ if ($order->orchestrator_priority !== null && $order->orchestrator_priority > 0) {
258
+ $task['priority'] = (int) $order->orchestrator_priority;
259
+ }
260
+
261
+ return $task;
262
+ })->values()->toArray();
263
+ }
264
+
265
+ /**
266
+ * Return Fleetbase's canonical ordered stops for an order:
267
+ * pickup, then waypoints by marker order, then dropoff.
268
+ */
269
+ public static function buildRouteStops(Order $order): array
270
+ {
271
+ return static::normalizeRouteStops(static::buildRouteStopCandidates($order));
272
+ }
273
+
274
+ protected static function buildRouteStopCandidates(Order $order): array
275
+ {
276
+ $payload = $order->payload;
277
+ if (!$payload) {
278
+ return [];
279
+ }
280
+
281
+ $stops = [];
282
+
283
+ if ($payload->pickup) {
284
+ $stops[] = ['role' => 'pickup', 'place' => $payload->pickup];
285
+ }
286
+
287
+ $waypointMarkers = method_exists($payload, 'relationLoaded') && $payload->relationLoaded('waypointMarkers')
288
+ ? $payload->waypointMarkers
289
+ : collect();
290
+ if ($waypointMarkers->isNotEmpty()) {
291
+ foreach ($waypointMarkers->sortBy('order')->values() as $index => $waypoint) {
292
+ $stops[] = [
293
+ 'role' => 'waypoint',
294
+ 'place' => $waypoint->place,
295
+ 'waypoint_id' => $waypoint->public_id,
296
+ 'waypoint_uuid' => $waypoint->uuid,
297
+ 'order' => $waypoint->order ?? $index,
298
+ ];
299
+ }
300
+ } elseif (isset($payload->waypoints) && $payload->waypoints) {
301
+ foreach ($payload->waypoints->values() as $index => $waypoint) {
302
+ $stops[] = [
303
+ 'role' => 'waypoint',
304
+ 'place' => $waypoint,
305
+ 'order' => $index,
306
+ ];
307
+ }
308
+ }
309
+
310
+ if ($payload->dropoff) {
311
+ $stops[] = ['role' => 'dropoff', 'place' => $payload->dropoff];
312
+ }
313
+
314
+ return $stops;
315
+ }
316
+
317
+ protected static function normalizeRouteStops(array $stops): array
318
+ {
319
+ return collect($stops)
320
+ ->map(function (array $stop) {
321
+ $coordinates = static::coordinatesFromPlace($stop['place'] ?? null);
322
+ if (!$coordinates) {
323
+ return null;
324
+ }
325
+
326
+ unset($stop['place'], $stop['waypoint_uuid']);
327
+
328
+ return array_merge($stop, [
329
+ 'location' => $coordinates,
330
+ ]);
331
+ })
332
+ ->filter()
333
+ ->values()
334
+ ->toArray();
335
+ }
336
+
337
+ protected static function resolveServiceTime(Order $order): int
338
+ {
339
+ $payload = $order->payload;
340
+ $waypointMarker = $payload?->waypointMarkers?->last();
341
+
342
+ return (int) (
343
+ $waypointMarker?->service_time
344
+ ?? $order->getMeta('service_time_seconds')
345
+ ?? 300
346
+ );
347
+ }
348
+
349
+ protected static function coordinatesFromPlace($place): ?array
350
+ {
351
+ $location = $place?->location;
352
+ if (!$location) {
353
+ return null;
354
+ }
355
+
356
+ $lng = $location->getLng();
357
+ $lat = $location->getLat();
358
+
359
+ if (!static::isValidCoordinate($lng, $lat)) {
360
+ return null;
361
+ }
362
+
363
+ return [(float) $lng, (float) $lat];
364
+ }
365
+
366
+ protected static function isValidCoordinate($lng, $lat): bool
367
+ {
368
+ if (!is_numeric($lng) || !is_numeric($lat)) {
369
+ return false;
370
+ }
371
+
372
+ $lng = (float) $lng;
373
+ $lat = (float) $lat;
374
+
375
+ return is_finite($lng)
376
+ && is_finite($lat)
377
+ && $lng >= -180
378
+ && $lng <= 180
379
+ && $lat >= -90
380
+ && $lat <= 90;
223
381
  }
224
382
 
225
383
  /**
@@ -337,6 +495,56 @@ class OrchestrationPayloadBuilder
337
495
  })->filter()->values()->toArray();
338
496
  }
339
497
 
498
+ /**
499
+ * Build vehicles for non-geospatial capacity allocation.
500
+ *
501
+ * Unlike buildVehicles(), this intentionally does not require a vehicle or
502
+ * driver location because no routing engine will be asked to calculate
503
+ * travel distance or duration.
504
+ */
505
+ public static function buildCapacityVehicles(Collection $vehicles): array
506
+ {
507
+ return $vehicles->map(function (Vehicle $vehicle) {
508
+ $driver = $vehicle->driver;
509
+ $entry = [
510
+ 'id' => $vehicle->public_id,
511
+ 'driver_id' => $driver?->public_id,
512
+ 'capacity' => static::buildVehicleCapacity($vehicle),
513
+ ];
514
+
515
+ if ($vehicle->max_tasks !== null && $vehicle->max_tasks > 0) {
516
+ $entry['max_tasks'] = (int) $vehicle->max_tasks;
517
+ }
518
+
519
+ $maxTravel = $driver?->max_travel_time ?? static::safeMeta($vehicle, 'max_travel_time_seconds');
520
+ if ($maxTravel) {
521
+ $entry['max_travel_time'] = (int) $maxTravel;
522
+ }
523
+
524
+ if ($driver?->time_window_start && $driver?->time_window_end) {
525
+ $entry['time_window'] = [
526
+ $driver->time_window_start->timestamp,
527
+ $driver->time_window_end->timestamp,
528
+ ];
529
+ } elseif ($vehicle->time_window_start && $vehicle->time_window_end) {
530
+ $entry['time_window'] = [
531
+ $vehicle->time_window_start->timestamp,
532
+ $vehicle->time_window_end->timestamp,
533
+ ];
534
+ }
535
+
536
+ $skills = array_values(array_unique(array_merge(
537
+ static::resolveSkills($vehicle->skills ?? [], $vehicle->custom_fields ?? []),
538
+ static::resolveSkills($driver?->skills ?? [], $driver?->custom_fields ?? [])
539
+ )));
540
+ if (!empty($skills)) {
541
+ $entry['skills'] = $skills;
542
+ }
543
+
544
+ return $entry;
545
+ })->values()->toArray();
546
+ }
547
+
340
548
  /**
341
549
  * Resolve integer skill codes from a first-class skills array and/or custom fields.
342
550
  *
@@ -99,6 +99,13 @@ class FleetOpsServiceProvider extends CoreServiceProvider
99
99
  \Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry::class,
100
100
  fn () => new \Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry()
101
101
  );
102
+
103
+ // Register the TrackingProviderRegistry as a singleton so FleetOps core
104
+ // and third-party extensions can share tracking intelligence providers.
105
+ $this->app->singleton(
106
+ \Fleetbase\FleetOps\Tracking\TrackingProviderRegistry::class,
107
+ fn () => new \Fleetbase\FleetOps\Tracking\TrackingProviderRegistry()
108
+ );
102
109
  }
103
110
 
104
111
  /**
@@ -135,6 +142,26 @@ class FleetOpsServiceProvider extends CoreServiceProvider
135
142
  if (!$registry->has('greedy')) {
136
143
  $registry->register(new \Fleetbase\FleetOps\Orchestration\Engines\GreedyOrchestrationEngine());
137
144
  }
145
+ if (!$registry->has('capacity')) {
146
+ $registry->register(new \Fleetbase\FleetOps\Orchestration\Engines\CapacityAllocationEngine());
147
+ }
148
+ }
149
+ );
150
+
151
+ // Register built-in tracking providers. Third-party extensions can
152
+ // register additional providers from their own service providers.
153
+ $this->app->resolving(
154
+ \Fleetbase\FleetOps\Tracking\TrackingProviderRegistry::class,
155
+ function (\Fleetbase\FleetOps\Tracking\TrackingProviderRegistry $registry) {
156
+ if (!$registry->has('google_routes')) {
157
+ $registry->register(new \Fleetbase\FleetOps\Tracking\Providers\GoogleRoutesTrackingProvider());
158
+ }
159
+ if (!$registry->has('osrm')) {
160
+ $registry->register(new \Fleetbase\FleetOps\Tracking\Providers\OsrmTrackingProvider());
161
+ }
162
+ if (!$registry->has('calculated')) {
163
+ $registry->register(new \Fleetbase\FleetOps\Tracking\Providers\CalculatedTrackingProvider());
164
+ }
138
165
  }
139
166
  );
140
167
  $this->loadRoutesFrom(__DIR__ . '/../routes.php');
@@ -0,0 +1,75 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support;
4
+
5
+ use Fleetbase\FleetOps\Models\Order;
6
+ use Illuminate\Database\Eloquent\Builder;
7
+
8
+ class LiveOrderQuery
9
+ {
10
+ public static array $baseExcludedStatuses = ['canceled', 'completed', 'expired'];
11
+ public static array $activeExcludedStatuses = ['created', 'completed', 'expired', 'order_canceled', 'canceled', 'pending'];
12
+
13
+ public static function make(?string $companyUuid = null, array $options = []): Builder
14
+ {
15
+ $companyUuid = $companyUuid ?: session('company');
16
+ $exclude = $options['exclude'] ?? [];
17
+ $active = $options['active'] ?? false;
18
+ $unassigned = $options['unassigned'] ?? false;
19
+ $withRelations = $options['with_relations'] ?? false;
20
+ $applyPermissions = $options['apply_permissions'] ?? true;
21
+
22
+ $query = Order::where('company_uuid', $companyUuid)
23
+ ->whereHas('payload', function ($query) {
24
+ $query->where(function ($q) {
25
+ $q->whereHas('waypoints');
26
+ $q->orWhereHas('pickup');
27
+ $q->orWhereHas('dropoff');
28
+ });
29
+ })
30
+ ->whereNotIn('status', static::$baseExcludedStatuses)
31
+ ->whereHas('trackingNumber')
32
+ ->whereHas('trackingStatuses')
33
+ ->whereNull('deleted_at');
34
+
35
+ if (!empty($exclude)) {
36
+ $query->whereNotIn('public_id', $exclude);
37
+ }
38
+
39
+ if ($applyPermissions) {
40
+ $query->applyDirectivesForPermissions('fleet-ops list order');
41
+ }
42
+
43
+ if ($active) {
44
+ $query->whereHas('driverAssigned');
45
+ $query->whereNotIn('status', static::$activeExcludedStatuses);
46
+ }
47
+
48
+ if ($unassigned) {
49
+ $query->whereNull('driver_assigned_uuid');
50
+ }
51
+
52
+ if ($withRelations) {
53
+ $query->with([
54
+ 'payload.entities',
55
+ 'payload.dropoff',
56
+ 'payload.pickup',
57
+ 'payload.return',
58
+ 'payload.firstWaypointMarker.place',
59
+ 'payload.lastWaypointMarker.place',
60
+ 'trackingNumber',
61
+ 'trackingStatuses',
62
+ 'driverAssigned' => function ($query) {
63
+ $query->without(['jobs', 'currentJob']);
64
+ },
65
+ 'vehicleAssigned' => function ($query) {
66
+ $query->without(['fleets', 'vendor']);
67
+ },
68
+ 'customer',
69
+ 'facilitator',
70
+ ]);
71
+ }
72
+
73
+ return $query;
74
+ }
75
+ }
@@ -215,6 +215,21 @@ class Metrics
215
215
  return $this->set('orders_in_progress', $data);
216
216
  }
217
217
 
218
+ public function activeLiveOrders(?callable $callback = null): Metrics
219
+ {
220
+ $query = LiveOrderQuery::make($this->company->uuid, [
221
+ 'active' => true,
222
+ ]);
223
+
224
+ if (is_callable($callback)) {
225
+ $callback($query);
226
+ }
227
+
228
+ $data = $query->count();
229
+
230
+ return $this->set('active_live_orders', $data);
231
+ }
232
+
218
233
  public function ordersScheduled(?callable $callback = null): Metrics
219
234
  {
220
235
  $query = Order::where('company_uuid', $this->company->uuid)
@@ -88,7 +88,7 @@ class OSRM
88
88
  $cacheKey = 'getRouteFromCoordinatesString:' . md5($coordinates . serialize($queryParameters));
89
89
 
90
90
  try {
91
- $url = self::$baseUrl . "/route/v1/driving/{$coordinates}";
91
+ $url = static::baseUrl() . "/route/v1/driving/{$coordinates}";
92
92
  $response = Http::timeout(1)->get($url, $queryParameters);
93
93
  $data = $response->json();
94
94
 
@@ -130,7 +130,7 @@ class OSRM
130
130
  }
131
131
 
132
132
  $coordinates = "{$location->getLng()},{$location->getLat()}";
133
- $url = self::$baseUrl . "/nearest/v1/driving/{$coordinates}";
133
+ $url = static::baseUrl() . "/nearest/v1/driving/{$coordinates}";
134
134
  $response = Http::timeout(1)->get($url, $queryParameters);
135
135
  $result = $response->json();
136
136
 
@@ -163,7 +163,7 @@ class OSRM
163
163
  return "{$point->getLng()},{$point->getLat()}";
164
164
  }, $points));
165
165
 
166
- $url = self::$baseUrl . "/table/v1/driving/{$coordinates}";
166
+ $url = static::baseUrl() . "/table/v1/driving/{$coordinates}";
167
167
  $response = Http::timeout(1)->get($url, $queryParameters);
168
168
  $result = $response->json();
169
169
 
@@ -196,7 +196,7 @@ class OSRM
196
196
  return "{$point->getLng()},{$point->getLat()}";
197
197
  }, $points));
198
198
 
199
- $url = self::$baseUrl . "/trip/v1/driving/{$coordinates}";
199
+ $url = static::baseUrl() . "/trip/v1/driving/{$coordinates}";
200
200
  $response = Http::timeout(1)->get($url, $queryParameters);
201
201
  $data = $response->json();
202
202
 
@@ -222,7 +222,7 @@ class OSRM
222
222
 
223
223
  return "{$point->getLng()},{$point->getLat()}";
224
224
  }, $points));
225
- $url = self::$baseUrl . "/match/v1/driving/{$coordinates}";
225
+ $url = static::baseUrl() . "/match/v1/driving/{$coordinates}";
226
226
 
227
227
  $response = Http::timeout(1)->get($url, $queryParameters);
228
228
 
@@ -241,7 +241,7 @@ class OSRM
241
241
  */
242
242
  public static function getTile(int $z, int $x, int $y, array $queryParameters = [])
243
243
  {
244
- $url = self::$baseUrl . "/tile/v1/car/{$z}/{$x}/{$y}.mvt";
244
+ $url = static::baseUrl() . "/tile/v1/car/{$z}/{$x}/{$y}.mvt";
245
245
 
246
246
  $response = Http::timeout(1)->get($url, $queryParameters);
247
247
 
@@ -259,4 +259,9 @@ class OSRM
259
259
  {
260
260
  return Polyline::decode($polyline);
261
261
  }
262
+
263
+ protected static function baseUrl(): string
264
+ {
265
+ return rtrim(config('fleetops.osrm.host', static::$baseUrl), '/');
266
+ }
262
267
  }