@fleetbase/fleetops-engine 0.6.53 → 0.6.55
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 +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.hbs +106 -135
- package/addon/components/device/details.js +174 -1
- package/addon/components/device/form.hbs +2 -2
- package/addon/components/device/form.js +7 -0
- package/addon/components/device/panel-header.hbs +46 -21
- 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/equipment/form.js +8 -1
- package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +76 -11
- package/addon/components/layout/fleet-ops-sidebar.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +101 -17
- 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/order/form/details.hbs +1 -43
- package/addon/components/order/form/details.js +18 -53
- package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
- package/addon/components/order/form/orchestrator-constraints.js +61 -0
- package/addon/components/order/form/payload.js +48 -2
- package/addon/components/order/form/route.js +15 -0
- package/addon/components/order/form/service-rate.hbs +62 -11
- package/addon/components/order/form/service-rate.js +124 -2
- package/addon/components/order/form.hbs +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 +7 -1
- package/addon/components/telematic/form.js +16 -8
- package/addon/components/vendor/panel-header.hbs +82 -0
- package/addon/components/vendor/panel-header.js +48 -0
- package/addon/components/work-order/details.hbs +5 -1
- package/addon/components/work-order/form.hbs +26 -4
- package/addon/components/work-order/form.js +5 -1
- package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
- package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
- package/addon/controllers/connectivity/devices/index/details/vehicle.js +122 -0
- package/addon/controllers/connectivity/devices/index/details.js +12 -0
- package/addon/controllers/connectivity/devices/index.js +144 -129
- package/addon/controllers/connectivity/events/details.js +3 -0
- package/addon/controllers/connectivity/events/index.js +187 -11
- package/addon/controllers/connectivity/sensors/index.js +156 -14
- package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
- package/addon/controllers/connectivity/telematics/details/devices.js +170 -129
- package/addon/controllers/connectivity/telematics/details/events.js +39 -11
- package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
- package/addon/controllers/maintenance/equipment/index.js +1 -2
- package/addon/controllers/maintenance/parts/index.js +1 -2
- package/addon/controllers/maintenance/work-orders/index.js +18 -1
- 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/routes/connectivity/devices/index/details/events.js +37 -1
- package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
- package/addon/routes/connectivity/devices/index/details/vehicle.js +29 -0
- package/addon/routes/connectivity/devices/index.js +7 -0
- package/addon/routes/connectivity/events/details.js +3 -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/attachments.js +72 -9
- package/addon/routes/connectivity/telematics/details/devices.js +6 -0
- package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
- package/addon/routes/connectivity/telematics/details.js +1 -1
- package/addon/routes/connectivity/telematics/edit.js +1 -1
- package/addon/routes/maintenance/work-orders/index.js +1 -0
- package/addon/routes.js +2 -0
- 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/order-creation.js +54 -1
- package/addon/services/order-validation.js +3 -2
- package/addon/services/sensor-actions.js +3 -0
- package/addon/services/service-rate-actions.js +40 -1
- package/addon/services/vehicle-actions.js +24 -12
- package/addon/services/vendor-actions.js +3 -0
- package/addon/styles/fleetops-engine.css +28 -4
- package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
- package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
- package/addon/templates/connectivity/devices/index/details/vehicle.hbs +147 -0
- package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
- package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
- package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
- package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
- package/addon/templates/management/vehicles/index/edit.hbs +1 -1
- package/addon/templates/management/vendors/index/details.hbs +2 -2
- 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/fleet-ops-options.js +38 -0
- package/addon/utils/identity-cell-resource.js +20 -0
- package/addon/utils/map-drawer-dropdown-position.js +47 -0
- 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/order/form/orchestrator-constraints.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/devices/index/details/sensors.js +1 -0
- package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/controllers/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
- package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/routes/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
- package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
- 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/2025_08_28_054930_create_work_orders_table.php +1 -0
- package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
- package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
- package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
- package/server/src/Console/Commands/SyncTelematics.php +89 -0
- package/server/src/Exceptions/TelematicProviderException.php +13 -2
- package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +55 -1
- package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +17 -6
- package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +178 -1
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
- package/server/src/Http/Controllers/Internal/v1/SearchController.php +2 -2
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
- package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +82 -13
- package/server/src/Http/Filter/DeviceEventFilter.php +65 -0
- package/server/src/Http/Filter/DeviceFilter.php +80 -0
- package/server/src/Http/Filter/SensorFilter.php +125 -0
- package/server/src/Http/Filter/VehicleFilter.php +13 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
- package/server/src/Http/Resources/v1/Driver.php +11 -2
- package/server/src/Http/Resources/v1/Index/Vehicle.php +19 -0
- package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
- package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
- package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
- package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
- package/server/src/Jobs/SyncTelematicDevicesJob.php +180 -123
- package/server/src/Models/Device.php +17 -1
- package/server/src/Models/DeviceEvent.php +82 -1
- package/server/src/Models/Driver.php +16 -0
- package/server/src/Models/Equipment.php +55 -0
- package/server/src/Models/Sensor.php +15 -2
- package/server/src/Models/Telematic.php +1 -1
- package/server/src/Models/Vehicle.php +5 -1
- package/server/src/Models/WorkOrder.php +5 -2
- package/server/src/Observers/DriverObserver.php +3 -3
- package/server/src/Observers/FleetObserver.php +23 -0
- package/server/src/Observers/VehicleObserver.php +3 -3
- package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
- package/server/src/Support/LiveCacheService.php +1 -1
- package/server/src/Support/Telematics/Providers/AfaqyProvider.php +143 -20
- 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 +70 -5
- package/server/src/Support/Telematics/Providers/SamsaraProvider.php +122 -14
- package/server/src/Support/Telematics/TelematicService.php +330 -30
- package/server/src/routes.php +1 -1
- package/server/tests/DeviceFilterTest.php +45 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +19 -2
- package/server/tests/EquipmentAssignmentMorphTypeTest.php +35 -0
- package/server/tests/OperationsMonitorEndpointTest.php +38 -0
- package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
- package/server/tests/ServiceRateRouteValidationTest.php +101 -0
- package/server/tests/TelematicsHardeningTest.php +598 -6
- package/server/tests/VehicleDriverAssignmentTest.php +27 -0
- package/translations/en-us.yaml +1 -0
- 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 → details.hbs} +0 -0
|
@@ -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')
|
|
@@ -66,14 +66,27 @@ class Sensor extends Model
|
|
|
66
66
|
*
|
|
67
67
|
* @var array
|
|
68
68
|
*/
|
|
69
|
-
protected $searchableColumns = ['name', 'type', 'internal_id', 'unit', 'public_id'];
|
|
69
|
+
protected $searchableColumns = ['name', 'type', 'internal_id', 'serial_number', 'imei', 'unit', 'public_id'];
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* The attributes that can be used for filtering.
|
|
73
73
|
*
|
|
74
74
|
* @var array
|
|
75
75
|
*/
|
|
76
|
-
protected $filterParams = [
|
|
76
|
+
protected $filterParams = [
|
|
77
|
+
'type',
|
|
78
|
+
'sensor_type',
|
|
79
|
+
'status',
|
|
80
|
+
'device_uuid',
|
|
81
|
+
'serial_number',
|
|
82
|
+
'imei',
|
|
83
|
+
'warranty_uuid',
|
|
84
|
+
'sensorable_type',
|
|
85
|
+
'telematic_uuid',
|
|
86
|
+
'last_reading_at',
|
|
87
|
+
'created_at',
|
|
88
|
+
'updated_at',
|
|
89
|
+
];
|
|
77
90
|
|
|
78
91
|
/**
|
|
79
92
|
* The attributes that are mass assignable.
|
|
@@ -63,7 +63,7 @@ class Telematic extends Model
|
|
|
63
63
|
*
|
|
64
64
|
* @var array
|
|
65
65
|
*/
|
|
66
|
-
protected $filterParams = ['provider', 'status', 'warranty_uuid'];
|
|
66
|
+
protected $filterParams = ['public_id', 'provider', 'status', 'warranty_uuid'];
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* The attributes that are mass assignable.
|
|
@@ -72,7 +72,7 @@ class Vehicle extends Model
|
|
|
72
72
|
*
|
|
73
73
|
* @var array
|
|
74
74
|
*/
|
|
75
|
-
protected $filterParams = ['vendor', 'driver', 'driver_uuid', 'vehicle_make', 'vehicle_model'];
|
|
75
|
+
protected $filterParams = ['vendor', 'driver', 'driver_uuid', 'vehicle_make', 'vehicle_model', 'telematic_uuid'];
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
78
|
* Relationships to auto load with driver.
|
|
@@ -407,6 +407,10 @@ class Vehicle extends Model
|
|
|
407
407
|
*/
|
|
408
408
|
public function getDisplayNameAttribute()
|
|
409
409
|
{
|
|
410
|
+
if (isset($this->name) && strlen($this->name)) {
|
|
411
|
+
return $this->name;
|
|
412
|
+
}
|
|
413
|
+
|
|
410
414
|
// Initialize an empty array to hold the name segments
|
|
411
415
|
$nameSegments = [];
|
|
412
416
|
|
|
@@ -59,14 +59,14 @@ class WorkOrder extends Model
|
|
|
59
59
|
*
|
|
60
60
|
* @var array
|
|
61
61
|
*/
|
|
62
|
-
protected $searchableColumns = ['code', 'subject', 'instructions', 'public_id'];
|
|
62
|
+
protected $searchableColumns = ['code', 'subject', 'category', 'instructions', 'public_id'];
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* The attributes that can be used for filtering.
|
|
66
66
|
*
|
|
67
67
|
* @var array
|
|
68
68
|
*/
|
|
69
|
-
protected $filterParams = ['status', 'priority', 'target_type', 'assignee_type'];
|
|
69
|
+
protected $filterParams = ['category', 'status', 'priority', 'target_type', 'assignee_type'];
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* The attributes that are mass assignable.
|
|
@@ -77,6 +77,7 @@ class WorkOrder extends Model
|
|
|
77
77
|
'company_uuid',
|
|
78
78
|
'code',
|
|
79
79
|
'subject',
|
|
80
|
+
'category',
|
|
80
81
|
'status',
|
|
81
82
|
'priority',
|
|
82
83
|
'target_type',
|
|
@@ -575,6 +576,7 @@ class WorkOrder extends Model
|
|
|
575
576
|
$row = array_filter($row);
|
|
576
577
|
|
|
577
578
|
$subject = Utils::or($row, ['subject', 'title', 'name']);
|
|
579
|
+
$category = Utils::or($row, ['category', 'work_order_category', 'work_type', 'type']);
|
|
578
580
|
$status = Utils::or($row, ['status'], 'open');
|
|
579
581
|
$priority = Utils::or($row, ['priority'], 'normal');
|
|
580
582
|
$instructions = Utils::or($row, ['instructions', 'description', 'notes']);
|
|
@@ -592,6 +594,7 @@ class WorkOrder extends Model
|
|
|
592
594
|
$workOrder = new static([
|
|
593
595
|
'company_uuid' => session('company'),
|
|
594
596
|
'subject' => $subject,
|
|
597
|
+
'category' => $category,
|
|
595
598
|
'status' => $status,
|
|
596
599
|
'priority' => $priority,
|
|
597
600
|
'instructions' => $instructions,
|
|
@@ -30,7 +30,7 @@ class DriverObserver
|
|
|
30
30
|
*/
|
|
31
31
|
public function created(Driver $driver)
|
|
32
32
|
{
|
|
33
|
-
LiveCacheService::
|
|
33
|
+
LiveCacheService::invalidateMultiple(['drivers', 'operations-monitor']);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -40,7 +40,7 @@ class DriverObserver
|
|
|
40
40
|
*/
|
|
41
41
|
public function updated(Driver $driver)
|
|
42
42
|
{
|
|
43
|
-
LiveCacheService::
|
|
43
|
+
LiveCacheService::invalidateMultiple(['drivers', 'operations-monitor']);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
@@ -70,6 +70,6 @@ class DriverObserver
|
|
|
70
70
|
$user->delete();
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
LiveCacheService::
|
|
73
|
+
LiveCacheService::invalidateMultiple(['drivers', 'operations-monitor']);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -3,9 +3,30 @@
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Observers;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\FleetOps\Models\Fleet;
|
|
6
|
+
use Fleetbase\FleetOps\Support\LiveCacheService;
|
|
6
7
|
|
|
7
8
|
class FleetObserver
|
|
8
9
|
{
|
|
10
|
+
/**
|
|
11
|
+
* Handle the Fleet "created" event.
|
|
12
|
+
*
|
|
13
|
+
* @return void
|
|
14
|
+
*/
|
|
15
|
+
public function created(Fleet $fleet)
|
|
16
|
+
{
|
|
17
|
+
LiveCacheService::invalidate('operations-monitor');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Handle the Fleet "updated" event.
|
|
22
|
+
*
|
|
23
|
+
* @return void
|
|
24
|
+
*/
|
|
25
|
+
public function updated(Fleet $fleet)
|
|
26
|
+
{
|
|
27
|
+
LiveCacheService::invalidate('operations-monitor');
|
|
28
|
+
}
|
|
29
|
+
|
|
9
30
|
/**
|
|
10
31
|
* Handle the Driver "deleted" event.
|
|
11
32
|
*
|
|
@@ -15,5 +36,7 @@ class FleetObserver
|
|
|
15
36
|
{
|
|
16
37
|
// If the fleet being deleted is set as parent fleet, remove it as the parent fleet
|
|
17
38
|
$subFleets = Fleet::where(['parent_fleet_uuid' => $fleet->uuid])->update(['parent_fleet_uuid' => null]);
|
|
39
|
+
|
|
40
|
+
LiveCacheService::invalidate('operations-monitor');
|
|
18
41
|
}
|
|
19
42
|
}
|
|
@@ -30,7 +30,7 @@ class VehicleObserver
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
LiveCacheService::
|
|
33
|
+
LiveCacheService::invalidateMultiple(['vehicles', 'operations-monitor']);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -55,7 +55,7 @@ class VehicleObserver
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
LiveCacheService::
|
|
58
|
+
LiveCacheService::invalidateMultiple(['vehicles', 'operations-monitor']);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -68,6 +68,6 @@ class VehicleObserver
|
|
|
68
68
|
// Unassign the deleted vehicle from matching driver/(s)
|
|
69
69
|
Driver::where(['vehicle_uuid' => $vehicle->uuid])->delete();
|
|
70
70
|
|
|
71
|
-
LiveCacheService::
|
|
71
|
+
LiveCacheService::invalidateMultiple(['vehicles', 'operations-monitor']);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -69,6 +69,7 @@ class FleetOpsServiceProvider extends CoreServiceProvider
|
|
|
69
69
|
\Fleetbase\FleetOps\Console\Commands\ProcessMaintenanceTriggers::class,
|
|
70
70
|
\Fleetbase\FleetOps\Console\Commands\SendMaintenanceReminders::class,
|
|
71
71
|
\Fleetbase\FleetOps\Console\Commands\ProcessOperationalAlerts::class,
|
|
72
|
+
\Fleetbase\FleetOps\Console\Commands\SyncTelematics::class,
|
|
72
73
|
];
|
|
73
74
|
|
|
74
75
|
/**
|
|
@@ -141,6 +142,7 @@ class FleetOpsServiceProvider extends CoreServiceProvider
|
|
|
141
142
|
$schedule->command('fleetops:process-maintenance-triggers')->daily()->withoutOverlapping()->storeOutputInDb();
|
|
142
143
|
$schedule->command('fleetops:send-maintenance-reminders')->daily()->withoutOverlapping()->storeOutputInDb();
|
|
143
144
|
$schedule->command('fleetops:process-operational-alerts')->everyMinute()->withoutOverlapping()->storeOutputInDb();
|
|
145
|
+
$schedule->command('fleetops:sync-telematics')->everyMinute()->withoutOverlapping()->storeOutputInDb();
|
|
144
146
|
});
|
|
145
147
|
$this->registerNotifications();
|
|
146
148
|
$this->registerExpansionsFrom(__DIR__ . '/../Expansions');
|
|
@@ -108,7 +108,7 @@ class LiveCacheService
|
|
|
108
108
|
}
|
|
109
109
|
} else {
|
|
110
110
|
// Invalidate all endpoints
|
|
111
|
-
$endpoints = ['orders', 'routes', 'coordinates', 'drivers', 'vehicles', 'places'];
|
|
111
|
+
$endpoints = ['orders', 'routes', 'coordinates', 'drivers', 'vehicles', 'places', 'operations-monitor'];
|
|
112
112
|
foreach ($endpoints as $ep) {
|
|
113
113
|
static::incrementVersion($ep);
|
|
114
114
|
}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Support\Telematics\Providers;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Exceptions\TelematicProviderException;
|
|
6
|
+
use Illuminate\Http\Client\ConnectionException;
|
|
7
|
+
use Illuminate\Http\Client\Response;
|
|
5
8
|
use Illuminate\Support\Carbon;
|
|
6
9
|
use Illuminate\Support\Facades\Http;
|
|
7
10
|
use Illuminate\Support\Facades\Log;
|
|
@@ -14,20 +17,23 @@ use Illuminate\Support\Facades\Log;
|
|
|
14
17
|
*/
|
|
15
18
|
class AfaqyProvider extends AbstractProvider
|
|
16
19
|
{
|
|
17
|
-
protected string $baseUrl
|
|
18
|
-
protected int $requestsPerMinute
|
|
20
|
+
protected string $baseUrl = 'https://api.afaqy.sa';
|
|
21
|
+
protected int $requestsPerMinute = 60;
|
|
22
|
+
protected int $dataTimeout = 120;
|
|
23
|
+
protected int $connectTimeout = 15;
|
|
24
|
+
protected int $connectionTestTimeout = 30;
|
|
25
|
+
protected int $connectionTestConnectTimeout = 10;
|
|
19
26
|
|
|
20
27
|
protected function prepareAuthentication(): void
|
|
21
28
|
{
|
|
22
29
|
$this->baseUrl = rtrim($this->credentials['base_url'] ?? $this->baseUrl, '/');
|
|
23
|
-
$token = $this->credentials['token'] ??
|
|
30
|
+
$token = $this->canRefreshToken() ? $this->authenticate() : ($this->credentials['token'] ?? null);
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
];
|
|
32
|
+
if (!$token) {
|
|
33
|
+
throw new \InvalidArgumentException('AFAQY username/password or token is required.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
$this->setToken($token);
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
public function testConnection(array $credentials): array
|
|
@@ -41,7 +47,7 @@ class AfaqyProvider extends AbstractProvider
|
|
|
41
47
|
'projection' => ['_id', 'name', 'imei', 'last_update'],
|
|
42
48
|
'filters' => new \stdClass(),
|
|
43
49
|
],
|
|
44
|
-
], true);
|
|
50
|
+
], true, $this->connectionTestTimeout, $this->connectionTestConnectTimeout);
|
|
45
51
|
|
|
46
52
|
return [
|
|
47
53
|
'success' => true,
|
|
@@ -55,7 +61,7 @@ class AfaqyProvider extends AbstractProvider
|
|
|
55
61
|
return [
|
|
56
62
|
'success' => false,
|
|
57
63
|
'message' => $e->getMessage(),
|
|
58
|
-
'metadata' => [],
|
|
64
|
+
'metadata' => method_exists($e, 'context') ? $e->context() : [],
|
|
59
65
|
];
|
|
60
66
|
}
|
|
61
67
|
}
|
|
@@ -145,6 +151,7 @@ class AfaqyProvider extends AbstractProvider
|
|
|
145
151
|
|
|
146
152
|
return [
|
|
147
153
|
'device_id' => $payload['_id'] ?? $payload['id'] ?? null,
|
|
154
|
+
'external_id' => $payload['_id'] ?? $payload['id'] ?? null,
|
|
148
155
|
'name' => $payload['name'] ?? data_get($payload, 'profile.plate_number') ?? 'Unknown Unit',
|
|
149
156
|
'provider' => 'afaqy',
|
|
150
157
|
'model' => data_get($payload, 'profile.model') ?? $payload['device'] ?? null,
|
|
@@ -187,12 +194,14 @@ class AfaqyProvider extends AbstractProvider
|
|
|
187
194
|
'event_type' => $payload['event'] ?? $payload['event_type'] ?? 'telemetry_update',
|
|
188
195
|
'message' => $payload['message'] ?? $payload['event'] ?? null,
|
|
189
196
|
'occurred_at' => $this->parseTimestamp($lastUpdate['dtt'] ?? $lastUpdate['dts'] ?? null),
|
|
197
|
+
'online' => $payload['active'] ?? null,
|
|
190
198
|
'location' => [
|
|
191
199
|
'lat' => $lastUpdate['lat'] ?? data_get($lastUpdate, 'loc.coordinates.1'),
|
|
192
200
|
'lng' => $lastUpdate['lng'] ?? data_get($lastUpdate, 'loc.coordinates.0'),
|
|
193
201
|
],
|
|
194
202
|
'speed' => $lastUpdate['speed'] ?? $lastUpdate['spd'] ?? null,
|
|
195
203
|
'heading' => $lastUpdate['angle'] ?? $lastUpdate['ang'] ?? null,
|
|
204
|
+
'altitude' => $lastUpdate['alt'] ?? null,
|
|
196
205
|
'odometer' => data_get($payload, 'counters.odometer'),
|
|
197
206
|
'ignition' => $this->extractIgnition($payload),
|
|
198
207
|
'fuel_level' => $this->extractFuelLevel($payload),
|
|
@@ -271,19 +280,64 @@ class AfaqyProvider extends AbstractProvider
|
|
|
271
280
|
]);
|
|
272
281
|
|
|
273
282
|
if ($response->failed()) {
|
|
274
|
-
throw new
|
|
283
|
+
throw new TelematicProviderException('AFAQY authentication failed with status ' . $response->status(), $this->providerErrorContext('/auth/login', $response, false));
|
|
275
284
|
}
|
|
276
285
|
|
|
277
286
|
$token = data_get($response->json(), 'data.token');
|
|
278
287
|
|
|
279
288
|
if (!$token) {
|
|
280
|
-
throw new
|
|
289
|
+
throw new TelematicProviderException('AFAQY authentication did not return a token.', $this->providerErrorContext('/auth/login', $response, false));
|
|
281
290
|
}
|
|
282
291
|
|
|
283
292
|
return $token;
|
|
284
293
|
}
|
|
285
294
|
|
|
286
|
-
protected function afaqyPost(string $endpoint, array $payload = [], bool $tokenInQuery = false): array
|
|
295
|
+
protected function afaqyPost(string $endpoint, array $payload = [], bool $tokenInQuery = false, ?int $timeout = null, ?int $connectTimeout = null): array
|
|
296
|
+
{
|
|
297
|
+
return $this->authenticatedPost($endpoint, $payload, $tokenInQuery, true, $timeout, $connectTimeout);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
protected function authenticatedPost(string $endpoint, array $payload = [], bool $tokenInQuery = false, bool $allowRetry = true, ?int $timeout = null, ?int $connectTimeout = null): array
|
|
301
|
+
{
|
|
302
|
+
[$url, $body] = $this->buildAuthenticatedRequest($endpoint, $payload, $tokenInQuery);
|
|
303
|
+
|
|
304
|
+
$startedAt = microtime(true);
|
|
305
|
+
$timeout ??= $this->dataTimeout;
|
|
306
|
+
$connectTimeout ??= $this->connectTimeout;
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
$response = Http::withHeaders($this->headers)
|
|
310
|
+
->timeout($timeout)
|
|
311
|
+
->connectTimeout($connectTimeout)
|
|
312
|
+
->post($url, $body);
|
|
313
|
+
} catch (ConnectionException $e) {
|
|
314
|
+
throw new TelematicProviderException('AFAQY API request timed out while waiting for provider response.', $this->transportErrorContext($endpoint, $payload, $e, !$allowRetry, $startedAt, $timeout, $connectTimeout), previous: $e);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if ($this->isTokenRejected($response)) {
|
|
318
|
+
if (!$allowRetry || !$this->canRefreshToken()) {
|
|
319
|
+
$message = $this->canRefreshToken()
|
|
320
|
+
? 'AFAQY token rejected after refresh with status ' . $response->status()
|
|
321
|
+
: 'AFAQY token rejected and username/password credentials are required to refresh it.';
|
|
322
|
+
|
|
323
|
+
throw new TelematicProviderException($message, $this->providerErrorContext($endpoint, $response, !$allowRetry));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
Log::warning('AFAQY token rejected; refreshing token and retrying request', $this->providerErrorContext($endpoint, $response, true));
|
|
327
|
+
|
|
328
|
+
$this->refreshToken();
|
|
329
|
+
|
|
330
|
+
return $this->authenticatedPost($endpoint, $payload, $tokenInQuery, false, $timeout, $connectTimeout);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if ($response->failed()) {
|
|
334
|
+
throw new TelematicProviderException('AFAQY API request failed with status ' . $response->status(), $this->providerErrorContext($endpoint, $response, !$allowRetry));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return $response->json() ?? [];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
protected function buildAuthenticatedRequest(string $endpoint, array $payload = [], bool $tokenInQuery = false): array
|
|
287
341
|
{
|
|
288
342
|
$url = $this->baseUrl . $endpoint;
|
|
289
343
|
$body = $tokenInQuery ? $payload : array_merge(['token' => $this->credentials['token']], $payload);
|
|
@@ -292,15 +346,84 @@ class AfaqyProvider extends AbstractProvider
|
|
|
292
346
|
$url .= (str_contains($url, '?') ? '&' : '?') . http_build_query(['token' => $this->credentials['token']]);
|
|
293
347
|
}
|
|
294
348
|
|
|
295
|
-
$
|
|
296
|
-
|
|
297
|
-
->post($url, $body);
|
|
349
|
+
return [$url, $body];
|
|
350
|
+
}
|
|
298
351
|
|
|
299
|
-
|
|
300
|
-
|
|
352
|
+
protected function setToken(string $token): void
|
|
353
|
+
{
|
|
354
|
+
$this->credentials['token'] = $token;
|
|
355
|
+
$this->headers = [
|
|
356
|
+
'Accept' => 'application/json',
|
|
357
|
+
'Content-Type' => 'application/json',
|
|
358
|
+
'Authorization' => 'Bearer ' . $token,
|
|
359
|
+
];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
protected function refreshToken(): void
|
|
363
|
+
{
|
|
364
|
+
unset($this->credentials['token']);
|
|
365
|
+
|
|
366
|
+
$this->setToken($this->authenticate());
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
protected function canRefreshToken(): bool
|
|
370
|
+
{
|
|
371
|
+
return !empty($this->credentials['username']) && !empty($this->credentials['password']);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
protected function isTokenRejected(Response $response): bool
|
|
375
|
+
{
|
|
376
|
+
return in_array($response->status(), [401, 403], true);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
protected function providerErrorContext(string $endpoint, Response $response, bool $retryAttempted): array
|
|
380
|
+
{
|
|
381
|
+
$json = $response->json() ?? [];
|
|
382
|
+
$json = is_array($json) ? $json : [];
|
|
383
|
+
|
|
384
|
+
return array_filter([
|
|
385
|
+
'provider' => 'afaqy',
|
|
386
|
+
'endpoint' => $endpoint,
|
|
387
|
+
'status_code' => $response->status(),
|
|
388
|
+
'provider_code' => data_get($json, 'code') ?? data_get($json, 'error.code') ?? data_get($json, 'error_code'),
|
|
389
|
+
'provider_message' => $this->providerErrorMessage($json),
|
|
390
|
+
'retry_attempted' => $retryAttempted,
|
|
391
|
+
], fn ($value) => $value !== null);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
protected function providerErrorMessage(array $json): ?string
|
|
395
|
+
{
|
|
396
|
+
$message = data_get($json, 'message')
|
|
397
|
+
?? data_get($json, 'error.message')
|
|
398
|
+
?? data_get($json, 'error_description')
|
|
399
|
+
?? data_get($json, 'error');
|
|
400
|
+
|
|
401
|
+
return is_scalar($message) ? (string) $message : null;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
protected function transportErrorContext(string $endpoint, array $payload, ConnectionException $e, bool $retryAttempted, float $startedAt, int $timeout, int $connectTimeout): array
|
|
405
|
+
{
|
|
406
|
+
return array_filter([
|
|
407
|
+
'provider' => 'afaqy',
|
|
408
|
+
'endpoint' => $endpoint,
|
|
409
|
+
'requested_limit' => data_get($payload, 'data.limit'),
|
|
410
|
+
'requested_offset' => data_get($payload, 'data.offset'),
|
|
411
|
+
'timeout' => $timeout,
|
|
412
|
+
'connect_timeout' => $connectTimeout,
|
|
413
|
+
'elapsed_ms' => (int) ((microtime(true) - $startedAt) * 1000),
|
|
414
|
+
'bytes_received' => $this->extractBytesReceived($e->getMessage()),
|
|
415
|
+
'retry_attempted' => $retryAttempted,
|
|
416
|
+
'transport_error' => 'connection_exception',
|
|
417
|
+
], fn ($value) => $value !== null);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
protected function extractBytesReceived(string $message): ?int
|
|
421
|
+
{
|
|
422
|
+
if (preg_match('/with\s+(\d+)\s+bytes\s+received/i', $message, $matches)) {
|
|
423
|
+
return (int) $matches[1];
|
|
301
424
|
}
|
|
302
425
|
|
|
303
|
-
return
|
|
426
|
+
return null;
|
|
304
427
|
}
|
|
305
428
|
|
|
306
429
|
protected function compactLastUpdate(array $lastUpdate): array
|