Erweiterungen

This commit is contained in:
2026-02-19 12:08:56 +01:00
parent 8b7256232f
commit 0aced91206
7 changed files with 219 additions and 54 deletions

View File

@@ -161,7 +161,7 @@ Aufgaben definieren, welche Schritte der App-Nutzer bei der Ausführung des Auft
1. **Pflichtleistungen** werden automatisch geladen. 1. **Pflichtleistungen** werden automatisch geladen.
2. Über die Dropdown-Liste können Sie weitere **Leistungen hinzufügen**. 2. Über die Dropdown-Liste können Sie weitere **Leistungen hinzufügen**.
3. Die Berechnung von **Netto**, **MwSt.** und **Brutto** erfolgt automatisch. 3. Die Berechnung von **Netto**, **USt.** und **Brutto** erfolgt automatisch.
4. Die **Routeninformationen** (Entfernung und Fahrtdauer) werden aus der Adressvalidierung angezeigt. 4. Die **Routeninformationen** (Entfernung und Fahrtdauer) werden aus der Adressvalidierung angezeigt.
5. Im Feld **Bemerkung** können Sie zusätzliche Anmerkungen zum Auftrag hinterlegen. 5. Im Feld **Bemerkung** können Sie zusätzliche Anmerkungen zum Auftrag hinterlegen.
@@ -320,7 +320,7 @@ Jede Konversation zeigt eine Vorschau, den Zeitpunkt der letzten Nachricht und d
- **Auftragsdetails** (Abhol- und Zustelladresse, Termine) - **Auftragsdetails** (Abhol- und Zustelladresse, Termine)
- **Leistungsdaten**: Geben Sie Kilometer und Zeitaufwand ein - **Leistungsdaten**: Geben Sie Kilometer und Zeitaufwand ein
- **Leistungen**: Wählen Sie die abzurechnenden Leistungen aus Ihrem Leistungskatalog - **Leistungen**: Wählen Sie die abzurechnenden Leistungen aus Ihrem Leistungskatalog
- **Zusammenfassung**: Automatische Berechnung von Netto, MwSt. und Brutto - **Zusammenfassung**: Automatische Berechnung von Netto, USt. und Brutto
5. Klicken Sie auf **"Rechnung erstellen"**. 5. Klicken Sie auf **"Rechnung erstellen"**.
### 8.2 Meine Rechnungen ### 8.2 Meine Rechnungen
@@ -386,7 +386,7 @@ Verwalten Sie Ihren **Leistungskatalog**, der bei der Auftragserstellung und Rec
1. Klicken Sie auf **"Leistung hinzufügen"**, um eine neue Leistung anzulegen. 1. Klicken Sie auf **"Leistung hinzufügen"**, um eine neue Leistung anzulegen.
2. Wählen Sie die **Leistungsbezeichnung** aus oder geben Sie eine neue ein. 2. Wählen Sie die **Leistungsbezeichnung** aus oder geben Sie eine neue ein.
3. Geben Sie den **Preis** und den **MwSt.-Satz** ein. 3. Geben Sie den **Preis** und den **USt.-Satz** ein.
4. Über das Papierkorb-Symbol können Sie einzelne Leistungen entfernen. 4. Über das Papierkorb-Symbol können Sie einzelne Leistungen entfernen.
Rechts neben dem Formular wird eine **Live-Vorschau** Ihrer Rechnungsvorlage angezeigt, die sich bei Änderungen automatisch aktualisiert. Rechts neben dem Formular wird eine **Live-Vorschau** Ihrer Rechnungsvorlage angezeigt, die sich bei Änderungen automatisch aktualisiert.

View File

