@fleetbase/fleetops-engine 0.6.55 → 0.6.57
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/map-settings.hbs +1 -1
- package/addon/components/ai/create-order-preview.hbs +297 -0
- package/addon/components/ai/create-order-preview.js +362 -0
- package/addon/components/ai/route-preview-map.hbs +79 -0
- package/addon/components/ai/route-preview-map.js +527 -0
- package/addon/components/device/details.js +1 -1
- package/addon/components/device-event/details.hbs +192 -0
- package/addon/components/device-event/details.js +151 -0
- package/addon/components/driver/schedule.js +1 -1
- package/addon/components/fleet/details.hbs +21 -8
- package/addon/components/fleet/details.js +55 -1
- package/addon/components/fleet/hierarchy-tree.hbs +137 -0
- package/addon/components/fleet/hierarchy-tree.js +528 -0
- package/addon/components/layout/fleet-ops-sidebar.js +15 -9
- package/addon/components/map/drawer/device-event-listing.js +1 -0
- package/addon/components/map/drawer/driver-listing.js +1 -0
- package/addon/components/map/drawer/place-listing.js +1 -0
- package/addon/components/map/drawer/vehicle-listing.js +1 -0
- package/addon/components/order/details.hbs +34 -1
- package/addon/components/order/form/details.hbs +4 -0
- package/addon/components/order/form.hbs +9 -1
- package/addon/components/telematic/details.hbs +1 -1
- package/addon/components/telematic/form.hbs +2 -2
- package/addon/components/telematic/settings.hbs +1 -1
- package/addon/components/vehicle/card.hbs +14 -2
- package/addon/components/vehicle/form.hbs +13 -1
- package/addon/controllers/connectivity/devices/index/details/events.js +1 -1
- package/addon/controllers/connectivity/devices/index/details/vehicle.js +2 -2
- package/addon/controllers/connectivity/events/details.js +68 -1
- package/addon/controllers/connectivity/sensors/index.js +4 -0
- package/addon/controllers/connectivity/telematics/details/events.js +1 -1
- package/addon/controllers/connectivity/telematics/details/sensors.js +4 -0
- package/addon/controllers/settings/map.js +19 -0
- package/addon/extension.js +14 -0
- package/addon/instance-initializers/register-vroom-allocation.js +1 -1
- package/addon/routes/connectivity/events/details.js +26 -1
- package/addon/routes/management/fleets/index/details.js +1 -1
- package/addon/routes/management/vehicles/index/details.js +3 -1
- package/addon/services/map-adapter/google.js +66 -1
- package/addon/services/map-adapter/leaflet.js +1 -1
- package/addon/services/map-adapter-interface.js +1 -1
- package/addon/services/map-manager.js +16 -1
- package/addon/services/map-settings.js +15 -0
- package/addon/styles/fleetops-engine.css +487 -1
- package/addon/templates/connectivity/devices/index/details/vehicle.hbs +2 -1
- package/addon/templates/connectivity/events/details.hbs +40 -1
- package/addon/templates/settings/map.hbs +38 -9
- package/addon/utils/leaflet-plugin-loader.js +5 -5
- package/addon/utils/map-drawer-dropdown-position.js +11 -13
- package/addon/utils/route-colors.js +1 -1
- package/app/components/ai/create-order-preview.js +1 -0
- package/app/components/ai/route-preview-map.js +1 -0
- package/app/components/device-event/details.hbs +192 -0
- package/app/components/device-event/details.js +1 -0
- package/app/components/fleet/hierarchy-tree.js +1 -0
- package/app/services/route-engine.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +6 -4
- package/scripts/normalize-rgba-alpha.js +90 -0
- package/server/migrations/2026_06_23_000001_delete_bad_afaqy_telematics_sensors.php +34 -0
- package/server/src/Exports/ContactExport.php +12 -5
- package/server/src/Exports/DeviceExport.php +92 -0
- package/server/src/Exports/DriverExport.php +28 -3
- package/server/src/Exports/EquipmentExport.php +94 -0
- package/server/src/Exports/FleetExport.php +22 -7
- package/server/src/Exports/FuelReportExport.php +16 -1
- package/server/src/Exports/IssueExport.php +25 -3
- package/server/src/Exports/MaintenanceExport.php +106 -0
- package/server/src/Exports/MaintenanceScheduleExport.php +99 -0
- package/server/src/Exports/OrderExport.php +24 -5
- package/server/src/Exports/PartExport.php +99 -0
- package/server/src/Exports/PlaceExport.php +33 -7
- package/server/src/Exports/SensorExport.php +94 -0
- package/server/src/Exports/ServiceAreaExport.php +31 -7
- package/server/src/Exports/ServiceRateExport.php +41 -1
- package/server/src/Exports/TelematicExport.php +90 -0
- package/server/src/Exports/VehicleExport.php +274 -8
- package/server/src/Exports/VendorExport.php +15 -2
- package/server/src/Exports/WorkOrderExport.php +92 -0
- package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php +176 -0
- package/server/src/Http/Controllers/Api/v1/DeviceController.php +225 -0
- package/server/src/Http/Controllers/Api/v1/EquipmentController.php +114 -0
- package/server/src/Http/Controllers/Api/v1/FuelTransactionController.php +185 -0
- package/server/src/Http/Controllers/Api/v1/PartController.php +120 -0
- package/server/src/Http/Controllers/Api/v1/SensorController.php +132 -0
- package/server/src/Http/Controllers/Api/v1/WorkOrderController.php +161 -0
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +18 -0
- package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/PartController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/SensorController.php +18 -0
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +29 -5
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +18 -0
- package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +17 -0
- package/server/src/Http/Filter/DeviceFilter.php +33 -10
- package/server/src/Http/Filter/EquipmentFilter.php +23 -0
- package/server/src/Http/Filter/FuelProviderTransactionFilter.php +57 -2
- package/server/src/Http/Filter/PartFilter.php +47 -0
- package/server/src/Http/Filter/SensorFilter.php +34 -6
- package/server/src/Http/Filter/WorkOrderFilter.php +23 -0
- package/server/src/Http/Requests/CreateDeviceRequest.php +51 -0
- package/server/src/Http/Requests/CreateEquipmentRequest.php +35 -0
- package/server/src/Http/Requests/CreateFuelTransactionRequest.php +50 -0
- package/server/src/Http/Requests/CreatePartRequest.php +40 -0
- package/server/src/Http/Requests/CreateSensorRequest.php +47 -0
- package/server/src/Http/Requests/CreateWorkOrderRequest.php +42 -0
- package/server/src/Http/Requests/UpdateDeviceRequest.php +7 -0
- package/server/src/Http/Requests/UpdateEquipmentRequest.php +7 -0
- package/server/src/Http/Requests/UpdateFuelTransactionRequest.php +7 -0
- package/server/src/Http/Requests/UpdatePartRequest.php +7 -0
- package/server/src/Http/Requests/UpdateSensorRequest.php +7 -0
- package/server/src/Http/Requests/UpdateWorkOrderRequest.php +7 -0
- package/server/src/Http/Resources/v1/Device.php +72 -0
- package/server/src/Http/Resources/v1/Equipment.php +57 -0
- package/server/src/Http/Resources/v1/Fleet.php +8 -2
- package/server/src/Http/Resources/v1/FuelTransaction.php +69 -0
- package/server/src/Http/Resources/v1/Part.php +64 -0
- package/server/src/Http/Resources/v1/Sensor.php +68 -0
- package/server/src/Jobs/SyncTelematicDevicesJob.php +194 -38
- package/server/src/Models/Driver.php +6 -0
- package/server/src/Models/Order.php +20 -7
- package/server/src/Models/Place.php +11 -3
- package/server/src/Observers/PurchaseRateObserver.php +2 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +26 -0
- package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +88 -0
- package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +108 -0
- package/server/src/Support/Ai/Capabilities/ConsoleNavigationCapability.php +55 -0
- package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +508 -0
- package/server/src/Support/Ai/Capabilities/DocsHelpCapability.php +49 -0
- package/server/src/Support/Ai/Capabilities/ImportOrdersPreviewCapability.php +66 -0
- package/server/src/Support/Ai/Capabilities/OperationalQueryCapability.php +319 -0
- package/server/src/Support/Ai/Capabilities/OptimizeOrderRouteCapability.php +240 -0
- package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +95 -0
- package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +195 -0
- package/server/src/Support/Ai/FleetOpsAiQueryResources.php +150 -0
- package/server/src/Support/Analytics/RevenueTrend.php +3 -8
- package/server/src/Support/Metrics/ActiveRevenueQuery.php +121 -0
- package/server/src/Support/Metrics/AvgOrderValueMetric.php +1 -7
- package/server/src/Support/Metrics/EarningsMetric.php +1 -10
- package/server/src/Support/Telematics/Providers/AfaqyProvider.php +104 -5
- package/server/src/Support/Telematics/Providers/SafeeProvider.php +550 -54
- package/server/src/Support/Telematics/TelematicService.php +28 -9
- package/server/src/Support/Utils.php +52 -0
- package/server/src/routes.php +67 -14
- package/server/tests/AiCreateOrderPreviewCapabilityTest.php +131 -0
- package/server/tests/AiOperationalQueryCapabilityTest.php +75 -0
- package/server/tests/ExportCoverageTest.php +79 -0
- package/server/tests/FleetHierarchyResourceTest.php +12 -0
- package/server/tests/MetricsRegistryTest.php +45 -5
- package/server/tests/OrderPayloadPlaceResolutionTest.php +53 -0
- package/server/tests/PointResolutionTest.php +63 -0
- package/server/tests/PublicFleetOpsRestEndpointsTest.php +191 -0
- package/server/tests/TelematicsHardeningTest.php +636 -1
- package/addon/components/table/cell/dropdown/action-item.hbs +0 -31
- package/addon/components/table/cell/dropdown/action-item.js +0 -62
- package/addon/components/table/cell/dropdown.hbs +0 -28
- package/addon/components/table/cell/dropdown.js +0 -91
- package/app/components/table/cell/dropdown/action-item.js +0 -1
- package/app/components/table/cell/dropdown.js +0 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Ai\Capabilities;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Ai\Models\AiTask;
|
|
6
|
+
use Fleetbase\Ai\Support\AiRelativeDateResolver;
|
|
7
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
8
|
+
|
|
9
|
+
class OrderInsightsCapability extends AbstractFleetOpsAICapability
|
|
10
|
+
{
|
|
11
|
+
public function key(): string
|
|
12
|
+
{
|
|
13
|
+
return 'fleet-ops.order_insights';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public function label(): string
|
|
17
|
+
{
|
|
18
|
+
return 'Fleet-Ops order insights';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public function description(): string
|
|
22
|
+
{
|
|
23
|
+
return 'Answers bounded aggregate questions about Fleet-Ops orders, statuses, and order values.';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public function permissions(): array
|
|
27
|
+
{
|
|
28
|
+
return ['fleet-ops see order'];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public function resolve(AiTask $task): array
|
|
32
|
+
{
|
|
33
|
+
if (!$this->can('fleet-ops see order')) {
|
|
34
|
+
return ['authorized' => false, 'message' => 'Current user cannot access Fleet-Ops orders.'];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
$prompt = $this->prompt($task);
|
|
38
|
+
$window = $this->dateWindow($prompt);
|
|
39
|
+
$amount = $this->amountThreshold($prompt);
|
|
40
|
+
$query = Order::where('company_uuid', session('company'));
|
|
41
|
+
|
|
42
|
+
if ($window) {
|
|
43
|
+
$query->whereBetween('created_at', [$window['start'], $window['end']]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if ($amount !== null) {
|
|
47
|
+
$query->whereHas('transaction', fn ($transaction) => $transaction->where('amount', '>', $amount));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
$total = (clone $query)->count();
|
|
51
|
+
|
|
52
|
+
return [
|
|
53
|
+
'authorized' => true,
|
|
54
|
+
'metric' => 'orders',
|
|
55
|
+
'date_window' => $window ? [
|
|
56
|
+
'label' => $window['label'],
|
|
57
|
+
'timezone' => $window['timezone'],
|
|
58
|
+
'start' => $window['start']->toIso8601String(),
|
|
59
|
+
'end' => $window['end']->toIso8601String(),
|
|
60
|
+
] : null,
|
|
61
|
+
'amount_threshold' => $amount,
|
|
62
|
+
'count' => $total,
|
|
63
|
+
'counts_by_status' => (clone $query)
|
|
64
|
+
->selectRaw('status, count(*) as aggregate')
|
|
65
|
+
->groupBy('status')
|
|
66
|
+
->pluck('aggregate', 'status')
|
|
67
|
+
->all(),
|
|
68
|
+
'sample_order_ids' => (clone $query)->latest()->limit(10)->pluck('public_id')->filter()->values()->all(),
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected function matchesPrompt(string $prompt): bool
|
|
73
|
+
{
|
|
74
|
+
return str_contains($prompt, 'order') && $this->containsAny($prompt, ['how many', 'count', 'report', 'value', 'over', 'status', 'today', 'yesterday', 'tomorrow', 'last week', 'this week', 'next week', 'last month', 'this month', 'last 30 days', 'completed', 'active']);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
protected function amountThreshold(string $prompt): ?float
|
|
78
|
+
{
|
|
79
|
+
if (preg_match('/(?:over|greater than|above)\s+\$?([0-9]+(?:\.[0-9]+)?)/', $prompt, $matches)) {
|
|
80
|
+
return (float) $matches[1];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
protected function dateWindow(string $prompt): ?array
|
|
87
|
+
{
|
|
88
|
+
return $this->relativeDateResolver()->resolveWindow($prompt);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
protected function relativeDateResolver(): AiRelativeDateResolver
|
|
92
|
+
{
|
|
93
|
+
return function_exists('app') ? app(AiRelativeDateResolver::class) : new AiRelativeDateResolver(null);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Ai\Capabilities;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Ai\Models\AiTask;
|
|
6
|
+
use Fleetbase\FleetOps\Models\Device;
|
|
7
|
+
use Fleetbase\FleetOps\Models\Driver;
|
|
8
|
+
use Fleetbase\FleetOps\Models\Maintenance;
|
|
9
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
10
|
+
use Fleetbase\FleetOps\Models\Sensor;
|
|
11
|
+
use Fleetbase\FleetOps\Models\Telematic;
|
|
12
|
+
use Fleetbase\FleetOps\Models\Vehicle;
|
|
13
|
+
use Fleetbase\FleetOps\Models\WorkOrder;
|
|
14
|
+
|
|
15
|
+
class SearchResourcesCapability extends AbstractFleetOpsAICapability
|
|
16
|
+
{
|
|
17
|
+
public function key(): string
|
|
18
|
+
{
|
|
19
|
+
return 'fleet-ops.search_resources';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public function label(): string
|
|
23
|
+
{
|
|
24
|
+
return 'Search Fleet-Ops resources';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public function description(): string
|
|
28
|
+
{
|
|
29
|
+
return 'Finds relevant Fleet-Ops orders, vehicles, drivers, work orders, maintenances, devices, sensors, and telematics.';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public function permissions(): array
|
|
33
|
+
{
|
|
34
|
+
return [
|
|
35
|
+
'fleet-ops see order',
|
|
36
|
+
'fleet-ops see vehicle',
|
|
37
|
+
'fleet-ops see driver',
|
|
38
|
+
'fleet-ops see work-order',
|
|
39
|
+
'fleet-ops see maintenance',
|
|
40
|
+
'fleet-ops see device',
|
|
41
|
+
'fleet-ops see sensor',
|
|
42
|
+
'fleet-ops see telematic',
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public function resolve(AiTask $task): array
|
|
47
|
+
{
|
|
48
|
+
$prompt = (string) $task->prompt;
|
|
49
|
+
$terms = $this->searchTerms($prompt);
|
|
50
|
+
|
|
51
|
+
return [
|
|
52
|
+
'query_terms' => $terms,
|
|
53
|
+
'results' => array_filter([
|
|
54
|
+
'orders' => $this->orders($terms),
|
|
55
|
+
'vehicles' => $this->vehicles($terms),
|
|
56
|
+
'drivers' => $this->drivers($terms),
|
|
57
|
+
'work_orders' => $this->workOrders($terms),
|
|
58
|
+
'maintenances' => $this->maintenances($terms),
|
|
59
|
+
'devices' => $this->devices($terms),
|
|
60
|
+
'sensors' => $this->sensors($terms),
|
|
61
|
+
'telematics' => $this->telematics($terms),
|
|
62
|
+
]),
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
protected function matchesPrompt(string $prompt): bool
|
|
67
|
+
{
|
|
68
|
+
return $this->containsAny($prompt, ['find', 'show', 'open', 'look up', 'tell me about', 'status of', 'order', 'vehicle', 'driver', 'work order', 'maintenance', 'device', 'sensor', 'telematic']);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
protected function orders(array $terms): array
|
|
72
|
+
{
|
|
73
|
+
if (!$this->can('fleet-ops see order')) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return Order::with(['transaction', 'trackingNumber'])
|
|
78
|
+
->where('company_uuid', session('company'))
|
|
79
|
+
->where(function ($query) use ($terms) {
|
|
80
|
+
$this->whereLikeAny($query, ['public_id', 'internal_id', 'uuid', 'status', 'type'], $terms);
|
|
81
|
+
$query->orWhereHas('trackingNumber', fn ($tracking) => $this->whereLikeAny($tracking, ['tracking_number', 'barcode'], $terms));
|
|
82
|
+
})
|
|
83
|
+
->limit(5)
|
|
84
|
+
->get()
|
|
85
|
+
->map(fn (Order $order) => [
|
|
86
|
+
'id' => $order->public_id,
|
|
87
|
+
'uuid' => $order->uuid,
|
|
88
|
+
'tracking' => $order->tracking,
|
|
89
|
+
'status' => $order->status,
|
|
90
|
+
'type' => $order->type,
|
|
91
|
+
'transaction_amount' => $order->transaction_amount,
|
|
92
|
+
'transaction_currency' => $order->transaction_currency,
|
|
93
|
+
'route' => 'console.fleet-ops.operations.orders.index.details',
|
|
94
|
+
'models' => [$order->public_id ?: $order->uuid],
|
|
95
|
+
])
|
|
96
|
+
->values()
|
|
97
|
+
->all();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
protected function vehicles(array $terms): array
|
|
101
|
+
{
|
|
102
|
+
if (!$this->can('fleet-ops see vehicle')) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return Vehicle::where('company_uuid', session('company'))
|
|
107
|
+
->where(fn ($query) => $this->whereLikeAny($query, ['name', 'make', 'model', 'plate_number', 'vin', 'public_id', 'internal_id'], $terms))
|
|
108
|
+
->limit(5)
|
|
109
|
+
->get()
|
|
110
|
+
->map(fn (Vehicle $vehicle) => [
|
|
111
|
+
'id' => $vehicle->public_id,
|
|
112
|
+
'uuid' => $vehicle->uuid,
|
|
113
|
+
'name' => $vehicle->display_name ?: $vehicle->name,
|
|
114
|
+
'plate' => $vehicle->plate_number,
|
|
115
|
+
'status' => $vehicle->status,
|
|
116
|
+
'route' => 'console.fleet-ops.management.vehicles.index.details',
|
|
117
|
+
'models' => [$vehicle->public_id ?: $vehicle->uuid],
|
|
118
|
+
])
|
|
119
|
+
->values()
|
|
120
|
+
->all();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
protected function drivers(array $terms): array
|
|
124
|
+
{
|
|
125
|
+
if (!$this->can('fleet-ops see driver')) {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return Driver::with('user')
|
|
130
|
+
->where('company_uuid', session('company'))
|
|
131
|
+
->where(function ($query) use ($terms) {
|
|
132
|
+
$this->whereLikeAny($query, ['public_id', 'uuid', 'drivers_license_number', 'status'], $terms);
|
|
133
|
+
$query->orWhereHas('user', fn ($user) => $this->whereLikeAny($user, ['name', 'email', 'phone'], $terms));
|
|
134
|
+
})
|
|
135
|
+
->limit(5)
|
|
136
|
+
->get()
|
|
137
|
+
->map(fn (Driver $driver) => [
|
|
138
|
+
'id' => $driver->public_id,
|
|
139
|
+
'uuid' => $driver->uuid,
|
|
140
|
+
'name' => data_get($driver, 'user.name'),
|
|
141
|
+
'status' => $driver->status,
|
|
142
|
+
'route' => 'console.fleet-ops.management.drivers.index.details',
|
|
143
|
+
'models' => [$driver->public_id ?: $driver->uuid],
|
|
144
|
+
])
|
|
145
|
+
->values()
|
|
146
|
+
->all();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
protected function workOrders(array $terms): array
|
|
150
|
+
{
|
|
151
|
+
return $this->generic(WorkOrder::class, 'fleet-ops see work-order', ['public_id', 'uuid', 'code', 'subject', 'status', 'priority'], 'console.fleet-ops.maintenance.work-orders.index.details', $terms);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
protected function maintenances(array $terms): array
|
|
155
|
+
{
|
|
156
|
+
return $this->generic(Maintenance::class, 'fleet-ops see maintenance', ['public_id', 'uuid', 'status', 'type', 'summary', 'notes'], 'console.fleet-ops.maintenance.maintenances.index.details', $terms);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
protected function devices(array $terms): array
|
|
160
|
+
{
|
|
161
|
+
return $this->generic(Device::class, 'fleet-ops see device', ['public_id', 'uuid', 'name', 'device_id', 'imei', 'serial_number', 'status'], 'console.fleet-ops.connectivity.devices.index.details', $terms);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
protected function sensors(array $terms): array
|
|
165
|
+
{
|
|
166
|
+
return $this->generic(Sensor::class, 'fleet-ops see sensor', ['public_id', 'uuid', 'name', 'internal_id', 'serial_number', 'imei', 'type', 'sensor_type', 'status'], 'console.fleet-ops.connectivity.sensors.index.details', $terms);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
protected function telematics(array $terms): array
|
|
170
|
+
{
|
|
171
|
+
return $this->generic(Telematic::class, 'fleet-ops see telematic', ['public_id', 'uuid', 'name', 'provider', 'status'], 'console.fleet-ops.connectivity.telematics.details', $terms);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
protected function generic(string $modelClass, string $permission, array $columns, string $route, array $terms): array
|
|
175
|
+
{
|
|
176
|
+
if (!$this->can($permission)) {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return $modelClass::where('company_uuid', session('company'))
|
|
181
|
+
->where(fn ($query) => $this->whereLikeAny($query, $columns, $terms))
|
|
182
|
+
->limit(5)
|
|
183
|
+
->get()
|
|
184
|
+
->map(fn ($record) => [
|
|
185
|
+
'id' => $record->public_id,
|
|
186
|
+
'uuid' => $record->uuid,
|
|
187
|
+
'name' => $record->name ?? $record->subject ?? $record->summary ?? $record->description ?? null,
|
|
188
|
+
'status' => $record->status ?? null,
|
|
189
|
+
'route' => $route,
|
|
190
|
+
'models' => [$record->public_id ?: $record->uuid],
|
|
191
|
+
])
|
|
192
|
+
->values()
|
|
193
|
+
->all();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Ai;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Ai\Support\AiQueryableResource;
|
|
6
|
+
use Fleetbase\Ai\Support\AiQueryRegistry;
|
|
7
|
+
use Fleetbase\FleetOps\Models\Device;
|
|
8
|
+
use Fleetbase\FleetOps\Models\Driver;
|
|
9
|
+
use Fleetbase\FleetOps\Models\Fleet;
|
|
10
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
11
|
+
use Fleetbase\FleetOps\Models\ServiceArea;
|
|
12
|
+
use Fleetbase\FleetOps\Models\Vehicle;
|
|
13
|
+
use Fleetbase\FleetOps\Models\Zone;
|
|
14
|
+
|
|
15
|
+
class FleetOpsAiQueryResources
|
|
16
|
+
{
|
|
17
|
+
public static function register(AiQueryRegistry $registry): void
|
|
18
|
+
{
|
|
19
|
+
$registry->register(new AiQueryableResource(
|
|
20
|
+
key: 'fleet-ops.drivers',
|
|
21
|
+
label: 'Fleet-Ops drivers',
|
|
22
|
+
module: 'fleet-ops',
|
|
23
|
+
modelClass: Driver::class,
|
|
24
|
+
permission: 'fleet-ops see driver',
|
|
25
|
+
aliases: ['drivers', 'driver'],
|
|
26
|
+
fields: [
|
|
27
|
+
'online' => ['column' => 'online', 'type' => 'boolean'],
|
|
28
|
+
'status' => ['column' => 'status', 'type' => 'string'],
|
|
29
|
+
'current_status' => ['column' => 'current_status', 'type' => 'string'],
|
|
30
|
+
'city' => ['column' => 'city', 'type' => 'string'],
|
|
31
|
+
'country' => ['column' => 'country', 'type' => 'string'],
|
|
32
|
+
'vehicle_uuid' => ['column' => 'vehicle_uuid', 'type' => 'uuid'],
|
|
33
|
+
'updated_at' => ['column' => 'updated_at', 'type' => 'datetime'],
|
|
34
|
+
'created_at' => ['column' => 'created_at', 'type' => 'datetime'],
|
|
35
|
+
],
|
|
36
|
+
sampleFields: ['public_id', 'name', 'status', 'current_status', 'online', 'city', 'country', 'vehicle_uuid'],
|
|
37
|
+
locationField: 'location',
|
|
38
|
+
directivePermission: 'fleet-ops list driver',
|
|
39
|
+
maxLimit: 500,
|
|
40
|
+
));
|
|
41
|
+
|
|
42
|
+
$registry->register(new AiQueryableResource(
|
|
43
|
+
key: 'fleet-ops.vehicles',
|
|
44
|
+
label: 'Fleet-Ops vehicles',
|
|
45
|
+
module: 'fleet-ops',
|
|
46
|
+
modelClass: Vehicle::class,
|
|
47
|
+
permission: 'fleet-ops see vehicle',
|
|
48
|
+
aliases: ['vehicles', 'vehicle'],
|
|
49
|
+
fields: [
|
|
50
|
+
'online' => ['column' => 'online', 'type' => 'boolean'],
|
|
51
|
+
'status' => ['column' => 'status', 'type' => 'string'],
|
|
52
|
+
'driver_uuid' => ['column' => 'driver_uuid', 'type' => 'uuid'],
|
|
53
|
+
'city' => ['column' => 'city', 'type' => 'string'],
|
|
54
|
+
'country' => ['column' => 'country', 'type' => 'string'],
|
|
55
|
+
'updated_at' => ['column' => 'updated_at', 'type' => 'datetime'],
|
|
56
|
+
'created_at' => ['column' => 'created_at', 'type' => 'datetime'],
|
|
57
|
+
],
|
|
58
|
+
sampleFields: ['public_id', 'display_name', 'status', 'online', 'city', 'country', 'driver_uuid'],
|
|
59
|
+
locationField: 'location',
|
|
60
|
+
directivePermission: 'fleet-ops list vehicle',
|
|
61
|
+
maxLimit: 500,
|
|
62
|
+
));
|
|
63
|
+
|
|
64
|
+
$registry->register(new AiQueryableResource(
|
|
65
|
+
key: 'fleet-ops.devices',
|
|
66
|
+
label: 'Fleet-Ops devices',
|
|
67
|
+
module: 'fleet-ops',
|
|
68
|
+
modelClass: Device::class,
|
|
69
|
+
permission: 'fleet-ops see device',
|
|
70
|
+
aliases: ['devices', 'device'],
|
|
71
|
+
fields: [
|
|
72
|
+
'online' => ['column' => 'online', 'type' => 'boolean'],
|
|
73
|
+
'status' => ['column' => 'status', 'type' => 'string'],
|
|
74
|
+
'provider' => ['column' => 'provider', 'type' => 'string'],
|
|
75
|
+
'telematic_uuid' => ['column' => 'telematic_uuid', 'type' => 'uuid'],
|
|
76
|
+
'attachable_uuid' => ['column' => 'attachable_uuid', 'type' => 'uuid'],
|
|
77
|
+
'last_online_at' => ['column' => 'last_online_at', 'type' => 'datetime'],
|
|
78
|
+
'updated_at' => ['column' => 'updated_at', 'type' => 'datetime'],
|
|
79
|
+
],
|
|
80
|
+
sampleFields: ['public_id', 'name', 'status', 'online', 'provider', 'telematic_uuid', 'attachable_uuid'],
|
|
81
|
+
directivePermission: 'fleet-ops list device',
|
|
82
|
+
maxLimit: 500,
|
|
83
|
+
));
|
|
84
|
+
|
|
85
|
+
$registry->register(new AiQueryableResource(
|
|
86
|
+
key: 'fleet-ops.orders',
|
|
87
|
+
label: 'Fleet-Ops orders',
|
|
88
|
+
module: 'fleet-ops',
|
|
89
|
+
modelClass: Order::class,
|
|
90
|
+
permission: 'fleet-ops see order',
|
|
91
|
+
aliases: ['orders', 'order'],
|
|
92
|
+
fields: [
|
|
93
|
+
'status' => ['column' => 'status', 'type' => 'string'],
|
|
94
|
+
'type' => ['column' => 'type', 'type' => 'string'],
|
|
95
|
+
'driver_assigned_uuid' => ['column' => 'driver_assigned_uuid', 'type' => 'uuid'],
|
|
96
|
+
'vehicle_assigned_uuid' => ['column' => 'vehicle_assigned_uuid', 'type' => 'uuid'],
|
|
97
|
+
'created_at' => ['column' => 'created_at', 'type' => 'datetime'],
|
|
98
|
+
'scheduled_at' => ['column' => 'scheduled_at', 'type' => 'datetime'],
|
|
99
|
+
],
|
|
100
|
+
sampleFields: ['public_id', 'status', 'type', 'driver_assigned_uuid', 'vehicle_assigned_uuid', 'created_at'],
|
|
101
|
+
directivePermission: 'fleet-ops list order',
|
|
102
|
+
maxLimit: 500,
|
|
103
|
+
));
|
|
104
|
+
|
|
105
|
+
$registry->register(new AiQueryableResource(
|
|
106
|
+
key: 'fleet-ops.fleets',
|
|
107
|
+
label: 'Fleet-Ops fleets',
|
|
108
|
+
module: 'fleet-ops',
|
|
109
|
+
modelClass: Fleet::class,
|
|
110
|
+
permission: 'fleet-ops see fleet',
|
|
111
|
+
aliases: ['fleets', 'fleet'],
|
|
112
|
+
fields: [
|
|
113
|
+
'status' => ['column' => 'status', 'type' => 'string'],
|
|
114
|
+
'task' => ['column' => 'task', 'type' => 'string'],
|
|
115
|
+
'updated_at' => ['column' => 'updated_at', 'type' => 'datetime'],
|
|
116
|
+
],
|
|
117
|
+
sampleFields: ['public_id', 'name', 'status', 'task'],
|
|
118
|
+
directivePermission: 'fleet-ops list fleet',
|
|
119
|
+
));
|
|
120
|
+
|
|
121
|
+
$registry->register(new AiQueryableResource(
|
|
122
|
+
key: 'fleet-ops.service_areas',
|
|
123
|
+
label: 'Fleet-Ops service areas',
|
|
124
|
+
module: 'fleet-ops',
|
|
125
|
+
modelClass: ServiceArea::class,
|
|
126
|
+
permission: 'fleet-ops see service-area',
|
|
127
|
+
aliases: ['service areas', 'service area', 'service_areas'],
|
|
128
|
+
fields: [
|
|
129
|
+
'status' => ['column' => 'status', 'type' => 'string'],
|
|
130
|
+
'country' => ['column' => 'country', 'type' => 'string'],
|
|
131
|
+
'type' => ['column' => 'type', 'type' => 'string'],
|
|
132
|
+
],
|
|
133
|
+
sampleFields: ['public_id', 'name', 'status', 'country', 'type'],
|
|
134
|
+
));
|
|
135
|
+
|
|
136
|
+
$registry->register(new AiQueryableResource(
|
|
137
|
+
key: 'fleet-ops.zones',
|
|
138
|
+
label: 'Fleet-Ops zones',
|
|
139
|
+
module: 'fleet-ops',
|
|
140
|
+
modelClass: Zone::class,
|
|
141
|
+
permission: 'fleet-ops see zone',
|
|
142
|
+
aliases: ['zones', 'zone'],
|
|
143
|
+
fields: [
|
|
144
|
+
'status' => ['column' => 'status', 'type' => 'string'],
|
|
145
|
+
'service_area_uuid' => ['column' => 'service_area_uuid', 'type' => 'uuid'],
|
|
146
|
+
],
|
|
147
|
+
sampleFields: ['public_id', 'name', 'status', 'service_area_uuid'],
|
|
148
|
+
));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Support\Analytics;
|
|
4
4
|
|
|
5
|
-
use Fleetbase\
|
|
5
|
+
use Fleetbase\FleetOps\Support\Metrics\ActiveRevenueQuery;
|
|
6
6
|
use Illuminate\Support\Carbon;
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -32,9 +32,7 @@ class RevenueTrend extends AbstractAnalytics
|
|
|
32
32
|
default => '%Y-%m-%d',
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
$rows =
|
|
36
|
-
->where('currency', $currency)
|
|
37
|
-
->whereBetween('created_at', [$start, $end])
|
|
35
|
+
$rows = ActiveRevenueQuery::forCompany($this->company, $currency, $start, $end)
|
|
38
36
|
->selectRaw('DATE_FORMAT(created_at, ?) as bucket, SUM(amount) as total', [$format])
|
|
39
37
|
->groupBy('bucket')
|
|
40
38
|
->orderBy('bucket')
|
|
@@ -47,10 +45,7 @@ class RevenueTrend extends AbstractAnalytics
|
|
|
47
45
|
$duration = $end->getTimestamp() - $start->getTimestamp();
|
|
48
46
|
$compareStart = (clone $start);
|
|
49
47
|
$compareStart = (new \DateTime())->setTimestamp($start->getTimestamp() - $duration);
|
|
50
|
-
$previousTotal = (float)
|
|
51
|
-
->where('currency', $currency)
|
|
52
|
-
->whereBetween('created_at', [$compareStart, $start])
|
|
53
|
-
->sum('amount');
|
|
48
|
+
$previousTotal = (float) ActiveRevenueQuery::forCompany($this->company, $currency, $compareStart, $start)->sum('amount');
|
|
54
49
|
|
|
55
50
|
$deltaPct = $previousTotal > 0
|
|
56
51
|
? round((($total - $previousTotal) / $previousTotal) * 100, 1)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
+
use Fleetbase\Models\Company;
|
|
7
|
+
use Fleetbase\Models\Transaction;
|
|
8
|
+
use Illuminate\Database\Eloquent\Builder;
|
|
9
|
+
use Illuminate\Support\Facades\Schema;
|
|
10
|
+
|
|
11
|
+
class ActiveRevenueQuery
|
|
12
|
+
{
|
|
13
|
+
public const ACTIVE_STATUSES = [Transaction::STATUS_SUCCESS];
|
|
14
|
+
public const INACTIVE_TRANSACTION_STATUSES = ['pending', 'failed', 'cancelled', 'canceled', 'void', 'voided', 'expired', 'reversed', 'refunded', 'ignored'];
|
|
15
|
+
public const INACTIVE_ORDER_STATUSES = ['canceled', 'cancelled', 'order_canceled'];
|
|
16
|
+
public const INACTIVE_INVOICE_STATUSES = ['void', 'voided', 'cancelled', 'canceled'];
|
|
17
|
+
|
|
18
|
+
public static function forCompany(Company $company, string $currency, ?\DateTimeInterface $start = null, ?\DateTimeInterface $end = null): Builder
|
|
19
|
+
{
|
|
20
|
+
$query = Transaction::query()
|
|
21
|
+
->where('company_uuid', $company->uuid)
|
|
22
|
+
->where('currency', $currency)
|
|
23
|
+
->where('direction', Transaction::DIRECTION_CREDIT)
|
|
24
|
+
->whereIn('status', self::ACTIVE_STATUSES)
|
|
25
|
+
->whereNotIn('status', self::INACTIVE_TRANSACTION_STATUSES)
|
|
26
|
+
->whereNull('deleted_at')
|
|
27
|
+
->whereNull('voided_at')
|
|
28
|
+
->whereNull('reversed_at')
|
|
29
|
+
->whereNull('parent_transaction_uuid');
|
|
30
|
+
|
|
31
|
+
if ($start && $end) {
|
|
32
|
+
$query->whereBetween('created_at', [$start, $end]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static::excludeInactiveOrders($query);
|
|
36
|
+
static::excludeInactiveInvoices($query);
|
|
37
|
+
|
|
38
|
+
return $query;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
protected static function excludeInactiveOrders(Builder $query): void
|
|
42
|
+
{
|
|
43
|
+
if (!Schema::hasTable('orders')) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
foreach (['subject_uuid' => 'subject_type', 'context_uuid' => 'context_type'] as $uuidColumn => $typeColumn) {
|
|
48
|
+
$query->whereNotExists(function ($subQuery) use ($uuidColumn, $typeColumn) {
|
|
49
|
+
$subQuery->selectRaw('1')
|
|
50
|
+
->from('orders')
|
|
51
|
+
->whereColumn('orders.uuid', "transactions.{$uuidColumn}")
|
|
52
|
+
->where("transactions.{$typeColumn}", Order::class)
|
|
53
|
+
->where(function ($orderQuery) {
|
|
54
|
+
static::inactiveOrderConstraint($orderQuery);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
$query->whereNotExists(function ($subQuery) {
|
|
60
|
+
$subQuery->selectRaw('1')
|
|
61
|
+
->from('orders')
|
|
62
|
+
->whereColumn('orders.transaction_uuid', 'transactions.uuid')
|
|
63
|
+
->where(function ($orderQuery) {
|
|
64
|
+
static::inactiveOrderConstraint($orderQuery);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
protected static function excludeInactiveInvoices(Builder $query): void
|
|
70
|
+
{
|
|
71
|
+
$invoiceClass = 'Fleetbase\\Ledger\\Models\\Invoice';
|
|
72
|
+
|
|
73
|
+
if (!class_exists($invoiceClass) || !Schema::hasTable('ledger_invoices')) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
foreach (['subject_uuid' => 'subject_type', 'context_uuid' => 'context_type'] as $uuidColumn => $typeColumn) {
|
|
78
|
+
$query->whereNotExists(function ($subQuery) use ($uuidColumn, $typeColumn, $invoiceClass) {
|
|
79
|
+
$subQuery->selectRaw('1')
|
|
80
|
+
->from('ledger_invoices')
|
|
81
|
+
->whereColumn('ledger_invoices.uuid', "transactions.{$uuidColumn}")
|
|
82
|
+
->where("transactions.{$typeColumn}", $invoiceClass)
|
|
83
|
+
->where(function ($invoiceQuery) {
|
|
84
|
+
static::inactiveInvoiceConstraint($invoiceQuery);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
$query->whereNotExists(function ($subQuery) {
|
|
90
|
+
$subQuery->selectRaw('1')
|
|
91
|
+
->from('ledger_invoices')
|
|
92
|
+
->whereColumn('ledger_invoices.transaction_uuid', 'transactions.uuid')
|
|
93
|
+
->where(function ($invoiceQuery) {
|
|
94
|
+
static::inactiveInvoiceConstraint($invoiceQuery);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected static function inactiveOrderConstraint($query): void
|
|
100
|
+
{
|
|
101
|
+
$query->whereNotNull('orders.deleted_at')
|
|
102
|
+
->orWhereIn('orders.status', self::INACTIVE_ORDER_STATUSES);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
protected static function inactiveInvoiceConstraint($query): void
|
|
106
|
+
{
|
|
107
|
+
$query->whereNotNull('ledger_invoices.deleted_at')
|
|
108
|
+
->orWhereIn('ledger_invoices.status', self::INACTIVE_INVOICE_STATUSES);
|
|
109
|
+
|
|
110
|
+
if (Schema::hasTable('orders')) {
|
|
111
|
+
$query->orWhereExists(function ($orderQuery) {
|
|
112
|
+
$orderQuery->selectRaw('1')
|
|
113
|
+
->from('orders')
|
|
114
|
+
->whereColumn('orders.uuid', 'ledger_invoices.order_uuid')
|
|
115
|
+
->where(function ($inactiveOrderQuery) {
|
|
116
|
+
static::inactiveOrderConstraint($inactiveOrderQuery);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\FleetOps\Models\Order;
|
|
6
|
-
use Fleetbase\Models\Transaction;
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Average revenue per completed order in the period. Computed as
|
|
@@ -39,12 +38,7 @@ class AvgOrderValueMetric extends MoneyMetric
|
|
|
39
38
|
return 0.0;
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
$revenueQuery =
|
|
43
|
-
->where('currency', $this->currency());
|
|
44
|
-
|
|
45
|
-
if ($this->currentStart && $this->currentEnd) {
|
|
46
|
-
$revenueQuery->whereBetween('created_at', [$this->currentStart, $this->currentEnd]);
|
|
47
|
-
}
|
|
41
|
+
$revenueQuery = ActiveRevenueQuery::forCompany($this->company, $this->currency(), $this->currentStart, $this->currentEnd);
|
|
48
42
|
|
|
49
43
|
$totalRevenue = (float) $revenueQuery->sum('amount');
|
|
50
44
|
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Support\Metrics;
|
|
4
4
|
|
|
5
|
-
use Fleetbase\Models\Transaction;
|
|
6
|
-
|
|
7
5
|
class EarningsMetric extends MoneyMetric
|
|
8
6
|
{
|
|
9
7
|
public static function slug(): string
|
|
@@ -13,14 +11,7 @@ class EarningsMetric extends MoneyMetric
|
|
|
13
11
|
|
|
14
12
|
protected function query(?\DateTimeInterface $start, ?\DateTimeInterface $end)
|
|
15
13
|
{
|
|
16
|
-
|
|
17
|
-
->where('currency', $this->currency());
|
|
18
|
-
|
|
19
|
-
if ($start && $end) {
|
|
20
|
-
$query->whereBetween('created_at', [$start, $end]);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return $query;
|
|
14
|
+
return ActiveRevenueQuery::forCompany($this->company, $this->currency(), $start, $end);
|
|
24
15
|
}
|
|
25
16
|
|
|
26
17
|
protected function aggregate($query): float
|