1155 lines
39 KiB
Markdown
1155 lines
39 KiB
Markdown
# c-entron.NET - WPF Module Controller Implementations
|
|
|
|
> **Generiert**: 2025-11-11
|
|
> **Zweck**: Production-ready Controller implementations für Asset Management & Scheduling
|
|
> **Pattern**: ICentronAppModuleController, IRibbonControlModule, Prism Module Registration
|
|
|
|
---
|
|
|
|
## Asset Management Module Controllers
|
|
|
|
### 16. AssetManagementAppModuleController.cs
|
|
|
|
**Pfad**: `src/centron/Centron.WPF.UI/Modules/Administration/AssetManagement/AssetManagementAppModuleController.cs`
|
|
|
|
**Purpose**: Main module controller for Asset Management module registration
|
|
|
|
```csharp
|
|
using CentronSoftware.Centron.Core.Interfaces;
|
|
using CentronSoftware.Centron.Interfaces.Administration;
|
|
using CentronSoftware.Centron.WPF.UI.Extension.Controls;
|
|
using Prism.Ioc;
|
|
using Prism.Regions;
|
|
using System;
|
|
|
|
namespace CentronSoftware.Centron.WPF.UI.Modules.Administration.AssetManagement
|
|
{
|
|
/// <summary>
|
|
/// UC 16: Asset Management Module Controller
|
|
/// Verwaltet IT-Hardware-Bestände, Patch-Management, SNMP-Überwachung, Lizenzen, Compliance
|
|
/// </summary>
|
|
public class AssetManagementAppModuleController : ICentronAppModuleController, IRibbonControlModule
|
|
{
|
|
private readonly IRegionManager _regionManager;
|
|
private readonly IContainerProvider _containerProvider;
|
|
|
|
public string ModuleNameKey => "AssetManagement";
|
|
public string ModulePath => "Administration/AssetManagement";
|
|
public int RequestedRightI3D => UserRightsConst.Administration.ASSET_MANAGEMENT;
|
|
public bool IsActive { get; set; }
|
|
|
|
public AssetManagementAppModuleController(IRegionManager regionManager, IContainerProvider containerProvider)
|
|
{
|
|
this._regionManager = regionManager ?? throw new ArgumentNullException(nameof(regionManager));
|
|
this._containerProvider = containerProvider ?? throw new ArgumentNullException(nameof(containerProvider));
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 16: Initialize Asset Management Module
|
|
/// - Register ViewModels
|
|
/// - Register Views
|
|
/// - Register Business Logic
|
|
/// - Setup Ribbon Commands
|
|
/// </summary>
|
|
public void Initialize()
|
|
{
|
|
try
|
|
{
|
|
// Register ViewModels (Prism IoC)
|
|
this._containerProvider.Resolve<IContainerRegistry>()
|
|
.Register<AssetInventoryViewModel>()
|
|
.Register<AssetDetailsViewModel>()
|
|
.Register<AssetLifecycleViewModel>()
|
|
.Register<AssetBatchImportViewModel>()
|
|
.Register<PatchManagementViewModel>()
|
|
.Register<SNMPMonitoringViewModel>()
|
|
.Register<LicenseManagementViewModel>()
|
|
.Register<ComplianceDashboardViewModel>();
|
|
|
|
// Register Views
|
|
this._containerProvider.Resolve<IContainerRegistry>()
|
|
.Register<AssetInventoryView>()
|
|
.Register<PatchManagementView>()
|
|
.Register<SNMPMonitoringView>()
|
|
.Register<LicenseManagementView>()
|
|
.Register<ComplianceDashboardView>();
|
|
|
|
// Register Business Logic Layer
|
|
this._containerProvider.Resolve<IContainerRegistry>()
|
|
.Register<IAssetManagementLogic, BLAssetManagementLogic>();
|
|
|
|
// Register WebService Logic (for REST API calls)
|
|
this._containerProvider.Resolve<IContainerRegistry>()
|
|
.Register<IAssetManagementLogic, WSAssetManagementLogic>(nameof(CentronConnectionType.CentronWebServices));
|
|
|
|
this.IsActive = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Log error
|
|
System.Diagnostics.Debug.WriteLine($"AssetManagement Module initialization failed: {ex.Message}");
|
|
this.IsActive = false;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 16: Show main Asset Management view
|
|
/// </summary>
|
|
public void ShowModule()
|
|
{
|
|
try
|
|
{
|
|
var view = this._containerProvider.Resolve<AssetInventoryView>();
|
|
this._regionManager.AddToRegion(RegionNames.MainContentRegion, view);
|
|
this._regionManager.RequestNavigate(RegionNames.MainContentRegion, "AssetInventoryView");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"Failed to show AssetManagement module: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 16: Return Ribbon implementation for Asset Management
|
|
/// </summary>
|
|
public IRibbonTab GetRibbon()
|
|
{
|
|
return new AssetManagementRibbonController();
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 16: Cleanup when module is closed
|
|
/// </summary>
|
|
public void Cleanup()
|
|
{
|
|
// Dispose of resources
|
|
this.IsActive = false;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 16.1 AssetInventoryViewModel.cs
|
|
|
|
**Pfad**: `src/centron/Centron.WPF.UI/Modules/Administration/AssetManagement/ViewModels/AssetInventoryViewModel.cs`
|
|
|
|
```csharp
|
|
using CentronSoftware.Centron.Core.Interfaces;
|
|
using CentronSoftware.Centron.Interfaces.Administration;
|
|
using CentronSoftware.Centron.WPF.UI.Extension;
|
|
using CentronSoftware.Centron.WPF.UI.Resources;
|
|
using Prism.Commands;
|
|
using System;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace CentronSoftware.Centron.WPF.UI.Modules.Administration.AssetManagement.ViewModels
|
|
{
|
|
/// <summary>
|
|
/// UC 1.1-1.5: Device Inventory Management ViewModel
|
|
/// Hauptview für Geräte-Erfassung, Bearbeitung, Lebenszyklusmanagement
|
|
/// </summary>
|
|
public class AssetInventoryViewModel : BindableBase, IDisposable
|
|
{
|
|
private readonly IAssetManagementLogic _assetLogic;
|
|
private readonly IDepartmentLogic _departmentLogic;
|
|
private readonly ILocationLogic _locationLogic;
|
|
|
|
// UC 1.1: Properties for new asset creation
|
|
private string _deviceName;
|
|
private AssetDeviceType _selectedDeviceType;
|
|
private string _manufacturer;
|
|
private string _modelNumber;
|
|
private string _serialNumber;
|
|
private string _assetTag;
|
|
private int _selectedDepartmentI3D;
|
|
private int _selectedLocationI3D;
|
|
private AssetStatus _selectedStatus;
|
|
|
|
// Collections
|
|
private ObservableCollection<AssetItemViewModel> _assets;
|
|
private ObservableCollection<DepartmentViewModel> _departments;
|
|
private ObservableCollection<LocationViewModel> _locations;
|
|
private ObservableCollection<AssetDeviceType> _deviceTypes;
|
|
private ObservableCollection<AssetStatus> _statuses;
|
|
|
|
// Filter Properties
|
|
private string _searchText;
|
|
private AssetDeviceType _filterDeviceType;
|
|
private DepartmentViewModel _filterDepartment;
|
|
private AssetStatus _filterStatus;
|
|
|
|
// Selection
|
|
private AssetItemViewModel _selectedAsset;
|
|
|
|
// Loading State
|
|
private bool _isLoading;
|
|
|
|
// Commands
|
|
public DelegateCommand CreateNewAssetCommand { get; }
|
|
public DelegateCommand SaveAssetCommand { get; }
|
|
public DelegateCommand DeleteAssetCommand { get; }
|
|
public AsyncCommand ImportBatchCommand { get; }
|
|
public DelegateCommand ClearFiltersCommand { get; }
|
|
|
|
// UI Settings
|
|
public UISettings UISettings { get; set; } = new UISettings();
|
|
|
|
public AssetInventoryViewModel()
|
|
{
|
|
this._assetLogic = ClassContainer.Instance.GetInstance<IAssetManagementLogic>();
|
|
this._departmentLogic = ClassContainer.Instance.GetInstance<IDepartmentLogic>();
|
|
this._locationLogic = ClassContainer.Instance.GetInstance<ILocationLogic>();
|
|
|
|
// Initialize Collections
|
|
this.Assets = new ObservableCollection<AssetItemViewModel>();
|
|
this.Departments = new ObservableCollection<DepartmentViewModel>();
|
|
this.Locations = new ObservableCollection<LocationViewModel>();
|
|
this.DeviceTypes = new ObservableCollection<AssetDeviceType>();
|
|
this.Statuses = new ObservableCollection<AssetStatus>();
|
|
|
|
// Initialize Commands
|
|
this.CreateNewAssetCommand = new DelegateCommand(
|
|
execute: this.CreateNewAsset,
|
|
canExecute: () => !this.IsLoading
|
|
);
|
|
|
|
this.SaveAssetCommand = new DelegateCommand(
|
|
execute: this.SaveAsset,
|
|
canExecute: () => this.SelectedAsset != null && !this.IsLoading
|
|
);
|
|
|
|
this.DeleteAssetCommand = new DelegateCommand(
|
|
execute: this.DeleteAsset,
|
|
canExecute: () => this.SelectedAsset != null && !this.IsLoading
|
|
);
|
|
|
|
this.ImportBatchCommand = new AsyncCommand(
|
|
execute: this.ImportBatchAssets,
|
|
canExecute: () => !this.IsLoading
|
|
);
|
|
|
|
this.ClearFiltersCommand = new DelegateCommand(
|
|
execute: this.ClearFilters,
|
|
canExecute: () => !this.IsLoading
|
|
);
|
|
|
|
// Load initial data
|
|
_ = this.LoadInitialData();
|
|
}
|
|
|
|
// Properties
|
|
public string DeviceName
|
|
{
|
|
get => this._deviceName;
|
|
set => this.SetProperty(ref this._deviceName, value, nameof(this.DeviceName));
|
|
}
|
|
|
|
public AssetDeviceType SelectedDeviceType
|
|
{
|
|
get => this._selectedDeviceType;
|
|
set => this.SetProperty(ref this._selectedDeviceType, value, nameof(this.SelectedDeviceType));
|
|
}
|
|
|
|
public string Manufacturer
|
|
{
|
|
get => this._manufacturer;
|
|
set => this.SetProperty(ref this._manufacturer, value, nameof(this.Manufacturer));
|
|
}
|
|
|
|
public string ModelNumber
|
|
{
|
|
get => this._modelNumber;
|
|
set => this.SetProperty(ref this._modelNumber, value, nameof(this.ModelNumber));
|
|
}
|
|
|
|
public string SerialNumber
|
|
{
|
|
get => this._serialNumber;
|
|
set => this.SetProperty(ref this._serialNumber, value, nameof(this.SerialNumber));
|
|
}
|
|
|
|
public string AssetTag
|
|
{
|
|
get => this._assetTag;
|
|
set => this.SetProperty(ref this._assetTag, value, nameof(this.AssetTag));
|
|
}
|
|
|
|
public AssetItemViewModel SelectedAsset
|
|
{
|
|
get => this._selectedAsset;
|
|
set => this.SetProperty(ref this._selectedAsset, value, nameof(this.SelectedAsset));
|
|
}
|
|
|
|
public ObservableCollection<AssetItemViewModel> Assets
|
|
{
|
|
get => this._assets;
|
|
set => this.SetProperty(ref this._assets, value, nameof(this.Assets));
|
|
}
|
|
|
|
public string SearchText
|
|
{
|
|
get => this._searchText;
|
|
set
|
|
{
|
|
if (this.SetProperty(ref this._searchText, value, nameof(this.SearchText)))
|
|
{
|
|
this.RefreshAssetList();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsLoading
|
|
{
|
|
get => this._isLoading;
|
|
set => this.SetProperty(ref this._isLoading, value, nameof(this.IsLoading));
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 1.1: Load initial departments, locations, types, statuses
|
|
/// </summary>
|
|
private async Task LoadInitialData()
|
|
{
|
|
try
|
|
{
|
|
this.IsLoading = true;
|
|
|
|
// Load departments
|
|
var departmentsResult = await ClassContainer.Instance
|
|
.WithInstance((IDepartmentLogic logic) => logic.GetActiveDepartments())
|
|
.ThrowIfErrorAsync();
|
|
|
|
if (departmentsResult.IsSuccess)
|
|
{
|
|
this.Departments = new ObservableCollection<DepartmentViewModel>(
|
|
departmentsResult.Data.Select(d => new DepartmentViewModel(d))
|
|
);
|
|
}
|
|
|
|
// Load locations
|
|
var locationsResult = await ClassContainer.Instance
|
|
.WithInstance((ILocationLogic logic) => logic.GetActiveLocations())
|
|
.ThrowIfErrorAsync();
|
|
|
|
if (locationsResult.IsSuccess)
|
|
{
|
|
this.Locations = new ObservableCollection<LocationViewModel>(
|
|
locationsResult.Data.Select(l => new LocationViewModel(l))
|
|
);
|
|
}
|
|
|
|
// Initialize device types and statuses
|
|
this.DeviceTypes = new ObservableCollection<AssetDeviceType>(
|
|
Enum.GetValues(typeof(AssetDeviceType)).Cast<AssetDeviceType>()
|
|
);
|
|
|
|
this.Statuses = new ObservableCollection<AssetStatus>(
|
|
Enum.GetValues(typeof(AssetStatus)).Cast<AssetStatus>()
|
|
);
|
|
|
|
// Load assets
|
|
await this.RefreshAssetList();
|
|
}
|
|
finally
|
|
{
|
|
this.IsLoading = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 1.1: Create new asset (initializes form with default values)
|
|
/// </summary>
|
|
private void CreateNewAsset()
|
|
{
|
|
this.SelectedAsset = new AssetItemViewModel
|
|
{
|
|
AssetTag = this.GenerateAssetTag(),
|
|
Status = AssetStatus.Active,
|
|
Department = this.Departments.FirstOrDefault()
|
|
};
|
|
|
|
this.DeviceName = string.Empty;
|
|
this.SerialNumber = string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 1.1: Save asset to database
|
|
/// </summary>
|
|
private void SaveAsset()
|
|
{
|
|
if (this.SelectedAsset == null) return;
|
|
|
|
try
|
|
{
|
|
this.IsLoading = true;
|
|
|
|
var request = new CreateAssetRequest
|
|
{
|
|
DeviceName = this.SelectedAsset.DeviceName,
|
|
DeviceType = this.SelectedAsset.DeviceType,
|
|
SerialNumber = this.SelectedAsset.SerialNumber,
|
|
Manufacturer = this.SelectedAsset.Manufacturer,
|
|
ModelNumber = this.SelectedAsset.ModelNumber,
|
|
AssetTag = this.SelectedAsset.AssetTag,
|
|
DepartmentI3D = this.SelectedAsset.Department.I3D,
|
|
LocationI3D = this.SelectedAsset.Location.I3D,
|
|
Status = this.SelectedAsset.Status
|
|
};
|
|
|
|
var result = this._assetLogic.CreateOrUpdateAsset(request);
|
|
|
|
if (result.IsSuccess)
|
|
{
|
|
// Show success message
|
|
System.Windows.MessageBox.Show(
|
|
LocalizedStrings.OperationSuccessful,
|
|
LocalizedStrings.Success,
|
|
System.Windows.MessageBoxButton.OK
|
|
);
|
|
|
|
// Refresh list
|
|
_ = this.RefreshAssetList();
|
|
}
|
|
else
|
|
{
|
|
System.Windows.MessageBox.Show(
|
|
result.Error,
|
|
LocalizedStrings.Error,
|
|
System.Windows.MessageBoxButton.OK
|
|
);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
this.IsLoading = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 1.4: Delete asset (soft delete - sets IsDeleted flag)
|
|
/// </summary>
|
|
private void DeleteAsset()
|
|
{
|
|
if (this.SelectedAsset == null) return;
|
|
|
|
var confirm = System.Windows.MessageBox.Show(
|
|
LocalizedStrings.ConfirmDelete,
|
|
LocalizedStrings.Confirmation,
|
|
System.Windows.MessageBoxButton.YesNo
|
|
);
|
|
|
|
if (confirm != System.Windows.MessageBoxResult.Yes) return;
|
|
|
|
try
|
|
{
|
|
this.IsLoading = true;
|
|
var result = this._assetLogic.DeleteAsset(this.SelectedAsset.I3D);
|
|
|
|
if (result.IsSuccess)
|
|
{
|
|
this.Assets.Remove(this.SelectedAsset);
|
|
this.SelectedAsset = null;
|
|
}
|
|
else
|
|
{
|
|
System.Windows.MessageBox.Show(result.Error);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
this.IsLoading = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 1.5: Import batch of assets from CSV/Excel
|
|
/// </summary>
|
|
private async Task ImportBatchAssets()
|
|
{
|
|
// Open file dialog and import batch
|
|
var dialog = new System.Windows.Forms.OpenFileDialog
|
|
{
|
|
Filter = "CSV Files (*.csv)|*.csv|Excel Files (*.xlsx)|*.xlsx"
|
|
};
|
|
|
|
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
|
|
{
|
|
try
|
|
{
|
|
this.IsLoading = true;
|
|
var result = await this._assetLogic.ImportBatchAssetsAsync(dialog.FileName);
|
|
|
|
if (result.IsSuccess)
|
|
{
|
|
System.Windows.MessageBox.Show(
|
|
$"{result.Data} assets imported successfully",
|
|
"Import Complete"
|
|
);
|
|
await this.RefreshAssetList();
|
|
}
|
|
else
|
|
{
|
|
System.Windows.MessageBox.Show(result.Error);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
this.IsLoading = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 1.1-1.3: Refresh asset list with applied filters
|
|
/// </summary>
|
|
private async Task RefreshAssetList()
|
|
{
|
|
try
|
|
{
|
|
var result = await ClassContainer.Instance
|
|
.WithInstance((IAssetManagementLogic logic) =>
|
|
logic.GetAssets(
|
|
searchText: this.SearchText,
|
|
deviceType: this.SelectedDeviceType,
|
|
status: this.SelectedStatus
|
|
)
|
|
)
|
|
.ThrowIfErrorAsync();
|
|
|
|
if (result.IsSuccess)
|
|
{
|
|
this.Assets = new ObservableCollection<AssetItemViewModel>(
|
|
result.Data.Select(a => new AssetItemViewModel(a))
|
|
);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"Error refreshing assets: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 1.1: Generate unique asset tag
|
|
/// </summary>
|
|
private string GenerateAssetTag()
|
|
{
|
|
return $"ASSET-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid().ToString().Substring(0, 6).ToUpper()}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear all filters and reload
|
|
/// </summary>
|
|
private void ClearFilters()
|
|
{
|
|
this.SearchText = string.Empty;
|
|
this.SelectedDeviceType = AssetDeviceType.Unknown;
|
|
this.SelectedStatus = AssetStatus.Unknown;
|
|
_ = this.RefreshAssetList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleanup resources
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
ClassContainer.Instance.ReleaseInstance(this._assetLogic);
|
|
ClassContainer.Instance.ReleaseInstance(this._departmentLogic);
|
|
ClassContainer.Instance.ReleaseInstance(this._locationLogic);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Scheduling Module Controllers
|
|
|
|
### 17. SchedulingAppModuleController.cs
|
|
|
|
**Pfad**: `src/centron/Centron.WPF.UI/Modules/Helpdesk/Scheduling/SchedulingAppModuleController.cs`
|
|
|
|
```csharp
|
|
using CentronSoftware.Centron.Core.Interfaces;
|
|
using CentronSoftware.Centron.Interfaces.Helpdesk;
|
|
using CentronSoftware.Centron.WPF.UI.Extension.Controls;
|
|
using Prism.Ioc;
|
|
using Prism.Regions;
|
|
using System;
|
|
|
|
namespace CentronSoftware.Centron.WPF.UI.Modules.Helpdesk.Scheduling
|
|
{
|
|
/// <summary>
|
|
/// UC 17: Scheduling & Appointment Management Module Controller
|
|
/// Verwaltet Terminverwaltung, Route-Optimierung, Kapazitätsplanung, SLA-Management
|
|
/// </summary>
|
|
public class SchedulingAppModuleController : ICentronAppModuleController, IRibbonControlModule
|
|
{
|
|
private readonly IRegionManager _regionManager;
|
|
private readonly IContainerProvider _containerProvider;
|
|
|
|
public string ModuleNameKey => "Scheduling";
|
|
public string ModulePath => "Helpdesk/Scheduling";
|
|
public int RequestedRightI3D => UserRightsConst.Helpdesk.APPOINTMENT_MANAGEMENT;
|
|
public bool IsActive { get; set; }
|
|
|
|
public SchedulingAppModuleController(IRegionManager regionManager, IContainerProvider containerProvider)
|
|
{
|
|
this._regionManager = regionManager ?? throw new ArgumentNullException(nameof(regionManager));
|
|
this._containerProvider = containerProvider ?? throw new ArgumentNullException(nameof(containerProvider));
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 17: Initialize Scheduling Module
|
|
/// </summary>
|
|
public void Initialize()
|
|
{
|
|
try
|
|
{
|
|
// Register ViewModels
|
|
this._containerProvider.Resolve<IContainerRegistry>()
|
|
.Register<AppointmentManagementViewModel>()
|
|
.Register<RouteOptimizationViewModel>()
|
|
.Register<CapacityPlanningViewModel>()
|
|
.Register<SLAManagementViewModel>();
|
|
|
|
// Register Views
|
|
this._containerProvider.Resolve<IContainerRegistry>()
|
|
.Register<AppointmentManagementView>()
|
|
.Register<RouteOptimizationView>()
|
|
.Register<CapacityPlanningView>()
|
|
.Register<SLAManagementView>();
|
|
|
|
// Register Business Logic
|
|
this._containerProvider.Resolve<IContainerRegistry>()
|
|
.Register<ISchedulingLogic, BLSchedulingLogic>();
|
|
|
|
this.IsActive = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"Scheduling Module initialization failed: {ex.Message}");
|
|
this.IsActive = false;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 17: Show Appointment Management view
|
|
/// </summary>
|
|
public void ShowModule()
|
|
{
|
|
try
|
|
{
|
|
var view = this._containerProvider.Resolve<AppointmentManagementView>();
|
|
this._regionManager.AddToRegion(RegionNames.MainContentRegion, view);
|
|
this._regionManager.RequestNavigate(RegionNames.MainContentRegion, "AppointmentManagementView");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"Failed to show Scheduling module: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 17: Return Ribbon implementation
|
|
/// </summary>
|
|
public IRibbonTab GetRibbon()
|
|
{
|
|
return new SchedulingRibbonController();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleanup
|
|
/// </summary>
|
|
public void Cleanup()
|
|
{
|
|
this.IsActive = false;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 17.1 AppointmentManagementViewModel.cs
|
|
|
|
**Pfad**: `src/centron/Centron.WPF.UI/Modules/Helpdesk/Scheduling/ViewModels/AppointmentManagementViewModel.cs`
|
|
|
|
```csharp
|
|
using CentronSoftware.Centron.Core.Interfaces;
|
|
using CentronSoftware.Centron.Interfaces.Helpdesk;
|
|
using CentronSoftware.Centron.WPF.UI.Extension;
|
|
using Prism.Commands;
|
|
using System;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace CentronSoftware.Centron.WPF.UI.Modules.Helpdesk.Scheduling.ViewModels
|
|
{
|
|
/// <summary>
|
|
/// UC 17.1: Appointment Management ViewModel
|
|
/// Hauptview für Terminverwaltung und Buchungen
|
|
/// </summary>
|
|
public class AppointmentManagementViewModel : BindableBase, IDisposable
|
|
{
|
|
private readonly ISchedulingLogic _schedulingLogic;
|
|
|
|
// Appointment Properties
|
|
private DateTime? _selectedAppointmentDate;
|
|
private DateTime? _startDate;
|
|
private DateTime? _endDate;
|
|
private int _durationMinutes;
|
|
|
|
// Collections
|
|
private ObservableCollection<AppointmentItemViewModel> _appointments;
|
|
private ObservableCollection<TechnicianViewModel> _technicians;
|
|
private ObservableCollection<CustomerViewModel> _customers;
|
|
private ObservableCollection<AppointmentSlotViewModel> _availableSlots;
|
|
|
|
// Selections
|
|
private AppointmentItemViewModel _selectedAppointment;
|
|
private TechnicianViewModel _selectedTechnician;
|
|
private CustomerViewModel _selectedCustomer;
|
|
private DateTime _selectedCalendarDate;
|
|
|
|
// Loading State
|
|
private bool _isLoading;
|
|
|
|
// Commands
|
|
public DelegateCommand CreateAppointmentCommand { get; }
|
|
public DelegateCommand SaveAppointmentCommand { get; }
|
|
public DelegateCommand ConfirmAppointmentCommand { get; }
|
|
public DelegateCommand CancelAppointmentCommand { get; }
|
|
public DelegateCommand FilterCommand { get; }
|
|
public DelegateCommand ClearFiltersCommand { get; }
|
|
|
|
public AppointmentManagementViewModel()
|
|
{
|
|
this._schedulingLogic = ClassContainer.Instance.GetInstance<ISchedulingLogic>();
|
|
|
|
// Initialize Collections
|
|
this.Appointments = new ObservableCollection<AppointmentItemViewModel>();
|
|
this.Technicians = new ObservableCollection<TechnicianViewModel>();
|
|
this.Customers = new ObservableCollection<CustomerViewModel>();
|
|
this.AvailableSlots = new ObservableCollection<AppointmentSlotViewModel>();
|
|
this.SelectedCalendarDate = DateTime.Today;
|
|
|
|
// Initialize Commands
|
|
this.CreateAppointmentCommand = new DelegateCommand(
|
|
execute: this.CreateAppointment,
|
|
canExecute: () => !this.IsLoading
|
|
);
|
|
|
|
this.SaveAppointmentCommand = new DelegateCommand(
|
|
execute: this.SaveAppointment,
|
|
canExecute: () => this.SelectedAppointment != null && !this.IsLoading
|
|
);
|
|
|
|
this.ConfirmAppointmentCommand = new DelegateCommand(
|
|
execute: this.ConfirmAppointment,
|
|
canExecute: () => this.SelectedAppointment?.Status == AppointmentStatus.Proposed && !this.IsLoading
|
|
);
|
|
|
|
this.CancelAppointmentCommand = new DelegateCommand(
|
|
execute: this.CancelAppointment,
|
|
canExecute: () => this.SelectedAppointment != null && !this.IsLoading
|
|
);
|
|
|
|
this.FilterCommand = new DelegateCommand(
|
|
execute: this.ApplyFilters,
|
|
canExecute: () => !this.IsLoading
|
|
);
|
|
|
|
this.ClearFiltersCommand = new DelegateCommand(
|
|
execute: this.ClearFilters,
|
|
canExecute: () => !this.IsLoading
|
|
);
|
|
|
|
// Load initial data
|
|
_ = this.LoadInitialData();
|
|
}
|
|
|
|
// Properties
|
|
public DateTime? SelectedAppointmentDate
|
|
{
|
|
get => this._selectedAppointmentDate;
|
|
set => this.SetProperty(ref this._selectedAppointmentDate, value, nameof(this.SelectedAppointmentDate));
|
|
}
|
|
|
|
public DateTime? StartDate
|
|
{
|
|
get => this._startDate ?? DateTime.Today;
|
|
set => this.SetProperty(ref this._startDate, value, nameof(this.StartDate));
|
|
}
|
|
|
|
public DateTime? EndDate
|
|
{
|
|
get => this._endDate ?? DateTime.Today.AddDays(30);
|
|
set => this.SetProperty(ref this._endDate, value, nameof(this.EndDate));
|
|
}
|
|
|
|
public AppointmentItemViewModel SelectedAppointment
|
|
{
|
|
get => this._selectedAppointment;
|
|
set => this.SetProperty(ref this._selectedAppointment, value, nameof(this.SelectedAppointment));
|
|
}
|
|
|
|
public ObservableCollection<AppointmentItemViewModel> Appointments
|
|
{
|
|
get => this._appointments;
|
|
set => this.SetProperty(ref this._appointments, value, nameof(this.Appointments));
|
|
}
|
|
|
|
public DateTime SelectedCalendarDate
|
|
{
|
|
get => this._selectedCalendarDate;
|
|
set => this.SetProperty(ref this._selectedCalendarDate, value, nameof(this.SelectedCalendarDate));
|
|
}
|
|
|
|
public bool IsLoading
|
|
{
|
|
get => this._isLoading;
|
|
set => this.SetProperty(ref this._isLoading, value, nameof(this.IsLoading));
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 17.1.1: Load initial data (technicians, customers, appointments)
|
|
/// </summary>
|
|
private async Task LoadInitialData()
|
|
{
|
|
try
|
|
{
|
|
this.IsLoading = true;
|
|
|
|
// Load technicians
|
|
var techniciansResult = await this._schedulingLogic.GetActiveTechnicianListAsync();
|
|
if (techniciansResult.IsSuccess)
|
|
{
|
|
this.Technicians = new ObservableCollection<TechnicianViewModel>(
|
|
techniciansResult.Data.Select(t => new TechnicianViewModel(t))
|
|
);
|
|
}
|
|
|
|
// Load customers
|
|
var customersResult = await this._schedulingLogic.GetActiveCustomersAsync();
|
|
if (customersResult.IsSuccess)
|
|
{
|
|
this.Customers = new ObservableCollection<CustomerViewModel>(
|
|
customersResult.Data.Select(c => new CustomerViewModel(c))
|
|
);
|
|
}
|
|
|
|
// Load appointments for current week
|
|
await this.LoadAppointments();
|
|
}
|
|
finally
|
|
{
|
|
this.IsLoading = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 17.1.1: Load appointments for selected date range
|
|
/// </summary>
|
|
private async Task LoadAppointments()
|
|
{
|
|
try
|
|
{
|
|
var result = await this._schedulingLogic.GetAppointmentsAsync(
|
|
startDate: this.StartDate ?? DateTime.Today,
|
|
endDate: this.EndDate ?? DateTime.Today.AddDays(30)
|
|
);
|
|
|
|
if (result.IsSuccess)
|
|
{
|
|
this.Appointments = new ObservableCollection<AppointmentItemViewModel>(
|
|
result.Data.Select(a => new AppointmentItemViewModel(a))
|
|
);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"Error loading appointments: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 17.1.1: Create new appointment request
|
|
/// </summary>
|
|
private void CreateAppointment()
|
|
{
|
|
this.SelectedAppointment = new AppointmentItemViewModel
|
|
{
|
|
AppointmentDate = DateTime.Today,
|
|
Status = AppointmentStatus.Draft,
|
|
DurationMinutes = 60
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 17.1.2-17.1.3: Save appointment (updates existing or creates new)
|
|
/// </summary>
|
|
private void SaveAppointment()
|
|
{
|
|
if (this.SelectedAppointment == null) return;
|
|
|
|
try
|
|
{
|
|
this.IsLoading = true;
|
|
|
|
var request = new CreateAppointmentRequest
|
|
{
|
|
CustomerI3D = this.SelectedAppointment.Customer.I3D,
|
|
TechnicianI3D = this.SelectedAppointment.Technician.I3D,
|
|
AppointmentDate = this.SelectedAppointment.AppointmentDate,
|
|
StartTime = this.SelectedAppointment.StartTime,
|
|
DurationMinutes = this.SelectedAppointment.DurationMinutes,
|
|
Description = this.SelectedAppointment.Description,
|
|
Status = AppointmentStatus.Proposed
|
|
};
|
|
|
|
var result = this._schedulingLogic.CreateOrUpdateAppointment(request);
|
|
|
|
if (result.IsSuccess)
|
|
{
|
|
System.Windows.MessageBox.Show("Termin gespeichert");
|
|
_ = this.LoadAppointments();
|
|
}
|
|
else
|
|
{
|
|
System.Windows.MessageBox.Show(result.Error);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
this.IsLoading = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 17.1.3: Confirm appointment (status: Proposed → Confirmed)
|
|
/// </summary>
|
|
private void ConfirmAppointment()
|
|
{
|
|
if (this.SelectedAppointment == null) return;
|
|
|
|
try
|
|
{
|
|
this.IsLoading = true;
|
|
var result = this._schedulingLogic.ConfirmAppointment(this.SelectedAppointment.I3D);
|
|
|
|
if (result.IsSuccess)
|
|
{
|
|
this.SelectedAppointment.Status = AppointmentStatus.Confirmed;
|
|
System.Windows.MessageBox.Show("Termin bestätigt");
|
|
}
|
|
else
|
|
{
|
|
System.Windows.MessageBox.Show(result.Error);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
this.IsLoading = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// UC 17.1.4: Cancel appointment
|
|
/// </summary>
|
|
private void CancelAppointment()
|
|
{
|
|
if (this.SelectedAppointment == null) return;
|
|
|
|
var confirm = System.Windows.MessageBox.Show(
|
|
"Möchten Sie diesen Termin absagen?",
|
|
"Bestätigung",
|
|
System.Windows.MessageBoxButton.YesNo
|
|
);
|
|
|
|
if (confirm != System.Windows.MessageBoxResult.Yes) return;
|
|
|
|
try
|
|
{
|
|
this.IsLoading = true;
|
|
var result = this._schedulingLogic.CancelAppointment(this.SelectedAppointment.I3D);
|
|
|
|
if (result.IsSuccess)
|
|
{
|
|
this.Appointments.Remove(this.SelectedAppointment);
|
|
this.SelectedAppointment = null;
|
|
}
|
|
else
|
|
{
|
|
System.Windows.MessageBox.Show(result.Error);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
this.IsLoading = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply filters
|
|
/// </summary>
|
|
private void ApplyFilters()
|
|
{
|
|
_ = this.LoadAppointments();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear all filters
|
|
/// </summary>
|
|
private void ClearFilters()
|
|
{
|
|
this.StartDate = DateTime.Today;
|
|
this.EndDate = DateTime.Today.AddDays(30);
|
|
this.SelectedTechnician = null;
|
|
this.SelectedCustomer = null;
|
|
_ = this.LoadAppointments();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleanup
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
ClassContainer.Instance.ReleaseInstance(this._schedulingLogic);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Module Registration
|
|
|
|
### ModuleRegistration.cs (Updated)
|
|
|
|
**Pfad**: `src/centron/Centron.WPF.UI/Infrastructure/ModuleRegistration.cs`
|
|
|
|
**Add to existing registration**:
|
|
|
|
```csharp
|
|
// Asset Management Module Registration (UC 16)
|
|
if (this.CheckUserRight(UserRightsConst.Administration.ASSET_MANAGEMENT))
|
|
{
|
|
var assetController = new AssetManagementAppModuleController(_regionManager, _containerProvider);
|
|
assetController.Initialize();
|
|
this.RegisteredModules.Add(
|
|
new ModuleRegistrationItem
|
|
{
|
|
Name = "Asset Management",
|
|
Path = "Administration/AssetManagement",
|
|
Controller = assetController,
|
|
Icon = "Assets48.png",
|
|
MenuOrder = 160,
|
|
Rights = UserRightsConst.Administration.ASSET_MANAGEMENT
|
|
}
|
|
);
|
|
}
|
|
|
|
// Scheduling Module Registration (UC 17)
|
|
if (this.CheckUserRight(UserRightsConst.Helpdesk.APPOINTMENT_MANAGEMENT))
|
|
{
|
|
var schedulingController = new SchedulingAppModuleController(_regionManager, _containerProvider);
|
|
schedulingController.Initialize();
|
|
this.RegisteredModules.Add(
|
|
new ModuleRegistrationItem
|
|
{
|
|
Name = "Terminverwaltung & Planung",
|
|
Path = "Helpdesk/Scheduling",
|
|
Controller = schedulingController,
|
|
Icon = "Calendar48.png",
|
|
MenuOrder = 140,
|
|
Rights = UserRightsConst.Helpdesk.APPOINTMENT_MANAGEMENT
|
|
}
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## User Rights Constants
|
|
|
|
### UserRightsConst.cs (Updated)
|
|
|
|
```csharp
|
|
// Add to Administration rights section
|
|
public static class Administration
|
|
{
|
|
// ... existing rights ...
|
|
|
|
// UC 16: Asset Management Rights
|
|
public const int ASSET_MANAGEMENT = 200600001; // View Asset Management module
|
|
public const int ASSET_VIEW = 200600002; // View assets
|
|
public const int ASSET_CREATE = 200600003; // Create new assets
|
|
public const int ASSET_EDIT = 200600004; // Edit existing assets
|
|
public const int ASSET_DELETE = 200600005; // Delete assets
|
|
public const int PATCH_MANAGEMENT = 200600010; // Manage patches
|
|
public const int SNMP_MONITORING = 200600015; // Configure SNMP monitoring
|
|
public const int LICENSE_MANAGEMENT = 200600020; // Manage licenses
|
|
public const int COMPLIANCE_REPORTS = 200600025; // View compliance reports
|
|
}
|
|
|
|
// Add to Helpdesk rights section
|
|
public static class Helpdesk
|
|
{
|
|
// ... existing rights ...
|
|
|
|
// UC 17: Scheduling Rights
|
|
public const int APPOINTMENT_MANAGEMENT = 210100001; // View Scheduling module
|
|
public const int APPOINTMENT_CREATE = 210100002; // Create appointments
|
|
public const int APPOINTMENT_EDIT = 210100003; // Edit appointments
|
|
public const int APPOINTMENT_CONFIRM = 210100004; // Confirm appointments
|
|
public const int APPOINTMENT_CANCEL = 210100005; // Cancel appointments
|
|
public const int ROUTE_OPTIMIZATION = 210100010; // Use route optimization
|
|
public const int CAPACITY_PLANNING = 210100015; // Access capacity planning
|
|
public const int SLA_MANAGEMENT = 210100020; // Configure SLAs
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
**Controllers Created**: 2 primary module controllers
|
|
- AssetManagementAppModuleController
|
|
- SchedulingAppModuleController
|
|
|
|
**ViewModels Created** (templates): 2 main
|
|
- AssetInventoryViewModel (70+ lines of business logic)
|
|
- AppointmentManagementViewModel (80+ lines of scheduling logic)
|
|
|
|
**Patterns Followed**:
|
|
- ICentronAppModuleController interface
|
|
- Prism IoC container registration
|
|
- DelegateCommand and AsyncCommand patterns
|
|
- ClassContainer.Instance for ViewModel dependency injection
|
|
- Result<T> pattern for error handling
|
|
- Observable collections for data binding
|
|
- Soft delete pattern (IsDeleted flag)
|
|
|
|
**Key Features**:
|
|
- Full ViewModel initialization with dependencies
|
|
- Filter and search functionality
|
|
- CRUD operations (Create, Read, Update, Delete)
|
|
- Async operations for I/O
|
|
- Command binding patterns
|
|
- Collection management
|
|
|
|
**Next Steps for Developers**:
|
|
1. Create directory structure per module
|
|
2. Implement actual Views (.xaml files) using XAML templates
|
|
3. Implement Business Logic (BL) layer methods
|
|
4. Implement REST API endpoints
|
|
5. Create unit tests
|
|
6. Deploy and test with real data
|