Files
Masterarbeit/Ergebnisse/Ergebnisse 03/USE_CASES_NEW_IMPLEMENTATION_GUIDE.md

32 KiB

Implementierungs-Leitfaden: Asset Management & Scheduling Modules

Zielgruppe: C# Entwickler, WPF/MVVM Architekten Schwerpunkt: Praktische Umsetzung der neuen Module Basis: USE_CASES_NEW.md + USE_CASES_NEW_GUI_MAPPING.md


🎯 Schnelleinstieg

Verzeichnis-Struktur nach Fertigstellung

src/
├── centron/Centron.WPF.UI/Modules/
│   ├── Administration/
│   │   ├── AssetManagement/                   [NEU]
│   │   │   ├── AssetManagementAppModuleController.cs
│   │   │   ├── AssetManagementView.xaml
│   │   │   ├── ViewModels/
│   │   │   │   ├── AssetInventoryViewModel.cs
│   │   │   │   ├── AssetDetailsViewModel.cs
│   │   │   │   ├── AssetLifecycleViewModel.cs
│   │   │   │   ├── AssetBatchImportViewModel.cs
│   │   │   │   ├── PatchManagementViewModel.cs
│   │   │   │   ├── SnmpMonitoringViewModel.cs
│   │   │   │   └── LicenseManagementViewModel.cs
│   │   │   └── Views/
│   │   │       ├── AssetInventoryView.xaml
│   │   │       ├── AssetDetailsView.xaml
│   │   │       ├── AssetLifecycleView.xaml
│   │   │       ├── PatchManagementView.xaml
│   │   │       ├── SnmpMonitoringView.xaml
│   │   │       └── LicenseManagementView.xaml
│   │
│   └── Calendar/
│       ├── AppointmentScheduling/             [NEU/ERWEITERT]
│       │   ├── AppointmentSchedulingAppModuleController.cs
│       │   ├── AppointmentSchedulingView.xaml
│       │   ├── ViewModels/
│       │   │   ├── AppointmentRequestViewModel.cs
│       │   │   ├── AppointmentProposalViewModel.cs
│       │   │   ├── AppointmentConfirmationViewModel.cs
│       │   │   ├── TechnicianRoutePlannerViewModel.cs
│       │   │   ├── CapacityPlanningViewModel.cs
│       │   │   ├── SLAMonitoringViewModel.cs
│       │   │   └── WizardPages/
│       │   │       ├── AppointmentRequestWizardPageViewModel.cs
│       │   │       ├── AppointmentProposalWizardPageViewModel.cs
│       │   │       ├── AppointmentConfirmationWizardPageViewModel.cs
│       │   └── Views/
│       │       ├── AppointmentRequestView.xaml
│       │       ├── AppointmentSlotSelectionView.xaml
│       │       ├── RouteOptimizationView.xaml
│       │       ├── CapacityPlanningView.xaml
│       │       ├── SLAMonitoringView.xaml
│       │       └── Wizard/
│       │           ├── AppointmentBookingWizardView.xaml
│       │
│       └── RouteOptimization/                 [NEU]
│           ├── RouteOptimizationAppModuleController.cs
│           ├── RouteOptimizationView.xaml
│           └── ViewModels/
│               ├── TechnicianRoutePlannerViewModel.cs
│               ├── ZoneManagementViewModel.cs
│               └── RouteMapViewModel.cs
│
└── backend/
    ├── Centron.BL/
    │   ├── Sales/CustomerAssets/              [ERWEITERN]
    │   │   ├── AssetBL.cs (erweitern um 1.1-1.5)
    │   │   ├── AssetArticleBL.cs
    │   │   ├── AssetPatchBL.cs                [NEU]
    │   │   ├── AssetSnmpMonitoringBL.cs       [NEU]
    │   │   ├── AssetLicenseBL.cs              [NEU]
    │   │   └── AssetDepreciationBL.cs         [NEU]
    │   │
    │   ├── AppointmentRequests/               [ERWEITERN]
    │   │   ├── AppointmentRequestBL.cs (erweitern um 17.1)
    │   │   ├── AppointmentProposalBL.cs       [NEU]
    │   │   ├── TechnicianRouteBL.cs           [NEU]
    │   │   ├── CapacityPlanningBL.cs          [NEU]
    │   │   └── SLAAgreementBL.cs              [NEU]
    │   │
    │   └── MyCentron/Schedulings/             [ERWEITERN]
    │       ├── SchedulingBL.cs (erweitern um Kapazität)
    │       ├── ScheduleOptimizationBL.cs      [NEU]
    │       └── ScheduleAlertingBL.cs          [NEU]
    │
    ├── Centron.Interfaces/
    │   ├── Administration/
    │   │   ├── AssetManagement/               [NEU]
    │   │   │   ├── IAssetManagementLogic.cs
    │   │   │   ├── IAssetPatchLogic.cs
    │   │   │   ├── IAssetSnmpMonitoringLogic.cs
    │   │   │   └── IAssetLicenseLogic.cs
    │   │   │
    │   │   └── Scheduling/                    [NEU]
    │   │       ├── IAppointmentSchedulingLogic.cs
    │   │       ├── ITechnicianRoutingLogic.cs
    │   │       ├── ICapacityPlanningLogic.cs
    │   │       └── ISLAAgreementLogic.cs
    │   │
    │   └── WebServiceBL/                      [ERWEITERN]
    │       ├── AssetManagementWebServiceBL.cs [NEU]
    │       └── SchedulingWebServiceBL.cs      [NEU]
    │
    ├── Centron.Entities/Entities/
    │   ├── Administration/MasterData/
    │   │   ├── AssetCondition.cs (existiert)
    │   │   ├── AssetManagementDevice.cs       [NEU - optional]
    │   │   └── AssetManagementPatch.cs        [NEU - optional]
    │   │
    │   ├── AppointmentRequests/               [EXISTIERT]
    │   │   ├── AppointmentRequest.cs
    │   │   ├── AppointmentProposal.cs
    │   │   └── AppointmentRequestFilter.cs
    │   │
    │   ├── Sales/CustomerAssets/              [EXISTIERT]
    │   │   ├── Asset.cs
    │   │   └── AssetBase.cs
    │   │
    │   └── MyCentron/Schedulings/             [EXISTIERT]
    │       ├── Scheduling.cs
    │       └── SchedulingFilter.cs
    │
    └── Centron.DAO/
        └── Repositories/
            ├── Sales/Customers/Assets/       [EXISTIERT]
            └── AppointmentRequests/          [EXISTIERT]

