@fleetbase/fleetops-engine 0.6.49 → 0.6.50
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 +2 -2
- package/addon/components/customer/form.hbs +2 -2
- package/addon/components/customer/order-form.hbs +2 -2
- package/addon/components/customer/orders.hbs +2 -2
- package/addon/components/display-place.hbs +2 -2
- package/addon/components/map/google-live-map.js +6 -2
- package/addon/components/map/leaflet-live-map.hbs +48 -14
- package/addon/components/map/leaflet-live-map.js +184 -20
- package/addon/components/modals/order-change-destination.hbs +46 -0
- package/addon/components/modals/order-change-destination.js +106 -0
- package/addon/components/modals/stop-activity.hbs +28 -0
- package/addon/components/modals/stop-activity.js +52 -0
- package/addon/components/modals/update-order-activity.hbs +45 -3
- package/addon/components/modals/update-order-activity.js +143 -5
- package/addon/components/orchestrator/resource-panel.hbs +0 -10
- package/addon/components/orchestrator/resource-panel.js +2 -4
- package/addon/components/order/activity-list.hbs +2 -2
- package/addon/components/order/activity-timeline.hbs +31 -29
- package/addon/components/order/details/activity.hbs +3 -3
- package/addon/components/order/details/activity.js +11 -0
- package/addon/components/order/details/documents.js +2 -0
- package/addon/components/order/details/purchase-rate.hbs +2 -2
- package/addon/components/order/details/route.hbs +11 -3
- package/addon/components/order/details/route.js +91 -14
- package/addon/components/order/details/tracking.hbs +101 -72
- package/addon/components/order/details/tracking.js +149 -2
- package/addon/components/order/form/details.hbs +3 -3
- package/addon/components/order/form/documents.js +11 -4
- package/addon/components/order/form/route.hbs +108 -104
- package/addon/components/order/form/route.js +37 -1
- package/addon/components/order/form/service-rate.hbs +2 -2
- package/addon/components/order-config-manager/activity-flow.hbs +4 -1
- package/addon/components/order-config-manager/activity-flow.js +178 -12
- package/addon/components/place/address.js +1 -1
- package/addon/components/route-list.hbs +17 -17
- package/addon/components/route-list.js +42 -10
- package/addon/components/service-rate/details.hbs +41 -0
- package/addon/components/service-rate/form.hbs +118 -0
- package/addon/components/service-rate/form.js +55 -0
- package/addon/components/vehicle/form.js +1 -1
- package/addon/components/vehicle/pill.hbs +1 -1
- package/addon/controllers/management/vehicles/index/new.js +1 -1
- package/addon/controllers/operations/orders/index/details.js +4 -4
- package/addon/controllers/operations/orders/index.js +14 -0
- package/addon/helpers/place-address-html.js +2 -2
- package/addon/routes/operations/service-rates/index.js +1 -1
- package/addon/services/geofence.js +163 -19
- package/addon/services/leaflet-contextmenu-manager.js +27 -0
- package/addon/services/vehicle-actions.js +1 -1
- package/addon/styles/fleetops-engine.css +91 -1
- package/addon/utils/fleet-ops-options.js +2 -1
- package/addon/utils/normalize-order-config-flow.js +41 -0
- package/addon/utils/place-address-html.js +3 -2
- package/addon/utils/route-visualization.js +54 -7
- package/app/components/modals/order-change-destination.js +1 -0
- package/app/components/modals/stop-activity.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
- package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
- package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
- package/server/resources/views/labels/default.php +57 -49
- package/server/resources/views/labels/waypoint-label.php +27 -20
- package/server/seeders/Testing/ConnectivitySeeder.php +1 -0
- package/server/seeders/Testing/FleetSeeder.php +22 -22
- package/server/seeders/Testing/OrdersSeeder.php +6 -22
- package/server/seeders/Testing/VroomOrchestrationSeeder.php +21 -21
- package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
- package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
- package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +98 -70
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +19 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +429 -26
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +2 -2
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
- package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
- package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
- package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
- package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
- package/server/src/Http/Resources/v1/Customer.php +79 -0
- package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
- package/server/src/Http/Resources/v1/Payload.php +23 -20
- package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
- package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
- package/server/src/Listeners/HandleOrderDispatched.php +2 -2
- package/server/src/Models/Contact.php +92 -15
- package/server/src/Models/Customer.php +64 -0
- package/server/src/Models/Driver.php +12 -3
- package/server/src/Models/Entity.php +2 -2
- package/server/src/Models/Order.php +29 -4
- package/server/src/Models/Payload.php +17 -1
- package/server/src/Models/ServiceRate.php +273 -1
- package/server/src/Models/ServiceRateFee.php +39 -9
- package/server/src/Models/TrackingNumber.php +8 -2
- package/server/src/Models/Vehicle.php +18 -2
- package/server/src/Models/Waypoint.php +2 -2
- package/server/src/Notifications/LateDeparture.php +2 -2
- package/server/src/Notifications/ProlongedStoppage.php +2 -2
- package/server/src/Notifications/RouteDeviation.php +2 -2
- package/server/src/Observers/ContactObserver.php +2 -0
- package/server/src/Observers/ServiceRateObserver.php +6 -6
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +1 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +1 -0
- package/server/src/Support/Analytics/AbstractAnalytics.php +6 -6
- package/server/src/Support/Analytics/FuelEfficiency.php +2 -2
- package/server/src/Support/Analytics/OrdersByStatus.php +1 -1
- package/server/src/Support/Analytics/RevenueTrend.php +0 -1
- package/server/src/Support/Analytics/TopDrivers.php +1 -1
- package/server/src/Support/CustomerAuth.php +98 -0
- package/server/src/Support/LabelPdf.php +52 -0
- package/server/src/Support/Metrics/AbstractMetric.php +4 -4
- package/server/src/Support/ResolvesOrderServiceStops.php +512 -0
- package/server/src/Tracking/TrackingContextBuilder.php +41 -38
- package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
- package/server/src/Tracking/TrackingStop.php +13 -11
- package/server/src/routes.php +43 -9
- package/server/tests/CapacityAllocationEngineTest.php +10 -10
- package/server/tests/CustomerContactIdentitySafetyTest.php +54 -0
- package/server/tests/CustomerEndpointTest.php +199 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
- package/server/tests/LabelPdfRenderingTest.php +23 -0
- package/server/tests/LiveControllerViewportTest.php +60 -0
- package/server/tests/MetricsRegistryTest.php +4 -8
- package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
- package/server/tests/OrderConfigEndpointTest.php +86 -0
- package/server/tests/OrderFileAttachmentTest.php +20 -0
- package/server/tests/ServiceAreaZoneApiShapeTest.php +34 -0
- package/server/tests/VehicleDriverAssignmentTest.php +18 -0
- package/translations/en-us.yaml +1 -0
- package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Events\EntityActivityChanged;
|
|
6
|
+
use Fleetbase\FleetOps\Events\EntityCompleted;
|
|
7
|
+
use Fleetbase\FleetOps\Events\WaypointActivityChanged;
|
|
8
|
+
use Fleetbase\FleetOps\Events\WaypointCompleted;
|
|
9
|
+
use Fleetbase\FleetOps\Flow\Activity;
|
|
10
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
11
|
+
use Fleetbase\FleetOps\Models\Payload;
|
|
12
|
+
use Fleetbase\FleetOps\Models\Place;
|
|
13
|
+
use Fleetbase\FleetOps\Models\Proof;
|
|
14
|
+
use Fleetbase\FleetOps\Models\TrackingNumber;
|
|
15
|
+
use Fleetbase\FleetOps\Models\TrackingStatus;
|
|
16
|
+
use Fleetbase\FleetOps\Models\Waypoint;
|
|
17
|
+
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
18
|
+
use Fleetbase\Support\TemplateString;
|
|
19
|
+
use Illuminate\Support\Collection;
|
|
20
|
+
use Illuminate\Support\Facades\DB;
|
|
21
|
+
|
|
22
|
+
trait ResolvesOrderServiceStops
|
|
23
|
+
{
|
|
24
|
+
protected function payloadHasWaypoints(?Payload $payload): bool
|
|
25
|
+
{
|
|
26
|
+
if (!$payload) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
$payload->loadMissing('waypoints');
|
|
31
|
+
|
|
32
|
+
return $payload->waypoints->isNotEmpty();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected function resolveProof($proof): ?Proof
|
|
36
|
+
{
|
|
37
|
+
if ($proof instanceof Proof) {
|
|
38
|
+
return $proof;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (is_string($proof)) {
|
|
42
|
+
return Proof::where('public_id', $proof)->orWhere('uuid', $proof)->first();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected function payloadServiceStops(?Payload $payload): Collection
|
|
49
|
+
{
|
|
50
|
+
if (!$payload) {
|
|
51
|
+
return collect();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
$payload->loadMissing([
|
|
55
|
+
'pickup',
|
|
56
|
+
'dropoff',
|
|
57
|
+
'return',
|
|
58
|
+
'waypoints',
|
|
59
|
+
'waypointMarkers.place',
|
|
60
|
+
'waypointMarkers.trackingNumber.status',
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
$stops = collect();
|
|
64
|
+
$sequence = 0;
|
|
65
|
+
|
|
66
|
+
if ($payload->pickup instanceof Place) {
|
|
67
|
+
$stops->push([
|
|
68
|
+
'type' => 'pickup',
|
|
69
|
+
'place' => $payload->pickup,
|
|
70
|
+
'waypoint' => null,
|
|
71
|
+
'tracking_number_uuid' => $payload->pickup_tracking_number_uuid,
|
|
72
|
+
'sequence' => ++$sequence,
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
$markers = $payload->waypointMarkers instanceof Collection ? $payload->waypointMarkers : collect();
|
|
77
|
+
if ($markers->isNotEmpty()) {
|
|
78
|
+
foreach ($markers->sortBy('order')->values() as $waypoint) {
|
|
79
|
+
$waypoint->loadMissing('place', 'trackingNumber.status');
|
|
80
|
+
|
|
81
|
+
if ($waypoint->place instanceof Place) {
|
|
82
|
+
$stops->push([
|
|
83
|
+
'type' => 'waypoint',
|
|
84
|
+
'place' => $waypoint->place,
|
|
85
|
+
'waypoint' => $waypoint,
|
|
86
|
+
'tracking_number_uuid' => $waypoint->tracking_number_uuid,
|
|
87
|
+
'sequence' => ++$sequence,
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} elseif ($payload->waypoints instanceof Collection) {
|
|
92
|
+
foreach ($payload->waypoints as $place) {
|
|
93
|
+
if ($place instanceof Place) {
|
|
94
|
+
$stops->push([
|
|
95
|
+
'type' => 'waypoint',
|
|
96
|
+
'place' => $place,
|
|
97
|
+
'waypoint' => null,
|
|
98
|
+
'tracking_number_uuid' => null,
|
|
99
|
+
'sequence' => ++$sequence,
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if ($payload->dropoff instanceof Place) {
|
|
106
|
+
$stops->push([
|
|
107
|
+
'type' => 'dropoff',
|
|
108
|
+
'place' => $payload->dropoff,
|
|
109
|
+
'waypoint' => null,
|
|
110
|
+
'tracking_number_uuid' => $payload->dropoff_tracking_number_uuid,
|
|
111
|
+
'sequence' => ++$sequence,
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if ($payload->return instanceof Place) {
|
|
116
|
+
$stops->push([
|
|
117
|
+
'type' => 'return',
|
|
118
|
+
'place' => $payload->return,
|
|
119
|
+
'waypoint' => null,
|
|
120
|
+
'tracking_number_uuid' => $payload->return_tracking_number_uuid,
|
|
121
|
+
'sequence' => ++$sequence,
|
|
122
|
+
]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return $stops->values();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected function payloadCurrentServiceStop(?Payload $payload): ?array
|
|
129
|
+
{
|
|
130
|
+
$stops = $this->payloadServiceStops($payload);
|
|
131
|
+
if ($stops->isEmpty()) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
$currentStopId = $payload?->current_waypoint_uuid;
|
|
136
|
+
if ($currentStopId) {
|
|
137
|
+
$current = $stops->first(function (array $stop) use ($currentStopId) {
|
|
138
|
+
$place = $stop['place'] ?? null;
|
|
139
|
+
$waypoint = $stop['waypoint'] ?? null;
|
|
140
|
+
|
|
141
|
+
return $place instanceof Place && in_array($currentStopId, [$place->uuid, $place->public_id, $place->id], true)
|
|
142
|
+
|| $waypoint instanceof Waypoint && in_array($currentStopId, [$waypoint->uuid, $waypoint->public_id, $waypoint->place_uuid], true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if ($current) {
|
|
146
|
+
return $current;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return $stops->first();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
protected function ensurePayloadCurrentServiceStop(?Payload $payload): ?array
|
|
154
|
+
{
|
|
155
|
+
$stop = $this->payloadCurrentServiceStop($payload);
|
|
156
|
+
if (!$payload || !$stop) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
$place = $stop['place'] ?? null;
|
|
161
|
+
if ($place instanceof Place && $payload->current_waypoint_uuid !== $place->uuid) {
|
|
162
|
+
$this->setPayloadCurrentServiceStop($payload, $stop);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return $stop;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
protected function setPayloadCurrentServiceStop(Payload $payload, array $stop): ?Place
|
|
169
|
+
{
|
|
170
|
+
$place = $stop['place'] ?? null;
|
|
171
|
+
if (!$place instanceof Place) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
$payload->current_waypoint_uuid = $place->uuid;
|
|
176
|
+
$payload->saveQuietly();
|
|
177
|
+
$payload->setRelation('currentWaypoint', $place);
|
|
178
|
+
|
|
179
|
+
if (($stop['waypoint'] ?? null) instanceof Waypoint) {
|
|
180
|
+
$payload->setRelation('currentWaypointMarker', $stop['waypoint']);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return $place;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
protected function resolveServiceStopFromKey(?Payload $payload, ?string $key = null): ?array
|
|
187
|
+
{
|
|
188
|
+
if (!$payload || !$key) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return $this->payloadServiceStops($payload)->first(function (array $stop) use ($key) {
|
|
193
|
+
$place = $stop['place'] ?? null;
|
|
194
|
+
$waypoint = $stop['waypoint'] ?? null;
|
|
195
|
+
|
|
196
|
+
return $place instanceof Place && in_array($key, [$place->uuid, $place->public_id, $place->id], true)
|
|
197
|
+
|| $waypoint instanceof Waypoint && in_array($key, [$waypoint->uuid, $waypoint->public_id, $waypoint->place_uuid], true);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
protected function nextIncompleteServiceStop(Order $order, Payload $payload): ?array
|
|
202
|
+
{
|
|
203
|
+
$current = $this->payloadCurrentServiceStop($payload);
|
|
204
|
+
$currentSequence = (int) data_get($current, 'sequence', 0);
|
|
205
|
+
|
|
206
|
+
return $this->payloadServiceStops($payload)
|
|
207
|
+
->filter(fn (array $stop) => (int) data_get($stop, 'sequence', 0) > $currentSequence)
|
|
208
|
+
->first(fn (array $stop) => !$this->serviceStopIsComplete($order, $payload, $stop));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
protected function serviceStopIsComplete(Order $order, Payload $payload, array $stop): bool
|
|
212
|
+
{
|
|
213
|
+
if (in_array($order->status, ['completed', 'canceled'], true)) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
$type = $stop['type'] ?? null;
|
|
218
|
+
if ($type === 'waypoint') {
|
|
219
|
+
$waypoint = $stop['waypoint'] ?? null;
|
|
220
|
+
|
|
221
|
+
return $waypoint instanceof Waypoint && $this->waypointMarkerIsComplete($waypoint);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
$trackingNumberUuid = $this->serviceStopTrackingNumberUuid($payload, $stop);
|
|
225
|
+
if ($trackingNumberUuid) {
|
|
226
|
+
return (bool) data_get($this->trackingNumberStatus($trackingNumberUuid), 'complete', false);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
protected function updateCurrentServiceStopActivity(Order $order, Activity $activity, $location = null, $proof = null, bool $skipEndpointOrderActivity = false, bool $updateEndpointOrderStatus = true): ?array
|
|
233
|
+
{
|
|
234
|
+
$payload = $order->payload;
|
|
235
|
+
$stop = $this->ensurePayloadCurrentServiceStop($payload);
|
|
236
|
+
if (!$payload || !$stop || !Utils::isActivity($activity) || !$location) {
|
|
237
|
+
return $stop;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
$waypoint = $stop['waypoint'] ?? null;
|
|
241
|
+
if ($waypoint instanceof Waypoint) {
|
|
242
|
+
$payload->loadMissing(['entities']);
|
|
243
|
+
$activityId = $waypoint->insertActivity($activity, $location, $proof);
|
|
244
|
+
if ($activityId && $waypoint->tracking_number_uuid) {
|
|
245
|
+
DB::table((new TrackingNumber())->getTable())->where('uuid', $waypoint->tracking_number_uuid)->update(['status_uuid' => $activityId]);
|
|
246
|
+
if ($waypoint->relationLoaded('trackingNumber')) {
|
|
247
|
+
$waypoint->trackingNumber->status_uuid = $activityId;
|
|
248
|
+
$waypoint->trackingNumber->unsetRelation('status');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
$activity->fireEvents($order, $waypoint);
|
|
252
|
+
|
|
253
|
+
$entities = $payload->entities->where('destination_uuid', $waypoint->place_uuid);
|
|
254
|
+
foreach ($entities as $entity) {
|
|
255
|
+
$entity->insertActivity($activity, $location, $proof);
|
|
256
|
+
if ($activity->complete()) {
|
|
257
|
+
event(new EntityCompleted($entity, $activity));
|
|
258
|
+
} else {
|
|
259
|
+
event(new EntityActivityChanged($entity, $activity));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if ($activity->complete()) {
|
|
264
|
+
event(new WaypointCompleted($waypoint, $activity));
|
|
265
|
+
} else {
|
|
266
|
+
event(new WaypointActivityChanged($waypoint, $activity));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return $stop;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!$skipEndpointOrderActivity) {
|
|
273
|
+
$this->insertEndpointServiceStopActivity($order, $payload, $stop, $activity, $location, $proof);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return $stop;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
protected function advanceCurrentServiceStopDestination(Order $order, Payload $payload): ?array
|
|
280
|
+
{
|
|
281
|
+
$nextStop = $this->nextIncompleteServiceStop($order, $payload);
|
|
282
|
+
if (!$nextStop) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
$this->setPayloadCurrentServiceStop($payload, $nextStop);
|
|
287
|
+
|
|
288
|
+
return $nextStop;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
protected function payloadHasCurrentServiceStopActivity(?Payload $payload, Activity $activity): bool
|
|
292
|
+
{
|
|
293
|
+
if (!$payload || !Utils::isActivity($activity)) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
$stop = $this->payloadCurrentServiceStop($payload);
|
|
298
|
+
if (!$stop) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
$waypoint = $stop['waypoint'] ?? null;
|
|
303
|
+
if ($waypoint instanceof Waypoint) {
|
|
304
|
+
if (!$waypoint->tracking_number_uuid) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return TrackingStatus::where('tracking_number_uuid', $waypoint->tracking_number_uuid)
|
|
309
|
+
->where('code', TrackingStatus::prepareCode($activity->code))
|
|
310
|
+
->exists();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
$trackingNumberUuid = $this->serviceStopTrackingNumberUuid($payload, $stop);
|
|
314
|
+
if (!$trackingNumberUuid) {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return TrackingStatus::where('tracking_number_uuid', $trackingNumberUuid)
|
|
319
|
+
->where('code', TrackingStatus::prepareCode($activity->code))
|
|
320
|
+
->exists();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
protected function serviceStopTrackingNumberUuid(Payload $payload, array $stop): ?string
|
|
324
|
+
{
|
|
325
|
+
$waypoint = $stop['waypoint'] ?? null;
|
|
326
|
+
if ($waypoint instanceof Waypoint) {
|
|
327
|
+
return $waypoint->tracking_number_uuid;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
$column = $this->endpointTrackingNumberColumn($stop['type'] ?? null);
|
|
331
|
+
if (!$column) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return $payload->{$column} ?: null;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
protected function endpointTrackingNumberColumn(?string $type): ?string
|
|
339
|
+
{
|
|
340
|
+
return match ($type) {
|
|
341
|
+
'pickup' => 'pickup_tracking_number_uuid',
|
|
342
|
+
'dropoff' => 'dropoff_tracking_number_uuid',
|
|
343
|
+
'return' => 'return_tracking_number_uuid',
|
|
344
|
+
default => null,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
protected function endpointServiceStopTrackingNumber(Order $order, Payload $payload, array $stop, bool $create = false): ?TrackingNumber
|
|
349
|
+
{
|
|
350
|
+
$column = $this->endpointTrackingNumberColumn($stop['type'] ?? null);
|
|
351
|
+
$place = $stop['place'] ?? null;
|
|
352
|
+
if (!$column || !$place instanceof Place) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
$trackingNumberUuid = $payload->{$column};
|
|
357
|
+
if ($trackingNumberUuid) {
|
|
358
|
+
$trackingNumber = TrackingNumber::where('uuid', $trackingNumberUuid)->first();
|
|
359
|
+
if ($trackingNumber) {
|
|
360
|
+
return $trackingNumber;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (!$create) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
$trackingNumberUuid = TrackingNumber::insertGetUuid([
|
|
369
|
+
'company_uuid' => $payload->company_uuid ?? $order->company_uuid,
|
|
370
|
+
'region' => strtoupper($place->country ?: $place->province ?: 'SG'),
|
|
371
|
+
'location' => Utils::parsePointToWkt($this->serviceStopLocationPoint($place)),
|
|
372
|
+
], $place);
|
|
373
|
+
|
|
374
|
+
if (!$trackingNumberUuid) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
DB::table($payload->getTable())->where('uuid', $payload->uuid)->update([$column => $trackingNumberUuid]);
|
|
379
|
+
$payload->{$column} = $trackingNumberUuid;
|
|
380
|
+
|
|
381
|
+
return TrackingNumber::where('uuid', $trackingNumberUuid)->first();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
protected function insertEndpointServiceStopActivity(Order $order, Payload $payload, array $stop, Activity $activity, $location = null, $proof = null): ?string
|
|
385
|
+
{
|
|
386
|
+
$trackingNumber = $this->endpointServiceStopTrackingNumber($order, $payload, $stop, true);
|
|
387
|
+
if (!$trackingNumber) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
$proof = $this->resolveProof($proof);
|
|
392
|
+
$activityId = TrackingStatus::insertGetUuid([
|
|
393
|
+
'company_uuid' => $payload->company_uuid ?? $order->company_uuid,
|
|
394
|
+
'tracking_number_uuid' => $trackingNumber->uuid,
|
|
395
|
+
'proof_uuid' => data_get($proof, 'uuid'),
|
|
396
|
+
'status' => TemplateString::resolve($activity->get('status', ''), $order),
|
|
397
|
+
'details' => TemplateString::resolve($activity->get('details', ''), $order),
|
|
398
|
+
'location' => Utils::parsePointToWkt($this->activityLocationPoint($location)),
|
|
399
|
+
'code' => TrackingStatus::prepareCode($activity->get('code')),
|
|
400
|
+
'complete' => $activity->complete(),
|
|
401
|
+
]);
|
|
402
|
+
|
|
403
|
+
if ($activityId) {
|
|
404
|
+
DB::table($trackingNumber->getTable())->where('uuid', $trackingNumber->uuid)->update(['status_uuid' => $activityId]);
|
|
405
|
+
$trackingNumber->status_uuid = $activityId;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if ($trackingNumber->relationLoaded('status')) {
|
|
409
|
+
$trackingNumber->unsetRelation('status');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return $activityId ?: null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
protected function serviceStopActivityContext(Order $order, Payload $payload, array $stop): Order|Waypoint
|
|
416
|
+
{
|
|
417
|
+
$waypoint = $stop['waypoint'] ?? null;
|
|
418
|
+
if ($waypoint instanceof Waypoint) {
|
|
419
|
+
return $waypoint;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
$trackingNumber = $this->endpointServiceStopTrackingNumber($order, $payload, $stop);
|
|
423
|
+
$context = new Waypoint([
|
|
424
|
+
'company_uuid' => $payload->company_uuid ?? $order->company_uuid,
|
|
425
|
+
'payload_uuid' => $payload->uuid,
|
|
426
|
+
'place_uuid' => data_get($stop, 'place.uuid'),
|
|
427
|
+
'tracking_number_uuid' => data_get($trackingNumber, 'uuid'),
|
|
428
|
+
'type' => $stop['type'] ?? null,
|
|
429
|
+
'order' => $stop['sequence'] ?? null,
|
|
430
|
+
]);
|
|
431
|
+
|
|
432
|
+
if ($trackingNumber) {
|
|
433
|
+
$trackingNumber->loadMissing('status');
|
|
434
|
+
$context->setRelation('trackingNumber', $trackingNumber);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return $context;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
protected function nextActivitiesForServiceStop(Order $order, Payload $payload, array $stop): Collection
|
|
441
|
+
{
|
|
442
|
+
$orderConfig = $order->ensureOrderConfig();
|
|
443
|
+
if (!$orderConfig) {
|
|
444
|
+
return collect();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
$waypoint = $stop['waypoint'] ?? null;
|
|
448
|
+
if ($waypoint instanceof Waypoint) {
|
|
449
|
+
return $orderConfig->nextActivity($waypoint);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
$trackingNumberUuid = $this->serviceStopTrackingNumberUuid($payload, $stop);
|
|
453
|
+
$currentStatus = $trackingNumberUuid ? $this->trackingNumberStatus($trackingNumberUuid) : null;
|
|
454
|
+
$currentCode = $currentStatus
|
|
455
|
+
? $currentStatus->code
|
|
456
|
+
: 'created';
|
|
457
|
+
|
|
458
|
+
$currentActivity = $orderConfig->activities()->firstWhere('code', strtolower((string) $currentCode));
|
|
459
|
+
if (!$currentActivity) {
|
|
460
|
+
return collect();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return $currentActivity->getNext($this->serviceStopActivityContext($order, $payload, $stop));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
protected function trackingNumberStatus(string $trackingNumberUuid): ?TrackingStatus
|
|
467
|
+
{
|
|
468
|
+
$trackingNumber = TrackingNumber::where('uuid', $trackingNumberUuid)->first();
|
|
469
|
+
if (!$trackingNumber) {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if ($trackingNumber->status_uuid) {
|
|
474
|
+
return TrackingStatus::where('uuid', $trackingNumber->status_uuid)->first();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return TrackingStatus::where('tracking_number_uuid', $trackingNumberUuid)->latest()->first();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
protected function serviceStopLocationPoint(Place $place): Point
|
|
481
|
+
{
|
|
482
|
+
try {
|
|
483
|
+
$point = Utils::getPointFromMixed($place);
|
|
484
|
+
|
|
485
|
+
return $point instanceof Point ? $point : new Point(0, 0);
|
|
486
|
+
} catch (\Throwable) {
|
|
487
|
+
return new Point(0, 0);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
protected function activityLocationPoint($location): Point
|
|
492
|
+
{
|
|
493
|
+
if ($location instanceof Point) {
|
|
494
|
+
return $location;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (is_array($location)) {
|
|
498
|
+
return new Point(...$location);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return new Point(0, 0);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
protected function waypointMarkerIsComplete(Waypoint $waypoint): bool
|
|
505
|
+
{
|
|
506
|
+
if (!$waypoint->tracking_number_uuid) {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return (bool) data_get($this->trackingNumberStatus($waypoint->tracking_number_uuid), 'complete', false);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
@@ -5,12 +5,15 @@ namespace Fleetbase\FleetOps\Tracking;
|
|
|
5
5
|
use Fleetbase\FleetOps\Models\Order;
|
|
6
6
|
use Fleetbase\FleetOps\Models\Place;
|
|
7
7
|
use Fleetbase\FleetOps\Models\Waypoint;
|
|
8
|
+
use Fleetbase\FleetOps\Support\ResolvesOrderServiceStops;
|
|
8
9
|
use Fleetbase\FleetOps\Support\Utils;
|
|
9
10
|
use Illuminate\Support\Collection;
|
|
10
11
|
use Illuminate\Support\Str;
|
|
11
12
|
|
|
12
13
|
class TrackingContextBuilder
|
|
13
14
|
{
|
|
15
|
+
use ResolvesOrderServiceStops;
|
|
16
|
+
|
|
14
17
|
public function build(Order $order, TrackingOptions $options): TrackingContext
|
|
15
18
|
{
|
|
16
19
|
$order->loadAssignedDriver();
|
|
@@ -18,8 +21,10 @@ class TrackingContextBuilder
|
|
|
18
21
|
'payload',
|
|
19
22
|
'payload.pickup',
|
|
20
23
|
'payload.dropoff',
|
|
24
|
+
'payload.return',
|
|
21
25
|
'payload.waypoints',
|
|
22
26
|
'payload.waypointMarkers.place',
|
|
27
|
+
'payload.waypointMarkers.trackingNumber.status',
|
|
23
28
|
]);
|
|
24
29
|
|
|
25
30
|
$payload = $order->payload;
|
|
@@ -91,54 +96,52 @@ class TrackingContextBuilder
|
|
|
91
96
|
$stops = collect();
|
|
92
97
|
$sequence = 0;
|
|
93
98
|
|
|
94
|
-
|
|
95
|
-
$
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
place: $payload->pickup,
|
|
101
|
-
completed: !in_array($order->status, ['created', 'dispatched', 'pending']),
|
|
102
|
-
sequence: ++$sequence
|
|
103
|
-
));
|
|
104
|
-
}
|
|
99
|
+
foreach ($this->payloadServiceStops($payload) as $serviceStop) {
|
|
100
|
+
$place = $serviceStop['place'] ?? null;
|
|
101
|
+
$waypoint = $serviceStop['waypoint'] ?? null;
|
|
102
|
+
$status = $waypoint instanceof Waypoint
|
|
103
|
+
? $this->waypointServiceStopStatus($waypoint)
|
|
104
|
+
: $this->endpointServiceStopStatus($payload, $serviceStop);
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
foreach ($markers->sortBy('order')->values() as $waypoint) {
|
|
109
|
-
$stops->push($this->stopFromWaypoint($waypoint, ++$sequence));
|
|
106
|
+
if (!$place instanceof Place) {
|
|
107
|
+
continue;
|
|
110
108
|
}
|
|
111
|
-
} elseif ($payload->waypoints instanceof Collection) {
|
|
112
|
-
foreach ($payload->waypoints as $place) {
|
|
113
|
-
if ($place instanceof Place) {
|
|
114
|
-
$stops->push(new TrackingStop(
|
|
115
|
-
uuid: $place->uuid,
|
|
116
|
-
publicId: $place->public_id,
|
|
117
|
-
type: 'waypoint',
|
|
118
|
-
status: null,
|
|
119
|
-
place: $place,
|
|
120
|
-
completed: false,
|
|
121
|
-
sequence: ++$sequence
|
|
122
|
-
));
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
109
|
|
|
127
|
-
if ($payload->dropoff instanceof Place) {
|
|
128
110
|
$stops->push(new TrackingStop(
|
|
129
|
-
uuid: $
|
|
130
|
-
publicId: $
|
|
131
|
-
type: '
|
|
132
|
-
status: null,
|
|
133
|
-
place: $
|
|
134
|
-
|
|
135
|
-
|
|
111
|
+
uuid: $place->uuid,
|
|
112
|
+
publicId: $place->public_id,
|
|
113
|
+
type: $serviceStop['type'],
|
|
114
|
+
status: $status ?: null,
|
|
115
|
+
place: $place,
|
|
116
|
+
waypoint: $waypoint,
|
|
117
|
+
completed: $this->serviceStopIsComplete($order, $payload, $serviceStop),
|
|
118
|
+
sequence: ++$sequence,
|
|
119
|
+
trackingNumberUuid: $serviceStop['tracking_number_uuid'] ?? null
|
|
136
120
|
));
|
|
137
121
|
}
|
|
138
122
|
|
|
139
123
|
return $stops->values();
|
|
140
124
|
}
|
|
141
125
|
|
|
126
|
+
protected function endpointServiceStopStatus($payload, array $serviceStop): ?string
|
|
127
|
+
{
|
|
128
|
+
$trackingNumberUuid = $this->serviceStopTrackingNumberUuid($payload, $serviceStop);
|
|
129
|
+
if (!$trackingNumberUuid) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return strtolower((string) data_get($this->trackingNumberStatus($trackingNumberUuid), 'code')) ?: null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
protected function waypointServiceStopStatus(Waypoint $waypoint): ?string
|
|
137
|
+
{
|
|
138
|
+
if (!$waypoint->tracking_number_uuid) {
|
|
139
|
+
return strtolower((string) $waypoint->status_code) ?: null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return strtolower((string) data_get($this->trackingNumberStatus($waypoint->tracking_number_uuid), 'code')) ?: null;
|
|
143
|
+
}
|
|
144
|
+
|
|
142
145
|
protected function stopFromWaypoint(Waypoint $waypoint, int $sequence): TrackingStop
|
|
143
146
|
{
|
|
144
147
|
$waypoint->loadMissing('place');
|