diff --git a/DocumentOperator.API/Controllers/PdfValidationController.cs b/DocumentOperator.API/Controllers/PdfValidationController.cs new file mode 100644 index 0000000..1b4a6fd --- /dev/null +++ b/DocumentOperator.API/Controllers/PdfValidationController.cs @@ -0,0 +1,79 @@ +using DocumentOperator.Application.Common.DTOs; +using DocumentOperator.Application.ValidatePdf.Queries; +using MediatR; +using Microsoft.AspNetCore.Mvc; + +namespace DocumentOperator.API.Controllers; + +/// +/// PDF validation operations +/// +[ApiController] +[Route("api/pdf/validation")] +[Produces("application/json")] +public class PdfValidationController(IMediator Mediator) : ControllerBase +{ + /// + /// Validates a PDF document and returns metadata (multipart/form-data) + /// + /// PDF file to validate + /// Cancellation token + /// PDF metadata (page count, file size, PDF version, attachments) + /// PDF is valid, metadata returned + /// Invalid PDF or file format + /// Internal server error during validation + [HttpPost("validate")] + [Consumes("multipart/form-data")] + [ProducesResponseType(typeof(PdfValidationResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)] + public async Task ValidateFromFile( + IFormFile file, + CancellationToken cancellationToken) + { + if (file == null || file.Length == 0) + { + return BadRequest(new ProblemDetails + { + Title = "Invalid file", + Detail = "File is required and cannot be empty", + Status = StatusCodes.Status400BadRequest + }); + } + + // Convert IFormFile to byte array + using var memoryStream = new MemoryStream(); + await file.CopyToAsync(memoryStream, cancellationToken); + byte[] pdfBytes = memoryStream.ToArray(); + + // Direct pass-through to MediatR + var query = new ValidatePdfQuery { PdfBytes = pdfBytes }; + var result = await Mediator.Send(query, cancellationToken); + + return Ok(result); + } + + /// + /// Validates a PDF document and returns metadata (Base64 JSON) + /// + /// PDF as Base64 string + /// Cancellation token + /// PDF metadata (page count, file size, PDF version, attachments) + /// PDF is valid, metadata returned + /// Invalid PDF or Base64 format + /// Internal server error during validation + [HttpPost("validate")] + [Consumes("application/json")] + [ProducesResponseType(typeof(PdfValidationResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)] + public async Task ValidateFromBase64( + [FromBody] ValidatePdfQuery query, + CancellationToken cancellationToken) + { + // Direct pass-through to MediatR + var result = await Mediator.Send(query, cancellationToken); + + return Ok(result); + } +} diff --git a/DocumentOperator.API/Controllers/SwissQrCodeController.cs b/DocumentOperator.API/Controllers/SwissQrCodeController.cs new file mode 100644 index 0000000..43a4630 --- /dev/null +++ b/DocumentOperator.API/Controllers/SwissQrCodeController.cs @@ -0,0 +1,94 @@ +using DocumentOperator.Application.Common.DTOs; +using DocumentOperator.Application.SwissQrCode.Queries; +using MediatR; +using Microsoft.AspNetCore.Mvc; + +namespace DocumentOperator.API.Controllers; + +/// +/// Swiss QR Code extraction operations +/// +[ApiController] +[Route("api/pdf/qr-code")] +[Produces("application/json")] +public class SwissQrCodeController(IMediator Mediator) : ControllerBase +{ + /// + /// Extracts Swiss QR Code from the last page of a PDF document (multipart/form-data) + /// + /// PDF file containing Swiss QR Code + /// Optional references (comma-separated) + /// Cancellation token + /// Swiss QR Code data (IBAN, amount, creditor, debtor, reference, etc.) + /// Swiss QR Code extracted successfully + /// Invalid PDF or file format + /// No Swiss QR Code found on the last page + /// Internal server error during extraction + [HttpPost("extract-swiss")] + [Consumes("multipart/form-data")] + [ProducesResponseType(typeof(SwissQrCodeExtractionResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)] + public async Task ExtractFromFile( + IFormFile file, + [FromForm] string? references, + CancellationToken cancellationToken) + { + if (file == null || file.Length == 0) + { + return BadRequest(new ProblemDetails + { + Title = "Invalid file", + Detail = "File is required and cannot be empty", + Status = StatusCodes.Status400BadRequest + }); + } + + // Convert IFormFile to byte array + using var memoryStream = new MemoryStream(); + await file.CopyToAsync(memoryStream, cancellationToken); + byte[] pdfBytes = memoryStream.ToArray(); + + // Parse references (comma-separated or empty) + var referencesList = string.IsNullOrWhiteSpace(references) + ? new List() + : references.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList(); + + // Direct pass-through to MediatR + var query = new ExtractSwissQrCodeQuery + { + PdfBytes = pdfBytes, + References = referencesList + }; + var result = await Mediator.Send(query, cancellationToken); + + return Ok(result); + } + + /// + /// Extracts Swiss QR Code from the last page of a PDF document (Base64 JSON) + /// + /// References array + PDF as Base64 string + /// Cancellation token + /// Swiss QR Code data (IBAN, amount, creditor, debtor, reference, etc.) + /// Swiss QR Code extracted successfully + /// Invalid PDF or Base64 format + /// No Swiss QR Code found on the last page + /// Internal server error during extraction + [HttpPost("extract-swiss")] + [Consumes("application/json")] + [ProducesResponseType(typeof(SwissQrCodeExtractionResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)] + public async Task ExtractFromBase64( + [FromBody] ExtractSwissQrCodeQuery query, + CancellationToken cancellationToken) + { + // Direct pass-through to MediatR + var result = await Mediator.Send(query, cancellationToken); + + return Ok(result); + } +} diff --git a/DocumentOperator.API/Endpoints/v1/DocumentEndpoints.cs b/DocumentOperator.API/Endpoints/v1/DocumentEndpoints.cs deleted file mode 100644 index 48b1f97..0000000 --- a/DocumentOperator.API/Endpoints/v1/DocumentEndpoints.cs +++ /dev/null @@ -1,165 +0,0 @@ -using DocumentOperator.Application.Common.DTOs; -using DocumentOperator.Application.Features.Documents.ExtractSwissQrCode; -using DocumentOperator.Application.Features.Documents.ValidatePdf; -using DocumentOperator.Domain.Models.ValueObjects; -using MediatR; -using Microsoft.AspNetCore.Mvc; - -namespace DocumentOperator.API.Endpoints.v1; - -/// -/// Document endpoints (Minimal API) -/// -public static class DocumentEndpoints -{ - /// - /// Maps all document-related endpoints - /// - public static void MapDocumentEndpoints(this IEndpointRouteBuilder app) - { - var group = app.MapGroup("/api/v1/documents") - .WithTags("Documents"); - - // POST /api/v1/documents/validate - group.MapPost("/validate", ValidatePdf) - .WithName("ValidatePdf") - .WithSummary("Validates a PDF document and returns metadata") - .WithDescription("Validates the PDF format and extracts metadata (page count, file size, PDF version, attachments)") - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status500InternalServerError); - - // POST /api/v1/documents/extract-swiss-qr-code - group.MapPost("/extract-swiss-qr-code", ExtractSwissQrCode) - .WithName("ExtractSwissQrCode") - .WithSummary("Extracts Swiss QR Code from the last page of a PDF document") - .WithDescription(@"Extracts and parses a Swiss QR Code (Swiss QR Bill Standard 2.0) from the last page of a PDF document. - -**Requirements:** -- PDF must contain a valid Swiss QR Code on the last page -- QR Code must conform to Swiss QR Bill Standard 2.0 -- References array is required (can be empty) - -**Returns:** -- All QR code fields (IBAN, amount, creditor, debtor, reference, etc.) -- References array (passed through from request) - -**Use Case:** -Extract payment information from Swiss QR invoices for automated processing.") - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status500InternalServerError); - } - - /// - /// Validates a PDF document and returns metadata - /// - /// PDF as Base64 string - /// MediatR instance - /// Cancellation token - /// PDF metadata (page count, file size, etc.) - /// PDF is valid, metadata returned - /// Invalid PDF or Base64 format - /// Internal server error during validation - private static async Task ValidatePdf( - ValidatePdfRequest request, - IMediator mediator, - CancellationToken cancellationToken) - { - // DTO → Query (Value Objects erstellen - kann DomainValidationException werfen!) - var query = new ValidatePdfQuery( - Base64String.Create(request.Base64Pdf) - ); - - // MediatR Handler aufrufen (ValidationBehavior → Handler) - var metadata = await mediator.Send(query, cancellationToken); - - // PdfMetadata → Response DTO - var response = new ValidatePdfResponse( - metadata.PageCount, - metadata.FileSizeBytes, - metadata.FileSizeMB, - metadata.PdfVersion, - metadata.HasAttachments, - metadata.AttachmentCount - ); - - return Results.Ok(response); - } - - /// - /// Extracts Swiss QR Code from the last page of a PDF document - /// - /// References array + PDF as Base64 string - /// MediatR instance - /// Cancellation token - /// References (passed through) + Swiss QR Code data - /// Swiss QR Code extracted successfully - /// Invalid PDF or Base64 format - /// No Swiss QR Code found on the last page - /// Internal server error during extraction - private static async Task ExtractSwissQrCode( - ExtractSwissQrCodeRequest request, - IMediator mediator, - CancellationToken cancellationToken) - { - // DTO → Query (Value Objects erstellen) - var query = new ExtractSwissQrCodeQuery( - References: request.References, - PdfContent: Base64String.Create(request.Base64Pdf) - ); - - // MediatR Handler aufrufen - var result = await mediator.Send(query, cancellationToken); - - // Map Domain Value Object → DTO - var response = new ExtractSwissQrCodeResponse( - References: result.References, - QrCodeData: MapQrCodeDataToDto(result.QrCodeData) - ); - - return Results.Ok(response); - } - - /// - /// Maps SwissQrCodeData domain value object to DTO - /// - private static SwissQrCodeDataDto MapQrCodeDataToDto(Domain.ValueObjects.SwissQrCodeData qrCodeData) - { - return new SwissQrCodeDataDto( - QrType: qrCodeData.QrType, - Version: qrCodeData.Version, - CodingType: qrCodeData.CodingType, - Iban: qrCodeData.Iban, - Creditor: MapAddressToDto(qrCodeData.Creditor), - UltimateCreditor: qrCodeData.UltimateCreditor != null ? MapAddressToDto(qrCodeData.UltimateCreditor) : null, - Amount: qrCodeData.Amount, - Currency: qrCodeData.Currency, - UltimateDebtor: qrCodeData.UltimateDebtor != null ? MapAddressToDto(qrCodeData.UltimateDebtor) : null, - ReferenceType: qrCodeData.ReferenceType, - Reference: qrCodeData.Reference, - UnstructuredMessage: qrCodeData.UnstructuredMessage, - BillInformation: qrCodeData.BillInformation, - AlternativeProcedureParameters: qrCodeData.AlternativeProcedureParameters - ); - } - - /// - /// Maps AddressData domain value object to DTO - /// - private static AddressDataDto MapAddressToDto(Domain.ValueObjects.AddressData address) - { - return new AddressDataDto( - AddressType: address.AddressType, - Name: address.Name, - Street: address.Street, - BuildingNumber: address.BuildingNumber, - AddressLine1: address.AddressLine1, - AddressLine2: address.AddressLine2, - PostalCode: address.PostalCode, - City: address.City, - Country: address.Country - ); - } -} diff --git a/DocumentOperator.API/ROADMAP.md b/DocumentOperator.API/ROADMAP.md deleted file mode 100644 index d8360ec..0000000 --- a/DocumentOperator.API/ROADMAP.md +++ /dev/null @@ -1,1101 +0,0 @@ -# ?? DocumentOperator - Project Roadmap (Feature-Driven Development) - -> **Last Updated:** 17.01.2025 | **Status:** In Development | **Current Feature:** Feature 1 - ValidatePDF ? ABGESCHLOSSEN! - ---- - -## ?? NEW APPROACH: Feature-by-Feature Development - -**Was hat sich geändert?** - -? **Feature-Driven Development** statt Layer-by-Layer -- Jedes Feature wird KOMPLETT umgesetzt (Domain ? Infrastructure ? Application ? API ? Tests ? Swagger) -- Feature ist erst "DONE" wenn es im Swagger testbar ist -- Dann nächstes Feature - -? **Kleine Schritte** (1 Layer pro Step) -- Besser überschaubar -- Weniger Merge-Konflikte -- Schnelleres Feedback - -? **Multi-Tenancy & Cross-Cutting Concerns später** -- Erst alle synchronen Features implementieren -- Dann Multi-Tenancy für ALLE Endpoints (kein Wiederholungsaufwand!) -- Dann Health Checks, Polly, Logging (einmal für alle!) - ---- - -## ?? TABLE OF CONTENTS - -1. [Feature Roadmap](#feature-roadmap) -2. [Project Overview](#project-overview) -3. [Architecture & Design Decisions](#architecture--design-decisions) -4. [Technology Stack](#technology-stack) -5. [Project Structure](#project-structure) -6. [Testing Strategy](#testing-strategy) -7. [Current Status](#current-status) -8. [Key Learnings & Decisions](#key-learnings--decisions) - ---- - -## ?? FEATURE ROADMAP - -### ?? Feature Overview - -| Feature | Type | Status | Swagger Testbar? | -|---------|------|--------|------------------| -| **1. ValidatePDF** | Synchron | ? Abgeschlossen | ? | -| **2. ExtractSwissQrCode** | Synchron | ? Abgeschlossen | ? | -| **3. ExtractAttachments** | Synchron | ? Pending | ? | -| **4. ApplyStamp** | Synchron | ? Pending | ? | -| **5. EmbedCertificate** | Synchron | ? Pending | ? | -| **6. ConcatenatePDFs** | Asynchron | ? Pending | ? | - -### ?? Cross-Cutting Concerns (nach Features 1-4) - -| Concern | Status | -|---------|--------| -| **Multi-Tenancy** (X-API-Key Header) | ? Pending | -| **Health Checks** (/health Endpoint) | ? Pending | -| **Polly Resilience** (Retry, Circuit Breaker) | ? Pending | -| **Logging & Monitoring** (Correlation IDs, Seq) | ? Pending | - ---- - -## ?? FEATURE 1: ValidatePDF (Synchron) - **In Progress** - -**Was macht dieses Feature?** -- Client sendet PDF als Base64 (JSON) -- API validiert PDF -- API gibt Metadaten zurück (Seitenzahl, Dateigröße, PDF-Version, Anhänge) - -**Endpoint:** -``` -POST /api/v1/documents/validate -Request: { "base64Pdf": "JVBERi0xLjQK..." } -Response: { "pageCount": 5, "fileSizeBytes": 1024, "pdfVersion": "1.4", "hasAttachments": false } -``` - ---- - -### ? Step 1.0: Foundation (ABGESCHLOSSEN) - -**Was wurde bereits erstellt:** -- ? Domain Layer (Exceptions, Enums, Value Objects) -- ? Infrastructure Layer (`DevExpressPdfProcessor.ValidateAsync` - FERTIG!) -- ? Tests für Infrastructure (`DevExpressPdfProcessorTests.cs` - 6 Tests) - -**Was wir wiederverwenden:** -- `Base64String` Value Object (Domain) -- `PdfMetadata` Value Object (Domain) -- `IPdfProcessor` Interface (Application) -- `DevExpressPdfProcessor.ValidateAsync` (Infrastructure) - ---- - -### ? Step 1.1: Application Layer (MediatR Setup + ValidatePDF Feature) - **ABGESCHLOSSEN** - -**Ziel:** MediatR + FluentValidation + ValidatePDF Handler - -**Was wurde erstellt:** - -#### 1.1.1: MediatR Setup -- ? **Datei:** `Application/DependencyInjection.cs` - - Registriert MediatR - - Registriert FluentValidation - - Registriert Pipeline Behaviors (Validation + Logging) - -#### 1.1.2: Pipeline Behaviors -- ? **Datei:** `Application/Common/Behaviors/ValidationBehavior.cs` - - Führt FluentValidation automatisch aus (vor jedem Handler) - - Wirft `ValidationException` bei Fehler - -- ? **Datei:** `Application/Common/Behaviors/LoggingBehavior.cs` - - Loggt jeden Request (mit Performance-Tracking) - - Nutzt `ILogger` (Clean Architecture konform!) - -#### 1.1.3: ValidatePDF Feature (Vertical Slice) -- ? **Ordner:** `Application/Features/Documents/ValidatePdf/` - -- ? **Datei:** `ValidatePdfQuery.cs` - ```csharp - public record ValidatePdfQuery(Base64String PdfContent) : IRequest; - ``` - -- ? **Datei:** `ValidatePdfHandler.cs` - ```csharp - public class ValidatePdfHandler : IRequestHandler - { - private readonly IPdfProcessor _processor; - - public async Task Handle(ValidatePdfQuery query, CancellationToken ct) - { - byte[] bytes = query.PdfContent.ToByteArray(); - return await _processor.ValidateAsync(bytes); - } - } - ``` - -- ? **Datei:** `ValidatePdfValidator.cs` - ```csharp - public class ValidatePdfValidator : AbstractValidator - { - public ValidatePdfValidator() - { - RuleFor(x => x.PdfContent).NotNull().WithMessage("PDF content is required"); - } - } - ``` - -#### 1.1.4: DTOs -- ? **Ordner:** `Application/Common/DTOs/` - -- ? **Datei:** `ValidatePdfRequest.cs` - ```csharp - public record ValidatePdfRequest(string Base64Pdf); - ``` - -- ? **Datei:** `ValidatePdfResponse.cs` - ```csharp - public record ValidatePdfResponse( - int PageCount, - long FileSizeBytes, - double FileSizeMB, - string PdfVersion, - bool HasAttachments, - int AttachmentCount); - ``` - -#### 1.1.5: Tests -- ? **Datei:** `Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs` - - ? Test: `Handle_ValidPdf_ReturnsPdfMetadata` - - ? Test: `Handle_PdfProcessorThrowsException_PropagatesException` - -**Akzeptanzkriterien:** -- ? Build erfolgreich -- ? Tests grün (alle 2 Tests) -- ? MediatR Pipeline funktioniert (Validation + Logging) - ---- - -### ? Step 1.2: API Layer (Endpoint + Exception Middleware) - **ABGESCHLOSSEN** - -**Ziel:** HTTP Endpoint + zentrale Exception Handling - -**Was wurde erstellt:** - -#### 1.2.1: Exception Handling Middleware -- ? **Datei:** `API/Middleware/ExceptionHandlingMiddleware.cs` - - Fängt alle Exceptions - - Mappt zu HTTP Status Codes (ValidationException ? 400, DomainValidationException ? 400, NotFoundException ? 404, PdfProcessingException ? 500) - - Gibt RFC 7807 Problem Details zurück - -#### 1.2.2: Minimal API Endpoint -- ? **Datei:** `API/Endpoints/v1/DocumentEndpoints.cs` - - POST /api/v1/documents/validate - - Nutzt MediatR (ValidatePdfQuery ? ValidatePdfHandler) - - Returns ValidatePdfResponse (200) oder ProblemDetails (400, 500) - -#### 1.2.3: Infrastructure DependencyInjection -- ? **Datei:** `Infrastructure/DependencyInjection.cs` - - Registriert IPdfProcessor ? DevExpressPdfProcessor - -#### 1.2.4: Program.cs Updates -- ? Application Layer registriert (AddApplication) -- ? Infrastructure Layer registriert (AddInfrastructure) -- ? Exception Middleware registriert (FIRST in pipeline!) -- ? Endpoints registriert (MapDocumentEndpoints) - -#### 1.2.5: Integration Tests -- ? **Datei:** `Tests/Integration/API/DocumentEndpointsTests.cs` - - ? Test: `POST_ValidatePdf_ValidPdf_Returns200` - - ? Test: `POST_ValidatePdf_InvalidBase64_Returns400` - - ? Test: `POST_ValidatePdf_EmptyPdf_Returns400` - -**Akzeptanzkriterien:** -- ? Build erfolgreich -- ? Integration Tests grün (alle 3 Tests) -- ? Exception Middleware funktioniert -- ? Endpoint gibt korrekte HTTP Status Codes zurück - ---- - -### ? Step 1.3: Swagger Dokumentation - **ABGESCHLOSSEN** - -**Ziel:** API-Dokumentation + Swagger UI testbar - -**Was wurde erstellt:** - -#### 1.3.1: Swagger Configuration -- ? **Datei:** `API/Configuration/SwaggerConfiguration.cs` - - `AddSwaggerDocumentation()` Extension Method - - Swagger mit XML Comments konfiguriert - - API-Titel, Version, Beschreibung gesetzt - -#### 1.3.2: XML Comments aktiviert -- ? **Datei:** `API/DocumentOperator.API.csproj` - ```xml - - true - - ``` - -#### 1.3.3: Endpoint dokumentiert -- ? **Datei:** `API/Endpoints/v1/DocumentEndpoints.cs` - - XML Comments für `ValidatePdf` Methode - - Swagger-Annotationen (`.WithSummary()`, `.WithDescription()`, `.Produces<>()`) - -#### 1.3.4: DTOs dokumentiert -- ? **Datei:** `Application/Common/DTOs/ValidatePdfRequest.cs` - - XML Comments für Request-Schema - -- ? **Datei:** `Application/Common/DTOs/ValidatePdfResponse.cs` - - XML Comments für Response-Schema - - `FileSizeMB` Property hinzugefügt - -#### 1.3.5: Program.cs aktualisiert -- ? **Datei:** `API/Program.cs` - - `builder.Services.AddSwaggerDocumentation()` statt `AddSwaggerGen()` - - `using DocumentOperator.API.Configuration;` hinzugefügt - -**Akzeptanzkriterien:** -- ? Build erfolgreich -- ? Alle Tests grün (11/11 Tests) -- ? XML-Dokumentation wird generiert (`DocumentOperator.API.xml`) -- ? Swagger UI zeigt Endpoint `/api/v1/documents/validate` mit Dokumentation -- ? Request/Response-Schemas sind dokumentiert -- ? Endpoint ist im Swagger UI testbar - ---- - -### ? Feature 1 ABGESCHLOSSEN! - -**Was haben wir erreicht?** -- ? ValidatePDF Feature komplett implementiert (Domain ? Infrastructure ? Application ? API ? Tests ? Swagger) -- ? Endpoint ist im Swagger UI testbar -- ? Unit Tests + Integration Tests grün (11/11) -- ? Clean Architecture eingehalten -- ? TDD angewendet -- ? Swagger-Dokumentation vollständig - -**Nächstes Feature:** -? **Feature 3: ExtractAttachments** - ---- - -## ?? FEATURE 2: ExtractSwissQrCode (Synchron) - **ABGESCHLOSSEN** - -**Was macht dieses Feature?** -- Client sendet PDF als Base64 + Referenzen (Array von Strings) -- API extrahiert Swiss QR Code von der **letzten Seite** des PDFs -- API gibt Referenzen + alle QR Code Felder zurück (Swiss QR Bill Standard 2.0) - -**Endpoint:** -``` -POST /api/v1/documents/extract-swiss-qr-code -Request: -{ - "references": ["REF-001", "REF-002"], - "base64Pdf": "JVBERi0xLjQK..." -} - -Response: -{ - "references": ["REF-001", "REF-002"], - "qrCodeData": { - "qrType": "SPC", - "version": "0200", - "codingType": "1", - "iban": "CH4431999123000889012", - "creditor": { - "name": "Robert Schneider AG", - "addressType": "S", - "street": "Rue du Lac", - "buildingNumber": "1268", - "postalCode": "2501", - "city": "Biel", - "country": "CH" - }, - "ultimateCreditor": null, - "amount": 1949.75, - "currency": "CHF", - "ultimateDebtor": { - "name": "Pia-Maria Rutschmann-Schnyder", - "addressType": "S", - "street": "Grosse Marktgasse", - "buildingNumber": "28", - "postalCode": "9400", - "city": "Rorschach", - "country": "CH" - }, - "referenceType": "QRR", - "reference": "210000000003139471430009017", - "unstructuredMessage": "Rechnung vom 15.01.2025", - "billInformation": "//S1/01/...", - "alternativeProcedureParameters": ["Name AV1: UV;UltraPay005;12345", "Name AV2: XY;XYService;54321"] - } -} -``` - -**Technologie:** -- **DevExpress PDF Document API** (PDF-Zugriff, letzte Seite, QR Code Image) -- **Codecrete.SwissQRBill.Generator** (Swiss QR Code Parsing - Standard 2.0) - -**Steps:** -- ? Step 2.1: Domain Layer (SwissQrCodeData Value Object) -- ? Step 2.2: Infrastructure Layer (IQrCodeProcessor + DevExpressSwissQrCodeProcessor) -- ? Step 2.3: Application Layer (ExtractSwissQrCodeQuery + Handler + Validator) -- ? Step 2.4: API Layer (Endpoint + Integration Tests) -- ? Step 2.5: Swagger Dokumentation - -**Akzeptanzkriterien:** -- ? QR Code wird von letzter Seite extrahiert -- ? Alle Swiss QR Bill Felder werden geparst (Standard 2.0) -- ? Referenzen werden durchgeschliffen (Echo) -- ? Fehler wenn kein QR Code gefunden -- ? Swagger-testbar -- ? Tests grün (19/19) - -### ? Feature 2 ABGESCHLOSSEN! - -**Was haben wir erreicht?** -- ? ExtractSwissQrCode Feature komplett implementiert (Domain ? Infrastructure ? Application ? API ? Tests ? Swagger) -- ? Endpoint ist im Swagger UI testbar: `POST /api/v1/documents/extract-swiss-qr-code` -- ? Unit Tests + Integration Tests grün (19/19) -- ? 3 Libraries integriert: DevExpress PDF, ZXing.Net, Codecrete.SwissQRBill.Generator -- ? Vollständige Swiss QR Bill Standard 2.0 Unterstützung -- ? Clean Architecture eingehalten -- ? Swagger-Dokumentation vollständig - -**Nächstes Feature:** -? **Feature 3: ExtractAttachments** - ---- - -## ?? FEATURE 3: ExtractAttachments (Synchron) - **PENDING** - -**Was macht dieses Feature?** -- Client sendet PDF als Base64 (JSON) -- API extrahiert eingebettete Anhänge -- API gibt Anhänge als Base64 zurück (oder Download-Links) - -**Endpoint:** -``` -POST /api/v1/documents/extract-attachments -Request: { "base64Pdf": "JVBERi0xLjQK..." } -Response: { "attachments": [{ "name": "invoice.xml", "base64Content": "..." }] } -``` - -**Steps:** -- ?? Step 3.1: Infrastructure Layer (DevExpressPdfProcessor.ExtractAttachmentsAsync) -- ?? Step 3.2: Application Layer (ExtractAttachmentsCommand + Handler + Validator) -- ?? Step 3.3: API Layer (Endpoint) -- ?? Step 3.4: Swagger Dokumentation - ---- - -## ?? FEATURE 4: ApplyStamp (Synchron) - **PENDING** - -**Was macht dieses Feature?** -- Client sendet PDF + Stamp-Konfiguration (Text, Position) -- API fügt Stamp hinzu (Logo, Text, Wasserzeichen) -- API gibt gestempeltes PDF zurück - -**Endpoint:** -``` -POST /api/v1/documents/apply-stamp -Request: { "base64Pdf": "...", "text": "CONFIDENTIAL", "position": "TopRight" } -Response: { "base64Pdf": "JVBERi0xLjQK..." } -``` - -**Steps:** -- ?? Step 4.1: Infrastructure Layer (DevExpressPdfProcessor.ApplyStampAsync) -- ?? Step 4.2: Application Layer (ApplyStampCommand + Handler + Validator) -- ?? Step 4.3: API Layer (Endpoint) -- ?? Step 4.4: Swagger Dokumentation - ---- - -## ?? FEATURE 5: EmbedCertificate (Synchron) - **PENDING** - -**Was macht dieses Feature?** -- Client sendet PDF + Zertifikat (PFX als Base64) -- API bettet Zertifikat als Attachment ein -- API gibt PDF mit Zertifikat zurück - -**Endpoint:** -``` -POST /api/v1/documents/embed-certificate -Request: { "base64Pdf": "...", "base64Certificate": "..." } -Response: { "base64Pdf": "JVBERi0xLjQK..." } -``` - -**Steps:** -- ?? Step 5.1: Infrastructure Layer (DevExpressPdfProcessor.EmbedCertificateAsync) -- ?? Step 5.2: Application Layer (EmbedCertificateCommand + Handler + Validator) -- ?? Step 5.3: API Layer (Endpoint) -- ?? Step 5.4: Swagger Dokumentation - ---- - -## ?? FEATURE 6: ConcatenatePDFs (Asynchron) - **PENDING** - -**Was macht dieses Feature?** -- Client sendet mehrere PDFs (Array von Base64) -- API startet asynchronen Job (gibt JobId zurück) -- Client pollt Job-Status -- Wenn fertig: Client lädt Ergebnis herunter - -**Endpoints:** -``` -POST /api/v1/documents/concatenate (Async) -Request: { "pdfFiles": ["JVBERi0x...", "JVBERi0y..."] } -Response: { "jobId": "abc-123", "status": "Pending" } - -GET /api/v1/jobs/{jobId} -Response: { "jobId": "abc-123", "status": "Processing", "progress": 50 } - -GET /api/v1/jobs/{jobId}/download -Response: PDF-Datei (Binary) -``` - -**Steps:** -- ?? Step 6.1: Infrastructure Layer (In-Memory Queue + Background Worker) -- ?? Step 6.2: Application Layer (SubmitConcatenateJobCommand + GetJobStatusQuery) -- ?? Step 6.3: API Layer (Async Endpoints) -- ?? Step 6.4: Swagger Dokumentation - ---- - -## ?? CROSS-CUTTING CONCERNS - -**Nach Features 1-4 abgeschlossen:** - -### ?? Multi-Tenancy (X-API-Key Header) - -**Was wird gebaut:** -- EF Core + SQLite (Tenant-Datenbank) -- Redis Cache (API-Key Lookups) -- TenantResolutionMiddleware (X-API-Key ? Tenant) -- BCrypt API-Key Hashing -- Admin API (Tenant CRUD) - -**Steps:** -- ?? Step MT.1: EF Core Setup (Entities, DbContext, Migrations) -- ?? Step MT.2: TenantResolutionMiddleware -- ?? Step MT.3: Redis Cache Integration -- ?? Step MT.4: Admin API (Tenant Management) -- ?? Step MT.5: Alle Endpoints mit X-API-Key absichern - ---- - -### ?? Health Checks - -**Was wird gebaut:** -- `/health` Endpoint (Liveness/Readiness Probes) -- DevExpressPdfHealthCheck (Smoke Test) -- Database Health Check (SQLite) -- Redis Health Check - -**Steps:** -- ?? Step HC.1: DevExpressPdfHealthCheck -- ?? Step HC.2: Database Health Check -- ?? Step HC.3: Redis Health Check (optional) - ---- - -### ??? Polly Resilience - -**Was wird gebaut:** -- Retry Policy (3x mit Exponential Backoff) -- Circuit Breaker (nach 5 Fehlern 30s öffnen) -- Timeout Policy (30s max) - -**Steps:** -- ?? Step PR.1: Polly Policies in DevExpressPdfProcessor -- ?? Step PR.2: Logging für Resilience Events - ---- - -### ?? Logging & Monitoring - -**Was wird gebaut:** -- Correlation IDs (X-Correlation-ID Header) -- Seq Sink (Log-Browsing UI) -- File Logging (Production) -- LoggingBehavior (MediatR Pipeline) - -**Steps:** -- ?? Step LM.1: CorrelationIdMiddleware -- ?? Step LM.2: Seq Sink konfigurieren -- ?? Step LM.3: File Logging konfigurieren -- ?? Step LM.4: LoggingBehavior erweitern (Performance-Tracking) - ---- - -## ?? PROJECT OVERVIEW - -### Vision & Purpose - -**DocumentOperator** ist ein zentralisierter REST API Service für PDF-Dokumenten-Operationen in einer Multi-Tenant DMS-Umgebung. - -### Problem Statement - -**Aktuell:** -- Verschiedene DMS-Kunden bei unterschiedlichen Mandanten -- Jede Anwendung implementiert PDF-Operationen redundant -- Keine zentrale Stelle für Dokumenten-Verarbeitung -- Wartungsaufwand multipliziert sich mit jeder Anwendung - -**Lösung:** -- **Ein** zentraler Service für alle PDF-Operationen -- Wiederverwendbar über HTTP REST API -- Mandantenfähig (Multi-Tenancy - später!) -- Wartbar an einer Stelle - ---- - -## ??? ARCHITECTURE & DESIGN DECISIONS - -### Clean Architecture (Pragmatisch!) - -Wir verwenden **Clean Architecture** mit 4 Layers - **ABER: pragmatisch, nicht dogmatisch!** - -``` -????????????????????????????????????? -? API Layer (Endpoints) ? ? HTTP Entry Point -????????????????????????????????????? -? Application Layer (Use Cases) ? ? MediatR Handlers, DTOs -????????????????????????????????????? -? Infrastructure Layer (Tech Stack) ? ? DevExpress, File I/O -????????????????????????????????????? -? Domain Layer (MINIMAL!) ? ? Nur Enums + Value Objects -????????????????????????????????????? -``` - -#### Dependency Rule - -**Abhängigkeiten zeigen immer nach innen:** - -``` -API ? Application ? Domain -API ? Infrastructure ? Domain -Infrastructure ? Application (für Interfaces) - -Domain ? NICHTS! (No External Dependencies) -Application ? NUR Domain -``` - -**Warum Clean Architecture?** -- ? Testbarkeit (Application Layer kann Services mocken) -- ? Austauschbarkeit (DevExpress ? anderes PDF-Lib ohne Application zu ändern) -- ? Separation of Concerns (jede Schicht hat klare Verantwortung) - -**ABER:** -- ? Kein Overengineering (nur was wir wirklich brauchen!) -- ? Keine spekulativen Abstraktionen (erst wenn 2. Use Case es braucht) -- ? Keine unnötigen Klassen (YAGNI - You Ain't Gonna Need It) - ---- - -### CQRS with MediatR - -**Pattern:** Command Query Responsibility Segregation - -**Warum MediatR?** -- ? Klare Trennung: 1 Command/Query = 1 Handler = 1 Verantwortung -- ? Testbarkeit (Handler kann isoliert getestet werden) -- ? Pipeline Behaviors (Validation, Logging zentral) -- ? Kein aufgeblähter Service mit 20 Methoden - -**CQRS in unserem Kontext:** -- **Command:** Ändert Daten (ApplyStamp, EmbedCertificate, etc.) -- **Query:** Liest Daten (ValidatePdf ? gibt nur Metadata zurück) - -**Beispiel:** -```csharp -// Query (Read-Only) -public record ValidatePdfQuery(Base64String PdfContent) : IRequest; - -// Handler -public class ValidatePdfHandler : IRequestHandler -{ - private readonly IPdfProcessor _processor; - - public async Task Handle(ValidatePdfQuery query, CancellationToken ct) - { - byte[] bytes = query.PdfContent.ToByteArray(); - var metadata = await _processor.ValidateAsync(bytes); - return metadata; - } -} -``` - ---- - -### Vertical Slice Architecture - -**Statt Horizontal Layers** (Commands/, Handlers/, Validators/): - -``` -? Horizontal (Schlecht für Wartung): -Application/ -??? Commands/ -? ??? ValidatePdfCommand.cs -? ??? ProcessDocumentCommand.cs -??? Handlers/ -? ??? ValidatePdfHandler.cs -? ??? ProcessDocumentHandler.cs -??? Validators/ - ??? ValidatePdfValidator.cs - ??? ProcessDocumentValidator.cs -``` - -**Nutzen wir Vertical Slices** (pro Feature alles zusammen): - -``` -? Vertical (Gut für Wartung): -Features/ -??? ValidatePdf/ -? ??? ValidatePdfQuery.cs -? ??? ValidatePdfHandler.cs -? ??? ValidatePdfValidator.cs -??? ProcessDocument/ - ??? ProcessDocumentCommand.cs - ??? ProcessDocumentHandler.cs - ??? ProcessDocumentValidator.cs -``` - -**Vorteile:** -- ? Zusammengehöriger Code ist zusammen (Cohesion) -- ? Einfacher zu finden ("Wo ist ValidatePdf?" ? ein Ordner!) -- ? Einfacher zu ändern (alle Dateien im gleichen Ordner) -- ? Weniger Merge-Konflikte im Team - ---- - -### Exception-based Error Handling - -**Entscheidung:** Keine Result Pattern Library - -**Stattdessen:** -1. **FluentValidation** für Input-Validierung (DTO-Ebene) -2. **Domain Exceptions** für fachliche Fehler -3. **Zentrale Exception Handling Middleware** im API Layer - -**Warum Exception-basiert?** -- ? Einfacherer Code (kein `if (result.IsSuccess)` überall) -- ? Weniger Boilerplate (kein Result Wrapping) -- ? Standard .NET Exception-Flow (jeder kennt es) -- ? Zentrales Error Handling = wartbar an **einer** Stelle - -**Flow:** -``` -HTTP Request - ? -FluentValidation (MediatR ValidationBehavior) - ? Bei Fehler: ValidationException ? Middleware ? HTTP 400 - ? -Handler - ? Bei Fehler: DomainException ? Middleware ? HTTP 400/404/500 - ? -Middleware (Exception Handler) - ? Mappt Exception Type ? HTTP Status Code - ? Loggt Exception (Serilog) - ? Gibt Problem Details (RFC 7807) zurück - ? -HTTP Response (JSON) -``` - ---- - -## ?? TECHNOLOGY STACK - -### Core Framework - -| Technology | Version | Purpose | -|------------|---------|---------| -| **.NET** | 8.0 | Runtime & Framework | -| **ASP.NET Core** | 8.0 | Web API | -| **C#** | 12 | Language (Primary Constructors, Record Types) | - ---- - -### NuGet Packages - -#### API Layer - -| Package | Version | Purpose | -|---------|---------|---------| -| **Swashbuckle.AspNetCore** | 6.6.2 | Swagger/OpenAPI Documentation | -| **Serilog.AspNetCore** | 10.0.0 | Strukturiertes Logging | -| **Serilog.Sinks.File** | 7.0.0 | Log-Datei-Output | -| **Serilog.Sinks.Seq** | 8.0.0 | Log-Browsing UI (Development) | -| **Serilog.Enrichers.CorrelationId** | 3.0.1 | Correlation IDs für Request-Tracking | -| **Asp.Versioning.Http** | 8.1.1 | API Versioning (/api/v1/, /api/v2/) | - -#### Application Layer - -| Package | Version | Purpose | -|---------|---------|---------| -| **MediatR** | 14.1.0 | CQRS Pattern Implementation | -| **FluentValidation** | 12.1.1 | Input Validation (DTOs) | -| **FluentValidation.DependencyInjectionExtensions** | 12.1.1 | DI Integration | - -#### Infrastructure Layer - -| Package | Version | Purpose | -|---------|---------|---------| -| **DevExpress.Pdf.Core** | 25.2.8 | PDF-Operationen (Merge, Extract, Sign, etc.) | -| **Polly** | 8.5.0 | Resilience (Retry, Circuit Breaker, Timeout) - SPÄTER! | -| **Microsoft.EntityFrameworkCore** | 8.0.0 | ORM für Tenant-Datenbank - SPÄTER! | -| **Microsoft.EntityFrameworkCore.Sqlite** | 8.0.0 | SQLite Provider - SPÄTER! | -| **BCrypt.Net-Next** | 4.0.3 | API-Key Hashing - SPÄTER! | - -#### Domain Layer - -| Package | Version | Purpose | -|---------|---------|---------| -| - | - | **Keine Dependencies!** (Clean Architecture) | - -#### Tests - -| Package | Version | Purpose | -|---------|---------|---------| -| **xUnit** | 2.9.3 | Test Framework | -| **FluentAssertions** | 7.0.0 | Assertions (result.Should().Be(expected)) | -| **Moq** | 4.20.72 | Mocking (für Services) | -| **Microsoft.NET.Test.Sdk** | 17.11.1 | Test SDK | - ---- - -## ?? PROJECT STRUCTURE - -### Solution Overview - -``` -DocumentOperator/ -??? DocumentOperator.API/ ? HTTP Entry Point -??? DocumentOperator.Application/ ? Use Cases (MediatR Handlers) -??? DocumentOperator.Infrastructure/ ? Technical Implementations -??? DocumentOperator.Domain/ ? Business Logic (MINIMAL!) -??? DocumentOperator.Tests/ ? Unit & Integration Tests -??? ROADMAP.md ? This file -??? PHASENPLAN.md ? Project timeline -``` - ---- - -### ?? API Layer (DocumentOperator.API) - -**Purpose:** HTTP Entry Point, Routing, Middleware - -**Folder Structure:** - -``` -DocumentOperator.API/ -??? Endpoints/ -? ??? v1/ -? ??? DocumentEndpoints.cs ? Minimal API Endpoints -??? Middleware/ -? ??? ExceptionHandlingMiddleware.cs ? Zentrale Exception Handling ? -??? Configuration/ -? ??? SwaggerConfiguration.cs ? Swagger Setup -??? appsettings.json ? Base Configuration -??? appsettings.Development.json ? Dev Overrides -??? Program.cs ? Application Entry Point -``` - ---- - -### ?? Application Layer (DocumentOperator.Application) - -**Purpose:** Use Cases, Business Logic Orchestration - -**Folder Structure:** - -``` -DocumentOperator.Application/ -??? Features/ ? Vertical Slices ? -? ??? Documents/ -? ??? ValidatePdf/ -? ? ??? ValidatePdfQuery.cs -? ? ??? ValidatePdfHandler.cs -? ? ??? ValidatePdfValidator.cs -? ??? ExtractAttachments/ -? ? ??? ExtractAttachmentsCommand.cs -? ? ??? ExtractAttachmentsHandler.cs -? ? ??? ExtractAttachmentsValidator.cs -? ??? ... (weitere Features iterativ) -??? Common/ -? ??? Interfaces/ ? Abstractions für Infrastructure -? ? ??? IPdfProcessor.cs -? ??? Behaviors/ ? MediatR Pipeline Behaviors -? ? ??? ValidationBehavior.cs ? FluentValidation Integration -? ? ??? LoggingBehavior.cs ? Structured Logging -? ??? DTOs/ ? Data Transfer Objects -? ??? ValidatePdfRequest.cs -? ??? ValidatePdfResponse.cs -??? DependencyInjection.cs ? Service Registration -``` - ---- - -### ?? Infrastructure Layer (DocumentOperator.Infrastructure) - -**Purpose:** Technische Implementierungen - -**Folder Structure:** - -``` -DocumentOperator.Infrastructure/ -??? Services/ -? ??? PdfProcessing/ -? ??? DevExpressPdfProcessor.cs ? IPdfProcessor Implementation ? -??? Configuration/ -? ??? DocumentOperatorSettings.cs ? Options Pattern Class -??? DependencyInjection.cs ? Service Registration -``` - ---- - -### ?? Domain Layer (DocumentOperator.Domain) - MINIMAL! - -**Purpose:** Business Rules (nur was wirklich gebraucht wird!) - -**Folder Structure:** - -``` -DocumentOperator.Domain/ -??? ValueObjects/ ? Immutable, selbst-validierend ? -? ??? Base64String.cs -? ??? TenantId.cs -? ??? PdfMetadata.cs -??? Enums/ ? ? -? ??? DocumentOperationType.cs -? ??? ProcessingStatus.cs -??? Exceptions/ ? Domain-spezifische Exceptions ? - ??? DomainException.cs - ??? DomainValidationException.cs - ??? NotFoundException.cs - ??? PdfProcessingException.cs -``` - ---- - -### ?? Tests Layer (DocumentOperator.Tests) - -**Purpose:** Unit & Integration Tests - -**Folder Structure:** - -``` -DocumentOperator.Tests/ -??? Unit/ -? ??? Application/ -? ? ??? Features/ -? ? ??? ValidatePdf/ -? ? ??? ValidatePdfHandlerTests.cs -? ??? Infrastructure/ -? ? ??? Services/ -? ? ??? DevExpressPdfProcessorTests.cs ? -? ??? Domain/ -? ??? ValueObjects/ -? ??? Base64StringTests.cs -??? Integration/ - ??? API/ - ??? DocumentEndpointsTests.cs -``` - ---- - -## ?? TESTING STRATEGY - -### Test-Driven Development (TDD) - -**Flow:** -1. **Red:** Test schreiben (schlägt fehl, weil Code noch nicht existiert) -2. **Green:** Code schreiben (Test wird grün) -3. **Refactor:** Code verbessern (Test bleibt grün) - -**Warum TDD?** -- ? Tests als Dokumentation (wie wird es genutzt?) -- ? Tests als Safety Net (Refactoring ohne Angst) -- ? Besseres Design (testbarer Code = guter Code) -- ? Keine "vergessenen" Tests (Test kommt ZUERST) - ---- - -### Test-Pyramide - -``` - /\ - / \ E2E Tests (wenige) - / \ - /------\ Integration Tests (einige) - / \ - /----------\ Unit Tests (viele) - / \ -``` - -**Konkret:** -- **Unit Tests (viele):** - - Value Objects (Base64String.Create() wirft Exception?) - - Handlers (ValidatePdfHandler ruft IPdfProcessor auf?) - - Services (DevExpressPdfProcessor gibt Metadata zurück?) - -- **Integration Tests (einige):** - - Endpoints (HTTP POST ? 200 OK + JSON?) - - MediatR Pipeline (ValidationBehavior funktioniert?) - -- **E2E Tests (wenige/keine):** - - Haben wir nicht (API ist selbst der "Top-Level") - ---- - -## ?? CURRENT STATUS - -### ? Completed - -- **Foundation & Domain Layer:** - - ? Solution Structure (4 Projekte) - - ? Dependencies (Clean Architecture Dependency Rule) - - ? Domain Exceptions (4 Exceptions) - - ? Enums (DocumentOperationType, ProcessingStatus) - - ? Value Objects (Base64String, TenantId, PdfMetadata) - -- **Infrastructure Layer:** - - ? IPdfProcessor Interface - - ? DevExpressPdfProcessor.ValidateAsync (mit Tests!) - - ? DependencyInjection.cs (Infrastructure Services) - -- **Application Layer:** - - ? DependencyInjection.cs (MediatR + FluentValidation) - - ? ValidationBehavior.cs (FluentValidation Pipeline) - - ? LoggingBehavior.cs (ILogger Pipeline) - - ? ValidatePDF Feature (Query, Handler, Validator) - - ? DTOs (ValidatePdfRequest, ValidatePdfResponse) - - ? Tests (ValidatePdfHandlerTests - 2 Tests grün) - -- **API Layer:** - - ? ExceptionHandlingMiddleware.cs (RFC 7807 Problem Details) - - ? DocumentEndpoints.cs (POST /api/v1/documents/validate) - - ? Program.cs (Services + Middleware + Endpoints) - - ? Tests (DocumentEndpointsTests - 3 Tests grün) - -### ?? In Progress - -- **Feature 1: ValidatePDF** - - ? Step 1.3: Swagger Dokumentation - **NEXT** - -### ? Pending - -- **Feature 1: ValidatePDF** - - ? Step 1.3: Swagger Dokumentation - -- **Feature 2-5:** ExtractAttachments, ApplyStamp, EmbedCertificate, ConcatenatePDFs -- **Cross-Cutting Concerns:** Multi-Tenancy, Health Checks, Polly, Logging - ---- - -## ?? KEY LEARNINGS & DECISIONS - -### 1. Feature-Driven Development statt Layer-by-Layer - -**Entscheidung:** Jedes Feature komplett fertig (bis Swagger testbar) - -**Warum:** -- ? Schnellerer Value (Feature 1 nach ~1 Tag fertig!) -- ? Klares Ziel (Swagger testbar = DONE) -- ? Weniger Komplexität (nicht alle Layer parallel) -- ? Besseres Lernen (Pattern wiederholt sich) - -**Alternative wäre gewesen:** -- Domain komplett ? Infrastructure komplett ? Application komplett ? API komplett -- **Nachteile:** Viel Code ohne sichtbares Ergebnis, spekulativ - ---- - -### 2. Domain Layer minimal halten - -**Entscheidung:** Nur Enums + Value Objects + Exceptions - -**Warum:** -- Domain = Business-Konzepte (technologie-unabhängig) -- Service-Anwendung (nicht Domain-lastig) -- YAGNI (You Ain't Gonna Need It) - -**Was bedeutet das?** -- Domain/ValueObjects/TenantId.cs ? Value Object (immutable, validierend) -- Infrastructure/Data/Entities/Tenant.cs ? EF Core Entity (später!) -- Domain kennt KEINE EF Core Dependencies! - ---- - -### 3. Multi-Tenancy NACH allen Features - -**Entscheidung:** Erst alle synchronen Features, dann Multi-Tenancy - -**Warum:** -- ? Multi-Tenancy betrifft ALLE Endpoints -- ? Einmal für alle Features (nicht 5x wiederholen!) -- ? Einfacher zu testen (erst ohne Tenancy, dann mit) - -**Nachteile (akzeptiert):** -- ? Refactoring später nötig (alle Endpoints müssen X-API-Key Header bekommen) -- ? Aber: Aufwand überschaubar (Middleware erledigt das meiste!) - ---- - -### 4. TDD beibehalten - -**Entscheidung:** Test-First Development - -**Warum:** -- ? Besseres Design (testbarer Code) -- ? Tests als Dokumentation -- ? Safety Net für Refactoring - ---- - -### 5. Vertical Slice Architecture - -**Entscheidung:** Pro Feature alles zusammen - -**Warum:** -- ? Zusammengehöriger Code ist zusammen -- ? Einfacher zu finden und zu ändern -- ? Besser für Teams (weniger Merge-Konflikte) - ---- - -## ?? UPDATE LOG - -| Date | Feature/Step | Changes | -|------|--------------|---------| -| 2024-XX-XX | Foundation | Project setup, dependencies, folder structure | -| 2024-XX-XX | Domain Layer | Exceptions, Enums, Value Objects | -| 17.01.2025 | Infrastructure | DevExpressPdfProcessor.ValidateAsync implementiert | -| 17.01.2025 | Tests | DevExpressPdfProcessorTests.cs erstellt (6 Tests) | -| 17.01.2025 | **ROADMAP** | ?? **Komplett umstrukturiert** (Feature-Driven Development!) | -| 17.01.2025 | **PHASENPLAN** | ?? **Komplett umstrukturiert** (Feature-basiert + Datum korrigiert) | -| 17.01.2025 | **Feature 1 - Step 1.1** | ? **ABGESCHLOSSEN** - Application Layer (MediatR, Behaviors, ValidatePDF Feature, DTOs, Tests) | -| 17.01.2025 | **Feature 1 - Step 1.2** | ? **ABGESCHLOSSEN** - API Layer (ExceptionMiddleware, Endpoint, Program.cs, Integration Tests - 3/3 grün) | -| 17.01.2025 | **Feature 1 - Step 1.3** | ? **ABGESCHLOSSEN** - Swagger Dokumentation (SwaggerConfiguration, XML Comments, Endpoint/DTO-Dokumentation - 11/11 Tests grün) | -| 17.01.2025 | **Feature 1** | ? **KOMPLETT ABGESCHLOSSEN** - ValidatePDF Feature testbar im Swagger UI! | -| 17.01.2025 | **Fix: Attachment Detection (Multiple Attachments)** | ? **KORRIGIERT** - ValidatePDF erkennt jetzt auch PDFs mit mehreren Attachments korrekt (globale Suche statt 1000-Zeichen-Limit) - 13/13 Tests grün | -| 17.01.2025 | **Fix: Attachment Count (6 Attachments)** | ? **KORRIGIERT** - AttachmentCount wird jetzt korrekt gezählt (objectCount statt objectCount/2). PDFs mit 6 Attachments werden korrekt erkannt - 13/13 Tests grün | -| 17.01.2025 | **ROADMAP** | ?? **Feature-Reihenfolge geändert** - Neues Feature 2: ExtractSwissQrCode (Swiss QR Bill Standard 2.0) eingefügt. Alte Features 2-5 werden zu Features 3-6. | -| 17.01.2025 | **Feature 2 - Step 2.1** | ? **ABGESCHLOSSEN** - Domain Layer (SwissQrCodeData, AddressData Value Objects, SwissQrCodeNotFoundException) | -| 17.01.2025 | **Feature 2 - Step 2.2** | ? **ABGESCHLOSSEN** - Infrastructure Layer (ISwissQrCodeProcessor, DevExpressSwissQrCodeProcessor, Libraries: Codecrete.SwissQRBill.Generator, ZXing.Net, System.Drawing.Common) | -| 17.01.2025 | **Feature 2 - Step 2.3** | ? **ABGESCHLOSSEN** - Application Layer (Query, Handler, Validator, DTOs, Unit Tests - 2/2 grün) | -| 17.01.2025 | **Feature 2 - Step 2.4** | ? **ABGESCHLOSSEN** - API Layer (Endpoint /extract-swiss-qr-code, Exception Handling, Integration Tests - 4/4 grün) | -| 17.01.2025 | **Feature 2 - Step 2.5** | ? **ABGESCHLOSSEN** - Swagger Dokumentation (XML Comments, Request/Response Beispiele, Endpoint-Beschreibung) | -| 17.01.2025 | **Feature 2** | ? **KOMPLETT ABGESCHLOSSEN** - ExtractSwissQrCode Feature testbar im Swagger UI! (19/19 Tests grün) | - - ---- - -**END OF ROADMAP** - -*This document is a living document and will be updated after each completed step.*