diff --git a/pom.xml b/pom.xml
index b31a52f..869cb80 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
de.assecutor.votianlt
votianlt
- 0.9.4
+ 0.9.5
jar
diff --git a/src/main/java/de/assecutor/votianlt/controller/MessageController.java b/src/main/java/de/assecutor/votianlt/controller/MessageController.java
index b27638a..47cce95 100644
--- a/src/main/java/de/assecutor/votianlt/controller/MessageController.java
+++ b/src/main/java/de/assecutor/votianlt/controller/MessageController.java
@@ -380,7 +380,11 @@ public class MessageController {
return;
}
- boolean allCompleted = allTasks.stream().allMatch(task -> task.isCompleted());
+ var mandatoryTasks = allTasks.stream().filter(task -> !task.isOptional()).toList();
+ if (mandatoryTasks.isEmpty()) {
+ return;
+ }
+ boolean allCompleted = mandatoryTasks.stream().allMatch(task -> task.isCompleted());
if (allCompleted) {
updateJobStatusToCompleted(jobId);
diff --git a/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java b/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java
index 555d355..64617f6 100644
--- a/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java
+++ b/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java
@@ -8,6 +8,7 @@ import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.datepicker.DatePicker;
+import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.timepicker.TimePicker;
import com.vaadin.flow.component.html.H3;
@@ -797,38 +798,42 @@ public class AddJobView extends Main implements HasDynamicTitle {
summaryTitle.getStyle().set("margin", "0");
summaryLayout.add(summaryTitle);
- // Net total
- HorizontalLayout netRow = new HorizontalLayout();
- netRow.setWidthFull();
- netRow.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
- Span netLabel = new Span(getTranslation("addjob.summary.net") + ":");
+ Div priceTable = new Div();
+ priceTable.getStyle().set("width", "100%");
+
+ // Net total row
+ Div netRow = new Div();
+ netRow.getStyle().set("display", "flex").set("justify-content", "space-between").set("padding", "4px 0");
+ Span netLabelSpan = new Span(getTranslation("addjob.summary.net") + ":");
+ netLabelSpan.getStyle().set("padding-right", "8px");
netTotalLabel = new Span("0,00 €");
- netTotalLabel.getStyle().set("font-weight", "bold");
- netRow.add(netLabel, netTotalLabel);
- summaryLayout.add(netRow);
+ netTotalLabel.getStyle().set("font-weight", "bold").set("white-space", "nowrap");
+ netRow.add(netLabelSpan, netTotalLabel);
+ priceTable.add(netRow);
- // VAT total
- HorizontalLayout vatRow = new HorizontalLayout();
- vatRow.setWidthFull();
- vatRow.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
- Span vatLabel = new Span(getTranslation("addjob.summary.vat") + ":");
+ // VAT total row
+ Div vatRow = new Div();
+ vatRow.getStyle().set("display", "flex").set("justify-content", "space-between").set("padding", "4px 0");
+ Span vatLabelSpan = new Span(getTranslation("addjob.summary.vat") + ":");
+ vatLabelSpan.getStyle().set("padding-right", "8px");
vatTotalLabel = new Span("0,00 €");
- vatTotalLabel.getStyle().set("font-weight", "bold");
- vatRow.add(vatLabel, vatTotalLabel);
- summaryLayout.add(vatRow);
+ vatTotalLabel.getStyle().set("font-weight", "bold").set("white-space", "nowrap");
+ vatRow.add(vatLabelSpan, vatTotalLabel);
+ priceTable.add(vatRow);
- // Gross total
- HorizontalLayout grossRow = new HorizontalLayout();
- grossRow.setWidthFull();
- grossRow.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
- Span grossLabel = new Span(getTranslation("addjob.summary.gross") + ":");
- grossLabel.getStyle().set("font-size", "var(--lumo-font-size-l)");
+ // Gross total row
+ Div grossRow = new Div();
+ grossRow.getStyle().set("display", "flex").set("justify-content", "space-between").set("padding", "4px 0");
+ Span grossLabelSpan = new Span(getTranslation("addjob.summary.gross") + ":");
+ grossLabelSpan.getStyle().set("padding-right", "8px").set("font-weight", "bold");
grossTotalLabel = new Span("0,00 €");
grossTotalLabel.getStyle().set("font-size", "var(--lumo-font-size-l)");
grossTotalLabel.getStyle().set("font-weight", "bold");
- grossTotalLabel.getStyle().set("color", "var(--lumo-primary-text-color)");
- grossRow.add(grossLabel, grossTotalLabel);
- summaryLayout.add(grossRow);
+ grossTotalLabel.getStyle().set("color", "var(--lumo-primary-text-color)").set("white-space", "nowrap");
+ grossRow.add(grossLabelSpan, grossTotalLabel);
+ priceTable.add(grossRow);
+
+ summaryLayout.add(priceTable);
content.add(summaryLayout);
diff --git a/src/main/java/de/assecutor/votianlt/pages/view/CreateInvoiceView.java b/src/main/java/de/assecutor/votianlt/pages/view/CreateInvoiceView.java
index 0087c41..8dcb797 100644
--- a/src/main/java/de/assecutor/votianlt/pages/view/CreateInvoiceView.java
+++ b/src/main/java/de/assecutor/votianlt/pages/view/CreateInvoiceView.java
@@ -316,24 +316,40 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
BigDecimal vatAmount = netAmount.multiply(vatRate);
BigDecimal totalAmount = netAmount.add(vatAmount);
- VerticalLayout summaryInfo = new VerticalLayout();
- summaryInfo.setSpacing(true);
- summaryInfo.setWidthFull();
+ Div priceTable = new Div();
+ priceTable.getStyle().set("width", "100%");
- // Show only net sum, VAT sums, and total amount without individual services
- summaryInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.summary.net")),
- new Span(netAmount.setScale(2, RoundingMode.HALF_UP) + " €")));
- summaryInfo.add(new HorizontalLayout(
- new Span(getTranslation("createinvoice.summary.vat",
- vatRate.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP).toString())),
- new Span(vatAmount.setScale(2, RoundingMode.HALF_UP) + " €")));
- summaryInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.summary.total")),
- new Span(totalAmount.setScale(2, RoundingMode.HALF_UP) + " €")));
+ priceTable.add(createPriceRow(getTranslation("createinvoice.summary.net") + ":",
+ netAmount.setScale(2, RoundingMode.HALF_UP) + " €", false));
+ priceTable.add(createPriceRow(
+ getTranslation("createinvoice.summary.vat",
+ vatRate.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP).toString()) + ":",
+ vatAmount.setScale(2, RoundingMode.HALF_UP) + " €", false));
+ priceTable.add(createPriceRow(getTranslation("createinvoice.summary.total") + ":",
+ totalAmount.setScale(2, RoundingMode.HALF_UP) + " €", true));
- section.add(summaryInfo);
+ section.add(priceTable);
return section;
}
+ private Div createPriceRow(String label, String value, boolean bold) {
+ Div row = new Div();
+ row.getStyle().set("display", "flex").set("justify-content", "space-between").set("padding", "4px 0");
+
+ Span labelSpan = new Span(label);
+ labelSpan.getStyle().set("padding-right", "8px");
+
+ Span valueSpan = new Span(value);
+ valueSpan.getStyle().set("white-space", "nowrap");
+ if (bold) {
+ labelSpan.getStyle().set("font-weight", "bold");
+ valueSpan.getStyle().set("font-weight", "bold");
+ }
+
+ row.add(labelSpan, valueSpan);
+ return row;
+ }
+
private BigDecimal calculateServicePrice(Service service) {
if (service.getCalculationBasis() == null) {
return null;
diff --git a/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java b/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java
index dd69854..a92bbcc 100644
--- a/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java
+++ b/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java
@@ -271,9 +271,15 @@ public class JobSummaryView extends Main implements HasUrlParameter, Has
// Preis basierend auf den hinterlegten Leistungen berechnen
PriceCalculationResult priceResult = calculatePriceFromServices(job);
- infoBox.add(new Span(getTranslation("jobsummary.info.netto") + ": " + formatPrice(priceResult.netAmount())));
- infoBox.add(new Span(getTranslation("jobsummary.info.ust") + ": " + formatPrice(priceResult.vatAmount())));
- infoBox.add(new Span(getTranslation("jobsummary.info.gesamt") + ": " + formatPrice(priceResult.totalAmount())));
+
+ Div priceTable = new Div();
+ priceTable.getStyle().set("width", "100%");
+
+ priceTable.add(createPriceRow(getTranslation("jobsummary.info.netto") + ":", formatPrice(priceResult.netAmount()), false));
+ priceTable.add(createPriceRow(getTranslation("jobsummary.info.ust") + ":", formatPrice(priceResult.vatAmount()), false));
+ priceTable.add(createPriceRow(getTranslation("jobsummary.info.gesamt") + ":", formatPrice(priceResult.totalAmount()), true));
+
+ infoBox.add(priceTable);
if (job.getRemark() != null && !job.getRemark().isBlank()) {
infoBox.add(new Span(getTranslation("jobsummary.info.bemerkung") + ": " + job.getRemark()));
@@ -411,6 +417,24 @@ public class JobSummaryView extends Main implements HasUrlParameter, Has
return nf.format(price);
}
+ private Div createPriceRow(String label, String value, boolean bold) {
+ Div row = new Div();
+ row.getStyle().set("display", "flex").set("justify-content", "space-between").set("padding", "4px 0");
+
+ Span labelSpan = new Span(label);
+ labelSpan.getStyle().set("padding-right", "8px");
+
+ Span valueSpan = new Span(value);
+ valueSpan.getStyle().set("white-space", "nowrap");
+ if (bold) {
+ labelSpan.getStyle().set("font-weight", "bold");
+ valueSpan.getStyle().set("font-weight", "bold");
+ }
+
+ row.add(labelSpan, valueSpan);
+ return row;
+ }
+
private String resolveAppUserName(String appUserIdString) {
try {
ObjectId id = new ObjectId(appUserIdString);
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index 19b902f..308a840 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -625,6 +625,7 @@ jobs.notification.deleted=Auftrag {0} wurde gelöscht
jobs.notification.delete.error=Fehler beim Löschen: {0}
# Create Invoice
+createinvoice.title=Rechnung erstellen \u2013 Auftrag {0}
createinvoice.error.invalidid=Ungültige Job-ID
createinvoice.error.notfound=Job nicht gefunden
createinvoice.button.create=Rechnung erstellen
@@ -641,6 +642,7 @@ createinvoice.route.duration=Fahrtzeit
createinvoice.column.service=Leistung
createinvoice.column.basis=Berechnungsbasis
createinvoice.summary.net=Nettosumme
+createinvoice.summary.vat=MwSt. ({0}%)
createinvoice.summary.total=Gesamtsumme
createinvoice.notification.noservices=Bitte wählen Sie mindestens eine Leistung aus
createinvoice.notification.nouser=Benutzer nicht gefunden
diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties
index 2f7b1b2..830b5f2 100644
--- a/src/main/resources/messages_en.properties
+++ b/src/main/resources/messages_en.properties
@@ -625,6 +625,7 @@ jobs.notification.deleted=Job {0} deleted
jobs.notification.delete.error=Error deleting job: {0}
# Create Invoice
+createinvoice.title=Create Invoice \u2013 Job {0}
createinvoice.error.invalidid=Invalid Job ID
createinvoice.error.notfound=Job not found
createinvoice.button.create=Create Invoice
@@ -641,6 +642,7 @@ createinvoice.route.duration=Duration
createinvoice.column.service=Service
createinvoice.column.basis=Calculation Basis
createinvoice.summary.net=Net Total
+createinvoice.summary.vat=VAT ({0}%)
createinvoice.summary.total=Total Amount
createinvoice.notification.noservices=Please select at least one service
createinvoice.notification.nouser=User not found
diff --git a/src/main/resources/messages_es.properties b/src/main/resources/messages_es.properties
index a33d253..29f71fc 100644
--- a/src/main/resources/messages_es.properties
+++ b/src/main/resources/messages_es.properties
@@ -625,6 +625,7 @@ jobs.notification.deleted=Trabajo {0} eliminado
jobs.notification.delete.error=Error al eliminar trabajo: {0}
# Create Invoice
+createinvoice.title=Crear Factura \u2013 Trabajo {0}
createinvoice.error.invalidid=ID de Trabajo Inválido
createinvoice.error.notfound=Trabajo no encontrado
createinvoice.button.create=Crear Factura
@@ -641,6 +642,7 @@ createinvoice.route.duration=Duración
createinvoice.column.service=Servicio
createinvoice.column.basis=Base de Cálculo
createinvoice.summary.net=Total Neto
+createinvoice.summary.vat=IVA ({0}%)
createinvoice.summary.total=Total General
createinvoice.notification.noservices=Por favor seleccione al menos un servicio
createinvoice.notification.nouser=Usuario no encontrado
diff --git a/src/main/resources/messages_fr.properties b/src/main/resources/messages_fr.properties
index 379a053..2740701 100644
--- a/src/main/resources/messages_fr.properties
+++ b/src/main/resources/messages_fr.properties
@@ -625,6 +625,7 @@ jobs.notification.deleted=Emploi {0} supprimé
jobs.notification.delete.error=Erreur lors de la suppression : {0}
# Create Invoice
+createinvoice.title=Cr\u00e9er une Facture \u2013 Travail {0}
createinvoice.error.invalidid=ID d'Emploi Invalide
createinvoice.error.notfound=Emploi non trouvé
createinvoice.button.create=Créer une Facture
@@ -640,7 +641,8 @@ createinvoice.route.distance=Distance
createinvoice.route.duration=Durée
createinvoice.column.service=Service
createinvoice.column.basis=Base de Calcul
-createsummary.net=Total Net
+createinvoice.summary.net=Total Net
+createinvoice.summary.vat=TVA ({0}%)
createinvoice.summary.total=Montant Total
createinvoice.notification.noservices=Veuillez sélectionner au moins un service
createinvoice.notification.nouser=Utilisateur non trouvé