🏗️ ViewModel-Template für Asset Management

// File: src/centron/Centron.WPF.UI/Modules/Administration/AssetManagement/ViewModels/AssetInventoryViewModel.cs
// UTF-8 with BOM encoding required!

using Centron.Interfaces.BL;
using Centron.Common;
using Prism.Mvvm;
using Prism.Commands;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using DevExpress.Mvvm;
using Centron.Data.Entities.Sales.CustomerAssets;
using Centron.WPF.UI.Common;
using Centron.WPF.UI.Services.Logics;

namespace Centron.WPF.UI.Modules.Administration.AssetManagement.ViewModels
{
    /// <summary>
    /// ViewModel für Asset-Inventarverwaltung (UC 1.1 - 1.5)
    ///
    /// Use Cases:
    /// - UC 1.1: Neue IT-Geräte erfassen
    /// - UC 1.2: Geräte-Details anzeigen und bearbeiten
    /// - UC 1.3: Geräte-Bestände nach Abteilung verwalten
    /// - UC 1.4: Geräte-Lebenszyklusmanagement
    /// - UC 1.5: Batch-Import von Geräteinventaren
    ///
    /// ViewModel Properties → Use Cases:
    /// DeviceName → UC 1.1 TextEdit
    /// SelectedDeviceType → UC 1.1 ComboBoxEdit
    /// SelectedDepartmentI3D → UC 1.3 Filter
    /// SelectedStatus → UC 1.4 Lifecycle
    /// </summary>
    public class AssetInventoryViewModel : BindableBase
    {
        #region Dependencies
        private readonly IAssetManagementLogic _assetLogic;
        private readonly IMessageDialogService _messageService;
        #endregion

