@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.
Files changed (91) hide show
  1. package/addon/components/customer/create-order-form.hbs +1 -1
  2. package/addon/components/customer/order-form.hbs +34 -34
  3. package/addon/components/customer/orders.hbs +2 -2
  4. package/addon/components/display-place.hbs +1 -1
  5. package/addon/components/driver/pill.hbs +16 -17
  6. package/addon/components/driver/pill.js +5 -1
  7. package/addon/components/map/leaflet-live-map.js +35 -3
  8. package/addon/components/map/order-list-overlay/order.hbs +1 -0
  9. package/addon/components/map/order-list-overlay/order.js +42 -0
  10. package/addon/components/modals/bulk-assign-driver.hbs +2 -2
  11. package/addon/components/order/details/detail.hbs +6 -6
  12. package/addon/components/order/details/notes.js +1 -1
  13. package/addon/components/order/route-editor.hbs +3 -3
  14. package/addon/components/order-tracking-lookup.hbs +1 -1
  15. package/addon/components/service-rate/details.hbs +117 -75
  16. package/addon/components/service-rate/form.hbs +7 -4
  17. package/addon/components/service-rate/form.js +6 -0
  18. package/addon/components/vehicle/pill.hbs +32 -33
  19. package/addon/components/vehicle/pill.js +5 -1
  20. package/addon/controllers/operations/orders/index.js +1 -1
  21. package/addon/controllers/operations/scheduler/index.js +17 -2
  22. package/addon/controllers/operations/service-rates/index/edit.js +1 -7
  23. package/addon/controllers/operations/service-rates/index/new.js +0 -7
  24. package/addon/controllers/operations/service-rates/index.js +10 -2
  25. package/addon/routes/operations/orders/index/details.js +7 -0
  26. package/addon/routes/operations/scheduler/index.js +3 -3
  27. package/addon/services/driver-actions.js +20 -4
  28. package/addon/services/leaflet-routing-control.js +7 -1
  29. package/addon/services/order-list-overlay.js +0 -1
  30. package/addon/services/place-actions.js +20 -4
  31. package/addon/services/service-rate-actions.js +31 -0
  32. package/addon/services/vehicle-actions.js +20 -4
  33. package/addon/templates/operations/scheduler/index.hbs +2 -2
  34. package/addon/utils/create-full-calendar-event-from-order.js +6 -0
  35. package/composer.json +1 -1
  36. package/extension.json +1 -1
  37. package/package.json +4 -4
  38. package/server/migrations/2025_12_16_000001_add_subject_created_at_index_to_positions.php +40 -0
  39. package/server/migrations/2025_12_16_000003_add_performance_indexes_to_fleetops_core_tables.php +442 -0
  40. package/server/src/Console/Commands/DispatchAdhocOrders.php +7 -2
  41. package/server/src/Http/Controllers/Api/v1/DriverController.php +2 -0
  42. package/server/src/Http/Controllers/Api/v1/OrderController.php +30 -0
  43. package/server/src/Http/Controllers/Internal/v1/LiveController.php +184 -86
  44. package/server/src/Http/Controllers/Internal/v1/OrderController.php +14 -1
  45. package/server/src/Http/Controllers/Internal/v1/PlaceController.php +5 -0
  46. package/server/src/Http/Filter/DriverFilter.php +10 -0
  47. package/server/src/Http/Filter/OrderFilter.php +8 -1
  48. package/server/src/Http/Resources/v1/Contact.php +2 -2
  49. package/server/src/Http/Resources/v1/Entity.php +1 -1
  50. package/server/src/Http/Resources/v1/Index/Customer.php +33 -0
  51. package/server/src/Http/Resources/v1/Index/Driver.php +45 -0
  52. package/server/src/Http/Resources/v1/Index/Facilitator.php +33 -0
  53. package/server/src/Http/Resources/v1/Index/Order.php +127 -0
  54. package/server/src/Http/Resources/v1/Index/Payload.php +57 -0
  55. package/server/src/Http/Resources/v1/Index/Place.php +45 -0
  56. package/server/src/Http/Resources/v1/Index/TrackingNumber.php +25 -0
  57. package/server/src/Http/Resources/v1/Index/Vehicle.php +50 -0
  58. package/server/src/Http/Resources/v1/Order.php +41 -14
  59. package/server/src/Http/Resources/v1/Place.php +1 -1
  60. package/server/src/Http/Resources/v1/Position.php +2 -1
  61. package/server/src/Http/Resources/v1/ServiceRate.php +4 -4
  62. package/server/src/Http/Resources/v1/ServiceRateFee.php +12 -12
  63. package/server/src/Http/Resources/v1/ServiceRateParcelFee.php +14 -7
  64. package/server/src/Http/Resources/v1/TrackingNumber.php +1 -1
  65. package/server/src/Http/Resources/v1/Waypoint.php +1 -1
  66. package/server/src/Listeners/HandleOrderDispatched.php +5 -0
  67. package/server/src/Models/Contact.php +2 -0
  68. package/server/src/Models/Device.php +2 -0
  69. package/server/src/Models/DeviceEvent.php +2 -0
  70. package/server/src/Models/Driver.php +2 -0
  71. package/server/src/Models/FuelReport.php +2 -0
  72. package/server/src/Models/Issue.php +2 -0
  73. package/server/src/Models/Order.php +12 -5
  74. package/server/src/Models/Place.php +2 -0
  75. package/server/src/Models/Position.php +2 -0
  76. package/server/src/Models/ServiceArea.php +2 -0
  77. package/server/src/Models/ServiceRate.php +5 -1
  78. package/server/src/Models/ServiceRateFee.php +3 -17
  79. package/server/src/Models/ServiceRateParcelFee.php +1 -12
  80. package/server/src/Models/Vehicle.php +2 -0
  81. package/server/src/Models/Vendor.php +2 -0
  82. package/server/src/Models/Zone.php +2 -0
  83. package/server/src/Observers/DriverObserver.php +23 -0
  84. package/server/src/Observers/OrderObserver.php +31 -0
  85. package/server/src/Observers/PlaceObserver.php +31 -0
  86. package/server/src/Observers/ServiceRateObserver.php +0 -18
  87. package/server/src/Observers/VehicleObserver.php +7 -0
  88. package/server/src/Support/LiveCacheService.php +165 -0
  89. package/server/src/Support/OSRM.php +49 -22
  90. package/server/src/Support/OrderTracker.php +100 -28
  91. 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
