Erweiterungen
This commit is contained in:
@@ -9,17 +9,18 @@ import com.vaadin.flow.router.PageTitle;
|
|||||||
import com.vaadin.flow.router.Route;
|
import com.vaadin.flow.router.Route;
|
||||||
import com.vaadin.flow.component.UI;
|
import com.vaadin.flow.component.UI;
|
||||||
import de.assecutor.votianlt.model.invoices.SystemInvoice;
|
import de.assecutor.votianlt.model.invoices.SystemInvoice;
|
||||||
import de.assecutor.votianlt.model.invoices.CustomerInvoiceData;
|
import de.assecutor.votianlt.model.invoices.SystemInvoiceData;
|
||||||
import de.assecutor.votianlt.model.invoices.CustomerInvoiceItem;
|
import de.assecutor.votianlt.model.invoices.SystemInvoiceItem;
|
||||||
import de.assecutor.votianlt.security.SecurityService;
|
import de.assecutor.votianlt.service.SystemInvoiceService;
|
||||||
import de.assecutor.votianlt.service.CustomerInvoiceService;
|
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.math.BigDecimal;
|
import java.text.NumberFormat;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import com.vaadin.flow.server.StreamResource;
|
import com.vaadin.flow.server.StreamResource;
|
||||||
import com.vaadin.flow.server.StreamRegistration;
|
import com.vaadin.flow.server.StreamRegistration;
|
||||||
@@ -31,12 +32,10 @@ public class InvoicesView extends VerticalLayout {
|
|||||||
|
|
||||||
private final Grid<SystemInvoice> invoiceGrid;
|
private final Grid<SystemInvoice> invoiceGrid;
|
||||||
|
|
||||||
private final CustomerInvoiceService customerInvoiceService;
|
private final SystemInvoiceService systemInvoiceService;
|
||||||
private final SecurityService securityService;
|
|
||||||
|
|
||||||
public InvoicesView(CustomerInvoiceService customerInvoiceService, SecurityService securityService) {
|
public InvoicesView(SystemInvoiceService systemInvoiceService) {
|
||||||
this.customerInvoiceService = customerInvoiceService;
|
this.systemInvoiceService = systemInvoiceService;
|
||||||
this.securityService = securityService;
|
|
||||||
|
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
setPadding(true);
|
setPadding(true);
|
||||||
@@ -77,8 +76,8 @@ public class InvoicesView extends VerticalLayout {
|
|||||||
|
|
||||||
private void downloadInvoicePdf(SystemInvoice systemInvoice) {
|
private void downloadInvoicePdf(SystemInvoice systemInvoice) {
|
||||||
try {
|
try {
|
||||||
// PDF generieren mit CustomerInvoice (HTML Template)
|
// PDF generieren mit SystemInvoice (HTML Template)
|
||||||
byte[] pdfBytes = generateCustomerInvoicePdf(systemInvoice);
|
byte[] pdfBytes = generateSystemInvoicePdf(systemInvoice);
|
||||||
StreamResource resource = new StreamResource(systemInvoice.getId() + ".pdf",
|
StreamResource resource = new StreamResource(systemInvoice.getId() + ".pdf",
|
||||||
() -> new ByteArrayInputStream(pdfBytes));
|
() -> new ByteArrayInputStream(pdfBytes));
|
||||||
resource.setContentType("application/pdf");
|
resource.setContentType("application/pdf");
|
||||||
@@ -94,72 +93,35 @@ public class InvoicesView extends VerticalLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] generateCustomerInvoicePdf(SystemInvoice systemInvoice) throws Exception {
|
private byte[] generateSystemInvoicePdf(SystemInvoice systemInvoice) throws Exception {
|
||||||
// Aktuellen Benutzer als Rechnungssteller ermitteln
|
DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("dd.MM.yyyy");
|
||||||
de.assecutor.votianlt.model.User user = securityService.getCurrentDatabaseUser();
|
NumberFormat CURRENCY_FMT = NumberFormat.getCurrencyInstance(Locale.GERMANY);
|
||||||
|
|
||||||
CustomerInvoiceData data = new CustomerInvoiceData();
|
SystemInvoiceData data = new SystemInvoiceData();
|
||||||
// Kopf
|
|
||||||
data.setInvoiceNumber(systemInvoice.getId());
|
data.setInvoiceNumber(systemInvoice.getId());
|
||||||
data.setInvoiceDate(systemInvoice.getDatum());
|
data.setInvoiceDate(DATE_FMT.format(systemInvoice.getDatum()));
|
||||||
data.setDeliveryDate(systemInvoice.getDatum());
|
data.setInvoiceText(systemInvoice.getBeschreibung());
|
||||||
data.setDescription(systemInvoice.getBeschreibung());
|
|
||||||
|
|
||||||
// Rechnungssteller = eingeloggter Benutzer
|
// Empfänger aus der Zeile (nur Name in den Testdaten vorhanden)
|
||||||
String senderName = (nullToEmpty(user.getFirstname()) + " " + nullToEmpty(user.getName())).trim();
|
|
||||||
if (senderName.isBlank() && user.getEmail() != null)
|
|
||||||
senderName = user.getEmail();
|
|
||||||
data.setSenderName(user.getCompany() != null && !user.getCompany().isBlank() ? user.getCompany() : senderName);
|
|
||||||
data.setSenderAddress((nullToEmpty(user.getStreet()) + " " + nullToEmpty(user.getHouseNumber())).trim());
|
|
||||||
data.setSenderPostcode(nullToEmpty(user.getZip()));
|
|
||||||
data.setSenderCity(nullToEmpty(user.getCity()));
|
|
||||||
data.setSenderCountry("Deutschland");
|
|
||||||
data.setSenderTaxNumber("");
|
|
||||||
data.setSenderVatId("");
|
|
||||||
data.setSenderPhone(nullToEmpty(user.getPhone()));
|
|
||||||
data.setSenderEmail(nullToEmpty(user.getEmail()));
|
|
||||||
data.setSenderWebsite("");
|
|
||||||
|
|
||||||
// Empfänger = Kunde aus der Zeile (nur Name vorhanden in Testdaten)
|
|
||||||
data.setRecipientCompany("");
|
|
||||||
data.setRecipientName(systemInvoice.getKunde());
|
data.setRecipientName(systemInvoice.getKunde());
|
||||||
data.setRecipientAddress("");
|
data.setRecipientDepartment("");
|
||||||
data.setRecipientPostcode("");
|
data.setRecipientStreet("");
|
||||||
data.setRecipientCity("");
|
data.setRecipientCity("");
|
||||||
data.setRecipientCountry("Deutschland");
|
|
||||||
data.setRecipientVatId("");
|
|
||||||
|
|
||||||
// Positionen (eine einfache Position aus Betrag/Beschreibung)
|
// Eine Position mit dem Betrag/Beschreibung
|
||||||
List<CustomerInvoiceItem> items = new ArrayList<>();
|
List<SystemInvoiceItem> items = new ArrayList<>();
|
||||||
BigDecimal vatRate = new BigDecimal("0.19");
|
String netStr = CURRENCY_FMT.format(systemInvoice.getBetrag());
|
||||||
BigDecimal unitPrice = BigDecimal.valueOf(systemInvoice.getBetrag());
|
items.add(new SystemInvoiceItem("1", systemInvoice.getBeschreibung(), netStr, netStr));
|
||||||
items.add(new CustomerInvoiceItem(BigDecimal.ONE, "Stk.", systemInvoice.getBeschreibung(), unitPrice, vatRate));
|
data.setInvoiceItems(items);
|
||||||
data.setItems(items);
|
|
||||||
|
|
||||||
// Summen berechnen
|
// Summen berechnen (Betrag als Nettobetrag interpretieren)
|
||||||
BigDecimal netAmount = items.stream().map(CustomerInvoiceItem::getNetTotal).reduce(BigDecimal.ZERO, BigDecimal::add);
|
double net = systemInvoice.getBetrag();
|
||||||
BigDecimal vatAmount = netAmount.multiply(vatRate);
|
double vat = Math.round(net * 0.19 * 100.0) / 100.0;
|
||||||
BigDecimal totalAmount = netAmount.add(vatAmount);
|
double total = net + vat;
|
||||||
data.setNetAmount(netAmount);
|
data.setNetAmount(CURRENCY_FMT.format(net));
|
||||||
data.setVatRate(vatRate);
|
data.setVatAmount(CURRENCY_FMT.format(vat));
|
||||||
data.setVatAmount(vatAmount);
|
data.setTotalAmount(CURRENCY_FMT.format(total));
|
||||||
data.setTotalAmount(totalAmount);
|
|
||||||
|
|
||||||
// Zahlung
|
return systemInvoiceService.generateInvoicePdfFromHtml(data);
|
||||||
data.setPaymentTerms("Zahlbar innerhalb von 14 Tagen netto ohne Abzug.");
|
|
||||||
data.setPaymentDueDate(systemInvoice.getDatum().plusDays(14));
|
|
||||||
data.setBankAccount(data.getSenderName());
|
|
||||||
data.setIban("");
|
|
||||||
data.setBic("");
|
|
||||||
|
|
||||||
// Rechtliches
|
|
||||||
data.setLegalNotes("");
|
|
||||||
data.setReverseChargeNote("");
|
|
||||||
|
|
||||||
return customerInvoiceService.generateCustomerInvoicePdf(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String nullToEmpty(String s) {
|
|
||||||
return s == null ? "" : s;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package de.assecutor.votianlt.pages.view;
|
|||||||
import com.vaadin.flow.component.Component;
|
import com.vaadin.flow.component.Component;
|
||||||
import com.vaadin.flow.component.button.Button;
|
import com.vaadin.flow.component.button.Button;
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
import com.vaadin.flow.component.grid.Grid;
|
||||||
|
import com.vaadin.flow.component.grid.GridVariant;
|
||||||
|
import com.vaadin.flow.component.grid.ColumnTextAlign;
|
||||||
import com.vaadin.flow.component.html.*;
|
import com.vaadin.flow.component.html.*;
|
||||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||||
@@ -10,23 +12,33 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
|||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.component.select.Select;
|
import com.vaadin.flow.component.select.Select;
|
||||||
import com.vaadin.flow.component.textfield.TextField;
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
|
import com.vaadin.flow.data.renderer.ComponentRenderer;
|
||||||
import com.vaadin.flow.data.value.ValueChangeMode;
|
import com.vaadin.flow.data.value.ValueChangeMode;
|
||||||
import com.vaadin.flow.router.PageTitle;
|
import com.vaadin.flow.router.PageTitle;
|
||||||
import com.vaadin.flow.router.Route;
|
import com.vaadin.flow.router.Route;
|
||||||
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
|
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
|
||||||
import de.assecutor.votianlt.pages.base.ui.view.MainLayout;
|
import de.assecutor.votianlt.pages.base.ui.view.MainLayout;
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
|
import com.vaadin.flow.component.UI;
|
||||||
|
import com.vaadin.flow.server.StreamResource;
|
||||||
|
import com.vaadin.flow.server.StreamRegistration;
|
||||||
|
import de.assecutor.votianlt.service.SystemInvoiceService;
|
||||||
|
import de.assecutor.votianlt.model.invoices.SystemInvoiceData;
|
||||||
|
import de.assecutor.votianlt.model.invoices.SystemInvoiceItem;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
|
||||||
|
import java.text.NumberFormat;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Meine Rechnungen – nutzerzentrierte Übersicht.
|
* Meine Rechnungen – nutzerzentrierte Übersicht.
|
||||||
*
|
*
|
||||||
* Layout orientiert am bereitgestellten Screenshot: - Zwei Karten oben (Offene
|
* Modernisierte Optik: Responsive Karten, Lumo-Theme-Varianten, Status-Badges,
|
||||||
* Rechnungen, Bankverbindung) - Darunter ein Bereich „Rechnungen" mit Grid,
|
* Suche und leere Zustandsanzeige.
|
||||||
* Suche und Seitengröße
|
|
||||||
*/
|
*/
|
||||||
@PageTitle("Meine Rechnungen")
|
@PageTitle("Meine Rechnungen")
|
||||||
@Route(value = "my-invoices", layout = MainLayout.class)
|
@Route(value = "my-invoices", layout = MainLayout.class)
|
||||||
@@ -35,11 +47,18 @@ public class MyInvoicesView extends Main {
|
|||||||
|
|
||||||
private final Grid<MyInvoiceRow> grid = new Grid<>(MyInvoiceRow.class, false);
|
private final Grid<MyInvoiceRow> grid = new Grid<>(MyInvoiceRow.class, false);
|
||||||
private final List<MyInvoiceRow> allRows = new ArrayList<>(); // zunächst leer
|
private final List<MyInvoiceRow> allRows = new ArrayList<>(); // zunächst leer
|
||||||
|
private final Div emptyState = new Div();
|
||||||
|
private final SystemInvoiceService systemInvoiceService;
|
||||||
|
|
||||||
public MyInvoicesView() {
|
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("dd.MM.yyyy");
|
||||||
getStyle().set("max-width", "90%");
|
private static final NumberFormat CURRENCY_FMT = NumberFormat.getCurrencyInstance(Locale.GERMANY);
|
||||||
|
|
||||||
|
public MyInvoicesView(SystemInvoiceService systemInvoiceService) {
|
||||||
|
this.systemInvoiceService = systemInvoiceService;
|
||||||
|
getStyle().set("max-width", "1100px");
|
||||||
getStyle().set("margin-left", "auto");
|
getStyle().set("margin-left", "auto");
|
||||||
getStyle().set("margin-right", "auto");
|
getStyle().set("margin-right", "auto");
|
||||||
|
getStyle().set("padding", "var(--lumo-space-m)");
|
||||||
|
|
||||||
// Toolbar / Titel
|
// Toolbar / Titel
|
||||||
add(new ViewToolbar("Meine Rechnungen"));
|
add(new ViewToolbar("Meine Rechnungen"));
|
||||||
@@ -49,15 +68,18 @@ public class MyInvoicesView extends Main {
|
|||||||
|
|
||||||
// Rechnungsbereich unten
|
// Rechnungsbereich unten
|
||||||
add(createInvoicesSection());
|
add(createInvoicesSection());
|
||||||
|
|
||||||
|
// Testdaten hinzufügen
|
||||||
|
addTestInvoices();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Component createTopCards() {
|
private Component createTopCards() {
|
||||||
// Container mit zwei Spalten (responsiv)
|
// Container mit responsiven Spalten
|
||||||
Div container = new Div();
|
Div container = new Div();
|
||||||
container.getStyle().set("display", "grid").set("grid-template-columns", "48% 2% 48%");
|
container.getStyle()
|
||||||
// .set("gap", "10px");
|
.set("display", "grid")
|
||||||
// Spaltenabstände: 2% zwischen den beiden Spalten
|
.set("grid-template-columns", "repeat(auto-fit, minmax(280px, 1fr))")
|
||||||
container.getStyle().set("column-gap", "0");
|
.set("gap", "var(--lumo-space-m)");
|
||||||
|
|
||||||
// Karte: Offene Rechnungen
|
// Karte: Offene Rechnungen
|
||||||
Paragraph hint = new Paragraph("Momentan sind keine neuen Rechnungen für Sie im System gespeichert.");
|
Paragraph hint = new Paragraph("Momentan sind keine neuen Rechnungen für Sie im System gespeichert.");
|
||||||
@@ -80,7 +102,7 @@ public class MyInvoicesView extends Main {
|
|||||||
private Component createInvoicesSection() {
|
private Component createInvoicesSection() {
|
||||||
Div card = new Div();
|
Div card = new Div();
|
||||||
// Abstand zur oberen Zeile
|
// Abstand zur oberen Zeile
|
||||||
card.getStyle().set("margin-top", "30px");
|
card.getStyle().set("margin-top", "var(--lumo-space-l)");
|
||||||
|
|
||||||
styleCard(card);
|
styleCard(card);
|
||||||
|
|
||||||
@@ -93,12 +115,14 @@ public class MyInvoicesView extends Main {
|
|||||||
pageSize.setItems(10, 25, 50);
|
pageSize.setItems(10, 25, 50);
|
||||||
pageSize.setLabel("Einträge anzeigen");
|
pageSize.setLabel("Einträge anzeigen");
|
||||||
pageSize.setValue(10);
|
pageSize.setValue(10);
|
||||||
pageSize.setWidth("140px");
|
pageSize.setWidth("160px");
|
||||||
|
|
||||||
TextField search = new TextField();
|
TextField search = new TextField();
|
||||||
search.setLabel("Suchen");
|
search.setLabel("Suchen");
|
||||||
|
search.setPlaceholder("Rechnungsnr., Datum, Betrag...");
|
||||||
search.setClearButtonVisible(true);
|
search.setClearButtonVisible(true);
|
||||||
search.setValueChangeMode(ValueChangeMode.EAGER);
|
search.setValueChangeMode(ValueChangeMode.EAGER);
|
||||||
|
search.setWidth("300px");
|
||||||
|
|
||||||
// Layout Kopf + Controls
|
// Layout Kopf + Controls
|
||||||
HorizontalLayout header = new HorizontalLayout();
|
HorizontalLayout header = new HorizontalLayout();
|
||||||
@@ -110,14 +134,36 @@ public class MyInvoicesView extends Main {
|
|||||||
HorizontalLayout controls = new HorizontalLayout(pageSize, search);
|
HorizontalLayout controls = new HorizontalLayout(pageSize, search);
|
||||||
controls.setWidthFull();
|
controls.setWidthFull();
|
||||||
controls.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
controls.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
||||||
|
controls.setAlignItems(FlexComponent.Alignment.END);
|
||||||
|
|
||||||
// Grid konfigurieren
|
// Grid konfigurieren
|
||||||
grid.addColumn(MyInvoiceRow::status).setHeader("Status").setAutoWidth(true);
|
grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES, GridVariant.LUMO_COMPACT,
|
||||||
grid.addColumn(MyInvoiceRow::invoiceNumber).setHeader("Rechnungsnummer").setAutoWidth(true);
|
GridVariant.LUMO_WRAP_CELL_CONTENT, GridVariant.LUMO_COLUMN_BORDERS);
|
||||||
grid.addColumn(MyInvoiceRow::date).setHeader("Datum").setAutoWidth(true);
|
grid.setWidthFull();
|
||||||
grid.addColumn(MyInvoiceRow::amount).setHeader("Betrag").setAutoWidth(true);
|
grid.addColumn(new ComponentRenderer<>(row -> statusBadge(row.status()))).setHeader("Status").setAutoWidth(true)
|
||||||
|
.setFlexGrow(0);
|
||||||
|
grid.addColumn(MyInvoicesView::formatInvoiceNumber).setHeader("Rechnungsnummer").setAutoWidth(true);
|
||||||
|
grid.addColumn(row -> DATE_FMT.format(row.date())).setHeader("Datum").setAutoWidth(true).setFlexGrow(0);
|
||||||
|
grid.addColumn(row -> CURRENCY_FMT.format(row.amount())).setHeader("Betrag").setAutoWidth(true)
|
||||||
|
.setTextAlign(ColumnTextAlign.END).setFlexGrow(0);
|
||||||
grid.setAllRowsVisible(true);
|
grid.setAllRowsVisible(true);
|
||||||
grid.setItems(allRows); // zunächst leer
|
grid.setItems(allRows); // zunächst leer
|
||||||
|
grid.addItemClickListener(event -> {
|
||||||
|
MyInvoiceRow row = event.getItem();
|
||||||
|
if (row != null) {
|
||||||
|
downloadInvoicePdf(row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Leerer Zustand
|
||||||
|
emptyState.removeAll();
|
||||||
|
H4 emptyTitle = new H4("Keine Rechnungen vorhanden");
|
||||||
|
Paragraph emptyDesc = new Paragraph("Sobald Rechnungen vorliegen, erscheinen sie hier.");
|
||||||
|
emptyState.add(emptyTitle, emptyDesc);
|
||||||
|
emptyState.getStyle().set("text-align", "center").set("color", "var(--lumo-secondary-text-color)")
|
||||||
|
.set("padding", "var(--lumo-space-m)").set("border", "1px dashed var(--lumo-contrast-20pct)")
|
||||||
|
.set("border-radius", "var(--lumo-border-radius-m)");
|
||||||
|
updateEmptyStateVisibility(allRows);
|
||||||
|
|
||||||
// Suche (einfacher Text-Filter über alle sichtbaren Felder)
|
// Suche (einfacher Text-Filter über alle sichtbaren Felder)
|
||||||
search.addValueChangeListener(e -> applyFilter(e.getValue()));
|
search.addValueChangeListener(e -> applyFilter(e.getValue()));
|
||||||
@@ -132,17 +178,51 @@ public class MyInvoicesView extends Main {
|
|||||||
pager.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
|
pager.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
|
||||||
|
|
||||||
// Zusammenbauen
|
// Zusammenbauen
|
||||||
card.add(header, controls, grid, pager);
|
card.add(header, controls, grid, emptyState, pager);
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyFilter(String filter) {
|
private void applyFilter(String filter) {
|
||||||
String f = filter == null ? "" : filter.toLowerCase();
|
String f = filter == null ? "" : filter.toLowerCase();
|
||||||
grid.setItems(allRows.stream()
|
List<MyInvoiceRow> filtered = allRows.stream()
|
||||||
.filter(row -> row.status.toLowerCase().contains(f) || row.invoiceNumber.toLowerCase().contains(f)
|
.filter(row -> row.status().toLowerCase().contains(f) || row.invoiceNumber().toLowerCase().contains(f)
|
||||||
|| row.date.toString().toLowerCase().contains(f)
|
|| row.date().toString().toLowerCase().contains(f)
|
||||||
|| String.valueOf(row.amount).toLowerCase().contains(f))
|
|| String.valueOf(row.amount()).toLowerCase().contains(f))
|
||||||
.toList());
|
.toList();
|
||||||
|
grid.setItems(filtered);
|
||||||
|
updateEmptyStateVisibility(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEmptyStateVisibility(List<MyInvoiceRow> current) {
|
||||||
|
emptyState.setVisible(current == null || current.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatInvoiceNumber(MyInvoiceRow row) {
|
||||||
|
return row.invoiceNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Span statusBadge(String status) {
|
||||||
|
Span badge = new Span(status);
|
||||||
|
badge.getElement().getThemeList().add("badge pill contrast");
|
||||||
|
String s = status == null ? "" : status.toLowerCase(Locale.ROOT);
|
||||||
|
if (s.contains("bezahlt") || s.contains("paid")) {
|
||||||
|
badge.getElement().getThemeList().add("success");
|
||||||
|
} else if (s.contains("fällig") || s.contains("überfällig") || s.contains("overdue")) {
|
||||||
|
badge.getElement().getThemeList().add("error");
|
||||||
|
} else if (s.contains("offen") || s.contains("open")) {
|
||||||
|
badge.getElement().getThemeList().add("warning");
|
||||||
|
}
|
||||||
|
return badge;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTestInvoices() {
|
||||||
|
// Drei Test-Rechnungen hinzufügen
|
||||||
|
allRows.clear();
|
||||||
|
allRows.add(new MyInvoiceRow("Offen", "MI-2025-001", LocalDate.now().minusDays(10), 199.99));
|
||||||
|
allRows.add(new MyInvoiceRow("Bezahlt", "MI-2025-002", LocalDate.now().minusDays(5), 299.49));
|
||||||
|
allRows.add(new MyInvoiceRow("Überfällig", "MI-2025-003", LocalDate.now().minusDays(20), 149.00));
|
||||||
|
grid.setItems(allRows);
|
||||||
|
updateEmptyStateVisibility(allRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Div createCard(String title, Component content) {
|
private Div createCard(String title, Component content) {
|
||||||
@@ -176,4 +256,48 @@ public class MyInvoicesView extends Main {
|
|||||||
// Schlanke lokale Repräsentation für das Grid
|
// Schlanke lokale Repräsentation für das Grid
|
||||||
public record MyInvoiceRow(String status, String invoiceNumber, LocalDate date, double amount) {
|
public record MyInvoiceRow(String status, String invoiceNumber, LocalDate date, double amount) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void downloadInvoicePdf(MyInvoiceRow row) {
|
||||||
|
try {
|
||||||
|
byte[] pdfBytes = generateSystemInvoicePdf(row);
|
||||||
|
StreamResource resource = new StreamResource(row.invoiceNumber() + ".pdf",
|
||||||
|
() -> new ByteArrayInputStream(pdfBytes));
|
||||||
|
resource.setContentType("application/pdf");
|
||||||
|
resource.setCacheTime(0);
|
||||||
|
|
||||||
|
StreamRegistration registration = UI.getCurrent().getSession().getResourceRegistry()
|
||||||
|
.registerResource(resource);
|
||||||
|
UI.getCurrent().getPage().open(registration.getResourceUri().toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Optional: could log or show a simple notification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] generateSystemInvoicePdf(MyInvoiceRow row) throws Exception {
|
||||||
|
SystemInvoiceData data = new SystemInvoiceData();
|
||||||
|
data.setInvoiceNumber(row.invoiceNumber());
|
||||||
|
data.setInvoiceDate(DATE_FMT.format(row.date()));
|
||||||
|
data.setInvoiceText("Rechnung " + row.invoiceNumber());
|
||||||
|
|
||||||
|
// Minimal recipient information
|
||||||
|
data.setRecipientName("Kunde");
|
||||||
|
data.setRecipientDepartment("");
|
||||||
|
data.setRecipientStreet("");
|
||||||
|
data.setRecipientCity("");
|
||||||
|
|
||||||
|
// One simple item based on row
|
||||||
|
List<SystemInvoiceItem> items = new ArrayList<>();
|
||||||
|
String netStr = CURRENCY_FMT.format(row.amount());
|
||||||
|
items.add(new SystemInvoiceItem("1", "Position: " + row.invoiceNumber(), netStr, netStr));
|
||||||
|
data.setInvoiceItems(items);
|
||||||
|
|
||||||
|
double net = row.amount();
|
||||||
|
double vat = Math.round(net * 0.19 * 100.0) / 100.0;
|
||||||
|
double total = net + vat;
|
||||||
|
data.setNetAmount(CURRENCY_FMT.format(net));
|
||||||
|
data.setVatAmount(CURRENCY_FMT.format(vat));
|
||||||
|
data.setTotalAmount(CURRENCY_FMT.format(total));
|
||||||
|
|
||||||
|
return systemInvoiceService.generateInvoicePdfFromHtml(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,15 @@ public class SystemInvoiceService {
|
|||||||
return generatePdfFromHtmlString(filledHtml);
|
return generatePdfFromHtmlString(filledHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a SystemInvoice PDF using provided data and the HTML template.
|
||||||
|
*/
|
||||||
|
public byte[] generateInvoicePdfFromHtml(SystemInvoiceData data) throws Exception {
|
||||||
|
String htmlContent = readHtmlTemplate();
|
||||||
|
String filledHtml = fillHtmlWithInvoiceData(htmlContent, data);
|
||||||
|
return generatePdfFromHtmlString(filledHtml);
|
||||||
|
}
|
||||||
|
|
||||||
public SystemInvoiceData createSampleInvoiceData() {
|
public SystemInvoiceData createSampleInvoiceData() {
|
||||||
SystemInvoiceData data = new SystemInvoiceData();
|
SystemInvoiceData data = new SystemInvoiceData();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user