@fleetbase/fleetops-engine 0.6.54 → 0.6.56
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/admin/routing-settings.hbs +2 -1
- package/addon/components/cell/attached-vehicle.hbs +10 -0
- package/addon/components/cell/attached-vehicle.js +58 -0
- package/addon/components/cell/device-identity.hbs +24 -0
- package/addon/components/cell/device-identity.js +129 -0
- package/addon/components/cell/driver-identity.hbs +33 -0
- package/addon/components/cell/driver-identity.js +143 -0
- package/addon/components/cell/equipment-identity.hbs +5 -0
- package/addon/components/cell/equipment-identity.js +48 -0
- package/addon/components/cell/part-identity.hbs +5 -0
- package/addon/components/cell/part-identity.js +63 -0
- package/addon/components/cell/telematic-device.hbs +38 -0
- package/addon/components/cell/telematic-device.js +44 -0
- package/addon/components/cell/telematic-provider.hbs +33 -16
- package/addon/components/cell/telematic-provider.js +50 -8
- package/addon/components/cell/vehicle-identity.hbs +33 -0
- package/addon/components/cell/vehicle-identity.js +138 -0
- package/addon/components/device/details.js +1 -1
- package/addon/components/device/form.hbs +2 -2
- package/addon/components/device/form.js +7 -0
- package/addon/components/device/panel-header.hbs +45 -20
- package/addon/components/device/panel-header.js +49 -1
- package/addon/components/device/panel-tabs/events.hbs +30 -0
- package/addon/components/device/panel-tabs/events.js +128 -0
- package/addon/components/device/panel-tabs/sensors.hbs +30 -0
- package/addon/components/device/panel-tabs/sensors.js +93 -0
- package/addon/components/device/panel-tabs/vehicle.hbs +68 -0
- package/addon/components/device/panel-tabs/vehicle.js +90 -0
- 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/equipment/form.js +8 -1
- package/addon/components/layout/fleet-ops-sidebar.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +20 -6
- package/addon/components/map/drawer/device-event-listing.js +2 -0
- package/addon/components/map/drawer/driver-listing.js +2 -0
- package/addon/components/map/drawer/place-listing.js +2 -0
- package/addon/components/map/drawer/position-listing.hbs +11 -6
- package/addon/components/map/drawer/position-listing.js +17 -4
- package/addon/components/map/drawer/vehicle-listing.js +2 -0
- package/addon/components/sensor/panel-header.hbs +47 -15
- package/addon/components/sensor/panel-header.js +59 -1
- package/addon/components/table/cell/dropdown/action-item.hbs +31 -0
- package/addon/components/table/cell/dropdown/action-item.js +62 -0
- package/addon/components/table/cell/dropdown.hbs +28 -0
- package/addon/components/table/cell/dropdown.js +91 -0
- 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/vendor/panel-header.hbs +82 -0
- package/addon/components/vendor/panel-header.js +48 -0
- package/addon/controllers/connectivity/devices/index/details/events.js +1 -1
- package/addon/controllers/connectivity/devices/index/details/vehicle.js +50 -2
- package/addon/controllers/connectivity/devices/index.js +144 -129
- package/addon/controllers/connectivity/events/details.js +70 -0
- package/addon/controllers/connectivity/events/index.js +187 -11
- package/addon/controllers/connectivity/sensors/index.js +160 -14
- package/addon/controllers/connectivity/telematics/details/devices.js +126 -134
- package/addon/controllers/connectivity/telematics/details/events.js +1 -1
- package/addon/controllers/connectivity/telematics/details/sensors.js +4 -0
- package/addon/controllers/maintenance/equipment/index.js +1 -2
- package/addon/controllers/maintenance/parts/index.js +1 -2
- package/addon/controllers/management/drivers/index.js +82 -42
- package/addon/controllers/management/fuel-reports/index.js +63 -4
- package/addon/controllers/management/issues/index.js +63 -4
- package/addon/controllers/management/vehicles/index.js +33 -6
- package/addon/controllers/settings/map.js +15 -0
- package/addon/instance-initializers/register-vroom-allocation.js +1 -1
- package/addon/routes/connectivity/devices/index/details/vehicle.js +24 -2
- package/addon/routes/connectivity/devices/index.js +7 -0
- package/addon/routes/connectivity/events/details.js +28 -0
- package/addon/routes/connectivity/events/index.js +6 -2
- package/addon/routes/connectivity/sensors/index.js +7 -2
- package/addon/routes/connectivity/telematics/details/devices.js +2 -0
- package/addon/routes/management/vehicles/index/details.js +3 -1
- package/addon/services/device-actions.js +30 -0
- package/addon/services/device-event-actions.js +1 -1
- package/addon/services/equipment-actions.js +1 -0
- package/addon/services/map-adapter/google.js +25 -0
- package/addon/services/map-adapter/leaflet.js +1 -1
- package/addon/services/map-adapter-interface.js +1 -1
- package/addon/services/map-manager.js +4 -1
- package/addon/services/map-settings.js +15 -0
- package/addon/services/sensor-actions.js +3 -0
- package/addon/services/service-rate-actions.js +39 -1
- package/addon/services/vehicle-actions.js +24 -12
- package/addon/services/vendor-actions.js +3 -0
- package/addon/styles/fleetops-engine.css +9 -1
- package/addon/templates/connectivity/devices/index/details/vehicle.hbs +66 -2
- package/addon/templates/connectivity/events/details.hbs +40 -0
- package/addon/templates/management/vendors/index/details.hbs +2 -2
- package/addon/templates/settings/map.hbs +38 -9
- package/addon/templates/settings/notifications.hbs +2 -1
- package/addon/templates/settings/routing.hbs +1 -0
- package/addon/utils/device-table-columns.js +288 -0
- package/addon/utils/identity-cell-resource.js +20 -0
- package/addon/utils/leaflet-plugin-loader.js +5 -5
- package/addon/utils/map-drawer-dropdown-position.js +47 -0
- package/addon/utils/route-colors.js +1 -1
- package/addon/utils/trackable-option.js +93 -0
- package/app/components/cell/attached-vehicle.js +1 -0
- package/app/components/cell/device-identity.js +1 -0
- package/app/components/cell/driver-identity.js +1 -0
- package/app/components/cell/equipment-identity.js +1 -0
- package/app/components/cell/part-identity.js +1 -0
- package/app/components/cell/telematic-device.js +1 -0
- package/app/components/cell/vehicle-identity.js +1 -0
- package/app/components/device/panel-tabs/events.hbs +30 -0
- package/app/components/device/panel-tabs/events.js +128 -0
- package/app/components/device/panel-tabs/sensors.hbs +30 -0
- package/app/components/device/panel-tabs/sensors.js +93 -0
- package/app/components/device/panel-tabs/vehicle.hbs +68 -0
- package/app/components/device/panel-tabs/vehicle.js +90 -0
- package/app/components/device-event/details.hbs +192 -0
- package/app/components/device-event/details.js +1 -0
- package/app/components/table/cell/dropdown/action-item.js +1 -0
- package/app/components/table/cell/dropdown.js +1 -0
- package/app/components/vendor/panel-header.js +1 -0
- package/app/controllers/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/routes/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/templates/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/utils/map-drawer-dropdown-position.js +1 -0
- package/app/utils/trackable-option.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +4 -4
- package/server/migrations/2026_06_23_000001_delete_bad_afaqy_telematics_sensors.php +34 -0
- package/server/src/Console/Commands/SyncTelematics.php +4 -4
- 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 +36 -1
- package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +1 -1
- 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/ServiceRateController.php +82 -13
- 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/DeviceEventFilter.php +21 -0
- package/server/src/Http/Filter/DeviceFilter.php +50 -4
- 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/FuelTransaction.php +69 -0
- package/server/src/Http/Resources/v1/Index/Vehicle.php +19 -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/Device.php +2 -0
- package/server/src/Models/DeviceEvent.php +81 -0
- package/server/src/Models/Equipment.php +55 -0
- package/server/src/Models/Order.php +2 -1
- package/server/src/Observers/PurchaseRateObserver.php +2 -1
- 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 +105 -5
- package/server/src/Support/Telematics/Providers/FlespiProvider.php +89 -15
- package/server/src/Support/Telematics/Providers/GeotabProvider.php +123 -35
- package/server/src/Support/Telematics/Providers/SafeeProvider.php +567 -47
- package/server/src/Support/Telematics/Providers/SamsaraProvider.php +122 -14
- package/server/src/Support/Telematics/TelematicService.php +28 -9
- package/server/src/routes.php +67 -15
- package/server/tests/DeviceFilterTest.php +9 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +19 -2
- package/server/tests/EquipmentAssignmentMorphTypeTest.php +35 -0
- package/server/tests/ExportCoverageTest.php +79 -0
- package/server/tests/MetricsRegistryTest.php +45 -5
- package/server/tests/PublicFleetOpsRestEndpointsTest.php +191 -0
- package/server/tests/ServiceRateRouteValidationTest.php +101 -0
- package/server/tests/TelematicsHardeningTest.php +873 -7
- package/addon/controllers/connectivity/events/index/details.js +0 -3
- package/addon/routes/connectivity/events/index/details.js +0 -3
- package/addon/templates/connectivity/events/index/details.hbs +0 -1
|
@@ -18,6 +18,7 @@ use Fleetbase\Traits\TracksApiCredential;
|
|
|
18
18
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
19
19
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
20
20
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
|
21
|
+
use Illuminate\Database\Eloquent\Relations\Relation;
|
|
21
22
|
use Spatie\Activitylog\LogOptions;
|
|
22
23
|
use Spatie\Activitylog\Traits\LogsActivity;
|
|
23
24
|
use Spatie\Sluggable\HasSlug;
|
|
@@ -94,6 +95,15 @@ class Equipment extends Model
|
|
|
94
95
|
'slug',
|
|
95
96
|
];
|
|
96
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Set attributes and defaults.
|
|
100
|
+
*
|
|
101
|
+
* @var array
|
|
102
|
+
*/
|
|
103
|
+
protected $attributes = [
|
|
104
|
+
'status' => 'available',
|
|
105
|
+
];
|
|
106
|
+
|
|
97
107
|
/**
|
|
98
108
|
* Dynamic attributes that are appended to object.
|
|
99
109
|
*
|
|
@@ -148,6 +158,19 @@ class Equipment extends Model
|
|
|
148
158
|
*/
|
|
149
159
|
protected static $logName = 'equipment';
|
|
150
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Enforce assignment morph aliases for existing short type values.
|
|
163
|
+
*/
|
|
164
|
+
public static function boot(): void
|
|
165
|
+
{
|
|
166
|
+
parent::boot();
|
|
167
|
+
|
|
168
|
+
Relation::morphMap([
|
|
169
|
+
'fleet-ops:vehicle' => Vehicle::class,
|
|
170
|
+
'fleet-ops:driver' => Driver::class,
|
|
171
|
+
]);
|
|
172
|
+
}
|
|
173
|
+
|
|
151
174
|
/**
|
|
152
175
|
* Get the options for generating the slug.
|
|
153
176
|
*/
|
|
@@ -192,6 +215,38 @@ class Equipment extends Model
|
|
|
192
215
|
return $this->morphTo(__FUNCTION__, 'equipable_type', 'equipable_uuid');
|
|
193
216
|
}
|
|
194
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Set the equipment status attribute.
|
|
220
|
+
*/
|
|
221
|
+
public function setStatusAttribute(?string $status = 'available'): void
|
|
222
|
+
{
|
|
223
|
+
$this->attributes['status'] = $status === null || $status === 'active' ? 'available' : $status;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Normalize equipment assignment type aliases to Eloquent morph classes.
|
|
228
|
+
*/
|
|
229
|
+
public function setEquipableTypeAttribute(?string $type): void
|
|
230
|
+
{
|
|
231
|
+
if ($type === null || $type === '') {
|
|
232
|
+
$this->attributes['equipable_type'] = null;
|
|
233
|
+
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (str_contains($type, '\\')) {
|
|
238
|
+
$this->attributes['equipable_type'] = $type;
|
|
239
|
+
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
$this->attributes['equipable_type'] = match ($type) {
|
|
244
|
+
'fleet-ops:vehicle', 'vehicle' => Utils::getMutationType('fleet-ops:vehicle'),
|
|
245
|
+
'fleet-ops:driver', 'driver' => Utils::getMutationType('fleet-ops:driver'),
|
|
246
|
+
default => $type,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
195
250
|
public function maintenances(): HasMany
|
|
196
251
|
{
|
|
197
252
|
return $this->hasMany(Maintenance::class, 'maintainable_uuid', 'uuid')
|
|
@@ -1208,7 +1208,8 @@ class Order extends Model
|
|
|
1208
1208
|
'description' => 'Dispatch order',
|
|
1209
1209
|
'type' => 'dispatch',
|
|
1210
1210
|
'direction' => Transaction::DIRECTION_CREDIT,
|
|
1211
|
-
'status' =>
|
|
1211
|
+
'status' => Transaction::STATUS_SUCCESS,
|
|
1212
|
+
'settlement_status' => Transaction::SETTLEMENT_STATUS_UNPAID,
|
|
1212
1213
|
]);
|
|
1213
1214
|
|
|
1214
1215
|
$this->updateQuietly(['transaction_uuid' => $transaction->uuid]);
|
|
@@ -49,7 +49,8 @@ class PurchaseRateObserver
|
|
|
49
49
|
'description' => 'Dispatch order',
|
|
50
50
|
'type' => 'dispatch',
|
|
51
51
|
'direction' => Transaction::DIRECTION_CREDIT,
|
|
52
|
-
'status' =>
|
|
52
|
+
'status' => Transaction::STATUS_SUCCESS,
|
|
53
|
+
'settlement_status' => Transaction::SETTLEMENT_STATUS_UNPAID,
|
|
53
54
|
]);
|
|
54
55
|
|
|
55
56
|
if (isset($purchaseRate->serviceQuote)) {
|
|
@@ -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
|
|
@@ -151,6 +151,7 @@ class AfaqyProvider extends AbstractProvider
|
|
|
151
151
|
|
|
152
152
|
return [
|
|
153
153
|
'device_id' => $payload['_id'] ?? $payload['id'] ?? null,
|
|
154
|
+
'external_id' => $payload['_id'] ?? $payload['id'] ?? null,
|
|
154
155
|
'name' => $payload['name'] ?? data_get($payload, 'profile.plate_number') ?? 'Unknown Unit',
|
|
155
156
|
'provider' => 'afaqy',
|
|
156
157
|
'model' => data_get($payload, 'profile.model') ?? $payload['device'] ?? null,
|
|
@@ -210,12 +211,35 @@ class AfaqyProvider extends AbstractProvider
|
|
|
210
211
|
|
|
211
212
|
public function normalizeSensor(array $payload): array
|
|
212
213
|
{
|
|
214
|
+
$unitId = $payload['device_id'] ?? $payload['unit_id'] ?? $payload['vehicle_id'] ?? null;
|
|
215
|
+
$sensorIdentity = $this->resolveSensorIdentity($payload);
|
|
216
|
+
$sensorName = $this->resolveSensorName($payload, $sensorIdentity);
|
|
217
|
+
$value = $this->resolveSensorValue($payload);
|
|
218
|
+
|
|
219
|
+
if (!$unitId || !$sensorIdentity || !$sensorName || !$this->isSensorValue($value)) {
|
|
220
|
+
throw new \InvalidArgumentException('AFAQY sensor payload does not contain a stable identity and scalar latest value.');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
$type = $this->normalizeSensorType($payload, $sensorName);
|
|
224
|
+
$internalId = implode(':', ['afaqy', $unitId, $this->sensorIdentityPart($sensorIdentity)]);
|
|
225
|
+
|
|
213
226
|
return [
|
|
214
|
-
'
|
|
215
|
-
'
|
|
216
|
-
'
|
|
217
|
-
'
|
|
218
|
-
'
|
|
227
|
+
'internal_id' => $internalId,
|
|
228
|
+
'external_id' => $internalId,
|
|
229
|
+
'name' => $sensorName,
|
|
230
|
+
'type' => $type,
|
|
231
|
+
'sensor_type' => $type,
|
|
232
|
+
'value' => $value,
|
|
233
|
+
'unit' => $payload['unit'] ?? $payload['units'] ?? data_get($payload, 'last_val.unit'),
|
|
234
|
+
'recorded_at' => $this->parseTimestamp(data_get($payload, 'last_val.dtt') ?? data_get($payload, 'last_update.dtt') ?? $payload['updated_at'] ?? $payload['created_at'] ?? null),
|
|
235
|
+
'status' => 'active',
|
|
236
|
+
'meta' => array_filter([
|
|
237
|
+
'provider' => 'afaqy',
|
|
238
|
+
'unit_id' => $unitId,
|
|
239
|
+
'provider_sensor_id' => $sensorIdentity,
|
|
240
|
+
'sensor_key' => $payload['sensor_key'] ?? null,
|
|
241
|
+
'raw' => $payload,
|
|
242
|
+
], fn ($value) => $value !== null),
|
|
219
243
|
];
|
|
220
244
|
}
|
|
221
245
|
|
|
@@ -476,4 +500,80 @@ class AfaqyProvider extends AbstractProvider
|
|
|
476
500
|
?? data_get($payload, 'fc.level')
|
|
477
501
|
?? null;
|
|
478
502
|
}
|
|
503
|
+
|
|
504
|
+
protected function resolveSensorIdentity(array $payload): ?string
|
|
505
|
+
{
|
|
506
|
+
$value = $payload['_id']
|
|
507
|
+
?? $payload['id']
|
|
508
|
+
?? $payload['sensor_id']
|
|
509
|
+
?? $payload['ident']
|
|
510
|
+
?? $payload['param']
|
|
511
|
+
?? $payload['sensor_key']
|
|
512
|
+
?? $payload['name']
|
|
513
|
+
?? data_get($payload, 'sensor.name');
|
|
514
|
+
|
|
515
|
+
if ($value === null || $value === '') {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return (string) $value;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
protected function resolveSensorName(array $payload, ?string $fallback = null): ?string
|
|
523
|
+
{
|
|
524
|
+
$value = $payload['name']
|
|
525
|
+
?? data_get($payload, 'sensor.name')
|
|
526
|
+
?? $payload['param']
|
|
527
|
+
?? $payload['sensor_key']
|
|
528
|
+
?? $fallback;
|
|
529
|
+
|
|
530
|
+
if ($value === null || $value === '') {
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return (string) $value;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
protected function resolveSensorValue(array $payload): mixed
|
|
538
|
+
{
|
|
539
|
+
if (array_key_exists('value', $payload)) {
|
|
540
|
+
return $payload['value'];
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return data_get($payload, 'last_val.value')
|
|
544
|
+
?? data_get($payload, 'last_update.value')
|
|
545
|
+
?? data_get($payload, 'last_update.val')
|
|
546
|
+
?? data_get($payload, 'last_update.state');
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
protected function isSensorValue(mixed $value): bool
|
|
550
|
+
{
|
|
551
|
+
return $value !== null && is_scalar($value);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
protected function normalizeSensorType(array $payload, string $sensorName): string
|
|
555
|
+
{
|
|
556
|
+
$providerType = (string) ($payload['sensor_type'] ?? $payload['type'] ?? $payload['param'] ?? '');
|
|
557
|
+
$type = strtolower(trim($providerType . ' ' . $sensorName));
|
|
558
|
+
|
|
559
|
+
return match (true) {
|
|
560
|
+
str_contains($type, 'door') => 'door',
|
|
561
|
+
str_contains($type, 'temp') => 'temperature',
|
|
562
|
+
str_contains($type, 'humid') => 'humidity',
|
|
563
|
+
str_contains($type, 'fuel') => 'fuel',
|
|
564
|
+
str_starts_with($type, 'di') || str_contains($type, 'digital') => 'digital',
|
|
565
|
+
str_starts_with($type, 'ai') || str_contains($type, 'analog') => 'analog',
|
|
566
|
+
str_contains($type, 'battery') || str_contains($type, 'voltage') => 'voltage',
|
|
567
|
+
default => $this->sensorIdentityPart($type) ?: 'generic',
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
protected function sensorIdentityPart(string $value): string
|
|
572
|
+
{
|
|
573
|
+
$normalized = strtolower(trim($value));
|
|
574
|
+
$normalized = preg_replace('/[^a-z0-9]+/i', '_', $normalized) ?? '';
|
|
575
|
+
$normalized = trim($normalized, '_');
|
|
576
|
+
|
|
577
|
+
return $normalized !== '' ? $normalized : 'sensor';
|
|
578
|
+
}
|
|
479
579
|
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Support\Telematics\Providers;
|
|
4
4
|
|
|
5
|
+
use Illuminate\Support\Carbon;
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Class FlespiProvider.
|
|
7
9
|
*
|
|
@@ -77,34 +79,71 @@ class FlespiProvider extends AbstractProvider
|
|
|
77
79
|
|
|
78
80
|
public function normalizeDevice(array $payload): array
|
|
79
81
|
{
|
|
82
|
+
$telemetry = $payload['telemetry'] ?? $payload;
|
|
83
|
+
$externalId = $payload['id'] ?? $payload['device.id'] ?? null;
|
|
84
|
+
$occurredAt = $this->parseTimestamp($telemetry['timestamp'] ?? $payload['last_active'] ?? null);
|
|
85
|
+
|
|
80
86
|
return [
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
'
|
|
84
|
-
'
|
|
85
|
-
'
|
|
86
|
-
'
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
'device_id' => $externalId,
|
|
88
|
+
'external_id' => $externalId,
|
|
89
|
+
'name' => $payload['name'] ?? $telemetry['device.name'] ?? 'Unknown Device',
|
|
90
|
+
'provider' => 'flespi',
|
|
91
|
+
'model' => $payload['device_type_id'] ?? $payload['device_type_name'] ?? null,
|
|
92
|
+
'imei' => $payload['configuration']['ident'] ?? $payload['ident'] ?? $telemetry['ident'] ?? null,
|
|
93
|
+
'serial_number' => $payload['configuration']['serial'] ?? $payload['serial'] ?? null,
|
|
94
|
+
'phone' => $payload['configuration']['phone'] ?? null,
|
|
95
|
+
'status' => isset($payload['telemetry']) ? 'active' : 'inactive',
|
|
96
|
+
'online' => $this->resolveOnline($telemetry),
|
|
97
|
+
'last_seen_at' => $occurredAt,
|
|
98
|
+
'location' => [
|
|
99
|
+
'lat' => $telemetry['position.latitude'] ?? null,
|
|
100
|
+
'lng' => $telemetry['position.longitude'] ?? null,
|
|
101
|
+
],
|
|
102
|
+
'speed' => $telemetry['position.speed'] ?? $telemetry['vehicle.speed'] ?? null,
|
|
103
|
+
'heading' => $telemetry['position.direction'] ?? $telemetry['position.heading'] ?? null,
|
|
104
|
+
'altitude' => $telemetry['position.altitude'] ?? null,
|
|
105
|
+
'odometer' => $telemetry['vehicle.mileage'] ?? $telemetry['vehicle.odometer'] ?? null,
|
|
106
|
+
'ignition' => $this->extractIgnition($telemetry),
|
|
107
|
+
'fuel_level' => $telemetry['fuel.level'] ?? $telemetry['can.fuel.level'] ?? null,
|
|
108
|
+
'meta' => [
|
|
109
|
+
'raw' => $payload,
|
|
110
|
+
'provider_status' => array_filter([
|
|
111
|
+
'status' => $payload['status'] ?? $telemetry['status'] ?? null,
|
|
112
|
+
'online' => $telemetry['online'] ?? null,
|
|
113
|
+
'connected' => $telemetry['device.connected'] ?? $telemetry['connected'] ?? null,
|
|
114
|
+
], fn ($value) => $value !== null),
|
|
91
115
|
],
|
|
92
|
-
'meta' => $payload,
|
|
93
116
|
];
|
|
94
117
|
}
|
|
95
118
|
|
|
96
119
|
public function normalizeEvent(array $payload): array
|
|
97
120
|
{
|
|
121
|
+
$deviceId = $payload['device.id'] ?? $payload['id'] ?? null;
|
|
122
|
+
|
|
98
123
|
return [
|
|
99
|
-
'external_id' => $payload['id'] ??
|
|
100
|
-
'device_id' => $
|
|
124
|
+
'external_id' => $payload['id'] ?? $deviceId,
|
|
125
|
+
'device_id' => $deviceId,
|
|
101
126
|
'event_type' => $payload['event.enum'] ?? 'telemetry_update',
|
|
102
|
-
'occurred_at' =>
|
|
127
|
+
'occurred_at' => $this->parseTimestamp($payload['timestamp'] ?? null) ?? now(),
|
|
128
|
+
'online' => $this->resolveOnline($payload),
|
|
103
129
|
'location' => [
|
|
104
130
|
'lat' => $payload['position.latitude'] ?? null,
|
|
105
131
|
'lng' => $payload['position.longitude'] ?? null,
|
|
106
132
|
],
|
|
107
|
-
'
|
|
133
|
+
'speed' => $payload['position.speed'] ?? $payload['vehicle.speed'] ?? null,
|
|
134
|
+
'heading' => $payload['position.direction'] ?? $payload['position.heading'] ?? null,
|
|
135
|
+
'altitude' => $payload['position.altitude'] ?? null,
|
|
136
|
+
'odometer' => $payload['vehicle.mileage'] ?? $payload['vehicle.odometer'] ?? null,
|
|
137
|
+
'ignition' => $this->extractIgnition($payload),
|
|
138
|
+
'fuel_level' => $payload['fuel.level'] ?? $payload['can.fuel.level'] ?? null,
|
|
139
|
+
'meta' => [
|
|
140
|
+
'raw' => $payload,
|
|
141
|
+
'provider_status' => array_filter([
|
|
142
|
+
'status' => $payload['status'] ?? null,
|
|
143
|
+
'online' => $payload['online'] ?? null,
|
|
144
|
+
'connected' => $payload['device.connected'] ?? $payload['connected'] ?? null,
|
|
145
|
+
], fn ($value) => $value !== null),
|
|
146
|
+
],
|
|
108
147
|
];
|
|
109
148
|
}
|
|
110
149
|
|
|
@@ -180,4 +219,39 @@ class FlespiProvider extends AbstractProvider
|
|
|
180
219
|
{
|
|
181
220
|
return true;
|
|
182
221
|
}
|
|
222
|
+
|
|
223
|
+
protected function parseTimestamp($value): ?string
|
|
224
|
+
{
|
|
225
|
+
if (!$value) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (is_numeric($value)) {
|
|
230
|
+
return Carbon::createFromTimestamp((float) $value)->toDateTimeString();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return Carbon::parse($value)->toDateTimeString();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
protected function resolveOnline(array $payload): ?bool
|
|
237
|
+
{
|
|
238
|
+
$value = $payload['online'] ?? $payload['device.connected'] ?? $payload['connected'] ?? null;
|
|
239
|
+
|
|
240
|
+
if ($value === null) {
|
|
241
|
+
return isset($payload['timestamp']);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? (bool) $value;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
protected function extractIgnition(array $payload): ?bool
|
|
248
|
+
{
|
|
249
|
+
$value = $payload['engine.ignition.status'] ?? $payload['ignition.status'] ?? null;
|
|
250
|
+
|
|
251
|
+
if ($value === null) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? (bool) $value;
|
|
256
|
+
}
|
|
183
257
|
}
|