@fleetbase/fleetops-engine 0.6.68 → 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 +44 -9
- package/addon/components/admin/telematics-settings.hbs +143 -0
- package/addon/components/admin/telematics-settings.js +145 -0
- package/addon/components/contact/form.hbs +13 -2
- package/addon/components/customer/details.hbs +17 -17
- package/addon/components/customer/form.hbs +13 -2
- package/addon/components/customer/form.js +0 -56
- package/addon/components/driver/details.hbs +13 -19
- package/addon/components/driver/form.hbs +20 -35
- package/addon/components/driver/form.js +0 -58
- 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/modals/driver-form.hbs +2 -2
- package/addon/components/modals/reset-profile-credentials.hbs +23 -0
- package/addon/components/modals/reset-profile-credentials.js +70 -0
- package/addon/components/profile-identity-hint.hbs +13 -0
- package/addon/components/profile-identity-hint.js +49 -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 +8 -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 +42 -8
- 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/services/contact-actions.js +50 -89
- package/addon/services/driver-actions.js +98 -0
- package/addon/templates/settings/telematics.hbs +70 -0
- package/addon/utils/profile-login-actions.js +172 -0
- package/app/components/admin/telematics-settings.js +1 -0
- package/app/components/{modals/reset-customer-credentials.js → map/marker-card/driver.js} +1 -1
- package/app/components/map/marker-card/vehicle.js +1 -0
- package/app/components/modals/reset-profile-credentials.js +1 -0
- package/app/components/profile-identity-hint.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 +3 -3
- package/scripts/pest-bootstrap.php +12 -0
- package/server/config/telemetry.php +12 -0
- package/server/migrations/2026_09_21_000001_convert_profile_only_users_to_managed_accounts.php +132 -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/resources/views/mail/driver-credentials.blade.php +21 -0
- package/server/src/Auth/Schemas/FleetOps.php +104 -5
- package/server/src/Console/Commands/DrainTelematicInbox.php +3 -13
- package/server/src/Console/Commands/FixCustomerCompanies.php +1 -1
- package/server/src/Console/Commands/FixDriverCompanies.php +1 -1
- package/server/src/Console/Commands/ProcessOperationalAlerts.php +3 -3
- package/server/src/Console/Commands/PruneTelematicsData.php +318 -0
- package/server/src/Exceptions/ProfileIdentityConflictException.php +34 -0
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +14 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +56 -39
- 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 +16 -17
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +54 -4
- 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 +204 -162
- package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +47 -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 +178 -27
- 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/Http/Requests/CreateDriverRequest.php +6 -2
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +9 -18
- package/server/src/Http/Resources/v1/Contact.php +3 -0
- package/server/src/Http/Resources/v1/Driver.php +3 -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/Mail/DriverCredentialsMail.php +55 -0
- package/server/src/Models/Contact.php +55 -90
- package/server/src/Models/Sensor.php +3 -5
- package/server/src/Observers/ContactObserver.php +2 -22
- package/server/src/Observers/DriverObserver.php +5 -6
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +9 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +18 -0
- package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +62 -5
- package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +24 -5
- package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +6 -2
- package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +40 -7
- package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +86 -24
- package/server/src/Support/Ai/FleetOpsAiConsoleCommands.php +122 -0
- package/server/src/Support/Ai/Tools/CreateOrderTool.php +86 -0
- package/server/src/Support/Ai/Tools/ImportOrdersTool.php +53 -0
- package/server/src/Support/Ai/Tools/OptimizeOrderRouteTool.php +70 -0
- package/server/src/Support/Ai/Tools/SearchResourcesTool.php +73 -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/ProfileAccountManager.php +405 -0
- 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 +14 -1
- package/server/tests/AiOperationalQueryCapabilityTest.php +1 -0
- package/server/tests/AnalyticsRoutesTest.php +29 -2
- package/server/tests/ApiCustomerControllerContractsTest.php +21 -0
- package/server/tests/ApiDriverControllerContractsTest.php +102 -20
- package/server/tests/CommandContractsTest.php +4 -4
- package/server/tests/ControllerHelperContractsTest.php +3 -11
- package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +21 -1
- package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +36 -0
- package/server/tests/Feature/Http/Api/DriverControllerSessionHelpersTest.php +19 -6
- package/server/tests/Feature/Http/Api/ProfileOnlyUserConversionMigrationTest.php +201 -0
- package/server/tests/Feature/Http/Api/TelematicsRetentionIndexMigrationTest.php +115 -0
- package/server/tests/Feature/Http/Internal/ContactControllerEndpointsTest.php +6 -4
- package/server/tests/Feature/Http/Internal/CustomerControllerCredentialsTest.php +6 -2
- package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +23 -0
- package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +42 -17
- package/server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php +168 -277
- package/server/tests/Feature/Http/Internal/DriverControllerLoginManagementTest.php +716 -0
- package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +10 -3
- package/server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php +18 -0
- package/server/tests/Feature/Http/Internal/OrderControllerTenantScopingTest.php +411 -0
- package/server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php +8 -20
- package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +71 -0
- package/server/tests/Feature/Http/Internal/TelematicsStorageDeadlinesTest.php +119 -0
- package/server/tests/InternalCustomerControllerContractsTest.php +32 -1
- package/server/tests/InternalImportExportControllerContractsTest.php +5 -8
- package/server/tests/ModelAccessorContractsTest.php +82 -20
- package/server/tests/NavigatorControllerContractsTest.php +36 -4
- package/server/tests/NotificationAndMailContractsTest.php +31 -0
- package/server/tests/ObserverContractsTest.php +28 -3
- package/server/tests/OrchestratorConsumableApiTest.php +42 -0
- package/server/tests/ProviderContractsTest.php +10 -0
- package/server/tests/ReportSchemaContractsTest.php +120 -6
- package/server/tests/RequestContractsTest.php +19 -11
- 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/SupportJobAndAiCoverageTest.php +3 -3
- package/server/tests/TelematicControllerContractsTest.php +22 -4
- 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/Models/ContactCustomerUserTest.php +90 -14
- package/server/tests/Unit/Models/ContactTest.php +48 -5
- package/server/tests/Unit/Models/SensorTest.php +2 -2
- package/server/tests/Unit/Models/SmallModelSurfacesTest.php +31 -32
- package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +7 -2
- package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +20 -0
- package/server/tests/Unit/Providers/FleetOpsAiRegistrationTest.php +15 -2
- package/server/tests/Unit/Support/Ai/AssetStatusCapabilityQueriesTest.php +42 -0
- package/server/tests/Unit/Support/Ai/Capabilities/OrderInsightsCapabilityTest.php +19 -1
- package/server/tests/Unit/Support/Ai/Capabilities/SearchResourcesCapabilityTest.php +85 -1
- package/server/tests/Unit/Support/Ai/FleetOpsAiConsoleCommandsTest.php +31 -0
- package/server/tests/Unit/Support/Ai/Tools/FleetOpsAiToolsTest.php +246 -0
- package/server/tests/Unit/Support/AiCapabilityPermissionsTest.php +3 -3
- 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/ProfileAccountManagerTest.php +643 -0
- 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 +85 -2
- package/translations/uk-ua.yaml +3475 -0
- package/addon/components/modals/reset-customer-credentials.hbs +0 -18
- package/addon/components/modals/reset-customer-credentials.js +0 -55
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Telematics\Retention;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Models\Setting;
|
|
6
|
+
use Illuminate\Support\Carbon;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolves how long a company keeps telematics data.
|
|
10
|
+
*
|
|
11
|
+
* Administrators control retention policy and infrastructure settings. Companies
|
|
12
|
+
* may override event and position history retention within the system maximums.
|
|
13
|
+
* Zero means "keep forever" only when the administrator has not set a maximum.
|
|
14
|
+
*/
|
|
15
|
+
final class RetentionPolicy
|
|
16
|
+
{
|
|
17
|
+
public const SETTING_KEY = 'fleet-ops.telematics-settings';
|
|
18
|
+
|
|
19
|
+
/** Cached policies are refreshed after this many seconds so saved settings reach long-lived workers. */
|
|
20
|
+
public const CACHE_TTL_SECONDS = 60;
|
|
21
|
+
|
|
22
|
+
public const HISTORY_KEYS = ['event_retention_days', 'position_retention_days'];
|
|
23
|
+
|
|
24
|
+
public const MAXIMUMS = [
|
|
25
|
+
'event_retention_days' => 'max_event_retention_days',
|
|
26
|
+
'position_retention_days' => 'max_position_retention_days',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/** @var array<string, array{0: int, 1: int}> */
|
|
30
|
+
public const LIMITS = [
|
|
31
|
+
'event_retention_days' => [1, 3650],
|
|
32
|
+
'event_compact_after_days' => [1, 3650],
|
|
33
|
+
'position_retention_days' => [1, 3650],
|
|
34
|
+
'processed_retention_hours' => [1, 720],
|
|
35
|
+
'quarantine_retention_days' => [1, 365],
|
|
36
|
+
'sync_run_retention_days' => [1, 365],
|
|
37
|
+
'max_event_retention_days' => [1, 3650],
|
|
38
|
+
'max_position_retention_days' => [1, 3650],
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
public const BOOLEANS = ['log_telemetry_activity'];
|
|
42
|
+
|
|
43
|
+
/** Used when neither config nor settings provide a value. */
|
|
44
|
+
public const FALLBACKS = [
|
|
45
|
+
'event_retention_days' => 30,
|
|
46
|
+
'event_compact_after_days' => 7,
|
|
47
|
+
'position_retention_days' => 90,
|
|
48
|
+
'processed_retention_hours' => 24,
|
|
49
|
+
'quarantine_retention_days' => 7,
|
|
50
|
+
'sync_run_retention_days' => 7,
|
|
51
|
+
// A separate, opt-in cap preserves existing history when upgrading.
|
|
52
|
+
'max_event_retention_days' => 0,
|
|
53
|
+
'max_position_retention_days' => 0,
|
|
54
|
+
'log_telemetry_activity' => false,
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Test seam: fn (string $scope, string $key, mixed $default, ?string $companyUuid): mixed
|
|
59
|
+
* where $scope is `system` or `company`.
|
|
60
|
+
*/
|
|
61
|
+
public static ?\Closure $settingsResolver = null;
|
|
62
|
+
|
|
63
|
+
/** @var array<string, array{policy: self, expires: int}> */
|
|
64
|
+
private static array $companyCache = [];
|
|
65
|
+
|
|
66
|
+
/** @var array{values: array, expires: int}|null */
|
|
67
|
+
private static ?array $defaultsCache = null;
|
|
68
|
+
|
|
69
|
+
private function __construct(private array $values)
|
|
70
|
+
{
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @return string[] */
|
|
74
|
+
public static function keys(): array
|
|
75
|
+
{
|
|
76
|
+
return array_keys(self::FALLBACKS);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* System-wide defaults: package config overridden by the admin setting.
|
|
81
|
+
*/
|
|
82
|
+
public static function defaults(): array
|
|
83
|
+
{
|
|
84
|
+
if (self::$defaultsCache && self::$defaultsCache['expires'] > time()) {
|
|
85
|
+
return self::$defaultsCache['values'];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
$config = array_intersect_key((array) config('telematics.telemetry', []), self::FALLBACKS);
|
|
89
|
+
$base = self::normalize($config, self::FALLBACKS);
|
|
90
|
+
$values = self::normalize(self::resolve('system', null), $base);
|
|
91
|
+
|
|
92
|
+
self::$defaultsCache = ['values' => $values, 'expires' => time() + self::CACHE_TTL_SECONDS];
|
|
93
|
+
|
|
94
|
+
return $values;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public static function forCompany(?string $companyUuid): self
|
|
98
|
+
{
|
|
99
|
+
if (!$companyUuid) {
|
|
100
|
+
return self::fromCompanyPreferences([]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
$cached = self::$companyCache[$companyUuid] ?? null;
|
|
104
|
+
if ($cached && $cached['expires'] > time()) {
|
|
105
|
+
return $cached['policy'];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
$defaults = self::defaults();
|
|
109
|
+
$policy = self::fromCompanyPreferences(self::resolve('company', $companyUuid), $defaults);
|
|
110
|
+
|
|
111
|
+
// A policy must not extend the lifetime of system defaults it inherited.
|
|
112
|
+
self::$companyCache[$companyUuid] = ['policy' => $policy, 'expires' => self::$defaultsCache['expires']];
|
|
113
|
+
|
|
114
|
+
return $policy;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public static function fromArray(array $values, ?array $base = null): self
|
|
118
|
+
{
|
|
119
|
+
return new self(self::applyMaximums(self::normalize($values, $base ?? self::FALLBACKS)));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Keep only explicit, valid customer history preferences. Missing or null
|
|
124
|
+
* values remain absent so later changes to the system defaults are inherited.
|
|
125
|
+
*/
|
|
126
|
+
public static function companyPreferences(array $input): array
|
|
127
|
+
{
|
|
128
|
+
$preferences = [];
|
|
129
|
+
foreach (self::HISTORY_KEYS as $key) {
|
|
130
|
+
$value = $input[$key] ?? null;
|
|
131
|
+
if ($value === null || $value === '' || is_bool($value) || filter_var($value, FILTER_VALIDATE_INT) === false) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
[$min, $max] = self::LIMITS[$key];
|
|
136
|
+
$value = (int) $value;
|
|
137
|
+
$preferences[$key] = $value === 0 ? 0 : max($min, min($max, $value));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return $preferences;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Legacy company overrides for ingestion, payloads and logging are ignored.
|
|
145
|
+
* Apply caps after inheritance so unlimited or overly long defaults cannot
|
|
146
|
+
* bypass an administrator's maximum either.
|
|
147
|
+
*/
|
|
148
|
+
public static function fromCompanyPreferences(array $preferences, ?array $defaults = null): self
|
|
149
|
+
{
|
|
150
|
+
return self::fromArray(self::companyPreferences($preferences), $defaults ?? self::defaults());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Return the explicit history choices after applying the administrator's
|
|
155
|
+
* maximums. Inherited values stay absent, and an allowed zero stays explicit.
|
|
156
|
+
*/
|
|
157
|
+
public static function constrainCompanyPreferences(array $preferences, ?array $defaults = null): array
|
|
158
|
+
{
|
|
159
|
+
$preferences = self::companyPreferences($preferences);
|
|
160
|
+
$effective = self::fromCompanyPreferences($preferences, $defaults)->toArray();
|
|
161
|
+
|
|
162
|
+
return array_intersect_key($effective, $preferences);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Clamp and cast user input, filling gaps from `$base`.
|
|
167
|
+
*/
|
|
168
|
+
public static function normalize(array $input, array $base): array
|
|
169
|
+
{
|
|
170
|
+
$values = [];
|
|
171
|
+
foreach (self::LIMITS as $key => [$min, $max]) {
|
|
172
|
+
$value = (int) self::pick($input, $base, $key);
|
|
173
|
+
$values[$key] = $value === 0 ? 0 : max($min, min($max, $value));
|
|
174
|
+
}
|
|
175
|
+
foreach (self::BOOLEANS as $key) {
|
|
176
|
+
$values[$key] = filter_var(self::pick($input, $base, $key), FILTER_VALIDATE_BOOLEAN);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return $values;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public static function flush(?string $companyUuid = null): void
|
|
183
|
+
{
|
|
184
|
+
if ($companyUuid === null) {
|
|
185
|
+
self::$companyCache = [];
|
|
186
|
+
self::$defaultsCache = null;
|
|
187
|
+
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
unset(self::$companyCache[$companyUuid]);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The point in time before which rows governed by `$key` are expired, or null when retention is off.
|
|
196
|
+
*/
|
|
197
|
+
public function cutoff(string $key): ?Carbon
|
|
198
|
+
{
|
|
199
|
+
$value = (int) ($this->values[$key] ?? 0);
|
|
200
|
+
if ($value <= 0) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return $key === 'processed_retention_hours' ? Carbon::now()->subHours($value) : Carbon::now()->subDays($value);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public function deletesEvents(): bool
|
|
208
|
+
{
|
|
209
|
+
return $this->values['event_retention_days'] > 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Compaction strips raw payloads from events that are kept but no longer need the provider blob.
|
|
214
|
+
* It is pointless when events are deleted before, or at the same age as, they would be compacted.
|
|
215
|
+
*/
|
|
216
|
+
public function compactsEvents(): bool
|
|
217
|
+
{
|
|
218
|
+
$compactAfter = $this->values['event_compact_after_days'];
|
|
219
|
+
if ($compactAfter <= 0) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return !$this->deletesEvents() || $compactAfter < $this->values['event_retention_days'];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
public function logsTelemetryActivity(): bool
|
|
227
|
+
{
|
|
228
|
+
return (bool) $this->values['log_telemetry_activity'];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
public function get(string $key): mixed
|
|
232
|
+
{
|
|
233
|
+
return $this->values[$key] ?? null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
public function toArray(): array
|
|
237
|
+
{
|
|
238
|
+
return $this->values;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private static function applyMaximums(array $values): array
|
|
242
|
+
{
|
|
243
|
+
foreach (self::MAXIMUMS as $key => $maximumKey) {
|
|
244
|
+
$maximum = $values[$maximumKey];
|
|
245
|
+
if ($maximum > 0 && ($values[$key] === 0 || $values[$key] > $maximum)) {
|
|
246
|
+
$values[$key] = $maximum;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return $values;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private static function pick(array $input, array $base, string $key): mixed
|
|
254
|
+
{
|
|
255
|
+
if (array_key_exists($key, $input) && $input[$key] !== null && $input[$key] !== '') {
|
|
256
|
+
return $input[$key];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return $base[$key] ?? self::FALLBACKS[$key];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Read the stored setting for a scope. Any failure (no settings table, no session store)
|
|
264
|
+
* falls back to the layer below so ingestion never depends on the settings store.
|
|
265
|
+
*/
|
|
266
|
+
private static function resolve(string $scope, ?string $companyUuid): array
|
|
267
|
+
{
|
|
268
|
+
try {
|
|
269
|
+
if (self::$settingsResolver) {
|
|
270
|
+
$value = (self::$settingsResolver)($scope, self::SETTING_KEY, [], $companyUuid);
|
|
271
|
+
} elseif ($scope === 'system') {
|
|
272
|
+
$value = Setting::lookup(self::SETTING_KEY, []);
|
|
273
|
+
} else {
|
|
274
|
+
$value = self::lookupCompanySetting($companyUuid);
|
|
275
|
+
}
|
|
276
|
+
} catch (\Throwable) {
|
|
277
|
+
$value = [];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return is_array($value) ? $value : [];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* `Setting::lookupCompany` reads the company from the session, which queue workers and
|
|
285
|
+
* console commands do not have; scope it for the lookup and restore whatever was there.
|
|
286
|
+
*/
|
|
287
|
+
private static function lookupCompanySetting(string $companyUuid): mixed
|
|
288
|
+
{
|
|
289
|
+
$session = app('session');
|
|
290
|
+
$had = $session->has('company');
|
|
291
|
+
$previous = $session->get('company');
|
|
292
|
+
$session->put('company', $companyUuid);
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
return Setting::lookupCompany(self::SETTING_KEY, []);
|
|
296
|
+
} finally {
|
|
297
|
+
$had ? $session->put('company', $previous) : $session->forget('company');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Support\Telematics\Retention;
|
|
4
|
+
|
|
5
|
+
use Spatie\Activitylog\ActivityLogger;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Runs telemetry-driven writes with activity logging suppressed unless the
|
|
9
|
+
* system administrator enabled it. Every poll saves the device, event, position and vehicle,
|
|
10
|
+
* which otherwise writes several activity rows per device per minute.
|
|
11
|
+
*/
|
|
12
|
+
final class TelemetryActivity
|
|
13
|
+
{
|
|
14
|
+
public static function run(?string $companyUuid, \Closure $callback): mixed
|
|
15
|
+
{
|
|
16
|
+
if (RetentionPolicy::forCompany($companyUuid)->logsTelemetryActivity()) {
|
|
17
|
+
return $callback();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
$logger = self::logger();
|
|
21
|
+
if (!$logger) {
|
|
22
|
+
return $callback();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return $logger->withoutLogs($callback);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The logger is optional: an environment without the activity log package
|
|
30
|
+
* bound (package tests, minimal consoles) must still ingest telemetry.
|
|
31
|
+
*/
|
|
32
|
+
private static function logger(): ?ActivityLogger
|
|
33
|
+
{
|
|
34
|
+
try {
|
|
35
|
+
return app(ActivityLogger::class);
|
|
36
|
+
} catch (\Throwable) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -239,6 +239,12 @@ class TelematicService
|
|
|
239
239
|
if ($provider instanceof \Fleetbase\FleetOps\Contracts\TelemetryProviderInterface) {
|
|
240
240
|
return app(Telemetry\Ingestor::class)->ingest($telematic, $provider, $payload, $this);
|
|
241
241
|
}
|
|
242
|
+
|
|
243
|
+
return Retention\TelemetryActivity::run($telematic->company_uuid, fn () => $this->ingestLegacySnapshot($telematic, $provider, $payload));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
protected function ingestLegacySnapshot(Telematic $telematic, TelematicProviderInterface $provider, array $payload): array
|
|
247
|
+
{
|
|
242
248
|
$device = $this->linkDevice($telematic, $provider->normalizeDevice($payload));
|
|
243
249
|
|
|
244
250
|
$event = null;
|
|
@@ -299,7 +305,9 @@ class TelematicService
|
|
|
299
305
|
], fn ($value) => $value !== null);
|
|
300
306
|
$event->payload = $eventData['payload'] ?? $eventData['meta'] ?? $eventData;
|
|
301
307
|
$event->_key = $eventKey;
|
|
302
|
-
|
|
308
|
+
// Providers hand over the raw unit in `meta`; it already lives in `payload`.
|
|
309
|
+
// Persist only the normalized block so each event is stored once, not twice.
|
|
310
|
+
$event->meta = array_filter([
|
|
303
311
|
'telematic_uuid' => $telematic->uuid,
|
|
304
312
|
'telematic_id' => $telematic->public_id,
|
|
305
313
|
'provider_event_id' => $event->ident,
|
|
@@ -310,7 +318,9 @@ class TelematicService
|
|
|
310
318
|
'odometer' => $eventData['odometer'] ?? null,
|
|
311
319
|
'ignition' => $eventData['ignition'] ?? null,
|
|
312
320
|
'fuel_level' => $eventData['fuel_level'] ?? null,
|
|
313
|
-
|
|
321
|
+
'telemetry' => data_get($eventData, 'meta.telemetry'),
|
|
322
|
+
'provider_status' => data_get($eventData, 'meta.provider_status'),
|
|
323
|
+
], fn ($value) => $value !== null);
|
|
314
324
|
|
|
315
325
|
$location = $this->normalizeLocation($eventData['location'] ?? null);
|
|
316
326
|
if ($location) {
|
|
@@ -7,6 +7,7 @@ use Fleetbase\FleetOps\Events\DeviceTelemetryUpdated;
|
|
|
7
7
|
use Fleetbase\FleetOps\Models\Device;
|
|
8
8
|
use Fleetbase\FleetOps\Models\DeviceEvent;
|
|
9
9
|
use Fleetbase\FleetOps\Models\Telematic;
|
|
10
|
+
use Fleetbase\FleetOps\Support\Telematics\Retention\TelemetryActivity;
|
|
10
11
|
use Fleetbase\FleetOps\Support\Telematics\TelematicService;
|
|
11
12
|
use Illuminate\Support\Carbon;
|
|
12
13
|
use Illuminate\Support\Facades\Cache;
|
|
@@ -32,7 +33,8 @@ class Ingestor
|
|
|
32
33
|
}
|
|
33
34
|
$key = 'telemetry:device:' . hash('sha256', $telematic->uuid . '|' . $id);
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
// Telemetry saves (device, event, position, vehicle, sensors) only reach the activity log when the company opts in.
|
|
37
|
+
return Cache::lock($key, 60)->block(5, fn () => TelemetryActivity::run($telematic->company_uuid, fn () => DB::transaction(function () use ($telematic, $sample, $options, $service, $receivedAt, $source, $normalized, $event, $id) {
|
|
36
38
|
$device = Device::withoutGlobalScopes()->where('company_uuid', $telematic->company_uuid)->where('telematic_uuid', $telematic->uuid)->where('device_id', $id)->lockForUpdate()->first();
|
|
37
39
|
$current = data_get($device?->meta, 'telemetry.position_at') ?? data_get($device?->meta, 'last_update.occurred_at');
|
|
38
40
|
$valid = Sample::validPosition($event);
|
|
@@ -107,6 +109,6 @@ class Ingestor
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
return ['device' => $device, 'event' => $stored, 'events' => $stored ? [$stored] : [], 'sensors' => $sensors, 'duplicate' => $duplicate, 'invalid_position' => !$valid];
|
|
110
|
-
}, 3));
|
|
112
|
+
}, 3)));
|
|
111
113
|
}
|
|
112
114
|
}
|
|
@@ -18,7 +18,7 @@ class TrackingIntelligenceService
|
|
|
18
18
|
|
|
19
19
|
public function track(Order $order, array|TrackingOptions $options = []): array
|
|
20
20
|
{
|
|
21
|
-
$options = $options instanceof TrackingOptions ? $options : TrackingOptions::fromArray($options);
|
|
21
|
+
$options = $options instanceof TrackingOptions ? $options : TrackingOptions::fromArray($options, $order->company_uuid);
|
|
22
22
|
$context = $this->contextBuilder->build($order, $options);
|
|
23
23
|
$cacheKey = $this->cacheKey($context, $options);
|
|
24
24
|
|
|
@@ -19,7 +19,7 @@ class TrackingOptions
|
|
|
19
19
|
) {
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
public static function fromArray(array $options = []): self
|
|
22
|
+
public static function fromArray(array $options = [], ?string $companyUuid = null): self
|
|
23
23
|
{
|
|
24
24
|
try {
|
|
25
25
|
$systemSettings = Setting::lookup('fleet-ops.tracking-settings', []);
|
|
@@ -30,7 +30,7 @@ class TrackingOptions
|
|
|
30
30
|
$config = array_merge(config('fleetops.tracking', []), is_array($systemSettings) ? $systemSettings : []);
|
|
31
31
|
$companySettings = [];
|
|
32
32
|
try {
|
|
33
|
-
$companySettings = Setting::
|
|
33
|
+
$companySettings = Setting::lookupForCompany($companyUuid ?? session('company'), 'tracking', []);
|
|
34
34
|
} catch (\Throwable) {
|
|
35
35
|
$companySettings = [];
|
|
36
36
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Traits;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Support\Authorization;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Registers controller middleware that enforces an explicit permission per method.
|
|
9
|
+
*
|
|
10
|
+
* Used for endpoints AuthorizationGuard cannot resolve on its own: methods on
|
|
11
|
+
* resource controllers whose names do not match a schema action (these also carry
|
|
12
|
+
* `#[SkipAuthorizationCheck]`) and every method of controllers that are not
|
|
13
|
+
* resource controllers.
|
|
14
|
+
*
|
|
15
|
+
* Map values are one permission in short form (`dispatch order`), a list of
|
|
16
|
+
* permissions of which the user needs any one, or `admin` for system administrators.
|
|
17
|
+
*/
|
|
18
|
+
trait AuthorizesMethods
|
|
19
|
+
{
|
|
20
|
+
/**
|
|
21
|
+
* @param array<string, string|string[]> $map method name => permission(s)
|
|
22
|
+
*/
|
|
23
|
+
protected function authorizeMethods(array $map): void
|
|
24
|
+
{
|
|
25
|
+
foreach ($map as $method => $permissions) {
|
|
26
|
+
$this->middleware(function ($request, $next) use ($permissions) {
|
|
27
|
+
if ($permissions === 'admin') {
|
|
28
|
+
Authorization::authorizeAdmin();
|
|
29
|
+
} else {
|
|
30
|
+
Authorization::authorize(...(array) $permissions);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return $next($request);
|
|
34
|
+
})->only($method);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
package/server/src/routes.php
CHANGED
|
@@ -405,8 +405,10 @@ Route::prefix(config('fleetops.api.routing.prefix'))->namespace('Fleetbase\Fleet
|
|
|
405
405
|
$router->group(
|
|
406
406
|
['prefix' => 'v1/fleet-ops/navigator', 'namespace' => 'v1'],
|
|
407
407
|
function ($router) {
|
|
408
|
-
|
|
408
|
+
// Opened from the Navigator app's QR scan without a session;
|
|
409
|
+
// NavigatorController::linkApp() requires a valid signature.
|
|
409
410
|
$router->get('link-app', 'NavigatorController@linkApp');
|
|
411
|
+
$router->get('get-link-app', 'NavigatorController@getLinkAppUrl')->middleware(['fleetbase.protected']);
|
|
410
412
|
}
|
|
411
413
|
);
|
|
412
414
|
|
|
@@ -444,6 +446,10 @@ Route::prefix(config('fleetops.api.routing.prefix'))->namespace('Fleetbase\Fleet
|
|
|
444
446
|
$router->post('{id}/unassign-order', $controller('unassignOrder'));
|
|
445
447
|
$router->post('{id}/assign-vehicle', $controller('assignVehicle'));
|
|
446
448
|
$router->post('{id}/unassign-vehicle', $controller('unassignVehicle'));
|
|
449
|
+
$router->post('{id}/send-credentials', $controller('sendCredentials'));
|
|
450
|
+
$router->post('{id}/reset-credentials', $controller('resetCredentials'));
|
|
451
|
+
$router->post('{id}/deactivate-login', $controller('deactivateLogin'));
|
|
452
|
+
$router->post('{id}/reactivate-login', $controller('reactivateLogin'));
|
|
447
453
|
$router->match(['get', 'post'], 'export', $controller('export'));
|
|
448
454
|
$router->post('import', $controller('import'));
|
|
449
455
|
// Driver scheduling endpoints
|
|
@@ -764,6 +770,7 @@ Route::prefix(config('fleetops.api.routing.prefix'))->namespace('Fleetbase\Fleet
|
|
|
764
770
|
function ($router) {
|
|
765
771
|
$router->get('customers', 'FleetOpsLookupController@polymorphs');
|
|
766
772
|
$router->get('facilitators', 'FleetOpsLookupController@polymorphs');
|
|
773
|
+
$router->get('profile-identity', 'FleetOpsLookupController@profileIdentity');
|
|
767
774
|
}
|
|
768
775
|
);
|
|
769
776
|
$router->group(
|
|
@@ -809,6 +816,12 @@ Route::prefix(config('fleetops.api.routing.prefix'))->namespace('Fleetbase\Fleet
|
|
|
809
816
|
$router->post('orchestrator-settings', 'SettingController@saveOrchestratorSettings');
|
|
810
817
|
$router->get('orchestrator-card-fields', 'SettingController@getOrchestratorCardFields');
|
|
811
818
|
$router->post('orchestrator-card-fields', 'SettingController@saveOrchestratorCardFields');
|
|
819
|
+
$router->get('telematics-settings', 'SettingController@getTelematicsSettings');
|
|
820
|
+
$router->post('telematics-settings', 'SettingController@saveTelematicsSettings');
|
|
821
|
+
$router->get('admin-telematics-settings', 'SettingController@getAdminTelematicsSettings');
|
|
822
|
+
$router->post('admin-telematics-settings', 'SettingController@saveAdminTelematicsSettings');
|
|
823
|
+
$router->get('telematics-storage-usage', 'SettingController@getTelematicsStorageUsage');
|
|
824
|
+
$router->post('telematics-retention/run', 'SettingController@runTelematicsRetention');
|
|
812
825
|
}
|
|
813
826
|
);
|
|
814
827
|
$router->group(
|
|
@@ -406,6 +406,7 @@ test('asset status capability exposes metadata and denied branches', function ()
|
|
|
406
406
|
->and($capability->label())->toBe('Fleet-Ops asset status')
|
|
407
407
|
->and($capability->description())->toContain('vehicle, device, sensor')
|
|
408
408
|
->and($capability->permissions())->toBe([
|
|
409
|
+
'fleet-ops see driver',
|
|
409
410
|
'fleet-ops see vehicle',
|
|
410
411
|
'fleet-ops see device',
|
|
411
412
|
'fleet-ops see sensor',
|
|
@@ -17,6 +17,9 @@ use Fleetbase\FleetOps\Support\Analytics\RevenueTrend;
|
|
|
17
17
|
use Fleetbase\FleetOps\Support\Analytics\TopDrivers;
|
|
18
18
|
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
19
19
|
use Fleetbase\Models\Company;
|
|
20
|
+
use Illuminate\Database\ConnectionResolver;
|
|
21
|
+
use Illuminate\Database\Eloquent\Model as EloquentModel;
|
|
22
|
+
use Illuminate\Database\SQLiteConnection;
|
|
20
23
|
use Illuminate\Http\Request;
|
|
21
24
|
use Illuminate\Support\Carbon;
|
|
22
25
|
|
|
@@ -273,6 +276,16 @@ test('analytics base applies company currency and explicit date ranges', functio
|
|
|
273
276
|
});
|
|
274
277
|
|
|
275
278
|
test('live fleet analytics serializes driver and vehicle map payloads', function () {
|
|
279
|
+
// The markers' cards look up the current order and last known position.
|
|
280
|
+
$connection = new SQLiteConnection(new PDO('sqlite::memory:'));
|
|
281
|
+
$connection->statement('create table orders (uuid varchar(64) primary key, public_id varchar(64) null, company_uuid varchar(64) null, driver_assigned_uuid varchar(64) null, vehicle_assigned_uuid varchar(64) null, tracking_number_uuid varchar(64) null, status varchar(64) null, deleted_at datetime null, created_at datetime null, updated_at datetime null)');
|
|
282
|
+
$connection->statement('create table drivers (uuid varchar(64) primary key, public_id varchar(64) null, company_uuid varchar(64) null, user_uuid varchar(64) null, vehicle_uuid varchar(64) null, current_job_uuid varchar(64) null, deleted_at datetime null, created_at datetime null, updated_at datetime null)');
|
|
283
|
+
$connection->statement('create table users (uuid varchar(64) primary key, company_uuid varchar(64) null, name varchar(255) null, deleted_at datetime null, created_at datetime null, updated_at datetime null)');
|
|
284
|
+
$connection->statement('create table positions (uuid varchar(64) primary key, company_uuid varchar(64) null, subject_uuid varchar(64) null, subject_type varchar(255) null, order_uuid varchar(64) null, speed numeric null, heading numeric null, deleted_at datetime null, created_at datetime null, updated_at datetime null)');
|
|
285
|
+
$resolver = new ConnectionResolver(['default' => $connection, 'mysql' => $connection]);
|
|
286
|
+
$resolver->setDefaultConnection('mysql');
|
|
287
|
+
EloquentModel::setConnectionResolver($resolver);
|
|
288
|
+
|
|
276
289
|
$analytics = new LiveFleet();
|
|
277
290
|
|
|
278
291
|
$driver = new TestFleetOpsLiveFleetDriver();
|
|
@@ -308,7 +321,21 @@ test('live fleet analytics serializes driver and vehicle map payloads', function
|
|
|
308
321
|
$driverPayload->setAccessible(true);
|
|
309
322
|
$vehiclePayload->setAccessible(true);
|
|
310
323
|
|
|
311
|
-
|
|
324
|
+
$driverResult = $driverPayload->invoke($analytics, $driver);
|
|
325
|
+
$vehicleResult = $vehiclePayload->invoke($analytics, $vehicle);
|
|
326
|
+
|
|
327
|
+
// Each marker carries the live map's card: the same index resource the live endpoints return.
|
|
328
|
+
expect($driverResult['card'])->toBeArray()
|
|
329
|
+
->and($driverResult['card'])->toHaveKeys(['name', 'status', 'phone', 'email', 'online', 'meta'])
|
|
330
|
+
->and($driverResult['card']['meta'])->toHaveKeys(['status_label', 'speed_label', 'heading_label', 'location_coordinates', 'current_order_reference'])
|
|
331
|
+
->and($driverResult['card']['name'])->toBe('Ada Driver')
|
|
332
|
+
->and($vehicleResult['card'])->toHaveKeys(['display_name', 'driver_name', 'plate_number', 'online', 'meta'])
|
|
333
|
+
->and($vehicleResult['card']['display_name'])->toBe('Van 7')
|
|
334
|
+
->and($vehicleResult['card']['meta'])->toHaveKeys(['status_label', 'speed_label', 'heading_label', 'location_coordinates', 'current_order_reference']);
|
|
335
|
+
|
|
336
|
+
unset($driverResult['card'], $vehicleResult['card']);
|
|
337
|
+
|
|
338
|
+
expect($driverResult)->toBe([
|
|
312
339
|
'uuid' => 'driver-uuid',
|
|
313
340
|
'public_id' => 'driver-public',
|
|
314
341
|
'name' => 'Ada Driver',
|
|
@@ -320,7 +347,7 @@ test('live fleet analytics serializes driver and vehicle map payloads', function
|
|
|
320
347
|
'lat' => 1.30,
|
|
321
348
|
'lng' => 103.80,
|
|
322
349
|
'updated_at' => '2026-01-01 10:00:00',
|
|
323
|
-
])->and($
|
|
350
|
+
])->and($vehicleResult)->toBe([
|
|
324
351
|
'uuid' => 'vehicle-uuid',
|
|
325
352
|
'public_id' => 'vehicle-public',
|
|
326
353
|
'name' => 'Van 7',
|
|
@@ -800,6 +800,27 @@ test('api customer controller authenticates and verifies customer codes', functi
|
|
|
800
800
|
]))))->toBe(['error' => 'Invalid verification code.']);
|
|
801
801
|
});
|
|
802
802
|
|
|
803
|
+
test('api customer controller refuses deactivated customer logins', function () {
|
|
804
|
+
$controller = fleetopsApiCustomerController();
|
|
805
|
+
foreach (['activeUser', 'loginUser', 'verificationUser'] as $property) {
|
|
806
|
+
$controller->{$property}->setAttribute('status', 'inactive');
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
$responses = [
|
|
810
|
+
$controller->login(Request::create('/v1/customers/login', 'POST', ['identity' => 'jane@example.test', 'password' => 'password-secret'])),
|
|
811
|
+
$controller->loginWithPhone(Request::create('/v1/customers/login/phone', 'POST', ['phone' => '15551234567'])),
|
|
812
|
+
$controller->verifyCode(Request::create('/v1/customers/verify', 'POST', ['identity' => 'jane@example.test', 'code' => '123456'])),
|
|
813
|
+
];
|
|
814
|
+
|
|
815
|
+
foreach ($responses as $response) {
|
|
816
|
+
expect($response->getStatusCode())->toBe(403)
|
|
817
|
+
->and(fleetopsApiCustomerJson($response))->toBe(['error' => 'This customer login has been deactivated.']);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// No verification code is sent
|
|
821
|
+
expect($controller->smsVerifications)->toBe([]);
|
|
822
|
+
});
|
|
823
|
+
|
|
803
824
|
test('api customer controller resets forgotten passwords', function () {
|
|
804
825
|
$controller = fleetopsApiCustomerController();
|
|
805
826
|
$forgot = $controller->forgotPassword(Request::create('/v1/customers/forgot-password', 'POST', [
|