Ergebnisse 1-3 + Typs Debug

This commit is contained in:
2026-02-17 09:57:10 +01:00
parent 2e6a75f93c
commit f6bdbab366
78 changed files with 139322 additions and 5 deletions

View File

@@ -0,0 +1,997 @@
# 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
```csharp
// 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
```csharp
// 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
```csharp
// 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
```csharp
// 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