        #region Fields
        private string _deviceName;
        private int _selectedDeviceType;
        private string _manufacturer;
        private string _modelNumber;
        private string _serialNumber;
        private string _assetTag;
        private int _selectedDepartmentI3D;
        private int _selectedLocationI3D;
        private int _selectedStatus;
        private bool _isLoading;
        #endregion

        #region Properties

        /// <summary>
        /// UC 1.1: Gerätname eingeben
        /// </summary>
        public string DeviceName
        {
            get => _deviceName;
            set => this.SetProperty(ref _deviceName, value, nameof(DeviceName));
        }

        /// <summary>
        /// UC 1.1: Gerättyp auswählen
        /// </summary>
        public int SelectedDeviceType
        {
            get => _selectedDeviceType;
            set => this.SetProperty(ref _selectedDeviceType, value, nameof(SelectedDeviceType));
        }

        /// <summary>
        /// UC 1.1: Hersteller angeben
        /// </summary>
        public string Manufacturer
        {
            get => _manufacturer;
            set => this.SetProperty(ref _manufacturer, value, nameof(Manufacturer));
        }

        /// <summary>
        /// UC 1.1: Modellnummer eingeben
        /// </summary>
        public string ModelNumber
        {
            get => _modelNumber;
            set => this.SetProperty(ref _modelNumber, value, nameof(ModelNumber));
        }

        /// <summary>
        /// UC 1.1: Seriennummer (eindeutig!)
        /// </summary>
        public string SerialNumber
        {
            get => _serialNumber;
            set => this.SetProperty(ref _serialNumber, value, nameof(SerialNumber));
        }

        /// <summary>
        /// UC 1.1: Asset-Tag (auto-generated)
        /// </summary>
        public string AssetTag
        {
            get => _assetTag;
            set => this.SetProperty(ref _assetTag, value, nameof(AssetTag));
        }

        /// <summary>
        /// UC 1.3: Abteilung filtern
        /// </summary>
        public int SelectedDepartmentI3D
        {
            get => _selectedDepartmentI3D;
            set => this.SetProperty(ref _selectedDepartmentI3D, value, nameof(SelectedDepartmentI3D));
        }

        /// <summary>
        /// UC 1.1: Standort zuordnen
        /// </summary>
        public int SelectedLocationI3D
        {
            get => _selectedLocationI3D;
            set => this.SetProperty(ref _selectedLocationI3D, value, nameof(SelectedLocationI3D));
        }

        /// <summary>
        /// UC 1.4: Status / Lifecycle Stage
        /// </summary>
        public int SelectedStatus
        {
            get => _selectedStatus;
            set => this.SetProperty(ref _selectedStatus, value, nameof(SelectedStatus));
        }

        /// <summary>
        /// UI-Status: Loading indicator
        /// </summary>
        public bool IsLoading
        {
            get => _isLoading;
            set => this.SetProperty(ref _isLoading, value, nameof(IsLoading));
        }

        /// <summary>
        /// UC 1.3: Assets-Grid Datenquelle
        /// </summary>
        public ObservableCollection<AssetListItemViewModel> Assets { get; set; }

        /// <summary>
        /// UC 1.3: Abteilungs-Dropdown Datenquelle
        /// </summary>
        public ObservableCollection<DepartmentViewModel> Departments { get; set; }

        /// <summary>
        /// UC 1.1: Locations-Dropdown
        /// </summary>
        public ObservableCollection<LocationViewModel> Locations { get; set; }

        #endregion

        #region Commands

        /// <summary>
        /// UC 1.1: Neues Gerät erstellen
        /// </summary>
        public ICommand CreateNewAssetCommand { get; private set; }

        /// <summary>
        /// UC 1.1: Asset-Tag auto-generieren
        /// </summary>
        public ICommand GenerateAssetTagCommand { get; private set; }

        /// <summary>
        /// UC 1.1: Gerät speichern
        /// </summary>
        public ICommand SaveAssetCommand { get; private set; }

