Erweiterungen

This commit is contained in:
2025-09-19 21:34:48 +02:00
parent 215e933f44
commit 09e39839cc
4 changed files with 288 additions and 10 deletions

31
pom.xml
View File

@@ -123,6 +123,35 @@
<version>3.0.3</version> <version>3.0.3</version>
</dependency> </dependency>
<!-- iText8 for PDF generation -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-core</artifactId>
<version>8.0.5</version>
<type>pom</type>
</dependency>
<!-- iText8 Kernel (PDF core functionality) -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>kernel</artifactId>
<version>8.0.5</version>
</dependency>
<!-- iText8 Layout (document layout) -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>layout</artifactId>
<version>8.0.5</version>
</dependency>
<!-- iText8 HTML to PDF converter -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>5.0.5</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -280,4 +309,4 @@
</snapshots> </snapshots>
</pluginRepository> </pluginRepository>
</pluginRepositories> </pluginRepositories>
</project> </project>

View File

@@ -2,7 +2,9 @@ package de.assecutor.votianlt.pages.view;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.IFrame;
import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
@@ -19,6 +21,7 @@ import java.io.ByteArrayInputStream;
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
public class PdfTestView extends VerticalLayout { public class PdfTestView extends VerticalLayout {
private final SystemInvoiceService systemInvoiceService; private final SystemInvoiceService systemInvoiceService;
private IFrame pdfViewer;
public PdfTestView(SystemInvoiceService systemInvoiceService) { public PdfTestView(SystemInvoiceService systemInvoiceService) {
this.systemInvoiceService = systemInvoiceService; this.systemInvoiceService = systemInvoiceService;
@@ -34,7 +37,29 @@ public class PdfTestView extends VerticalLayout {
Button generatePdfButton = new Button("Test-Rechnung PDF generieren"); Button generatePdfButton = new Button("Test-Rechnung PDF generieren");
generatePdfButton.addClickListener(e -> generateTestPdf()); generatePdfButton.addClickListener(e -> generateTestPdf());
add(generatePdfButton); Button generateHtmlPdfButton = new Button("PDF aus vltInvoice.html generieren");
generateHtmlPdfButton.addClickListener(e -> generateHtmlPdf());
Button generateTestPdfFromHtmlButton = new Button("Test PDF aus vltInvoice.html erstellen");
generateTestPdfFromHtmlButton.addClickListener(e -> generateTestPdfFromHtml());
Button showPdfInlineButton = new Button("PDF inline anzeigen");
showPdfInlineButton.addClickListener(e -> showPdfInline());
// Create button layout
HorizontalLayout buttonLayout = new HorizontalLayout();
buttonLayout.add(generatePdfButton, generateHtmlPdfButton, generateTestPdfFromHtmlButton, showPdfInlineButton);
buttonLayout.setSpacing(true);
// Initialize PDF viewer
pdfViewer = new IFrame();
pdfViewer.setWidth("100%");
pdfViewer.setHeight("800px");
pdfViewer.getStyle().set("border", "1px solid #ccc");
pdfViewer.setVisible(false);
add(buttonLayout);
add(pdfViewer);
} }
private void generateTestPdf() { private void generateTestPdf() {
@@ -58,5 +83,67 @@ public class PdfTestView extends VerticalLayout {
} }
} }
private void generateHtmlPdf() {
try {
byte[] pdfBytes = systemInvoiceService.generateInvoicePdfFromHtml();
} StreamResource resource = new StreamResource("vlt-invoice.pdf",
() -> new ByteArrayInputStream(pdfBytes));
resource.setContentType("application/pdf");
getUI().ifPresent(ui -> {
var registration = ui.getSession().getResourceRegistry().registerResource(resource);
ui.getPage().open(registration.getResourceUri().toString(), "_blank");
});
Notification.show("PDF aus HTML erfolgreich generiert!", 3000, Notification.Position.BOTTOM_CENTER);
} catch (Exception ex) {
Notification.show("Fehler beim Generieren des PDFs aus HTML: " + ex.getMessage(),
5000, Notification.Position.BOTTOM_CENTER);
}
}
private void generateTestPdfFromHtml() {
try {
SystemInvoiceData sampleData = systemInvoiceService.createSampleInvoiceData();
byte[] pdfBytes = systemInvoiceService.generateInvoicePdfFromHtml();
StreamResource resource = new StreamResource("test-invoice-from-html.pdf",
() -> new ByteArrayInputStream(pdfBytes));
resource.setContentType("application/pdf");
getUI().ifPresent(ui -> {
var registration = ui.getSession().getResourceRegistry().registerResource(resource);
ui.getPage().open(registration.getResourceUri().toString(), "_blank");
});
Notification.show("Test PDF aus HTML erfolgreich generiert!", 3000, Notification.Position.BOTTOM_CENTER);
} catch (Exception ex) {
Notification.show("Fehler beim Generieren des Test PDFs aus HTML: " + ex.getMessage(),
5000, Notification.Position.BOTTOM_CENTER);
}
}
private void showPdfInline() {
try {
SystemInvoiceData sampleData = systemInvoiceService.createSampleInvoiceData();
byte[] pdfBytes = systemInvoiceService.generateInvoicePdf(sampleData);
StreamResource resource = new StreamResource("inline-invoice.pdf",
() -> new ByteArrayInputStream(pdfBytes));
resource.setContentType("application/pdf");
getUI().ifPresent(ui -> {
var registration = ui.getSession().getResourceRegistry().registerResource(resource);
pdfViewer.setSrc(registration.getResourceUri().toString());
pdfViewer.setVisible(true);
});
Notification.show("PDF wird inline angezeigt!", 3000, Notification.Position.BOTTOM_CENTER);
} catch (Exception ex) {
Notification.show("Fehler beim Anzeigen des PDFs: " + ex.getMessage(),
5000, Notification.Position.BOTTOM_CENTER);
}
}
}

