# 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
{
///
/// 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
///
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
///
/// UC 1.1: GerΓ€tname eingeben
///
public string DeviceName
{
get => _deviceName;
set => this.SetProperty(ref _deviceName, value, nameof(DeviceName));
}
///
/// UC 1.1: GerΓ€ttyp auswΓ€hlen
///
public int SelectedDeviceType
{
get => _selectedDeviceType;
set => this.SetProperty(ref _selectedDeviceType, value, nameof(SelectedDeviceType));
}
///
/// UC 1.1: Hersteller angeben
///
public string Manufacturer
{
get => _manufacturer;
set => this.SetProperty(ref _manufacturer, value, nameof(Manufacturer));
}
///
/// UC 1.1: Modellnummer eingeben
///
public string ModelNumber
{
get => _modelNumber;
set => this.SetProperty(ref _modelNumber, value, nameof(ModelNumber));
}
///
/// UC 1.1: Seriennummer (eindeutig!)
///
public string SerialNumber
{
get => _serialNumber;
set => this.SetProperty(ref _serialNumber, value, nameof(SerialNumber));
}
///
/// UC 1.1: Asset-Tag (auto-generated)
///
public string AssetTag
{
get => _assetTag;
set => this.SetProperty(ref _assetTag, value, nameof(AssetTag));
}
///
/// UC 1.3: Abteilung filtern
///
public int SelectedDepartmentI3D
{
get => _selectedDepartmentI3D;
set => this.SetProperty(ref _selectedDepartmentI3D, value, nameof(SelectedDepartmentI3D));
}
///
/// UC 1.1: Standort zuordnen
///
public int SelectedLocationI3D
{
get => _selectedLocationI3D;
set => this.SetProperty(ref _selectedLocationI3D, value, nameof(SelectedLocationI3D));
}
///
/// UC 1.4: Status / Lifecycle Stage
///
public int SelectedStatus
{
get => _selectedStatus;
set => this.SetProperty(ref _selectedStatus, value, nameof(SelectedStatus));
}
///
/// UI-Status: Loading indicator
///
public bool IsLoading
{
get => _isLoading;
set => this.SetProperty(ref _isLoading, value, nameof(IsLoading));
}
///
/// UC 1.3: Assets-Grid Datenquelle
///
public ObservableCollection Assets { get; set; }
///
/// UC 1.3: Abteilungs-Dropdown Datenquelle
///
public ObservableCollection Departments { get; set; }
///
/// UC 1.1: Locations-Dropdown
///
public ObservableCollection Locations { get; set; }
#endregion
#region Commands
///
/// UC 1.1: Neues GerΓ€t erstellen
///
public ICommand CreateNewAssetCommand { get; private set; }
///
/// UC 1.1: Asset-Tag auto-generieren
///
public ICommand GenerateAssetTagCommand { get; private set; }
///
/// UC 1.1: GerΓ€t speichern
///
public ICommand SaveAssetCommand { get; private set; }
///
/// UC 1.5: Batch-Import starten
///
public ICommand OpenBatchImportCommand { get; private set; }
///
/// UC 1.3: Filter anwenden
///
public ICommand FilterCommand { get; private set; }
#endregion
#region Constructor
public AssetInventoryViewModel()
{
// Dependency Injection (from ClassContainer or direct DI)
_assetLogic = ClassContainer.Instance
.GetInstance();
_messageService = ClassContainer.Instance
.GetInstance();
// Collections
this.Assets = new ObservableCollection();
this.Departments = new ObservableCollection();
this.Locations = new ObservableCollection();
// 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
///
/// UC 1.1: Neues Asset-Objekt initialisieren
///
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");
}
///
/// UC 1.1: Asset-Tag auto-generieren
///
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}";
}
///
/// UC 1.1: GerΓ€t speichern
///
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;
}
}
///
/// UC 1.1: CanExecute fΓΌr SaveCommand
///
private bool CanSaveAsset()
{
return !string.IsNullOrWhiteSpace(this.DeviceName)
&& !string.IsNullOrWhiteSpace(this.SerialNumber);
}
///
/// UC 1.5: Batch-Import ΓΆffnen
///
private void OpenBatchImport()
{
// Open wizard view
var wizard = new AssetBatchImportWizardView();
// ... present wizard dialog
}
///
/// UC 1.3: Filter anwenden
///
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;
}
}
///
/// Load initial data (departments, locations, etc.)
///
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
{
///
/// 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
///
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
///
/// UC 17.1.1: Hidden - aus Ticket Context
///
public int TicketI3D
{
get => _ticketI3D;
set => this.SetProperty(ref _ticketI3D, value, nameof(TicketI3D));
}
///
/// UC 17.1.1: Service-Typ (Installation/Wartung/Reparatur)
///
public int SelectedServiceType
{
get => _selectedServiceType;
set => this.SetProperty(ref _selectedServiceType, value, nameof(SelectedServiceType));
}
///
/// UC 17.1.1: GewΓΌnschtes Startdatum
///
public DateTime PreferredDateFrom
{
get => _preferredDateFrom;
set => this.SetProperty(ref _preferredDateFrom, value, nameof(PreferredDateFrom));
}
///
/// UC 17.1.1: GewΓΌnschtes Enddatum
///
public DateTime PreferredDateTo
{
get => _preferredDateTo;
set => this.SetProperty(ref _preferredDateTo, value, nameof(PreferredDateTo));
}
///
/// UC 17.1.1: GeschΓ€tzte Dauer in Stunden
///
public int EstimatedDurationInHours
{
get => _estimatedDurationInHours;
set => this.SetProperty(ref _estimatedDurationInHours, value, nameof(EstimatedDurationInHours));
}
///
/// UC 17.1.1: Ort des Termins
///
public string LocationAddress
{
get => _locationAddress;
set => this.SetProperty(ref _locationAddress, value, nameof(LocationAddress));
}
///
/// UC 17.1.1: Kundennotizen
///
public string CustomerNotes
{
get => _customerNotes;
set => this.SetProperty(ref _customerNotes, value, nameof(CustomerNotes));
}
///
/// UC 17.1.2: VerfΓΌgbare Termine
///
public ObservableCollection ProposedSlots { get; set; }
///
/// Aktueller Status der Anfrage
///
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();
this.ProposedSlots = new ObservableCollection();
// Initialize Commands
this.RequestAppointmentCommand = new DelegateCommand(this.RequestAppointment);
this.SelectSlotCommand = new DelegateCommand(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
///
/// UC 17.1.1: Anfrage einreichen und Slots vorschlagen
///
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}";
}
}
///
/// UC 17.1.3: Slot auswΓ€hlen
///
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
}
}
///
/// UC 17.1.4: Termin bestΓ€tigen
///
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 GetAssetDeviceDetails(Request 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 ImportAssetBatch(Request 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> GetAvailableAppointments(Request 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 BookAppointment(Request 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 OptimizeRoute(Request 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 GetSLAStatus(Request 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 _assetBLMock;
private Mock _sessionMock;
[SetUp]
public void Setup()
{
_sessionMock = new Mock();
_assetBLMock = new Mock();
_logic = new AssetManagementLogic(_sessionMock.Object, _assetBLMock.Object);
}
///
/// UC 1.1: Test creating new asset
///
[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()))
.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"));
}
///
/// UC 1.1: Test validation - missing serial number
///
[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