@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.
- 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/driver/details.hbs +5 -1
- package/addon/components/driver/form.hbs +11 -1
- package/addon/components/fleet-listing-panel.hbs +2 -2
- package/addon/components/fleet-listing-panel.js +10 -0
- package/addon/components/map/google-live-map.js +12 -73
- package/addon/components/map/leaflet-live-map.hbs +149 -33
- 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 +49 -5
- package/addon/components/orchestrator/phase-builder.js +34 -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 +4 -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.hbs +2 -2
- 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/components/widget/fleet-ops-key-metrics.hbs +19 -17
- package/addon/components/widget/fleet-ops-key-metrics.js +26 -0
- package/addon/components/widget/fuel-efficiency.hbs +26 -0
- package/addon/components/widget/fuel-efficiency.js +43 -0
- package/addon/components/widget/geofence-violations.hbs +44 -0
- package/addon/components/widget/geofence-violations.js +55 -0
- package/addon/components/widget/issues-insights.hbs +43 -0
- package/addon/components/widget/issues-insights.js +62 -0
- package/addon/components/widget/kpi-active-orders.hbs +10 -0
- package/addon/components/widget/kpi-active-orders.js +5 -0
- package/addon/components/widget/kpi-aov.hbs +10 -0
- package/addon/components/widget/kpi-aov.js +5 -0
- package/addon/components/widget/kpi-distance.hbs +10 -0
- package/addon/components/widget/kpi-distance.js +5 -0
- package/addon/components/widget/kpi-drivers-online.hbs +10 -0
- package/addon/components/widget/kpi-drivers-online.js +5 -0
- package/addon/components/widget/kpi-earnings.hbs +10 -0
- package/addon/components/widget/kpi-earnings.js +5 -0
- package/addon/components/widget/kpi-open-issues.hbs +11 -0
- package/addon/components/widget/kpi-open-issues.js +5 -0
- package/addon/components/widget/kpi-tile.hbs +59 -0
- package/addon/components/widget/kpi-tile.js +168 -0
- package/addon/components/widget/live-fleet.hbs +122 -0
- package/addon/components/widget/live-fleet.js +365 -0
- package/addon/components/widget/maintenance-overview.hbs +45 -0
- package/addon/components/widget/maintenance-overview.js +40 -0
- package/addon/components/widget/on-time-delivery.hbs +27 -0
- package/addon/components/widget/on-time-delivery.js +73 -0
- package/addon/components/widget/operations-pulse.hbs +51 -0
- package/addon/components/widget/operations-pulse.js +173 -0
- package/addon/components/widget/orders-by-status.hbs +26 -0
- package/addon/components/widget/orders-by-status.js +54 -0
- package/addon/components/widget/revenue-trend.hbs +34 -0
- package/addon/components/widget/revenue-trend.js +67 -0
- package/addon/components/widget/top-drivers.hbs +44 -0
- package/addon/components/widget/top-drivers.js +54 -0
- package/addon/controllers/settings/routing.js +124 -1
- package/addon/extension.js +172 -3
- package/addon/helpers/format-duration.js +38 -0
- package/addon/routes/application.js +6 -0
- package/addon/services/map-adapter/google.js +13 -2
- package/addon/services/order-list-overlay.js +2 -2
- package/addon/styles/fleetops-engine.css +806 -46
- package/addon/templates/maintenance/equipment/index.hbs +1 -1
- package/addon/templates/maintenance/parts/index.hbs +1 -1
- package/addon/templates/maintenance/schedules/index.hbs +1 -1
- package/addon/templates/management/vehicles/index.hbs +1 -1
- package/addon/templates/settings/avatars.hbs +1 -1
- package/addon/templates/settings/custom-fields.hbs +1 -1
- package/addon/templates/settings/map.hbs +1 -1
- package/addon/templates/settings/navigator-app.hbs +1 -1
- package/addon/templates/settings/notifications.hbs +1 -1
- package/addon/templates/settings/orchestrator.hbs +1 -1
- package/addon/templates/settings/payments/index.hbs +1 -1
- package/addon/templates/settings/payments/onboard.hbs +1 -1
- package/addon/templates/settings/routing.hbs +82 -4
- package/addon/templates/settings/scheduling.hbs +1 -1
- package/addon/utils/live-map-card-content.js +171 -0
- package/app/components/tracking-stop-progress.js +1 -0
- package/app/components/widget/fuel-efficiency.js +1 -0
- package/app/components/widget/geofence-violations.js +1 -0
- package/app/components/widget/issues-insights.js +1 -0
- package/app/components/widget/kpi-active-orders.js +1 -0
- package/app/components/widget/kpi-aov.js +1 -0
- package/app/components/widget/kpi-distance.js +1 -0
- package/app/components/widget/kpi-drivers-online.js +1 -0
- package/app/components/widget/kpi-earnings.js +1 -0
- package/app/components/widget/kpi-open-issues.js +1 -0
- package/app/components/widget/kpi-tile.js +1 -0
- package/app/components/widget/live-fleet.js +1 -0
- package/app/components/widget/maintenance-overview.js +1 -0
- package/app/components/widget/on-time-delivery.js +1 -0
- package/app/components/widget/operations-pulse.js +1 -0
- package/app/components/widget/orders-by-status.js +1 -0
- package/app/components/widget/revenue-trend.js +1 -0
- package/app/components/widget/top-drivers.js +1 -0
- package/app/helpers/format-duration.js +1 -0
- package/app/utils/live-map-card-content.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/pnpm-workspace.yaml +1 -0
- package/server/config/fleetops.php +32 -11
- package/server/config/telematics.php +111 -1
- package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +86 -0
- package/server/migrations/2026_05_18_000001_add_license_expiry_to_drivers_table.php +25 -0
- package/server/seeders/Testing/Concerns/SeedsTestingData.php +183 -0
- package/server/seeders/Testing/ConnectivitySeeder.php +212 -0
- package/server/seeders/Testing/FleetSeeder.php +341 -0
- package/server/seeders/Testing/MaintenanceSeeder.php +238 -0
- package/server/seeders/Testing/NetworkSeeder.php +250 -0
- package/server/seeders/Testing/OrdersSeeder.php +214 -0
- package/server/seeders/Testing/TestingSeeder.php +40 -0
- package/server/seeders/Testing/VroomOrchestrationSeeder.php +409 -0
- package/server/src/Console/Commands/ProcessOperationalAlerts.php +279 -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/AnalyticsController.php +119 -0
- 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/MetricsController.php +82 -3
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +52 -21
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +164 -12
- package/server/src/Http/Resources/v1/Driver.php +2 -0
- package/server/src/Http/Resources/v1/Fleet.php +4 -0
- package/server/src/Http/Resources/v1/Index/Driver.php +48 -0
- package/server/src/Http/Resources/v1/Index/Payload.php +2 -2
- package/server/src/Http/Resources/v1/Index/Vehicle.php +74 -1
- package/server/src/Models/Contact.php +122 -13
- package/server/src/Models/Driver.php +2 -0
- package/server/src/Models/Order.php +1 -1
- package/server/src/Models/OrderConfig.php +14 -11
- 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/Notifications/LateDeparture.php +48 -0
- package/server/src/Notifications/ProlongedStoppage.php +48 -0
- package/server/src/Notifications/RouteDeviation.php +48 -0
- package/server/src/Observers/ContactObserver.php +8 -0
- package/server/src/Orchestration/Engines/CapacityAllocationEngine.php +220 -0
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +357 -38
- package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +259 -51
- package/server/src/Providers/FleetOpsServiceProvider.php +32 -0
- package/server/src/Providers/NotificationServiceProvider.php +3 -0
- package/server/src/Support/Analytics/AbstractAnalytics.php +77 -0
- package/server/src/Support/Analytics/FuelEfficiency.php +83 -0
- package/server/src/Support/Analytics/GeofenceViolations.php +63 -0
- package/server/src/Support/Analytics/IssuesInsights.php +67 -0
- package/server/src/Support/Analytics/LiveFleet.php +127 -0
- package/server/src/Support/Analytics/MaintenanceOverview.php +70 -0
- package/server/src/Support/Analytics/OnTimeDelivery.php +75 -0
- package/server/src/Support/Analytics/OperationsPulse.php +86 -0
- package/server/src/Support/Analytics/OrdersByStatus.php +64 -0
- package/server/src/Support/Analytics/RevenueTrend.php +79 -0
- package/server/src/Support/Analytics/TopDrivers.php +92 -0
- package/server/src/Support/LiveOrderQuery.php +75 -0
- package/server/src/Support/Metrics/AbstractMetric.php +170 -0
- package/server/src/Support/Metrics/ActiveLiveOrdersMetric.php +28 -0
- package/server/src/Support/Metrics/AvgOrderValueMetric.php +53 -0
- package/server/src/Support/Metrics/DriversOnlineMetric.php +30 -0
- package/server/src/Support/Metrics/EarningsMetric.php +30 -0
- package/server/src/Support/Metrics/FuelCostsMetric.php +30 -0
- package/server/src/Support/Metrics/MoneyMetric.php +30 -0
- package/server/src/Support/Metrics/OpenIssuesMetric.php +35 -0
- package/server/src/Support/Metrics/OrdersCanceledMetric.php +35 -0
- package/server/src/Support/Metrics/OrdersCompletedMetric.php +35 -0
- package/server/src/Support/Metrics/OrdersInProgressMetric.php +51 -0
- package/server/src/Support/Metrics/OrdersScheduledMetric.php +37 -0
- package/server/src/Support/Metrics/Registry.php +46 -0
- package/server/src/Support/Metrics/ResolvedIssuesMetric.php +35 -0
- package/server/src/Support/Metrics/TotalCustomersMetric.php +29 -0
- package/server/src/Support/Metrics/TotalDistanceTraveledMetric.php +35 -0
- package/server/src/Support/Metrics/TotalDriversMetric.php +28 -0
- package/server/src/Support/Metrics/TotalTimeTraveledMetric.php +35 -0
- package/server/src/Support/Metrics.php +46 -234
- package/server/src/Support/OSRM.php +11 -6
- package/server/src/Support/OrderTracker.php +7 -582
- package/server/src/Support/Telematics/Providers/AfaqyProvider.php +290 -0
- package/server/src/Support/Telematics/Providers/SafeeProvider.php +333 -0
- 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 +27 -0
- package/server/tests/AnalyticsRoutesTest.php +41 -0
- package/server/tests/CapacityAllocationEngineTest.php +108 -0
- package/server/tests/LiveOrderQueryTest.php +34 -0
- package/server/tests/MetricsRegistryTest.php +88 -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 +250 -0
- package/translations/en-us.yaml +41 -0
|
@@ -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
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Support\Analytics\AbstractAnalytics;
|
|
6
|
+
use Fleetbase\FleetOps\Support\Analytics\FuelEfficiency;
|
|
7
|
+
use Fleetbase\FleetOps\Support\Analytics\GeofenceViolations;
|
|
8
|
+
use Fleetbase\FleetOps\Support\Analytics\IssuesInsights;
|
|
9
|
+
use Fleetbase\FleetOps\Support\Analytics\LiveFleet;
|
|
10
|
+
use Fleetbase\FleetOps\Support\Analytics\MaintenanceOverview;
|
|
11
|
+
use Fleetbase\FleetOps\Support\Analytics\OnTimeDelivery;
|
|
12
|
+
use Fleetbase\FleetOps\Support\Analytics\OperationsPulse;
|
|
13
|
+
use Fleetbase\FleetOps\Support\Analytics\OrdersByStatus;
|
|
14
|
+
use Fleetbase\FleetOps\Support\Analytics\RevenueTrend;
|
|
15
|
+
use Fleetbase\FleetOps\Support\Analytics\TopDrivers;
|
|
16
|
+
use Fleetbase\Http\Controllers\Controller;
|
|
17
|
+
use Illuminate\Http\Request;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* One public method per dashboard widget. Each method:
|
|
21
|
+
* - Resolves a period from ?period=7d|30d|... or explicit start/end
|
|
22
|
+
* - Constructs the corresponding Support\Analytics\* helper
|
|
23
|
+
* - Returns the helper's JSON payload (Chart.js datasets, ranked rows,
|
|
24
|
+
* scalar tiles, etc. — shape is widget-specific)
|
|
25
|
+
*
|
|
26
|
+
* Cache headers are set per-widget by the route middleware in routes.php.
|
|
27
|
+
*/
|
|
28
|
+
class AnalyticsController extends Controller
|
|
29
|
+
{
|
|
30
|
+
public function operationsPulse(Request $request)
|
|
31
|
+
{
|
|
32
|
+
return $this->run($request, OperationsPulse::class);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public function revenueTrend(Request $request)
|
|
36
|
+
{
|
|
37
|
+
return $this->run($request, RevenueTrend::class, function (RevenueTrend $a) use ($request) {
|
|
38
|
+
$a->groupBy($request->string('group_by', 'day')->toString());
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public function ordersByStatus(Request $request)
|
|
43
|
+
{
|
|
44
|
+
return $this->run($request, OrdersByStatus::class, defaultDays: 14);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public function onTimeDelivery(Request $request)
|
|
48
|
+
{
|
|
49
|
+
return $this->run($request, OnTimeDelivery::class, function (OnTimeDelivery $a) use ($request) {
|
|
50
|
+
$a->slaMinutes((int) $request->input('sla_minutes', 30));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public function topDrivers(Request $request)
|
|
55
|
+
{
|
|
56
|
+
return $this->run($request, TopDrivers::class, function (TopDrivers $a) use ($request) {
|
|
57
|
+
$a->limit((int) $request->input('limit', 10));
|
|
58
|
+
$a->sortBy($request->string('sort_by', 'orders_completed')->toString());
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public function fuelEfficiency(Request $request)
|
|
63
|
+
{
|
|
64
|
+
return $this->run($request, FuelEfficiency::class, defaultDays: 90);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public function issuesInsights(Request $request)
|
|
68
|
+
{
|
|
69
|
+
return $this->run($request, IssuesInsights::class);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public function maintenanceOverview(Request $request)
|
|
73
|
+
{
|
|
74
|
+
return $this->run($request, MaintenanceOverview::class);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public function geofenceViolations(Request $request)
|
|
78
|
+
{
|
|
79
|
+
return $this->run($request, GeofenceViolations::class, defaultDays: 7);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public function liveFleet(Request $request)
|
|
83
|
+
{
|
|
84
|
+
return $this->run($request, LiveFleet::class);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Shared boilerplate: period parsing + helper construction + JSON response.
|
|
89
|
+
*
|
|
90
|
+
* @param class-string<AbstractAnalytics> $class
|
|
91
|
+
*/
|
|
92
|
+
private function run(Request $request, string $class, ?callable $configure = null, int $defaultDays = 30)
|
|
93
|
+
{
|
|
94
|
+
[$start, $end] = AbstractAnalytics::resolvePeriod(
|
|
95
|
+
$request->string('period')->toString() ?: null,
|
|
96
|
+
$request->date('start'),
|
|
97
|
+
$request->date('end'),
|
|
98
|
+
$defaultDays,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
/** @var AbstractAnalytics $analytics */
|
|
103
|
+
$analytics = $class::forCompany($request->user()->company)->between($start, $end);
|
|
104
|
+
|
|
105
|
+
if ($configure !== null) {
|
|
106
|
+
$configure($analytics);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return response()->json($analytics->get());
|
|
110
|
+
} catch (\Throwable $e) {
|
|
111
|
+
report($e);
|
|
112
|
+
|
|
113
|
+
return response()->json([
|
|
114
|
+
'error' => $e->getMessage(),
|
|
115
|
+
'widget' => $class,
|
|
116
|
+
], 500);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -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
|
}
|
|
@@ -13,6 +13,7 @@ use Fleetbase\FleetOps\Models\Place;
|
|
|
13
13
|
use Fleetbase\FleetOps\Models\Route;
|
|
14
14
|
use Fleetbase\FleetOps\Models\Vehicle;
|
|
15
15
|
use Fleetbase\FleetOps\Support\LiveCacheService;
|
|
16
|
+
use Fleetbase\FleetOps\Support\LiveOrderQuery;
|
|
16
17
|
use Fleetbase\Http\Controllers\Controller;
|
|
17
18
|
use Illuminate\Http\Request;
|
|
18
19
|
|
|
@@ -110,50 +111,13 @@ class LiveController extends Controller
|
|
|
110
111
|
];
|
|
111
112
|
|
|
112
113
|
return LiveCacheService::remember('orders', $cacheParams, function () use ($exclude, $active, $unassigned) {
|
|
113
|
-
$query =
|
|
114
|
-
|
|
115
|
-
$
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
$q->orWhereHas('pickup');
|
|
119
|
-
$q->orWhereHas('dropoff');
|
|
120
|
-
}
|
|
121
|
-
);
|
|
122
|
-
})
|
|
123
|
-
->whereNotIn('status', ['canceled', 'completed', 'expired'])
|
|
124
|
-
->whereHas('trackingNumber')
|
|
125
|
-
->whereHas('trackingStatuses')
|
|
126
|
-
->whereNotIn('public_id', $exclude)
|
|
127
|
-
->whereNull('deleted_at')
|
|
128
|
-
->applyDirectivesForPermissions('fleet-ops list order')
|
|
129
|
-
->with([
|
|
130
|
-
'payload.entities',
|
|
131
|
-
'payload.dropoff',
|
|
132
|
-
'payload.pickup',
|
|
133
|
-
'payload.return',
|
|
134
|
-
'payload.firstWaypointMarker.place',
|
|
135
|
-
'payload.lastWaypointMarker.place',
|
|
136
|
-
'trackingNumber',
|
|
137
|
-
'trackingStatuses',
|
|
138
|
-
'driverAssigned' => function ($query) {
|
|
139
|
-
$query->without(['jobs', 'currentJob']);
|
|
140
|
-
},
|
|
141
|
-
'vehicleAssigned' => function ($query) {
|
|
142
|
-
$query->without(['fleets', 'vendor']);
|
|
143
|
-
},
|
|
144
|
-
'customer',
|
|
145
|
-
'facilitator',
|
|
114
|
+
$query = LiveOrderQuery::make(session('company'), [
|
|
115
|
+
'exclude' => $exclude,
|
|
116
|
+
'active' => $active,
|
|
117
|
+
'unassigned' => $unassigned,
|
|
118
|
+
'with_relations' => true,
|
|
146
119
|
]);
|
|
147
120
|
|
|
148
|
-
if ($active) {
|
|
149
|
-
$query->whereHas('driverAssigned');
|
|
150
|
-
$query->whereNotIn('status', ['created', 'completed', 'expired', 'order_canceled', 'canceled', 'pending']);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if ($unassigned) {
|
|
154
|
-
$query->whereNull('driver_assigned_uuid');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
121
|
$query->limit(60); // max 60 latest
|
|
158
122
|
$query->latest();
|
|
159
123
|
|
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\FleetOps\Support\Metrics;
|
|
6
|
+
use Fleetbase\FleetOps\Support\Metrics\Registry;
|
|
6
7
|
use Fleetbase\Http\Controllers\Controller;
|
|
7
8
|
use Illuminate\Http\Request;
|
|
9
|
+
use Illuminate\Support\Carbon;
|
|
8
10
|
|
|
9
11
|
class MetricsController extends Controller
|
|
10
12
|
{
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* Legacy bulk endpoint. Returns a flat map of slug → scalar value for the
|
|
15
|
+
* requested period. Preserved for backward compat; the dashboard widgets
|
|
16
|
+
* now prefer per-slug GET /metrics/{slug}.
|
|
15
17
|
*/
|
|
16
18
|
public function all(Request $request)
|
|
17
19
|
{
|
|
@@ -27,4 +29,81 @@ class MetricsController extends Controller
|
|
|
27
29
|
|
|
28
30
|
return response()->json($data);
|
|
29
31
|
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Per-metric endpoint backing <Widget::KpiTile>.
|
|
35
|
+
*
|
|
36
|
+
* Accepts:
|
|
37
|
+
* ?period=7d|30d|90d OR ?start=ISO&end=ISO
|
|
38
|
+
* ?sparkline=true to include a per-day mini-chart series
|
|
39
|
+
* ?compare=true to include period-over-period delta (default: true)
|
|
40
|
+
*
|
|
41
|
+
* Returns: { slug, value, format, currency?, delta_pct?, sparkline? }
|
|
42
|
+
*/
|
|
43
|
+
public function show(Request $request, string $slug)
|
|
44
|
+
{
|
|
45
|
+
$class = Registry::resolve($slug);
|
|
46
|
+
if ($class === null) {
|
|
47
|
+
return response()->json(['error' => "Unknown metric: {$slug}"], 404);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[$start, $end] = $this->resolvePeriod($request);
|
|
51
|
+
|
|
52
|
+
$metric = $class::forCompany($request->user()->company)->between($start, $end);
|
|
53
|
+
|
|
54
|
+
if ($request->boolean('compare', true)) {
|
|
55
|
+
$duration = $end->getTimestamp() - $start->getTimestamp();
|
|
56
|
+
$compareEnd = (clone $start);
|
|
57
|
+
$compareStart = (clone $start)->modify("-{$duration} seconds");
|
|
58
|
+
$metric->compareTo($compareStart, $compareEnd);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if ($request->boolean('sparkline')) {
|
|
62
|
+
$buckets = (int) $request->input('sparkline_buckets', 14);
|
|
63
|
+
$metric->withSparkline($buckets, 'day');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return response()->json($metric->get());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parse ?period=7d/30d/90d into a [start, end] pair, falling back to
|
|
71
|
+
* explicit ?start=&end= ISO dates, then to a 30-day default.
|
|
72
|
+
*/
|
|
73
|
+
private function resolvePeriod(Request $request): array
|
|
74
|
+
{
|
|
75
|
+
$period = $request->string('period')->toString();
|
|
76
|
+
|
|
77
|
+
if ($period !== '') {
|
|
78
|
+
$days = match ($period) {
|
|
79
|
+
'7d' => 7,
|
|
80
|
+
'14d' => 14,
|
|
81
|
+
'30d' => 30,
|
|
82
|
+
'90d' => 90,
|
|
83
|
+
'180d' => 180,
|
|
84
|
+
'365d' => 365,
|
|
85
|
+
default => null,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if ($days !== null) {
|
|
89
|
+
$end = Carbon::now()->toDateTime();
|
|
90
|
+
$start = Carbon::now()->subDays($days)->toDateTime();
|
|
91
|
+
|
|
92
|
+
return [$start, $end];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
$start = $request->date('start') ?? Carbon::now()->subDays(30)->toDateTime();
|
|
97
|
+
$end = $request->date('end') ?? Carbon::now()->toDateTime();
|
|
98
|
+
|
|
99
|
+
if (!$start instanceof \DateTime) {
|
|
100
|
+
$start = Carbon::parse($start)->toDateTime();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!$end instanceof \DateTime) {
|
|
104
|
+
$end = Carbon::parse($end)->toDateTime();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return [$start, $end];
|
|
108
|
+
}
|
|
30
109
|
}
|
|
@@ -573,7 +573,7 @@ class OrchestrationController extends Controller
|
|
|
573
573
|
$isMulti = $orderType === 'multi_waypoint';
|
|
574
574
|
|
|
575
575
|
// ── Resolve OrderConfig ───────────────────────────────────────
|
|
576
|
-
$orderConfigUuid
|
|
576
|
+
$orderConfigUuid = null;
|
|
577
577
|
$resolvedOrderConfig = null;
|
|
578
578
|
if (!empty($firstRow['type'])) {
|
|
579
579
|
$resolvedOrderConfig = OrderConfig::resolveFromIdentifier($firstRow['type']);
|