Erweiterungen

This commit is contained in:
2026-02-25 14:12:40 +01:00
parent 98ec10fdb8
commit 7da7c71315
9 changed files with 101 additions and 44 deletions

View File

@@ -6,7 +6,7 @@
<groupId>de.assecutor.votianlt</groupId> <groupId>de.assecutor.votianlt</groupId>
<artifactId>votianlt</artifactId> <artifactId>votianlt</artifactId>
<version>0.9.4</version> <version>0.9.5</version>
<packaging>jar</packaging> <packaging>jar</packaging>

View File

@@ -380,7 +380,11 @@ public class MessageController {
return; 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) { if (allCompleted) {
updateJobStatusToCompleted(jobId); updateJobStatusToCompleted(jobId);

View File

@@ -8,6 +8,7 @@ import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.datepicker.DatePicker; 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.html.H2;
import com.vaadin.flow.component.timepicker.TimePicker; import com.vaadin.flow.component.timepicker.TimePicker;
import com.vaadin.flow.component.html.H3; import com.vaadin.flow.component.html.H3;
@@ -797,38 +798,42 @@ public class AddJobView extends Main implements HasDynamicTitle {
summaryTitle.getStyle().set("margin", "0"); summaryTitle.getStyle().set("margin", "0");
summaryLayout.add(summaryTitle); summaryLayout.add(summaryTitle);
// Net total Div priceTable = new Div();
HorizontalLayout netRow = new HorizontalLayout(); priceTable.getStyle().set("width", "100%");
netRow.setWidthFull();
netRow.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN); // Net total row
Span netLabel = new Span(getTranslation("addjob.summary.net") + ":"); 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 = new Span("0,00 €");
netTotalLabel.getStyle().set("font-weight", "bold"); netTotalLabel.getStyle().set("font-weight", "bold").set("white-space", "nowrap");
netRow.add(netLabel, netTotalLabel); netRow.add(netLabelSpan, netTotalLabel);
summaryLayout.add(netRow); priceTable.add(netRow);
// VAT total // VAT total row
HorizontalLayout vatRow = new HorizontalLayout(); Div vatRow = new Div();
vatRow.setWidthFull(); vatRow.getStyle().set("display", "flex").set("justify-content", "space-between").set("padding", "4px 0");
vatRow.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN); Span vatLabelSpan = new Span(getTranslation("addjob.summary.vat") + ":");
Span vatLabel = new Span(getTranslation("addjob.summary.vat") + ":"); vatLabelSpan.getStyle().set("padding-right", "8px");
vatTotalLabel = new Span("0,00 €"); vatTotalLabel = new Span("0,00 €");
vatTotalLabel.getStyle().set("font-weight", "bold"); vatTotalLabel.getStyle().set("font-weight", "bold").set("white-space", "nowrap");
vatRow.add(vatLabel, vatTotalLabel); vatRow.add(vatLabelSpan, vatTotalLabel);
summaryLayout.add(vatRow); priceTable.add(vatRow);
// Gross total // Gross total row
HorizontalLayout grossRow = new HorizontalLayout(); Div grossRow = new Div();
grossRow.setWidthFull(); grossRow.getStyle().set("display", "flex").set("justify-content", "space-between").set("padding", "4px 0");
grossRow.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN); Span grossLabelSpan = new Span(getTranslation("addjob.summary.gross") + ":");
Span grossLabel = new Span(getTranslation("addjob.summary.gross") + ":"); grossLabelSpan.getStyle().set("padding-right", "8px").set("font-weight", "bold");
grossLabel.getStyle().set("font-size", "var(--lumo-font-size-l)");
grossTotalLabel = new Span("0,00 €"); grossTotalLabel = new Span("0,00 €");
grossTotalLabel.getStyle().set("font-size", "var(--lumo-font-size-l)"); grossTotalLabel.getStyle().set("font-size", "var(--lumo-font-size-l)");
grossTotalLabel.getStyle().set("font-weight", "bold"); grossTotalLabel.getStyle().set("font-weight", "bold");
grossTotalLabel.getStyle().set("color", "var(--lumo-primary-text-color)"); grossTotalLabel.getStyle().set("color", "var(--lumo-primary-text-color)").set("white-space", "nowrap");
grossRow.add(grossLabel, grossTotalLabel); grossRow.add(grossLabelSpan, grossTotalLabel);
summaryLayout.add(grossRow); priceTable.add(grossRow);
summaryLayout.add(priceTable);
content.add(summaryLayout); content.add(summaryLayout);