        /// <summary>
        /// UC 1.5: Batch-Import starten
        /// </summary>
        public ICommand OpenBatchImportCommand { get; private set; }

        /// <summary>
        /// UC 1.3: Filter anwenden
        /// </summary>
        public ICommand FilterCommand { get; private set; }

        #endregion

        #region Constructor
        public AssetInventoryViewModel()
        {
            // Dependency Injection (from ClassContainer or direct DI)
            _assetLogic = ClassContainer.Instance
                .GetInstance<IAssetManagementLogic>();

            _messageService = ClassContainer.Instance
                .GetInstance<IMessageDialogService>();

            // Collections
            this.Assets = new ObservableCollection<AssetListItemViewModel>();
            this.Departments = new ObservableCollection<DepartmentViewModel>();
            this.Locations = new ObservableCollection<LocationViewModel>();

            // Initialize Commands
            this.CreateNewAssetCommand = new DelegateCommand(this.CreateNewAsset);
            this.GenerateAssetTagCommand = new DelegateCommand(this.GenerateAssetTag);
            this.SaveAssetCommand = new DelegateCommand(this.SaveAsset, this.CanSaveAsset);
            this.OpenBatchImportCommand = new DelegateCommand(this.OpenBatchImport);
            this.FilterCommand = new DelegateCommand(this.ApplyFilter);

            // Load initial data
            this.LoadInitialData();
        }
        #endregion

        #region Methods

        /// <summary>
        /// UC 1.1: Neues Asset-Objekt initialisieren
        /// </summary>
        private void CreateNewAsset()
        {
            // Clear form for new entry
            this.DeviceName = string.Empty;
            this.Manufacturer = string.Empty;
            this.ModelNumber = string.Empty;
            this.SerialNumber = string.Empty;
            this.SelectedDeviceType = 0;
            this.SelectedLocationI3D = 0;
            this.SelectedStatus = 1; // Active

            this._messageService.ShowMessage("Neues Gerät bereit zur Eingabe", "Info");
        }

        /// <summary>
        /// UC 1.1: Asset-Tag auto-generieren
        /// </summary>
        private void GenerateAssetTag()
        {
            // Pattern: AST-YYYYMMDD-XXX
            string prefix = "AST-" + DateTime.Now.ToString("yyyyMMdd");
            string randomPart = new Random().Next(100, 999).ToString();
            this.AssetTag = $"{prefix}-{randomPart}";
        }

        /// <summary>
        /// UC 1.1: Gerät speichern
        /// </summary>
        private void SaveAsset()
        {
            try
            {
                this.IsLoading = true;

                // Validation
                if (string.IsNullOrWhiteSpace(this.DeviceName))
                {
                    this._messageService.ShowError("Gerätname erforderlich", "Validierung");
                    return;
                }

                if (string.IsNullOrWhiteSpace(this.SerialNumber))
                {
                    this._messageService.ShowError("Seriennummer erforderlich", "Validierung");
                    return;
                }

                // Call BL
                var result = this._assetLogic.CreateNewAsset(new CreateAssetRequest
                {
                    DeviceName = this.DeviceName,
                    DeviceType = (AssetDeviceType)this.SelectedDeviceType,
                    Manufacturer = this.Manufacturer,
                    ModelNumber = this.ModelNumber,
                    SerialNumber = this.SerialNumber,
                    AssetTag = this.AssetTag,
                    DepartmentI3D = this.SelectedDepartmentI3D,
                    LocationI3D = this.SelectedLocationI3D,
                    Status = (AssetStatus)this.SelectedStatus
                });

                if (result.Status == ResultStatus.Success)
                {
                    this._messageService.ShowMessage($"Gerät gespeichert: {result.Data.I3D}", "Erfolg");
                    this.CreateNewAsset(); // Reset form
                    this.ApplyFilter(); // Refresh list
                }
                else
                {
                    this._messageService.ShowError(result.Error, "Fehler");
                }
            }
            catch (Exception ex)
            {
                this._messageService.ShowError($"Fehler beim Speichern: {ex.Message}", "Fehler");
            }
            finally
            {
                this.IsLoading = false;
            }
        }

