@fleetbase/fleetops-engine 0.6.69 → 0.6.70
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/RELEASE.md +41 -8
- package/addon/components/admin/telematics-settings.hbs +143 -0
- package/addon/components/admin/telematics-settings.js +145 -0
- package/addon/components/fuel-integration/hub.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +37 -17
- package/addon/components/map/leaflet-live-map.hbs +4 -242
- package/addon/components/map/leaflet-live-map.js +2 -1
- package/addon/components/map/marker-card/driver.hbs +57 -0
- package/addon/components/map/marker-card/driver.js +3 -0
- package/addon/components/map/marker-card/vehicle.hbs +78 -0
- package/addon/components/map/marker-card/vehicle.js +3 -0
- package/addon/components/telematic/hub.hbs +1 -1
- package/addon/components/widget/live-fleet.hbs +8 -40
- package/addon/controllers/analytics/reports/index.js +5 -0
- package/addon/controllers/connectivity/devices/index.js +4 -0
- package/addon/controllers/connectivity/fuel-providers/index.js +6 -4
- package/addon/controllers/connectivity/sensors/index.js +4 -0
- package/addon/controllers/connectivity/telematics/index.js +4 -0
- package/addon/controllers/maintenance/equipment/index.js +11 -4
- package/addon/controllers/maintenance/inspection-forms/index.js +2 -2
- package/addon/controllers/maintenance/inspection-submissions/index.js +8 -2
- package/addon/controllers/maintenance/maintenances/index.js +4 -0
- package/addon/controllers/maintenance/parts/index.js +11 -4
- package/addon/controllers/maintenance/schedules/index.js +17 -4
- package/addon/controllers/maintenance/work-orders/index.js +11 -4
- package/addon/controllers/management/contacts/index.js +4 -0
- package/addon/controllers/management/drivers/index.js +4 -0
- package/addon/controllers/management/fleets/index.js +4 -0
- package/addon/controllers/management/fuel-reports/index.js +4 -0
- package/addon/controllers/management/fuel-transactions/index.js +9 -7
- package/addon/controllers/management/issues/index.js +4 -0
- package/addon/controllers/management/places/index.js +4 -0
- package/addon/controllers/management/vehicles/index.js +4 -0
- package/addon/controllers/management/vendors/index.js +4 -0
- package/addon/controllers/operations/orders/index/details.js +30 -1
- package/addon/controllers/operations/orders/index.js +7 -0
- package/addon/controllers/operations/service-rates/index.js +3 -0
- package/addon/controllers/settings/index.js +13 -0
- package/addon/controllers/settings/telematics.js +113 -0
- package/addon/extension.js +38 -6
- package/addon/routes/analytics/index.js +14 -1
- package/addon/routes/analytics/reports/index/details.js +1 -1
- package/addon/routes/analytics/reports/index/edit.js +1 -1
- package/addon/routes/analytics/reports/index/new.js +14 -1
- package/addon/routes/analytics/reports.js +14 -1
- package/addon/routes/connectivity/devices/index/new.js +14 -1
- package/addon/routes/connectivity/devices.js +14 -1
- package/addon/routes/connectivity/events.js +14 -1
- package/addon/routes/connectivity/fuel-providers/details/transactions.js +12 -0
- package/addon/routes/connectivity/fuel-providers/details.js +9 -0
- package/addon/routes/connectivity/fuel-providers/edit.js +9 -0
- package/addon/routes/connectivity/fuel-providers/new.js +14 -1
- package/addon/routes/connectivity/fuel-providers.js +14 -1
- package/addon/routes/connectivity/sensors/index/new.js +14 -1
- package/addon/routes/connectivity/sensors.js +14 -1
- package/addon/routes/connectivity/telematics/new.js +14 -1
- package/addon/routes/connectivity/telematics.js +14 -1
- package/addon/routes/connectivity/tracking.js +14 -1
- package/addon/routes/maintenance/equipment/index/details.js +2 -2
- package/addon/routes/maintenance/equipment/index/edit.js +2 -2
- package/addon/routes/maintenance/equipment/index/new.js +14 -1
- package/addon/routes/maintenance/equipment.js +14 -1
- package/addon/routes/maintenance/index.js +14 -1
- package/addon/routes/maintenance/inspection-forms/index/details.js +10 -1
- package/addon/routes/maintenance/inspection-forms/index/edit.js +10 -1
- package/addon/routes/maintenance/inspection-forms/index/new.js +14 -1
- package/addon/routes/maintenance/inspection-forms.js +14 -1
- package/addon/routes/maintenance/inspection-submissions/index/details.js +10 -1
- package/addon/routes/maintenance/inspection-submissions/index/edit.js +10 -1
- package/addon/routes/maintenance/inspection-submissions/index/new.js +14 -1
- package/addon/routes/maintenance/inspection-submissions.js +14 -1
- package/addon/routes/maintenance/maintenances/index/details.js +2 -2
- package/addon/routes/maintenance/maintenances/index/edit.js +2 -2
- package/addon/routes/maintenance/maintenances/index/new.js +14 -1
- package/addon/routes/maintenance/maintenances.js +14 -1
- package/addon/routes/maintenance/parts/index/details.js +2 -2
- package/addon/routes/maintenance/parts/index/edit.js +2 -2
- package/addon/routes/maintenance/parts/index/new.js +14 -1
- package/addon/routes/maintenance/parts.js +14 -1
- package/addon/routes/maintenance/schedules/index/details.js +2 -2
- package/addon/routes/maintenance/schedules/index/edit.js +2 -2
- package/addon/routes/maintenance/schedules/index/new.js +15 -1
- package/addon/routes/maintenance/schedules.js +15 -1
- package/addon/routes/maintenance/work-orders/index/details.js +2 -2
- package/addon/routes/maintenance/work-orders/index/edit.js +2 -2
- package/addon/routes/maintenance/work-orders/index/new.js +14 -1
- package/addon/routes/maintenance/work-orders.js +14 -1
- package/addon/routes/management/fuel-transactions/index/details.js +9 -0
- package/addon/routes/management/fuel-transactions.js +14 -1
- package/addon/routes/management/index.js +13 -0
- package/addon/routes/management/trailers.js +16 -0
- package/addon/routes/management/vendors/integrated/details.js +14 -1
- package/addon/routes/management/vendors/integrated/edit.js +14 -1
- package/addon/routes/management/vendors/integrated/new.js +14 -1
- package/addon/routes/management/vendors/integrated.js +14 -1
- package/addon/routes/operations/orders.js +40 -1
- package/addon/routes/operations/routes/index/details.js +14 -1
- package/addon/routes/operations/routes/index/new.js +13 -0
- package/addon/routes/operations/routes.js +14 -1
- package/addon/routes/operations/service-rates/index/new.js +11 -0
- package/addon/routes/operations/service-rates.js +14 -1
- package/addon/routes/settings/avatars.js +14 -1
- package/addon/routes/settings/custom-fields.js +14 -1
- package/addon/routes/settings/map.js +14 -1
- package/addon/routes/settings/notifications.js +11 -0
- package/addon/routes/settings/orchestrator.js +1 -1
- package/addon/routes/settings/payments/index.js +1 -1
- package/addon/routes/settings/routing.js +14 -1
- package/addon/routes/settings/scheduling.js +14 -1
- package/addon/routes/settings/telematics.js +21 -0
- package/addon/routes/virtual.js +11 -0
- package/addon/routes.js +1 -0
- package/addon/templates/settings/telematics.hbs +70 -0
- package/app/components/admin/telematics-settings.js +1 -0
- package/app/components/map/marker-card/driver.js +1 -0
- package/app/components/map/marker-card/vehicle.js +1 -0
- package/app/controllers/settings/telematics.js +1 -0
- package/app/routes/settings/telematics.js +1 -0
- package/app/templates/settings/telematics.js +1 -0
- package/composer.json +2 -2
- package/docs/AFAQY.md +1 -1
- package/docs/TELEMATICS_QUEUES.md +1 -1
- package/docs/TELEMETRY_ARCHITECTURE.md +72 -1
- package/extension.json +1 -1
- package/package.json +1 -1
- package/server/config/telemetry.php +12 -0
- package/server/migrations/2026_09_23_000001_add_retention_index_to_device_events.php +40 -0
- package/server/migrations/2026_09_23_000002_add_retention_index_to_positions.php +40 -0
- package/server/migrations/2026_09_23_000003_add_retention_index_to_telematic_sync_runs.php +40 -0
- package/server/src/Auth/Schemas/FleetOps.php +104 -5
- package/server/src/Console/Commands/DrainTelematicInbox.php +3 -13
- package/server/src/Console/Commands/ProcessOperationalAlerts.php +3 -3
- package/server/src/Console/Commands/PruneTelematicsData.php +318 -0
- package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +9 -0
- package/server/src/Http/Controllers/FleetOpsController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +14 -0
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +22 -0
- package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +9 -3
- package/server/src/Http/Controllers/Internal/v1/InspectionFormController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +16 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/ManifestController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/MetricsController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +42 -1
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +12 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +22 -0
- package/server/src/Http/Controllers/Internal/v1/PaymentController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/RadarController.php +19 -0
- package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +9 -3
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +465 -14
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/TrailerController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/VendorController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +11 -0
- package/server/src/Jobs/DispatchTelematicsRetentionJobs.php +70 -0
- package/server/src/Jobs/PruneTelematicsDataJob.php +74 -0
- package/server/src/Listeners/NotifyDriverOnShiftChange.php +3 -3
- package/server/src/Mail/CustomerCredentialsMail.php +1 -1
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +9 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +3 -0
- package/server/src/Support/Analytics/LiveFleet.php +18 -2
- package/server/src/Support/Authorization.php +103 -0
- package/server/src/Support/Database/TableIndexes.php +66 -0
- package/server/src/Support/OrderTracker.php +2 -2
- package/server/src/Support/Reporting/FleetOpsReportSchema.php +476 -116
- package/server/src/Support/Telematics/Retention/RetentionPolicy.php +300 -0
- package/server/src/Support/Telematics/Retention/TelemetryActivity.php +40 -0
- package/server/src/Support/Telematics/TelematicService.php +12 -2
- package/server/src/Support/Telematics/Telemetry/Ingestor.php +4 -2
- package/server/src/Tracking/TrackingIntelligenceService.php +1 -1
- package/server/src/Tracking/TrackingOptions.php +2 -2
- package/server/src/Traits/AuthorizesMethods.php +37 -0
- package/server/src/routes.php +9 -1
- package/server/tests/AnalyticsRoutesTest.php +29 -2
- package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +16 -1
- package/server/tests/Feature/Http/Api/TelematicsRetentionIndexMigrationTest.php +115 -0
- package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +71 -0
- package/server/tests/Feature/Http/Internal/TelematicsStorageDeadlinesTest.php +119 -0
- package/server/tests/InternalImportExportControllerContractsTest.php +5 -8
- package/server/tests/NavigatorControllerContractsTest.php +36 -4
- package/server/tests/NotificationAndMailContractsTest.php +31 -0
- package/server/tests/ObserverContractsTest.php +1 -1
- package/server/tests/OrchestratorConsumableApiTest.php +42 -0
- package/server/tests/ProviderContractsTest.php +10 -0
- package/server/tests/ReportSchemaContractsTest.php +120 -6
- package/server/tests/SettingControllerContractsTest.php +429 -18
- package/server/tests/SmallControllerContractsTest.php +8 -5
- package/server/tests/Support/PruneTelematicsDataProbe.php +55 -0
- package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +2 -2
- package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +353 -0
- package/server/tests/Unit/Jobs/DispatchTelematicsRetentionJobsTest.php +114 -0
- package/server/tests/Unit/Jobs/PruneTelematicsDataJobTest.php +60 -0
- package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +20 -0
- package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +6 -0
- package/server/tests/Unit/Support/AuthorizationTest.php +142 -0
- package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +12 -1
- package/server/tests/Unit/Support/MoreQuerySeamsTest.php +11 -2
- package/server/tests/Unit/Support/Telematics/Retention/RetentionPolicyTest.php +190 -0
- package/server/tests/Unit/Support/Telematics/Retention/TelemetryActivityTest.php +68 -0
- package/server/tests/Unit/Tracking/TrackingOptionsCompanySettingsTest.php +109 -0
- package/translations/en-us.yaml +48 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Fleetbase\FleetOps\Support\Authorization;
|
|
4
|
+
use Fleetbase\FleetOps\Traits\AuthorizesMethods;
|
|
5
|
+
use Fleetbase\Models\User;
|
|
6
|
+
use Illuminate\Database\ConnectionResolver;
|
|
7
|
+
use Illuminate\Database\Eloquent\Model;
|
|
8
|
+
use Illuminate\Database\SQLiteConnection;
|
|
9
|
+
use Illuminate\Http\Exceptions\HttpResponseException;
|
|
10
|
+
use Illuminate\Http\Request;
|
|
11
|
+
use Illuminate\Routing\Controller;
|
|
12
|
+
|
|
13
|
+
if (!function_exists('Fleetbase\\Support\\auth')) {
|
|
14
|
+
eval('namespace Fleetbase\\Support; function auth() { return \\app("fleetops.authorization.test-auth"); }');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!function_exists('Fleetbase\\Support\\session')) {
|
|
18
|
+
eval('namespace Fleetbase\\Support; function session($key = null, $default = null) { return \\session($key, $default); }');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class FleetOpsAuthorizationUser extends User
|
|
22
|
+
{
|
|
23
|
+
public array $grantedPermissions = [];
|
|
24
|
+
public bool $permissionStoreUnavailable = false;
|
|
25
|
+
|
|
26
|
+
public function hasPermissionTo($permission, $guardName = null): bool
|
|
27
|
+
{
|
|
28
|
+
if ($this->permissionStoreUnavailable) {
|
|
29
|
+
throw new RuntimeException('Permission store unavailable');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return in_array($permission->name, $this->grantedPermissions, true);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
beforeEach(function () {
|
|
37
|
+
$connection = new SQLiteConnection(new PDO('sqlite::memory:'));
|
|
38
|
+
$resolver = new ConnectionResolver(['mysql' => $connection]);
|
|
39
|
+
$resolver->setDefaultConnection('mysql');
|
|
40
|
+
Model::setConnectionResolver($resolver);
|
|
41
|
+
config()->set('auth.defaults.guard', 'sanctum');
|
|
42
|
+
config()->set('permission.table_names.permissions', 'permissions');
|
|
43
|
+
$connection->getSchemaBuilder()->create('permissions', function ($table) {
|
|
44
|
+
$table->string('id')->primary();
|
|
45
|
+
$table->string('name');
|
|
46
|
+
$table->string('guard_name')->default('sanctum');
|
|
47
|
+
});
|
|
48
|
+
$connection->table('permissions')->insert([
|
|
49
|
+
['id' => 'optimize', 'name' => 'fleet-ops optimize order'],
|
|
50
|
+
['id' => 'dispatch', 'name' => 'fleet-ops dispatch order'],
|
|
51
|
+
['id' => 'resource', 'name' => 'fleet-ops * order'],
|
|
52
|
+
['id' => 'service', 'name' => 'fleet-ops *'],
|
|
53
|
+
]);
|
|
54
|
+
session(['user' => null]);
|
|
55
|
+
$this->user = new FleetOpsAuthorizationUser();
|
|
56
|
+
$this->user->setRawAttributes(['type' => 'user'], true);
|
|
57
|
+
$this->auth = new class($this->user) {
|
|
58
|
+
public bool $unavailable = false;
|
|
59
|
+
|
|
60
|
+
public function __construct(public ?User $currentUser)
|
|
61
|
+
{
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public function user(): ?User
|
|
65
|
+
{
|
|
66
|
+
if ($this->unavailable) {
|
|
67
|
+
throw new RuntimeException('Session unavailable');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return $this->currentUser;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
app()->instance('fleetops.authorization.test-auth', $this->auth);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('operation authorization accepts exact and wildcard grants', function (string $grant) {
|
|
77
|
+
$this->user->grantedPermissions = [$grant];
|
|
78
|
+
|
|
79
|
+
expect(Authorization::canAny('optimize order'))->toBeTrue();
|
|
80
|
+
Authorization::authorize('optimize order');
|
|
81
|
+
})->with(['fleet-ops optimize order', 'fleet-ops * order', 'fleet-ops *']);
|
|
82
|
+
|
|
83
|
+
test('operation authorization accepts any requested permission and rejects missing grants', function () {
|
|
84
|
+
$this->user->grantedPermissions = ['fleet-ops dispatch order'];
|
|
85
|
+
|
|
86
|
+
expect(Authorization::canAny('optimize order'))->toBeFalse()
|
|
87
|
+
->and(Authorization::canAny('optimize order', 'dispatch order'))->toBeTrue()
|
|
88
|
+
->and(Authorization::canAny('list unseeded-resource'))->toBeTrue();
|
|
89
|
+
expect(fn () => Authorization::authorize('optimize order'))->toThrow(HttpResponseException::class);
|
|
90
|
+
|
|
91
|
+
$this->user->permissionStoreUnavailable = true;
|
|
92
|
+
expect(Authorization::canAny('dispatch order'))->toBeFalse();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('platform administrators pass operations while system settings reject ordinary users', function () {
|
|
96
|
+
expect(fn () => Authorization::authorizeAdmin())->toThrow(HttpResponseException::class);
|
|
97
|
+
$this->user->setAttribute('type', 'admin');
|
|
98
|
+
expect(Authorization::canAny('optimize order'))->toBeTrue();
|
|
99
|
+
Authorization::authorizeAdmin();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('authorization fails closed when the authenticated user is missing or cannot be read', function () {
|
|
103
|
+
$this->auth->currentUser = null;
|
|
104
|
+
expect(Authorization::canAny('optimize order'))->toBeFalse();
|
|
105
|
+
expect(fn () => Authorization::authorizeAdmin())->toThrow(HttpResponseException::class);
|
|
106
|
+
$this->auth->unavailable = true;
|
|
107
|
+
expect(Authorization::canAny('optimize order'))->toBeFalse();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('controller middleware applies the mapped operation and administrator checks', function () {
|
|
111
|
+
$controller = new class extends Controller {
|
|
112
|
+
use AuthorizesMethods;
|
|
113
|
+
|
|
114
|
+
public function __construct()
|
|
115
|
+
{
|
|
116
|
+
$this->authorizeMethods(['optimize' => ['optimize order', 'dispatch order'], 'settings' => 'admin']);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
$middleware = $controller->getMiddleware();
|
|
120
|
+
$request = Request::create('/');
|
|
121
|
+
$this->user->grantedPermissions = ['fleet-ops dispatch order'];
|
|
122
|
+
|
|
123
|
+
expect($middleware[0]['options']['only'])->toBe(['optimize'])
|
|
124
|
+
->and($middleware[0]['middleware']($request, fn ($nextRequest) => $nextRequest))->toBe($request);
|
|
125
|
+
expect(fn () => $middleware[1]['middleware']($request, fn () => 'allowed'))->toThrow(HttpResponseException::class);
|
|
126
|
+
$this->user->setAttribute('type', 'admin');
|
|
127
|
+
expect($middleware[1]['middleware']($request, fn () => 'allowed'))->toBe('allowed');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('analytics controllers enforce authorization before invoking their next middleware', function (string $class) {
|
|
131
|
+
$connection = Model::getConnectionResolver()->connection();
|
|
132
|
+
$connection->table('permissions')->insert(['id' => 'analytics', 'name' => 'fleet-ops view analytics']);
|
|
133
|
+
$controller = new $class();
|
|
134
|
+
$middleware = $controller->getMiddleware()[0]['middleware'];
|
|
135
|
+
$request = Request::create('/analytics');
|
|
136
|
+
expect(fn () => $middleware($request, fn () => 'private analytics'))->toThrow(HttpResponseException::class);
|
|
137
|
+
$this->user->grantedPermissions = ['fleet-ops view analytics'];
|
|
138
|
+
expect($middleware($request, fn ($forwarded) => $forwarded))->toBe($request);
|
|
139
|
+
})->with([
|
|
140
|
+
Fleetbase\FleetOps\Http\Controllers\Internal\v1\AnalyticsController::class,
|
|
141
|
+
Fleetbase\FleetOps\Http\Controllers\Internal\v1\MetricsController::class,
|
|
142
|
+
]);
|
|
@@ -184,10 +184,21 @@ test('operational alert helpers query orders positions and settings', function (
|
|
|
184
184
|
$order = Order::where('uuid', 'order-1')->withoutGlobalScopes()->first();
|
|
185
185
|
expect($probe->callHelper('latestPositionForOrder', $order))->toBeInstanceOf(Position::class);
|
|
186
186
|
|
|
187
|
-
$settings = $probe->callHelper('alertSettings');
|
|
187
|
+
$settings = $probe->callHelper('alertSettings', $order);
|
|
188
188
|
expect($settings['late_departures']['enabled'])->toBeTrue()
|
|
189
189
|
->and($settings['route_deviations']['distance_threshold_meters'])->toBe(500)
|
|
190
190
|
->and($settings['prolonged_stoppages']['duration_threshold_minutes'])->toBe(30);
|
|
191
|
+
|
|
192
|
+
// Settings resolve from the order's company without a company session
|
|
193
|
+
$connection->table('settings')->insert([
|
|
194
|
+
'key' => 'company.company-1.tracking',
|
|
195
|
+
'value' => json_encode(['alerts' => ['late_departures' => ['enabled' => false], 'route_deviations' => ['distance_threshold_meters' => 750]]]),
|
|
196
|
+
]);
|
|
197
|
+
session(['company' => null]);
|
|
198
|
+
$settings = $probe->callHelper('alertSettings', $order);
|
|
199
|
+
session(['company' => 'company-1']);
|
|
200
|
+
expect($settings['late_departures']['enabled'])->toBeFalse()
|
|
201
|
+
->and($settings['route_deviations']['distance_threshold_meters'])->toBe(750);
|
|
191
202
|
});
|
|
192
203
|
|
|
193
204
|
test('route point collection normalizes pairs and measures distances', function () {
|
|
@@ -384,13 +384,22 @@ test('index driver resource counts assigned orders and names the current one', f
|
|
|
384
384
|
});
|
|
385
385
|
|
|
386
386
|
test('shift change listener reads company scheduling settings', function () {
|
|
387
|
-
fleetopsMoreSeamBoot();
|
|
387
|
+
$connection = fleetopsMoreSeamBoot();
|
|
388
388
|
|
|
389
389
|
$class = Fleetbase\FleetOps\Listeners\NotifyDriverOnShiftChange::class;
|
|
390
390
|
$listener = (new ReflectionClass($class))->newInstanceWithoutConstructor();
|
|
391
391
|
|
|
392
392
|
// With nothing stored the lookup falls back to the empty default
|
|
393
|
-
expect(fleetopsMoreSeamInvoke($listener, $class, 'getSchedulingSettings'))->toBe([]);
|
|
393
|
+
expect(fleetopsMoreSeamInvoke($listener, $class, 'getSchedulingSettings', ['company-seam-2']))->toBe([]);
|
|
394
|
+
|
|
395
|
+
// Queued listeners have no company session, so the schedule's company is used
|
|
396
|
+
$connection->table('settings')->insert(['key' => 'company.company-seam-3.fleet-ops.scheduling-settings', 'value' => json_encode(['notify_drivers_on_shift_change' => true])]);
|
|
397
|
+
session(['company' => null]);
|
|
398
|
+
$settings = fleetopsMoreSeamInvoke($listener, $class, 'getSchedulingSettings', ['company-seam-3']);
|
|
399
|
+
session(['company' => 'company-seam-2']);
|
|
400
|
+
|
|
401
|
+
expect($settings)->toBe(['notify_drivers_on_shift_change' => true])
|
|
402
|
+
->and(fleetopsMoreSeamInvoke($listener, $class, 'getSchedulingSettings', [null]))->toBe([]);
|
|
394
403
|
});
|
|
395
404
|
|
|
396
405
|
test('order insights capability builds its own company-scoped order query', function () {
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Fleetbase\FleetOps\Support\Telematics\Retention\RetentionPolicy;
|
|
4
|
+
use Illuminate\Support\Carbon;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* RetentionPolicy combines administrator policy with allowed company history
|
|
8
|
+
* preferences, enforces maximums, and caches policies for long-lived workers.
|
|
9
|
+
*/
|
|
10
|
+
function fleetopsRetentionResolver(array $system, array $company, ?array &$calls = null): void
|
|
11
|
+
{
|
|
12
|
+
$calls = [];
|
|
13
|
+
RetentionPolicy::$settingsResolver = function (string $scope, string $key, mixed $default, ?string $companyUuid) use ($system, $company, &$calls) {
|
|
14
|
+
$calls[] = [$scope, $key, $companyUuid];
|
|
15
|
+
expect($key)->toBe(RetentionPolicy::SETTING_KEY);
|
|
16
|
+
|
|
17
|
+
return $scope === 'system' ? $system : ($company[$companyUuid] ?? $default);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
beforeEach(function () {
|
|
22
|
+
RetentionPolicy::flush();
|
|
23
|
+
RetentionPolicy::$settingsResolver = null;
|
|
24
|
+
config(['telematics.telemetry' => ['event_retention_days' => 45, 'processed_retention_hours' => 48, 'log_telemetry_activity' => true, 'poll_queue' => 'default']]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(function () {
|
|
28
|
+
RetentionPolicy::flush();
|
|
29
|
+
RetentionPolicy::$settingsResolver = null;
|
|
30
|
+
Carbon::setTestNow();
|
|
31
|
+
config(['telematics.telemetry' => []]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('defaults come from package config, overridden by the admin setting, with everything clamped', function () {
|
|
35
|
+
fleetopsRetentionResolver([], []);
|
|
36
|
+
expect(RetentionPolicy::defaults())->toBe([
|
|
37
|
+
'event_retention_days' => 45,
|
|
38
|
+
'event_compact_after_days' => 7,
|
|
39
|
+
'position_retention_days' => 90,
|
|
40
|
+
'processed_retention_hours' => 48,
|
|
41
|
+
'quarantine_retention_days' => 7,
|
|
42
|
+
'sync_run_retention_days' => 7,
|
|
43
|
+
'max_event_retention_days' => 0,
|
|
44
|
+
'max_position_retention_days' => 0,
|
|
45
|
+
'log_telemetry_activity' => true,
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
RetentionPolicy::flush();
|
|
49
|
+
fleetopsRetentionResolver([
|
|
50
|
+
'event_retention_days' => 99999,
|
|
51
|
+
'event_compact_after_days' => '',
|
|
52
|
+
'position_retention_days' => 0,
|
|
53
|
+
'quarantine_retention_days' => -5,
|
|
54
|
+
'processed_retention_hours' => null,
|
|
55
|
+
'log_telemetry_activity' => 'no',
|
|
56
|
+
'ignored' => 'value',
|
|
57
|
+
], []);
|
|
58
|
+
$defaults = RetentionPolicy::defaults();
|
|
59
|
+
expect($defaults['event_retention_days'])->toBe(3650)
|
|
60
|
+
->and($defaults['event_compact_after_days'])->toBe(7)
|
|
61
|
+
->and($defaults['position_retention_days'])->toBe(0)
|
|
62
|
+
->and($defaults['quarantine_retention_days'])->toBe(1)
|
|
63
|
+
->and($defaults['processed_retention_hours'])->toBe(48)
|
|
64
|
+
->and($defaults['log_telemetry_activity'])->toBeFalse()
|
|
65
|
+
->and($defaults)->not->toHaveKey('ignored')
|
|
66
|
+
->and(RetentionPolicy::keys())->toBe(array_keys(RetentionPolicy::FALLBACKS));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('company policies layer over the defaults and are cached until flushed', function () {
|
|
70
|
+
fleetopsRetentionResolver(['position_retention_days' => 30], ['company-1' => ['event_retention_days' => 10, 'log_telemetry_activity' => false]], $calls);
|
|
71
|
+
|
|
72
|
+
$policy = RetentionPolicy::forCompany('company-1');
|
|
73
|
+
expect($policy->get('event_retention_days'))->toBe(10)
|
|
74
|
+
->and($policy->get('position_retention_days'))->toBe(30)
|
|
75
|
+
->and($policy->get('processed_retention_hours'))->toBe(48)
|
|
76
|
+
->and($policy->logsTelemetryActivity())->toBeTrue()
|
|
77
|
+
->and($policy->toArray())->toHaveKeys(RetentionPolicy::keys())
|
|
78
|
+
->and(RetentionPolicy::forCompany('company-1'))->toBe($policy)
|
|
79
|
+
->and(RetentionPolicy::forCompany(null)->toArray())->toBe(RetentionPolicy::defaults())
|
|
80
|
+
->and(RetentionPolicy::forCompany('company-2')->get('event_retention_days'))->toBe(45)
|
|
81
|
+
->and(array_column($calls, 0))->toBe(['system', 'company', 'company']);
|
|
82
|
+
|
|
83
|
+
RetentionPolicy::flush('company-1');
|
|
84
|
+
RetentionPolicy::forCompany('company-1');
|
|
85
|
+
RetentionPolicy::forCompany('company-2');
|
|
86
|
+
expect(count($calls))->toBe(4);
|
|
87
|
+
|
|
88
|
+
RetentionPolicy::flush();
|
|
89
|
+
RetentionPolicy::forCompany('company-2');
|
|
90
|
+
expect(array_column($calls, 0))->toBe(['system', 'company', 'company', 'company', 'system', 'company']);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('company history respects admin maximums and cannot override infrastructure or logging', function () {
|
|
94
|
+
fleetopsRetentionResolver([
|
|
95
|
+
'event_retention_days' => 0,
|
|
96
|
+
'position_retention_days' => 90,
|
|
97
|
+
'max_event_retention_days' => 30,
|
|
98
|
+
'max_position_retention_days' => 45,
|
|
99
|
+
'log_telemetry_activity' => false,
|
|
100
|
+
], [
|
|
101
|
+
'company-1' => [
|
|
102
|
+
'event_retention_days' => 0,
|
|
103
|
+
'position_retention_days' => 120,
|
|
104
|
+
'max_event_retention_days' => 0,
|
|
105
|
+
'max_position_retention_days' => 0,
|
|
106
|
+
'event_compact_after_days' => 0,
|
|
107
|
+
'processed_retention_hours' => 0,
|
|
108
|
+
'quarantine_retention_days' => 0,
|
|
109
|
+
'sync_run_retention_days' => 0,
|
|
110
|
+
'log_telemetry_activity' => true,
|
|
111
|
+
],
|
|
112
|
+
'company-2' => ['event_retention_days' => 5, 'position_retention_days' => 15],
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
$policy = RetentionPolicy::forCompany('company-1');
|
|
116
|
+
expect($policy->get('event_retention_days'))->toBe(30)
|
|
117
|
+
->and($policy->get('position_retention_days'))->toBe(45)
|
|
118
|
+
->and($policy->get('event_compact_after_days'))->toBe(7)
|
|
119
|
+
->and($policy->get('processed_retention_hours'))->toBe(48)
|
|
120
|
+
->and($policy->get('quarantine_retention_days'))->toBe(7)
|
|
121
|
+
->and($policy->get('sync_run_retention_days'))->toBe(7)
|
|
122
|
+
->and($policy->logsTelemetryActivity())->toBeFalse()
|
|
123
|
+
->and(RetentionPolicy::forCompany('company-2')->get('event_retention_days'))->toBe(5)
|
|
124
|
+
->and(RetentionPolicy::forCompany('company-2')->get('position_retention_days'))->toBe(15)
|
|
125
|
+
->and(RetentionPolicy::forCompany('inherited')->get('event_retention_days'))->toBe(30)
|
|
126
|
+
->and(RetentionPolicy::forCompany('inherited')->get('position_retention_days'))->toBe(45)
|
|
127
|
+
->and(RetentionPolicy::forCompany(null)->get('event_retention_days'))->toBe(30);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('unconfigured maximums preserve unlimited history and null preferences inherit defaults', function () {
|
|
131
|
+
fleetopsRetentionResolver([], ['company-1' => ['event_retention_days' => 0, 'position_retention_days' => null]]);
|
|
132
|
+
|
|
133
|
+
expect(RetentionPolicy::forCompany('company-1')->get('event_retention_days'))->toBe(0)
|
|
134
|
+
->and(RetentionPolicy::forCompany('company-1')->get('position_retention_days'))->toBe(90)
|
|
135
|
+
->and(RetentionPolicy::companyPreferences(['event_retention_days' => 'invalid', 'position_retention_days' => false]))->toBe([])
|
|
136
|
+
->and(RetentionPolicy::companyPreferences(['event_retention_days' => null, 'position_retention_days' => '', 'log_telemetry_activity' => true]))->toBe([]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('constrained preferences keep zero explicit when allowed and replace it when an administrator imposes a maximum', function () {
|
|
140
|
+
$preferences = ['event_retention_days' => 0, 'position_retention_days' => null];
|
|
141
|
+
expect(RetentionPolicy::constrainCompanyPreferences($preferences, RetentionPolicy::FALLBACKS))->toBe(['event_retention_days' => 0]);
|
|
142
|
+
|
|
143
|
+
$defaults = array_replace(RetentionPolicy::FALLBACKS, ['max_event_retention_days' => 90, 'max_position_retention_days' => 360]);
|
|
144
|
+
$bounded = RetentionPolicy::constrainCompanyPreferences($preferences, $defaults);
|
|
145
|
+
expect($bounded)->toBe(['event_retention_days' => 90])
|
|
146
|
+
->and(RetentionPolicy::constrainCompanyPreferences(['event_retention_days' => 30, 'position_retention_days' => 720], $defaults))->toBe(['event_retention_days' => 30, 'position_retention_days' => 360])
|
|
147
|
+
->and(RetentionPolicy::constrainCompanyPreferences([], $defaults))->toBe([])
|
|
148
|
+
->and(RetentionPolicy::constrainCompanyPreferences($bounded, RetentionPolicy::FALLBACKS))->toBe(['event_retention_days' => 90]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('cutoffs, compaction and deletion rules follow the clamped values', function () {
|
|
152
|
+
Carbon::setTestNow('2026-09-23 12:00:00 UTC');
|
|
153
|
+
|
|
154
|
+
$policy = RetentionPolicy::fromArray(['event_retention_days' => 10, 'event_compact_after_days' => 10, 'processed_retention_hours' => 6, 'position_retention_days' => 0]);
|
|
155
|
+
expect($policy->deletesEvents())->toBeTrue()
|
|
156
|
+
->and($policy->compactsEvents())->toBeFalse()
|
|
157
|
+
->and($policy->cutoff('processed_retention_hours')?->toDateTimeString())->toBe('2026-09-23 06:00:00')
|
|
158
|
+
->and($policy->cutoff('event_retention_days')?->toDateTimeString())->toBe('2026-09-13 12:00:00')
|
|
159
|
+
->and($policy->cutoff('position_retention_days'))->toBeNull()
|
|
160
|
+
->and($policy->cutoff('unknown'))->toBeNull()
|
|
161
|
+
->and($policy->get('unknown'))->toBeNull();
|
|
162
|
+
|
|
163
|
+
expect(RetentionPolicy::fromArray(['event_retention_days' => 10, 'event_compact_after_days' => 3])->compactsEvents())->toBeTrue()
|
|
164
|
+
->and(RetentionPolicy::fromArray(['event_retention_days' => 0, 'event_compact_after_days' => 5])->compactsEvents())->toBeTrue()
|
|
165
|
+
->and(RetentionPolicy::fromArray(['event_retention_days' => 0, 'event_compact_after_days' => 5])->deletesEvents())->toBeFalse()
|
|
166
|
+
->and(RetentionPolicy::fromArray(['event_compact_after_days' => 0])->compactsEvents())->toBeFalse()
|
|
167
|
+
->and(RetentionPolicy::fromArray([], ['event_retention_days' => 12])->get('event_retention_days'))->toBe(12)
|
|
168
|
+
->and(RetentionPolicy::normalize(['log_telemetry_activity' => '1'], [])['log_telemetry_activity'])->toBeTrue();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('a failing or malformed settings store falls back to the layer below', function () {
|
|
172
|
+
RetentionPolicy::$settingsResolver = fn () => throw new RuntimeException('settings unavailable');
|
|
173
|
+
expect(RetentionPolicy::forCompany('company-1')->get('event_retention_days'))->toBe(45);
|
|
174
|
+
|
|
175
|
+
RetentionPolicy::flush();
|
|
176
|
+
RetentionPolicy::$settingsResolver = fn () => 'not-an-array';
|
|
177
|
+
expect(RetentionPolicy::defaults()['processed_retention_hours'])->toBe(48);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('company lookups scope the session for the setting store and restore it afterwards', function () {
|
|
181
|
+
// Without a resolver the policy reaches Setting, which has no database here and throws.
|
|
182
|
+
app('session')->put('company', 'previous-company');
|
|
183
|
+
expect(RetentionPolicy::forCompany('company-9')->get('event_retention_days'))->toBe(45)
|
|
184
|
+
->and(app('session')->get('company'))->toBe('previous-company');
|
|
185
|
+
|
|
186
|
+
app('session')->forget('company');
|
|
187
|
+
RetentionPolicy::flush();
|
|
188
|
+
expect(RetentionPolicy::forCompany('company-9')->get('processed_retention_hours'))->toBe(48)
|
|
189
|
+
->and(app('session')->has('company'))->toBeFalse();
|
|
190
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
use Fleetbase\FleetOps\Support\Telematics\Retention\RetentionPolicy;
|
|
4
|
+
use Fleetbase\FleetOps\Support\Telematics\Retention\TelemetryActivity;
|
|
5
|
+
use Spatie\Activitylog\ActivityLogger;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Telemetry writes bypass the activity log unless an administrator enabled it, and
|
|
9
|
+
* still run when no activity logger is available.
|
|
10
|
+
*/
|
|
11
|
+
function fleetopsTelemetryActivityLogger(): ActivityLogger
|
|
12
|
+
{
|
|
13
|
+
$logger = new class extends ActivityLogger {
|
|
14
|
+
public int $suppressed = 0;
|
|
15
|
+
|
|
16
|
+
public function __construct()
|
|
17
|
+
{
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public function withoutLogs(Closure $callback): mixed
|
|
21
|
+
{
|
|
22
|
+
$this->suppressed++;
|
|
23
|
+
|
|
24
|
+
return $callback();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
app()->instance(ActivityLogger::class, $logger);
|
|
28
|
+
|
|
29
|
+
return $logger;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
beforeEach(function () {
|
|
33
|
+
RetentionPolicy::flush();
|
|
34
|
+
config(['telematics.telemetry' => []]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(function () {
|
|
38
|
+
RetentionPolicy::flush();
|
|
39
|
+
RetentionPolicy::$settingsResolver = null;
|
|
40
|
+
app()->forgetInstance(ActivityLogger::class);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('telemetry activity follows the system policy and ignores legacy company overrides', function () {
|
|
44
|
+
RetentionPolicy::$settingsResolver = fn (string $scope, string $key, mixed $default, ?string $company) => $company === 'verbose' ? ['log_telemetry_activity' => true] : [];
|
|
45
|
+
$logger = fleetopsTelemetryActivityLogger();
|
|
46
|
+
|
|
47
|
+
expect(TelemetryActivity::run('quiet', fn () => 'saved'))->toBe('saved')
|
|
48
|
+
->and($logger->suppressed)->toBe(1)
|
|
49
|
+
->and(TelemetryActivity::run('verbose', fn () => 'saved'))->toBe('saved')
|
|
50
|
+
->and($logger->suppressed)->toBe(2)
|
|
51
|
+
->and(TelemetryActivity::run(null, fn () => 'system'))->toBe('system')
|
|
52
|
+
->and($logger->suppressed)->toBe(3);
|
|
53
|
+
|
|
54
|
+
RetentionPolicy::flush();
|
|
55
|
+
RetentionPolicy::$settingsResolver = fn (string $scope) => ['log_telemetry_activity' => $scope === 'system'];
|
|
56
|
+
|
|
57
|
+
expect(TelemetryActivity::run('quiet', fn () => 'logged'))->toBe('logged')
|
|
58
|
+
->and(TelemetryActivity::run('verbose', fn () => 'logged'))->toBe('logged')
|
|
59
|
+
->and(TelemetryActivity::run(null, fn () => 'system'))->toBe('system')
|
|
60
|
+
->and($logger->suppressed)->toBe(3);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('telemetry writes still run when the activity logger cannot be resolved', function () {
|
|
64
|
+
RetentionPolicy::$settingsResolver = fn () => [];
|
|
65
|
+
app()->forgetInstance(ActivityLogger::class);
|
|
66
|
+
|
|
67
|
+
expect(TelemetryActivity::run('company-1', fn () => 'unlogged'))->toBe('unlogged');
|
|
68
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
if (!function_exists('Fleetbase\Models\session')) {
|
|
4
|
+
eval('namespace Fleetbase\Models; function session($key = null, $default = null) { if ($key === null) { return new class { public function has($k) { return \session($k) !== null; } public function get($k, $d = null) { return \session($k, $d); } public function missing($k) { return \session($k) === null; } }; } return \session($key, $default); }');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
8
|
+
use Fleetbase\FleetOps\Support\OrderTracker;
|
|
9
|
+
use Fleetbase\FleetOps\Tracking\TrackingIntelligenceService;
|
|
10
|
+
use Fleetbase\FleetOps\Tracking\TrackingOptions;
|
|
11
|
+
use Illuminate\Database\ConnectionResolver;
|
|
12
|
+
use Illuminate\Database\Eloquent\Model as EloquentModel;
|
|
13
|
+
use Illuminate\Database\SQLiteConnection;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Covers company tracking settings resolution for TrackingOptions: the
|
|
17
|
+
* order's company is used when there is no company session (queued jobs,
|
|
18
|
+
* console commands), and the session company remains the fallback.
|
|
19
|
+
*/
|
|
20
|
+
function fleetopsTrackingOptionsBoot(): SQLiteConnection
|
|
21
|
+
{
|
|
22
|
+
$connection = new SQLiteConnection(new PDO('sqlite::memory:'));
|
|
23
|
+
$resolver = new ConnectionResolver(['default' => $connection, 'mysql' => $connection]);
|
|
24
|
+
$resolver->setDefaultConnection('mysql');
|
|
25
|
+
EloquentModel::setConnectionResolver($resolver);
|
|
26
|
+
|
|
27
|
+
$connection->getSchemaBuilder()->create('settings', function ($blueprint) {
|
|
28
|
+
$blueprint->increments('id');
|
|
29
|
+
$blueprint->string('key')->nullable();
|
|
30
|
+
$blueprint->text('value')->nullable();
|
|
31
|
+
$blueprint->timestamps();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
$connection->table('settings')->insert([
|
|
35
|
+
['key' => 'company.company-a.tracking', 'value' => json_encode(['provider' => 'osrm', 'cache_ttl_seconds' => 15])],
|
|
36
|
+
['key' => 'company.company-b.tracking', 'value' => json_encode(['provider' => 'calculated', 'cache_ttl_seconds' => 45])],
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
session(['company' => null]);
|
|
40
|
+
|
|
41
|
+
return $connection;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
test('tracking options read the given company settings regardless of session', function (?string $sessionCompany) {
|
|
45
|
+
fleetopsTrackingOptionsBoot();
|
|
46
|
+
session(['company' => $sessionCompany]);
|
|
47
|
+
|
|
48
|
+
$options = TrackingOptions::fromArray([], 'company-a');
|
|
49
|
+
|
|
50
|
+
expect($options->provider)->toBe('osrm')
|
|
51
|
+
->and($options->cacheTtlSeconds)->toBe(15);
|
|
52
|
+
|
|
53
|
+
// Explicit options still win over company settings
|
|
54
|
+
expect(TrackingOptions::fromArray(['provider' => 'google'], 'company-a')->provider)->toBe('google');
|
|
55
|
+
})->with([null, 'company-b']);
|
|
56
|
+
|
|
57
|
+
test('tracking options fall back to the session company when no company is given', function () {
|
|
58
|
+
fleetopsTrackingOptionsBoot();
|
|
59
|
+
session(['company' => 'company-b']);
|
|
60
|
+
|
|
61
|
+
$options = TrackingOptions::fromArray([]);
|
|
62
|
+
session(['company' => null]);
|
|
63
|
+
|
|
64
|
+
expect($options->provider)->toBe('calculated')
|
|
65
|
+
->and($options->cacheTtlSeconds)->toBe(45);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('order tracker builds options from the order company', function () {
|
|
69
|
+
fleetopsTrackingOptionsBoot();
|
|
70
|
+
|
|
71
|
+
$service = new class extends TrackingIntelligenceService {
|
|
72
|
+
public ?TrackingOptions $received = null;
|
|
73
|
+
|
|
74
|
+
public function __construct()
|
|
75
|
+
{
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public function track(Order $order, array|TrackingOptions $options = []): array
|
|
79
|
+
{
|
|
80
|
+
$this->received = $options;
|
|
81
|
+
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
app()->instance(TrackingIntelligenceService::class, $service);
|
|
86
|
+
|
|
87
|
+
$order = new Order();
|
|
88
|
+
$order->setRawAttributes(['uuid' => 'order-tracking-options', 'company_uuid' => 'company-a'], true);
|
|
89
|
+
|
|
90
|
+
(new OrderTracker($order))->toArray();
|
|
91
|
+
app()->forgetInstance(TrackingIntelligenceService::class);
|
|
92
|
+
|
|
93
|
+
expect($service->received)->toBeInstanceOf(TrackingOptions::class)
|
|
94
|
+
->and($service->received->provider)->toBe('osrm');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('tracking remains available with configured defaults when the settings database is unavailable', function () {
|
|
98
|
+
$connection = fleetopsTrackingOptionsBoot();
|
|
99
|
+
$connection->getSchemaBuilder()->drop('settings');
|
|
100
|
+
$original = config('fleetops.tracking', []);
|
|
101
|
+
try {
|
|
102
|
+
config(['fleetops.tracking' => ['provider' => 'calculated', 'cache_ttl_seconds' => 17]]);
|
|
103
|
+
$options = TrackingOptions::fromArray([], 'uncached-unavailable-company');
|
|
104
|
+
expect($options->provider)->toBe('calculated')->and($options->cacheTtlSeconds)->toBe(17);
|
|
105
|
+
expect(TrackingOptions::fromArray(['provider' => 'osrm'], 'uncached-unavailable-company')->provider)->toBe('osrm');
|
|
106
|
+
} finally {
|
|
107
|
+
config(['fleetops.tracking' => $original]);
|
|
108
|
+
}
|
|
109
|
+
});
|
package/translations/en-us.yaml
CHANGED
|
@@ -114,6 +114,7 @@ menu:
|
|
|
114
114
|
maintenances: Maintenances
|
|
115
115
|
orchestrator: Orchestrator
|
|
116
116
|
orchestrator-settings: Orchestrator Settings
|
|
117
|
+
telematics-settings: Telematics
|
|
117
118
|
|
|
118
119
|
trailer:
|
|
119
120
|
navigation-description: Manage towed assets, towing connections, equipment, and telematics.
|
|
@@ -2723,6 +2724,53 @@ route-list:
|
|
|
2723
2724
|
more-waypoints: more waypoints
|
|
2724
2725
|
|
|
2725
2726
|
settings:
|
|
2727
|
+
telematics:
|
|
2728
|
+
telematics-settings: Telematics Settings
|
|
2729
|
+
intro: Choose how long your organization keeps device events and position history within the limits set by your system administrator.
|
|
2730
|
+
use-system-default: Use system default
|
|
2731
|
+
history-policy-maximum: "Your administrator allows up to {days} days of history. Unlimited retention is not available."
|
|
2732
|
+
history-policy-unlimited: Your administrator allows unlimited history. Set the retention period to 0 to keep history forever.
|
|
2733
|
+
device-events: Device Events Retention
|
|
2734
|
+
event-retention-days: Keep device events for (days)
|
|
2735
|
+
event-retention-days-help: Events older than this are permanently deleted. Applies to telemetry updates and provider alerts alike.
|
|
2736
|
+
event-compact-after-days: Strip raw payloads after (days)
|
|
2737
|
+
event-compact-after-days-help: Events older than this keep their type, severity, location and normalized data, but the raw provider payload is removed. Raw payloads are the bulk of each event's size.
|
|
2738
|
+
compaction-warning: Raw payloads are only stripped from events that are kept. Set this lower than the event retention or set it to 0 to disable compaction.
|
|
2739
|
+
positions: Position History Retention
|
|
2740
|
+
position-retention-days: Keep positions for (days)
|
|
2741
|
+
position-retention-days-help: Vehicle and trailer position history older than this is permanently deleted. Route replay and history views only reach back this far.
|
|
2742
|
+
inbox: Delivery Inbox & Sync Run Retention
|
|
2743
|
+
processed-retention-hours: Keep processed deliveries for (hours)
|
|
2744
|
+
processed-retention-hours-help: Raw provider batches are stored encrypted until processed and kept briefly for replay and diagnostics. Each batch can be close to a megabyte.
|
|
2745
|
+
quarantine-retention-days: Keep quarantined deliveries for (days)
|
|
2746
|
+
quarantine-retention-days-help: Batches that could not be processed stay available for inspection and replay for this long.
|
|
2747
|
+
sync-run-retention-days: Keep sync run diagnostics for (days)
|
|
2748
|
+
sync-run-retention-days-help: One sync run row is recorded per connection per poll. In-flight runs are never removed.
|
|
2749
|
+
activity-logging: Telemetry Activity Logging
|
|
2750
|
+
log-telemetry-activity: Log telemetry activity
|
|
2751
|
+
log-telemetry-activity-help: Write telemetry-driven device, event and vehicle saves to the activity log. Off by default; every poll otherwise adds several activity rows per device.
|
|
2752
|
+
storage-usage: System Telematics Storage Usage
|
|
2753
|
+
table: Table
|
|
2754
|
+
rows: Rows
|
|
2755
|
+
estimated-rows: "{count} (estimated)"
|
|
2756
|
+
estimated-size: Estimated size
|
|
2757
|
+
oldest: Oldest row
|
|
2758
|
+
not-available: n/a
|
|
2759
|
+
raw-payload-rows: "{count} rows still carry raw payloads, {compactable} eligible for compaction now"
|
|
2760
|
+
refresh: Refresh
|
|
2761
|
+
run-cleanup: Run system cleanup
|
|
2762
|
+
run-cleanup-confirm: This queues telematics cleanup across all organizations, using the saved system policy and each organization's effective history preferences. Expired records are permanently deleted and cannot be recovered. Continue?
|
|
2763
|
+
cleanup-queued: Telematics cleanup has been queued across all organizations. Usage refreshes shortly; cleanup may still be running.
|
|
2764
|
+
cleanup-unavailable: System-wide telematics cleanup could not be confirmed. Please try again.
|
|
2765
|
+
usage-unavailable: Storage usage could not be loaded. Select Refresh to try again.
|
|
2766
|
+
space-reclaim-note: Usage includes all telematics data across the system. Row counts marked as estimated use database statistics. Sizes include table data and indexes. Deleting records frees space for reuse within the database; physical disk usage may not decrease immediately.
|
|
2767
|
+
settings-saved: Telematics settings saved.
|
|
2768
|
+
settings-unavailable: Telematics settings could not be loaded. Please try again.
|
|
2769
|
+
tables:
|
|
2770
|
+
device_events: Device events
|
|
2771
|
+
positions: Positions
|
|
2772
|
+
telematic_deliveries: Delivery inbox
|
|
2773
|
+
telematic_sync_runs: Sync runs
|
|
2726
2774
|
avatar-management: Avatar Management
|
|
2727
2775
|
custom-fields: Custom Fields
|
|
2728
2776
|
notifications:
|