View File

@@ -316,24 +316,40 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
BigDecimal vatAmount = netAmount.multiply(vatRate); BigDecimal vatAmount = netAmount.multiply(vatRate);
BigDecimal totalAmount = netAmount.add(vatAmount); BigDecimal totalAmount = netAmount.add(vatAmount);
VerticalLayout summaryInfo = new VerticalLayout(); Div priceTable = new Div();
summaryInfo.setSpacing(true); priceTable.getStyle().set("width", "100%");
summaryInfo.setWidthFull();
// Show only net sum, VAT sums, and total amount without individual services priceTable.add(createPriceRow(getTranslation("createinvoice.summary.net") + ":",
summaryInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.summary.net")), netAmount.setScale(2, RoundingMode.HALF_UP) + "", false));
new Span(netAmount.setScale(2, RoundingMode.HALF_UP) + ""))); priceTable.add(createPriceRow(
summaryInfo.add(new HorizontalLayout( getTranslation("createinvoice.summary.vat",
new Span(getTranslation("createinvoice.summary.vat", vatRate.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP).toString()) + ":",
vatRate.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP).toString())), vatAmount.setScale(2, RoundingMode.HALF_UP) + "", false));
new Span(vatAmount.setScale(2, RoundingMode.HALF_UP) + ""))); priceTable.add(createPriceRow(getTranslation("createinvoice.summary.total") + ":",
summaryInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.summary.total")), totalAmount.setScale(2, RoundingMode.HALF_UP) + "", true));
new Span(totalAmount.setScale(2, RoundingMode.HALF_UP) + "")));
section.add(summaryInfo); section.add(priceTable);
return section; 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) { private BigDecimal calculateServicePrice(Service service) {
if (service.getCalculationBasis() == null) { if (service.getCalculationBasis() == null) {
return null; return null;

View File

@@ -271,9 +271,15 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
// Preis basierend auf den hinterlegten Leistungen berechnen // Preis basierend auf den hinterlegten Leistungen berechnen
PriceCalculationResult priceResult = calculatePriceFromServices(job); 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()))); Div priceTable = new Div();
infoBox.add(new Span(getTranslation("jobsummary.info.gesamt") + ": " + formatPrice(priceResult.totalAmount()))); 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()) { if (job.getRemark() != null && !job.getRemark().isBlank()) {
infoBox.add(new Span(getTranslation("jobsummary.info.bemerkung") + ": " + job.getRemark())); infoBox.add(new Span(getTranslation("jobsummary.info.bemerkung") + ": " + job.getRemark()));
@@ -411,6 +417,24 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
return nf.format(price); 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) { private String resolveAppUserName(String appUserIdString) {
try { try {
ObjectId id = new ObjectId(appUserIdString); ObjectId id = new ObjectId(appUserIdString);

View File

@@ -625,6 +625,7 @@ jobs.notification.deleted=Auftrag {0} wurde gelöscht
jobs.notification.delete.error=Fehler beim Löschen: {0} jobs.notification.delete.error=Fehler beim Löschen: {0}
# Create Invoice # Create Invoice
createinvoice.title=Rechnung erstellen \u2013 Auftrag {0}
createinvoice.error.invalidid=Ungültige Job-ID createinvoice.error.invalidid=Ungültige Job-ID
createinvoice.error.notfound=Job nicht gefunden createinvoice.error.notfound=Job nicht gefunden
createinvoice.button.create=Rechnung erstellen createinvoice.button.create=Rechnung erstellen
@@ -641,6 +642,7 @@ createinvoice.route.duration=Fahrtzeit
createinvoice.column.service=Leistung createinvoice.column.service=Leistung
createinvoice.column.basis=Berechnungsbasis createinvoice.column.basis=Berechnungsbasis
createinvoice.summary.net=Nettosumme createinvoice.summary.net=Nettosumme
createinvoice.summary.vat=MwSt. ({0}%)
createinvoice.summary.total=Gesamtsumme createinvoice.summary.total=Gesamtsumme
createinvoice.notification.noservices=Bitte wählen Sie mindestens eine Leistung aus createinvoice.notification.noservices=Bitte wählen Sie mindestens eine Leistung aus
createinvoice.notification.nouser=Benutzer nicht gefunden createinvoice.notification.nouser=Benutzer nicht gefunden

View File

@@ -625,6 +625,7 @@ jobs.notification.deleted=Job {0} deleted
jobs.notification.delete.error=Error deleting job: {0} jobs.notification.delete.error=Error deleting job: {0}
# Create Invoice # Create Invoice
createinvoice.title=Create Invoice \u2013 Job {0}
createinvoice.error.invalidid=Invalid Job ID createinvoice.error.invalidid=Invalid Job ID
createinvoice.error.notfound=Job not found createinvoice.error.notfound=Job not found
createinvoice.button.create=Create Invoice createinvoice.button.create=Create Invoice
@@ -641,6 +642,7 @@ createinvoice.route.duration=Duration
createinvoice.column.service=Service createinvoice.column.service=Service
createinvoice.column.basis=Calculation Basis createinvoice.column.basis=Calculation Basis
createinvoice.summary.net=Net Total createinvoice.summary.net=Net Total
createinvoice.summary.vat=VAT ({0}%)
createinvoice.summary.total=Total Amount createinvoice.summary.total=Total Amount
createinvoice.notification.noservices=Please select at least one service createinvoice.notification.noservices=Please select at least one service
createinvoice.notification.nouser=User not found createinvoice.notification.nouser=User not found

View File

@@ -625,6 +625,7 @@ jobs.notification.deleted=Trabajo {0} eliminado
jobs.notification.delete.error=Error al eliminar trabajo: {0} jobs.notification.delete.error=Error al eliminar trabajo: {0}
# Create Invoice # Create Invoice
createinvoice.title=Crear Factura \u2013 Trabajo {0}
createinvoice.error.invalidid=ID de Trabajo Inválido createinvoice.error.invalidid=ID de Trabajo Inválido
createinvoice.error.notfound=Trabajo no encontrado createinvoice.error.notfound=Trabajo no encontrado
createinvoice.button.create=Crear Factura createinvoice.button.create=Crear Factura
@@ -641,6 +642,7 @@ createinvoice.route.duration=Duración
createinvoice.column.service=Servicio createinvoice.column.service=Servicio
createinvoice.column.basis=Base de Cálculo createinvoice.column.basis=Base de Cálculo
createinvoice.summary.net=Total Neto createinvoice.summary.net=Total Neto
createinvoice.summary.vat=IVA ({0}%)
createinvoice.summary.total=Total General createinvoice.summary.total=Total General
createinvoice.notification.noservices=Por favor seleccione al menos un servicio createinvoice.notification.noservices=Por favor seleccione al menos un servicio
createinvoice.notification.nouser=Usuario no encontrado createinvoice.notification.nouser=Usuario no encontrado

View File

@@ -625,6 +625,7 @@ jobs.notification.deleted=Emploi {0} supprimé
jobs.notification.delete.error=Erreur lors de la suppression : {0} jobs.notification.delete.error=Erreur lors de la suppression : {0}
# Create Invoice # Create Invoice
createinvoice.title=Cr\u00e9er une Facture \u2013 Travail {0}
createinvoice.error.invalidid=ID d'Emploi Invalide createinvoice.error.invalidid=ID d'Emploi Invalide
createinvoice.error.notfound=Emploi non trouvé createinvoice.error.notfound=Emploi non trouvé
createinvoice.button.create=Créer une Facture createinvoice.button.create=Créer une Facture
@@ -640,7 +641,8 @@ createinvoice.route.distance=Distance
createinvoice.route.duration=Durée createinvoice.route.duration=Durée
createinvoice.column.service=Service createinvoice.column.service=Service
createinvoice.column.basis=Base de Calcul 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.summary.total=Montant Total
createinvoice.notification.noservices=Veuillez sélectionner au moins un service createinvoice.notification.noservices=Veuillez sélectionner au moins un service
createinvoice.notification.nouser=Utilisateur non trouvé createinvoice.notification.nouser=Utilisateur non trouvé