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(); } - + }