        /// <summary>
        /// UC 1.1: CanExecute für SaveCommand
        /// </summary>
        private bool CanSaveAsset()
        {
            return !string.IsNullOrWhiteSpace(this.DeviceName)
                && !string.IsNullOrWhiteSpace(this.SerialNumber);
        }

        /// <summary>
        /// UC 1.5: Batch-Import öffnen
        /// </summary>
        private void OpenBatchImport()
        {
            // Open wizard view
            var wizard = new AssetBatchImportWizardView();
            // ... present wizard dialog
        }

        /// <summary>
        /// UC 1.3: Filter anwenden
        /// </summary>
        private async void ApplyFilter()
        {
            try
            {
                this.IsLoading = true;

                var result = await this._assetLogic.GetAssetsByDepartmentAsync(
                    this.SelectedDepartmentI3D,
                    (AssetStatus?)this.SelectedStatus
                );

                if (result.Status == ResultStatus.Success)
                {
                    this.Assets.Clear();
                    foreach (var asset in result.Data)
                    {
                        this.Assets.Add(new AssetListItemViewModel
                        {
                            I3D = asset.I3D,
                            DeviceName = asset.DeviceName,
                            AssetTag = asset.AssetTag,
                            Status = asset.Status.ToString()
                        });
                    }
                }
            }
            finally
            {
                this.IsLoading = false;
            }
        }

        /// <summary>
        /// Load initial data (departments, locations, etc.)
        /// </summary>
        private async void LoadInitialData()
        {
            try
            {
                // Load departments
                var deptResult = await this._assetLogic.GetDepartmentsAsync();
                if (deptResult.Status == ResultStatus.Success)
                {
                    this.Departments.Clear();
                    foreach (var dept in deptResult.Data)
                    {
                        this.Departments.Add(new DepartmentViewModel { I3D = dept.I3D, Name = dept.Name });
                    }
                }
            }
            catch (Exception ex)
            {
                this._messageService.ShowError($"Fehler beim Laden: {ex.Message}", "Fehler");
            }
        }

        #endregion
    }
}

🏗️ ViewModel-Template für Scheduling

// File: src/centron/Centron.WPF.UI/Modules/Calendar/AppointmentScheduling/ViewModels/AppointmentRequestViewModel.cs

using Prism.Mvvm;
using Prism.Commands;
using System;
using System.Collections.ObjectModel;
using Centron.Interfaces.BL;
using Centron.Common;

namespace Centron.WPF.UI.Modules.Calendar.AppointmentScheduling.ViewModels
{
    /// <summary>
    /// ViewModel für Terminanfragen (UC 17.1)
    /// Use Cases:
    /// - UC 17.1.1: Kunde fordert Termin an
    /// - UC 17.1.2: System schlägt verfügbare Slots vor
    /// - UC 17.1.3: Kunde wählt Termin-Slot
    /// - UC 17.1.4: Terminbestätigung an Kunde
    /// - UC 17.1.5: Termine verschieben/absagen
    /// </summary>
    public class AppointmentRequestViewModel : BindableBase
    {
        #region Dependencies
        private readonly IAppointmentSchedulingLogic _appointmentLogic;
        #endregion

        #region Fields
        private int _ticketI3D;
        private int _selectedServiceType;
        private DateTime _preferredDateFrom;
        private DateTime _preferredDateTo;
        private int _estimatedDurationInHours;
        private string _locationAddress;
        private string _customerNotes;
        private string _appointmentStatus;
        #endregion

        #region Properties

        /// <summary>
        /// UC 17.1.1: Hidden - aus Ticket Context
        /// </summary>
        public int TicketI3D
        {
            get => _ticketI3D;
            set => this.SetProperty(ref _ticketI3D, value, nameof(TicketI3D));
        }

        /// <summary>
        /// UC 17.1.1: Service-Typ (Installation/Wartung/Reparatur)
        /// </summary>
        public int SelectedServiceType
        {
            get => _selectedServiceType;
            set => this.SetProperty(ref _selectedServiceType, value, nameof(SelectedServiceType));
        }

