@fleetbase/fleetops-engine 0.6.47 → 0.6.49

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 (223) 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/driver/details.hbs +5 -1
  6. package/addon/components/driver/form.hbs +11 -1
  7. package/addon/components/fleet-listing-panel.hbs +2 -2
  8. package/addon/components/fleet-listing-panel.js +10 -0
  9. package/addon/components/map/google-live-map.js +12 -73
  10. package/addon/components/map/leaflet-live-map.hbs +149 -33
  11. package/addon/components/map/order-list-overlay/order.hbs +18 -3
  12. package/addon/components/map/toolbar.hbs +2 -2
  13. package/addon/components/map/toolbar.js +10 -2
  14. package/addon/components/orchestrator/phase-builder.hbs +49 -5
  15. package/addon/components/orchestrator/phase-builder.js +34 -1
  16. package/addon/components/orchestrator/resource-panel.hbs +19 -1
  17. package/addon/components/orchestrator/resource-panel.js +4 -0
  18. package/addon/components/orchestrator-workbench.js +4 -1
  19. package/addon/components/order/details/purchase-rate.hbs +19 -22
  20. package/addon/components/order/details/route.hbs +26 -1
  21. package/addon/components/order/details/route.js +50 -0
  22. package/addon/components/order/details/tracking.hbs +171 -27
  23. package/addon/components/order/details/tracking.js +457 -0
  24. package/addon/components/order/form.hbs +2 -2
  25. package/addon/components/order/kanban-card.hbs +18 -3
  26. package/addon/components/order-list-overlay/order.hbs +18 -3
  27. package/addon/components/order-progress-card.hbs +21 -6
  28. package/addon/components/order-tracking-lookup.hbs +7 -7
  29. package/addon/components/order-tracking-lookup.js +1 -1
  30. package/addon/components/route-list.hbs +78 -8
  31. package/addon/components/route-list.js +48 -6
  32. package/addon/components/tracking-stop-progress.hbs +45 -0
  33. package/addon/components/tracking-stop-progress.js +79 -0
  34. package/addon/components/widget/fleet-ops-key-metrics.hbs +19 -17
  35. package/addon/components/widget/fleet-ops-key-metrics.js +26 -0
  36. package/addon/components/widget/fuel-efficiency.hbs +26 -0
  37. package/addon/components/widget/fuel-efficiency.js +43 -0
  38. package/addon/components/widget/geofence-violations.hbs +44 -0
  39. package/addon/components/widget/geofence-violations.js +55 -0
  40. package/addon/components/widget/issues-insights.hbs +43 -0
  41. package/addon/components/widget/issues-insights.js +62 -0
  42. package/addon/components/widget/kpi-active-orders.hbs +10 -0
  43. package/addon/components/widget/kpi-active-orders.js +5 -0
  44. package/addon/components/widget/kpi-aov.hbs +10 -0
  45. package/addon/components/widget/kpi-aov.js +5 -0
  46. package/addon/components/widget/kpi-distance.hbs +10 -0
  47. package/addon/components/widget/kpi-distance.js +5 -0
  48. package/addon/components/widget/kpi-drivers-online.hbs +10 -0
  49. package/addon/components/widget/kpi-drivers-online.js +5 -0
  50. package/addon/components/widget/kpi-earnings.hbs +10 -0
  51. package/addon/components/widget/kpi-earnings.js +5 -0
  52. package/addon/components/widget/kpi-open-issues.hbs +11 -0
  53. package/addon/components/widget/kpi-open-issues.js +5 -0
  54. package/addon/components/widget/kpi-tile.hbs +59 -0
  55. package/addon/components/widget/kpi-tile.js +168 -0
  56. package/addon/components/widget/live-fleet.hbs +122 -0
  57. package/addon/components/widget/live-fleet.js +365 -0
  58. package/addon/components/widget/maintenance-overview.hbs +45 -0
  59. package/addon/components/widget/maintenance-overview.js +40 -0
  60. package/addon/components/widget/on-time-delivery.hbs +27 -0
  61. package/addon/components/widget/on-time-delivery.js +73 -0
  62. package/addon/components/widget/operations-pulse.hbs +51 -0
  63. package/addon/components/widget/operations-pulse.js +173 -0
  64. package/addon/components/widget/orders-by-status.hbs +26 -0
  65. package/addon/components/widget/orders-by-status.js +54 -0
  66. package/addon/components/widget/revenue-trend.hbs +34 -0
  67. package/addon/components/widget/revenue-trend.js +67 -0
  68. package/addon/components/widget/top-drivers.hbs +44 -0
  69. package/addon/components/widget/top-drivers.js +54 -0
  70. package/addon/controllers/settings/routing.js +124 -1
  71. package/addon/extension.js +172 -3
  72. package/addon/helpers/format-duration.js +38 -0
  73. package/addon/routes/application.js +6 -0
  74. package/addon/services/map-adapter/google.js +13 -2
  75. package/addon/services/order-list-overlay.js +2 -2
  76. package/addon/styles/fleetops-engine.css +806 -46
  77. package/addon/templates/maintenance/equipment/index.hbs +1 -1
  78. package/addon/templates/maintenance/parts/index.hbs +1 -1
  79. package/addon/templates/maintenance/schedules/index.hbs +1 -1
  80. package/addon/templates/management/vehicles/index.hbs +1 -1
  81. package/addon/templates/settings/avatars.hbs +1 -1
  82. package/addon/templates/settings/custom-fields.hbs +1 -1
  83. package/addon/templates/settings/map.hbs +1 -1
  84. package/addon/templates/settings/navigator-app.hbs +1 -1
  85. package/addon/templates/settings/notifications.hbs +1 -1
  86. package/addon/templates/settings/orchestrator.hbs +1 -1
  87. package/addon/templates/settings/payments/index.hbs +1 -1
  88. package/addon/templates/settings/payments/onboard.hbs +1 -1
  89. package/addon/templates/settings/routing.hbs +82 -4
  90. package/addon/templates/settings/scheduling.hbs +1 -1
  91. package/addon/utils/live-map-card-content.js +171 -0
  92. package/app/components/tracking-stop-progress.js +1 -0
  93. package/app/components/widget/fuel-efficiency.js +1 -0
  94. package/app/components/widget/geofence-violations.js +1 -0
  95. package/app/components/widget/issues-insights.js +1 -0
  96. package/app/components/widget/kpi-active-orders.js +1 -0
  97. package/app/components/widget/kpi-aov.js +1 -0
  98. package/app/components/widget/kpi-distance.js +1 -0
  99. package/app/components/widget/kpi-drivers-online.js +1 -0
  100. package/app/components/widget/kpi-earnings.js +1 -0
  101. package/app/components/widget/kpi-open-issues.js +1 -0
  102. package/app/components/widget/kpi-tile.js +1 -0
  103. package/app/components/widget/live-fleet.js +1 -0
  104. package/app/components/widget/maintenance-overview.js +1 -0
  105. package/app/components/widget/on-time-delivery.js +1 -0
  106. package/app/components/widget/operations-pulse.js +1 -0
  107. package/app/components/widget/orders-by-status.js +1 -0
  108. package/app/components/widget/revenue-trend.js +1 -0
  109. package/app/components/widget/top-drivers.js +1 -0
  110. package/app/helpers/format-duration.js +1 -0
  111. package/app/utils/live-map-card-content.js +1 -0
  112. package/composer.json +1 -1
  113. package/extension.json +1 -1
  114. package/package.json +3 -3
  115. package/pnpm-workspace.yaml +1 -0
  116. package/server/config/fleetops.php +32 -11
  117. package/server/config/telematics.php +111 -1
  118. package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +86 -0
  119. package/server/migrations/2026_05_18_000001_add_license_expiry_to_drivers_table.php +25 -0
  120. package/server/seeders/Testing/Concerns/SeedsTestingData.php +183 -0
  121. package/server/seeders/Testing/ConnectivitySeeder.php +212 -0
  122. package/server/seeders/Testing/FleetSeeder.php +341 -0
  123. package/server/seeders/Testing/MaintenanceSeeder.php +238 -0
  124. package/server/seeders/Testing/NetworkSeeder.php +250 -0
  125. package/server/seeders/Testing/OrdersSeeder.php +214 -0
  126. package/server/seeders/Testing/TestingSeeder.php +40 -0
  127. package/server/seeders/Testing/VroomOrchestrationSeeder.php +409 -0
  128. package/server/src/Console/Commands/ProcessOperationalAlerts.php +279 -0
  129. package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +64 -0
  130. package/server/src/Http/Controllers/Api/v1/OrderController.php +26 -26
  131. package/server/src/Http/Controllers/Api/v1/PayloadController.php +10 -10
  132. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +1 -1
  133. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +119 -0
  134. package/server/src/Http/Controllers/Internal/v1/ContactController.php +41 -0
  135. package/server/src/Http/Controllers/Internal/v1/LiveController.php +6 -42
  136. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +82 -3
  137. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +1 -1
  138. package/server/src/Http/Controllers/Internal/v1/OrderController.php +52 -21
  139. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
  140. package/server/src/Http/Controllers/Internal/v1/SettingController.php +164 -12
  141. package/server/src/Http/Resources/v1/Driver.php +2 -0
  142. package/server/src/Http/Resources/v1/Fleet.php +4 -0
  143. package/server/src/Http/Resources/v1/Index/Driver.php +48 -0
  144. package/server/src/Http/Resources/v1/Index/Payload.php +2 -2
  145. package/server/src/Http/Resources/v1/Index/Vehicle.php +74 -1
  146. package/server/src/Models/Contact.php +122 -13
  147. package/server/src/Models/Driver.php +2 -0
  148. package/server/src/Models/Order.php +1 -1
  149. package/server/src/Models/OrderConfig.php +14 -11
  150. package/server/src/Models/Payload.php +8 -8
  151. package/server/src/Models/Place.php +0 -3
  152. package/server/src/Models/ServiceRate.php +1 -1
  153. package/server/src/Notifications/LateDeparture.php +48 -0
  154. package/server/src/Notifications/ProlongedStoppage.php +48 -0
  155. package/server/src/Notifications/RouteDeviation.php +48 -0
  156. package/server/src/Observers/ContactObserver.php +8 -0
  157. package/server/src/Orchestration/Engines/CapacityAllocationEngine.php +220 -0
  158. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +357 -38
  159. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +259 -51
  160. package/server/src/Providers/FleetOpsServiceProvider.php +32 -0
  161. package/server/src/Providers/NotificationServiceProvider.php +3 -0
  162. package/server/src/Support/Analytics/AbstractAnalytics.php +77 -0
  163. package/server/src/Support/Analytics/FuelEfficiency.php +83 -0
  164. package/server/src/Support/Analytics/GeofenceViolations.php +63 -0
  165. package/server/src/Support/Analytics/IssuesInsights.php +67 -0
  166. package/server/src/Support/Analytics/LiveFleet.php +127 -0
  167. package/server/src/Support/Analytics/MaintenanceOverview.php +70 -0
  168. package/server/src/Support/Analytics/OnTimeDelivery.php +75 -0
  169. package/server/src/Support/Analytics/OperationsPulse.php +86 -0
  170. package/server/src/Support/Analytics/OrdersByStatus.php +64 -0
  171. package/server/src/Support/Analytics/RevenueTrend.php +79 -0
  172. package/server/src/Support/Analytics/TopDrivers.php +92 -0
  173. package/server/src/Support/LiveOrderQuery.php +75 -0
  174. package/server/src/Support/Metrics/AbstractMetric.php +170 -0
  175. package/server/src/Support/Metrics/ActiveLiveOrdersMetric.php +28 -0
  176. package/server/src/Support/Metrics/AvgOrderValueMetric.php +53 -0
  177. package/server/src/Support/Metrics/DriversOnlineMetric.php +30 -0
  178. package/server/src/Support/Metrics/EarningsMetric.php +30 -0
  179. package/server/src/Support/Metrics/FuelCostsMetric.php +30 -0
  180. package/server/src/Support/Metrics/MoneyMetric.php +30 -0
  181. package/server/src/Support/Metrics/OpenIssuesMetric.php +35 -0
  182. package/server/src/Support/Metrics/OrdersCanceledMetric.php +35 -0
  183. package/server/src/Support/Metrics/OrdersCompletedMetric.php +35 -0
  184. package/server/src/Support/Metrics/OrdersInProgressMetric.php +51 -0
  185. package/server/src/Support/Metrics/OrdersScheduledMetric.php +37 -0
  186. package/server/src/Support/Metrics/Registry.php +46 -0
  187. package/server/src/Support/Metrics/ResolvedIssuesMetric.php +35 -0
  188. package/server/src/Support/Metrics/TotalCustomersMetric.php +29 -0
  189. package/server/src/Support/Metrics/TotalDistanceTraveledMetric.php +35 -0
  190. package/server/src/Support/Metrics/TotalDriversMetric.php +28 -0
  191. package/server/src/Support/Metrics/TotalTimeTraveledMetric.php +35 -0
  192. package/server/src/Support/Metrics.php +46 -234
  193. package/server/src/Support/OSRM.php +11 -6
  194. package/server/src/Support/OrderTracker.php +7 -582
  195. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +290 -0
  196. package/server/src/Support/Telematics/Providers/SafeeProvider.php +333 -0
  197. package/server/src/Support/Utils.php +1 -1
  198. package/server/src/Tracking/Contracts/TrackingProviderInterface.php +19 -0
  199. package/server/src/Tracking/Providers/CalculatedTrackingProvider.php +65 -0
  200. package/server/src/Tracking/Providers/GoogleRoutesTrackingProvider.php +118 -0
  201. package/server/src/Tracking/Providers/OsrmTrackingProvider.php +71 -0
  202. package/server/src/Tracking/README.md +25 -0
  203. package/server/src/Tracking/Support/FakeTrackingProvider.php +42 -0
  204. package/server/src/Tracking/TrackingContext.php +63 -0
  205. package/server/src/Tracking/TrackingContextBuilder.php +204 -0
  206. package/server/src/Tracking/TrackingIntelligenceService.php +180 -0
  207. package/server/src/Tracking/TrackingOptions.php +49 -0
  208. package/server/src/Tracking/TrackingProviderCapabilities.php +30 -0
  209. package/server/src/Tracking/TrackingProviderManager.php +89 -0
  210. package/server/src/Tracking/TrackingProviderRegistry.php +35 -0
  211. package/server/src/Tracking/TrackingProviderResult.php +20 -0
  212. package/server/src/Tracking/TrackingStop.php +59 -0
  213. package/server/src/routes.php +27 -0
  214. package/server/tests/AnalyticsRoutesTest.php +41 -0
  215. package/server/tests/CapacityAllocationEngineTest.php +108 -0
  216. package/server/tests/LiveOrderQueryTest.php +34 -0
  217. package/server/tests/MetricsRegistryTest.php +88 -0
  218. package/server/tests/OrchestrationPayloadBuilderTest.php +132 -0
  219. package/server/tests/OrchestratorConsumableApiTest.php +74 -0
  220. package/server/tests/PingDriverEndpointTest.php +19 -0
  221. package/server/tests/TrackingIntelligenceTest.php +176 -0
  222. package/server/tests/VroomOrchestrationEngineTest.php +250 -0
  223. package/translations/en-us.yaml +41 -0