View File

@@ -10,6 +10,8 @@ import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts; import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.itextpdf.html2pdf.HtmlConverter;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -72,7 +74,7 @@ public class SystemInvoiceService {
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), TITLE_FONT_SIZE); contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), TITLE_FONT_SIZE);
contentStream.setNonStrokingColor(27/255f, 18/255f, 185/255f); // Blue color (RGB values normalized to 0-1) contentStream.setNonStrokingColor(27/255f, 18/255f, 185/255f); // Blue color (RGB values normalized to 0-1)
contentStream.newLineAtOffset(MARGIN, yPosition); contentStream.newLineAtOffset(MARGIN, yPosition);
contentStream.showText(data.getCompanyName()); contentStream.showText(data.getCompanyName() != null ? data.getCompanyName() : "");
contentStream.endText(); contentStream.endText();
yPosition -= 30; yPosition -= 30;
@@ -82,7 +84,7 @@ public class SystemInvoiceService {
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), FONT_SIZE); contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), FONT_SIZE);
contentStream.setNonStrokingColor(27/255f, 18/255f, 185/255f); contentStream.setNonStrokingColor(27/255f, 18/255f, 185/255f);
contentStream.newLineAtOffset(MARGIN, yPosition); contentStream.newLineAtOffset(MARGIN, yPosition);
contentStream.showText(data.getCompanySubtitle()); contentStream.showText(data.getCompanySubtitle() != null ? data.getCompanySubtitle() : "");
contentStream.endText(); contentStream.endText();
yPosition -= 20; yPosition -= 20;
@@ -108,7 +110,7 @@ public class SystemInvoiceService {
contentStream.beginText(); contentStream.beginText();
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 8); contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 8);
contentStream.newLineAtOffset(leftColumn, yPosition); contentStream.newLineAtOffset(leftColumn, yPosition);
contentStream.showText(data.getSenderLine()); contentStream.showText(data.getSenderLine() != null ? data.getSenderLine() : "");
contentStream.endText(); contentStream.endText();
yPosition -= 20; yPosition -= 20;
@@ -143,7 +145,7 @@ public class SystemInvoiceService {
contentStream.beginText(); contentStream.beginText();
contentStream.newLineAtOffset(dateColumn, recipientStartY - LINE_HEIGHT); contentStream.newLineAtOffset(dateColumn, recipientStartY - LINE_HEIGHT);
contentStream.showText(data.getInvoiceDate()); contentStream.showText(data.getInvoiceDate() != null ? data.getInvoiceDate() : "");
contentStream.endText(); contentStream.endText();
// Company address (right side) // Company address (right side)
@@ -158,7 +160,7 @@ public class SystemInvoiceService {
contentStream.beginText(); contentStream.beginText();
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), SUBTITLE_FONT_SIZE); contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), SUBTITLE_FONT_SIZE);
contentStream.newLineAtOffset(MARGIN, yPosition); contentStream.newLineAtOffset(MARGIN, yPosition);
contentStream.showText("Rechnung " + data.getInvoiceNumber()); contentStream.showText("Rechnung " + (data.getInvoiceNumber() != null ? data.getInvoiceNumber() : ""));
contentStream.endText(); contentStream.endText();
yPosition -= 30; yPosition -= 30;
@@ -307,7 +309,7 @@ public class SystemInvoiceService {
// VAT // VAT
contentStream.beginText(); contentStream.beginText();
contentStream.newLineAtOffset(rightColumn, yPosition); contentStream.newLineAtOffset(rightColumn, yPosition);
contentStream.showText("+ " + data.getVatRate() + "% MwSt.:"); contentStream.showText("+ " + (data.getVatRate() != null ? data.getVatRate() : "") + "% MwSt.:");
contentStream.endText(); contentStream.endText();
contentStream.beginText(); contentStream.beginText();
@@ -431,4 +433,67 @@ public class SystemInvoiceService {
return data; return data;
} }
}
public byte[] generateInvoicePdfFromHtml() throws Exception {
// Read the HTML template
String htmlContent = readHtmlTemplate();
// For simplicity, we'll use the existing sample data to fill the PDF
SystemInvoiceData sampleData = createSampleInvoiceData();
// Replace placeholders in HTML with actual data
String filledHtml = fillHtmlWithInvoiceData(htmlContent, sampleData);
// Create PDF from HTML using iText
return generatePdfFromHtmlString(filledHtml);
}
private String readHtmlTemplate() throws Exception {
// Read the HTML template file
java.nio.file.Path path = java.nio.file.Paths.get("src/main/resources/templates/vltInvoice.html");
return java.nio.file.Files.readString(path);
}
private String fillHtmlWithInvoiceData(String html, SystemInvoiceData data) {
// Replace placeholders in HTML with actual invoice data
String filledHtml = html;
filledHtml = filledHtml.replace("HHA-2021-07", data.getInvoiceNumber() != null ? data.getInvoiceNumber() : "");
filledHtml = filledHtml.replace("19.07.2021", data.getInvoiceDate() != null ? data.getInvoiceDate() : "");
filledHtml = filledHtml.replace("Gemäß unserem Nutzungsvertrag zu der Bestellnummer 45519389 berechnen wir Ihnen für den Monat Juli 2021 wie folgt:",
data.getInvoiceText() != null ? data.getInvoiceText() : "");
// Replace recipient address
filledHtml = filledHtml.replace("Hamburger Hochbahn AG<br>\n Kreditorenbuchhaltung<br>\n Steinstraße 20<br>\n 20095 Hamburg",
data.getRecipientName() != null ? data.getRecipientName() : "" + "<br>\n " +
(data.getRecipientDepartment() != null ? data.getRecipientDepartment() : "") + "<br>\n " +
(data.getRecipientStreet() != null ? data.getRecipientStreet() : "") + "<br>\n " +
(data.getRecipientCity() != null ? data.getRecipientCity() : ""));
// Replace invoice items
if (data.getInvoiceItems() != null && !data.getInvoiceItems().isEmpty()) {
SystemInvoiceItem item = data.getInvoiceItems().get(0);
filledHtml = filledHtml.replace("1", item.getQuantity() != null ? item.getQuantity() : "");
filledHtml = filledHtml.replace("Mtl. Lizenzgebühr »ILLT«", item.getDescription() != null ? item.getDescription() : "");
filledHtml = filledHtml.replace("5.639,00 €", item.getUnitPrice() != null ? item.getUnitPrice() : "");
filledHtml = filledHtml.replace("5.639,00 €", item.getTotalPrice() != null ? item.getTotalPrice() : "");
}
// Replace amounts
filledHtml = filledHtml.replace("5.639,00 €", data.getNetAmount() != null ? data.getNetAmount() : "");
filledHtml = filledHtml.replace("1.071,41 €", data.getVatAmount() != null ? data.getVatAmount() : "");
filledHtml = filledHtml.replace("6.710,41 €", data.getTotalAmount() != null ? data.getTotalAmount() : "");
return filledHtml;
}
private byte[] generatePdfFromHtmlString(String htmlContent) throws Exception {
// Create PDF using iText library
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Convert HTML to PDF using iText 8 HtmlConverter
HtmlConverter.convertToPdf(htmlContent, baos);
return baos.toByteArray();
}
}