        /// <summary>
        /// UC 17.1.1: Gewünschtes Startdatum
        /// </summary>
        public DateTime PreferredDateFrom
        {
            get => _preferredDateFrom;
            set => this.SetProperty(ref _preferredDateFrom, value, nameof(PreferredDateFrom));
        }

        /// <summary>
        /// UC 17.1.1: Gewünschtes Enddatum
        /// </summary>
        public DateTime PreferredDateTo
        {
            get => _preferredDateTo;
            set => this.SetProperty(ref _preferredDateTo, value, nameof(PreferredDateTo));
        }

        /// <summary>
        /// UC 17.1.1: Geschätzte Dauer in Stunden
        /// </summary>
        public int EstimatedDurationInHours
        {
            get => _estimatedDurationInHours;
            set => this.SetProperty(ref _estimatedDurationInHours, value, nameof(EstimatedDurationInHours));
        }

        /// <summary>
        /// UC 17.1.1: Ort des Termins
        /// </summary>
        public string LocationAddress
        {
            get => _locationAddress;
            set => this.SetProperty(ref _locationAddress, value, nameof(LocationAddress));
        }

        /// <summary>
        /// UC 17.1.1: Kundennotizen
        /// </summary>
        public string CustomerNotes
        {
            get => _customerNotes;
            set => this.SetProperty(ref _customerNotes, value, nameof(CustomerNotes));
        }

        /// <summary>
        /// UC 17.1.2: Verfügbare Termine
        /// </summary>
        public ObservableCollection<AppointmentSlotViewModel> ProposedSlots { get; set; }

        /// <summary>
        /// Aktueller Status der Anfrage
        /// </summary>
        public string AppointmentStatus
        {
            get => _appointmentStatus;
            set => this.SetProperty(ref _appointmentStatus, value, nameof(AppointmentStatus));
        }

        #endregion

        #region Commands

        public ICommand RequestAppointmentCommand { get; private set; }
        public ICommand SelectSlotCommand { get; private set; }
        public ICommand ConfirmAppointmentCommand { get; private set; }

        #endregion

        #region Constructor
        public AppointmentRequestViewModel()
        {
            this._appointmentLogic = ClassContainer.Instance
                .GetInstance<IAppointmentSchedulingLogic>();

            this.ProposedSlots = new ObservableCollection<AppointmentSlotViewModel>();

            // Initialize Commands
            this.RequestAppointmentCommand = new DelegateCommand(this.RequestAppointment);
            this.SelectSlotCommand = new DelegateCommand<AppointmentSlotViewModel>(this.SelectSlot);
            this.ConfirmAppointmentCommand = new DelegateCommand(this.ConfirmAppointment);

            // Default values
            this.PreferredDateFrom = DateTime.Now;
            this.PreferredDateTo = DateTime.Now.AddDays(7);
            this.EstimatedDurationInHours = 2;
            this.AppointmentStatus = "Bereit für Anfrage";
        }
        #endregion

        #region Methods

        /// <summary>
        /// UC 17.1.1: Anfrage einreichen und Slots vorschlagen
        /// </summary>
        private async void RequestAppointment()
        {
            try
            {
                // UC 17.1.2: Get available slots
                var result = await this._appointmentLogic.GetAvailableAppointmentSlotsAsync(
                    this.TicketI3D,
                    this.PreferredDateFrom,
                    this.PreferredDateTo,
                    this.EstimatedDurationInHours
                );

                if (result.Status == ResultStatus.Success)
                {
                    this.ProposedSlots.Clear();
                    foreach (var slot in result.Data)
                    {
                        this.ProposedSlots.Add(new AppointmentSlotViewModel
                        {
                            SlotDateTime = slot.StartTime,
                            TechnicianName = slot.TechnicianName,
                            TravelTime = $"{slot.TravelTimeMinutes} Min",
                            ProposalI3D = slot.I3D
                        });
                    }

                    this.AppointmentStatus = $"✓ {this.ProposedSlots.Count} Slots verfügbar";
                }
            }
            catch (Exception ex)
            {
                this.AppointmentStatus = $"✗ Fehler: {ex.Message}";
            }
        }

