@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,75 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
use Illuminate\Database\Eloquent\Builder;
|
|
7
|
+
|
|
8
|
+
class LiveOrderQuery
|
|
9
|
+
{
|
|
10
|
+
public static array $baseExcludedStatuses = ['canceled', 'completed', 'expired'];
|
|
11
|
+
public static array $activeExcludedStatuses = ['created', 'completed', 'expired', 'order_canceled', 'canceled', 'pending'];
|
|
12
|
+
|
|
13
|
+
public static function make(?string $companyUuid = null, array $options = []): Builder
|
|
14
|
+
{
|
|
15
|
+
$companyUuid = $companyUuid ?: session('company');
|
|
16
|
+
$exclude = $options['exclude'] ?? [];
|
|
17
|
+
$active = $options['active'] ?? false;
|
|
18
|
+
$unassigned = $options['unassigned'] ?? false;
|
|
19
|
+
$withRelations = $options['with_relations'] ?? false;
|
|
20
|
+
$applyPermissions = $options['apply_permissions'] ?? true;
|
|
21
|
+
|
|
22
|
+
$query = Order::where('company_uuid', $companyUuid)
|
|
23
|
+
->whereHas('payload', function ($query) {
|
|
24
|
+
$query->where(function ($q) {
|
|
25
|
+
$q->whereHas('waypoints');
|
|
26
|
+
$q->orWhereHas('pickup');
|
|
27
|
+
$q->orWhereHas('dropoff');
|
|
28
|
+
});
|
|
29
|
+
})
|
|
30
|
+
->whereNotIn('status', static::$baseExcludedStatuses)
|
|
31
|
+
->whereHas('trackingNumber')
|
|
32
|
+
->whereHas('trackingStatuses')
|
|
33
|
+
->whereNull('deleted_at');
|
|
34
|
+
|
|
35
|
+
if (!empty($exclude)) {
|
|
36
|
+
$query->whereNotIn('public_id', $exclude);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if ($applyPermissions) {
|
|
40
|
+
$query->applyDirectivesForPermissions('fleet-ops list order');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if ($active) {
|
|
44
|
+
$query->whereHas('driverAssigned');
|
|
45
|
+
$query->whereNotIn('status', static::$activeExcludedStatuses);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if ($unassigned) {
|
|
49
|
+
$query->whereNull('driver_assigned_uuid');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if ($withRelations) {
|
|
53
|
+
$query->with([
|
|
54
|
+
'payload.entities',
|
|
55
|
+
'payload.dropoff',
|
|
56
|
+
'payload.pickup',
|
|
57
|
+
'payload.return',
|
|
58
|
+
'payload.firstWaypointMarker.place',
|
|
59
|
+
'payload.lastWaypointMarker.place',
|
|
60
|
+
'trackingNumber',
|
|
61
|
+
'trackingStatuses',
|
|
62
|
+
'driverAssigned' => function ($query) {
|
|
63
|
+
$query->without(['jobs', 'currentJob']);
|
|
64
|
+
},
|
|
65
|
+
'vehicleAssigned' => function ($query) {
|
|
66
|
+
$query->without(['fleets', 'vendor']);
|
|
67
|
+
},
|
|
68
|
+
'customer',
|
|
69
|
+
'facilitator',
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return $query;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Models\Company;
|
|
6
|
+
use Illuminate\Support\Carbon;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base class for FleetOps scalar metrics.
|
|
10
|
+
*
|
|
11
|
+
* Subclasses implement query() + aggregate() and declare slug() + format(). The base
|
|
12
|
+
* handles period selection, optional period-over-period delta, and optional sparkline.
|
|
13
|
+
*/
|
|
14
|
+
abstract class AbstractMetric
|
|
15
|
+
{
|
|
16
|
+
protected Company $company;
|
|
17
|
+
protected ?\DateTimeInterface $start = null;
|
|
18
|
+
protected ?\DateTimeInterface $end = null;
|
|
19
|
+
protected ?\DateTimeInterface $compareStart = null;
|
|
20
|
+
protected ?\DateTimeInterface $compareEnd = null;
|
|
21
|
+
protected int $sparklineBuckets = 0;
|
|
22
|
+
protected string $sparklineUnit = 'day';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Period boundaries for the currently-executing aggregate call.
|
|
26
|
+
* Set by value()/delta()/sparkline() before invoking aggregate(); subclasses
|
|
27
|
+
* that need to issue a second parallel query (e.g. AvgOrderValueMetric) read
|
|
28
|
+
* these instead of re-introspecting the query's where clauses.
|
|
29
|
+
*/
|
|
30
|
+
protected ?\DateTimeInterface $currentStart = null;
|
|
31
|
+
protected ?\DateTimeInterface $currentEnd = null;
|
|
32
|
+
|
|
33
|
+
abstract public static function slug(): string;
|
|
34
|
+
|
|
35
|
+
abstract public function format(): string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build the query for the given period. The base class invokes this with the
|
|
39
|
+
* current period, the comparison period (for delta), and one sub-range per
|
|
40
|
+
* sparkline bucket — so it must be cheap and idempotent.
|
|
41
|
+
*/
|
|
42
|
+
abstract protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Reduce a query to a single numeric value. Override for sums (`->sum('amount')`),
|
|
46
|
+
* counts (`->count()`), or computed expressions.
|
|
47
|
+
*/
|
|
48
|
+
abstract protected function aggregate($query): float|int;
|
|
49
|
+
|
|
50
|
+
public function currency(): ?string
|
|
51
|
+
{
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public static function forCompany(Company $company): static
|
|
56
|
+
{
|
|
57
|
+
return (new static())->setCompany($company);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public function between(?\DateTimeInterface $start, ?\DateTimeInterface $end): static
|
|
61
|
+
{
|
|
62
|
+
$this->start = $start;
|
|
63
|
+
$this->end = $end;
|
|
64
|
+
|
|
65
|
+
return $this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public function compareTo(\DateTimeInterface $start, \DateTimeInterface $end): static
|
|
69
|
+
{
|
|
70
|
+
$this->compareStart = $start;
|
|
71
|
+
$this->compareEnd = $end;
|
|
72
|
+
|
|
73
|
+
return $this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public function withSparkline(int $buckets = 14, string $unit = 'day'): static
|
|
77
|
+
{
|
|
78
|
+
$this->sparklineBuckets = max(0, $buckets);
|
|
79
|
+
$this->sparklineUnit = $unit;
|
|
80
|
+
|
|
81
|
+
return $this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public function value(): float|int
|
|
85
|
+
{
|
|
86
|
+
return $this->runAggregate($this->start, $this->end);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public function delta(): ?float
|
|
90
|
+
{
|
|
91
|
+
if (!$this->compareStart || !$this->compareEnd) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
$current = $this->value();
|
|
96
|
+
$previous = $this->runAggregate($this->compareStart, $this->compareEnd);
|
|
97
|
+
|
|
98
|
+
if ((float) $previous === 0.0) {
|
|
99
|
+
return $current > 0 ? 100.0 : 0.0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return round((($current - $previous) / $previous) * 100, 1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public function sparkline(): ?array
|
|
106
|
+
{
|
|
107
|
+
if ($this->sparklineBuckets === 0 || $this->end === null) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
$end = Carbon::instance($this->end);
|
|
112
|
+
$cursor = $end->copy()->sub($this->sparklineUnit, $this->sparklineBuckets);
|
|
113
|
+
$labels = [];
|
|
114
|
+
$data = [];
|
|
115
|
+
|
|
116
|
+
for ($i = 0; $i < $this->sparklineBuckets; $i++) {
|
|
117
|
+
$next = $cursor->copy()->add($this->sparklineUnit, 1);
|
|
118
|
+
$labels[] = $cursor->format('Y-m-d');
|
|
119
|
+
$data[] = $this->runAggregate($cursor->toDateTime(), $next->toDateTime());
|
|
120
|
+
$cursor = $next;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return ['labels' => $labels, 'data' => $data];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Run a single aggregation for a sub-period. Sets $currentStart/$currentEnd
|
|
128
|
+
* so subclasses that need to issue a parallel query can read the bucket range.
|
|
129
|
+
*/
|
|
130
|
+
protected function runAggregate(?\DateTimeInterface $start, ?\DateTimeInterface $end): float|int
|
|
131
|
+
{
|
|
132
|
+
$this->currentStart = $start;
|
|
133
|
+
$this->currentEnd = $end;
|
|
134
|
+
|
|
135
|
+
return $this->aggregate($this->query($start, $end));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public function get(): array
|
|
139
|
+
{
|
|
140
|
+
$payload = [
|
|
141
|
+
'slug' => static::slug(),
|
|
142
|
+
'value' => $this->value(),
|
|
143
|
+
'format' => $this->format(),
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
$currency = $this->currency();
|
|
147
|
+
if ($currency !== null) {
|
|
148
|
+
$payload['currency'] = $currency;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
$delta = $this->delta();
|
|
152
|
+
if ($delta !== null) {
|
|
153
|
+
$payload['delta_pct'] = $delta;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
$sparkline = $this->sparkline();
|
|
157
|
+
if ($sparkline !== null) {
|
|
158
|
+
$payload['sparkline'] = $sparkline;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return $payload;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
protected function setCompany(Company $company): static
|
|
165
|
+
{
|
|
166
|
+
$this->company = $company;
|
|
167
|
+
|
|
168
|
+
return $this;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Support\LiveOrderQuery;
|
|
6
|
+
|
|
7
|
+
class ActiveLiveOrdersMetric extends AbstractMetric
|
|
8
|
+
{
|
|
9
|
+
public static function slug(): string
|
|
10
|
+
{
|
|
11
|
+
return 'active_live_orders';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public function format(): string
|
|
15
|
+
{
|
|
16
|
+
return 'count';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
|
|
20
|
+
{
|
|
21
|
+
return LiveOrderQuery::make($this->company->uuid, ['active' => true]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protected function aggregate($query): int
|
|
25
|
+
{
|
|
26
|
+
return (int) $query->count();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
use Fleetbase\Models\Transaction;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Average revenue per completed order in the period. Computed as
|
|
10
|
+
* sum(transactions.amount, primary_currency) / count(completed orders).
|
|
11
|
+
*
|
|
12
|
+
* Uses $this->currentStart/$currentEnd (set by the base class) so the metric
|
|
13
|
+
* cooperates with the sparkline machinery — each bucket recomputes both
|
|
14
|
+
* numerator and denominator over its own sub-range.
|
|
15
|
+
*/
|
|
16
|
+
class AvgOrderValueMetric extends MoneyMetric
|
|
17
|
+
{
|
|
18
|
+
public static function slug(): string
|
|
19
|
+
{
|
|
20
|
+
return 'avg_order_value';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
|
|
24
|
+
{
|
|
25
|
+
$query = Order::where('company_uuid', $this->company->uuid)
|
|
26
|
+
->where('status', 'completed');
|
|
27
|
+
|
|
28
|
+
if ($start && $end) {
|
|
29
|
+
$query->whereBetween('created_at', [$start, $end]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return $query;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected function aggregate($query): float
|
|
36
|
+
{
|
|
37
|
+
$orderCount = (int) $query->count();
|
|
38
|
+
if ($orderCount === 0) {
|
|
39
|
+
return 0.0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
$revenueQuery = Transaction::where('company_uuid', $this->company->uuid)
|
|
43
|
+
->where('currency', $this->currency());
|
|
44
|
+
|
|
45
|
+
if ($this->currentStart && $this->currentEnd) {
|
|
46
|
+
$revenueQuery->whereBetween('created_at', [$this->currentStart, $this->currentEnd]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
$totalRevenue = (float) $revenueQuery->sum('amount');
|
|
50
|
+
|
|
51
|
+
return round($totalRevenue / $orderCount, 2);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Driver;
|
|
6
|
+
|
|
7
|
+
class DriversOnlineMetric extends AbstractMetric
|
|
8
|
+
{
|
|
9
|
+
public static function slug(): string
|
|
10
|
+
{
|
|
11
|
+
return 'drivers_online';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public function format(): string
|
|
15
|
+
{
|
|
16
|
+
return 'count';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
|
|
20
|
+
{
|
|
21
|
+
return Driver::where('company_uuid', $this->company->uuid)
|
|
22
|
+
->where('online', true)
|
|
23
|
+
->whereNotNull('current_job_uuid');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
protected function aggregate($query): int
|
|
27
|
+
{
|
|
28
|
+
return (int) $query->count();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Models\Transaction;
|
|
6
|
+
|
|
7
|
+
class EarningsMetric extends MoneyMetric
|
|
8
|
+
{
|
|
9
|
+
public static function slug(): string
|
|
10
|
+
{
|
|
11
|
+
return 'earnings';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
|
|
15
|
+
{
|
|
16
|
+
$query = Transaction::where('company_uuid', $this->company->uuid)
|
|
17
|
+
->where('currency', $this->currency());
|
|
18
|
+
|
|
19
|
+
if ($start && $end) {
|
|
20
|
+
$query->whereBetween('created_at', [$start, $end]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return $query;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
protected function aggregate($query): float
|
|
27
|
+
{
|
|
28
|
+
return (float) $query->sum('amount');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\FuelReport;
|
|
6
|
+
|
|
7
|
+
class FuelCostsMetric extends MoneyMetric
|
|
8
|
+
{
|
|
9
|
+
public static function slug(): string
|
|
10
|
+
{
|
|
11
|
+
return 'fuel_costs';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
|
|
15
|
+
{
|
|
16
|
+
$query = FuelReport::where('company_uuid', $this->company->uuid)
|
|
17
|
+
->where('currency', $this->currency());
|
|
18
|
+
|
|
19
|
+
if ($start && $end) {
|
|
20
|
+
$query->whereBetween('created_at', [$start, $end]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return $query;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
protected function aggregate($query): float
|
|
27
|
+
{
|
|
28
|
+
return (float) $query->sum('amount');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Base for money-valued metrics. Enforces currency awareness so we never repeat
|
|
7
|
+
* the legacy bug of summing mixed-currency rows.
|
|
8
|
+
*/
|
|
9
|
+
abstract class MoneyMetric extends AbstractMetric
|
|
10
|
+
{
|
|
11
|
+
protected ?string $currencyOverride = null;
|
|
12
|
+
|
|
13
|
+
public function inCurrency(?string $currency): static
|
|
14
|
+
{
|
|
15
|
+
$this->currencyOverride = $currency;
|
|
16
|
+
|
|
17
|
+
return $this;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public function format(): string
|
|
21
|
+
{
|
|
22
|
+
return 'money';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public function currency(): string
|
|
26
|
+
{
|
|
27
|
+
return $this->currencyOverride
|
|
28
|
+
?? ($this->company->currency ?? 'USD');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Issue;
|
|
6
|
+
|
|
7
|
+
class OpenIssuesMetric extends AbstractMetric
|
|
8
|
+
{
|
|
9
|
+
public static function slug(): string
|
|
10
|
+
{
|
|
11
|
+
return 'open_issues';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public function format(): string
|
|
15
|
+
{
|
|
16
|
+
return 'count';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
|
|
20
|
+
{
|
|
21
|
+
$query = Issue::where('company_uuid', $this->company->uuid)
|
|
22
|
+
->where('status', 'pending');
|
|
23
|
+
|
|
24
|
+
if ($start && $end) {
|
|
25
|
+
$query->whereBetween('created_at', [$start, $end]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return $query;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected function aggregate($query): int
|
|
32
|
+
{
|
|
33
|
+
return (int) $query->count();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
|
|
7
|
+
class OrdersCanceledMetric extends AbstractMetric
|
|
8
|
+
{
|
|
9
|
+
public static function slug(): string
|
|
10
|
+
{
|
|
11
|
+
return 'orders_canceled';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public function format(): string
|
|
15
|
+
{
|
|
16
|
+
return 'count';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
|
|
20
|
+
{
|
|
21
|
+
$query = Order::where('company_uuid', $this->company->uuid)
|
|
22
|
+
->where('status', 'canceled');
|
|
23
|
+
|
|
24
|
+
if ($start && $end) {
|
|
25
|
+
$query->whereBetween('created_at', [$start, $end]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return $query;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected function aggregate($query): int
|
|
32
|
+
{
|
|
33
|
+
return (int) $query->count();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
|
|
7
|
+
class OrdersCompletedMetric extends AbstractMetric
|
|
8
|
+
{
|
|
9
|
+
public static function slug(): string
|
|
10
|
+
{
|
|
11
|
+
return 'orders_completed';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public function format(): string
|
|
15
|
+
{
|
|
16
|
+
return 'count';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
|
|
20
|
+
{
|
|
21
|
+
$query = Order::where('company_uuid', $this->company->uuid)
|
|
22
|
+
->where('status', 'completed');
|
|
23
|
+
|
|
24
|
+
if ($start && $end) {
|
|
25
|
+
$query->whereBetween('created_at', [$start, $end]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return $query;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected function aggregate($query): int
|
|
32
|
+
{
|
|
33
|
+
return (int) $query->count();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Counts orders currently in flight. Uses an explicit allowlist of "active" statuses
|
|
9
|
+
* rather than the legacy exclusion list, which was brittle against new statuses.
|
|
10
|
+
*/
|
|
11
|
+
class OrdersInProgressMetric extends AbstractMetric
|
|
12
|
+
{
|
|
13
|
+
public const IN_PROGRESS_STATUSES = [
|
|
14
|
+
'dispatched',
|
|
15
|
+
'assigned',
|
|
16
|
+
'started',
|
|
17
|
+
'enroute',
|
|
18
|
+
'in_progress',
|
|
19
|
+
'driver_enroute',
|
|
20
|
+
'picking_up',
|
|
21
|
+
'picked_up',
|
|
22
|
+
'dropping_off',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
public static function slug(): string
|
|
26
|
+
{
|
|
27
|
+
return 'orders_in_progress';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public function format(): string
|
|
31
|
+
{
|
|
32
|
+
return 'count';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
|
|
36
|
+
{
|
|
37
|
+
$query = Order::where('company_uuid', $this->company->uuid)
|
|
38
|
+
->whereIn('status', self::IN_PROGRESS_STATUSES);
|
|
39
|
+
|
|
40
|
+
if ($start && $end) {
|
|
41
|
+
$query->whereBetween('created_at', [$start, $end]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return $query;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
protected function aggregate($query): int
|
|
48
|
+
{
|
|
49
|
+
return (int) $query->count();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
use Illuminate\Support\Carbon;
|
|
7
|
+
|
|
8
|
+
class OrdersScheduledMetric extends AbstractMetric
|
|
9
|
+
{
|
|
10
|
+
public static function slug(): string
|
|
11
|
+
{
|
|
12
|
+
return 'orders_scheduled';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public function format(): string
|
|
16
|
+
{
|
|
17
|
+
return 'count';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
|
|
21
|
+
{
|
|
22
|
+
$query = Order::where('company_uuid', $this->company->uuid)
|
|
23
|
+
->where('status', 'created')
|
|
24
|
+
->whereDate('scheduled_at', '>', Carbon::now());
|
|
25
|
+
|
|
26
|
+
if ($start && $end) {
|
|
27
|
+
$query->whereBetween('created_at', [$start, $end]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return $query;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected function aggregate($query): int
|
|
34
|
+
{
|
|
35
|
+
return (int) $query->count();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Explicit slug → metric-class map. Replaces the legacy
|
|
7
|
+
* `array_slice(get_class_methods, 9)` reflection which broke whenever a
|
|
8
|
+
* private helper was added to the wrong position in Metrics.php.
|
|
9
|
+
*/
|
|
10
|
+
class Registry
|
|
11
|
+
{
|
|
12
|
+
/** @var array<string, class-string<AbstractMetric>> */
|
|
13
|
+
public const METRICS = [
|
|
14
|
+
'earnings' => EarningsMetric::class,
|
|
15
|
+
'fuel_costs' => FuelCostsMetric::class,
|
|
16
|
+
'total_distance_traveled' => TotalDistanceTraveledMetric::class,
|
|
17
|
+
'total_time_traveled' => TotalTimeTraveledMetric::class,
|
|
18
|
+
'orders_completed' => OrdersCompletedMetric::class,
|
|
19
|
+
'orders_canceled' => OrdersCanceledMetric::class,
|
|
20
|
+
'orders_in_progress' => OrdersInProgressMetric::class,
|
|
21
|
+
'orders_scheduled' => OrdersScheduledMetric::class,
|
|
22
|
+
'active_live_orders' => ActiveLiveOrdersMetric::class,
|
|
23
|
+
'drivers_online' => DriversOnlineMetric::class,
|
|
24
|
+
'total_drivers' => TotalDriversMetric::class,
|
|
25
|
+
'total_customers' => TotalCustomersMetric::class,
|
|
26
|
+
'open_issues' => OpenIssuesMetric::class,
|
|
27
|
+
'resolved_issues' => ResolvedIssuesMetric::class,
|
|
28
|
+
'avg_order_value' => AvgOrderValueMetric::class,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
public static function resolve(string $slug): ?string
|
|
32
|
+
{
|
|
33
|
+
return self::METRICS[$slug] ?? null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** @return array<string, class-string<AbstractMetric>> */
|
|
37
|
+
public static function all(): array
|
|
38
|
+
{
|
|
39
|
+
return self::METRICS;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static function slugs(): array
|
|
43
|
+
{
|
|
44
|
+
return array_keys(self::METRICS);
|
|
45
|
+
}
|
|
46
|
+
}
|