@fleetbase/fleetops-engine 0.6.29 → 0.6.30
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/customer/create-order-form.hbs +1 -1
- package/addon/components/customer/order-form.hbs +34 -34
- package/addon/components/customer/orders.hbs +2 -2
- package/addon/components/display-place.hbs +1 -1
- package/addon/components/driver/pill.hbs +16 -17
- package/addon/components/driver/pill.js +5 -1
- package/addon/components/map/leaflet-live-map.js +35 -3
- package/addon/components/map/order-list-overlay/order.hbs +1 -0
- package/addon/components/map/order-list-overlay/order.js +42 -0
- package/addon/components/modals/bulk-assign-driver.hbs +2 -2
- package/addon/components/order/details/detail.hbs +6 -6
- package/addon/components/order/details/notes.js +1 -1
- package/addon/components/order/route-editor.hbs +3 -3
- package/addon/components/order-tracking-lookup.hbs +1 -1
- package/addon/components/service-rate/details.hbs +117 -75
- package/addon/components/service-rate/form.hbs +7 -4
- package/addon/components/service-rate/form.js +6 -0
- package/addon/components/vehicle/pill.hbs +32 -33
- package/addon/components/vehicle/pill.js +5 -1
- package/addon/controllers/operations/orders/index.js +1 -1
- package/addon/controllers/operations/scheduler/index.js +17 -2
- package/addon/controllers/operations/service-rates/index/edit.js +1 -7
- package/addon/controllers/operations/service-rates/index/new.js +0 -7
- package/addon/controllers/operations/service-rates/index.js +10 -2
- package/addon/routes/operations/orders/index/details.js +7 -0
- package/addon/routes/operations/scheduler/index.js +3 -3
- package/addon/services/driver-actions.js +20 -4
- package/addon/services/leaflet-routing-control.js +7 -1
- package/addon/services/order-list-overlay.js +0 -1
- package/addon/services/place-actions.js +20 -4
- package/addon/services/service-rate-actions.js +31 -0
- package/addon/services/vehicle-actions.js +20 -4
- package/addon/templates/operations/scheduler/index.hbs +2 -2
- package/addon/utils/create-full-calendar-event-from-order.js +6 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +4 -4
- package/server/migrations/2025_12_16_000001_add_subject_created_at_index_to_positions.php +40 -0
- package/server/migrations/2025_12_16_000003_add_performance_indexes_to_fleetops_core_tables.php +442 -0
- package/server/src/Console/Commands/DispatchAdhocOrders.php +7 -2
- package/server/src/Http/Controllers/Api/v1/DriverController.php +2 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +30 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +184 -86
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +14 -1
- package/server/src/Http/Controllers/Internal/v1/PlaceController.php +5 -0
- package/server/src/Http/Filter/DriverFilter.php +10 -0
- package/server/src/Http/Filter/OrderFilter.php +8 -1
- package/server/src/Http/Resources/v1/Contact.php +2 -2
- package/server/src/Http/Resources/v1/Entity.php +1 -1
- package/server/src/Http/Resources/v1/Index/Customer.php +33 -0
- package/server/src/Http/Resources/v1/Index/Driver.php +45 -0
- package/server/src/Http/Resources/v1/Index/Facilitator.php +33 -0
- package/server/src/Http/Resources/v1/Index/Order.php +127 -0
- package/server/src/Http/Resources/v1/Index/Payload.php +57 -0
- package/server/src/Http/Resources/v1/Index/Place.php +45 -0
- package/server/src/Http/Resources/v1/Index/TrackingNumber.php +25 -0
- package/server/src/Http/Resources/v1/Index/Vehicle.php +50 -0
- package/server/src/Http/Resources/v1/Order.php +41 -14
- package/server/src/Http/Resources/v1/Place.php +1 -1
- package/server/src/Http/Resources/v1/Position.php +2 -1
- package/server/src/Http/Resources/v1/ServiceRate.php +4 -4
- package/server/src/Http/Resources/v1/ServiceRateFee.php +12 -12
- package/server/src/Http/Resources/v1/ServiceRateParcelFee.php +14 -7
- package/server/src/Http/Resources/v1/TrackingNumber.php +1 -1
- package/server/src/Http/Resources/v1/Waypoint.php +1 -1
- package/server/src/Listeners/HandleOrderDispatched.php +5 -0
- package/server/src/Models/Contact.php +2 -0
- package/server/src/Models/Device.php +2 -0
- package/server/src/Models/DeviceEvent.php +2 -0
- package/server/src/Models/Driver.php +2 -0
- package/server/src/Models/FuelReport.php +2 -0
- package/server/src/Models/Issue.php +2 -0
- package/server/src/Models/Order.php +12 -5
- package/server/src/Models/Place.php +2 -0
- package/server/src/Models/Position.php +2 -0
- package/server/src/Models/ServiceArea.php +2 -0
- package/server/src/Models/ServiceRate.php +5 -1
- package/server/src/Models/ServiceRateFee.php +3 -17
- package/server/src/Models/ServiceRateParcelFee.php +1 -12
- package/server/src/Models/Vehicle.php +2 -0
- package/server/src/Models/Vendor.php +2 -0
- package/server/src/Models/Zone.php +2 -0
- package/server/src/Observers/DriverObserver.php +23 -0
- package/server/src/Observers/OrderObserver.php +31 -0
- package/server/src/Observers/PlaceObserver.php +31 -0
- package/server/src/Observers/ServiceRateObserver.php +0 -18
- package/server/src/Observers/VehicleObserver.php +7 -0
- package/server/src/Support/LiveCacheService.php +165 -0
- package/server/src/Support/OSRM.php +49 -22
- package/server/src/Support/OrderTracker.php +100 -28
- package/translations/en-us.yaml +3 -1
|
@@ -6,6 +6,7 @@ use Fleetbase\FleetOps\Support\Encoding\Polyline;
|
|
|
6
6
|
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
7
7
|
use Illuminate\Support\Facades\Cache;
|
|
8
8
|
use Illuminate\Support\Facades\Http;
|
|
9
|
+
use Illuminate\Support\Facades\Log;
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Class OSRM
|
|
@@ -60,7 +61,13 @@ class OSRM
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
// Convert the array of Point objects into an OSRM-compatible string
|
|
63
|
-
|
|
64
|
+
// Ensure all points are Point objects, not SpatialExpression
|
|
65
|
+
$coordinates = array_map(function ($point) {
|
|
66
|
+
// Convert to Point if it's a SpatialExpression or other type
|
|
67
|
+
if (!$point instanceof Point) {
|
|
68
|
+
$point = Utils::getPointFromMixed($point);
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
return "{$point->getLng()},{$point->getLat()}";
|
|
65
72
|
}, $points);
|
|
66
73
|
|
|
@@ -79,23 +86,31 @@ class OSRM
|
|
|
79
86
|
public static function getRouteFromCoordinatesString(string $coordinates, array $queryParameters = [])
|
|
80
87
|
{
|
|
81
88
|
$cacheKey = 'getRouteFromCoordinatesString:' . md5($coordinates . serialize($queryParameters));
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
$url = self::$baseUrl . "/route/v1/driving/{$coordinates}";
|
|
92
|
+
$response = Http::timeout(1)->get($url, $queryParameters);
|
|
93
|
+
$data = $response->json();
|
|
94
|
+
|
|
95
|
+
// Check for the presence of the encoded polyline in each route and decode it if found
|
|
96
|
+
if (isset($data['routes']) && is_array($data['routes'])) {
|
|
97
|
+
foreach ($data['routes'] as &$route) {
|
|
98
|
+
if (isset($route['geometry'])) {
|
|
99
|
+
$route['waypoints'] = self::decodePolyline($route['geometry']);
|
|
100
|
+
}
|
|
91
101
|
}
|
|
92
102
|
}
|
|
93
|
-
}
|
|
94
103
|
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
// Store the result in the cache for 60 minutes
|
|
105
|
+
Cache::put($cacheKey, $data, 60 * 60);
|
|
97
106
|
|
|
98
|
-
|
|
107
|
+
return $data;
|
|
108
|
+
} catch (\Exception $e) {
|
|
109
|
+
Log::warning('OSRM request timeout or error', ['error' => $e->getMessage(), 'coordinates' => $coordinates]);
|
|
110
|
+
|
|
111
|
+
// Return empty response structure on error
|
|
112
|
+
return ['code' => 'Error', 'routes' => []];
|
|
113
|
+
}
|
|
99
114
|
}
|
|
100
115
|
|
|
101
116
|
/**
|
|
@@ -116,7 +131,7 @@ class OSRM
|
|
|
116
131
|
|
|
117
132
|
$coordinates = "{$location->getLng()},{$location->getLat()}";
|
|
118
133
|
$url = self::$baseUrl . "/nearest/v1/driving/{$coordinates}";
|
|
119
|
-
$response = Http::get($url, $queryParameters);
|
|
134
|
+
$response = Http::timeout(1)->get($url, $queryParameters);
|
|
120
135
|
$result = $response->json();
|
|
121
136
|
|
|
122
137
|
Cache::put($cacheKey, $result, 60 * 60);
|
|
@@ -140,12 +155,16 @@ class OSRM
|
|
|
140
155
|
return Cache::get($cacheKey);
|
|
141
156
|
}
|
|
142
157
|
|
|
143
|
-
$coordinates = implode(';', array_map(function (
|
|
158
|
+
$coordinates = implode(';', array_map(function ($point) {
|
|
159
|
+
if (!$point instanceof Point) {
|
|
160
|
+
$point = Utils::getPointFromMixed($point);
|
|
161
|
+
}
|
|
162
|
+
|
|
144
163
|
return "{$point->getLng()},{$point->getLat()}";
|
|
145
164
|
}, $points));
|
|
146
165
|
|
|
147
166
|
$url = self::$baseUrl . "/table/v1/driving/{$coordinates}";
|
|
148
|
-
$response = Http::get($url, $queryParameters);
|
|
167
|
+
$response = Http::timeout(1)->get($url, $queryParameters);
|
|
149
168
|
$result = $response->json();
|
|
150
169
|
|
|
151
170
|
Cache::put($cacheKey, $result, 60 * 60);
|
|
@@ -169,12 +188,16 @@ class OSRM
|
|
|
169
188
|
return Cache::get($cacheKey);
|
|
170
189
|
}
|
|
171
190
|
|
|
172
|
-
$coordinates = implode(';', array_map(function (
|
|
191
|
+
$coordinates = implode(';', array_map(function ($point) {
|
|
192
|
+
if (!$point instanceof Point) {
|
|
193
|
+
$point = Utils::getPointFromMixed($point);
|
|
194
|
+
}
|
|
195
|
+
|
|
173
196
|
return "{$point->getLng()},{$point->getLat()}";
|
|
174
197
|
}, $points));
|
|
175
198
|
|
|
176
199
|
$url = self::$baseUrl . "/trip/v1/driving/{$coordinates}";
|
|
177
|
-
$response = Http::get($url, $queryParameters);
|
|
200
|
+
$response = Http::timeout(1)->get($url, $queryParameters);
|
|
178
201
|
$data = $response->json();
|
|
179
202
|
|
|
180
203
|
Cache::put($cacheKey, $data, 60 * 60);
|
|
@@ -192,12 +215,16 @@ class OSRM
|
|
|
192
215
|
*/
|
|
193
216
|
public static function getMatch(array $points, array $queryParameters = [])
|
|
194
217
|
{
|
|
195
|
-
$coordinates = implode(';', array_map(function (
|
|
218
|
+
$coordinates = implode(';', array_map(function ($point) {
|
|
219
|
+
if (!$point instanceof Point) {
|
|
220
|
+
$point = Utils::getPointFromMixed($point);
|
|
221
|
+
}
|
|
222
|
+
|
|
196
223
|
return "{$point->getLng()},{$point->getLat()}";
|
|
197
224
|
}, $points));
|
|
198
225
|
$url = self::$baseUrl . "/match/v1/driving/{$coordinates}";
|
|
199
226
|
|
|
200
|
-
$response = Http::get($url, $queryParameters);
|
|
227
|
+
$response = Http::timeout(1)->get($url, $queryParameters);
|
|
201
228
|
|
|
202
229
|
return $response->json();
|
|
203
230
|
}
|
|
@@ -216,7 +243,7 @@ class OSRM
|
|
|
216
243
|
{
|
|
217
244
|
$url = self::$baseUrl . "/tile/v1/car/{$z}/{$x}/{$y}.mvt";
|
|
218
245
|
|
|
219
|
-
$response = Http::get($url, $queryParameters);
|
|
246
|
+
$response = Http::timeout(1)->get($url, $queryParameters);
|
|
220
247
|
|
|
221
248
|
return $response->body();
|
|
222
249
|
}
|
|
@@ -11,6 +11,7 @@ use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
|
11
11
|
use Illuminate\Support\Arr;
|
|
12
12
|
use Illuminate\Support\Carbon;
|
|
13
13
|
use Illuminate\Support\Collection;
|
|
14
|
+
use Illuminate\Support\Facades\Cache;
|
|
14
15
|
use Illuminate\Support\Facades\Log;
|
|
15
16
|
use Illuminate\Support\Str;
|
|
16
17
|
|
|
@@ -184,6 +185,14 @@ class OrderTracker
|
|
|
184
185
|
return -1;
|
|
185
186
|
}
|
|
186
187
|
|
|
188
|
+
// Convert SpatialExpression to Point objects
|
|
189
|
+
$start = Utils::getPointFromMixed($start);
|
|
190
|
+
$end = Utils::getPointFromMixed($end);
|
|
191
|
+
|
|
192
|
+
if (!$start || !$end) {
|
|
193
|
+
return -1;
|
|
194
|
+
}
|
|
195
|
+
|
|
187
196
|
try {
|
|
188
197
|
$response = OSRM::getRoute($start, $end);
|
|
189
198
|
if (isset($response['code']) && $response['code'] === 'Ok') {
|
|
@@ -211,6 +220,14 @@ class OrderTracker
|
|
|
211
220
|
$start = $this->getDriverCurrentLocation();
|
|
212
221
|
$end = $waypoint->location;
|
|
213
222
|
|
|
223
|
+
// Convert SpatialExpression to Point objects
|
|
224
|
+
$start = Utils::getPointFromMixed($start);
|
|
225
|
+
$end = Utils::getPointFromMixed($end);
|
|
226
|
+
|
|
227
|
+
if (!$start || !$end) {
|
|
228
|
+
return -1;
|
|
229
|
+
}
|
|
230
|
+
|
|
214
231
|
try {
|
|
215
232
|
$response = OSRM::getRoute($start, $end);
|
|
216
233
|
if (isset($response['code']) && $response['code'] === 'Ok') {
|
|
@@ -238,6 +255,14 @@ class OrderTracker
|
|
|
238
255
|
$start = $this->getDriverCurrentLocation();
|
|
239
256
|
$end = $this->payload->getDropoffOrLastWaypoint()->location;
|
|
240
257
|
|
|
258
|
+
// Convert SpatialExpression to Point objects
|
|
259
|
+
$start = Utils::getPointFromMixed($start);
|
|
260
|
+
$end = Utils::getPointFromMixed($end);
|
|
261
|
+
|
|
262
|
+
if (!$start || !$end) {
|
|
263
|
+
return -1;
|
|
264
|
+
}
|
|
265
|
+
|
|
241
266
|
try {
|
|
242
267
|
$response = OSRM::getRoute($start, $end);
|
|
243
268
|
if (isset($response['code']) && $response['code'] === 'Ok') {
|
|
@@ -487,41 +512,88 @@ class OrderTracker
|
|
|
487
512
|
|
|
488
513
|
public function eta(): array
|
|
489
514
|
{
|
|
490
|
-
//
|
|
491
|
-
$
|
|
492
|
-
|
|
493
|
-
//
|
|
494
|
-
$
|
|
495
|
-
|
|
496
|
-
$
|
|
497
|
-
|
|
515
|
+
// Generate cache key based on order UUID and updated_at timestamp
|
|
516
|
+
$cacheKey = 'order_eta:' . $this->order->uuid . ':' . optional($this->order->updated_at)->timestamp;
|
|
517
|
+
|
|
518
|
+
// Return cached data if available
|
|
519
|
+
return Cache::remember($cacheKey, 60, function () {
|
|
520
|
+
// Load missing waypoints and places
|
|
521
|
+
$waypoints = $this->payload->getAllStops();
|
|
522
|
+
|
|
523
|
+
// ETA's
|
|
524
|
+
$eta = [];
|
|
525
|
+
foreach ($waypoints as $waypoint) {
|
|
526
|
+
$eta[$waypoint->uuid] = $this->getWaypointETA($waypoint);
|
|
527
|
+
}
|
|
498
528
|
|
|
499
|
-
|
|
529
|
+
return $eta;
|
|
530
|
+
});
|
|
500
531
|
}
|
|
501
532
|
|
|
502
533
|
/**
|
|
503
534
|
* Get all key tracker information as an array.
|
|
535
|
+
* Cached for 60 seconds to avoid repeated OSRM calls.
|
|
504
536
|
*/
|
|
505
537
|
public function toArray(): array
|
|
506
538
|
{
|
|
507
|
-
|
|
508
|
-
$
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
539
|
+
// Generate cache key based on order UUID and updated_at timestamp
|
|
540
|
+
$cacheKey = 'order_tracker:' . $this->order->uuid . ':' . optional($this->order->updated_at)->timestamp;
|
|
541
|
+
|
|
542
|
+
// Return cached data if available
|
|
543
|
+
return Cache::remember($cacheKey, 60, function () {
|
|
544
|
+
// Early return for completed orders - skip expensive OSRM calls
|
|
545
|
+
if (in_array($this->order->status, ['completed', 'canceled'])) {
|
|
546
|
+
return [
|
|
547
|
+
'driver_current_location' => null,
|
|
548
|
+
'progress_percentage' => 100,
|
|
549
|
+
'total_distance' => 0,
|
|
550
|
+
'completed_distance' => 0,
|
|
551
|
+
'current_destination_eta' => 0,
|
|
552
|
+
'completion_eta' => 0,
|
|
553
|
+
'estimated_completion_time' => null,
|
|
554
|
+
'estimated_completion_time_formatted' => null,
|
|
555
|
+
'start_time' => $this->getOrderStartTime(),
|
|
556
|
+
'completion_time' => $this->getOrderCompletionTime(),
|
|
557
|
+
'current_destination' => null,
|
|
558
|
+
'next_destination' => null,
|
|
559
|
+
'first_waypoint_completed' => true,
|
|
560
|
+
'last_waypoint_completed' => true,
|
|
561
|
+
];
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Wrap OSRM-dependent calculations in try-catch for graceful degradation
|
|
565
|
+
try {
|
|
566
|
+
$totalDistance = $this->getTotalDistance();
|
|
567
|
+
$completedDistance = $this->getCompletedDistance();
|
|
568
|
+
$currentDestinationEta = $this->getCurrentDestinationETA();
|
|
569
|
+
$completionEta = $this->getCompletionETA();
|
|
570
|
+
} catch (\Exception $e) {
|
|
571
|
+
Log::warning('OrderTracker: Failed to calculate distances/ETAs', ['error' => $e->getMessage()]);
|
|
572
|
+
$totalDistance = 0;
|
|
573
|
+
$completedDistance = 0;
|
|
574
|
+
$currentDestinationEta = 0;
|
|
575
|
+
$completionEta = 0;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
$estimatedCompletionTime = $this->getEstimatedCompletionTime();
|
|
579
|
+
$orderProgressPercentage = $this->getOrderProgressPercentage();
|
|
580
|
+
|
|
581
|
+
return [
|
|
582
|
+
'driver_current_location' => $this->getDriverCurrentLocation(),
|
|
583
|
+
'progress_percentage' => $orderProgressPercentage,
|
|
584
|
+
'total_distance' => $totalDistance,
|
|
585
|
+
'completed_distance' => $completedDistance,
|
|
586
|
+
'current_destination_eta' => $currentDestinationEta,
|
|
587
|
+
'completion_eta' => $completionEta,
|
|
588
|
+
'estimated_completion_time' => $estimatedCompletionTime,
|
|
589
|
+
'estimated_completion_time_formatted' => $estimatedCompletionTime instanceof Carbon ? $estimatedCompletionTime->format('M jS, Y H:i') : null,
|
|
590
|
+
'start_time' => $this->getOrderStartTime(),
|
|
591
|
+
'completion_time' => $this->getOrderCompletionTime(),
|
|
592
|
+
'current_destination' => $this->getCurrentDestination(),
|
|
593
|
+
'next_destination' => $this->getNextDestination(),
|
|
594
|
+
'first_waypoint_completed' => $orderProgressPercentage > 10,
|
|
595
|
+
'last_waypoint_completed' => $orderProgressPercentage === 100 || $this->order->status === 'completed',
|
|
596
|
+
];
|
|
597
|
+
});
|
|
526
598
|
}
|
|
527
599
|
}
|
package/translations/en-us.yaml
CHANGED
|
@@ -7,7 +7,8 @@ common:
|
|
|
7
7
|
bulk-dispatch: Bulk Dispatch
|
|
8
8
|
dispatch-orders: Dispatch Orders
|
|
9
9
|
dispatch-order: Dispatch Order
|
|
10
|
-
assign-driver:
|
|
10
|
+
assign-driver: Assign Driver
|
|
11
|
+
assign-drivers: Assign Drivers
|
|
11
12
|
no-driver-assigned: No driver assigned
|
|
12
13
|
details: Details
|
|
13
14
|
name: Name
|
|
@@ -604,6 +605,7 @@ order:
|
|
|
604
605
|
tracking-number: Tracking Number
|
|
605
606
|
vehicle-assigned: Vehicle Assigned
|
|
606
607
|
view-activity: View Activity
|
|
608
|
+
no-order-activity: No order activity
|
|
607
609
|
waypoint-actions: Waypoint Actions
|
|
608
610
|
documents-files: Documents & Files
|
|
609
611
|
order-metadata: Order Metadata
|