        /// <summary>
        /// UC 17.1.3: Slot auswählen
        /// </summary>
        private void SelectSlot(AppointmentSlotViewModel slot)
        {
            if (slot != null)
            {
                this.AppointmentStatus = $"Slot ausgewählt: {slot.SlotDateTime:dd.MM.yyyy HH:mm}";
                // Store selected slot for confirmation
            }
        }

        /// <summary>
        /// UC 17.1.4: Termin bestätigen
        /// </summary>
        private async void ConfirmAppointment()
        {
            try
            {
                // TODO: Call BookAppointment logic
                this.AppointmentStatus = "✓ Termin bestätigt!";
            }
            catch (Exception ex)
            {
                this.AppointmentStatus = $"✗ Fehler: {ex.Message}";
            }
        }

        #endregion
    }
}

📋 REST API Endpoints Template

// File: src/webservice/Centron.WebServices.Core/RestService/CentronRestService.cs
// Add these methods to existing service:

[WebInvoke(Method = "POST", UriTemplate = "AssetManagement/GetDeviceDetails")]
[Authenticate]
public Response<AssetDetailsDTO> GetAssetDeviceDetails(Request<int> request)
{
    // UC 1.2: Get asset details
    try
    {
        var result = this.classContainer
            .WithInstance((IAssetManagementLogic logic) => logic.GetAssetDetails(request.Data))
            .ThrowIfError();

        return Response.Success(result.Data);
    }
    catch (Exception ex)
    {
        return Response.Error(ex.Message);
    }
}

[WebInvoke(Method = "POST", UriTemplate = "AssetManagement/ImportBatch")]
[Authenticate]
public Response<BatchImportResultDTO> ImportAssetBatch(Request<AssetBatchImportRequest> request)
{
    // UC 1.5: Batch import
    try
    {
        var result = this.classContainer
            .WithInstance((IAssetManagementLogic logic) => logic.ImportAssetsBatch(
                request.Data.FileContent,
                request.Data.Format))
            .ThrowIfError();

        return Response.Success(result.Data);
    }
    catch (Exception ex)
    {
        return Response.Error(ex.Message);
    }
}

[WebInvoke(Method = "POST", UriTemplate = "Scheduling/GetAvailableAppointments")]
[Authenticate]
public Response<List<AppointmentSlotDTO>> GetAvailableAppointments(Request<AppointmentRequestFilter> request)
{
    // UC 17.1.2: Get available slots
    try
    {
        var result = this.classContainer
            .WithInstance((IAppointmentSchedulingLogic logic) => logic.GetAvailableAppointmentSlots(
                request.Data.TicketI3D,
                request.Data.PreferredDateFrom,
                request.Data.PreferredDateTo,
                request.Data.DurationHours))
            .ThrowIfError();

        return Response.Success(result.Data);
    }
    catch (Exception ex)
    {
        return Response.Error(ex.Message);
    }
}

[WebInvoke(Method = "POST", UriTemplate = "Scheduling/BookAppointment")]
[Authenticate]
public Response<AppointmentConfirmationDTO> BookAppointment(Request<int> request)
{
    // UC 17.1.3: Book appointment
    try
    {
        var result = this.classContainer
            .WithInstance((IAppointmentSchedulingLogic logic) => logic.BookAppointment(request.Data))
            .ThrowIfError();

        return Response.Success(result.Data);
    }
    catch (Exception ex)
    {
        return Response.Error(ex.Message);
    }
}

[WebInvoke(Method = "POST", UriTemplate = "Scheduling/OptimizeRoute")]
[Authenticate]
public Response<OptimizedRouteDTO> OptimizeRoute(Request<RouteOptimizationRequest> request)
{
    // UC 17.2: Route optimization
    try
    {
        var result = this.classContainer
            .WithInstance((ITechnicianRoutingLogic logic) => logic.OptimizeRoute(
                request.Data.TechnicianI3D,
                request.Data.Date))
            .ThrowIfError();

        return Response.Success(result.Data);
    }
    catch (Exception ex)
    {
        return Response.Error(ex.Message);
    }
}