@@ -749,7 +749,7 @@ public class AddJobView extends Main {
return service.getVatRate().multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + " %"; return service.getVatRate().multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + " %";
} }
return ""; return "";
}).setHeader("MwSt").setSortable(true); }).setHeader("USt").setSortable(true);
servicesGrid.addComponentColumn(service -> { servicesGrid.addComponentColumn(service -> {
// Verbindliche Leistungen können nicht gelöscht werden // Verbindliche Leistungen können nicht gelöscht werden
if (service.isMandatory()) { if (service.isMandatory()) {
@@ -913,6 +913,10 @@ public class AddJobView extends Main {
* route distance (for distance-based services). * route distance (for distance-based services).
*/ */
private BigDecimal calculateServicePrice(Service service, Double routeDistance) { private BigDecimal calculateServicePrice(Service service, Double routeDistance) {
return calculateServicePrice(service, routeDistance, null);
}
private BigDecimal calculateServicePrice(Service service, Double routeDistance, Integer durationSeconds) {
if (service.getCalculationBasis() == null) { if (service.getCalculationBasis() == null) {
return BigDecimal.ZERO; return BigDecimal.ZERO;
} }
@@ -928,9 +932,13 @@ public class AddJobView extends Main {
return BigDecimal.ZERO; return BigDecimal.ZERO;
case TIME: case TIME:
// For time-based services, we would need time units if (service.getPricePer15Minutes() != null && durationSeconds != null && durationSeconds > 0) {
// For now, return the price per 15 minutes as base value // Dauer in 15-Minuten-Einheiten umrechnen (aufrunden)
return service.getPricePer15Minutes() != null ? service.getPricePer15Minutes() : BigDecimal.ZERO; int units = durationSeconds / 900; // 900 Sekunden = 15 Minuten
if (durationSeconds % 900 > 0) units++; // Aufrunden
return service.getPricePer15Minutes().multiply(BigDecimal.valueOf(units));
}
return BigDecimal.ZERO;
default: default:
return BigDecimal.ZERO; return BigDecimal.ZERO;
@@ -1185,15 +1193,7 @@ public class AddJobView extends Main {
binder.forField(deliveryCity).asRequired("").bind(Job::getDeliveryCity, Job::setDeliveryCity); binder.forField(deliveryCity).asRequired("").bind(Job::getDeliveryCity, Job::setDeliveryCity);
// Price is now calculated from selected services - bind to job price for // Price wird manuell in submit() berechnet und gesetzt - kein Binder notwendig
// storage
binder.forField(new com.vaadin.flow.component.textfield.TextField()).withConverter((String str) -> {
// Calculate total from selected services
BigDecimal total = selectedServices.stream()
.map(svc -> svc.getEffectivePrice() != null ? svc.getEffectivePrice() : BigDecimal.ZERO)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return total;
}, (java.math.BigDecimal bd) -> bd == null ? "" : bd.toString()).bind(Job::getPrice, Job::setPrice);
// Bind date picker fields with validation // Bind date picker fields with validation
binder.forField(pickupDate).asRequired("") binder.forField(pickupDate).asRequired("")
@@ -1501,15 +1501,19 @@ public class AddJobView extends Main {
if (remarkArea != null) if (remarkArea != null)
job.setRemark(remarkArea.getValue()); job.setRemark(remarkArea.getValue());
// Calculate price from selected services
BigDecimal totalPrice = selectedServices.stream()
.map(s -> s.getEffectivePrice() != null ? s.getEffectivePrice() : BigDecimal.ZERO)
.reduce(BigDecimal.ZERO, BigDecimal::add);
job.setPrice(totalPrice);
// Store selected service IDs in job for invoice creation // Store selected service IDs in job for invoice creation
job.setServiceIds(selectedServices.stream().map(Service::getId).toList()); job.setServiceIds(selectedServices.stream().map(Service::getId).toList());
// Validate all required fields using the binder
if (binder.writeBeanIfValid(job)) {
// Preis nach dem Binder-Call berechnen (damit er nicht überschrieben wird)
Double routeDistance = getEffectiveRouteDistance();
Integer durationSeconds = getEffectiveRouteDuration();
BigDecimal netTotal = selectedServices.stream()
.map(s -> calculateServicePrice(s, routeDistance, durationSeconds))
.reduce(BigDecimal.ZERO, BigDecimal::add);
job.setPrice(netTotal);
// Store route distance and duration in job for invoice creation // Store route distance and duration in job for invoice creation
if (routeCalculationResult != null && routeCalculationResult.isValid()) { if (routeCalculationResult != null && routeCalculationResult.isValid()) {
// Berechnete Route verwenden // Berechnete Route verwenden
@@ -1522,9 +1526,6 @@ public class AddJobView extends Main {
job.setRouteDurationSeconds(manualDurationInput.getValue() * 60); // Minuten in Sekunden umrechnen job.setRouteDurationSeconds(manualDurationInput.getValue() * 60); // Minuten in Sekunden umrechnen
} }
} }
// Validate all required fields using the binder
if (binder.writeBeanIfValid(job)) {
// Additional validation: If digital processing is enabled, app user must be // Additional validation: If digital processing is enabled, app user must be
// selected // selected
if (digitalProcessing.getValue() && appUser.getValue() == null) { if (digitalProcessing.getValue() && appUser.getValue() == null) {
@@ -3263,6 +3264,21 @@ public class AddJobView extends Main {
return null; return null;
} }
/**
* Gibt die effektive Fahrtzeit zurück (berechnet oder manuell eingegeben).
*
* @return Dauer in Sekunden oder null, wenn keine Dauer verfügbar
*/
private Integer getEffectiveRouteDuration() {
if (routeCalculationResult != null && routeCalculationResult.isValid()) {
return routeCalculationResult.getDurationSeconds();
}
if (manualDurationInput != null && manualDurationInput.getValue() != null) {
return manualDurationInput.getValue() * 60; // Minuten in Sekunden
}
return null;
}
/** /**
* Registriert ValueChangeListener für alle Adressfelder, um bei Änderungen die * Registriert ValueChangeListener für alle Adressfelder, um bei Änderungen die
* Streckeninformationen zurückzusetzen. * Streckeninformationen zurückzusetzen.

View File

@@ -519,6 +519,40 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
variables.put("job.distance_km", "-"); variables.put("job.distance_km", "-");
} }
// Services data - add as JSON array for the template
List<Map<String, String>> servicesData = new ArrayList<>();
for (Service service : getSelectedServices()) {
Map<String, String> serviceData = new HashMap<>();
serviceData.put("name", service.getName());
// Calculate price based on calculation basis
BigDecimal price = calculateServicePrice(service);
if (price != null) {
serviceData.put("netAmount", price.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ","));
} else {
serviceData.put("netAmount", "0,00");
}
// VAT rate
if (service.getVatRate() != null) {
serviceData.put("vatRate", service.getVatRate().multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + "%");
} else {
serviceData.put("vatRate", "19%");
}
servicesData.add(serviceData);
}
// Serialize services data to JSON and store in variables
if (!servicesData.isEmpty()) {
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
variables.put("services.json", mapper.writeValueAsString(servicesData));
variables.put("services.count", String.valueOf(servicesData.size()));
} else {
variables.put("services.json", "[]");
variables.put("services.count", "0");
}
// Generate PDF using CustomerInvoiceService // Generate PDF using CustomerInvoiceService
return customerInvoiceService.generatePdfFromCanvasTemplateWithData(templateData, variables, currentUser); return customerInvoiceService.generatePdfFromCanvasTemplateWithData(templateData, variables, currentUser);
} }

View File

@@ -39,6 +39,8 @@ import de.assecutor.votianlt.repository.SignatureRepository;
import de.assecutor.votianlt.repository.BarcodeRepository; import de.assecutor.votianlt.repository.BarcodeRepository;
import de.assecutor.votianlt.repository.PhotoRepository; import de.assecutor.votianlt.repository.PhotoRepository;
import de.assecutor.votianlt.repository.CommentRepository; import de.assecutor.votianlt.repository.CommentRepository;
import de.assecutor.votianlt.repository.ServiceRepository;
import de.assecutor.votianlt.model.Service;
import de.assecutor.votianlt.model.Signature; import de.assecutor.votianlt.model.Signature;
import de.assecutor.votianlt.model.Barcode; import de.assecutor.votianlt.model.Barcode;
import de.assecutor.votianlt.model.Photo; import de.assecutor.votianlt.model.Photo;
@@ -55,6 +57,7 @@ import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -76,6 +79,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
private final AppUserService appUserService; private final AppUserService appUserService;
private final JobHistoryService jobHistoryService; private final JobHistoryService jobHistoryService;
private final LocationService locationService; private final LocationService locationService;
private final ServiceRepository serviceRepository;
@Value("${app.google.maps.api-key}") @Value("${app.google.maps.api-key}")
private String googleMapsApiKey; private String googleMapsApiKey;
@@ -86,7 +90,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
public JobSummaryView(JobRepository jobRepository, CargoItemRepository cargoItemRepository, public JobSummaryView(JobRepository jobRepository, CargoItemRepository cargoItemRepository,
TaskRepository taskRepository, SignatureRepository signatureRepository, BarcodeRepository barcodeRepository, TaskRepository taskRepository, SignatureRepository signatureRepository, BarcodeRepository barcodeRepository,
PhotoRepository photoRepository, CommentRepository commentRepository, AppUserService appUserService, PhotoRepository photoRepository, CommentRepository commentRepository, AppUserService appUserService,
MessageService messageService, JobHistoryService jobHistoryService, LocationService locationService) { MessageService messageService, JobHistoryService jobHistoryService, LocationService locationService,
ServiceRepository serviceRepository) {
this.jobRepository = jobRepository; this.jobRepository = jobRepository;
this.cargoItemRepository = cargoItemRepository; this.cargoItemRepository = cargoItemRepository;
this.taskRepository = taskRepository; this.taskRepository = taskRepository;
@@ -97,6 +102,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
this.appUserService = appUserService; this.appUserService = appUserService;
this.jobHistoryService = jobHistoryService; this.jobHistoryService = jobHistoryService;
this.locationService = locationService; this.locationService = locationService;
this.serviceRepository = serviceRepository;
setSizeFull(); setSizeFull();
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN, addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
@@ -260,7 +266,13 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
VerticalLayout infoBox = borderedBox(); VerticalLayout infoBox = borderedBox();
infoBox.add(new H3("Weitere Informationen")); infoBox.add(new H3("Weitere Informationen"));
infoBox.add(new Span("Preis: " + (job.getPrice() != null ? formatPrice(job.getPrice()) : "-")));
// Preis basierend auf den hinterlegten Leistungen berechnen
PriceCalculationResult priceResult = calculatePriceFromServices(job);
infoBox.add(new Span("Netto: " + formatPrice(priceResult.netAmount())));
infoBox.add(new Span("USt: " + formatPrice(priceResult.vatAmount())));
infoBox.add(new Span("Gesamt: " + formatPrice(priceResult.totalAmount())));
if (job.getRemark() != null && !job.getRemark().isBlank()) { if (job.getRemark() != null && !job.getRemark().isBlank()) {
infoBox.add(new Span("Bemerkung: " + job.getRemark())); infoBox.add(new Span("Bemerkung: " + job.getRemark()));
} }
@@ -1176,4 +1188,75 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
private String getGoogleMapsApiKey() { private String getGoogleMapsApiKey() {
return googleMapsApiKey != null ? googleMapsApiKey : ""; return googleMapsApiKey != null ? googleMapsApiKey : "";
} }
/**
* Berechnet den Preis basierend auf den hinterlegten Leistungen des Jobs.
*/
private PriceCalculationResult calculatePriceFromServices(Job job) {
BigDecimal netTotal = BigDecimal.ZERO;
BigDecimal vatTotal = BigDecimal.ZERO;
List<String> serviceIds = job.getServiceIds();
if (serviceIds == null || serviceIds.isEmpty()) {
// Fallback auf gespeicherten Preis
BigDecimal price = job.getPrice() != null ? job.getPrice() : BigDecimal.ZERO;
return new PriceCalculationResult(price, BigDecimal.ZERO, price);
}
// Routendaten für die Berechnung
Double routeDistance = job.getRouteDistanceKm();
Integer durationSeconds = job.getRouteDurationSeconds();
for (String serviceId : serviceIds) {
Service service = serviceRepository.findById(serviceId).orElse(null);
if (service == null) continue;
BigDecimal price = calculateServicePrice(service, routeDistance, durationSeconds);
BigDecimal vatRate = service.getVatRate() != null ? service.getVatRate() : new BigDecimal("0.19");
netTotal = netTotal.add(price);
vatTotal = vatTotal.add(price.multiply(vatRate));
}
BigDecimal totalAmount = netTotal.add(vatTotal);
return new PriceCalculationResult(netTotal, vatTotal, totalAmount);
}
/**
* Berechnet den Preis für eine einzelne Leistung basierend auf ihrer Berechnungsgrundlage.
*/
private BigDecimal calculateServicePrice(Service service, Double routeDistance, Integer durationSeconds) {
if (service.getCalculationBasis() == null) {
return BigDecimal.ZERO;
}
switch (service.getCalculationBasis()) {
case FLAT_RATE:
return service.getPrice() != null ? service.getPrice() : BigDecimal.ZERO;
case DISTANCE:
if (service.getPricePerKilometer() != null && routeDistance != null && routeDistance > 0) {
return service.getPricePerKilometer().multiply(BigDecimal.valueOf(routeDistance));
}
return BigDecimal.ZERO;
case TIME:
if (service.getPricePer15Minutes() != null && durationSeconds != null && durationSeconds > 0) {
// Dauer in 15-Minuten-Einheiten umrechnen
int units = durationSeconds / 900; // 900 Sekunden = 15 Minuten
if (durationSeconds % 900 > 0) units++; // Aufrunden
return service.getPricePer15Minutes().multiply(BigDecimal.valueOf(units));
}
return BigDecimal.ZERO;
default:
return BigDecimal.ZERO;
}
}
/**
* Record für die Preisberechnungsergebnisse.
*/
private record PriceCalculationResult(BigDecimal netAmount, BigDecimal vatAmount, BigDecimal totalAmount) {
}
} }

View File

@@ -84,7 +84,7 @@ public class CustomerInvoiceService {
// Rechnungsposten // Rechnungsposten
List<CustomerInvoiceItem> items = new ArrayList<>(); List<CustomerInvoiceItem> items = new ArrayList<>();
BigDecimal vatRate = new BigDecimal("0.19"); // 19% MwSt. BigDecimal vatRate = new BigDecimal("0.19"); // 19% USt.
CustomerInvoiceItem item1 = new CustomerInvoiceItem(new BigDecimal("2"), "Std.", "Transportdienstleistung", CustomerInvoiceItem item1 = new CustomerInvoiceItem(new BigDecimal("2"), "Std.", "Transportdienstleistung",
new BigDecimal("85.00"), vatRate); new BigDecimal("85.00"), vatRate);
@@ -739,13 +739,19 @@ public class CustomerInvoiceService {
String grossTotal = variables.getOrDefault("invoice.gross_total", "0,00 €"); String grossTotal = variables.getOrDefault("invoice.gross_total", "0,00 €");
String vatRate = variables.getOrDefault("invoice.vat_rate", "19%"); String vatRate = variables.getOrDefault("invoice.vat_rate", "19%");
// Sample data for now - in the future this would come from actual job services // Parse services JSON from variables
// Parse the net total to get individual service amounts (split evenly for demo) java.util.List<java.util.Map<String, String>> servicesData = new java.util.ArrayList<>();
String[][] serviceData = { String servicesJson = variables.get("services.json");
{"Leistung 1", vatRate, "450,00 €"}, if (servicesJson != null && !servicesJson.isEmpty() && !servicesJson.equals("[]")) {
{"Leistung 2", vatRate, "85,00 €"}, try {
{"Leistung 3", vatRate, "120,00 €"} com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
}; com.fasterxml.jackson.core.type.TypeReference<java.util.List<java.util.Map<String, String>>> typeRef =
new com.fasterxml.jackson.core.type.TypeReference<>() {};
servicesData = mapper.readValue(servicesJson, typeRef);
} catch (Exception e) {
System.err.println("DEBUG: Failed to parse services JSON: " + e.getMessage());
}
}
// Wrapper div // Wrapper div
html.append("<div style='width:100%;box-sizing:border-box;'>"); html.append("<div style='width:100%;box-sizing:border-box;'>");
@@ -760,15 +766,27 @@ public class CustomerInvoiceService {
html.append("<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>"); html.append("<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>");
html.append("</tr>"); html.append("</tr>");
// Data rows - use actual service data // Data rows - use actual service data from the job
for (int i = 0; i < serviceData.length; i++) { if (servicesData.isEmpty()) {
// Fallback: show a single row with no data
html.append("<tr style='border-bottom:1px solid #eeeeee;'>");
html.append("<td colspan='3' style='text-align:center;padding:4px 8px;white-space:nowrap;'>Keine Leistungen vorhanden</td>");
html.append("</tr>");
} else {
for (int i = 0; i < servicesData.size(); i++) {
java.util.Map<String, String> service = servicesData.get(i);
String name = service.getOrDefault("name", "Unbekannte Leistung");
String serviceVatRate = service.getOrDefault("vatRate", vatRate);
String netAmount = service.getOrDefault("netAmount", "0,00");
String bgColor = (i % 2 == 1) ? "background-color:rgba(0,0,0,0.02);" : ""; 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("<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(serviceData[i][0]).append("</td>"); html.append("<td style='text-align:left;padding:4px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>").append(escapeHtml(name)).append("</td>");
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(serviceData[i][1]).append("</td>"); html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(serviceVatRate).append("</td>");
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(serviceData[i][2]).append("</td>"); html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(netAmount).append("</td>");
html.append("</tr>"); html.append("</tr>");
} }
}
html.append("</table>"); html.append("</table>");
@@ -806,4 +824,18 @@ public class CustomerInvoiceService {
return html.toString(); return html.toString();
} }
/**
* Escape HTML special characters to prevent XSS.
*/
private String escapeHtml(String input) {
if (input == null) {
return "";
}
return input.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#x27;");
}
} }

View File

@@ -423,7 +423,7 @@
<td class="amount-col">${invoiceData.netAmount}</td> <td class="amount-col">${invoiceData.netAmount}</td>
</tr> </tr>
<tr> <tr>
<td class="label-col">zzgl. ${invoiceData.vatRate} MwSt.:</td> <td class="label-col">zzgl. ${invoiceData.vatRate} USt.:</td>
<td class="amount-col">${invoiceData.vatAmount}</td> <td class="amount-col">${invoiceData.vatAmount}</td>
</tr> </tr>
<tr class="total-row"> <tr class="total-row">

View File

@@ -75,7 +75,7 @@
<td></td><td style="text-align:left">Nettobetrag</td><td></td><td>5.639,00 €</td> <td></td><td style="text-align:left">Nettobetrag</td><td></td><td>5.639,00 €</td>
</tr> </tr>
<tr> <tr>
<td></td><td style="text-align:left">+ 19% MwSt.</td><td></td><td>1.071,41 €</td> <td></td><td style="text-align:left">+ 19% USt.</td><td></td><td>1.071,41 €</td>
</tr> </tr>
<tr class="total-row"> <tr class="total-row">
<td></td><td style="text-align:left">Endbetrag</td><td></td><td>6.710,41 €</td> <td></td><td style="text-align:left">Endbetrag</td><td></td><td>6.710,41 €</td>