View File

@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Rechnung VOTIAN-LT</title>
<style>
@page { size: A4; margin: 0; }
html, body { margin: 0; padding: 0; width: 21cm; height: 29.7cm; }
body {
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 2cm;
border: 2px solid #1b12b9;
font-family: Arial, sans-serif;
font-size: 12pt;
position: relative;
}
main { flex: 1 0 auto; }
h1, h2 { color: #1b12b9; text-align: center; margin: 0; }
table { width: 100%; border-collapse: collapse; margin-top: 1em; }
th, td { padding: 0.3em; text-align: right; border-top: 1px solid #ddd; vertical-align: top; }
th:first-child, td:first-child { text-align: left; }
.total-row { font-weight: bold; background: #e6e6e6; }
/* Footer exakt 5cm über dem unteren Rand */
.footer-section {
position: absolute;
bottom: 0cm; /* Abstand von unten */
left: 2cm; /* gleiche Einrückung wie body-padding */
right: 2cm;
}
.footer { font-size: 10pt; text-align: center; margin-top: 1em; }
</style>
</head>
<body>
<main>
<h1>Assecutor</h1>
<h2>Data Service GmbH1111</h2>
<p><strong>Empfänger:</strong><br>
Hamburger Hochbahasdfasdasn AG<br>
Kreditorenbuchhaltung<br>
Steinstraße 20<br>
20095 Hamburg
</p>
<p><strong>Absender:</strong><br>
Assecutor Data Service GmbH<br>
Gerhart-Hauptmann-Weg 14, 21502 Geesthacht<br>
Tel.: 040-181237710 · www.assecutor.de
</p>
<p><strong>Datum:</strong> 19.07.2021</p>
<h2>Rechnung HHA-2021-07</h2>
<p>Gemäß unserem Nutzungsvertrag zu der Bestellnummer <strong>45519389</strong>
berechnen wir Ihnen für den Monat Juli 2021 wie folgt:</p>
<table>
<tr>
<th>Menge</th><th>Bezeichnung</th><th>Einzelpreis</th><th>Gesamt</th>
</tr>
<tr>
<td>1</td>
<td style="text-align:left">
Mtl. Lizenzgebühr »ILLT«
</td>
<td>5.639,00 €</td>
<td>5.639,00 €</td>
</tr>
<tr class="total-row">
<td></td><td style="text-align:left">Nettobetrag</td><td></td><td>5.639,00 €</td>
</tr>
<tr>
<td></td><td style="text-align:left">+ 19% MwSt.</td><td></td><td>1.071,41 €</td>
</tr>
<tr class="total-row">
<td></td><td style="text-align:left">Endbetrag</td><td></td><td>6.710,41 €</td>
</tr>
</table>
</main>
<div class="footer-section">
<p><strong>Zahlungsbedingungen:</strong> Gesamtbetrag bis spätestens zum 10. Werktag nach Rechnungserhalt auf unser Konto.</p>
<br>
<p class="footer">
Geschäftsführer: Carsten Annacker, Gunnar Timm · Steuernummer: 22 294 53099 · USt-IdNr.: DE261094748<br>
Sitz: Geesthacht · Handelsregister: Lübeck HRB 8595<br>
Bankverbindung: Hamburger Sparkasse · IBAN DE67200505501217139888 · BIC HASPDEHHXXX
</p>
</div>
</body>
</html>