Erweiterungen

This commit is contained in:
2025-09-18 21:40:51 +02:00
parent 95eaabf41d
commit 79169cf9fd
3 changed files with 172 additions and 93 deletions

19
AGENTS.md Normal file
View File

@@ -0,0 +1,19 @@
# Repository Guidelines
## Project Structure & Module Organization
Backend Java sits in `src/main/java/de/assecutor/votianlt`; domain models stay in `model`, persistence logic in `repository`, services in `service`, MQTT integration under `mqtt`, and access control in `security`. Vaadin views and UI helpers live in `pages/view` and `pages/base`. TypeScript, styles, and theming live in `src/main/frontend` (leave `generated/` untouched). Shared configs and templates live in `src/main/resources`, while Vaadin bundle descriptors reside in `src/main/bundles`. Maven output lands in `target/`.
## Build, Test, and Development Commands
Use `./mvnw` for the Spring Boot dev server with frontend hot reload. Build production bits with `./mvnw -Pproduction package`. Run `./mvnw test` for unit checks and `./mvnw -Pintegration-test verify` when integration coverage is needed. After dependency changes, refresh Vaadin assets with `./mvnw vaadin:prepare-frontend`. Apply formatting via `./mvnw spotless:apply`.
## Coding Style & Naming Conventions
Spotless enforces Java 21 formatting using `eclipse-formatter.xml`; keep imports ordered and rely on Lombok already present. Classes remain PascalCase, Spring stereotypes end with `Service`, `Repository`, or `Config`, and Vaadin views retain the `*View` naming within `pages/view`. Frontend code follows the repos Prettier rules (`.prettierrc.json`); keep TypeScript modules co-located with their views, prefer camelCase for variables, and avoid checking in generated `.class` files.
## Testing Guidelines
Create tests under `src/test/java` mirroring the production package path. Name unit classes `*Test` and integration suites `*IT` so the failsafe profile picks them up. Lean on Spring Boots testing annotations for wiring, Mockito for isolates, and add Testcontainers when MongoDB or MQTT brokers are involved. Run `./mvnw test` before any push; trigger `./mvnw -Pintegration-test verify` for messaging, persistence, or security changes.
## Commit & Pull Request Guidelines
History currently uses brief German titles; shift to imperative, scoped summaries such as `feat: add PDF mailer` or `fix: guard MQTT reconnects`. Keep unrelated updates out of the same commit and exclude artifacts like `node_modules/` or `target/`. Pull requests should explain the motivation, link issues, note config or data-seed impacts, and attach screenshots or screencasts when Vaadin views change. List manual verification steps and flag any migrations or bundle adjustments for reviewers.
## Security & Configuration Tips
External service credentials for MongoDB, SMTP, and MQTT belong in environment variables or a developer-specific `application-local.properties` kept out of version control. Document default ports and topics when touching `MqttConfig` or Zeroconf publishers so ops can replicate environments. For two-factor flows, keep shared secrets in secure storage and avoid logging codes during development.

View File

@@ -209,13 +209,22 @@ public class PdfBoxService {
}
private float drawItemsTable(PDPageContentStream contentStream, float yPosition, InvoiceData data) throws IOException {
float tableWidth = PDRectangle.A4.getWidth() - 2 * MARGIN;
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, tableWidth, rowHeight);
contentStream.addRect(MARGIN, yPosition - rowHeight, headerWidth, rowHeight);
contentStream.fill();
contentStream.setNonStrokingColor(0f, 0f, 0f); // Black
@@ -340,37 +349,77 @@ public class PdfBoxService {
private float drawPaymentTerms(PDPageContentStream contentStream, float yPosition, InvoiceData data) throws IOException {
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), FONT_SIZE);
// Payment terms
contentStream.beginText();
contentStream.newLineAtOffset(MARGIN, yPosition);
contentStream.showText(data.getPaymentTerms() != null ? data.getPaymentTerms() : "");
contentStream.endText();
// 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 - 40;
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, InvoiceData data) throws IOException {
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 8);
// Calculate footer position - start from bottom of page
float footerStartY = MARGIN + 60; // 60 points from bottom
// Footer text (company details) - positioned at bottom
// Footer text (company details) - positioned at absolute bottom
String footerText = data.getFooterText();
if (footerText != null) {
String[] footerLines = footerText.split("<br>");
float currentY = footerStartY;
// Calculate the total height needed for footer text
float totalFooterHeight = footerLines.length * 12;
currentY = footerStartY + totalFooterHeight - 12; // Start from top of footer area
// 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 -= 12;
currentY -= lineSpacing;
}
}
}

View File

@@ -9,6 +9,11 @@
margin: 1.5cm 1cm 1.5cm 2cm;
}
html,
body {
height: 100%;
}
body {
font-family: Arial, sans-serif;
font-size: 12pt;
@@ -17,6 +22,8 @@
border: 2px solid #1b12b9;
max-width: 21.001cm;
background-color: transparent;
display: flex;
flex-direction: column;
}
.header {
@@ -162,11 +169,16 @@
background-color: #e6e6e6;
}
.page-content {
flex: 1 0 auto;
}
.footer-text {
text-align: center;
font-size: 8pt;
font-family: Arial;
margin-top: 2cm;
margin-top: auto;
padding: 0.5cm 1cm;
}
.clear {
@@ -175,6 +187,7 @@
</style>
</head>
<body>
<div class="page-content">
<div class="header">
<p class="company-name">
<b>{{companyName}}</b>
@@ -248,10 +261,8 @@
</tr>
</table>
<br>
<p class="text-body">{{paymentTerms}}</p>
<br><br><br>
</div>
<p class="footer-text">
<span>{{footerText}}</span>
</p>