@@ -2,598 +2,23 @@
2
2
 
3
3
  namespace Fleetbase\FleetOps\Support;
4
4
 
5
- use Fleetbase\FleetOps\Models\Driver;
6
5
  use Fleetbase\FleetOps\Models\Order;
7
- use Fleetbase\FleetOps\Models\Payload;
8
- use Fleetbase\FleetOps\Models\Place;
9
- use Fleetbase\FleetOps\Models\Waypoint;
10
- use Fleetbase\LaravelMysqlSpatial\Types\Point;
11
- use Illuminate\Support\Arr;
12
- use Illuminate\Support\Carbon;
13
- use Illuminate\Support\Collection;
14
- use Illuminate\Support\Facades\Cache;
15
- use Illuminate\Support\Facades\Log;
16
- use Illuminate\Support\Str;
6
+ use Fleetbase\FleetOps\Tracking\TrackingIntelligenceService;
7
+ use Fleetbase\FleetOps\Tracking\TrackingOptions;
17
8
 
18
- /**
19
- * Class OrderTracker.
20
- *
21
- * Provides functionality to track an order's progress, calculate ETA, and retrieve various order-related data.
22
- */
23
9
  class OrderTracker
24
10
  {
25
- /** @var Order The order being tracked */
26
- protected Order $order;
27
-
28
- /** @var Payload The payload associated with the order */
29
- protected Payload $payload;
30
-
31
- /** @var Driver The driver assigned to the order */
32
- protected ?Driver $driver;
33
-
34
- /** @var bool Flag to indicate if the order has multiple dropoff points */
35
- protected bool $isMultipleDropOrder = false;
36
-
37
- /**
38
- * Constructor for OrderTracker.
39
- *
40
- * @param Order $order the order to track
41
- */
42
- public function __construct(Order $order)
43
- {
44
- $order->loadAssignedDriver();
45
-
46
- $this->order = $order;
47
- $this->payload = $order->getPayload(function ($payload) {
48
- $payload->loadMissing(['pickup', 'dropoff', 'waypoints', 'waypointMarkers']);
49
- });
50
- $this->isMultipleDropOrder = $this->payload->isMultipleDropOrder;
51
- $this->driver = $order->driverAssigned;
52
- }
53
-
54
- /**
55
- * Get the current location of the driver or starting point.
56
- *
57
- * @return Point|null the current location of the driver, or null if unavailable
58
- */
59
- public function getDriverCurrentLocation(): ?Point
60
- {
61
- if ($this->driver) {
62
- return $this->driver->location;
63
- }
64
-
65
- $startingPoint = $this->payload->getPickupOrCurrentWaypoint();
66
- if ($startingPoint) {
67
- return $startingPoint->location;
68
- }
69
-
70
- return null;
71
- }
72
-
73
- /**
74
- * Get the percentage progress of the order.
75
- *
76
- * @return int|float the percentage of the order progress
77
- */
78
- public function getOrderProgressPercentage(): int|float
79
- {
80
- $totalDistance = $this->getTotalDistance();
81
- $completedDistance = $this->getCompletedDistance();
82
- $cannotUseDistance = $totalDistance == -1 || $completedDistance == -1 || $completedDistance === 0;
83
-
84
- // Get order percentage by activity if distance-based progress is not available
85
- if ($cannotUseDistance) {
86
- /** @var Collection $activities */
87
- $activities = $this->order->orderConfig ? $this->order->orderConfig->activities() : collect();
88
- $totalActivity = $activities->count();
89
- if ($totalActivity === 0) {
90
- return 100; // No activities, so treat it as 100% complete
91
- }
92
-
93
- // Calculate completed activities
94
- $completedActivity = $activities->filter(function ($activity) {
95
- return $activity->isCompleted($this->order);
96
- })->count();
97
-
98
- // Return progress percentage based on completed activities
99
- return round(($completedActivity / $totalActivity) * 100, 2);
100
- }
101
-
102
- if ($totalDistance === 0) {
103
- return 100;
104
- }
105
-
106
- return round(($completedDistance / $totalDistance) * 100, 2);
107
- }
108
-
109
- /**
110
- * Get the total distance for the order.
111
- *
112
- * @return int|float the total distance of the order in meters
113
- */
114
- public function getTotalDistance(): int|float
115
- {
116
- $points = $this->getAllDestinationPoints()->toArray();
117
- if (count($points) < 2) {
118
- return -1;
119
- }
120
-
121
- try {
122
- $response = OSRM::getRouteFromPoints($points);
123
- if (isset($response['code']) && $response['code'] === 'Ok') {
124
- $route = Arr::first($response['routes']);
125
- if ($route) {
126
- return data_get($route, 'distance', 0);
127
- }
128
- }
129
- } catch (\Exception $e) {
130
- Log::warning('Error loading OSRM routes from order points.', [$e]);
131
-
132
- return -1;
133
- }
134
-
135
- return -1;
136
- }
137
-
138
- /**
139
- * Get the completed distance by the driver.
140
- *
141
- * @return float the completed distance in meters
142
- */
143
- public function getCompletedDistance(): float
144
- {
145
- $points = $this->getCompletedDestinationPoints()->toArray();
146
- if (count($points) < 2) {
147
- return -1;
148
- }
149
-
150
- try {
151
- $response = OSRM::getRouteFromPoints($points);
152
- if (isset($response['code']) && $response['code'] === 'Ok') {
153
- $route = Arr::first($response['routes']);
154
- if ($route) {
155
- return data_get($route, 'distance', 0);
156
- }
157
- }
158
- } catch (\Exception $e) {
159
- Log::warning('Error loading OSRM routes from order points.', [$e]);
160
-
161
- return -1;
162
- }
163
-
164
- return -1;
165
- }
166
-
167
- /**
168
- * Get the estimated time of arrival (ETA) for the current destination.
169
- *
170
- * @return float the ETA in seconds or -1 if unable to calculate
171
- */
172
- public function getCurrentDestinationETA(): float
173
- {
174
- $start = $this->getDriverCurrentLocation();
175
- $currentDestination = $this->getCurrentDestination();
176
- $end = $currentDestination ? $currentDestination->location : null;
177
- if ($start == $end) {
178
- $nextDestination = $this->getNextDestination();
179
- if ($nextDestination) {
180
- $end = $nextDestination->location;
181
- }
182
- }
183
-
184
- if (!$start || !$end) {
185
- return -1;
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
-
196
- try {
197
- $response = OSRM::getRoute($start, $end);
198
- if (isset($response['code']) && $response['code'] === 'Ok') {
199
- $route = Arr::first($response['routes']);
200
- if ($route) {
201
- return data_get($route, 'duration', -1);
202
- }
203
- }
204
- } catch (\Exception $e) {
205
- Log::warning('Error loading OSRM route from start and end points.', [$e]);
206
-
207
- return -1;
208
- }
209
-
210
- return -1;
211
- }
212
-
213
- public function getWaypointETA(Waypoint|Place $waypoint): float
214
- {
215
- if ($waypoint instanceof Waypoint) {
216
- $waypoint->loadMissing('place');
217
- $waypoint = $waypoint->place;
218
- }
219
-
220
- $start = $this->getDriverCurrentLocation();
221
- $end = $waypoint->location;
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
-
231
- try {
232
- $response = OSRM::getRoute($start, $end);
233
- if (isset($response['code']) && $response['code'] === 'Ok') {
234
- $route = Arr::first($response['routes']);
235
- if ($route) {
236
- return data_get($route, 'duration', -1);
237
- }
238
- }
239
- } catch (\Exception $e) {
240
- Log::warning('Error loading OSRM routes from start and end points.', [$e]);
241
-
242
- return -1;
243
- }
244
-
245
- return -1;
246
- }
247
-
248
- /**
249
- * Get the estimated time of arrival (ETA) for the completion of the order.
250
- *
251
- * @return float the ETA in seconds or -1 if unable to calculate
252
- */
253
- public function getCompletionETA(): float
254
- {
255
- $start = $this->getDriverCurrentLocation();
256
- $end = $this->payload->getDropoffOrLastWaypoint()->location;
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
-
266
- try {
267
- $response = OSRM::getRoute($start, $end);
268
- if (isset($response['code']) && $response['code'] === 'Ok') {
269
- $route = Arr::first($response['routes']);
270
- if ($route) {
271
- return data_get($route, 'duration', -1);
272
- }
273
- }
274
- } catch (\Exception $e) {
275
- Log::warning('Error loading OSRM routes from start and end points.', [$e]);
276
-
277
- return -1;
278
- }
279
-
280
- return -1;
281
- }
282
-
283
- /**
284
- * Get the estimated completion time of the order.
285
- *
286
- * @return Carbon the estimated completion time
287
- */
288
- public function getEstimatedCompletionTime(): ?Carbon
289
- {
290
- $currentEtaCompletion = $this->getCompletionETA();
291
-
292
- return now()->addSeconds($currentEtaCompletion);
293
- }
294
-
295
- /**
296
- * Get the start time of the order.
297
- *
298
- * @return Carbon the time the order started
299
- */
300
- public function getOrderStartTime(): ?Carbon
11
+ public function __construct(protected Order $order)
301
12
  {
302
- return $this->order->started_at;
303
13
  }
304
14
 
305
- /**
306
- * Get the completion time of the order, if completed.
307
- *
308
- * @return Carbon|null the time the order was completed, or null if not completed
309
- */
310
- public function getOrderCompletionTime(): ?Carbon
15
+ public function eta(array $options = []): array
311
16
  {
312
- return $this->order->completed_at;
17
+ return app(TrackingIntelligenceService::class)->eta($this->order, TrackingOptions::fromArray($options));
313
18
  }
314
19
 
315
- /**
316
- * Get all destinations (pickup, waypoints, dropoff) for the order.
317
- *
318
- * @return Collection a collection of all destination places
319
- */
320
- public function getAllDestinations(): Collection
20
+ public function toArray(array $options = []): array
321
21
  {
322
- return collect([$this->payload->pickup, ...$this->payload->waypoints, $this->payload->dropoff])->filter();
323
- }
324
-
325
- /**
326
- * Get all destination points (pickup, waypoints, dropoff) as spatial points.
327
- *
328
- * @return Collection a collection of Point objects for each destination
329
- */
330
- public function getAllDestinationPoints(): Collection
331
- {
332
- return $this->getAllDestinations()->map(function (Place $place) {
333
- return $place->location;
334
- })->filter()->values();
335
- }
336
-
337
- /**
338
- * Get the current destination the driver is heading to.
339
- *
340
- * @return Place|null the current destination place object, or null if not available
341
- */
342
- public function getCurrentDestination(): ?Place
343
- {
344
- if ($this->isMultipleDropOrder) {
345
- if ($this->payload->waypoints) {
346
- $destination = null;
347
-
348
- if (Str::isUuid($this->payload->current_waypoint_uuid)) {
349
- $destination = $this->payload->waypoints->firstWhere('uuid', $this->payload->current_waypoint_uuid);
350
- }
351
-
352
- if (!$destination) {
353
- $destination = $this->payload->waypoints->first();
354
- }
355
-
356
- return $destination;
357
- }
358
-
359
- return null;
360
- }
361
-
362
- if ($this->order->status === 'created' || $this->order->status === 'dispatched' || $this->order->status === 'pending') {
363
- return $this->payload->pickup;
364
- }
365
-
366
- return $this->payload->dropoff;
367
- }
368
-
369
- /**
370
- * Get the completed destinations of the order.
371
- *
372
- * @return Collection a collection of completed destination places
373
- */
374
- public function getCompletedDestinations(): Collection
375
- {
376
- if ($this->isMultipleDropOrder) {
377
- return $this->payload->waypointMarkers->filter(
378
- function (Waypoint $waypoint) {
379
- return $this->isWaypointCompleted($waypoint);
380
- }
381
- )->map(
382
- function (Waypoint $waypoint) {
383
- $waypoint->loadMissing('place');
384
-
385
- return $waypoint->place;
386
- }
387
- );
388
- }
389
-
390
- if ($this->order->status === 'created' || $this->order->status === 'dispatched' || $this->order->status === 'pending') {
391
- return collect();
392
- }
393
-
394
- if ($this->order->status === 'completed') {
395
- return collect([$this->getPickup(), $this->getDropoff()]);
396
- }
397
-
398
- return collect([$this->getPickup()]);
399
- }
400
-
401
- /**
402
- * Get the completed destination points as spatial points.
403
- *
404
- * @return Collection a collection of Point objects representing the completed destination points
405
- */
406
- public function getCompletedDestinationPoints(): Collection
407
- {
408
- return $this->getCompletedDestinations()->map(function (Place $place) {
409
- return $place->location;
410
- })->filter()->values();
411
- }
412
-
413
- /**
414
- * Get the next destination that the driver should go to.
415
- *
416
- * @return Place|null the next destination place, or null if not available
417
- */
418
- public function getNextDestination(): ?Place
419
- {
420
- if ($this->isMultipleDropOrder) {
421
- /** @var Waypoint $nextWaypoint */
422
- $nextWaypoint = $this->payload->waypointMarkers->filter(function (Waypoint $waypoint) {
423
- return $this->isWaypointNotCompleted($waypoint) && $this->isWaypointNotCurrentDestination($waypoint);
424
- })->first();
425
-
426
- if ($nextWaypoint) {
427
- $nextWaypoint->loadMissing('place');
428
-
429
- return $nextWaypoint->place;
430
- }
431
- }
432
-
433
- return $this->payload->dropoff;
434
- }
435
-
436
- /**
437
- * Check if a waypoint has been completed.
438
- *
439
- * @param Waypoint $waypoint the waypoint to check
440
- *
441
- * @return bool true if the waypoint is completed or canceled, otherwise false
442
- */
443
- public function isWaypointCompleted(Waypoint $waypoint): bool
444
- {
445
- $waypoint->loadMissing(['trackingNumber', 'trackingNumber.status']);
446
- $status = strtolower($waypoint->status_code);
447
-
448
- return $status === 'completed' || $status === 'canceled';
449
- }
450
-
451
- /**
452
- * Check if a waypoint is not completed.
453
- *
454
- * @param Waypoint $waypoint the waypoint to check
455
- *
456
- * @return bool true if the waypoint is not completed, otherwise false
457
- */
458
- public function isWaypointNotCompleted(Waypoint $waypoint): bool
459
- {
460
- return !$this->isWaypointCompleted($waypoint);
461
- }
462
-
463
- /**
464
- * Check if a waypoint is the current destination.
465
- *
466
- * @param Waypoint $waypoint the waypoint to check
467
- *
468
- * @return bool true if the waypoint is the current destination, otherwise false
469
- */
470
- public function isWaypointCurrentDestination(Waypoint $waypoint): bool
471
- {
472
- if (!$this->payload->current_waypoint_uuid) {
473
- $currentDestination = $this->getCurrentDestination();
474
-
475
- return $currentDestination->uuid === $waypoint->place_uuid;
476
- }
477
-
478
- return $waypoint->place_uuid === $this->payload->current_waypoint_uuid;
479
- }
480
-
481
- /**
482
- * Check if a waypoint is not the current destination.
483
- *
484
- * @param Waypoint $waypoint the waypoint to check
485
- *
486
- * @return bool true if the waypoint is not the current destination, otherwise false
487
- */
488
- public function isWaypointNotCurrentDestination(Waypoint $waypoint): bool
489
- {
490
- return !$this->isWaypointCurrentDestination($waypoint);
491
- }
492
-
493
- /**
494
- * Get the pickup location of the order.
495
- *
496
- * @return Place the pickup place of the order
497
- */
498
- public function getPickup(): ?Place
499
- {
500
- return $this->payload->pickup;
501
- }
502
-
503
- /**
504
- * Get the dropoff location of the order.
505
- *
506
- * @return Place the dropoff place of the order
507
- */
508
- public function getDropoff(): ?Place
509
- {
510
- return $this->payload->dropoff;
511
- }
512
-
513
- public function eta(): array
514
- {
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
- }
528
-
529
- return $eta;
530
- });
531
- }
532
-
533
- /**
534
- * Get all key tracker information as an array.
535
- * Cached for 60 seconds to avoid repeated OSRM calls.
536
- */
537
- public function toArray(): array
538
- {
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
- });
22
+ return app(TrackingIntelligenceService::class)->track($this->order, TrackingOptions::fromArray($options));
598
23
  }
599
24
  }