Erweiterungen
This commit is contained in:
Binary file not shown.
@@ -239,6 +239,9 @@ window.initProfileInvoiceGenerator = function() {
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText('Bild', x + w / 2, y + h / 2);
|
||||
}
|
||||
} else if (el.variable === 'services.list') {
|
||||
// Draw services list as a table
|
||||
drawServicesTable(el, x, y, w, h, fontSize);
|
||||
} else {
|
||||
// Text elements
|
||||
var lines = (el.text || '').split('\n');
|
||||
@@ -274,6 +277,169 @@ window.initProfileInvoiceGenerator = function() {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// Draw services list as a table with columns: Name, Steuersatz, Nettobetrag
|
||||
// Plus summary section below: Nettosumme, USt, Gesamtsumme
|
||||
function drawServicesTable(el, x, y, w, h, fontSize) {
|
||||
var lineHeight = fontSize * 1.4;
|
||||
var padding = 4 * zoomFactor;
|
||||
var rowHeight = lineHeight + padding * 2;
|
||||
var summaryRowHeight = fontSize * 1.6;
|
||||
var summaryGap = fontSize * 0.5;
|
||||
|
||||
// Column widths (percentages of total width)
|
||||
var colNameWidth = w * 0.55; // 55% for Name (left-aligned)
|
||||
var colVatWidth = w * 0.20; // 20% for Steuersatz (right-aligned)
|
||||
var colNetWidth = w * 0.25; // 25% for Nettobetrag (right-aligned)
|
||||
|
||||
var colNameX = x;
|
||||
var colVatX = x + colNameWidth;
|
||||
var colNetX = colVatX + colVatWidth;
|
||||
|
||||
// Calculate actual content height based on table + summary
|
||||
var tableOnlyHeight = rowHeight * 4; // Header + 3 data rows
|
||||
var summaryOnlyHeight = summaryGap + (summaryRowHeight * 3) + summaryGap + summaryRowHeight + summaryGap;
|
||||
var calculatedContentHeight = tableOnlyHeight + summaryOnlyHeight;
|
||||
// Ensure background covers at least the element height or the calculated content
|
||||
var bgHeight = Math.max(h, calculatedContentHeight * zoomFactor);
|
||||
|
||||
// Draw orange background highlight for entire service variable element
|
||||
var bgPadding = 3 * zoomFactor;
|
||||
ctx.fillStyle = 'rgba(255, 152, 0, 0.15)';
|
||||
ctx.fillRect(x - bgPadding, y - (2 * zoomFactor), w + (2 * bgPadding), bgHeight + (4 * zoomFactor));
|
||||
|
||||
// Draw table header (overlays the background)
|
||||
ctx.fillStyle = '#f5f5f5';
|
||||
ctx.fillRect(x, y, w, rowHeight);
|
||||
|
||||
// Header border
|
||||
ctx.strokeStyle = '#cccccc';
|
||||
ctx.lineWidth = Math.max(0.5, zoomFactor);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y + rowHeight);
|
||||
ctx.lineTo(x + w, y + rowHeight);
|
||||
ctx.stroke();
|
||||
|
||||
// Header text
|
||||
ctx.fillStyle = '#333333';
|
||||
ctx.font = 'bold ' + fontSize + 'px Arial';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
// Name column header (left-aligned)
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText('Name', colNameX + padding, y + rowHeight / 2);
|
||||
|
||||
// Steuersatz column header (right-aligned)
|
||||
ctx.textAlign = 'right';
|
||||
ctx.fillText('Steuersatz', colVatX + colVatWidth - padding, y + rowHeight / 2);
|
||||
|
||||
// Nettobetrag column header (right-aligned)
|
||||
ctx.fillText('Nettobetrag', colNetX + colNetWidth - padding, y + rowHeight / 2);
|
||||
|
||||
// Sample data rows (placeholder)
|
||||
var sampleData = [
|
||||
{ name: 'Umzugsleistung inkl. Verpackung', vat: '19%', net: '450,00 €' },
|
||||
{ name: 'Entsorgung Möbel', vat: '19%', net: '85,00 €' },
|
||||
{ name: 'Montage/De-Montage', vat: '19%', net: '120,00 €' }
|
||||
];
|
||||
|
||||
var currentY = y + rowHeight;
|
||||
|
||||
// Draw sample rows
|
||||
ctx.font = fontSize + 'px Arial';
|
||||
sampleData.forEach(function(row, index) {
|
||||
// Draw row background (alternating)
|
||||
if (index % 2 === 1) {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.02)';
|
||||
ctx.fillRect(x, currentY, w, rowHeight);
|
||||
}
|
||||
|
||||
// Row bottom border
|
||||
ctx.strokeStyle = '#eeeeee';
|
||||
ctx.lineWidth = Math.max(0.5, zoomFactor * 0.5);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, currentY + rowHeight);
|
||||
ctx.lineTo(x + w, currentY + rowHeight);
|
||||
ctx.stroke();
|
||||
|
||||
// Draw cell text
|
||||
ctx.fillStyle = '#333333';
|
||||
|
||||
// Name (left-aligned)
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(row.name, colNameX + padding, currentY + rowHeight / 2);
|
||||
|
||||
// Steuersatz (right-aligned)
|
||||
ctx.textAlign = 'right';
|
||||
ctx.fillText(row.vat, colVatX + colVatWidth - padding, currentY + rowHeight / 2);
|
||||
|
||||
// Nettobetrag (right-aligned)
|
||||
ctx.fillText(row.net, colNetX + colNetWidth - padding, currentY + rowHeight / 2);
|
||||
|
||||
currentY += rowHeight;
|
||||
});
|
||||
|
||||
// Draw column separator lines
|
||||
ctx.strokeStyle = '#e0e0e0';
|
||||
ctx.lineWidth = Math.max(0.5, zoomFactor * 0.5);
|
||||
ctx.beginPath();
|
||||
// Line between Name and Steuersatz
|
||||
ctx.moveTo(colVatX, y);
|
||||
ctx.lineTo(colVatX, currentY);
|
||||
// Line between Steuersatz and Nettobetrag
|
||||
ctx.moveTo(colNetX, y);
|
||||
ctx.lineTo(colNetX, currentY);
|
||||
ctx.stroke();
|
||||
|
||||
// Draw outer border around table
|
||||
ctx.strokeStyle = '#cccccc';
|
||||
ctx.lineWidth = Math.max(0.5, zoomFactor);
|
||||
ctx.strokeRect(x, y, w, currentY - y);
|
||||
|
||||
// Draw summary section below the table
|
||||
var summaryY = currentY + summaryGap;
|
||||
|
||||
// Column positions for summary section
|
||||
var labelX = x + colNameWidth + colVatWidth * 0.3; // Label column (left-aligned)
|
||||
var valueX = x + w - padding; // Value column (right-aligned)
|
||||
|
||||
// Calculate totals from sample data
|
||||
var netTotal = 655.00; // 450 + 85 + 120
|
||||
var vatRate = 0.19;
|
||||
var vatTotal = 124.45; // 655 * 0.19
|
||||
var grossTotal = 779.45; // 655 + 124.45
|
||||
|
||||
// Draw summary lines
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
// Nettosumme - label left, value right
|
||||
ctx.fillStyle = '#333333';
|
||||
ctx.font = fontSize + 'px Arial';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText('Nettosumme:', labelX, summaryY + summaryRowHeight / 2);
|
||||
ctx.font = 'bold ' + fontSize + 'px Arial';
|
||||
ctx.textAlign = 'right';
|
||||
ctx.fillText(netTotal.toFixed(2).replace('.', ',') + ' €', valueX, summaryY + summaryRowHeight / 2);
|
||||
summaryY += summaryRowHeight;
|
||||
|
||||
// Umsatzsteuer - label left, value right
|
||||
ctx.font = fontSize + 'px Arial';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText('zzgl. 19% USt:', labelX, summaryY + summaryRowHeight / 2);
|
||||
ctx.font = 'bold ' + fontSize + 'px Arial';
|
||||
ctx.textAlign = 'right';
|
||||
ctx.fillText(vatTotal.toFixed(2).replace('.', ',') + ' €', valueX, summaryY + summaryRowHeight / 2);
|
||||
summaryY += summaryRowHeight;
|
||||
|
||||
// Gesamtsumme - label left, value right
|
||||
summaryY += summaryGap; // Extra gap before total
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.font = 'bold ' + (fontSize * 1.1) + 'px Arial';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText('Gesamtsumme:', labelX, summaryY + summaryRowHeight / 2);
|
||||
ctx.textAlign = 'right';
|
||||
ctx.fillText(grossTotal.toFixed(2).replace('.', ',') + ' €', valueX, summaryY + summaryRowHeight / 2);
|
||||
}
|
||||
|
||||
function drawSelection(el) {
|
||||
var x = pageX + (el.x * zoomFactor);
|
||||
var y = pageY + (el.y * zoomFactor);
|
||||
@@ -285,20 +451,33 @@ window.initProfileInvoiceGenerator = function() {
|
||||
var fontSize = (el.fontSize || 14) * zoomFactor;
|
||||
ctx.font = (el.fontStyle || '') + ' ' + fontSize + 'px Arial';
|
||||
|
||||
var lines = (el.text || '').split('\n');
|
||||
var maxLineWidth = 0;
|
||||
lines.forEach(function(line) {
|
||||
var lineWidth = ctx.measureText(line).width;
|
||||
maxLineWidth = Math.max(maxLineWidth, lineWidth);
|
||||
});
|
||||
// For services.list, calculate table height including summary section
|
||||
if (el.variable === 'services.list') {
|
||||
var lineHeight = fontSize * 1.4;
|
||||
var padding = 4 * zoomFactor;
|
||||
var rowHeight = lineHeight + padding * 2;
|
||||
var tableHeight = rowHeight + 3 * rowHeight; // Header + 3 rows
|
||||
var summaryRowHeight = fontSize * 1.6;
|
||||
var summaryGap = fontSize * 0.5;
|
||||
var summaryHeight = summaryGap + (summaryRowHeight * 3) + summaryGap + summaryRowHeight + summaryGap;
|
||||
var totalHeight = tableHeight + summaryHeight;
|
||||
h = Math.max(h, totalHeight);
|
||||
} else {
|
||||
var lines = (el.text || '').split('\n');
|
||||
var maxLineWidth = 0;
|
||||
lines.forEach(function(line) {
|
||||
var lineWidth = ctx.measureText(line).width;
|
||||
maxLineWidth = Math.max(maxLineWidth, lineWidth);
|
||||
});
|
||||
|
||||
// Use the larger of defined width or actual text width
|
||||
w = Math.max(w, maxLineWidth + (10 * zoomFactor));
|
||||
// Use the larger of defined width or actual text width
|
||||
w = Math.max(w, maxLineWidth + (10 * zoomFactor));
|
||||
|
||||
// Calculate actual height based on number of lines
|
||||
var lineHeight = fontSize * 1.2;
|
||||
var textHeight = lines.length * lineHeight;
|
||||
h = Math.max(h, textHeight + (6 * zoomFactor));
|
||||
// Calculate actual height based on number of lines
|
||||
var lineHeight = fontSize * 1.2;
|
||||
var textHeight = lines.length * lineHeight;
|
||||
h = Math.max(h, textHeight + (6 * zoomFactor));
|
||||
}
|
||||
}
|
||||
|
||||
ctx.strokeStyle = '#1976d2';
|
||||
@@ -307,8 +486,8 @@ window.initProfileInvoiceGenerator = function() {
|
||||
ctx.strokeRect(x, y, w, h);
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// Don't show resize handles for static elements
|
||||
if (el.isStatic) {
|
||||
// Don't show resize handles for static elements (except services.list)
|
||||
if (el.isStatic && el.variable !== 'services.list') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -346,18 +525,30 @@ window.initProfileInvoiceGenerator = function() {
|
||||
var fontSize = (el.fontSize || 14) * zoomFactor;
|
||||
ctx.font = (el.fontStyle || '') + ' ' + fontSize + 'px Arial';
|
||||
|
||||
var lines = (el.text || '').split('\n');
|
||||
var maxLineWidth = 0;
|
||||
lines.forEach(function(line) {
|
||||
var lineWidth = ctx.measureText(line).width;
|
||||
maxLineWidth = Math.max(maxLineWidth, lineWidth);
|
||||
});
|
||||
// For services.list, calculate table height
|
||||
if (el.variable === 'services.list') {
|
||||
var lineHeight = fontSize * 1.4;
|
||||
var padding = 4 * zoomFactor;
|
||||
var rowHeight = lineHeight + padding * 2;
|
||||
var tableHeight = rowHeight + 3 * rowHeight; // Header + 3 rows
|
||||
var summaryRowHeight = fontSize * 1.6;
|
||||
var summaryGap = fontSize * 0.5;
|
||||
var summaryHeight = summaryGap + (summaryRowHeight * 3) + summaryGap + summaryRowHeight + summaryGap;
|
||||
eh = Math.max(eh, tableHeight + summaryHeight);
|
||||
} else {
|
||||
var lines = (el.text || '').split('\n');
|
||||
var maxLineWidth = 0;
|
||||
lines.forEach(function(line) {
|
||||
var lineWidth = ctx.measureText(line).width;
|
||||
maxLineWidth = Math.max(maxLineWidth, lineWidth);
|
||||
});
|
||||
|
||||
ew = Math.max(ew, maxLineWidth + (10 * zoomFactor));
|
||||
ew = Math.max(ew, maxLineWidth + (10 * zoomFactor));
|
||||
|
||||
var lineHeight = fontSize * 1.2;
|
||||
var textHeight = lines.length * lineHeight;
|
||||
eh = Math.max(eh, textHeight + (6 * zoomFactor));
|
||||
var lineHeight = fontSize * 1.2;
|
||||
var textHeight = lines.length * lineHeight;
|
||||
eh = Math.max(eh, textHeight + (6 * zoomFactor));
|
||||
}
|
||||
}
|
||||
|
||||
return x >= ex && x <= ex + ew && y >= ey && y <= ey + eh;
|
||||
@@ -370,12 +561,28 @@ window.initProfileInvoiceGenerator = function() {
|
||||
|
||||
// Helper function to check if a point is on a resize handle
|
||||
function getResizeHandle(x, y, el) {
|
||||
if (!el || el.isStatic) return -1;
|
||||
if (!el) return -1;
|
||||
// Allow resizing for services.list even though it's static
|
||||
if (el.isStatic && el.variable !== 'services.list') return -1;
|
||||
|
||||
var ex = pageX + (el.x * zoomFactor);
|
||||
var ey = pageY + (el.y * zoomFactor);
|
||||
var ew = (el.width || 100) * zoomFactor;
|
||||
var eh = (el.height || 30) * zoomFactor;
|
||||
|
||||
// For services.list, calculate table height including summary
|
||||
if (el.variable === 'services.list') {
|
||||
var fontSize = (el.fontSize || 14) * zoomFactor;
|
||||
var lineHeight = fontSize * 1.4;
|
||||
var padding = 4 * zoomFactor;
|
||||
var rowHeight = lineHeight + padding * 2;
|
||||
var tableHeight = rowHeight + 3 * rowHeight; // Header + 3 rows
|
||||
var summaryRowHeight = fontSize * 1.6;
|
||||
var summaryGap = fontSize * 0.5;
|
||||
var summaryHeight = summaryGap + (summaryRowHeight * 3) + summaryGap + summaryRowHeight + summaryGap;
|
||||
eh = Math.max(eh, tableHeight + summaryHeight);
|
||||
}
|
||||
|
||||
var hs = Math.max(6, 8 * zoomFactor);
|
||||
var halfHs = hs / 2;
|
||||
|
||||
@@ -657,7 +864,21 @@ window.initProfileInvoiceGenerator = function() {
|
||||
el.text = staticText;
|
||||
el.color = '#000000'; // Black text for static elements
|
||||
el.fontStyle = 'bold';
|
||||
el.height = calculateHeight(el.fontSize, 1);
|
||||
// For services.list, set larger dimensions for the table with summary section
|
||||
if (variable === 'services.list') {
|
||||
el.width = 450;
|
||||
var lineHeight = el.fontSize * 1.4;
|
||||
var padding = 4;
|
||||
var rowHeight = lineHeight + padding * 2;
|
||||
var tableHeight = rowHeight + 3 * rowHeight; // Header + 3 sample rows
|
||||
var summaryRowHeight = el.fontSize * 1.6;
|
||||
var summaryGap = el.fontSize * 0.5;
|
||||
var summaryHeight = summaryGap + (summaryRowHeight * 3) + summaryGap + summaryRowHeight + summaryGap;
|
||||
var totalHeight = tableHeight + summaryHeight;
|
||||
el.height = Math.round(totalHeight);
|
||||
} else {
|
||||
el.height = calculateHeight(el.fontSize, 1);
|
||||
}
|
||||
} else {
|
||||
switch (type) {
|
||||
case 'text':
|
||||
|
||||
@@ -2,7 +2,7 @@ package de.assecutor.votianlt.pages.view;
|
||||
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.combobox.ComboBox;
|
||||
|
||||
import com.vaadin.flow.component.grid.Grid;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.H2;
|
||||
@@ -11,7 +11,7 @@ import com.vaadin.flow.component.html.Span;
|
||||
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.textfield.IntegerField;
|
||||
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.router.BeforeEvent;
|
||||
@@ -39,8 +39,6 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
|
||||
private final JobRepository jobRepository;
|
||||
private final ServiceRepository serviceRepository;
|
||||
private final SecurityService securityService;
|
||||
|
||||
private Job currentJob;
|
||||
private List<ServiceRow> gridRows = new ArrayList<>();
|
||||
private Grid<ServiceRow> servicesGrid;
|
||||
@@ -78,8 +76,6 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
SecurityService securityService) {
|
||||
this.jobRepository = jobRepository;
|
||||
this.serviceRepository = serviceRepository;
|
||||
this.securityService = securityService;
|
||||
|
||||
setSizeFull();
|
||||
setPadding(true);
|
||||
setSpacing(true);
|
||||
@@ -256,12 +252,6 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
return servicesSection;
|
||||
}
|
||||
|
||||
private void refreshServicesGrid() {
|
||||
if (servicesGrid != null) {
|
||||
servicesGrid.getDataProvider().refreshAll();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Service> getSelectedServices() {
|
||||
return gridRows.stream().filter(row -> row.getService() != null).map(ServiceRow::getService).toList();
|
||||
}
|
||||
@@ -365,17 +355,6 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
return new BigDecimal("0.19"); // Default 19% VAT
|
||||
}
|
||||
|
||||
private void updateSummarySection() {
|
||||
// Refresh the services grid to update calculated prices
|
||||
refreshServicesGrid();
|
||||
|
||||
// Recreate the summary section to update the values
|
||||
int summarySectionIndex = getComponentCount() - 2; // Summary section is second to last
|
||||
Div newSummarySection = createSummarySection();
|
||||
remove(getComponentAt(summarySectionIndex)); // Remove old summary section
|
||||
addComponentAtIndex(summarySectionIndex, newSummarySection); // Add new summary section
|
||||
}
|
||||
|
||||
private String extractCompanyName(String customerSelection) {
|
||||
if (customerSelection == null || customerSelection.isBlank()) {
|
||||
return "";
|
||||
|
||||
@@ -863,8 +863,8 @@ public class EditProfileView extends HorizontalLayout {
|
||||
panel.setSpacing(true);
|
||||
panel.setHeightFull();
|
||||
|
||||
// Bereich 1: Meine Stammdaten (Variablen)
|
||||
Span invoiceHeader = new Span("Meine Stammdaten (Variablen)");
|
||||
// Bereich 1: Meine Stammdaten
|
||||
Span invoiceHeader = new Span("Meine Stammdaten");
|
||||
invoiceHeader.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-m)")
|
||||
.set("margin-top", "var(--lumo-space-s)");
|
||||
|
||||
@@ -889,8 +889,23 @@ public class EditProfileView extends HorizontalLayout {
|
||||
Div senderPhone = createVariableTemplate("Telefon", VaadinIcon.PHONE, "masterdata.phone",
|
||||
phone.isEmpty() ? "Ihre Telefonnummer" : phone);
|
||||
|
||||
// Bereich 2: Kundendaten (Variablen)
|
||||
Span customerHeader = new Span("Kundendaten (Variablen)");
|
||||
// Bereich 2: Leistungen
|
||||
Span servicesHeader = new Span("Leistungen");
|
||||
servicesHeader.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-m)")
|
||||
.set("margin-top", "var(--lumo-space-m)");
|
||||
|
||||
// Leistungen als draggable Variable
|
||||
Div servicesListBlock = createServicesVariableTemplate("Leistungen auflisten", VaadinIcon.LIST, "services.list",
|
||||
"Artikel 1: 100,00 €\nArtikel 2: 50,00 €");
|
||||
Div servicesNetBlock = createServicesVariableTemplate("Nettosumme", VaadinIcon.COIN_PILES, "services.net_total",
|
||||
"150,00 €");
|
||||
Div servicesVatBlock = createServicesVariableTemplate("Umsatzsteuer", VaadinIcon.COIN_PILES,
|
||||
"services.vat_total", "28,50 €");
|
||||
Div servicesGrossBlock = createServicesVariableTemplate("Bruttosumme", VaadinIcon.MONEY, "services.gross_total",
|
||||
"178,50 €");
|
||||
|
||||
// Bereich 3: Kundendaten
|
||||
Span customerHeader = new Span("Kundendaten");
|
||||
customerHeader.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-m)")
|
||||
.set("margin-top", "var(--lumo-space-m)");
|
||||
|
||||
@@ -924,6 +939,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
Div imageBlock = createDraggableTemplate("Bild", VaadinIcon.PICTURE, "image");
|
||||
|
||||
panel.add(invoiceHeader, senderCompany, senderName, senderAddress, senderCity, senderEmail, senderPhone,
|
||||
servicesHeader, servicesListBlock, servicesNetBlock, servicesVatBlock, servicesGrossBlock,
|
||||
customerHeader, customerCompany, customerName, customerAddress, customerCity, customerEmail,
|
||||
customerPhone, freeHeader, textBlock, headerBlock, dateBlock, customerBlock, companyBlock, amountBlock,
|
||||
lineBlock, imageBlock);
|
||||
@@ -1000,6 +1016,43 @@ public class EditProfileView extends HorizontalLayout {
|
||||
return template;
|
||||
}
|
||||
|
||||
private Div createServicesVariableTemplate(String label, VaadinIcon icon, String variable, String defaultText) {
|
||||
Div template = new Div();
|
||||
template.setText(label);
|
||||
template.getStyle().set("padding", "var(--lumo-space-s)").set("margin", "var(--lumo-space-xs) 0")
|
||||
.set("background-color", "rgba(255, 193, 7, 0.15)") // Amber/gelb für Leistungen
|
||||
.set("border", "1px solid rgba(255, 193, 7, 0.4)").set("border-radius", "var(--lumo-border-radius-m)")
|
||||
.set("cursor", "grab").set("display", "flex").set("align-items", "center")
|
||||
.set("gap", "var(--lumo-space-s)").set("user-select", "none")
|
||||
.set("font-size", "var(--lumo-font-size-s)").set("color", "#f57c00"); // Dunkles Orange
|
||||
|
||||
Icon templateIcon = icon.create();
|
||||
templateIcon.setSize("var(--lumo-icon-size-s)");
|
||||
template.getElement().insertChild(0, templateIcon.getElement());
|
||||
|
||||
template.getElement().setAttribute("draggable", "true");
|
||||
template.getElement().setAttribute("data-template-type", "variable");
|
||||
template.getElement().setAttribute("data-template-label", label);
|
||||
template.getElement().setAttribute("data-variable", variable);
|
||||
template.getElement().setAttribute("data-static-text", defaultText);
|
||||
template.getElement().setAttribute("data-is-customer", "false");
|
||||
template.getElement().setAttribute("data-is-services", "true");
|
||||
|
||||
template.getElement()
|
||||
.executeJs("this.addEventListener('dragstart', function(e) {"
|
||||
+ " e.dataTransfer.setData('template-type', this.getAttribute('data-template-type'));"
|
||||
+ " e.dataTransfer.setData('template-label', this.getAttribute('data-template-label'));"
|
||||
+ " e.dataTransfer.setData('variable', this.getAttribute('data-variable'));"
|
||||
+ " e.dataTransfer.setData('static-text', this.getAttribute('data-static-text'));"
|
||||
+ " e.dataTransfer.setData('is-static', 'true');"
|
||||
+ " e.dataTransfer.setData('is-customer', this.getAttribute('data-is-customer'));"
|
||||
+ " e.dataTransfer.setData('is-services', this.getAttribute('data-is-services'));"
|
||||
+ " this.style.opacity = '0.5';" + "});" + "this.addEventListener('dragend', function(e) {"
|
||||
+ " this.style.opacity = '1';" + "});");
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
private Div createDraggableTemplate(String label, VaadinIcon icon, String type) {
|
||||
Div template = new Div();
|
||||
template.setText(label);
|
||||
@@ -1328,6 +1381,10 @@ public class EditProfileView extends HorizontalLayout {
|
||||
case "customer.city" -> "PLZ und Ort des Kunden";
|
||||
case "customer.email" -> "E-Mail des Kunden";
|
||||
case "customer.phone" -> "Telefon des Kunden";
|
||||
case "services.list" -> "Liste aller Leistungen auf der Rechnung";
|
||||
case "services.net_total" -> "Nettosumme aller Leistungen";
|
||||
case "services.vat_total" -> "Umsatzsteuer aller Leistungen";
|
||||
case "services.gross_total" -> "Bruttosumme aller Leistungen";
|
||||
default -> "Variable: " + variable;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -372,8 +372,13 @@ public class CustomerInvoiceService {
|
||||
if (fontStyle.contains("bold"))
|
||||
htmlBuilder.append("font-weight:bold;");
|
||||
}
|
||||
// Vertically center content
|
||||
htmlBuilder.append("display:flex;align-items:center;");
|
||||
// For services.list use block display to allow table to fill width
|
||||
if ("services.list".equals(variable)) {
|
||||
htmlBuilder.append("display:block;overflow:visible;padding:0;");
|
||||
} else {
|
||||
// Vertically center content for other elements
|
||||
htmlBuilder.append("display:flex;align-items:center;");
|
||||
}
|
||||
htmlBuilder.append("'");
|
||||
htmlBuilder.append(">");
|
||||
|
||||
@@ -425,6 +430,9 @@ public class CustomerInvoiceService {
|
||||
htmlBuilder.append(
|
||||
"<div style='width:100%;height:100%;background:#f0f0f0;display:flex;align-items:center;justify-content:center;font-size:10pt;color:#666;'>[Bild]</div>");
|
||||
}
|
||||
} else if ("services.list".equals(variable)) {
|
||||
// Render services list as a table
|
||||
htmlBuilder.append(generateServicesTableHtml(mmWidth));
|
||||
} else {
|
||||
// Wrap text in a span to prevent flexbox issues
|
||||
htmlBuilder.append("<span style='white-space:nowrap;'>").append(text).append("</span>");
|
||||
@@ -443,4 +451,81 @@ public class CustomerInvoiceService {
|
||||
private String safe(String value) {
|
||||
return value != null ? value : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HTML table for services list with summary section below.
|
||||
*/
|
||||
private String generateServicesTableHtml(double widthMm) {
|
||||
StringBuilder html = new StringBuilder();
|
||||
|
||||
// Sample data for preview (will be replaced with actual job data later)
|
||||
String[][] sampleData = {
|
||||
{"Umzugsleistung inkl. Verpackung", "19%", "450,00 €"},
|
||||
{"Entsorgung Möbel", "19%", "85,00 €"},
|
||||
{"Montage/De-Montage", "19%", "120,00 €"}
|
||||
};
|
||||
|
||||
// Calculate totals
|
||||
double netTotal = 655.00;
|
||||
double vatTotal = 124.45;
|
||||
double grossTotal = 779.45;
|
||||
|
||||
// Wrapper div
|
||||
html.append("<div style='width:100%;box-sizing:border-box;'>");
|
||||
|
||||
// Table
|
||||
html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
|
||||
|
||||
// Header row
|
||||
html.append("<tr style='background-color:#f5f5f5;border-bottom:1px solid #cccccc;'>");
|
||||
html.append("<th style='text-align:left;padding:4px 8px;font-weight:bold;width:55%;white-space:nowrap;'>Name</th>");
|
||||
html.append("<th style='text-align:right;padding:4px 8px;font-weight:bold;width:20%;white-space:nowrap;'>Steuersatz</th>");
|
||||
html.append("<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>");
|
||||
html.append("</tr>");
|
||||
|
||||
// Data rows
|
||||
for (int i = 0; i < sampleData.length; i++) {
|
||||
String bgColor = (i % 2 == 1) ? "background-color:rgba(0,0,0,0.02);" : "";
|
||||
html.append("<tr style='").append(bgColor).append("border-bottom:1px solid #eeeeee;'>");
|
||||
html.append("<td style='text-align:left;padding:4px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>").append(sampleData[i][0]).append("</td>");
|
||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(sampleData[i][1]).append("</td>");
|
||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(sampleData[i][2]).append("</td>");
|
||||
html.append("</tr>");
|
||||
}
|
||||
|
||||
html.append("</table>");
|
||||
|
||||
// Summary section
|
||||
html.append("<div style='margin-top:8px;width:100%;'>");
|
||||
|
||||
// Summary table for alignment with proper column separation
|
||||
html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
|
||||
|
||||
// Nettosumme - label in col 2, value in col 3
|
||||
html.append("<tr>");
|
||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>Nettosumme:</td>");
|
||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>").append(String.format(java.util.Locale.GERMANY, "%,.2f €", netTotal)).append("</td>");
|
||||
html.append("</tr>");
|
||||
|
||||
// Umsatzsteuer - label in col 2, value in col 3
|
||||
html.append("<tr>");
|
||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>zzgl. 19% USt:</td>");
|
||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>").append(String.format(java.util.Locale.GERMANY, "%,.2f €", vatTotal)).append("</td>");
|
||||
html.append("</tr>");
|
||||
|
||||
// Gesamtsumme - label in col 2, value in col 3
|
||||
html.append("<tr>");
|
||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||
html.append("<td style='width:20%;text-align:left;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>Gesamtsumme:</td>");
|
||||
html.append("<td style='width:25%;text-align:right;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>").append(String.format(java.util.Locale.GERMANY, "%,.2f €", grossTotal)).append("</td>");
|
||||
html.append("</tr>");
|
||||
|
||||
html.append("</table>");
|
||||
html.append("</div>");
|
||||
html.append("</div>");
|
||||
|
||||
return html.toString();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user