@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.
- package/addon/components/admin/routing-settings.hbs +71 -0
- package/addon/components/admin/routing-settings.js +124 -0
- package/addon/components/customer/orders.js +1 -1
- package/addon/components/display-place.hbs +1 -1
- package/addon/components/map/order-list-overlay/order.hbs +18 -3
- package/addon/components/map/toolbar.hbs +2 -2
- package/addon/components/map/toolbar.js +10 -2
- package/addon/components/orchestrator/phase-builder.hbs +28 -5
- package/addon/components/orchestrator/phase-builder.js +21 -1
- package/addon/components/orchestrator/resource-panel.hbs +19 -1
- package/addon/components/orchestrator/resource-panel.js +4 -0
- package/addon/components/orchestrator-workbench.js +3 -1
- package/addon/components/order/details/purchase-rate.hbs +19 -22
- package/addon/components/order/details/route.hbs +26 -1
- package/addon/components/order/details/route.js +50 -0
- package/addon/components/order/details/tracking.hbs +171 -27
- package/addon/components/order/details/tracking.js +457 -0
- package/addon/components/order/form/details.hbs +1 -2
- package/addon/components/order/form/details.js +6 -0
- package/addon/components/order/form/route.js +9 -1
- package/addon/components/order/kanban-card.hbs +18 -3
- package/addon/components/order-list-overlay/order.hbs +18 -3
- package/addon/components/order-progress-card.hbs +21 -6
- package/addon/components/order-tracking-lookup.hbs +7 -7
- package/addon/components/order-tracking-lookup.js +1 -1
- package/addon/components/route-list.hbs +78 -8
- package/addon/components/route-list.js +48 -6
- package/addon/components/tracking-stop-progress.hbs +45 -0
- package/addon/components/tracking-stop-progress.js +79 -0
- package/addon/controllers/settings/payments/index.js +1 -1
- package/addon/controllers/settings/routing.js +98 -1
- package/addon/helpers/format-duration.js +38 -0
- package/addon/routes/application.js +6 -0
- package/addon/services/order-list-overlay.js +2 -2
- package/addon/styles/fleetops-engine.css +733 -0
- package/addon/templates/settings/routing.hbs +60 -2
- package/app/components/tracking-stop-progress.js +1 -0
- package/app/helpers/format-duration.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/pnpm-workspace.yaml +7 -0
- package/server/config/fleetops.php +32 -11
- package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +86 -0
- package/server/seeders/VroomOrchestrationSeeder.php +402 -0
- package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +64 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +26 -26
- package/server/src/Http/Controllers/Api/v1/PayloadController.php +10 -10
- package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +41 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +6 -42
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +69 -15
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +119 -12
- package/server/src/Http/Resources/v1/Index/Payload.php +2 -2
- package/server/src/Models/Contact.php +122 -13
- package/server/src/Models/Order.php +1 -1
- package/server/src/Models/Payload.php +8 -8
- package/server/src/Models/Place.php +0 -3
- package/server/src/Models/ServiceRate.php +1 -1
- package/server/src/Observers/ContactObserver.php +8 -0
- package/server/src/Orchestration/Engines/CapacityAllocationEngine.php +220 -0
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +330 -38
- package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +259 -51
- package/server/src/Providers/FleetOpsServiceProvider.php +27 -0
- package/server/src/Support/LiveOrderQuery.php +75 -0
- package/server/src/Support/Metrics.php +15 -0
- package/server/src/Support/OSRM.php +11 -6
- package/server/src/Support/OrderTracker.php +7 -582
- package/server/src/Support/Utils.php +1 -1
- package/server/src/Tracking/Contracts/TrackingProviderInterface.php +19 -0
- package/server/src/Tracking/Providers/CalculatedTrackingProvider.php +65 -0
- package/server/src/Tracking/Providers/GoogleRoutesTrackingProvider.php +118 -0
- package/server/src/Tracking/Providers/OsrmTrackingProvider.php +71 -0
- package/server/src/Tracking/README.md +25 -0
- package/server/src/Tracking/Support/FakeTrackingProvider.php +42 -0
- package/server/src/Tracking/TrackingContext.php +63 -0
- package/server/src/Tracking/TrackingContextBuilder.php +204 -0
- package/server/src/Tracking/TrackingIntelligenceService.php +180 -0
- package/server/src/Tracking/TrackingOptions.php +49 -0
- package/server/src/Tracking/TrackingProviderCapabilities.php +30 -0
- package/server/src/Tracking/TrackingProviderManager.php +89 -0
- package/server/src/Tracking/TrackingProviderRegistry.php +35 -0
- package/server/src/Tracking/TrackingProviderResult.php +20 -0
- package/server/src/Tracking/TrackingStop.php +59 -0
- package/server/src/routes.php +11 -0
- package/server/tests/CapacityAllocationEngineTest.php +108 -0
- package/server/tests/LiveOrderQueryTest.php +34 -0
- package/server/tests/OrchestrationPayloadBuilderTest.php +132 -0
- package/server/tests/OrchestratorConsumableApiTest.php +74 -0
- package/server/tests/PingDriverEndpointTest.php +19 -0
- package/server/tests/TrackingIntelligenceTest.php +176 -0
- package/server/tests/VroomOrchestrationEngineTest.php +159 -0
- package/translations/en-us.yaml +4 -0
|
@@ -141,85 +141,243 @@ class OrchestrationPayloadBuilder
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
|
-
* Build
|
|
144
|
+
* Build normalized route tasks from Fleetbase orders.
|
|
145
145
|
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
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
|
|
150
|
+
public static function buildRouteTasks(Collection $orders): array
|
|
158
151
|
{
|
|
159
152
|
return $orders->map(function (Order $order) {
|
|
160
|
-
$
|
|
161
|
-
$
|
|
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 (
|
|
164
|
-
return
|
|
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
|
-
|
|
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
|
-
$
|
|
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
|
|
202
|
-
$end
|
|
203
|
-
$
|
|
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
|
-
$
|
|
201
|
+
$task['skills'] = $skills;
|
|
214
202
|
}
|
|
215
203
|
|
|
216
|
-
// --- Priority ---
|
|
217
204
|
if ($order->orchestrator_priority !== null && $order->orchestrator_priority > 0) {
|
|
218
|
-
$
|
|
205
|
+
$task['priority'] = (int) $order->orchestrator_priority;
|
|
219
206
|
}
|
|
220
207
|
|
|
221
|
-
return $
|
|
222
|
-
})->
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
}
|