@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
package/RELEASE.md
CHANGED
|
@@ -1,21 +1,56 @@
|
|
|
1
|
-
> v0.6.
|
|
1
|
+
> v0.6.70 ~ "Telematics data retention"
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
## What's New
|
|
5
|
-
- **
|
|
6
|
-
- **
|
|
7
|
-
- **
|
|
5
|
+
- **Telematics Data settings.** A new Fleet-Ops → Settings → Telematics Data page lets each organization decide how long telemetry is kept: device events, raw event payloads, positions, processed and quarantined delivery envelopes, and sync run diagnostics. Administrators set system-wide defaults from the admin console (Fleet-Ops Config → Telematics Data). A value of 0 keeps rows forever.
|
|
6
|
+
- **Scheduled retention sweep.** `fleetops:prune-telematics-data` runs every fifteen minutes and applies each organization's policy in bounded batches, so a large backlog is drained gradually without stalling ingestion. Defaults: events 30 days, raw payloads stripped after 7 days, positions 90 days, processed deliveries 24 hours, quarantined deliveries 7 days, sync runs 7 days.
|
|
7
|
+
- **Storage usage and on-demand cleanup.** The settings page shows rows, oldest row and estimated size per table for your organization, and "Run cleanup now" queues an immediate retention run.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
## Fixes
|
|
11
|
-
-
|
|
12
|
-
-
|
|
11
|
+
- Device events no longer store the raw provider unit twice. `payload` keeps the raw unit; `meta` holds only the normalized block (speed, heading, odometer, ignition, fuel level, timing). New event rows are roughly half the size.
|
|
12
|
+
- Telemetry-driven saves (device, event, position, vehicle, sensors) no longer write to the activity log on every poll. Organizations that want them can enable "Log telemetry activity" in Telematics Data settings. Manual edits are logged as before.
|
|
13
|
+
- Delivery inbox and sync run retention moved from the drain command into the retention sweep; `fleetops:drain-telematic-inbox` now only recovers deliveries and finishes interrupted runs.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
## Upgrade notes
|
|
17
|
+
- New indexes on `device_events (company_uuid, created_at)`, `positions (company_uuid, created_at)` and `telematic_sync_runs (telematic_uuid, updated_at)`; on very large tables run `php artisan migrate` in a maintenance window.
|
|
18
|
+
- Retention is enabled by default. Existing rows older than the defaults are removed over successive runs after upgrading; set the values to 0 before upgrading if you need to keep history indefinitely.
|
|
19
|
+
- Deleting rows frees space inside the database files, not on disk. Run `OPTIMIZE TABLE` off-peak to return space to the operating system.
|
|
20
|
+
- Run `php artisan fleetbase:create-permissions` to register the `telematics-settings` permission and the Telematics Settings Manager policy and role.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
## Testing
|
|
24
|
+
- Backend tests cover the retention policy layering and clamping, the prune command (per-company policies, compaction, inbox tables via connections, orphans, batch caps, dry runs, filters, locking), the settings and storage usage endpoints, the index migrations, the on-demand job, and the meta and activity-log changes to ingestion.
|
|
25
|
+
- Ember tests cover the settings route guard, the settings controller, and the admin defaults component.
|
|
26
|
+
> v0.6.70 ~ "Order reporting and a cleaner default dashboard"
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
## What's New
|
|
30
|
+
- **Report on orders, their items and tracking.** The report builder can answer questions like "Which products sold the most this month?" and "What did orders total this month?".
|
|
31
|
+
- Orders expose their ID and Internal ID, and relationships for tracking (tracking number, region, latest status), Order Config, Customer Vendor, Created By, Purchase Rate › Service Quote and Payload Return.
|
|
32
|
+
- `Payload` exposes its items with name, SKU, price, dimensions, metadata and destination. Select an item column for one row per item, or group by one to summarise per product.
|
|
33
|
+
- Summary columns count orders distinctly, so they stay correct when item rows are selected. Distance, duration and transaction amount have totals and averages.
|
|
34
|
+
- `meta` has no fixed shape, so no column assumes a key in it. Read keys with a computed column, for example `CAST(JSON_UNQUOTE(JSON_EXTRACT(payload.entities.meta, '$.quantity')) AS DECIMAL(15,2))`.
|
|
35
|
+
- Column labels drop the table name ("Type", not "Order Type"). Distance and duration are labelled in meters and seconds, and money columns are marked "(minor units)".
|
|
36
|
+
- **A cleaner default dashboard.** Fleet-Ops widgets declare their place on the default dashboard: Radar first, then Active Orders and Drivers Online, a full-width Live Fleet map, and Revenue Trend, Top Drivers and Maintenance side by side.
|
|
37
|
+
- **Live Fleet widget cards match the live map.** Clicking a driver or vehicle on the widget map shows the same card as **Operations › Live Map**.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
## Fixes
|
|
41
|
+
- Custom field values on fuel reports and service areas are saved. They were dropped because the save hooks read the snake_case payload root, while the console sends `fuelReport` and `serviceArea`.
|
|
42
|
+
- The report schema no longer references 13 columns and join keys that don't exist: driver name, email and phone and the driver's vehicle, the vehicle's driver, and the fuel report cost, odometer and date. Transaction summaries sum `transaction.amount`.
|
|
43
|
+
- Every report table, and the item join, leaves soft-deleted rows out on core-api v1.6.64 and later.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
## Dependencies
|
|
47
|
+
- Order items, JSON totals and soft deletes in reports need `fleetbase/core-api` v1.6.64. The schema still registers on older versions.
|
|
48
|
+
- The report builder and the default dashboard order ship in `@fleetbase/ember-ui` v0.4.4.
|
|
13
49
|
|
|
14
50
|
---
|
|
15
51
|
## Testing
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- `docs/TELEMATICS_QUEUES.md` now documents that dedicated telematics workers must share the queue worker's image and `APP_KEY`, with verification commands and troubleshooting.
|
|
52
|
+
- Every declared report column and join key was checked against the database schema, and order reports were run against real storefront orders on MySQL in strict mode.
|
|
53
|
+
- Tests cover the report schema at 100% line coverage, the Live Fleet map cards and the default dashboard order.
|
|
19
54
|
|
|
20
55
|
---
|
|
21
56
|
## Need help?
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<div class="space-y-6">
|
|
2
|
+
<InfoBlock @type="info" @icon="circle-info" class="mb-4">
|
|
3
|
+
Configure telematics retention policy and activity logging for all organizations. Organizations can choose device event and position history retention within these limits from Fleet-Ops › Settings › Telematics.
|
|
4
|
+
A maximum takes precedence over organization preferences, inherited defaults and requests to keep history forever. Reducing a maximum can delete existing history at the next cleanup.
|
|
5
|
+
</InfoBlock>
|
|
6
|
+
|
|
7
|
+
<ContentPanel @title="System Device Events Retention Policy" @open={{true}} @wrapperClass="bordered-classic">
|
|
8
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
9
|
+
<InputGroup @name="Default Device Event Retention (days)" @helpText="Retention for organizations that inherit the system default. 0 keeps history forever, subject to the maximum below.">
|
|
10
|
+
<Input @type="number" @value={{this.eventRetentionDays}} min="0" max="3650" class="form-input w-full" />
|
|
11
|
+
</InputGroup>
|
|
12
|
+
<InputGroup @name="Maximum Device Event Retention (days)" @helpText="The longest any organization can keep device events. 0 sets no maximum and allows organizations to keep history forever.">
|
|
13
|
+
<Input @type="number" @value={{this.maxEventRetentionDays}} min="0" max="3650" class="form-input w-full" />
|
|
14
|
+
</InputGroup>
|
|
15
|
+
</div>
|
|
16
|
+
</ContentPanel>
|
|
17
|
+
|
|
18
|
+
<ContentPanel @title="System Position History Retention Policy" @open={{true}} @wrapperClass="bordered-classic">
|
|
19
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
20
|
+
<InputGroup @name="Default Position History Retention (days)" @helpText="Retention for organizations that inherit the system default. 0 keeps history forever, subject to the maximum below.">
|
|
21
|
+
<Input @type="number" @value={{this.positionRetentionDays}} min="0" max="3650" class="form-input w-full" />
|
|
22
|
+
</InputGroup>
|
|
23
|
+
<InputGroup @name="Maximum Position History Retention (days)" @helpText="The longest any organization can keep vehicle and trailer positions. 0 sets no maximum and allows organizations to keep history forever.">
|
|
24
|
+
<Input @type="number" @value={{this.maxPositionRetentionDays}} min="0" max="3650" class="form-input w-full" />
|
|
25
|
+
</InputGroup>
|
|
26
|
+
</div>
|
|
27
|
+
</ContentPanel>
|
|
28
|
+
|
|
29
|
+
<ContentPanel @title="System Device Event Raw Payload Retention" @open={{true}} @wrapperClass="bordered-classic">
|
|
30
|
+
<InputGroup @name="Device Event Raw Payload Retention (days)" @helpText="Remove raw provider payloads from retained device events across all organizations after this many days. 0 disables compaction. This does not change how long the event history itself is kept.">
|
|
31
|
+
<Input @type="number" @value={{this.eventCompactAfterDays}} min="0" max="3650" class="form-input w-full" />
|
|
32
|
+
</InputGroup>
|
|
33
|
+
</ContentPanel>
|
|
34
|
+
|
|
35
|
+
<ContentPanel @title="System Delivery Inbox and Sync Run Retention Policy" @open={{true}} @wrapperClass="bordered-classic">
|
|
36
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
37
|
+
<InputGroup @name="Processed Delivery Retention (hours)" @helpText="Delete processed delivery envelopes across all organizations after this many hours. 0 keeps them forever.">
|
|
38
|
+
<Input @type="number" @value={{this.processedRetentionHours}} min="0" max="720" class="form-input w-full" />
|
|
39
|
+
</InputGroup>
|
|
40
|
+
<InputGroup @name="Quarantined Delivery Retention (days)" @helpText="Delete quarantined delivery envelopes across all organizations after this many days. 0 keeps them forever.">
|
|
41
|
+
<Input @type="number" @value={{this.quarantineRetentionDays}} min="0" max="365" class="form-input w-full" />
|
|
42
|
+
</InputGroup>
|
|
43
|
+
<InputGroup @name="Sync Run Diagnostics Retention (days)" @helpText="Delete finished sync run diagnostics across all organizations after this many days. 0 keeps them forever.">
|
|
44
|
+
<Input @type="number" @value={{this.syncRunRetentionDays}} min="0" max="365" class="form-input w-full" />
|
|
45
|
+
</InputGroup>
|
|
46
|
+
</div>
|
|
47
|
+
</ContentPanel>
|
|
48
|
+
|
|
49
|
+
<ContentPanel @title="System Telemetry Activity Logging" @open={{true}} @wrapperClass="bordered-classic">
|
|
50
|
+
<Toggle
|
|
51
|
+
@label="Log Telemetry Activity"
|
|
52
|
+
@helpText="Write telemetry-driven device, event and vehicle saves to the activity log for all organizations. Every poll can add several activity rows per device."
|
|
53
|
+
@isToggled={{this.logTelemetryActivity}}
|
|
54
|
+
@onToggle={{fn (mut this.logTelemetryActivity)}}
|
|
55
|
+
/>
|
|
56
|
+
</ContentPanel>
|
|
57
|
+
|
|
58
|
+
<div class="flex justify-end">
|
|
59
|
+
<Button
|
|
60
|
+
@type="primary"
|
|
61
|
+
@size="sm"
|
|
62
|
+
@icon="save"
|
|
63
|
+
@text="Save System Policy"
|
|
64
|
+
@onClick={{perform this.saveSettings}}
|
|
65
|
+
@disabled={{or this.saveSettings.isRunning this.loadSettings.isRunning (not this.settingsLoaded)}}
|
|
66
|
+
@isLoading={{or this.saveSettings.isRunning this.loadSettings.isRunning}}
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<ContentPanel @title={{t "settings.telematics.storage-usage"}} @open={{true}} @wrapperClass="bordered-classic">
|
|
71
|
+
<p class="mb-3 text-sm text-gray-600 dark:text-gray-300">
|
|
72
|
+
Storage usage includes telematics data across all organizations. Cleanup applies system-wide, using the saved system policy and each organization's history preferences within that policy.
|
|
73
|
+
</p>
|
|
74
|
+
<div class="flex items-center justify-end gap-2 mb-3">
|
|
75
|
+
<Button
|
|
76
|
+
@size="xs"
|
|
77
|
+
@icon="rotate"
|
|
78
|
+
@text={{t "settings.telematics.refresh"}}
|
|
79
|
+
@onClick={{perform this.loadUsage}}
|
|
80
|
+
@disabled={{this.loadUsage.isRunning}}
|
|
81
|
+
@isLoading={{this.loadUsage.isRunning}}
|
|
82
|
+
/>
|
|
83
|
+
<Button
|
|
84
|
+
@type="danger"
|
|
85
|
+
@size="xs"
|
|
86
|
+
@icon="broom"
|
|
87
|
+
@text={{t "settings.telematics.run-cleanup"}}
|
|
88
|
+
@onClick={{this.confirmRunCleanup}}
|
|
89
|
+
@disabled={{or this.runCleanup.isRunning this.loadSettings.isRunning this.saveSettings.isRunning (not this.settingsLoaded)}}
|
|
90
|
+
@isLoading={{this.runCleanup.isRunning}}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
{{#if this.usageRows.length}}
|
|
94
|
+
<div class="overflow-x-auto">
|
|
95
|
+
<table class="w-full text-sm">
|
|
96
|
+
<thead>
|
|
97
|
+
<tr class="text-left text-xs uppercase text-gray-500 dark:text-gray-400">
|
|
98
|
+
<th class="py-2 pr-3">{{t "settings.telematics.table"}}</th>
|
|
99
|
+
<th class="py-2 pr-3 text-right">{{t "settings.telematics.rows"}}</th>
|
|
100
|
+
<th class="py-2 pr-3 text-right">{{t "settings.telematics.estimated-size"}}</th>
|
|
101
|
+
<th class="py-2 pl-6 text-right whitespace-nowrap">{{t "settings.telematics.oldest"}}</th>
|
|
102
|
+
</tr>
|
|
103
|
+
</thead>
|
|
104
|
+
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
105
|
+
{{#each this.usageRows as |row|}}
|
|
106
|
+
<tr class="text-gray-800 dark:text-gray-100">
|
|
107
|
+
<td class="py-2 pr-3">
|
|
108
|
+
<div class="font-medium">{{row.label}}</div>
|
|
109
|
+
{{#if row.raw_payload_rows}}
|
|
110
|
+
<div class="text-xs text-gray-500 dark:text-gray-400">
|
|
111
|
+
{{t "settings.telematics.raw-payload-rows" count=(format-number row.raw_payload_rows) compactable=(format-number row.compactable_rows)}}
|
|
112
|
+
</div>
|
|
113
|
+
{{/if}}
|
|
114
|
+
</td>
|
|
115
|
+
<td class="py-2 pr-3 text-right">
|
|
116
|
+
{{#if (eq row.rows null)}}
|
|
117
|
+
{{t "settings.telematics.not-available"}}
|
|
118
|
+
{{else if row.rows_estimated}}
|
|
119
|
+
{{t "settings.telematics.estimated-rows" count=(format-number row.rows)}}
|
|
120
|
+
{{else}}
|
|
121
|
+
{{format-number row.rows}}
|
|
122
|
+
{{/if}}
|
|
123
|
+
</td>
|
|
124
|
+
<td class="py-2 pr-3 text-right">{{#if (or row.estimated_bytes (eq row.estimated_bytes 0))}}{{format-bytes row.estimated_bytes}}{{else}}{{t "settings.telematics.not-available"}}{{/if}}</td>
|
|
125
|
+
<td class="py-2 pl-6 text-right text-xs whitespace-nowrap">{{#if row.oldest}}{{row.oldest}}{{else}}{{t "settings.telematics.not-available"}}{{/if}}</td>
|
|
126
|
+
</tr>
|
|
127
|
+
{{/each}}
|
|
128
|
+
</tbody>
|
|
129
|
+
</table>
|
|
130
|
+
</div>
|
|
131
|
+
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400">{{t "settings.telematics.space-reclaim-note"}}</p>
|
|
132
|
+
{{else if this.loadUsage.isRunning}}
|
|
133
|
+
<div class="flex items-center justify-center py-8">
|
|
134
|
+
<Spinner />
|
|
135
|
+
</div>
|
|
136
|
+
{{else}}
|
|
137
|
+
<div class="text-center py-8 text-sm text-gray-400 dark:text-gray-500">
|
|
138
|
+
{{t "settings.telematics.usage-unavailable"}}
|
|
139
|
+
</div>
|
|
140
|
+
{{/if}}
|
|
141
|
+
</ContentPanel>
|
|
142
|
+
<Spacer @height="200px" />
|
|
143
|
+
</div>
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action } from '@ember/object';
|
|
5
|
+
import { task, timeout } from 'ember-concurrency';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* System-wide telematics policy, storage diagnostics and operational controls.
|
|
9
|
+
*/
|
|
10
|
+
export default class AdminTelematicsSettingsComponent extends Component {
|
|
11
|
+
@service fetch;
|
|
12
|
+
@service notifications;
|
|
13
|
+
@service intl;
|
|
14
|
+
@service modalsManager;
|
|
15
|
+
@tracked eventRetentionDays = 30;
|
|
16
|
+
@tracked maxEventRetentionDays = 0;
|
|
17
|
+
@tracked eventCompactAfterDays = 7;
|
|
18
|
+
@tracked positionRetentionDays = 90;
|
|
19
|
+
@tracked maxPositionRetentionDays = 0;
|
|
20
|
+
@tracked processedRetentionHours = 24;
|
|
21
|
+
@tracked quarantineRetentionDays = 7;
|
|
22
|
+
@tracked syncRunRetentionDays = 7;
|
|
23
|
+
@tracked logTelemetryActivity = false;
|
|
24
|
+
@tracked settingsLoaded = false;
|
|
25
|
+
@tracked usage = null;
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
super(...arguments);
|
|
29
|
+
this.loadSettings.perform();
|
|
30
|
+
this.loadUsage.perform();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@task({ drop: true }) *loadSettings() {
|
|
34
|
+
try {
|
|
35
|
+
const settings = yield this.fetch.get('fleet-ops/settings/admin-telematics-settings');
|
|
36
|
+
this.applySettings(settings);
|
|
37
|
+
this.settingsLoaded = true;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
this.notifications.serverError(error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@task({ drop: true }) *saveSettings() {
|
|
44
|
+
if (!this.settingsLoaded) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const settings = yield this.fetch.post('fleet-ops/settings/admin-telematics-settings', this.settingsPayload);
|
|
50
|
+
this.applySettings(settings);
|
|
51
|
+
this.notifications.success('System telematics settings saved.');
|
|
52
|
+
} catch (error) {
|
|
53
|
+
this.notifications.serverError(error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Load system-wide storage usage across all organizations. */
|
|
58
|
+
@task({ drop: true }) *loadUsage() {
|
|
59
|
+
const abortController = new AbortController();
|
|
60
|
+
const requestTimeout = setTimeout(() => abortController.abort(), 30000);
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
// Pass the signal as request data; fetch.get options do not forward it.
|
|
64
|
+
const usage = yield this.fetch.request('fleet-ops/settings/telematics-storage-usage?include_payload_counts=0', 'GET', { signal: abortController.signal });
|
|
65
|
+
if (usage?.scope !== 'system') {
|
|
66
|
+
throw new Error(this.intl.t('settings.telematics.usage-unavailable'));
|
|
67
|
+
}
|
|
68
|
+
this.usage = usage;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
this.notifications.serverError(abortController.signal.aborted ? new Error(this.intl.t('settings.telematics.usage-unavailable')) : error);
|
|
71
|
+
} finally {
|
|
72
|
+
clearTimeout(requestTimeout);
|
|
73
|
+
abortController.abort();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Queue cleanup for all organizations using the saved policy. */
|
|
78
|
+
@task({ drop: true }) *runCleanup() {
|
|
79
|
+
try {
|
|
80
|
+
const cleanup = yield this.fetch.post('fleet-ops/settings/telematics-retention/run');
|
|
81
|
+
if (cleanup?.scope !== 'system') {
|
|
82
|
+
throw new Error(this.intl.t('settings.telematics.cleanup-unavailable'));
|
|
83
|
+
}
|
|
84
|
+
this.notifications.success(this.intl.t('settings.telematics.cleanup-queued'));
|
|
85
|
+
yield timeout(5000);
|
|
86
|
+
yield this.loadUsage.perform();
|
|
87
|
+
} catch (error) {
|
|
88
|
+
this.notifications.serverError(error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@action confirmRunCleanup() {
|
|
93
|
+
return this.modalsManager.confirm({
|
|
94
|
+
title: this.intl.t('settings.telematics.run-cleanup'),
|
|
95
|
+
body: this.intl.t('settings.telematics.run-cleanup-confirm'),
|
|
96
|
+
acceptButtonText: this.intl.t('settings.telematics.run-cleanup'),
|
|
97
|
+
acceptButtonIcon: 'broom',
|
|
98
|
+
onConfirm: () => this.runCleanup.perform(),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get usageRows() {
|
|
103
|
+
const tables = this.usage?.tables ?? {};
|
|
104
|
+
|
|
105
|
+
return ['device_events', 'positions', 'telematic_deliveries', 'telematic_sync_runs']
|
|
106
|
+
.filter((table) => tables[table])
|
|
107
|
+
.map((table) => ({ table, label: this.intl.t(`settings.telematics.tables.${table}`), ...tables[table] }));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get settingsPayload() {
|
|
111
|
+
return {
|
|
112
|
+
event_retention_days: this.toInteger(this.eventRetentionDays),
|
|
113
|
+
max_event_retention_days: this.maxEventRetentionDays,
|
|
114
|
+
event_compact_after_days: this.toInteger(this.eventCompactAfterDays),
|
|
115
|
+
position_retention_days: this.toInteger(this.positionRetentionDays),
|
|
116
|
+
max_position_retention_days: this.maxPositionRetentionDays,
|
|
117
|
+
processed_retention_hours: this.toInteger(this.processedRetentionHours),
|
|
118
|
+
quarantine_retention_days: this.toInteger(this.quarantineRetentionDays),
|
|
119
|
+
sync_run_retention_days: this.toInteger(this.syncRunRetentionDays),
|
|
120
|
+
log_telemetry_activity: Boolean(this.logTelemetryActivity),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
applySettings(settings = {}) {
|
|
125
|
+
if (!Object.prototype.hasOwnProperty.call(settings ?? {}, 'max_event_retention_days') || !Object.prototype.hasOwnProperty.call(settings ?? {}, 'max_position_retention_days')) {
|
|
126
|
+
throw new Error(this.intl.t('settings.telematics.settings-unavailable'));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.eventRetentionDays = settings.event_retention_days ?? this.eventRetentionDays;
|
|
130
|
+
this.maxEventRetentionDays = settings.max_event_retention_days ?? this.maxEventRetentionDays;
|
|
131
|
+
this.eventCompactAfterDays = settings.event_compact_after_days ?? this.eventCompactAfterDays;
|
|
132
|
+
this.positionRetentionDays = settings.position_retention_days ?? this.positionRetentionDays;
|
|
133
|
+
this.maxPositionRetentionDays = settings.max_position_retention_days ?? this.maxPositionRetentionDays;
|
|
134
|
+
this.processedRetentionHours = settings.processed_retention_hours ?? this.processedRetentionHours;
|
|
135
|
+
this.quarantineRetentionDays = settings.quarantine_retention_days ?? this.quarantineRetentionDays;
|
|
136
|
+
this.syncRunRetentionDays = settings.sync_run_retention_days ?? this.syncRunRetentionDays;
|
|
137
|
+
this.logTelemetryActivity = settings.log_telemetry_activity ?? this.logTelemetryActivity;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
toInteger(value) {
|
|
141
|
+
const number = parseInt(value, 10);
|
|
142
|
+
|
|
143
|
+
return Number.isFinite(number) && number > 0 ? number : 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -30,11 +30,22 @@
|
|
|
30
30
|
<Input @value={{@resource.title}} @type="text" class="w-full form-input" placeholder={{t "common.title"}} disabled={{cannot-write @resource}} />
|
|
31
31
|
</InputGroup>
|
|
32
32
|
<InputGroup @name={{t "common.email"}}>
|
|
33
|
-
<Input
|
|
33
|
+
<Input
|
|
34
|
+
@value={{@resource.email}}
|
|
35
|
+
@type="text"
|
|
36
|
+
class="w-full form-input"
|
|
37
|
+
placeholder={{t "common.email"}}
|
|
38
|
+
disabled={{or (cannot-write @resource) @resource.is_staff_linked}}
|
|
39
|
+
/>
|
|
34
40
|
</InputGroup>
|
|
35
41
|
<InputGroup @name={{t "common.phone"}}>
|
|
36
|
-
<PhoneInput @value={{@resource.phone}} @onInput={{fn (mut @resource.phone)}} class="form-input w-full" disabled={{cannot-write @resource}} />
|
|
42
|
+
<PhoneInput @value={{@resource.phone}} @onInput={{fn (mut @resource.phone)}} class="form-input w-full" disabled={{or (cannot-write @resource) @resource.is_staff_linked}} />
|
|
37
43
|
</InputGroup>
|
|
44
|
+
{{#if @resource.is_staff_linked}}
|
|
45
|
+
<div class="lg:col-span-2 -mt-1 mb-2 text-xs text-gray-500 dark:text-gray-400" data-test-staff-linked-helper>{{t "profile-account.staff-linked-helper"}}</div>
|
|
46
|
+
{{else if @resource.isNew}}
|
|
47
|
+
<ProfileIdentityHint @email={{@resource.email}} @phone={{@resource.phone}} @type="contact" class="lg:col-span-2" />
|
|
48
|
+
{{/if}}
|
|
38
49
|
<InputGroup @name={{t "common.internal-id"}}>
|
|
39
50
|
<Input @value={{@resource.internal_id}} @type="text" class="w-full form-input" placeholder={{t "common.internal-id"}} disabled={{cannot-write @resource}} />
|
|
40
51
|
</InputGroup>
|
|
@@ -1,20 +1,4 @@
|
|
|
1
1
|
<div class="details-wrapper" ...attributes>
|
|
2
|
-
<ContentPanel @title={{t "customer.fields.user-account"}} @open={{true}} @wrapperClass="bordered-top">
|
|
3
|
-
<div class="grid grid-cols-2 gap-2 text-xs dark:text-gray-100">
|
|
4
|
-
<div class="field-info-container md:col-span-3">
|
|
5
|
-
<div class="field-name">{{t "common.name"}}</div>
|
|
6
|
-
<div class="field-value">{{n-a @resource.name}}</div>
|
|
7
|
-
</div>
|
|
8
|
-
<div class="field-info-container">
|
|
9
|
-
<div class="field-name">{{t "common.email"}}</div>
|
|
10
|
-
<ClickToCopy @value={{@resource.email}} class="field-value">{{n-a @resource.email}}</ClickToCopy>
|
|
11
|
-
</div>
|
|
12
|
-
<div class="field-info-container">
|
|
13
|
-
<div class="field-name">{{t "common.phone"}}</div>
|
|
14
|
-
<ClickToCopy @value={{@resource.phone}} class="field-value">{{n-a @resource.phone}}</ClickToCopy>
|
|
15
|
-
</div>
|
|
16
|
-
</div>
|
|
17
|
-
</ContentPanel>
|
|
18
2
|
<ContentPanel @title={{t "common.details"}} @open={{true}} @wrapperClass="bordered-top">
|
|
19
3
|
<div class="grid grid-cols-2 gap-2 text-xs dark:text-gray-100">
|
|
20
4
|
<div class="field-info-container">
|
|
@@ -30,7 +14,7 @@
|
|
|
30
14
|
<ClickToCopy @value={{@resource.email}} class="field-value">{{n-a @resource.email}}</ClickToCopy>
|
|
31
15
|
</div>
|
|
32
16
|
<div class="field-info-container">
|
|
33
|
-
<div class="field-name">{{t "common.
|
|
17
|
+
<div class="field-name">{{t "common.phone"}}</div>
|
|
34
18
|
<ClickToCopy @value={{@resource.phone}} class="field-value">{{n-a @resource.phone}}</ClickToCopy>
|
|
35
19
|
</div>
|
|
36
20
|
<div class="field-info-container">
|
|
@@ -47,5 +31,21 @@
|
|
|
47
31
|
</div>
|
|
48
32
|
</div>
|
|
49
33
|
</ContentPanel>
|
|
34
|
+
<ContentPanel @title={{t "customer.fields.addresses"}} @open={{true}} @wrapperClass="bordered-top">
|
|
35
|
+
{{#if @resource.places.length}}
|
|
36
|
+
<div class="space-y-2 text-xs dark:text-gray-100">
|
|
37
|
+
{{#each @resource.places as |place|}}
|
|
38
|
+
<div class="flex items-center justify-between gap-2">
|
|
39
|
+
<Place::Pill @place={{place}} @subtitle={{place.address}} />
|
|
40
|
+
{{#if (eq place.id @resource.place_uuid)}}
|
|
41
|
+
<Badge @status="info" @hideStatusDot={{true}}>{{t "customer.fields.primary-address"}}</Badge>
|
|
42
|
+
{{/if}}
|
|
43
|
+
</div>
|
|
44
|
+
{{/each}}
|
|
45
|
+
</div>
|
|
46
|
+
{{else}}
|
|
47
|
+
<div class="text-xs text-gray-500 dark:text-gray-400">{{t "customer.fields.no-addresses"}}</div>
|
|
48
|
+
{{/if}}
|
|
49
|
+
</ContentPanel>
|
|
50
50
|
<CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
|
|
51
51
|
</div>
|
|
@@ -37,11 +37,22 @@
|
|
|
37
37
|
<Input @value={{@resource.title}} @type="text" class="w-full form-input" placeholder={{t "common.title"}} disabled={{cannot-write @resource}} />
|
|
38
38
|
</InputGroup>
|
|
39
39
|
<InputGroup @name={{t "common.email"}}>
|
|
40
|
-
<Input
|
|
40
|
+
<Input
|
|
41
|
+
@value={{@resource.email}}
|
|
42
|
+
@type="text"
|
|
43
|
+
class="w-full form-input"
|
|
44
|
+
placeholder={{t "common.email"}}
|
|
45
|
+
disabled={{or (cannot-write @resource) @resource.is_staff_linked}}
|
|
46
|
+
/>
|
|
41
47
|
</InputGroup>
|
|
42
48
|
<InputGroup @name={{t "common.phone"}}>
|
|
43
|
-
<PhoneInput @value={{@resource.phone}} @onInput={{fn (mut @resource.phone)}} class="form-input w-full" disabled={{cannot-write @resource}} />
|
|
49
|
+
<PhoneInput @value={{@resource.phone}} @onInput={{fn (mut @resource.phone)}} class="form-input w-full" disabled={{or (cannot-write @resource) @resource.is_staff_linked}} />
|
|
44
50
|
</InputGroup>
|
|
51
|
+
{{#if @resource.is_staff_linked}}
|
|
52
|
+
<div class="lg:col-span-2 -mt-1 mb-2 text-xs text-gray-500 dark:text-gray-400" data-test-staff-linked-helper>{{t "profile-account.staff-linked-helper"}}</div>
|
|
53
|
+
{{else if @resource.isNew}}
|
|
54
|
+
<ProfileIdentityHint @email={{@resource.email}} @phone={{@resource.phone}} @type="customer" class="lg:col-span-2" />
|
|
55
|
+
{{/if}}
|
|
45
56
|
<InputGroup @name={{t "common.internal-id"}}>
|
|
46
57
|
<Input @value={{@resource.internal_id}} @type="text" class="w-full form-input" placeholder={{t "common.internal-id"}} disabled={{cannot-write @resource}} />
|
|
47
58
|
</InputGroup>
|
|
@@ -1,71 +1,15 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
|
-
import { tracked } from '@glimmer/tracking';
|
|
3
2
|
import { inject as service } from '@ember/service';
|
|
4
3
|
import { action } from '@ember/object';
|
|
5
4
|
import { task } from 'ember-concurrency';
|
|
6
5
|
|
|
7
6
|
export default class CustomerFormComponent extends Component {
|
|
8
7
|
@service customerActions;
|
|
9
|
-
@service store;
|
|
10
8
|
@service fetch;
|
|
11
9
|
@service currentUser;
|
|
12
10
|
@service notifications;
|
|
13
|
-
@service modalsManager;
|
|
14
11
|
@service('universe/extension-manager') extensionManager;
|
|
15
12
|
|
|
16
|
-
@tracked userAccountActionButtons = [
|
|
17
|
-
{
|
|
18
|
-
icon: 'user-plus',
|
|
19
|
-
size: 'xs',
|
|
20
|
-
permission: 'iam create user',
|
|
21
|
-
onClick: async () => {
|
|
22
|
-
// Load IAM engine for user-form modal component
|
|
23
|
-
await this.extensionManager.ensureEngineLoaded('@fleetbase/iam-engine');
|
|
24
|
-
|
|
25
|
-
const user = this.store.createRecord('user', {
|
|
26
|
-
status: 'pending',
|
|
27
|
-
type: 'user',
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
this.modalsManager.show('modals/user-form', {
|
|
31
|
-
title: 'Create a new user',
|
|
32
|
-
user,
|
|
33
|
-
formPermission: 'iam create user',
|
|
34
|
-
uploadNewPhoto: (file) => {
|
|
35
|
-
this.fetch.uploadFile.perform(
|
|
36
|
-
file,
|
|
37
|
-
{
|
|
38
|
-
path: `uploads/${this.currentUser.companyId}/users/${user.slug}`,
|
|
39
|
-
subject_uui: user.id,
|
|
40
|
-
subject_type: 'user',
|
|
41
|
-
type: 'user_photo',
|
|
42
|
-
},
|
|
43
|
-
(uploadedFile) => {
|
|
44
|
-
user.setProperties({
|
|
45
|
-
avatar_uuid: uploadedFile.id,
|
|
46
|
-
avatar_url: uploadedFile.url,
|
|
47
|
-
avatar: uploadedFile,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
);
|
|
51
|
-
},
|
|
52
|
-
confirm: async (modal) => {
|
|
53
|
-
modal.startLoading();
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
await user.save();
|
|
57
|
-
this.notifications.success('New user created successfully!');
|
|
58
|
-
modal.done();
|
|
59
|
-
} catch (error) {
|
|
60
|
-
this.notifications.serverError(error);
|
|
61
|
-
modal.stopLoading();
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
];
|
|
68
|
-
|
|
69
13
|
get showWelcomeEmailOption() {
|
|
70
14
|
return this.args.resource?.isNew && this.extensionManager.isInstalled('@fleetbase/customer-portal-engine');
|
|
71
15
|
}
|
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
<div class="details-wrapper" ...attributes>
|
|
2
|
-
<ContentPanel @title={{t "
|
|
3
|
-
<div class="grid grid-cols-
|
|
2
|
+
<ContentPanel @title={{t "common.details"}} @open={{true}} @wrapperClass="bordered-top">
|
|
3
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-2 text-xs dark:text-gray-100">
|
|
4
4
|
<div class="field-info-container md:col-span-3">
|
|
5
5
|
<div class="field-name">{{t "common.name"}}</div>
|
|
6
6
|
<div class="field-value">{{n-a @resource.name}}</div>
|
|
7
7
|
</div>
|
|
8
|
-
<div class="field-info-container">
|
|
9
|
-
<div class="field-name">{{t "common.email"}}</div>
|
|
10
|
-
<ClickToCopy @value={{@resource.email}} class="field-value">{{n-a @resource.email}}</ClickToCopy>
|
|
11
|
-
</div>
|
|
12
8
|
<div class="field-info-container">
|
|
13
9
|
<div class="field-name">{{t "common.phone"}}</div>
|
|
14
10
|
<ClickToCopy @value={{@resource.phone}} class="field-value">{{n-a @resource.phone}}</ClickToCopy>
|
|
15
11
|
</div>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<div class="grid grid-cols-2 gap-2 text-xs dark:text-gray-100">
|
|
12
|
+
<div class="field-info-container md:col-span-2">
|
|
13
|
+
<div class="field-name">{{t "common.email"}}</div>
|
|
14
|
+
<ClickToCopy @value={{@resource.email}} class="field-value">{{n-a @resource.email}}</ClickToCopy>
|
|
15
|
+
</div>
|
|
21
16
|
<div class="field-info-container">
|
|
22
17
|
<div class="field-name">{{t "common.id"}}</div>
|
|
23
18
|
<ClickToCopy @value={{@resource.public_id}} class="field-value">{{n-a @resource.public_id}}</ClickToCopy>
|
|
@@ -30,28 +25,27 @@
|
|
|
30
25
|
<div class="field-name">{{t "driver.fields.driver-license"}}</div>
|
|
31
26
|
<div class="field-value">{{n-a @resource.drivers_license_number}}</div>
|
|
32
27
|
</div>
|
|
33
|
-
<div class="field-info-container">
|
|
28
|
+
<div class="field-info-container md:col-span-3">
|
|
34
29
|
<div class="field-name">{{t "driver.fields.license-expiry"}}</div>
|
|
35
30
|
<div class="field-value">{{n-a (format-date-fns @resource.license_expiry)}}</div>
|
|
36
31
|
</div>
|
|
37
32
|
<div class="field-info-container">
|
|
38
|
-
<div class="field-name">{{t "resource.
|
|
33
|
+
<div class="field-name">{{t "resource.vehicle"}}</div>
|
|
39
34
|
<div class="field-value">
|
|
40
|
-
<
|
|
35
|
+
<Vehicle::Pill @vehicle={{resource-relation @resource "vehicle"}} @titleFallback={{n-a @resource.vehicle_name (t "driver.fields.no-vehicle-assigned")}} />
|
|
41
36
|
</div>
|
|
42
37
|
</div>
|
|
43
|
-
<div class="field-info-container">
|
|
44
|
-
<div class="field-name">{{t "resource.
|
|
38
|
+
<div class="field-info-container md:col-span-2">
|
|
39
|
+
<div class="field-name">{{t "resource.vendor"}}</div>
|
|
45
40
|
<div class="field-value">
|
|
46
|
-
<
|
|
41
|
+
<Vendor::Pill @vendor={{resource-relation @resource "vendor"}} @titleFallback={{n-a @resource.vendor_name}} />
|
|
47
42
|
</div>
|
|
48
43
|
</div>
|
|
49
|
-
<div class="col-span-3"></div>
|
|
50
44
|
<div class="field-info-container">
|
|
51
45
|
<div class="field-name">{{t "common.city"}}</div>
|
|
52
46
|
<div class="field-value">{{n-a @resource.city}}</div>
|
|
53
47
|
</div>
|
|
54
|
-
<div class="field-info-container">
|
|
48
|
+
<div class="field-info-container md:col-span-2">
|
|
55
49
|
<div class="field-name">{{t "common.country"}}</div>
|
|
56
50
|
<div class="field-value">
|
|
57
51
|
<CountryName @country={{@resource.country}} />
|