@fleetbase/fleetops-engine 0.6.69 → 0.6.71
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 +21 -14
- package/addon/components/admin/telematics-settings.hbs +143 -0
- package/addon/components/admin/telematics-settings.js +145 -0
- package/addon/components/fuel-integration/hub.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +37 -17
- package/addon/components/map/leaflet-live-map.hbs +4 -242
- package/addon/components/map/leaflet-live-map.js +2 -1
- package/addon/components/map/marker-card/driver.hbs +57 -0
- package/addon/components/map/marker-card/driver.js +3 -0
- package/addon/components/map/marker-card/vehicle.hbs +78 -0
- package/addon/components/map/marker-card/vehicle.js +3 -0
- package/addon/components/order/details/activity.hbs +8 -6
- package/addon/components/order/details/activity.js +9 -0
- package/addon/components/order/details.hbs +29 -0
- package/addon/components/order/details.js +26 -0
- package/addon/components/order/form/details.hbs +30 -4
- package/addon/components/order/form/details.js +46 -0
- package/addon/components/order/form/route.hbs +4 -2
- package/addon/components/order/form/route.js +11 -0
- package/addon/components/order/form.hbs +12 -0
- package/addon/components/order/form.js +21 -1
- package/addon/components/order/kanban.js +72 -0
- package/addon/components/order-config-manager/activity-flow.js +45 -10
- package/addon/components/order-config-manager/lifecycle.hbs +250 -0
- package/addon/components/order-config-manager/lifecycle.js +358 -0
- package/addon/components/order-config-manager.js +5 -1
- package/addon/components/telematic/hub.hbs +1 -1
- package/addon/components/widget/live-fleet.hbs +8 -40
- package/addon/controllers/analytics/reports/index.js +15 -1
- package/addon/controllers/connectivity/devices/index/details.js +1 -0
- package/addon/controllers/connectivity/devices/index.js +12 -2
- package/addon/controllers/connectivity/events/index.js +31 -1
- package/addon/controllers/connectivity/fuel-providers/index.js +6 -4
- package/addon/controllers/connectivity/sensors/index/details.js +1 -1
- package/addon/controllers/connectivity/sensors/index.js +41 -1
- package/addon/controllers/connectivity/telematics/index.js +4 -0
- package/addon/controllers/maintenance/equipment/index/details.js +2 -2
- package/addon/controllers/maintenance/equipment/index.js +36 -10
- package/addon/controllers/maintenance/inspection-forms/index.js +32 -12
- package/addon/controllers/maintenance/inspection-submissions/index/details.js +2 -1
- package/addon/controllers/maintenance/inspection-submissions/index.js +56 -10
- package/addon/controllers/maintenance/maintenances/index/details.js +2 -0
- package/addon/controllers/maintenance/maintenances/index.js +24 -1
- package/addon/controllers/maintenance/parts/index/details.js +2 -2
- package/addon/controllers/maintenance/parts/index.js +36 -9
- package/addon/controllers/maintenance/schedules/index.js +48 -6
- package/addon/controllers/maintenance/work-orders/index/details.js +3 -3
- package/addon/controllers/maintenance/work-orders/index.js +47 -9
- package/addon/controllers/management/contacts/customers.js +13 -0
- package/addon/controllers/management/contacts/index.js +47 -1
- package/addon/controllers/management/drivers/index/details.js +1 -0
- package/addon/controllers/management/drivers/index.js +36 -2
- package/addon/controllers/management/fleets/index/details.js +1 -0
- package/addon/controllers/management/fleets/index.js +43 -1
- package/addon/controllers/management/fuel-reports/index.js +26 -2
- package/addon/controllers/management/fuel-transactions/index.js +45 -7
- package/addon/controllers/management/issues/index/details.js +1 -0
- package/addon/controllers/management/issues/index.js +26 -2
- package/addon/controllers/management/places/index.js +40 -1
- package/addon/controllers/management/trailers/index/details.js +1 -0
- package/addon/controllers/management/trailers/index.js +50 -2
- package/addon/controllers/management/vehicles/index/details.js +1 -0
- package/addon/controllers/management/vehicles/index.js +37 -2
- package/addon/controllers/management/vendors/index.js +30 -2
- package/addon/controllers/operations/orders/index/details.js +61 -5
- package/addon/controllers/operations/orders/index.js +66 -4
- package/addon/controllers/operations/service-rates/index.js +18 -1
- package/addon/controllers/settings/index.js +13 -0
- package/addon/controllers/settings/telematics.js +113 -0
- package/addon/extension.js +70 -6
- package/addon/routes/analytics/index.js +14 -1
- package/addon/routes/analytics/reports/index/details.js +1 -1
- package/addon/routes/analytics/reports/index/edit.js +1 -1
- package/addon/routes/analytics/reports/index/new.js +14 -1
- package/addon/routes/analytics/reports.js +14 -1
- package/addon/routes/connectivity/devices/index/new.js +14 -1
- package/addon/routes/connectivity/devices.js +14 -1
- package/addon/routes/connectivity/events.js +14 -1
- package/addon/routes/connectivity/fuel-providers/details/transactions.js +12 -0
- package/addon/routes/connectivity/fuel-providers/details.js +9 -0
- package/addon/routes/connectivity/fuel-providers/edit.js +9 -0
- package/addon/routes/connectivity/fuel-providers/new.js +14 -1
- package/addon/routes/connectivity/fuel-providers.js +14 -1
- package/addon/routes/connectivity/sensors/index/new.js +14 -1
- package/addon/routes/connectivity/sensors.js +14 -1
- package/addon/routes/connectivity/telematics/new.js +14 -1
- package/addon/routes/connectivity/telematics.js +14 -1
- package/addon/routes/connectivity/tracking.js +14 -1
- package/addon/routes/maintenance/equipment/index/details.js +2 -2
- package/addon/routes/maintenance/equipment/index/edit.js +2 -2
- package/addon/routes/maintenance/equipment/index/new.js +14 -1
- package/addon/routes/maintenance/equipment.js +14 -1
- package/addon/routes/maintenance/index.js +14 -1
- package/addon/routes/maintenance/inspection-forms/index/details.js +10 -1
- package/addon/routes/maintenance/inspection-forms/index/edit.js +10 -1
- package/addon/routes/maintenance/inspection-forms/index/new.js +14 -1
- package/addon/routes/maintenance/inspection-forms.js +14 -1
- package/addon/routes/maintenance/inspection-submissions/index/details.js +10 -1
- package/addon/routes/maintenance/inspection-submissions/index/edit.js +10 -1
- package/addon/routes/maintenance/inspection-submissions/index/new.js +14 -1
- package/addon/routes/maintenance/inspection-submissions.js +14 -1
- package/addon/routes/maintenance/maintenances/index/details.js +2 -2
- package/addon/routes/maintenance/maintenances/index/edit.js +2 -2
- package/addon/routes/maintenance/maintenances/index/new.js +14 -1
- package/addon/routes/maintenance/maintenances.js +14 -1
- package/addon/routes/maintenance/parts/index/details.js +2 -2
- package/addon/routes/maintenance/parts/index/edit.js +2 -2
- package/addon/routes/maintenance/parts/index/new.js +14 -1
- package/addon/routes/maintenance/parts.js +14 -1
- package/addon/routes/maintenance/schedules/index/details.js +2 -2
- package/addon/routes/maintenance/schedules/index/edit.js +2 -2
- package/addon/routes/maintenance/schedules/index/new.js +15 -1
- package/addon/routes/maintenance/schedules.js +15 -1
- package/addon/routes/maintenance/work-orders/index/details.js +2 -2
- package/addon/routes/maintenance/work-orders/index/edit.js +2 -2
- package/addon/routes/maintenance/work-orders/index/new.js +14 -1
- package/addon/routes/maintenance/work-orders.js +14 -1
- package/addon/routes/management/fuel-transactions/index/details.js +9 -0
- package/addon/routes/management/fuel-transactions.js +14 -1
- package/addon/routes/management/index.js +13 -0
- package/addon/routes/management/trailers.js +16 -0
- package/addon/routes/management/vendors/integrated/details.js +14 -1
- package/addon/routes/management/vendors/integrated/edit.js +14 -1
- package/addon/routes/management/vendors/integrated/new.js +14 -1
- package/addon/routes/management/vendors/integrated.js +14 -1
- package/addon/routes/operations/orders/index/details.js +1 -0
- 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 +1 -0
- package/addon/services/customer-actions.js +2 -1
- package/addon/services/device-actions.js +1 -0
- package/addon/services/device-event-actions.js +1 -0
- package/addon/services/driver-actions.js +4 -13
- package/addon/services/equipment-actions.js +1 -0
- package/addon/services/fleet-actions.js +1 -0
- package/addon/services/fuel-report-actions.js +1 -0
- package/addon/services/inspection-form-actions.js +1 -0
- package/addon/services/inspection-submission-actions.js +1 -0
- package/addon/services/integrated-vendor-actions.js +1 -0
- package/addon/services/issue-actions.js +1 -0
- package/addon/services/maintenance-actions.js +1 -0
- package/addon/services/maintenance-schedule-actions.js +2 -1
- package/addon/services/order-actions.js +7 -0
- package/addon/services/order-presentation.js +129 -0
- package/addon/services/part-actions.js +1 -0
- package/addon/services/place-actions.js +1 -0
- package/addon/services/sensor-actions.js +1 -0
- package/addon/services/service-area-actions.js +1 -0
- package/addon/services/service-rate-actions.js +1 -0
- package/addon/services/telematic-actions.js +1 -0
- package/addon/services/trailer-actions.js +4 -14
- package/addon/services/vehicle-actions.js +4 -13
- package/addon/services/vendor-actions.js +1 -0
- package/addon/services/work-order-actions.js +1 -0
- package/addon/services/zone-actions.js +1 -0
- package/addon/templates/analytics/reports/index/details.hbs +1 -0
- package/addon/templates/analytics/reports/index.hbs +1 -0
- package/addon/templates/connectivity/devices/index/details.hbs +1 -0
- package/addon/templates/connectivity/devices/index.hbs +1 -0
- package/addon/templates/connectivity/events/index.hbs +1 -0
- package/addon/templates/connectivity/sensors/index/details.hbs +1 -0
- package/addon/templates/connectivity/sensors/index.hbs +1 -0
- package/addon/templates/maintenance/equipment/index/details.hbs +1 -0
- package/addon/templates/maintenance/equipment/index.hbs +2 -0
- package/addon/templates/maintenance/inspection-forms/index/details.hbs +1 -0
- package/addon/templates/maintenance/inspection-forms/index.hbs +1 -0
- package/addon/templates/maintenance/inspection-submissions/index/details.hbs +1 -0
- package/addon/templates/maintenance/inspection-submissions/index.hbs +1 -0
- package/addon/templates/maintenance/maintenances/index/details.hbs +1 -0
- package/addon/templates/maintenance/maintenances/index.hbs +1 -0
- package/addon/templates/maintenance/parts/index/details.hbs +1 -0
- package/addon/templates/maintenance/parts/index.hbs +2 -0
- package/addon/templates/maintenance/schedules/index/details.hbs +1 -0
- package/addon/templates/maintenance/schedules/index.hbs +2 -0
- package/addon/templates/maintenance/work-orders/index/details.hbs +1 -0
- package/addon/templates/maintenance/work-orders/index.hbs +1 -0
- package/addon/templates/management/contacts/customers/details.hbs +1 -0
- package/addon/templates/management/contacts/customers.hbs +1 -0
- package/addon/templates/management/contacts/index/details.hbs +1 -0
- package/addon/templates/management/contacts/index.hbs +1 -0
- package/addon/templates/management/contacts.hbs +1 -0
- package/addon/templates/management/drivers/index/details.hbs +1 -0
- package/addon/templates/management/drivers/index.hbs +2 -0
- package/addon/templates/management/fleets/index/details.hbs +1 -0
- package/addon/templates/management/fleets/index.hbs +1 -0
- package/addon/templates/management/fuel-reports/index/details.hbs +1 -0
- package/addon/templates/management/fuel-reports/index.hbs +1 -0
- package/addon/templates/management/fuel-transactions/index.hbs +1 -0
- package/addon/templates/management/issues/index/details.hbs +1 -0
- package/addon/templates/management/issues/index.hbs +1 -0
- package/addon/templates/management/places/index/details.hbs +1 -0
- package/addon/templates/management/places/index.hbs +1 -0
- package/addon/templates/management/trailers/index/details.hbs +1 -0
- package/addon/templates/management/trailers/index.hbs +2 -0
- package/addon/templates/management/vehicles/index/details.hbs +1 -0
- package/addon/templates/management/vehicles/index.hbs +2 -0
- package/addon/templates/management/vendors/index/details.hbs +1 -0
- package/addon/templates/management/vendors/index.hbs +2 -0
- package/addon/templates/management/vendors/integrated.hbs +1 -0
- package/addon/templates/operations/orders/index/details.hbs +1 -0
- package/addon/templates/operations/orders/index.hbs +1 -0
- package/addon/templates/operations/service-rates/index/details.hbs +1 -0
- package/addon/templates/operations/service-rates/index.hbs +1 -0
- package/addon/templates/settings/telematics.hbs +70 -0
- package/addon/utils/normalize-order-config-flow.js +6 -1
- package/app/components/admin/telematics-settings.js +1 -0
- package/app/components/map/marker-card/driver.js +1 -0
- package/app/components/map/marker-card/vehicle.js +1 -0
- package/app/controllers/settings/telematics.js +1 -0
- package/app/routes/settings/telematics.js +1 -0
- package/app/services/order-presentation.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/server/config/telemetry.php +12 -0
- package/server/migrations/2026_09_23_000001_add_retention_index_to_device_events.php +40 -0
- package/server/migrations/2026_09_23_000002_add_retention_index_to_positions.php +40 -0
- package/server/migrations/2026_09_23_000003_add_retention_index_to_telematic_sync_runs.php +40 -0
- package/server/src/Auth/Schemas/FleetOps.php +104 -5
- package/server/src/Console/Commands/DrainTelematicInbox.php +3 -13
- package/server/src/Console/Commands/ProcessOperationalAlerts.php +3 -3
- package/server/src/Console/Commands/PruneTelematicsData.php +318 -0
- package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +9 -0
- package/server/src/Http/Controllers/FleetOpsController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +14 -0
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +22 -0
- package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +9 -3
- package/server/src/Http/Controllers/Internal/v1/InspectionFormController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +16 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/ManifestController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/MetricsController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +42 -1
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +12 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +56 -4
- package/server/src/Http/Controllers/Internal/v1/PaymentController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/RadarController.php +19 -0
- package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +9 -3
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +465 -14
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/TrailerController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/VendorController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +11 -0
- package/server/src/Jobs/DispatchTelematicsRetentionJobs.php +70 -0
- package/server/src/Jobs/PruneTelematicsDataJob.php +74 -0
- package/server/src/Listeners/NotifyDriverOnShiftChange.php +3 -3
- package/server/src/Mail/CustomerCredentialsMail.php +1 -1
- package/server/src/Models/Order.php +35 -3
- package/server/src/Models/OrderConfig.php +129 -0
- package/server/src/Models/TrackingNumber.php +8 -4
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +9 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +3 -0
- package/server/src/Support/Analytics/LiveFleet.php +18 -2
- package/server/src/Support/Authorization.php +103 -0
- package/server/src/Support/Database/TableIndexes.php +66 -0
- package/server/src/Support/OrderTracker.php +2 -2
- package/server/src/Support/Reporting/FleetOpsReportSchema.php +476 -116
- package/server/src/Support/Telematics/Retention/RetentionPolicy.php +300 -0
- package/server/src/Support/Telematics/Retention/TelemetryActivity.php +40 -0
- package/server/src/Support/Telematics/TelematicService.php +12 -2
- package/server/src/Support/Telematics/Telemetry/Ingestor.php +4 -2
- package/server/src/Tracking/TrackingIntelligenceService.php +1 -1
- package/server/src/Tracking/TrackingOptions.php +2 -2
- package/server/src/Traits/AuthorizesMethods.php +37 -0
- package/server/src/routes.php +9 -1
- package/server/tests/AnalyticsRoutesTest.php +29 -2
- package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +16 -1
- package/server/tests/Feature/Http/Api/TelematicsRetentionIndexMigrationTest.php +115 -0
- package/server/tests/Feature/Http/Internal/OrderControllerActivityFlowsTest.php +72 -0
- package/server/tests/Feature/Http/Internal/OrderControllerCreateRecordTest.php +26 -0
- package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +71 -0
- package/server/tests/Feature/Http/Internal/TelematicsStorageDeadlinesTest.php +119 -0
- package/server/tests/InternalImportExportControllerContractsTest.php +5 -8
- package/server/tests/NavigatorControllerContractsTest.php +36 -4
- package/server/tests/NotificationAndMailContractsTest.php +31 -0
- package/server/tests/ObserverContractsTest.php +1 -1
- package/server/tests/OrchestratorConsumableApiTest.php +42 -0
- package/server/tests/OrderConfigLifecycleTest.php +120 -0
- package/server/tests/ProviderContractsTest.php +10 -0
- package/server/tests/ReportSchemaContractsTest.php +120 -6
- package/server/tests/SettingControllerContractsTest.php +429 -18
- package/server/tests/SmallControllerContractsTest.php +8 -5
- package/server/tests/Support/PruneTelematicsDataProbe.php +55 -0
- package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +2 -2
- package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +353 -0
- package/server/tests/Unit/Events/OrderDispatchFailedTest.php +2 -1
- package/server/tests/Unit/Jobs/DispatchTelematicsRetentionJobsTest.php +114 -0
- package/server/tests/Unit/Jobs/PruneTelematicsDataJobTest.php +60 -0
- package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +20 -0
- package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +6 -0
- package/server/tests/Unit/Support/AuthorizationTest.php +142 -0
- package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +12 -1
- package/server/tests/Unit/Support/MoreQuerySeamsTest.php +11 -2
- package/server/tests/Unit/Support/Telematics/Retention/RetentionPolicyTest.php +190 -0
- package/server/tests/Unit/Support/Telematics/Retention/TelemetryActivityTest.php +68 -0
- package/server/tests/Unit/Tracking/TrackingOptionsCompanySettingsTest.php +109 -0
- package/translations/en-us.yaml +108 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
<div class="px-6 py-2 space-y-4">
|
|
2
|
+
<ContentPanel @title={{t "order-config-manager.lifecycle.title"}} @open={{true}} @wrapperClass="bordered-classic">
|
|
3
|
+
<p class="text-sm text-gray-600 dark:text-gray-300 mb-4">{{t "order-config-manager.lifecycle.description"}}</p>
|
|
4
|
+
|
|
5
|
+
<div class="mb-4">
|
|
6
|
+
<Toggle
|
|
7
|
+
@label={{t "order-config-manager.lifecycle.enable"}}
|
|
8
|
+
@helpText={{t "order-config-manager.lifecycle.enable-help"}}
|
|
9
|
+
@isToggled={{this.enabled}}
|
|
10
|
+
@disabled={{or (not this.canEdit) this.save.isRunning}}
|
|
11
|
+
@onToggle={{this.toggleEnabled}}
|
|
12
|
+
/>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
{{#if this.enabled}}
|
|
16
|
+
<div class="mb-6">
|
|
17
|
+
<div class="flex items-center justify-between mb-1">
|
|
18
|
+
<div class="text-sm font-semibold dark:text-gray-100">{{t "order-config-manager.lifecycle.activities"}}</div>
|
|
19
|
+
{{#if (and this.canEdit this.hasActivities)}}
|
|
20
|
+
<button type="button" class="text-xs text-blue-600 dark:text-blue-400 hover:underline" {{on "click" this.startBlankFlow}}>{{t "order-config-manager.lifecycle.blank-flow"}}</button>
|
|
21
|
+
{{/if}}
|
|
22
|
+
</div>
|
|
23
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">{{t "order-config-manager.lifecycle.activities-help"}}</p>
|
|
24
|
+
|
|
25
|
+
{{#if this.hasActivities}}
|
|
26
|
+
<div class="space-y-2">
|
|
27
|
+
{{#each this.activityRows as |activity|}}
|
|
28
|
+
<div class="rounded-md border border-gray-200 dark:border-gray-700 p-3 grid grid-cols-1 lg:grid-cols-12 gap-2 items-end" data-activity-code={{activity.code}}>
|
|
29
|
+
<InputGroup @name={{t "order-config-manager.lifecycle.activity-label"}} @wrapperClass="mb-0 lg:col-span-3">
|
|
30
|
+
<Input class="form-input w-full" @value={{activity.status}} disabled={{not this.canEdit}} {{on "input" (fn this.setActivityField activity.code "status")}} />
|
|
31
|
+
</InputGroup>
|
|
32
|
+
<InputGroup @name={{t "order-config-manager.lifecycle.activity-code"}} @wrapperClass="mb-0 lg:col-span-2">
|
|
33
|
+
<div class="form-input bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 truncate" title={{activity.code}}>{{activity.code}}{{#if activity.isInitial}} <span class="text-xs text-green-600 dark:text-green-400">({{t "order-config-manager.lifecycle.initial-short"}})</span>{{/if}}</div>
|
|
34
|
+
</InputGroup>
|
|
35
|
+
<InputGroup @name={{t "order-config-manager.lifecycle.activity-details"}} @wrapperClass="mb-0 lg:col-span-3">
|
|
36
|
+
<Input class="form-input w-full" @value={{activity.details}} disabled={{not this.canEdit}} {{on "input" (fn this.setActivityField activity.code "details")}} />
|
|
37
|
+
</InputGroup>
|
|
38
|
+
<InputGroup @name={{t "order-config-manager.lifecycle.activity-next"}} @wrapperClass="mb-0 lg:col-span-3">
|
|
39
|
+
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
40
|
+
<PowerSelectMultiple
|
|
41
|
+
@options={{this.activities}}
|
|
42
|
+
@selected={{activity.next}}
|
|
43
|
+
@onChange={{fn this.setNextActivities activity.code}}
|
|
44
|
+
@searchEnabled={{true}}
|
|
45
|
+
@searchField="label"
|
|
46
|
+
@placeholder={{t "order-config-manager.lifecycle.select-activities"}}
|
|
47
|
+
@triggerClass="form-select form-input"
|
|
48
|
+
@disabled={{not this.canEdit}}
|
|
49
|
+
as |next|
|
|
50
|
+
>
|
|
51
|
+
{{next.label}}
|
|
52
|
+
</PowerSelectMultiple>
|
|
53
|
+
</div>
|
|
54
|
+
</InputGroup>
|
|
55
|
+
<div class="flex items-center justify-end space-x-2 lg:col-span-1">
|
|
56
|
+
<input type="color" value={{activity.color}} title={{t "order-config-manager.lifecycle.activity-color"}} class="h-9 w-9 rounded border border-gray-300 dark:border-gray-700 bg-transparent" disabled={{not this.canEdit}} {{on "input" (fn this.setActivityField activity.code "color")}} />
|
|
57
|
+
{{#if this.canEdit}}
|
|
58
|
+
<Button @type="danger" @size="sm" @icon="trash" @helpText={{t "order-config-manager.lifecycle.remove-activity"}} @onClick={{fn this.removeActivity activity.code}} />
|
|
59
|
+
{{/if}}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
{{/each}}
|
|
63
|
+
</div>
|
|
64
|
+
{{else}}
|
|
65
|
+
<div class="rounded-md border border-dashed border-gray-300 dark:border-gray-700 px-3 py-4 text-sm text-gray-500 dark:text-gray-400 text-center">{{t "order-config-manager.lifecycle.no-activities"}}</div>
|
|
66
|
+
{{/if}}
|
|
67
|
+
|
|
68
|
+
{{#if this.canEdit}}
|
|
69
|
+
<div class="mt-3 rounded-md border border-blue-200 dark:border-blue-800 p-3">
|
|
70
|
+
<div class="text-sm font-semibold mb-2">{{t "order-config-manager.lifecycle.add-activity"}}</div>
|
|
71
|
+
<div class="grid grid-cols-1 lg:grid-cols-12 gap-2 items-end">
|
|
72
|
+
<InputGroup @name={{t "order-config-manager.lifecycle.activity-label"}} @required={{true}} @wrapperClass="mb-0 lg:col-span-3">
|
|
73
|
+
<Input class="form-input w-full" placeholder={{t "order-config-manager.lifecycle.activity-label-placeholder"}} @value={{this.newActivity.status}} {{on "input" (fn this.setNewActivityField "status")}} />
|
|
74
|
+
</InputGroup>
|
|
75
|
+
<InputGroup @name={{t "order-config-manager.lifecycle.activity-code"}} @helpText={{t "order-config-manager.lifecycle.activity-code-help"}} @wrapperClass="mb-0 lg:col-span-2">
|
|
76
|
+
<Input class="form-input w-full" placeholder={{or this.newActivityCode (t "order-config-manager.lifecycle.activity-code-placeholder")}} @value={{this.newActivity.code}} {{on "input" (fn this.setNewActivityField "code")}} />
|
|
77
|
+
</InputGroup>
|
|
78
|
+
<InputGroup @name={{t "order-config-manager.lifecycle.activity-details"}} @wrapperClass="mb-0 lg:col-span-4">
|
|
79
|
+
<Input class="form-input w-full" placeholder={{t "order-config-manager.lifecycle.activity-details-placeholder"}} @value={{this.newActivity.details}} {{on "input" (fn this.setNewActivityField "details")}} />
|
|
80
|
+
</InputGroup>
|
|
81
|
+
<div class="flex items-center justify-end space-x-2 lg:col-span-3">
|
|
82
|
+
<input type="color" value={{this.newActivity.color}} title={{t "order-config-manager.lifecycle.activity-color"}} class="h-9 w-9 rounded border border-gray-300 dark:border-gray-700 bg-transparent" {{on "input" (fn this.setNewActivityField "color")}} />
|
|
83
|
+
<Button @type="primary" @icon="plus" @text={{t "order-config-manager.lifecycle.add-activity"}} @disabled={{not this.canAddActivity}} @onClick={{this.addActivity}} />
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
{{#if this.newActivityCodeTaken}}
|
|
87
|
+
<div class="mt-1 text-xs text-red-600 dark:text-red-400">{{t "order-config-manager.lifecycle.problem-duplicate-code" code=this.newActivityCode}}</div>
|
|
88
|
+
{{/if}}
|
|
89
|
+
</div>
|
|
90
|
+
{{/if}}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-x-6 gap-y-2">
|
|
94
|
+
<InputGroup @name={{t "order-config-manager.lifecycle.initial"}} @required={{true}} @helpText={{t "order-config-manager.lifecycle.initial-help"}}>
|
|
95
|
+
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
96
|
+
<PowerSelect
|
|
97
|
+
@options={{this.activities}}
|
|
98
|
+
@selected={{this.selectedInitial}}
|
|
99
|
+
@onChange={{fn this.setActivity "initial"}}
|
|
100
|
+
@searchEnabled={{true}}
|
|
101
|
+
@searchField="label"
|
|
102
|
+
@placeholder={{t "order-config-manager.lifecycle.select-activity"}}
|
|
103
|
+
@triggerClass="form-select form-input"
|
|
104
|
+
@disabled={{not this.canEdit}}
|
|
105
|
+
as |activity|
|
|
106
|
+
>
|
|
107
|
+
<span>{{activity.label}}</span>
|
|
108
|
+
<span class="ml-2 text-xs text-gray-500">{{activity.code}}</span>
|
|
109
|
+
</PowerSelect>
|
|
110
|
+
</div>
|
|
111
|
+
</InputGroup>
|
|
112
|
+
|
|
113
|
+
<InputGroup @name={{t "order-config-manager.lifecycle.terminal"}} @helpText={{t "order-config-manager.lifecycle.terminal-help"}}>
|
|
114
|
+
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
115
|
+
<PowerSelectMultiple
|
|
116
|
+
@options={{this.activities}}
|
|
117
|
+
@selected={{this.selectedTerminal}}
|
|
118
|
+
@onChange={{this.setTerminal}}
|
|
119
|
+
@searchEnabled={{true}}
|
|
120
|
+
@searchField="label"
|
|
121
|
+
@placeholder={{t "order-config-manager.lifecycle.select-activities"}}
|
|
122
|
+
@triggerClass="form-select form-input"
|
|
123
|
+
@disabled={{not this.canEdit}}
|
|
124
|
+
as |activity|
|
|
125
|
+
>
|
|
126
|
+
{{activity.label}}
|
|
127
|
+
</PowerSelectMultiple>
|
|
128
|
+
</div>
|
|
129
|
+
</InputGroup>
|
|
130
|
+
|
|
131
|
+
<InputGroup @name={{t "order-config-manager.lifecycle.completed"}} @helpText={{t "order-config-manager.lifecycle.completed-help"}}>
|
|
132
|
+
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
133
|
+
<PowerSelect
|
|
134
|
+
@options={{this.activities}}
|
|
135
|
+
@selected={{this.selectedCompleted}}
|
|
136
|
+
@onChange={{fn this.setActivity "completed"}}
|
|
137
|
+
@searchEnabled={{true}}
|
|
138
|
+
@searchField="label"
|
|
139
|
+
@allowClear={{true}}
|
|
140
|
+
@placeholder={{t "order-config-manager.lifecycle.select-activity"}}
|
|
141
|
+
@triggerClass="form-select form-input"
|
|
142
|
+
@disabled={{not this.canEdit}}
|
|
143
|
+
as |activity|
|
|
144
|
+
>
|
|
145
|
+
<span>{{activity.label}}</span>
|
|
146
|
+
<span class="ml-2 text-xs text-gray-500">{{activity.code}}</span>
|
|
147
|
+
</PowerSelect>
|
|
148
|
+
</div>
|
|
149
|
+
</InputGroup>
|
|
150
|
+
|
|
151
|
+
<InputGroup @name={{t "order-config-manager.lifecycle.canceled"}} @helpText={{t "order-config-manager.lifecycle.canceled-help"}}>
|
|
152
|
+
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
|
|
153
|
+
<PowerSelect
|
|
154
|
+
@options={{this.activities}}
|
|
155
|
+
@selected={{this.selectedCanceled}}
|
|
156
|
+
@onChange={{fn this.setActivity "canceled"}}
|
|
157
|
+
@searchEnabled={{true}}
|
|
158
|
+
@searchField="label"
|
|
159
|
+
@allowClear={{true}}
|
|
160
|
+
@placeholder={{t "order-config-manager.lifecycle.select-activity"}}
|
|
161
|
+
@triggerClass="form-select form-input"
|
|
162
|
+
@disabled={{not this.canEdit}}
|
|
163
|
+
as |activity|
|
|
164
|
+
>
|
|
165
|
+
<span>{{activity.label}}</span>
|
|
166
|
+
<span class="ml-2 text-xs text-gray-500">{{activity.code}}</span>
|
|
167
|
+
</PowerSelect>
|
|
168
|
+
</div>
|
|
169
|
+
</InputGroup>
|
|
170
|
+
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<div class="flex flex-col space-y-2 pt-2">
|
|
174
|
+
<Toggle
|
|
175
|
+
@label={{t "order-config-manager.lifecycle.dispatch"}}
|
|
176
|
+
@helpText={{t "order-config-manager.lifecycle.dispatch-help"}}
|
|
177
|
+
@isToggled={{this.lifecycle.dispatch}}
|
|
178
|
+
@disabled={{not this.canEdit}}
|
|
179
|
+
@onToggle={{fn this.setFlag "dispatch"}}
|
|
180
|
+
/>
|
|
181
|
+
<Toggle
|
|
182
|
+
@label={{t "order-config-manager.lifecycle.strict-transitions"}}
|
|
183
|
+
@helpText={{t "order-config-manager.lifecycle.strict-transitions-help"}}
|
|
184
|
+
@isToggled={{this.lifecycle.strict_transitions}}
|
|
185
|
+
@disabled={{not this.canEdit}}
|
|
186
|
+
@onToggle={{fn this.setFlag "strict_transitions"}}
|
|
187
|
+
/>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
{{#if this.problems.length}}
|
|
191
|
+
<div class="mt-2 rounded-md border border-red-300 dark:border-red-700 px-3 py-2 text-sm">
|
|
192
|
+
<div class="font-semibold text-red-700 dark:text-red-400 mb-1">{{t "order-config-manager.lifecycle.problems-title"}}</div>
|
|
193
|
+
<ul class="list-disc pl-5 text-red-700 dark:text-red-400">
|
|
194
|
+
{{#each this.problems as |problem|}}<li>{{problem}}</li>{{/each}}
|
|
195
|
+
</ul>
|
|
196
|
+
</div>
|
|
197
|
+
{{/if}}
|
|
198
|
+
{{else}}
|
|
199
|
+
<p class="text-sm text-gray-600 dark:text-gray-300">{{t "order-config-manager.lifecycle.default-lifecycle"}}</p>
|
|
200
|
+
{{/if}}
|
|
201
|
+
|
|
202
|
+
{{#if this.canEdit}}
|
|
203
|
+
<div class="flex justify-end pt-4">
|
|
204
|
+
<Button
|
|
205
|
+
@type="primary"
|
|
206
|
+
@icon="save"
|
|
207
|
+
@text={{t "common.save-changes"}}
|
|
208
|
+
@onClick={{perform this.save}}
|
|
209
|
+
@isLoading={{this.save.isRunning}}
|
|
210
|
+
@disabled={{not this.canSave}}
|
|
211
|
+
@permission="fleet-ops update order-config"
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
{{/if}}
|
|
215
|
+
</ContentPanel>
|
|
216
|
+
|
|
217
|
+
{{#if this.saved}}
|
|
218
|
+
<ContentPanel @title={{t "order-config-manager.lifecycle.summary"}} @open={{true}} @wrapperClass="bordered-classic">
|
|
219
|
+
<dl class="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-1 text-sm">
|
|
220
|
+
<div class="flex"><dt class="w-40 shrink-0 text-gray-500">{{t "order-config-manager.lifecycle.summary-initial"}}</dt><dd>{{or
|
|
221
|
+
(get (this.option this.saved.initial) "label")
|
|
222
|
+
(t "order-config-manager.lifecycle.none")
|
|
223
|
+
}}</dd></div>
|
|
224
|
+
<div class="flex"><dt class="w-40 shrink-0 text-gray-500">{{t "order-config-manager.lifecycle.summary-completed"}}</dt><dd>{{or
|
|
225
|
+
(get (this.option this.saved.completed) "label")
|
|
226
|
+
(t "order-config-manager.lifecycle.none")
|
|
227
|
+
}}</dd></div>
|
|
228
|
+
<div class="flex"><dt class="w-40 shrink-0 text-gray-500">{{t "order-config-manager.lifecycle.summary-canceled"}}</dt><dd>{{or
|
|
229
|
+
(get (this.option this.saved.canceled) "label")
|
|
230
|
+
(t "order-config-manager.lifecycle.none")
|
|
231
|
+
}}</dd></div>
|
|
232
|
+
<div class="flex"><dt class="w-40 shrink-0 text-gray-500">{{t "order-config-manager.lifecycle.summary-terminal"}}</dt><dd>{{#if this.saved.terminal.length}}{{#each
|
|
233
|
+
this.saved.terminal
|
|
234
|
+
as |code index|
|
|
235
|
+
}}{{if index ", "}}{{get (this.option code) "label"}}{{/each}}{{else}}{{t "order-config-manager.lifecycle.none"}}{{/if}}</dd></div>
|
|
236
|
+
<div class="flex"><dt class="w-40 shrink-0 text-gray-500">{{t "order-config-manager.lifecycle.summary-dispatch"}}</dt><dd>{{if
|
|
237
|
+
this.saved.dispatch
|
|
238
|
+
(t "order-config-manager.lifecycle.yes")
|
|
239
|
+
(t "order-config-manager.lifecycle.no")
|
|
240
|
+
}}</dd></div>
|
|
241
|
+
<div class="flex"><dt class="w-40 shrink-0 text-gray-500">{{t "order-config-manager.lifecycle.summary-strict"}}</dt><dd>{{if
|
|
242
|
+
this.saved.strict_transitions
|
|
243
|
+
(t "order-config-manager.lifecycle.yes")
|
|
244
|
+
(t "order-config-manager.lifecycle.no")
|
|
245
|
+
}}</dd></div>
|
|
246
|
+
</dl>
|
|
247
|
+
</ContentPanel>
|
|
248
|
+
{{/if}}
|
|
249
|
+
|
|
250
|
+
</div>
|
|
@@ -0,0 +1,358 @@
|
|
|
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 { isArray } from '@ember/array';
|
|
6
|
+
import { task } from 'ember-concurrency';
|
|
7
|
+
import { underscore } from '@ember/string';
|
|
8
|
+
import generateUUID from '@fleetbase/ember-core/utils/generate-uuid';
|
|
9
|
+
import normalizeOrderConfigFlow, { getOrderConfigFlowRootCode } from '../../utils/normalize-order-config-flow';
|
|
10
|
+
|
|
11
|
+
/** Keys of `meta.lifecycle` understood by the server (see OrderConfig::lifecycle()). */
|
|
12
|
+
const DEFAULTS = { initial: null, completed: null, canceled: null, terminal: [], dispatch: true, strict_transitions: false };
|
|
13
|
+
|
|
14
|
+
const NEW_ACTIVITY = { status: '', code: '', details: '', color: '#1f2937' };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Manages an order config's configured lifecycle (`meta.lifecycle`): the activity new orders start
|
|
18
|
+
* at, the activities that complete, cancel and end an order, whether orders may be dispatched and
|
|
19
|
+
* whether transitions must follow the flow. Without a configured lifecycle the default dispatch
|
|
20
|
+
* lifecycle applies.
|
|
21
|
+
*
|
|
22
|
+
* The activities themselves (the config's `flow`) can be defined here too, so a lifecycle can be
|
|
23
|
+
* built from scratch without the graph editor: add or remove activities, set their labels and the
|
|
24
|
+
* activities each one can move to. Flow and lifecycle are saved together.
|
|
25
|
+
*
|
|
26
|
+
* @extends Component
|
|
27
|
+
*/
|
|
28
|
+
export default class OrderConfigManagerLifecycleComponent extends Component {
|
|
29
|
+
@service modalsManager;
|
|
30
|
+
@service notifications;
|
|
31
|
+
@service abilities;
|
|
32
|
+
@service intl;
|
|
33
|
+
|
|
34
|
+
/** The config being managed. */
|
|
35
|
+
@tracked config;
|
|
36
|
+
|
|
37
|
+
/** Working copy of `meta.lifecycle`, or null when the config has none. */
|
|
38
|
+
@tracked lifecycle = null;
|
|
39
|
+
|
|
40
|
+
/** Last saved lifecycle, to tell whether there is anything to save. */
|
|
41
|
+
@tracked saved = null;
|
|
42
|
+
|
|
43
|
+
/** Working copy of the config's flow, keyed by activity code, with next activities as codes. */
|
|
44
|
+
@tracked flow = {};
|
|
45
|
+
|
|
46
|
+
/** Flow as last saved, serialized, to tell whether there is anything to save. */
|
|
47
|
+
@tracked savedFlow = '{}';
|
|
48
|
+
|
|
49
|
+
/** The activity being added. */
|
|
50
|
+
@tracked newActivity = { ...NEW_ACTIVITY };
|
|
51
|
+
|
|
52
|
+
constructor(owner, { config, configManagerContext }) {
|
|
53
|
+
super(...arguments);
|
|
54
|
+
this.changeConfig(config);
|
|
55
|
+
|
|
56
|
+
configManagerContext.on('onConfigChanged', (newConfig) => {
|
|
57
|
+
this.changeConfig(newConfig);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get canEdit() {
|
|
62
|
+
return this.abilities.can('fleet-ops update order-config') && !this.config?.core_service;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get enabled() {
|
|
66
|
+
return this.lifecycle !== null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Activities of the flow as select options. */
|
|
70
|
+
get activities() {
|
|
71
|
+
return Object.values(this.flow).map((activity) => ({ code: activity.code, label: activity.status || activity.code }));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Activities with their next activities resolved, for the editor. */
|
|
75
|
+
get activityRows() {
|
|
76
|
+
return Object.values(this.flow).map((activity) => ({
|
|
77
|
+
...activity,
|
|
78
|
+
next: (activity.activities ?? []).map((code) => this.option(code)).filter(Boolean),
|
|
79
|
+
isInitial: this.lifecycle?.initial === activity.code,
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get hasActivities() {
|
|
84
|
+
return Object.keys(this.flow).length > 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get newActivityCode() {
|
|
88
|
+
return underscore((this.newActivity.code || this.newActivity.status || '').trim());
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get canAddActivity() {
|
|
92
|
+
const code = this.newActivityCode;
|
|
93
|
+
return this.canEdit && this.newActivity.status.trim() !== '' && code !== '' && !this.flow[code];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get newActivityCodeTaken() {
|
|
97
|
+
const code = this.newActivityCode;
|
|
98
|
+
return code !== '' && Boolean(this.flow[code]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get selectedInitial() {
|
|
102
|
+
return this.option(this.lifecycle?.initial);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get selectedCompleted() {
|
|
106
|
+
return this.option(this.lifecycle?.completed);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get selectedCanceled() {
|
|
110
|
+
return this.option(this.lifecycle?.canceled);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get selectedTerminal() {
|
|
114
|
+
return (this.lifecycle?.terminal ?? []).map((code) => this.option(code)).filter(Boolean);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get isDirty() {
|
|
118
|
+
return JSON.stringify(this.lifecycle) !== JSON.stringify(this.saved) || this.serializeFlow() !== this.savedFlow;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Problems that make the lifecycle unusable server-side. */
|
|
122
|
+
get problems() {
|
|
123
|
+
if (!this.lifecycle) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const problems = [];
|
|
128
|
+
const codes = this.activities.map((activity) => activity.code);
|
|
129
|
+
if (codes.length === 0) {
|
|
130
|
+
problems.push(this.intl.t('order-config-manager.lifecycle.problem-no-activities'));
|
|
131
|
+
}
|
|
132
|
+
if (!this.lifecycle.initial) {
|
|
133
|
+
problems.push(this.intl.t('order-config-manager.lifecycle.problem-initial-required'));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const referenced = [this.lifecycle.initial, this.lifecycle.completed, this.lifecycle.canceled, ...(this.lifecycle.terminal ?? [])].filter(Boolean);
|
|
137
|
+
for (const code of new Set(referenced)) {
|
|
138
|
+
if (!codes.includes(code)) {
|
|
139
|
+
problems.push(this.intl.t('order-config-manager.lifecycle.problem-missing-activity', { code }));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return problems;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
get canSave() {
|
|
147
|
+
return this.canEdit && this.isDirty && this.problems.length === 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Arrow function so templates can call it as a helper: (this.option code). */
|
|
151
|
+
option = (code) => {
|
|
152
|
+
return code ? (this.activities.find((activity) => activity.code === code) ?? { code, label: code }) : null;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
changeConfig(config) {
|
|
156
|
+
this.config = config;
|
|
157
|
+
this.saved = this.normalize(config?.meta?.lifecycle);
|
|
158
|
+
this.lifecycle = this.normalize(config?.meta?.lifecycle);
|
|
159
|
+
this.flow = this.readFlow(config?.flow);
|
|
160
|
+
this.savedFlow = this.serializeFlow();
|
|
161
|
+
this.newActivity = { ...NEW_ACTIVITY };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The stored flow as plain activity objects with next activities as codes. */
|
|
165
|
+
readFlow(flow) {
|
|
166
|
+
const normalized = normalizeOrderConfigFlow(flow ?? {});
|
|
167
|
+
const result = {};
|
|
168
|
+
Object.values(normalized).forEach((activity, index) => {
|
|
169
|
+
if (!activity || typeof activity !== 'object') {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const code = activity.code ?? activity.key;
|
|
173
|
+
if (!code) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
result[code] = {
|
|
177
|
+
...activity,
|
|
178
|
+
code,
|
|
179
|
+
key: activity.key ?? code,
|
|
180
|
+
sequence: activity.sequence ?? index,
|
|
181
|
+
activities: (isArray(activity.activities) ? activity.activities : []).map((next) => (typeof next === 'string' ? next : (next?.code ?? next?.key))).filter(Boolean),
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
serializeFlow() {
|
|
188
|
+
const serialized = {};
|
|
189
|
+
Object.values(this.flow).forEach((activity) => {
|
|
190
|
+
// Graph-only state never belongs in the stored flow.
|
|
191
|
+
// eslint-disable-next-line no-unused-vars
|
|
192
|
+
const { node, id, parentId, _internalModel, ...stored } = activity;
|
|
193
|
+
serialized[activity.code] = { ...stored, activities: [...(activity.activities ?? [])] };
|
|
194
|
+
});
|
|
195
|
+
return JSON.stringify(serialized);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@action setNewActivityField(field, event) {
|
|
199
|
+
this.newActivity = { ...this.newActivity, [field]: event.target.value };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@action addActivity() {
|
|
203
|
+
if (!this.canAddActivity) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const code = this.newActivityCode;
|
|
208
|
+
const activity = {
|
|
209
|
+
key: code,
|
|
210
|
+
code,
|
|
211
|
+
status: this.newActivity.status.trim(),
|
|
212
|
+
details: this.newActivity.details.trim(),
|
|
213
|
+
color: this.newActivity.color || '#1f2937',
|
|
214
|
+
sequence: Object.keys(this.flow).length,
|
|
215
|
+
activities: [],
|
|
216
|
+
events: [],
|
|
217
|
+
logic: [],
|
|
218
|
+
actions: [],
|
|
219
|
+
entities: [],
|
|
220
|
+
complete: false,
|
|
221
|
+
require_pod: false,
|
|
222
|
+
pod_method: 'scan',
|
|
223
|
+
options: {},
|
|
224
|
+
internalId: generateUUID(),
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
this.flow = { ...this.flow, [code]: activity };
|
|
228
|
+
if (this.lifecycle && !this.lifecycle.initial) {
|
|
229
|
+
this.lifecycle = { ...this.lifecycle, initial: code };
|
|
230
|
+
}
|
|
231
|
+
this.newActivity = { ...NEW_ACTIVITY };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@action removeActivity(code) {
|
|
235
|
+
const flow = { ...this.flow };
|
|
236
|
+
delete flow[code];
|
|
237
|
+
Object.keys(flow).forEach((key) => {
|
|
238
|
+
flow[key] = { ...flow[key], activities: flow[key].activities.filter((next) => next !== code) };
|
|
239
|
+
});
|
|
240
|
+
this.flow = flow;
|
|
241
|
+
|
|
242
|
+
if (this.lifecycle) {
|
|
243
|
+
const without = (value) => (value === code ? null : value);
|
|
244
|
+
this.lifecycle = {
|
|
245
|
+
...this.lifecycle,
|
|
246
|
+
initial: without(this.lifecycle.initial),
|
|
247
|
+
completed: without(this.lifecycle.completed),
|
|
248
|
+
canceled: without(this.lifecycle.canceled),
|
|
249
|
+
terminal: this.lifecycle.terminal.filter((item) => item !== code),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@action setActivityField(code, field, event) {
|
|
255
|
+
this.flow = { ...this.flow, [code]: { ...this.flow[code], [field]: event.target.value } };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@action setNextActivities(code, selected) {
|
|
259
|
+
this.flow = { ...this.flow, [code]: { ...this.flow[code], activities: (selected ?? []).map((activity) => activity.code) } };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Drops the default dispatch activities so a lifecycle can be designed from nothing. */
|
|
263
|
+
@action startBlankFlow() {
|
|
264
|
+
this.modalsManager.confirm({
|
|
265
|
+
title: this.intl.t('order-config-manager.lifecycle.blank-flow-title'),
|
|
266
|
+
body: this.intl.t('order-config-manager.lifecycle.blank-flow-body'),
|
|
267
|
+
acceptButtonText: this.intl.t('order-config-manager.lifecycle.blank-flow-confirm'),
|
|
268
|
+
confirm: (modal) => {
|
|
269
|
+
this.flow = {};
|
|
270
|
+
if (this.lifecycle) {
|
|
271
|
+
this.lifecycle = { ...this.lifecycle, initial: null, completed: null, canceled: null, terminal: [] };
|
|
272
|
+
}
|
|
273
|
+
modal.done();
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
normalize(lifecycle) {
|
|
279
|
+
if (!lifecycle || typeof lifecycle !== 'object' || Object.keys(lifecycle).length === 0) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
...DEFAULTS,
|
|
285
|
+
...lifecycle,
|
|
286
|
+
terminal: isArray(lifecycle.terminal) ? [...lifecycle.terminal] : [],
|
|
287
|
+
dispatch: !['false', '0', 0, false].includes(lifecycle.dispatch),
|
|
288
|
+
strict_transitions: Boolean(lifecycle.strict_transitions),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
@action toggleEnabled(enabled) {
|
|
293
|
+
if (enabled) {
|
|
294
|
+
const flow = normalizeOrderConfigFlow(this.config?.flow ?? {});
|
|
295
|
+
this.lifecycle = this.saved ? { ...this.saved, terminal: [...this.saved.terminal] } : { ...DEFAULTS, terminal: [], initial: getOrderConfigFlowRootCode(flow) ?? null };
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (this.saved) {
|
|
300
|
+
this.modalsManager.confirm({
|
|
301
|
+
title: this.intl.t('order-config-manager.lifecycle.remove-title'),
|
|
302
|
+
body: this.intl.t('order-config-manager.lifecycle.remove-body'),
|
|
303
|
+
acceptButtonText: this.intl.t('order-config-manager.lifecycle.remove-confirm'),
|
|
304
|
+
confirm: async (modal) => {
|
|
305
|
+
modal.startLoading();
|
|
306
|
+
this.lifecycle = null;
|
|
307
|
+
await this.save.perform();
|
|
308
|
+
modal.done();
|
|
309
|
+
},
|
|
310
|
+
decline: (modal) => {
|
|
311
|
+
modal.done();
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
this.lifecycle = null;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
@action setActivity(key, activity) {
|
|
321
|
+
this.lifecycle = { ...this.lifecycle, [key]: activity?.code ?? null };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
@action setTerminal(activities) {
|
|
325
|
+
this.lifecycle = { ...this.lifecycle, terminal: (activities ?? []).map((activity) => activity.code) };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
@action setFlag(key, value) {
|
|
329
|
+
this.lifecycle = { ...this.lifecycle, [key]: Boolean(value) };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@task *save() {
|
|
333
|
+
const meta = { ...(this.config.meta ?? {}) };
|
|
334
|
+
if (this.lifecycle) {
|
|
335
|
+
meta.lifecycle = { ...this.lifecycle };
|
|
336
|
+
} else {
|
|
337
|
+
delete meta.lifecycle;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
this.config.set('meta', meta);
|
|
342
|
+
this.config.set('flow', JSON.parse(this.serializeFlow()));
|
|
343
|
+
yield this.config.save();
|
|
344
|
+
this.saved = this.normalize(this.lifecycle);
|
|
345
|
+
this.lifecycle = this.normalize(this.lifecycle);
|
|
346
|
+
this.savedFlow = this.serializeFlow();
|
|
347
|
+
this.notifications.success(this.intl.t('order-config-manager.lifecycle.saved', { orderConfigName: this.config.name }));
|
|
348
|
+
if (typeof this.args.onConfigUpdated === 'function') {
|
|
349
|
+
this.args.onConfigUpdated(this.config);
|
|
350
|
+
}
|
|
351
|
+
} catch (error) {
|
|
352
|
+
this.config.rollbackAttributes();
|
|
353
|
+
this.lifecycle = this.normalize(this.config.meta?.lifecycle);
|
|
354
|
+
this.flow = this.readFlow(this.config.flow);
|
|
355
|
+
this.notifications.serverError(error);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -14,6 +14,7 @@ import applyContextComponentArguments from '@fleetbase/ember-core/utils/apply-co
|
|
|
14
14
|
import OrderConfigManagerDetailsComponent from './order-config-manager/details';
|
|
15
15
|
import OrderConfigManagerCustomFieldsComponent from './order-config-manager/custom-fields';
|
|
16
16
|
import OrderConfigManagerActivityFlowComponent from './order-config-manager/activity-flow';
|
|
17
|
+
import OrderConfigManagerLifecycleComponent from './order-config-manager/lifecycle';
|
|
17
18
|
import OrderConfigManagerEntitiesComponent from './order-config-manager/entities';
|
|
18
19
|
import findActiveTab from '../utils/find-active-tab';
|
|
19
20
|
|
|
@@ -45,6 +46,7 @@ export default class OrderConfigManagerComponent extends Component {
|
|
|
45
46
|
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.details'), null, { icon: 'circle-info', component: OrderConfigManagerDetailsComponent }),
|
|
46
47
|
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.custom-fields'), null, { icon: 'rectangle-list', component: OrderConfigManagerCustomFieldsComponent }),
|
|
47
48
|
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.activity-flow'), null, { icon: 'diagram-project', component: OrderConfigManagerActivityFlowComponent }),
|
|
49
|
+
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.lifecycle'), null, { icon: 'arrows-spin', component: OrderConfigManagerLifecycleComponent }),
|
|
48
50
|
this.universe._createMenuItem(this.intl.t('order-config-manager.tabs.entities'), null, { icon: 'boxes-packing', component: OrderConfigManagerEntitiesComponent }),
|
|
49
51
|
];
|
|
50
52
|
|
|
@@ -106,7 +108,9 @@ export default class OrderConfigManagerComponent extends Component {
|
|
|
106
108
|
*/
|
|
107
109
|
@task *loadOrderConfigs(options = {}) {
|
|
108
110
|
try {
|
|
109
|
-
|
|
111
|
+
// Always fetch: `findAll` otherwise resolves with whatever subset of configs the store
|
|
112
|
+
// already holds (e.g. from an order's embedded config) and only refreshes in the background.
|
|
113
|
+
const configs = yield this.store.findAll('order-config', { reload: true }).then(Array.from);
|
|
110
114
|
let currentConfig;
|
|
111
115
|
if (isArray(configs) && configs.length > 0) {
|
|
112
116
|
currentConfig = configs.find((c) => c.id === this.args.orderConfig);
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
</div>
|
|
16
16
|
<div class="flex flex-shrink-0 items-center gap-2 whitespace-nowrap">
|
|
17
17
|
<Button @icon="refresh" @size="sm" @helpText={{t "common.reload"}} @onClick={{this.refresh}} />
|
|
18
|
-
<Button @icon="plus" @text="Connect Provider" @type="primary" @size="sm" @onClick={{this.telematicActions.transition.create}} />
|
|
18
|
+
<Button @icon="plus" @text="Connect Provider" @type="primary" @size="sm" @permission="fleet-ops create telematic" @onClick={{this.telematicActions.transition.create}} />
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
|
21
21
|
</section>
|