[WebInvoke(Method = "POST", UriTemplate = "Scheduling/GetSLAStatus")]
[Authenticate]
public Response<SLAStatusDTO> GetSLAStatus(Request<int> request)
{
    // UC 17.4: Get SLA status
    try
    {
        var result = this.classContainer
            .WithInstance((ISLAAgreementLogic logic) => logic.GetSLAStatus(request.Data))
            .ThrowIfError();

        return Response.Success(result.Data);
    }
    catch (Exception ex)
    {
        return Response.Error(ex.Message);
    }
}

🧪 Unit Test Template

// File: tests/Centron.Tests/BL/AssetManagementLogicTests.cs

[TestFixture]
public class AssetManagementLogicTests
{
    private AssetManagementLogic _logic;
    private Mock<IAssetBL> _assetBLMock;
    private Mock<DAOSession> _sessionMock;

    [SetUp]
    public void Setup()
    {
        _sessionMock = new Mock<DAOSession>();
        _assetBLMock = new Mock<IAssetBL>();

        _logic = new AssetManagementLogic(_sessionMock.Object, _assetBLMock.Object);
    }

    /// <summary>
    /// UC 1.1: Test creating new asset
    /// </summary>
    [Test]
    public void CreateNewAsset_WithValidData_ReturnsSuccess()
    {
        // Arrange
        var request = new CreateAssetRequest
        {
            DeviceName = "Test-Laptop",
            SerialNumber = "SN-123456",
            Manufacturer = "Dell"
        };

        var expectedAsset = new Asset
        {
            I3D = 1,
            DeviceName = "Test-Laptop",
            SerialNumber = "SN-123456"
        };

        _assetBLMock
            .Setup(bl => bl.CreateNewAsset(It.IsAny<CreateAssetRequest>()))
            .Returns(expectedAsset);

        // Act
        var result = _logic.CreateNewAsset(request);

        // Assert
        Assert.That(result.Status, Is.EqualTo(ResultStatus.Success));
        Assert.That(result.Data.I3D, Is.EqualTo(1));
        Assert.That(result.Data.DeviceName, Is.EqualTo("Test-Laptop"));
    }

    /// <summary>
    /// UC 1.1: Test validation - missing serial number
    /// </summary>
    [Test]
    public void CreateNewAsset_WithoutSerialNumber_ReturnsError()
    {
        // Arrange
        var request = new CreateAssetRequest
        {
            DeviceName = "Test-Laptop",
            SerialNumber = "" // Missing!
        };

        // Act
        var result = _logic.CreateNewAsset(request);

        // Assert
        Assert.That(result.Status, Is.EqualTo(ResultStatus.Error));
        Assert.That(result.Error, Contains.Substring("Seriennummer"));
    }
}

📦 Deployment Checklist

  • Code-Review durchführen
  • Unit Tests schreiben und ausführen
  • Integration Tests mit echtem DB durchführen
  • Code Analysis (SonarQube/StyleCop) ausführen
  • Performance Tests durchführen
  • UI-Tests durchführen (manual)
  • Ribbon-Integration testen
  • Localization überprüfen (Deutsche Strings)
  • Documentation finalisieren
  • Release Notes schreiben
  • Module Registration in main app durchführen
  • Build Pipeline anpassen

🔗 Nächste Schritte

  1. Code-Struktur erstellen (Verzeichnisse, leere Dateien)
  2. Templates ausfüllen (Controller, ViewModel, View, BL)
  3. Database-Access via DAO implementieren
  4. UI-Binding in XAML durchführen
  5. Commands und Event-Handler implementieren
  6. Unit Tests schreiben
  7. Integration Tests durchführen
  8. User Acceptance Testing organisieren
  9. Deployment durchführen

Status: Implementierungs-Leitfaden abgeschlossen | Templates & Checklisten bereit