Erweiterungen
This commit is contained in:
19
AGENTS.md
Normal file
19
AGENTS.md
Normal 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 repo’s 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 Boot’s 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.
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,83 +187,82 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<p class="company-name">
|
||||
<b>{{companyName}}</b>
|
||||
<div class="page-content">
|
||||
<div class="header">
|
||||
<p class="company-name">
|
||||
<b>{{companyName}}</b>
|
||||
</p>
|
||||
<hr>
|
||||
<p class="company-subtitle">
|
||||
<b>{{companySubtitle}}</b>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="recipient-box">
|
||||
<p class="sender-line">{{senderLine}}</p>
|
||||
<p class="recipient-content">{{recipientName}}</p>
|
||||
<p class="recipient-content">{{recipientDepartment}}</p>
|
||||
<p class="recipient-content">{{recipientStreet}}</p>
|
||||
<p class="recipient-content">{{recipientCity}}</p>
|
||||
</div>
|
||||
|
||||
<div class="company-address">
|
||||
<p>{{companyStreet}}</p>
|
||||
<p>{{companyCity}}</p>
|
||||
<p> </p>
|
||||
<p>Tel.: {{companyPhone}}</p>
|
||||
<p>{{companyWebsite}}</p>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
|
||||
<p class="contact-info">
|
||||
Fax
|
||||
<span style="margin-left: 60px">eMail</span>
|
||||
<br/>
|
||||
{{companyFax}}
|
||||
<span>{{companyEmail}}</span>
|
||||
</p>
|
||||
<hr>
|
||||
<p class="company-subtitle">
|
||||
<b>{{companySubtitle}}</b>
|
||||
|
||||
<p class="invoice-date">
|
||||
<span>Datum<br></span>
|
||||
<span>{{invoiceDate}}</span>
|
||||
</p>
|
||||
|
||||
<p class="subject">Rechnung {{invoiceNumber}}</p>
|
||||
|
||||
<p class="text-body">{{invoiceText}}</p>
|
||||
|
||||
<table class="invoice-table">
|
||||
<tr>
|
||||
<td class="table-header" style="width:2cm;">Menge</td>
|
||||
<td class="table-header" style="width:9.999cm;">Bezeichnung</td>
|
||||
<td class="table-header" style="width:3cm;">Einzelpreis</td>
|
||||
<td class="table-header" style="width:3cm;">Gesamt</td>
|
||||
</tr>
|
||||
{{invoiceItems}}
|
||||
<tr>
|
||||
<td class="table-cell" style="width:2cm;"> </td>
|
||||
<td class="table-cell" style="width:9.999cm;"> </td>
|
||||
<td class="table-header" style="width:3cm;">Nettobetrag</td>
|
||||
<td class="table-cell summary-row table-cell-right" style="width:3cm;">{{netAmount}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="table-cell" style="width:2cm;"> </td>
|
||||
<td class="table-cell" style="width:9.999cm;"> </td>
|
||||
<td class="table-cell table-cell-right" style="width:3cm;">+ {{vatRate}}% MwSt.</td>
|
||||
<td class="table-cell table-cell-right" style="width:3cm;">{{vatAmount}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="table-cell" style="width:2cm;"> </td>
|
||||
<td class="table-cell" style="width:9.999cm;"> </td>
|
||||
<td class="table-header" style="width:3cm;">Endbetrag</td>
|
||||
<td class="table-cell summary-row table-cell-right" style="width:3cm;">{{totalAmount}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p class="text-body">{{paymentTerms}}</p>
|
||||
</div>
|
||||
|
||||
<div class="recipient-box">
|
||||
<p class="sender-line">{{senderLine}}</p>
|
||||
<p class="recipient-content">{{recipientName}}</p>
|
||||
<p class="recipient-content">{{recipientDepartment}}</p>
|
||||
<p class="recipient-content">{{recipientStreet}}</p>
|
||||
<p class="recipient-content">{{recipientCity}}</p>
|
||||
</div>
|
||||
|
||||
<div class="company-address">
|
||||
<p>{{companyStreet}}</p>
|
||||
<p>{{companyCity}}</p>
|
||||
<p> </p>
|
||||
<p>Tel.: {{companyPhone}}</p>
|
||||
<p>{{companyWebsite}}</p>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
|
||||
<p class="contact-info">
|
||||
Fax
|
||||
<span style="margin-left: 60px">eMail</span>
|
||||
<br/>
|
||||
{{companyFax}}
|
||||
<span>{{companyEmail}}</span>
|
||||
</p>
|
||||
|
||||
<p class="invoice-date">
|
||||
<span>Datum<br></span>
|
||||
<span>{{invoiceDate}}</span>
|
||||
</p>
|
||||
|
||||
<p class="subject">Rechnung {{invoiceNumber}}</p>
|
||||
|
||||
<p class="text-body">{{invoiceText}}</p>
|
||||
|
||||
<table class="invoice-table">
|
||||
<tr>
|
||||
<td class="table-header" style="width:2cm;">Menge</td>
|
||||
<td class="table-header" style="width:9.999cm;">Bezeichnung</td>
|
||||
<td class="table-header" style="width:3cm;">Einzelpreis</td>
|
||||
<td class="table-header" style="width:3cm;">Gesamt</td>
|
||||
</tr>
|
||||
{{invoiceItems}}
|
||||
<tr>
|
||||
<td class="table-cell" style="width:2cm;"> </td>
|
||||
<td class="table-cell" style="width:9.999cm;"> </td>
|
||||
<td class="table-header" style="width:3cm;">Nettobetrag</td>
|
||||
<td class="table-cell summary-row table-cell-right" style="width:3cm;">{{netAmount}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="table-cell" style="width:2cm;"> </td>
|
||||
<td class="table-cell" style="width:9.999cm;"> </td>
|
||||
<td class="table-cell table-cell-right" style="width:3cm;">+ {{vatRate}}% MwSt.</td>
|
||||
<td class="table-cell table-cell-right" style="width:3cm;">{{vatAmount}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="table-cell" style="width:2cm;"> </td>
|
||||
<td class="table-cell" style="width:9.999cm;"> </td>
|
||||
<td class="table-header" style="width:3cm;">Endbetrag</td>
|
||||
<td class="table-cell summary-row table-cell-right" style="width:3cm;">{{totalAmount}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
<p class="text-body">{{paymentTerms}}</p>
|
||||
<br><br><br>
|
||||
|
||||
<p class="footer-text">
|
||||
<span>{{footerText}}</span>
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user