@fleetbase/fleetops-engine 0.6.46 → 0.6.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/addon/components/admin/routing-settings.hbs +71 -0
- package/addon/components/admin/routing-settings.js +124 -0
- package/addon/components/customer/orders.js +1 -1
- package/addon/components/display-place.hbs +1 -1
- package/addon/components/map/order-list-overlay/order.hbs +18 -3
- package/addon/components/map/toolbar.hbs +2 -2
- package/addon/components/map/toolbar.js +10 -2
- package/addon/components/orchestrator/phase-builder.hbs +28 -5
- package/addon/components/orchestrator/phase-builder.js +21 -1
- package/addon/components/orchestrator/resource-panel.hbs +19 -1
- package/addon/components/orchestrator/resource-panel.js +4 -0
- package/addon/components/orchestrator-workbench.js +3 -1
- package/addon/components/order/details/purchase-rate.hbs +19 -22
- package/addon/components/order/details/route.hbs +26 -1
- package/addon/components/order/details/route.js +50 -0
- package/addon/components/order/details/tracking.hbs +171 -27
- package/addon/components/order/details/tracking.js +457 -0
- package/addon/components/order/form/details.hbs +1 -2
- package/addon/components/order/form/details.js +6 -0
- package/addon/components/order/form/route.js +9 -1
- package/addon/components/order/kanban-card.hbs +18 -3
- package/addon/components/order-list-overlay/order.hbs +18 -3
- package/addon/components/order-progress-card.hbs +21 -6
- package/addon/components/order-tracking-lookup.hbs +7 -7
- package/addon/components/order-tracking-lookup.js +1 -1
- package/addon/components/route-list.hbs +78 -8
- package/addon/components/route-list.js +48 -6
- package/addon/components/tracking-stop-progress.hbs +45 -0
- package/addon/components/tracking-stop-progress.js +79 -0
- package/addon/controllers/settings/payments/index.js +1 -1
- package/addon/controllers/settings/routing.js +98 -1
- package/addon/helpers/format-duration.js +38 -0
- package/addon/routes/application.js +6 -0
- package/addon/services/order-list-overlay.js +2 -2
- package/addon/styles/fleetops-engine.css +733 -0
- package/addon/templates/settings/routing.hbs +60 -2
- package/app/components/tracking-stop-progress.js +1 -0
- package/app/helpers/format-duration.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/pnpm-workspace.yaml +7 -0
- package/server/config/fleetops.php +32 -11
- package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +86 -0
- package/server/seeders/VroomOrchestrationSeeder.php +402 -0
- package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +64 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +26 -26
- package/server/src/Http/Controllers/Api/v1/PayloadController.php +10 -10
- package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +41 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +6 -42
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +69 -15
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +119 -12
- package/server/src/Http/Resources/v1/Index/Payload.php +2 -2
- package/server/src/Models/Contact.php +122 -13
- package/server/src/Models/Order.php +1 -1
- package/server/src/Models/Payload.php +8 -8
- package/server/src/Models/Place.php +0 -3
- package/server/src/Models/ServiceRate.php +1 -1
- package/server/src/Observers/ContactObserver.php +8 -0
- package/server/src/Orchestration/Engines/CapacityAllocationEngine.php +220 -0
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +330 -38
- package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +259 -51
- package/server/src/Providers/FleetOpsServiceProvider.php +27 -0
- package/server/src/Support/LiveOrderQuery.php +75 -0
- package/server/src/Support/Metrics.php +15 -0
- package/server/src/Support/OSRM.php +11 -6
- package/server/src/Support/OrderTracker.php +7 -582
- package/server/src/Support/Utils.php +1 -1
- package/server/src/Tracking/Contracts/TrackingProviderInterface.php +19 -0
- package/server/src/Tracking/Providers/CalculatedTrackingProvider.php +65 -0
- package/server/src/Tracking/Providers/GoogleRoutesTrackingProvider.php +118 -0
- package/server/src/Tracking/Providers/OsrmTrackingProvider.php +71 -0
- package/server/src/Tracking/README.md +25 -0
- package/server/src/Tracking/Support/FakeTrackingProvider.php +42 -0
- package/server/src/Tracking/TrackingContext.php +63 -0
- package/server/src/Tracking/TrackingContextBuilder.php +204 -0
- package/server/src/Tracking/TrackingIntelligenceService.php +180 -0
- package/server/src/Tracking/TrackingOptions.php +49 -0
- package/server/src/Tracking/TrackingProviderCapabilities.php +30 -0
- package/server/src/Tracking/TrackingProviderManager.php +89 -0
- package/server/src/Tracking/TrackingProviderRegistry.php +35 -0
- package/server/src/Tracking/TrackingProviderResult.php +20 -0
- package/server/src/Tracking/TrackingStop.php +59 -0
- package/server/src/routes.php +11 -0
- package/server/tests/CapacityAllocationEngineTest.php +108 -0
- package/server/tests/LiveOrderQueryTest.php +34 -0
- package/server/tests/OrchestrationPayloadBuilderTest.php +132 -0
- package/server/tests/OrchestratorConsumableApiTest.php +74 -0
- package/server/tests/PingDriverEndpointTest.php +19 -0
- package/server/tests/TrackingIntelligenceTest.php +176 -0
- package/server/tests/VroomOrchestrationEngineTest.php +159 -0
- package/translations/en-us.yaml +4 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Seeders;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Entity;
|
|
6
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
7
|
+
use Fleetbase\FleetOps\Models\Payload;
|
|
8
|
+
use Fleetbase\FleetOps\Models\Place;
|
|
9
|
+
use Fleetbase\FleetOps\Models\Vehicle;
|
|
10
|
+
use Fleetbase\FleetOps\Models\Waypoint;
|
|
11
|
+
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
12
|
+
use Fleetbase\Models\Company;
|
|
13
|
+
use Fleetbase\Seeders\Concerns\ResolvesSeedCompany;
|
|
14
|
+
use Illuminate\Database\Seeder;
|
|
15
|
+
use Illuminate\Support\Facades\DB;
|
|
16
|
+
use Illuminate\Support\Str;
|
|
17
|
+
|
|
18
|
+
class VroomOrchestrationSeeder extends Seeder
|
|
19
|
+
{
|
|
20
|
+
use ResolvesSeedCompany;
|
|
21
|
+
|
|
22
|
+
protected const SEED_NAME = 'vroom-orchestration';
|
|
23
|
+
|
|
24
|
+
public function run(): void
|
|
25
|
+
{
|
|
26
|
+
$company = $this->resolveCompany();
|
|
27
|
+
if (!$company) {
|
|
28
|
+
$this->command?->error('No company found. Create a Fleetbase company before running the VROOM orchestration seeder.');
|
|
29
|
+
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
session(['company' => $company->uuid]);
|
|
34
|
+
|
|
35
|
+
DB::transaction(function () use ($company) {
|
|
36
|
+
$this->purgePreviousSeedData();
|
|
37
|
+
|
|
38
|
+
$places = $this->seedPlaces($company);
|
|
39
|
+
$vehicles = $this->seedVehicles($company);
|
|
40
|
+
$orders = $this->seedOrders($company, $places);
|
|
41
|
+
|
|
42
|
+
$this->command?->info('Seeded VROOM orchestration test data for company: ' . $company->public_id);
|
|
43
|
+
$this->command?->line('Order IDs: ' . implode(', ', array_map(fn (Order $order) => $order->public_id, $orders)));
|
|
44
|
+
$this->command?->line('Vehicle IDs: ' . implode(', ', array_map(fn (Vehicle $vehicle) => $vehicle->public_id, $vehicles)));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected function resolveCompany(): ?Company
|
|
49
|
+
{
|
|
50
|
+
return $this->resolveSeedCompany(
|
|
51
|
+
'FLEETOPS_VROOM_SEED_COMPANY_UUID',
|
|
52
|
+
'FLEETOPS_VROOM_SEED_COMPANY_PUBLIC_ID'
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected function purgePreviousSeedData(): void
|
|
57
|
+
{
|
|
58
|
+
$payloadUuids = $this->seeded(Payload::query())->pluck('uuid');
|
|
59
|
+
|
|
60
|
+
$this->seeded(Order::query())->forceDelete();
|
|
61
|
+
Entity::whereIn('payload_uuid', $payloadUuids)->forceDelete();
|
|
62
|
+
Waypoint::whereIn('payload_uuid', $payloadUuids)->forceDelete();
|
|
63
|
+
$this->seeded(Payload::query())->forceDelete();
|
|
64
|
+
$this->seeded(Vehicle::query())->forceDelete();
|
|
65
|
+
$this->seeded(Place::query())->forceDelete();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected function seeded($query)
|
|
69
|
+
{
|
|
70
|
+
return $query->where('meta->seed', static::SEED_NAME);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected function meta(string $seedId, array $extra = []): array
|
|
74
|
+
{
|
|
75
|
+
return array_merge([
|
|
76
|
+
'seed' => static::SEED_NAME,
|
|
77
|
+
'seed_id' => $seedId,
|
|
78
|
+
], $extra);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
protected function seedPlaces(Company $company): array
|
|
82
|
+
{
|
|
83
|
+
$places = [
|
|
84
|
+
'depot' => [
|
|
85
|
+
'seed_id' => 'depot_tanjong_pagar',
|
|
86
|
+
'name' => 'VROOM Test Depot - Tanjong Pagar',
|
|
87
|
+
'street1' => '1 Raffles Quay',
|
|
88
|
+
'city' => 'Singapore',
|
|
89
|
+
'country' => 'SG',
|
|
90
|
+
'lat' => 1.2816,
|
|
91
|
+
'lng' => 103.8510,
|
|
92
|
+
],
|
|
93
|
+
'orchard' => [
|
|
94
|
+
'seed_id' => 'dropoff_orchard',
|
|
95
|
+
'name' => 'VROOM Test Dropoff - Orchard',
|
|
96
|
+
'street1' => '2 Orchard Turn',
|
|
97
|
+
'city' => 'Singapore',
|
|
98
|
+
'country' => 'SG',
|
|
99
|
+
'lat' => 1.3048,
|
|
100
|
+
'lng' => 103.8318,
|
|
101
|
+
],
|
|
102
|
+
'changi' => [
|
|
103
|
+
'seed_id' => 'dropoff_changi',
|
|
104
|
+
'name' => 'VROOM Test Dropoff - Changi',
|
|
105
|
+
'street1' => '80 Airport Boulevard',
|
|
106
|
+
'city' => 'Singapore',
|
|
107
|
+
'country' => 'SG',
|
|
108
|
+
'lat' => 1.3644,
|
|
109
|
+
'lng' => 103.9915,
|
|
110
|
+
],
|
|
111
|
+
'woodlands' => [
|
|
112
|
+
'seed_id' => 'dropoff_woodlands',
|
|
113
|
+
'name' => 'VROOM Test Dropoff - Woodlands',
|
|
114
|
+
'street1' => '1 Woodlands Square',
|
|
115
|
+
'city' => 'Singapore',
|
|
116
|
+
'country' => 'SG',
|
|
117
|
+
'lat' => 1.4360,
|
|
118
|
+
'lng' => 103.7860,
|
|
119
|
+
],
|
|
120
|
+
'rochor' => [
|
|
121
|
+
'seed_id' => 'waypoint_rochor',
|
|
122
|
+
'name' => 'VROOM Test Waypoint - Rochor',
|
|
123
|
+
'street1' => '1 Rochor Canal Road',
|
|
124
|
+
'city' => 'Singapore',
|
|
125
|
+
'country' => 'SG',
|
|
126
|
+
'lat' => 1.3039,
|
|
127
|
+
'lng' => 103.8520,
|
|
128
|
+
],
|
|
129
|
+
'novena' => [
|
|
130
|
+
'seed_id' => 'waypoint_novena',
|
|
131
|
+
'name' => 'VROOM Test Waypoint - Novena',
|
|
132
|
+
'street1' => '10 Sinaran Drive',
|
|
133
|
+
'city' => 'Singapore',
|
|
134
|
+
'country' => 'SG',
|
|
135
|
+
'lat' => 1.3206,
|
|
136
|
+
'lng' => 103.8439,
|
|
137
|
+
],
|
|
138
|
+
'serangoon' => [
|
|
139
|
+
'seed_id' => 'waypoint_serangoon',
|
|
140
|
+
'name' => 'VROOM Test Waypoint - Serangoon',
|
|
141
|
+
'street1' => '23 Serangoon Central',
|
|
142
|
+
'city' => 'Singapore',
|
|
143
|
+
'country' => 'SG',
|
|
144
|
+
'lat' => 1.3508,
|
|
145
|
+
'lng' => 103.8723,
|
|
146
|
+
],
|
|
147
|
+
'paya_lebar' => [
|
|
148
|
+
'seed_id' => 'waypoint_paya_lebar',
|
|
149
|
+
'name' => 'VROOM Test Waypoint - Paya Lebar',
|
|
150
|
+
'street1' => '10 Paya Lebar Road',
|
|
151
|
+
'city' => 'Singapore',
|
|
152
|
+
'country' => 'SG',
|
|
153
|
+
'lat' => 1.3174,
|
|
154
|
+
'lng' => 103.8927,
|
|
155
|
+
],
|
|
156
|
+
'tampines' => [
|
|
157
|
+
'seed_id' => 'waypoint_tampines',
|
|
158
|
+
'name' => 'VROOM Test Waypoint - Tampines',
|
|
159
|
+
'street1' => '4 Tampines Central 5',
|
|
160
|
+
'city' => 'Singapore',
|
|
161
|
+
'country' => 'SG',
|
|
162
|
+
'lat' => 1.3525,
|
|
163
|
+
'lng' => 103.9447,
|
|
164
|
+
],
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
return collect($places)->mapWithKeys(function (array $place, string $key) use ($company) {
|
|
168
|
+
$model = new Place();
|
|
169
|
+
$model->forceFill([
|
|
170
|
+
'uuid' => (string) Str::uuid(),
|
|
171
|
+
'company_uuid' => $company->uuid,
|
|
172
|
+
'name' => $place['name'],
|
|
173
|
+
'street1' => $place['street1'],
|
|
174
|
+
'city' => $place['city'],
|
|
175
|
+
'country' => $place['country'],
|
|
176
|
+
'location' => new Point($place['lat'], $place['lng']),
|
|
177
|
+
'meta' => $this->meta($place['seed_id']),
|
|
178
|
+
])->save();
|
|
179
|
+
|
|
180
|
+
return [$key => $model];
|
|
181
|
+
})->all();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
protected function seedVehicles(Company $company): array
|
|
185
|
+
{
|
|
186
|
+
$vehicles = [
|
|
187
|
+
[
|
|
188
|
+
'seed_id' => 'central_1',
|
|
189
|
+
'name' => 'VROOM Test Van - Central',
|
|
190
|
+
'make' => 'Nissan',
|
|
191
|
+
'model' => 'NV200',
|
|
192
|
+
'lat' => 1.2798,
|
|
193
|
+
'lng' => 103.8500,
|
|
194
|
+
'capacity' => [120, 4.5, 1, 20],
|
|
195
|
+
],
|
|
196
|
+
[
|
|
197
|
+
'seed_id' => 'east_1',
|
|
198
|
+
'name' => 'VROOM Test Van - East',
|
|
199
|
+
'make' => 'Toyota',
|
|
200
|
+
'model' => 'HiAce',
|
|
201
|
+
'lat' => 1.3349,
|
|
202
|
+
'lng' => 103.9617,
|
|
203
|
+
'capacity' => [250, 8, 2, 40],
|
|
204
|
+
],
|
|
205
|
+
[
|
|
206
|
+
'seed_id' => 'north_1',
|
|
207
|
+
'name' => 'VROOM Test Van - North',
|
|
208
|
+
'make' => 'Mercedes-Benz',
|
|
209
|
+
'model' => 'Sprinter',
|
|
210
|
+
'lat' => 1.4326,
|
|
211
|
+
'lng' => 103.7856,
|
|
212
|
+
'capacity' => [500, 16, 4, 80],
|
|
213
|
+
],
|
|
214
|
+
[
|
|
215
|
+
'seed_id' => 'west_1',
|
|
216
|
+
'name' => 'VROOM Test Van - West',
|
|
217
|
+
'make' => 'Ford',
|
|
218
|
+
'model' => 'Transit',
|
|
219
|
+
'lat' => 1.3345,
|
|
220
|
+
'lng' => 103.7420,
|
|
221
|
+
'capacity' => [90, 3, 0, 12],
|
|
222
|
+
],
|
|
223
|
+
[
|
|
224
|
+
'seed_id' => 'capacity_only_heavy_1',
|
|
225
|
+
'name' => 'VROOM Test Capacity Truck - No Position',
|
|
226
|
+
'make' => 'Isuzu',
|
|
227
|
+
'model' => 'NPR',
|
|
228
|
+
'capacity' => [900, 24, 8, 160],
|
|
229
|
+
'skills' => ['tail_lift'],
|
|
230
|
+
'max_tasks' => 10,
|
|
231
|
+
],
|
|
232
|
+
[
|
|
233
|
+
'seed_id' => 'capacity_only_cold_1',
|
|
234
|
+
'name' => 'VROOM Test Cold Chain Van - No Position',
|
|
235
|
+
'make' => 'Hyundai',
|
|
236
|
+
'model' => 'H350',
|
|
237
|
+
'capacity' => [350, 10, 3, 60],
|
|
238
|
+
'skills' => ['cold_chain'],
|
|
239
|
+
'max_tasks' => 8,
|
|
240
|
+
],
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
return collect($vehicles)->mapWithKeys(function (array $vehicle) use ($company) {
|
|
244
|
+
$model = new Vehicle();
|
|
245
|
+
$model->forceFill([
|
|
246
|
+
'uuid' => (string) Str::uuid(),
|
|
247
|
+
'company_uuid' => $company->uuid,
|
|
248
|
+
'name' => $vehicle['name'],
|
|
249
|
+
'make' => $vehicle['make'],
|
|
250
|
+
'model' => $vehicle['model'],
|
|
251
|
+
'year' => '2024',
|
|
252
|
+
'type' => 'van',
|
|
253
|
+
'status' => 'active',
|
|
254
|
+
'online' => true,
|
|
255
|
+
'location' => isset($vehicle['lat'], $vehicle['lng']) ? new Point($vehicle['lat'], $vehicle['lng']) : null,
|
|
256
|
+
'skills' => $vehicle['skills'] ?? [],
|
|
257
|
+
'payload_capacity' => $vehicle['capacity'][0],
|
|
258
|
+
'payload_capacity_volume' => $vehicle['capacity'][1],
|
|
259
|
+
'payload_capacity_pallets' => $vehicle['capacity'][2],
|
|
260
|
+
'payload_capacity_parcels' => $vehicle['capacity'][3],
|
|
261
|
+
'max_tasks' => $vehicle['max_tasks'] ?? 6,
|
|
262
|
+
'return_to_depot' => false,
|
|
263
|
+
'meta' => $this->meta($vehicle['seed_id'], [
|
|
264
|
+
'location_state' => isset($vehicle['lat'], $vehicle['lng']) ? 'set' : 'not_set',
|
|
265
|
+
'capacity' => [
|
|
266
|
+
'weight_kg' => $vehicle['capacity'][0],
|
|
267
|
+
'volume_m3' => $vehicle['capacity'][1],
|
|
268
|
+
'pallets' => $vehicle['capacity'][2],
|
|
269
|
+
'parcels' => $vehicle['capacity'][3],
|
|
270
|
+
],
|
|
271
|
+
]),
|
|
272
|
+
])->save();
|
|
273
|
+
|
|
274
|
+
return [$vehicle['seed_id'] => $model];
|
|
275
|
+
})->all();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
protected function seedOrders(Company $company, array $places): array
|
|
279
|
+
{
|
|
280
|
+
$orders = [
|
|
281
|
+
'endpoint_only' => [
|
|
282
|
+
'payload_seed_id' => 'endpoint_only',
|
|
283
|
+
'name' => 'VROOM Test Order - Pickup Dropoff',
|
|
284
|
+
'pickup' => $places['depot'],
|
|
285
|
+
'dropoff' => $places['orchard'],
|
|
286
|
+
'waypoints' => [],
|
|
287
|
+
'entities' => [
|
|
288
|
+
['name' => 'Small carton', 'type' => 'parcel', 'weight' => 12, 'weight_unit' => 'kg', 'length' => 60, 'width' => 40, 'height' => 35, 'dimensions_unit' => 'cm'],
|
|
289
|
+
['name' => 'Fragile tote', 'type' => 'parcel', 'weight' => 8, 'weight_unit' => 'kg', 'length' => 50, 'width' => 35, 'height' => 30, 'dimensions_unit' => 'cm'],
|
|
290
|
+
],
|
|
291
|
+
],
|
|
292
|
+
'waypoint_only' => [
|
|
293
|
+
'payload_seed_id' => 'waypoint_only',
|
|
294
|
+
'name' => 'VROOM Test Order - Waypoints Only',
|
|
295
|
+
'pickup' => null,
|
|
296
|
+
'dropoff' => null,
|
|
297
|
+
'waypoints' => [$places['rochor'], $places['novena'], $places['serangoon']],
|
|
298
|
+
'entities' => [
|
|
299
|
+
['name' => 'Multi-stop parcel 1', 'type' => 'parcel', 'weight' => 25, 'weight_unit' => 'kg', 'length' => 80, 'width' => 50, 'height' => 40, 'dimensions_unit' => 'cm'],
|
|
300
|
+
['name' => 'Multi-stop parcel 2', 'type' => 'parcel', 'weight' => 18, 'weight_unit' => 'kg', 'length' => 70, 'width' => 45, 'height' => 35, 'dimensions_unit' => 'cm'],
|
|
301
|
+
['name' => 'Multi-stop parcel 3', 'type' => 'parcel', 'weight' => 15, 'weight_unit' => 'kg', 'length' => 65, 'width' => 40, 'height' => 35, 'dimensions_unit' => 'cm'],
|
|
302
|
+
],
|
|
303
|
+
],
|
|
304
|
+
'mixed' => [
|
|
305
|
+
'payload_seed_id' => 'mixed',
|
|
306
|
+
'name' => 'VROOM Test Order - Mixed Stops',
|
|
307
|
+
'pickup' => $places['depot'],
|
|
308
|
+
'dropoff' => $places['changi'],
|
|
309
|
+
'waypoints' => [$places['paya_lebar'], $places['tampines']],
|
|
310
|
+
'entities' => [
|
|
311
|
+
['name' => 'Heavy equipment crate', 'type' => 'freight', 'weight' => 180, 'weight_unit' => 'kg', 'length' => 120, 'width' => 80, 'height' => 90, 'dimensions_unit' => 'cm'],
|
|
312
|
+
['name' => 'Accessory box', 'type' => 'parcel', 'weight' => 22, 'weight_unit' => 'kg', 'length' => 75, 'width' => 55, 'height' => 45, 'dimensions_unit' => 'cm'],
|
|
313
|
+
],
|
|
314
|
+
],
|
|
315
|
+
'single_waypoint' => [
|
|
316
|
+
'payload_seed_id' => 'single_waypoint',
|
|
317
|
+
'name' => 'VROOM Test Order - Single Waypoint',
|
|
318
|
+
'pickup' => null,
|
|
319
|
+
'dropoff' => null,
|
|
320
|
+
'waypoints' => [$places['woodlands']],
|
|
321
|
+
'entities' => [
|
|
322
|
+
['name' => 'Oversized sample case', 'type' => 'parcel', 'weight' => 60, 'weight_unit' => 'kg', 'length' => 100, 'width' => 60, 'height' => 60, 'dimensions_unit' => 'cm'],
|
|
323
|
+
],
|
|
324
|
+
],
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
return collect($orders)->mapWithKeys(function (array $definition, string $seedId) use ($company) {
|
|
328
|
+
$payload = $this->createPayload($company, $definition);
|
|
329
|
+
$order = new Order();
|
|
330
|
+
$order->forceFill([
|
|
331
|
+
'uuid' => (string) Str::uuid(),
|
|
332
|
+
'internal_id' => 'VROOM-SEED-' . Str::of($seedId)->upper(),
|
|
333
|
+
'company_uuid' => $company->uuid,
|
|
334
|
+
'payload_uuid' => $payload->uuid,
|
|
335
|
+
'status' => 'created',
|
|
336
|
+
'type' => 'transport',
|
|
337
|
+
'notes' => $definition['name'],
|
|
338
|
+
'orchestrator_priority' => 50,
|
|
339
|
+
'required_skills' => [],
|
|
340
|
+
'meta' => $this->meta($seedId),
|
|
341
|
+
])->save();
|
|
342
|
+
|
|
343
|
+
$this->seedEntities($company, $payload, $definition['entities'] ?? []);
|
|
344
|
+
|
|
345
|
+
return [$seedId => $order];
|
|
346
|
+
})->all();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
protected function createPayload(Company $company, array $definition): Payload
|
|
350
|
+
{
|
|
351
|
+
$payload = new Payload();
|
|
352
|
+
$payload->forceFill([
|
|
353
|
+
'uuid' => (string) Str::uuid(),
|
|
354
|
+
'company_uuid' => $company->uuid,
|
|
355
|
+
'pickup_uuid' => $definition['pickup']?->uuid,
|
|
356
|
+
'dropoff_uuid' => $definition['dropoff']?->uuid,
|
|
357
|
+
'type' => 'transport',
|
|
358
|
+
'meta' => $this->meta($definition['payload_seed_id']),
|
|
359
|
+
])->save();
|
|
360
|
+
|
|
361
|
+
foreach ($definition['waypoints'] as $index => $place) {
|
|
362
|
+
$waypoint = new Waypoint();
|
|
363
|
+
$waypoint->forceFill([
|
|
364
|
+
'uuid' => (string) Str::uuid(),
|
|
365
|
+
'_import_id' => static::SEED_NAME . ':waypoint:' . $definition['payload_seed_id'] . ':' . ($index + 1),
|
|
366
|
+
'company_uuid' => $company->uuid,
|
|
367
|
+
'payload_uuid' => $payload->uuid,
|
|
368
|
+
'place_uuid' => $place->uuid,
|
|
369
|
+
'type' => 'dropoff',
|
|
370
|
+
'order' => $index + 1,
|
|
371
|
+
'service_time' => 300,
|
|
372
|
+
]);
|
|
373
|
+
|
|
374
|
+
Waypoint::withoutEvents(fn () => $waypoint->save());
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return $payload;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
protected function seedEntities(Company $company, Payload $payload, array $entities): void
|
|
381
|
+
{
|
|
382
|
+
foreach ($entities as $index => $definition) {
|
|
383
|
+
$entity = new Entity();
|
|
384
|
+
$entity->forceFill([
|
|
385
|
+
'uuid' => (string) Str::uuid(),
|
|
386
|
+
'company_uuid' => $company->uuid,
|
|
387
|
+
'payload_uuid' => $payload->uuid,
|
|
388
|
+
'name' => $definition['name'],
|
|
389
|
+
'type' => $definition['type'],
|
|
390
|
+
'weight' => $definition['weight'],
|
|
391
|
+
'weight_unit' => $definition['weight_unit'],
|
|
392
|
+
'length' => $definition['length'],
|
|
393
|
+
'width' => $definition['width'],
|
|
394
|
+
'height' => $definition['height'],
|
|
395
|
+
'dimensions_unit' => $definition['dimensions_unit'],
|
|
396
|
+
'meta' => $this->meta($payload->getMeta('seed_id') . '_' . ($index + 1)),
|
|
397
|
+
]);
|
|
398
|
+
|
|
399
|
+
Entity::withoutEvents(fn () => $entity->save());
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Http\Controllers\Internal\v1\OrchestrationController as InternalOrchestrationController;
|
|
6
|
+
use Illuminate\Http\JsonResponse;
|
|
7
|
+
use Illuminate\Http\Request;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Public consumable API for FleetOps orchestration.
|
|
11
|
+
*
|
|
12
|
+
* This controller delegates orchestration behavior to the internal workbench
|
|
13
|
+
* controller and only changes the public API serialization boundary.
|
|
14
|
+
*/
|
|
15
|
+
class OrchestrationController extends InternalOrchestrationController
|
|
16
|
+
{
|
|
17
|
+
public function run(Request $request): JsonResponse
|
|
18
|
+
{
|
|
19
|
+
return $this->publicResponse(parent::run($request));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public function commit(Request $request): JsonResponse
|
|
23
|
+
{
|
|
24
|
+
return $this->publicResponse(parent::commit($request));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected function publicResponse(JsonResponse $response): JsonResponse
|
|
28
|
+
{
|
|
29
|
+
return response()->json(
|
|
30
|
+
$this->sanitizePublicPayload($response->getData(true)),
|
|
31
|
+
$response->getStatusCode()
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Recursively remove internal identifiers from public orchestrator payloads.
|
|
37
|
+
*/
|
|
38
|
+
protected function sanitizePublicPayload(mixed $payload): mixed
|
|
39
|
+
{
|
|
40
|
+
if (!is_array($payload)) {
|
|
41
|
+
return $payload;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
$sanitized = [];
|
|
45
|
+
|
|
46
|
+
foreach ($payload as $key => $value) {
|
|
47
|
+
if (is_string($key) && $this->isInternalIdentifierKey($key)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
$sanitized[$key] = $this->sanitizePublicPayload($value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return $sanitized;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
protected function isInternalIdentifierKey(string $key): bool
|
|
58
|
+
{
|
|
59
|
+
return $key === 'uuid'
|
|
60
|
+
|| str_ends_with($key, '_uuid')
|
|
61
|
+
|| $key === 'internal_id'
|
|
62
|
+
|| $key === 'company_uuid';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -93,17 +93,17 @@ class OrderController extends Controller
|
|
|
93
93
|
|
|
94
94
|
// create payload
|
|
95
95
|
if ($request->has('payload') && $request->isArray('payload')) {
|
|
96
|
-
$payload
|
|
97
|
-
$payloadInput
|
|
98
|
-
$entities
|
|
99
|
-
$waypoints
|
|
100
|
-
$pickup
|
|
101
|
-
$dropoff
|
|
102
|
-
$return
|
|
103
|
-
$hasPickupField
|
|
104
|
-
$hasDropoffField
|
|
105
|
-
$hasReturnField
|
|
106
|
-
$hasWaypointsField
|
|
96
|
+
$payload = new Payload();
|
|
97
|
+
$payloadInput = $request->input('payload');
|
|
98
|
+
$entities = data_get($payloadInput, 'entities', []);
|
|
99
|
+
$waypoints = data_get($payloadInput, 'waypoints', []);
|
|
100
|
+
$pickup = data_get($payloadInput, 'pickup');
|
|
101
|
+
$dropoff = data_get($payloadInput, 'dropoff');
|
|
102
|
+
$return = data_get($payloadInput, 'return');
|
|
103
|
+
$hasPickupField = array_key_exists('pickup', $payloadInput);
|
|
104
|
+
$hasDropoffField = array_key_exists('dropoff', $payloadInput);
|
|
105
|
+
$hasReturnField = array_key_exists('return', $payloadInput);
|
|
106
|
+
$hasWaypointsField = array_key_exists('waypoints', $payloadInput);
|
|
107
107
|
$hasRouteEndpointFields = $hasPickupField || $hasDropoffField || $hasReturnField;
|
|
108
108
|
|
|
109
109
|
if ($pickup) {
|
|
@@ -445,17 +445,17 @@ class OrderController extends Controller
|
|
|
445
445
|
|
|
446
446
|
// create a payload if missing payload[] but has pickup/dropoff/etc
|
|
447
447
|
if ($request->missing('payload')) {
|
|
448
|
-
$payload
|
|
449
|
-
$payloadInput
|
|
450
|
-
$entities
|
|
451
|
-
$waypoints
|
|
452
|
-
$pickup
|
|
453
|
-
$dropoff
|
|
454
|
-
$return
|
|
455
|
-
$hasPickupField
|
|
456
|
-
$hasDropoffField
|
|
457
|
-
$hasReturnField
|
|
458
|
-
$hasWaypointsField
|
|
448
|
+
$payload = data_get($order, 'payload', new Payload());
|
|
449
|
+
$payloadInput = $request->only(['pickup', 'dropoff', 'return', 'waypoints', 'entities']);
|
|
450
|
+
$entities = data_get($payloadInput, 'entities', []);
|
|
451
|
+
$waypoints = data_get($payloadInput, 'waypoints', []);
|
|
452
|
+
$pickup = data_get($payloadInput, 'pickup');
|
|
453
|
+
$dropoff = data_get($payloadInput, 'dropoff');
|
|
454
|
+
$return = data_get($payloadInput, 'return');
|
|
455
|
+
$hasPickupField = $request->exists('pickup');
|
|
456
|
+
$hasDropoffField = $request->exists('dropoff');
|
|
457
|
+
$hasReturnField = $request->exists('return');
|
|
458
|
+
$hasWaypointsField = $request->exists('waypoints');
|
|
459
459
|
$hasRouteEndpointFields = $hasPickupField || $hasDropoffField || $hasReturnField;
|
|
460
460
|
|
|
461
461
|
// if no pickup and dropoff extract from waypoints
|
|
@@ -1328,13 +1328,13 @@ class OrderController extends Controller
|
|
|
1328
1328
|
*
|
|
1329
1329
|
* @return \Illuminate\Http\Response
|
|
1330
1330
|
*/
|
|
1331
|
-
public function trackerData(string $id)
|
|
1331
|
+
public function trackerData(Request $request, string $id)
|
|
1332
1332
|
{
|
|
1333
1333
|
set_time_limit(280);
|
|
1334
1334
|
|
|
1335
1335
|
try {
|
|
1336
1336
|
$order = Order::findRecordOrFail($id);
|
|
1337
|
-
$data = $order->tracker()->toArray();
|
|
1337
|
+
$data = $order->tracker()->toArray($request->only(['provider', 'fallbacks', 'traffic_enabled']));
|
|
1338
1338
|
|
|
1339
1339
|
return response()->json($data);
|
|
1340
1340
|
} catch (ModelNotFoundException $e) {
|
|
@@ -1351,11 +1351,11 @@ class OrderController extends Controller
|
|
|
1351
1351
|
*
|
|
1352
1352
|
* @return \Illuminate\Http\Response
|
|
1353
1353
|
*/
|
|
1354
|
-
public function etaData(string $id)
|
|
1354
|
+
public function etaData(Request $request, string $id)
|
|
1355
1355
|
{
|
|
1356
1356
|
try {
|
|
1357
1357
|
$order = Order::findRecordOrFail($id);
|
|
1358
|
-
$data = $order->tracker()->eta();
|
|
1358
|
+
$data = $order->tracker()->eta($request->only(['provider', 'fallbacks', 'traffic_enabled']));
|
|
1359
1359
|
|
|
1360
1360
|
return response()->json($data);
|
|
1361
1361
|
} catch (ModelNotFoundException $e) {
|
|
@@ -25,16 +25,16 @@ class PayloadController extends Controller
|
|
|
25
25
|
*/
|
|
26
26
|
public function create(CreatePayloadRequest $request)
|
|
27
27
|
{
|
|
28
|
-
$input
|
|
29
|
-
$entities
|
|
30
|
-
$waypoints
|
|
31
|
-
$pickup
|
|
32
|
-
$dropoff
|
|
33
|
-
$return
|
|
34
|
-
$hasPickupField
|
|
35
|
-
$hasDropoffField
|
|
36
|
-
$hasReturnField
|
|
37
|
-
$hasWaypointsField
|
|
28
|
+
$input = $request->all();
|
|
29
|
+
$entities = data_get($input, 'entities', []);
|
|
30
|
+
$waypoints = data_get($input, 'waypoints', []);
|
|
31
|
+
$pickup = data_get($input, 'pickup');
|
|
32
|
+
$dropoff = data_get($input, 'dropoff');
|
|
33
|
+
$return = data_get($input, 'return');
|
|
34
|
+
$hasPickupField = array_key_exists('pickup', $input);
|
|
35
|
+
$hasDropoffField = array_key_exists('dropoff', $input);
|
|
36
|
+
$hasReturnField = array_key_exists('return', $input);
|
|
37
|
+
$hasWaypointsField = array_key_exists('waypoints', $input);
|
|
38
38
|
$hasRouteEndpointFields = $hasPickupField || $hasDropoffField || $hasReturnField;
|
|
39
39
|
|
|
40
40
|
// make sure company is set
|
|
@@ -231,7 +231,7 @@ class ServiceQuoteController extends Controller
|
|
|
231
231
|
$entities = collect($entities)->mapInto(Entity::class);
|
|
232
232
|
|
|
233
233
|
// should all be Place like
|
|
234
|
-
$waypoints
|
|
234
|
+
$waypoints = collect([$pickup, ...$waypoints, $dropoff])->filter();
|
|
235
235
|
$endpointCount = (int) ($pickup instanceof Place) + (int) ($dropoff instanceof Place);
|
|
236
236
|
|
|
237
237
|
// if facilitator is an integrated partner resolve service quotes from bridge
|
|
@@ -8,6 +8,7 @@ use Fleetbase\FleetOps\Imports\ContactImport;
|
|
|
8
8
|
use Fleetbase\FleetOps\Models\Contact;
|
|
9
9
|
use Fleetbase\Http\Requests\ExportRequest;
|
|
10
10
|
use Fleetbase\Http\Requests\ImportRequest;
|
|
11
|
+
use Fleetbase\Models\User;
|
|
11
12
|
use Illuminate\Http\Request;
|
|
12
13
|
use Illuminate\Support\Str;
|
|
13
14
|
use Maatwebsite\Excel\Facades\Excel;
|
|
@@ -21,11 +22,35 @@ class ContactController extends FleetOpsController
|
|
|
21
22
|
*/
|
|
22
23
|
public $resource = 'contact';
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Handle pre-create transactions.
|
|
27
|
+
*/
|
|
28
|
+
public function onBeforeCreate(Request $request, array &$input)
|
|
29
|
+
{
|
|
30
|
+
$this->resolveUserInput($request, $input);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Handle pre-update transactions.
|
|
35
|
+
*/
|
|
36
|
+
public function onBeforeUpdate(Request $request, Contact $contact, array &$input)
|
|
37
|
+
{
|
|
38
|
+
if ($contact->type === 'customer' && isset($input['type']) && $input['type'] !== 'customer') {
|
|
39
|
+
throw new \Exception('Customer contact type cannot be changed.');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
$this->resolveUserInput($request, $input);
|
|
43
|
+
}
|
|
44
|
+
|
|
24
45
|
/**
|
|
25
46
|
* Handle post save transactions.
|
|
26
47
|
*/
|
|
27
48
|
public function afterSave(Request $request, Contact $contact)
|
|
28
49
|
{
|
|
50
|
+
if ($contact->type === 'customer') {
|
|
51
|
+
$contact->normalizeCustomerUser();
|
|
52
|
+
}
|
|
53
|
+
|
|
29
54
|
$customFieldValues = $request->array('contact.custom_field_values');
|
|
30
55
|
if ($customFieldValues) {
|
|
31
56
|
$contact->syncCustomFieldValues($customFieldValues);
|
|
@@ -105,4 +130,20 @@ class ContactController extends FleetOpsController
|
|
|
105
130
|
|
|
106
131
|
return response()->json(['status' => 'ok', 'message' => 'Import completed', 'imported' => $importedCount]);
|
|
107
132
|
}
|
|
133
|
+
|
|
134
|
+
private function resolveUserInput(Request $request, array &$input): void
|
|
135
|
+
{
|
|
136
|
+
$user = data_get($input, 'user_uuid') ?? data_get($input, 'user') ?? $request->input('contact.user_uuid') ?? $request->input('contact.user');
|
|
137
|
+
|
|
138
|
+
if (is_array($user)) {
|
|
139
|
+
$user = data_get($user, 'uuid') ?? data_get($user, 'id');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!$user) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
$input['user_uuid'] = User::where('uuid', $user)->orWhere('public_id', $user)->value('uuid') ?? $user;
|
|
147
|
+
unset($input['user']);
|
|
148
|
+
}
|
|
108
149
|
}
|