- $coordinates = array_map(function (Point $point) {
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
- $url = self::$baseUrl . "/route/v1/driving/{$coordinates}";
83
- $response = Http::get($url, $queryParameters);
84
- $data = $response->json();
85
-
86
- // Check for the presence of the encoded polyline in each route and decode it if found
87
- if (isset($data['routes']) && is_array($data['routes'])) {
88
- foreach ($data['routes'] as &$route) {
89
- if (isset($route['geometry'])) {
90
- $route['waypoints'] = self::decodePolyline($route['geometry']);
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
- // Store the result in the cache for 60 minutes
96
- Cache::put($cacheKey, $data, 60 * 60);
104
+ // Store the result in the cache for 60 minutes
105
+ Cache::put($cacheKey, $data, 60 * 60);
97
106
 
98
- return $data;
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 (Point $point) {
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 (Point $point) {
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 (Point $point) {
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
- // Load missing waypoints and places
491
- $waypoints = $this->payload->getAllStops();
492
-
493
- // ETA's
494
- $eta = [];
495
- foreach ($waypoints as $waypoint) {
496
- $eta[$waypoint->uuid] = $this->getWaypointETA($waypoint);
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
- return $eta;
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
- $estimatedCompletionTime = $this->getEstimatedCompletionTime();
508
- $orderProgressPercentage = $this->getOrderProgressPercentage();
509
-
510
- return [
511
- 'driver_current_location' => $this->getDriverCurrentLocation(),
512
- 'progress_percentage' => $orderProgressPercentage,
513
- 'total_distance' => $this->getTotalDistance(),
514
- 'completed_distance' => $this->getCompletedDistance(),
515
- 'current_destination_eta' => $this->getCurrentDestinationETA(),
516
- 'completion_eta' => $this->getCompletionETA(),
517
- 'estimated_completion_time' => $estimatedCompletionTime,
518
- 'estimated_completion_time_formatted' => $estimatedCompletionTime instanceof Carbon ? $estimatedCompletionTime->format('M jS, Y H:i') : null,
519
- 'start_time' => $this->getOrderStartTime(),
520
- 'completion_time' => $this->getOrderCompletionTime(),
521
- 'current_destination' => $this->getCurrentDestination(),
522
- 'next_destination' => $this->getNextDestination(),
523
- 'first_waypoint_completed' => $orderProgressPercentage > 10,
524
- 'last_waypoint_completed' => $orderProgressPercentage === 100 || $this->order->status === 'completed',
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
  }
@@ -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: Dispatch Orders
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