diff --git a/README.md b/README.md index dcf2fd6..d6371b1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PDF Tool — ZUGFeRD E-Rechnungs-Konverter -Spring Boot + Vaadin Flow Anwendung, die ein herkömmliches B2B-Rechnungs-PDF in eine -**rechtskonforme ZUGFeRD-E-Rechnung** umwandelt (PDF/A-3 mit eingebettetem EN16931-XML, +Spring Boot + Vaadin Flow Anwendung, die aus gespeicherten Rechnungstemplates +**rechtskonforme ZUGFeRD-E-Rechnungen** erzeugt (PDF/A-3 mit eingebettetem EN16931-XML, Profil EN 16931 / Factur-X), das Ergebnis mit dem **Mustang-Validator** prüft und als **ZIP-Datei** bereitstellt. @@ -26,7 +26,8 @@ mvn spring-boot:run ``` Die Weboberfläche ist dann unter erreichbar: -PDF hochladen, Rechnungsdaten ausfüllen, "E-Rechnung erzeugen" klicken, ZIP herunterladen. +Rechnungstemplate auswählen, Rechnungsdaten ausfüllen, "E-Rechnung erzeugen" klicken — +das ZIP wird automatisch heruntergeladen. Produktions-Build: @@ -37,83 +38,10 @@ java -jar target/pdf-tool-1.0.0-SNAPSHOT.jar ## REST-API -### E-Rechnung erzeugen - -`POST /api/invoices/sign` (multipart/form-data) mit zwei Parts: - -| Part | Inhalt | -|------------|-------------------------------------------| -| `file` | das Rechnungs-PDF | -| `metadata` | Rechnungsdaten als JSON (siehe unten) | - -Antwort: `application/zip` mit `-zugferd.pdf`, `factur-x.xml` und -`validation-report.xml` (Mustang-Prüfbericht). Der Header `X-Zugferd-Valid: true|false` -meldet das Validierungsergebnis. Bei Fehlern: HTTP 400 mit `{"error": "..."}`. - -> Häufigste Ursache für `X-Zugferd-Valid: false`: Das Quell-PDF bettet seine -> Schriften nicht ein. Das kann die Konvertierung nicht reparieren — das PDF muss -> dann mit eingebetteten Schriften neu erzeugt werden (Standard bei den meisten -> ERP-/Reporting-Systemen konfigurierbar). - -### Bestehende E-Rechnung prüfen - -`POST /api/invoices/validate` (multipart/form-data, Part `file` = ZUGFeRD-PDF oder -Factur-X-XML). Antwort: der Mustang-Prüfbericht als XML — HTTP 200 bei gültiger, -HTTP 422 bei ungültiger Rechnung, ebenfalls mit `X-Zugferd-Valid`-Header. - -```bash -curl -X POST http://localhost:8083/api/invoices/validate \ - -F "file=@rechnung-zugferd.pdf" -``` - -### Beispiel - -```bash -curl -X POST http://localhost:8083/api/invoices/sign \ - -F "file=@rechnung.pdf" \ - -F "metadata=@metadata.json;type=application/json" \ - -o rechnung-zugferd.zip -``` - -`metadata.json`: - -```json -{ - "invoiceNumber": "RE-2026-0815", - "issueDate": "2026-07-08", - "deliveryDate": "2026-07-01", - "dueDate": "2026-07-22", - "currency": "EUR", - "paymentTerms": "Zahlbar innerhalb von 14 Tagen ohne Abzug.", - "sender": { - "name": "Assecutor GmbH", - "street": "Musterstrasse 1", - "zip": "20095", - "city": "Hamburg", - "countryCode": "DE", - "vatId": "DE123456789" - }, - "recipient": { - "name": "Kunde AG", - "street": "Beispielweg 2", - "zip": "10115", - "city": "Berlin", - "countryCode": "DE" - }, - "items": [ - { - "description": "Beratungsleistung", - "quantity": 1, - "unitPriceNet": 1500.00, - "vatPercent": 19 - } - ] -} -``` - -Pflichtfelder: `invoiceNumber`, `issueDate`, `sender` (inkl. Adresse), `recipient`, -mindestens ein Eintrag in `items`. `deliveryDate` fällt auf das Rechnungsdatum zurück, -`dueDate` auf Rechnungsdatum + 14 Tage, `currency` auf `EUR`. +Die REST-API (`/api/invoices/sign` und `/api/invoices/validate`) ist in das +eigenständige Projekt **PDF Service** ausgelagert +(`../PDF Service`, Port 8084). Dieses Tool enthält nur noch die Weboberfläche; +Rechnungen entstehen hier ausschließlich aus gespeicherten Templates. ## Architektur @@ -122,7 +50,6 @@ mindestens ein Eintrag in `items`. `deliveryDate` fällt auf das Rechnungsdatum Nicht-PDF/A-Eingaben (der Normalfall) werden tolerant konvertiert. - [ZugferdValidationService](src/main/java/de/assecutor/pdftool/zugferd/ZugferdValidationService.java) — Mustang-Validator (XSD + Schematron + veraPDF), liefert Status und XML-Prüfbericht -- [InvoiceSigningController](src/main/java/de/assecutor/pdftool/api/InvoiceSigningController.java) — REST-Endpunkte `/sign` und `/validate` - [MainView](src/main/java/de/assecutor/pdftool/ui/MainView.java) — Vaadin-Weboberfläche - [InvoiceMetadata](src/main/java/de/assecutor/pdftool/invoice/InvoiceMetadata.java) — DTO für die Rechnungsdaten diff --git a/src/main/java/de/assecutor/pdftool/api/InvoiceSigningController.java b/src/main/java/de/assecutor/pdftool/api/InvoiceSigningController.java deleted file mode 100644 index 73910b0..0000000 --- a/src/main/java/de/assecutor/pdftool/api/InvoiceSigningController.java +++ /dev/null @@ -1,122 +0,0 @@ -package de.assecutor.pdftool.api; - -import com.fasterxml.jackson.databind.ObjectMapper; -import de.assecutor.pdftool.invoice.InvoiceMetadata; -import de.assecutor.pdftool.zugferd.ZugferdConversionException; -import de.assecutor.pdftool.zugferd.ZugferdResult; -import de.assecutor.pdftool.zugferd.ZugferdService; -import de.assecutor.pdftool.zugferd.ZugferdValidationService; -import jakarta.validation.ConstraintViolation; -import jakarta.validation.Validator; -import org.springframework.http.ContentDisposition; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * REST-API zur Erzeugung und Prüfung rechtskonformer ZUGFeRD-E-Rechnungen. - * - *
- * curl -X POST http://localhost:8083/api/invoices/sign \
- *   -F "file=@rechnung.pdf" \
- *   -F "metadata=@metadata.json;type=application/json" \
- *   -o rechnung-zugferd.zip
- *
- * curl -X POST http://localhost:8083/api/invoices/validate \
- *   -F "file=@rechnung-zugferd.pdf"
- * 
- */ -@RestController -@RequestMapping("/api/invoices") -public class InvoiceSigningController { - - /** Response-Header mit dem Ergebnis der Mustang-Validierung (true/false). */ - public static final String VALIDATION_HEADER = "X-Zugferd-Valid"; - - private final ZugferdService zugferdService; - private final ZugferdValidationService validationService; - private final ObjectMapper objectMapper; - private final Validator validator; - - public InvoiceSigningController(ZugferdService zugferdService, - ZugferdValidationService validationService, - ObjectMapper objectMapper, - Validator validator) { - this.zugferdService = zugferdService; - this.validationService = validationService; - this.objectMapper = objectMapper; - this.validator = validator; - } - - /** - * Erzeugt aus PDF und Metadaten eine ZUGFeRD-Rechnung als ZIP (PDF + Prüfbericht). - * HTTP 200 bei gültiger, HTTP 422 bei ungültiger Rechnung; das ZIP wird in - * beiden Fällen geliefert. - */ - @PostMapping(value = "/sign", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity sign(@RequestPart("file") MultipartFile file, - @RequestPart("metadata") String metadataJson) throws IOException { - InvoiceMetadata metadata = parseAndValidate(metadataJson); - ZugferdResult result = zugferdService.createZugferdZip(file.getBytes(), metadata); - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.parseMediaType("application/zip")); - headers.setContentDisposition(ContentDisposition.attachment().filename(result.zipFileName()).build()); - headers.set(VALIDATION_HEADER, String.valueOf(result.valid())); - return new ResponseEntity<>(result.zip(), headers, - result.valid() ? HttpStatus.OK : HttpStatus.UNPROCESSABLE_ENTITY); - } - - /** - * Validiert eine bestehende ZUGFeRD-Rechnung (PDF oder Factur-X-XML). - * Antwort ist der Mustang-Prüfbericht als XML; HTTP 200 bei gültiger, - * HTTP 422 bei ungültiger Rechnung. - */ - @PostMapping(value = "/validate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, - produces = MediaType.APPLICATION_XML_VALUE) - public ResponseEntity validate(@RequestPart("file") MultipartFile file) throws IOException { - ZugferdValidationService.ValidationResult result = - validationService.validate(file.getBytes(), file.getOriginalFilename()); - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_XML); - headers.set(VALIDATION_HEADER, String.valueOf(result.valid())); - return new ResponseEntity<>(result.reportXml(), headers, - result.valid() ? HttpStatus.OK : HttpStatus.UNPROCESSABLE_ENTITY); - } - - private InvoiceMetadata parseAndValidate(String metadataJson) { - InvoiceMetadata metadata; - try { - metadata = objectMapper.readValue(metadataJson, InvoiceMetadata.class); - } catch (IOException e) { - throw new ZugferdConversionException("Metadaten sind kein gültiges JSON: " + e.getMessage(), e); - } - Set> violations = validator.validate(metadata); - if (!violations.isEmpty()) { - String details = violations.stream() - .map(v -> v.getPropertyPath() + ": " + v.getMessage()) - .sorted() - .collect(Collectors.joining("; ")); - throw new ZugferdConversionException("Metadaten unvollständig: " + details); - } - return metadata; - } - - @ExceptionHandler(ZugferdConversionException.class) - public ResponseEntity> handleConversionError(ZugferdConversionException e) { - return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fe1f038..1986ef1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -15,9 +15,6 @@ server.servlet.session.cookie.name=PDFTOOL_SESSION # Wird beim Build durch die Version aus der pom.xml ersetzt (Resource-Filtering) pdftool.version=@project.version@ -spring.servlet.multipart.max-file-size=25MB -spring.servlet.multipart.max-request-size=30MB - # Auch normale (Nicht-Multipart-)Requests bis 30 MB zulassen: Canvas-Daten mit # eingebetteten Bild-Elementen können das Tomcat-Standardlimit von 2 MB # überschreiten; ein abgelehnter Request führt im Browser sonst zu einem