diff --git a/pom.xml b/pom.xml
index e3a0b4a..6dff52a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -116,12 +116,7 @@
jackson-datatype-jsr310
-
-
- org.apache.pdfbox
- pdfbox
- 3.0.3
-
+
diff --git a/src/main/java/de/assecutor/votianlt/service/SystemInvoiceService.java b/src/main/java/de/assecutor/votianlt/service/SystemInvoiceService.java
index 595667a..823edb5 100644
--- a/src/main/java/de/assecutor/votianlt/service/SystemInvoiceService.java
+++ b/src/main/java/de/assecutor/votianlt/service/SystemInvoiceService.java
@@ -2,418 +2,38 @@ package de.assecutor.votianlt.service;
import de.assecutor.votianlt.model.invoices.SystemInvoiceData;
import de.assecutor.votianlt.model.invoices.SystemInvoiceItem;
-import org.apache.pdfbox.pdmodel.PDDocument;
-import org.apache.pdfbox.pdmodel.PDPage;
-import org.apache.pdfbox.pdmodel.PDPageContentStream;
-import org.apache.pdfbox.pdmodel.common.PDRectangle;
-import org.apache.pdfbox.pdmodel.font.PDType1Font;
-import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.springframework.stereotype.Service;
import com.itextpdf.html2pdf.HtmlConverter;
import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Service
public class SystemInvoiceService {
- private static final float MARGIN = 50;
- private static final float FONT_SIZE = 12;
- private static final float TITLE_FONT_SIZE = 24;
- private static final float SUBTITLE_FONT_SIZE = 16;
- private static final float LINE_HEIGHT = 15;
-
- public byte[] generateInvoicePdf(SystemInvoiceData systemInvoiceData) throws IOException {
- try (PDDocument document = new PDDocument();
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
-
- PDPage page = new PDPage(PDRectangle.A4);
- document.addPage(page);
-
- try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
- float yPosition = page.getMediaBox().getHeight() - MARGIN;
-
- // Draw header
- yPosition = drawHeader(contentStream, yPosition, systemInvoiceData);
- yPosition -= 30;
-
- // Draw company and recipient info
- yPosition = drawAddresses(contentStream, yPosition, systemInvoiceData);
- yPosition -= 30;
-
- // Draw invoice details
- yPosition = drawInvoiceDetails(contentStream, yPosition, systemInvoiceData);
- yPosition -= 30;
-
- // Draw items table
- yPosition = drawItemsTable(contentStream, yPosition, systemInvoiceData);
- yPosition -= 30;
-
- // Draw totals
- yPosition = drawTotals(contentStream, yPosition, systemInvoiceData);
- yPosition -= 30;
-
- // Draw payment terms
- yPosition = drawPaymentTerms(contentStream, yPosition, systemInvoiceData);
-
- // Draw footer at bottom of page
- drawFooter(contentStream, page, systemInvoiceData);
- }
-
- document.save(outputStream);
- return outputStream.toByteArray();
- }
+ public byte[] generateInvoicePdf(SystemInvoiceData systemInvoiceData) throws Exception {
+ // Generate PDF using HTML to PDF conversion
+ return generateInvoicePdfFromHtml();
}
- private float drawHeader(PDPageContentStream contentStream, float yPosition, SystemInvoiceData data) throws IOException {
- // Company name
- contentStream.beginText();
- 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.newLineAtOffset(MARGIN, yPosition);
- contentStream.showText(data.getCompanyName() != null ? data.getCompanyName() : "");
- contentStream.endText();
+ public byte[] generateInvoicePdfFromHtml() throws Exception {
+ // Read the HTML template
+ String htmlContent = readHtmlTemplate();
- yPosition -= 30;
+ // Fill HTML with sample data
+ SystemInvoiceData sampleData = createSampleInvoiceData();
+ String filledHtml = fillHtmlWithInvoiceData(htmlContent, sampleData);
- // Company subtitle
- contentStream.beginText();
- contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), FONT_SIZE);
- contentStream.setNonStrokingColor(27/255f, 18/255f, 185/255f);
- contentStream.newLineAtOffset(MARGIN, yPosition);
- contentStream.showText(data.getCompanySubtitle() != null ? data.getCompanySubtitle() : "");
- contentStream.endText();
-
- yPosition -= 20;
-
- // Draw line
- contentStream.setStrokingColor(27/255f, 18/255f, 185/255f);
- contentStream.setLineWidth(2);
- contentStream.moveTo(MARGIN, yPosition);
- contentStream.lineTo(PDRectangle.A4.getWidth() - MARGIN, yPosition);
- contentStream.stroke();
-
- return yPosition - 10;
- }
-
- private float drawAddresses(PDPageContentStream contentStream, float yPosition, SystemInvoiceData data) throws IOException {
- contentStream.setNonStrokingColor(0f, 0f, 0f); // Black
- contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), FONT_SIZE);
-
- float leftColumn = MARGIN;
- float rightColumn = 350;
-
- // Sender line
- contentStream.beginText();
- contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 8);
- contentStream.newLineAtOffset(leftColumn, yPosition);
- contentStream.showText(data.getSenderLine() != null ? data.getSenderLine() : "");
- contentStream.endText();
-
- yPosition -= 20;
- float recipientStartY = yPosition; // Store starting position for date alignment
-
- // Recipient address
- String[] recipientLines = {
- data.getRecipientName(),
- data.getRecipientDepartment(),
- data.getRecipientStreet(),
- data.getRecipientCity()
- };
-
- contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), FONT_SIZE);
- for (String line : recipientLines) {
- if (line != null && !line.trim().isEmpty()) {
- contentStream.beginText();
- contentStream.newLineAtOffset(leftColumn, yPosition);
- contentStream.showText(line);
- contentStream.endText();
- yPosition -= LINE_HEIGHT;
- }
- }
-
- // Date (right aligned, same vertical level as recipient)
- float dateColumn = 450; // Move further to the right
- contentStream.beginText();
- contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), FONT_SIZE);
- contentStream.newLineAtOffset(dateColumn, recipientStartY);
- contentStream.showText("Datum");
- contentStream.endText();
-
- contentStream.beginText();
- contentStream.newLineAtOffset(dateColumn, recipientStartY - LINE_HEIGHT);
- contentStream.showText(data.getInvoiceDate() != null ? data.getInvoiceDate() : "");
- contentStream.endText();
-
- // Company address (right side)
- float rightYPosition = yPosition + (recipientLines.length * LINE_HEIGHT);
-
- return Math.min(yPosition, rightYPosition) - 10;
- }
-
- private float drawInvoiceDetails(PDPageContentStream contentStream, float yPosition, SystemInvoiceData data) throws IOException {
-
- // Invoice title
- contentStream.beginText();
- contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), SUBTITLE_FONT_SIZE);
- contentStream.newLineAtOffset(MARGIN, yPosition);
- contentStream.showText("Rechnung " + (data.getInvoiceNumber() != null ? data.getInvoiceNumber() : ""));
- contentStream.endText();
-
- yPosition -= 30;
-
- // Invoice text
- contentStream.beginText();
- contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), FONT_SIZE);
- contentStream.newLineAtOffset(MARGIN, yPosition);
-
- // Split long text into multiple lines
- String invoiceText = data.getInvoiceText();
- if (invoiceText != null && invoiceText.length() > 80) {
- String[] words = invoiceText.split(" ");
- StringBuilder currentLine = new StringBuilder();
- for (String word : words) {
- if (currentLine.length() + word.length() + 1 > 80) {
- contentStream.showText(currentLine.toString());
- contentStream.newLineAtOffset(0, -LINE_HEIGHT);
- currentLine = new StringBuilder(word);
- } else {
- if (currentLine.length() > 0) currentLine.append(" ");
- currentLine.append(word);
- }
- }
- if (currentLine.length() > 0) {
- contentStream.showText(currentLine.toString());
- }
- } else {
- contentStream.showText(invoiceText != null ? invoiceText : "");
- }
- contentStream.endText();
-
- return yPosition - 40;
- }
-
- private float drawItemsTable(PDPageContentStream contentStream, float yPosition, SystemInvoiceData data) throws IOException {
- float[] columnWidths = {60, 300, 100, 100}; // Menge, Bezeichnung, Einzelpreis, Gesamt
- float rowHeight = 20;
-
- // Calculate actual table width based on column widths
- float actualTableWidth = 0;
- for (float width : columnWidths) {
- actualTableWidth += width;
- }
-
- // Add 2cm margin to the right of the gray header
- float rightMargin = 2 * 28.35f; // 2cm in points (1cm = 28.35pt)
- float headerWidth = actualTableWidth + rightMargin;
-
- // Table header
- contentStream.setNonStrokingColor(230/255f, 230/255f, 230/255f); // Light gray background
- contentStream.addRect(MARGIN, yPosition - rowHeight, headerWidth, rowHeight);
- contentStream.fill();
-
- contentStream.setNonStrokingColor(0f, 0f, 0f); // Black
- contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), 10);
-
- String[] headers = {"Menge", "Bezeichnung", "Einzelpreis", "Gesamt"};
- float xOffset = MARGIN + 5;
-
- contentStream.beginText();
- contentStream.newLineAtOffset(xOffset, yPosition - 15);
- contentStream.showText(headers[0]);
- contentStream.endText();
-
- xOffset += columnWidths[0];
- contentStream.beginText();
- contentStream.newLineAtOffset(xOffset, yPosition - 15);
- contentStream.showText(headers[1]);
- contentStream.endText();
-
- xOffset += columnWidths[1];
- contentStream.beginText();
- contentStream.newLineAtOffset(xOffset, yPosition - 15);
- contentStream.showText(headers[2]);
- contentStream.endText();
-
- xOffset += columnWidths[2];
- contentStream.beginText();
- contentStream.newLineAtOffset(xOffset, yPosition - 15);
- contentStream.showText(headers[3]);
- contentStream.endText();
-
- yPosition -= rowHeight;
-
- // Table rows
- contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 10);
-
- if (data.getInvoiceItems() != null) {
- for (SystemInvoiceItem item : data.getInvoiceItems()) {
- xOffset = MARGIN + 5;
-
- contentStream.beginText();
- contentStream.newLineAtOffset(xOffset, yPosition - 15);
- contentStream.showText(item.getQuantity() != null ? item.getQuantity() : "");
- contentStream.endText();
-
- xOffset += columnWidths[0];
- contentStream.beginText();
- contentStream.newLineAtOffset(xOffset, yPosition - 15);
- contentStream.showText(item.getDescription() != null ? item.getDescription() : "");
- contentStream.endText();
-
- xOffset += columnWidths[1];
- contentStream.beginText();
- contentStream.newLineAtOffset(xOffset, yPosition - 15);
- contentStream.showText(item.getUnitPrice() != null ? item.getUnitPrice() : "");
- contentStream.endText();
-
- xOffset += columnWidths[2];
- contentStream.beginText();
- contentStream.newLineAtOffset(xOffset, yPosition - 15);
- contentStream.showText(item.getTotalPrice() != null ? item.getTotalPrice() : "");
- contentStream.endText();
-
- yPosition -= rowHeight;
- }
- }
-
- // Add some empty rows for spacing
- for (int i = 0; i < 3; i++) {
- yPosition -= rowHeight;
- }
-
- return yPosition;
- }
-
- private float drawTotals(PDPageContentStream contentStream, float yPosition, SystemInvoiceData data) throws IOException {
- float rightColumn = 400;
-
- contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), FONT_SIZE);
-
- // Net amount
- contentStream.beginText();
- contentStream.newLineAtOffset(rightColumn, yPosition);
- contentStream.showText("Nettobetrag:");
- contentStream.endText();
-
- contentStream.beginText();
- contentStream.newLineAtOffset(rightColumn + 100, yPosition);
- contentStream.showText(data.getNetAmount() != null ? data.getNetAmount() : "");
- contentStream.endText();
-
- yPosition -= LINE_HEIGHT;
-
- // VAT
- contentStream.beginText();
- contentStream.newLineAtOffset(rightColumn, yPosition);
- contentStream.showText("+ " + (data.getVatRate() != null ? data.getVatRate() : "") + "% MwSt.:");
- contentStream.endText();
-
- contentStream.beginText();
- contentStream.newLineAtOffset(rightColumn + 100, yPosition);
- contentStream.showText(data.getVatAmount() != null ? data.getVatAmount() : "");
- contentStream.endText();
-
- yPosition -= LINE_HEIGHT;
-
- // Total amount
- contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), FONT_SIZE);
- contentStream.beginText();
- contentStream.newLineAtOffset(rightColumn, yPosition);
- contentStream.showText("Endbetrag:");
- contentStream.endText();
-
- contentStream.beginText();
- contentStream.newLineAtOffset(rightColumn + 100, yPosition);
- contentStream.showText(data.getTotalAmount() != null ? data.getTotalAmount() : "");
- contentStream.endText();
-
- return yPosition - 30;
- }
-
- private float drawPaymentTerms(PDPageContentStream contentStream, float yPosition, SystemInvoiceData data) throws IOException {
- contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), FONT_SIZE);
-
- // Payment terms with text wrapping
- String paymentTerms = data.getPaymentTerms();
- if (paymentTerms != null && !paymentTerms.trim().isEmpty()) {
- float maxWidth = PDRectangle.A4.getWidth() - 2 * MARGIN;
- yPosition = drawWrappedText(contentStream, paymentTerms, MARGIN, yPosition, maxWidth, FONT_SIZE);
- }
-
- return yPosition - 20;
- }
-
- private float drawWrappedText(PDPageContentStream contentStream, String text, float x, float y, float maxWidth, float fontSize) throws IOException {
- String[] words = text.split(" ");
- StringBuilder currentLine = new StringBuilder();
- float currentY = y;
-
- contentStream.beginText();
- contentStream.newLineAtOffset(x, currentY);
-
- for (String word : words) {
- String testLine = currentLine.length() > 0 ? currentLine + " " + word : word;
-
- // Estimate text width (rough calculation)
- float textWidth = estimateTextWidth(testLine, fontSize);
-
- if (textWidth > maxWidth && currentLine.length() > 0) {
- // Print current line and start new line
- contentStream.showText(currentLine.toString());
- currentLine = new StringBuilder(word);
- currentY -= LINE_HEIGHT;
- contentStream.newLineAtOffset(0, -LINE_HEIGHT);
- } else {
- currentLine = new StringBuilder(testLine);
- }
- }
-
- // Print remaining text
- if (currentLine.length() > 0) {
- contentStream.showText(currentLine.toString());
- currentY -= LINE_HEIGHT;
- }
-
- contentStream.endText();
- return currentY;
- }
-
- private float estimateTextWidth(String text, float fontSize) {
- // Rough estimation: average character width is about 0.6 * font size
- return text.length() * fontSize * 0.6f;
- }
-
- private void drawFooter(PDPageContentStream contentStream, PDPage page, SystemInvoiceData data) throws IOException {
- contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 8);
-
- // Footer text (company details) - positioned at absolute bottom
- String footerText = data.getFooterText();
- if (footerText != null) {
- String[] footerLines = footerText.split("
");
-
- // Calculate footer position from bottom of page
- float lineSpacing = 10; // 10 points between lines
- float footerBottomMargin = 30; // 30 points from absolute bottom
-
- // Start from the bottom and work upwards
- float currentY = footerBottomMargin + (footerLines.length - 1) * lineSpacing;
-
- for (String line : footerLines) {
- contentStream.beginText();
- contentStream.newLineAtOffset(MARGIN, currentY);
- contentStream.showText(line.trim());
- contentStream.endText();
- currentY -= lineSpacing;
- }
- }
+ // Generate PDF from HTML
+ return generatePdfFromHtmlString(filledHtml);
}
public SystemInvoiceData createSampleInvoiceData() {
SystemInvoiceData data = new SystemInvoiceData();
+
+ // Set sample data
data.setInvoiceNumber("HHA-2021-007");
data.setInvoiceDate("19.07.2021");
data.setInvoiceText("Gemäß unserem Nutzungsvertrag zu der Bestellnummer 45519389 berechnen wir Ihnen für den Monat Juli 2021 wie folgt:");
@@ -434,66 +54,49 @@ public class SystemInvoiceService {
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() : "");
+
+ // Replace invoice data placeholders
+ filledHtml = filledHtml.replace("HHA-2021-007", 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:",
+ 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
\n Kreditorenbuchhaltung
\n Steinstraße 20
\n 20095 Hamburg",
- data.getRecipientName() != null ? data.getRecipientName() : "" + "
\n " +
- (data.getRecipientDepartment() != null ? data.getRecipientDepartment() : "") + "
\n " +
- (data.getRecipientStreet() != null ? data.getRecipientStreet() : "") + "
\n " +
- (data.getRecipientCity() != null ? data.getRecipientCity() : ""));
-
+ filledHtml = filledHtml.replace("Hamburger Hochbahn AG", data.getRecipientName() != null ? data.getRecipientName() : "");
+ filledHtml = filledHtml.replace("Kreditorenbuchhaltung", data.getRecipientDepartment() != null ? data.getRecipientDepartment() : "");
+ filledHtml = filledHtml.replace("Steinstraße 20", data.getRecipientStreet() != null ? data.getRecipientStreet() : "");
+ filledHtml = filledHtml.replace("20095 Hamburg", 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() : "");
+ SystemInvoiceItem item = data.getInvoiceItems().getFirst();
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();
}
-
+
}