Erweiterungen
This commit is contained in:
77
src/main/java/de/assecutor/votianlt/model/InvoiceData.java
Normal file
77
src/main/java/de/assecutor/votianlt/model/InvoiceData.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package de.assecutor.votianlt.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class InvoiceData {
|
||||
|
||||
// Invoice details
|
||||
private String invoiceNumber;
|
||||
private LocalDate invoiceDate;
|
||||
private LocalDate dueDate;
|
||||
|
||||
// Company information (sender)
|
||||
private String companyName;
|
||||
private String companyStreet;
|
||||
private String companyHouseNumber;
|
||||
private String companyZip;
|
||||
private String companyCity;
|
||||
private String companyPhone;
|
||||
private String companyEmail;
|
||||
private String companyWebsite;
|
||||
|
||||
// Tax information
|
||||
private String taxNumber; // Steuernummer
|
||||
private String vatId; // USt-IdNr
|
||||
private String commercialRegister; // Handelsregistereintrag
|
||||
private String managingDirector; // Geschäftsführer
|
||||
|
||||
// Bank details
|
||||
private String bankName;
|
||||
private String iban;
|
||||
private String bic;
|
||||
|
||||
// Customer information (recipient)
|
||||
private String customerName;
|
||||
private String customerStreet;
|
||||
private String customerHouseNumber;
|
||||
private String customerZip;
|
||||
private String customerCity;
|
||||
private String customerCountry;
|
||||
|
||||
// Invoice items
|
||||
private List<InvoiceItem> items;
|
||||
|
||||
// Totals
|
||||
private BigDecimal subtotal;
|
||||
private BigDecimal vatAmount;
|
||||
private BigDecimal totalAmount;
|
||||
private BigDecimal vatRate = BigDecimal.valueOf(19.0); // 19% standard VAT rate
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class InvoiceItem {
|
||||
private String description;
|
||||
private BigDecimal quantity;
|
||||
private String unit; // e.g., "Stück", "km", "h"
|
||||
private BigDecimal unitPrice;
|
||||
private BigDecimal totalPrice;
|
||||
private BigDecimal vatRate;
|
||||
|
||||
public InvoiceItem(String description, BigDecimal quantity, String unit,
|
||||
BigDecimal unitPrice, BigDecimal vatRate) {
|
||||
this.description = description;
|
||||
this.quantity = quantity;
|
||||
this.unit = unit;
|
||||
this.unitPrice = unitPrice;
|
||||
this.vatRate = vatRate;
|
||||
this.totalPrice = quantity.multiply(unitPrice);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package de.assecutor.votianlt.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Document(collection = "pending_mqtt_messages")
|
||||
public class PendingMqttMessage {
|
||||
|
||||
@Id
|
||||
private ObjectId id;
|
||||
|
||||
@Field("topic")
|
||||
private String topic;
|
||||
|
||||
@Field("payload")
|
||||
private byte[] payload;
|
||||
|
||||
@Field("qos")
|
||||
private int qos;
|
||||
|
||||
@Field("retained")
|
||||
private boolean retained;
|
||||
|
||||
@Field("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Field("retry_count")
|
||||
private int retryCount = 0;
|
||||
|
||||
@Field("last_retry_at")
|
||||
private LocalDateTime lastRetryAt;
|
||||
|
||||
public PendingMqttMessage(String topic, byte[] payload, int qos, boolean retained) {
|
||||
this.topic = topic;
|
||||
this.payload = payload;
|
||||
this.qos = qos;
|
||||
this.retained = retained;
|
||||
this.createdAt = LocalDateTime.now();
|
||||
this.retryCount = 0;
|
||||
}
|
||||
|
||||
public void incrementRetryCount() {
|
||||
this.retryCount++;
|
||||
this.lastRetryAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package de.assecutor.votianlt.pages.base.ui.view;
|
||||
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.applayout.AppLayout;
|
||||
import com.vaadin.flow.component.avatar.Avatar;
|
||||
import com.vaadin.flow.component.avatar.AvatarVariant;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
import com.vaadin.flow.component.html.Span;
|
||||
import com.vaadin.flow.component.icon.Icon;
|
||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.component.menubar.MenuBar;
|
||||
import com.vaadin.flow.component.menubar.MenuBarVariant;
|
||||
import com.vaadin.flow.component.orderedlayout.Scroller;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.sidenav.SideNav;
|
||||
import com.vaadin.flow.component.sidenav.SideNavItem;
|
||||
import com.vaadin.flow.router.Layout;
|
||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||
import de.assecutor.votianlt.pages.view.EditProfileView;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
|
||||
import static com.vaadin.flow.theme.lumo.LumoUtility.*;
|
||||
|
||||
@AnonymousAllowed
|
||||
@Layout("admin")
|
||||
public final class AdminLayout extends AppLayout {
|
||||
|
||||
private final SecurityService securityService;
|
||||
private Div headerRef;
|
||||
private Scroller navRef;
|
||||
private Component userMenuRef;
|
||||
|
||||
public AdminLayout(SecurityService securityService) {
|
||||
this.securityService = securityService;
|
||||
setPrimarySection(Section.DRAWER);
|
||||
|
||||
// Always build the drawer; keep references and toggle visibility on attach and
|
||||
// after navigation
|
||||
headerRef = createHeader();
|
||||
navRef = new Scroller(createSideNav());
|
||||
userMenuRef = createUserMenu();
|
||||
addToDrawer(headerRef, navRef, userMenuRef);
|
||||
|
||||
updateDrawerVisibility();
|
||||
|
||||
// Re-check on attach (new UI/session) and on every navigation cycle
|
||||
addAttachListener(e -> updateDrawerVisibility());
|
||||
}
|
||||
|
||||
private void updateDrawerVisibility() {
|
||||
boolean loggedIn = securityService.isUserLoggedIn();
|
||||
if (headerRef != null)
|
||||
headerRef.setVisible(loggedIn);
|
||||
if (navRef != null)
|
||||
navRef.setVisible(loggedIn);
|
||||
if (userMenuRef != null)
|
||||
userMenuRef.setVisible(loggedIn);
|
||||
setDrawerOpened(loggedIn);
|
||||
}
|
||||
|
||||
private Div createHeader() {
|
||||
var appLogo = VaadinIcon.SHIELD.create();
|
||||
appLogo.addClassNames(TextColor.PRIMARY, IconSize.LARGE);
|
||||
|
||||
var appName = new Span("VotianLT Admin");
|
||||
appName.addClassNames(FontWeight.SEMIBOLD, FontSize.LARGE);
|
||||
|
||||
var header = new Div(appLogo, appName);
|
||||
header.addClassNames(Display.FLEX, Padding.MEDIUM, Gap.MEDIUM, AlignItems.CENTER);
|
||||
return header;
|
||||
}
|
||||
|
||||
private Component createSideNav() {
|
||||
var nav = new SideNav();
|
||||
nav.addClassNames(Margin.Horizontal.MEDIUM);
|
||||
|
||||
// Only admin-specific menu items
|
||||
SideNavItem dashboard = new SideNavItem("Dashboard", "admin-dashboard", new Icon(VaadinIcon.DASHBOARD));
|
||||
SideNavItem systemSettings = new SideNavItem("Systemeinstellungen", "admin-settings", new Icon(VaadinIcon.COG));
|
||||
SideNavItem userManagement = new SideNavItem("Benutzerverwaltung", "admin-users", new Icon(VaadinIcon.USERS));
|
||||
SideNavItem systemLogs = new SideNavItem("System-Logs", "admin-logs", new Icon(VaadinIcon.FILE_TEXT));
|
||||
|
||||
nav.addItem(dashboard);
|
||||
nav.addItem(systemSettings);
|
||||
nav.addItem(userManagement);
|
||||
nav.addItem(systemLogs);
|
||||
|
||||
// Create a vertical layout to hold menu items
|
||||
VerticalLayout navContainer = new VerticalLayout();
|
||||
navContainer.setPadding(false);
|
||||
navContainer.setSpacing(false);
|
||||
navContainer.add(nav);
|
||||
|
||||
return navContainer;
|
||||
}
|
||||
|
||||
private Component createUserMenu() {
|
||||
var userMenu = new MenuBar();
|
||||
userMenu.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
|
||||
userMenu.addClassNames(Margin.MEDIUM);
|
||||
|
||||
// Dynamically updatable components
|
||||
var avatar = new Avatar();
|
||||
avatar.addThemeVariants(AvatarVariant.LUMO_XSMALL);
|
||||
avatar.addClassNames(Margin.Right.SMALL);
|
||||
avatar.setColorIndex(1); // Different color for admin
|
||||
|
||||
var userNameSpan = new Span();
|
||||
|
||||
var userMenuItem = userMenu.addItem(avatar);
|
||||
userMenuItem.add(userNameSpan);
|
||||
|
||||
// Profile display with navigation
|
||||
userMenuItem.getSubMenu().addItem("Profil anzeigen", e -> UI.getCurrent().navigate(EditProfileView.class));
|
||||
userMenuItem.getSubMenu().addItem("Admin-Einstellungen");
|
||||
userMenuItem.getSubMenu().addItem("Abmelden", e -> securityService.logout());
|
||||
|
||||
// Update function for username and avatar
|
||||
Runnable updateUserInfo = () -> {
|
||||
String currentUser = securityService.getCurrentUsername();
|
||||
avatar.setName(currentUser + " (Admin)");
|
||||
userNameSpan.setText(currentUser);
|
||||
};
|
||||
|
||||
// Update initially and on attach
|
||||
updateUserInfo.run();
|
||||
addAttachListener(e -> updateUserInfo.run());
|
||||
|
||||
return userMenu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
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.html.Div;
|
||||
import com.vaadin.flow.component.html.H1;
|
||||
import com.vaadin.flow.component.html.H3;
|
||||
import com.vaadin.flow.component.html.Main;
|
||||
import com.vaadin.flow.component.html.Span;
|
||||
import com.vaadin.flow.component.icon.Icon;
|
||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.Menu;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||
import de.assecutor.votianlt.model.JobStatus;
|
||||
import de.assecutor.votianlt.repository.*;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Route(value = "admin-dashboard", layout = de.assecutor.votianlt.pages.base.ui.view.AdminLayout.class)
|
||||
@PageTitle("Admin Dashboard")
|
||||
@RolesAllowed("ADMIN")
|
||||
@Menu(order = 1, icon = "lumo:edit")
|
||||
@Slf4j
|
||||
public class AdminDashboardView extends Main {
|
||||
|
||||
private final JobRepository jobRepository;
|
||||
private final TaskRepository taskRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final AppUserRepository appUserRepository;
|
||||
private final CargoItemRepository cargoItemRepository;
|
||||
private final PhotoRepository photoRepository;
|
||||
private final BarcodeRepository barcodeRepository;
|
||||
private final SignatureRepository signatureRepository;
|
||||
private final CommentRepository commentRepository;
|
||||
private final PendingMqttMessageRepository pendingMqttMessageRepository;
|
||||
|
||||
private final Div statisticsContainer;
|
||||
private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
|
||||
|
||||
@Autowired
|
||||
public AdminDashboardView(
|
||||
JobRepository jobRepository,
|
||||
TaskRepository taskRepository,
|
||||
UserRepository userRepository,
|
||||
AppUserRepository appUserRepository,
|
||||
CargoItemRepository cargoItemRepository,
|
||||
PhotoRepository photoRepository,
|
||||
BarcodeRepository barcodeRepository,
|
||||
SignatureRepository signatureRepository,
|
||||
CommentRepository commentRepository,
|
||||
PendingMqttMessageRepository pendingMqttMessageRepository) {
|
||||
|
||||
this.jobRepository = jobRepository;
|
||||
this.taskRepository = taskRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.appUserRepository = appUserRepository;
|
||||
this.cargoItemRepository = cargoItemRepository;
|
||||
this.photoRepository = photoRepository;
|
||||
this.barcodeRepository = barcodeRepository;
|
||||
this.signatureRepository = signatureRepository;
|
||||
this.commentRepository = commentRepository;
|
||||
this.pendingMqttMessageRepository = pendingMqttMessageRepository;
|
||||
|
||||
setSizeFull();
|
||||
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX,
|
||||
LumoUtility.FlexDirection.COLUMN, LumoUtility.Padding.MEDIUM);
|
||||
|
||||
// Header
|
||||
H1 title = new H1("Administrator Dashboard");
|
||||
title.addClassNames(LumoUtility.Margin.Bottom.MEDIUM, LumoUtility.Margin.Top.NONE);
|
||||
|
||||
HorizontalLayout header = new HorizontalLayout(title);
|
||||
header.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
header.setWidthFull();
|
||||
|
||||
// Statistics container
|
||||
statisticsContainer = new Div();
|
||||
statisticsContainer.setSizeFull();
|
||||
|
||||
// Content container
|
||||
VerticalLayout content = new VerticalLayout(header, statisticsContainer);
|
||||
content.setSizeFull();
|
||||
content.setPadding(false);
|
||||
content.setSpacing(true);
|
||||
|
||||
add(content);
|
||||
|
||||
// Load initial statistics
|
||||
loadStatistics();
|
||||
}
|
||||
|
||||
private void loadStatistics() {
|
||||
log.info("Loading dashboard statistics for admin user");
|
||||
|
||||
// Show loading indicator
|
||||
statisticsContainer.removeAll();
|
||||
statisticsContainer.add(new Span("Lade Statistiken..."));
|
||||
|
||||
// Load statistics asynchronously
|
||||
CompletableFuture.runAsync(() -> {
|
||||
getUI().ifPresent(ui -> ui.access(() -> {
|
||||
try {
|
||||
displayStatistics();
|
||||
} catch (Exception e) {
|
||||
log.error("Error loading dashboard statistics", e);
|
||||
statisticsContainer.removeAll();
|
||||
statisticsContainer.add(new Span("Fehler beim Laden der Statistiken: " + e.getMessage()));
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private void displayStatistics() {
|
||||
statisticsContainer.removeAll();
|
||||
|
||||
// Create main layout
|
||||
VerticalLayout mainLayout = new VerticalLayout();
|
||||
mainLayout.setPadding(false);
|
||||
mainLayout.setSpacing(true);
|
||||
|
||||
// System overview section
|
||||
mainLayout.add(createSystemOverviewSection());
|
||||
|
||||
// Job statistics section
|
||||
mainLayout.add(createJobStatisticsSection());
|
||||
|
||||
// Task statistics section
|
||||
mainLayout.add(createTaskStatisticsSection());
|
||||
|
||||
// User statistics section
|
||||
mainLayout.add(createUserStatisticsSection());
|
||||
|
||||
// System health section
|
||||
mainLayout.add(createSystemHealthSection());
|
||||
|
||||
statisticsContainer.add(mainLayout);
|
||||
}
|
||||
|
||||
private VerticalLayout createSystemOverviewSection() {
|
||||
VerticalLayout section = new VerticalLayout();
|
||||
section.setPadding(false);
|
||||
section.addClassName(LumoUtility.Background.CONTRAST_5);
|
||||
section.getStyle().set("border-radius", "8px").set("padding", "1rem");
|
||||
|
||||
H3 title = new H3("System-Übersicht");
|
||||
title.addClassName(LumoUtility.Margin.Bottom.MEDIUM);
|
||||
|
||||
HorizontalLayout cards = new HorizontalLayout();
|
||||
cards.setWidthFull();
|
||||
cards.setSpacing(true);
|
||||
|
||||
// Total jobs card
|
||||
long totalJobs = jobRepository.count();
|
||||
cards.add(createStatCard("Gesamt Jobs", String.valueOf(totalJobs), VaadinIcon.PACKAGE, "blue"));
|
||||
|
||||
// Total users card
|
||||
long totalUsers = userRepository.count();
|
||||
cards.add(createStatCard("Benutzer", String.valueOf(totalUsers), VaadinIcon.USERS, "green"));
|
||||
|
||||
// Total app users card
|
||||
long totalAppUsers = appUserRepository.count();
|
||||
cards.add(createStatCard("App-Benutzer", String.valueOf(totalAppUsers), VaadinIcon.MOBILE, "purple"));
|
||||
|
||||
// Current time
|
||||
String currentTime = LocalDateTime.now().format(dateTimeFormatter);
|
||||
cards.add(createStatCard("Letzte Aktualisierung", currentTime, VaadinIcon.CLOCK, "gray"));
|
||||
|
||||
section.add(title, cards);
|
||||
return section;
|
||||
}
|
||||
|
||||
private VerticalLayout createJobStatisticsSection() {
|
||||
VerticalLayout section = new VerticalLayout();
|
||||
section.setPadding(false);
|
||||
section.addClassName(LumoUtility.Background.CONTRAST_5);
|
||||
section.getStyle().set("border-radius", "8px").set("padding", "1rem");
|
||||
|
||||
H3 title = new H3("Job-Statistiken");
|
||||
title.addClassName(LumoUtility.Margin.Bottom.MEDIUM);
|
||||
|
||||
HorizontalLayout cards = new HorizontalLayout();
|
||||
cards.setWidthFull();
|
||||
cards.setSpacing(true);
|
||||
|
||||
// Jobs by status
|
||||
try {
|
||||
long openJobs = jobRepository.countByStatus(JobStatus.CREATED);
|
||||
long inProgressJobs = jobRepository.countByStatus(JobStatus.IN_PROGRESS);
|
||||
long completedJobs = jobRepository.countByStatus(JobStatus.COMPLETED);
|
||||
|
||||
cards.add(createStatCard("Offene Jobs", String.valueOf(openJobs), VaadinIcon.HOURGLASS_START, "orange"));
|
||||
cards.add(createStatCard("In Bearbeitung", String.valueOf(inProgressJobs), VaadinIcon.PLAY, "blue"));
|
||||
cards.add(createStatCard("Abgeschlossen", String.valueOf(completedJobs), VaadinIcon.CHECK_CIRCLE, "green"));
|
||||
|
||||
// Total cargo items
|
||||
long totalCargoItems = cargoItemRepository.count();
|
||||
cards.add(createStatCard("Frachtgüter", String.valueOf(totalCargoItems), VaadinIcon.CUBE, "purple"));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not load job statistics by status", e);
|
||||
cards.add(createStatCard("Status-Info", "Nicht verfügbar", VaadinIcon.WARNING, "red"));
|
||||
}
|
||||
|
||||
section.add(title, cards);
|
||||
return section;
|
||||
}
|
||||
|
||||
private VerticalLayout createTaskStatisticsSection() {
|
||||
VerticalLayout section = new VerticalLayout();
|
||||
section.setPadding(false);
|
||||
section.addClassName(LumoUtility.Background.CONTRAST_5);
|
||||
section.getStyle().set("border-radius", "8px").set("padding", "1rem");
|
||||
|
||||
H3 title = new H3("Aufgaben-Statistiken");
|
||||
title.addClassName(LumoUtility.Margin.Bottom.MEDIUM);
|
||||
|
||||
HorizontalLayout cards = new HorizontalLayout();
|
||||
cards.setWidthFull();
|
||||
cards.setSpacing(true);
|
||||
|
||||
// Total tasks
|
||||
long totalTasks = taskRepository.count();
|
||||
cards.add(createStatCard("Gesamt Aufgaben", String.valueOf(totalTasks), VaadinIcon.TASKS, "blue"));
|
||||
|
||||
// Completed tasks
|
||||
long completedTasks = taskRepository.countByCompleted(true);
|
||||
cards.add(createStatCard("Abgeschlossen", String.valueOf(completedTasks), VaadinIcon.CHECK, "green"));
|
||||
|
||||
// Pending tasks
|
||||
long pendingTasks = totalTasks - completedTasks;
|
||||
cards.add(createStatCard("Offen", String.valueOf(pendingTasks), VaadinIcon.CLOCK, "orange"));
|
||||
|
||||
// Completion rate
|
||||
double completionRate = totalTasks > 0 ? (completedTasks * 100.0 / totalTasks) : 0;
|
||||
cards.add(createStatCard("Erfolgsquote", String.format("%.1f%%", completionRate), VaadinIcon.TRENDING_UP, "purple"));
|
||||
|
||||
section.add(title, cards);
|
||||
return section;
|
||||
}
|
||||
|
||||
private VerticalLayout createUserStatisticsSection() {
|
||||
VerticalLayout section = new VerticalLayout();
|
||||
section.setPadding(false);
|
||||
section.addClassName(LumoUtility.Background.CONTRAST_5);
|
||||
section.getStyle().set("border-radius", "8px").set("padding", "1rem");
|
||||
|
||||
H3 title = new H3("Benutzer-Aktivität");
|
||||
title.addClassName(LumoUtility.Margin.Bottom.MEDIUM);
|
||||
|
||||
HorizontalLayout cards = new HorizontalLayout();
|
||||
cards.setWidthFull();
|
||||
cards.setSpacing(true);
|
||||
|
||||
// Content statistics
|
||||
long totalPhotos = photoRepository.count();
|
||||
cards.add(createStatCard("Fotos", String.valueOf(totalPhotos), VaadinIcon.CAMERA, "blue"));
|
||||
|
||||
long totalBarcodes = barcodeRepository.count();
|
||||
cards.add(createStatCard("Barcodes", String.valueOf(totalBarcodes), VaadinIcon.BARCODE, "green"));
|
||||
|
||||
long totalSignatures = signatureRepository.count();
|
||||
cards.add(createStatCard("Unterschriften", String.valueOf(totalSignatures), VaadinIcon.EDIT, "purple"));
|
||||
|
||||
long totalComments = commentRepository.count();
|
||||
cards.add(createStatCard("Kommentare", String.valueOf(totalComments), VaadinIcon.COMMENT, "orange"));
|
||||
|
||||
section.add(title, cards);
|
||||
return section;
|
||||
}
|
||||
|
||||
private VerticalLayout createSystemHealthSection() {
|
||||
VerticalLayout section = new VerticalLayout();
|
||||
section.setPadding(false);
|
||||
section.addClassName(LumoUtility.Background.CONTRAST_5);
|
||||
section.getStyle().set("border-radius", "8px").set("padding", "1rem");
|
||||
|
||||
H3 title = new H3("System-Status");
|
||||
title.addClassName(LumoUtility.Margin.Bottom.MEDIUM);
|
||||
|
||||
HorizontalLayout cards = new HorizontalLayout();
|
||||
cards.setWidthFull();
|
||||
cards.setSpacing(true);
|
||||
|
||||
// Database connection status
|
||||
try {
|
||||
userRepository.count(); // Test database connection
|
||||
cards.add(createStatCard("Datenbank", "Verbunden", VaadinIcon.DATABASE, "green"));
|
||||
} catch (Exception e) {
|
||||
cards.add(createStatCard("Datenbank", "Fehler", VaadinIcon.DATABASE, "red"));
|
||||
}
|
||||
|
||||
// Pending MQTT messages
|
||||
long pendingMqttMessages = pendingMqttMessageRepository.count();
|
||||
String mqttStatus = pendingMqttMessages == 0 ? "OK" : "Warteschlange: " + pendingMqttMessages;
|
||||
String mqttColor = pendingMqttMessages == 0 ? "green" : "orange";
|
||||
cards.add(createStatCard("MQTT", mqttStatus, VaadinIcon.CONNECT, mqttColor));
|
||||
|
||||
// System uptime (placeholder)
|
||||
cards.add(createStatCard("Anwendung", "Läuft", VaadinIcon.HEART, "green"));
|
||||
|
||||
// Memory usage (placeholder)
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long maxMemory = runtime.maxMemory() / 1024 / 1024; // MB
|
||||
long totalMemory = runtime.totalMemory() / 1024 / 1024; // MB
|
||||
long usedMemory = totalMemory - (runtime.freeMemory() / 1024 / 1024); // MB
|
||||
String memoryInfo = usedMemory + "/" + maxMemory + " MB";
|
||||
cards.add(createStatCard("Speicher", memoryInfo, VaadinIcon.SERVER, "blue"));
|
||||
|
||||
section.add(title, cards);
|
||||
return section;
|
||||
}
|
||||
|
||||
private Div createStatCard(String title, String value, VaadinIcon icon, String color) {
|
||||
Div card = new Div();
|
||||
card.addClassName(LumoUtility.Background.BASE);
|
||||
card.getStyle()
|
||||
.set("border-radius", "8px")
|
||||
.set("padding", "1rem")
|
||||
.set("box-shadow", "0 2px 4px rgba(0,0,0,0.1)")
|
||||
.set("min-width", "200px")
|
||||
.set("border-left", "4px solid var(--lumo-" + color + "-color, #007bff)");
|
||||
|
||||
HorizontalLayout header = new HorizontalLayout();
|
||||
header.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
|
||||
|
||||
Icon cardIcon = icon.create();
|
||||
cardIcon.getStyle().set("color", "var(--lumo-" + color + "-color, #007bff)");
|
||||
|
||||
Span titleSpan = new Span(title);
|
||||
titleSpan.addClassName(LumoUtility.FontSize.SMALL);
|
||||
titleSpan.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||
|
||||
header.add(titleSpan, cardIcon);
|
||||
|
||||
Span valueSpan = new Span(value);
|
||||
valueSpan.addClassName(LumoUtility.FontSize.XLARGE);
|
||||
valueSpan.addClassName(LumoUtility.FontWeight.BOLD);
|
||||
|
||||
VerticalLayout content = new VerticalLayout(header, valueSpan);
|
||||
content.setPadding(false);
|
||||
content.setSpacing(false);
|
||||
|
||||
card.add(content);
|
||||
return card;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package de.assecutor.votianlt.repository;
|
||||
|
||||
import de.assecutor.votianlt.model.PendingMqttMessage;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface PendingMqttMessageRepository extends MongoRepository<PendingMqttMessage, ObjectId> {
|
||||
|
||||
/**
|
||||
* Find all pending messages ordered by creation time (oldest first)
|
||||
*/
|
||||
List<PendingMqttMessage> findAllByOrderByCreatedAtAsc();
|
||||
|
||||
/**
|
||||
* Find messages that haven't been retried for a while (for cleanup)
|
||||
*/
|
||||
List<PendingMqttMessage> findByLastRetryAtBeforeOrLastRetryAtIsNull(LocalDateTime before);
|
||||
|
||||
/**
|
||||
* Count pending messages
|
||||
*/
|
||||
long count();
|
||||
|
||||
/**
|
||||
* Delete messages older than specified date (for cleanup)
|
||||
*/
|
||||
void deleteByCreatedAtBefore(LocalDateTime before);
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
package de.assecutor.votianlt.service;
|
||||
|
||||
import com.itextpdf.kernel.colors.ColorConstants;
|
||||
import com.itextpdf.kernel.font.PdfFont;
|
||||
import com.itextpdf.kernel.font.PdfFontFactory;
|
||||
import com.itextpdf.kernel.geom.PageSize;
|
||||
import com.itextpdf.kernel.pdf.PdfDocument;
|
||||
import com.itextpdf.kernel.pdf.PdfWriter;
|
||||
import com.itextpdf.layout.Document;
|
||||
import com.itextpdf.layout.borders.Border;
|
||||
import com.itextpdf.layout.element.Cell;
|
||||
import com.itextpdf.layout.element.Paragraph;
|
||||
import com.itextpdf.layout.element.Table;
|
||||
import com.itextpdf.layout.properties.TextAlignment;
|
||||
import com.itextpdf.layout.properties.UnitValue;
|
||||
import de.assecutor.votianlt.model.InvoiceData;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class InvoicePdfGenerator {
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy", Locale.GERMANY);
|
||||
private static final int ITEMS_PER_PAGE = 15; // Maximum items per page before page break
|
||||
|
||||
public byte[] generateInvoicePdf(InvoiceData invoiceData) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
try (PdfWriter writer = new PdfWriter(baos);
|
||||
PdfDocument pdfDoc = new PdfDocument(writer);
|
||||
Document document = new Document(pdfDoc, PageSize.A4)) {
|
||||
|
||||
// Set document margins
|
||||
document.setMargins(50, 50, 80, 50); // top, right, bottom, left
|
||||
|
||||
PdfFont font = PdfFontFactory.createFont("Helvetica");
|
||||
PdfFont boldFont = PdfFontFactory.createFont("Helvetica-Bold");
|
||||
|
||||
document.setFont(font);
|
||||
|
||||
// Calculate totals
|
||||
calculateTotals(invoiceData);
|
||||
|
||||
// Add header with company and customer info
|
||||
addHeader(document, invoiceData, boldFont, font);
|
||||
|
||||
// Add invoice details
|
||||
addInvoiceDetails(document, invoiceData, boldFont, font);
|
||||
|
||||
// Add invoice items (with page breaks if necessary)
|
||||
addInvoiceItems(document, invoiceData, boldFont, font);
|
||||
|
||||
// Add totals
|
||||
addTotals(document, invoiceData, boldFont, font);
|
||||
|
||||
// Add footer on all pages
|
||||
addFooter(pdfDoc, invoiceData, font);
|
||||
|
||||
document.close();
|
||||
}
|
||||
|
||||
log.info("Generated invoice PDF for invoice number: {}", invoiceData.getInvoiceNumber());
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private void calculateTotals(InvoiceData invoiceData) {
|
||||
BigDecimal subtotal = BigDecimal.ZERO;
|
||||
BigDecimal vatAmount = BigDecimal.ZERO;
|
||||
|
||||
for (InvoiceData.InvoiceItem item : invoiceData.getItems()) {
|
||||
BigDecimal itemTotal = item.getQuantity().multiply(item.getUnitPrice());
|
||||
item.setTotalPrice(itemTotal);
|
||||
subtotal = subtotal.add(itemTotal);
|
||||
|
||||
// Calculate VAT for this item
|
||||
BigDecimal itemVat = itemTotal.multiply(item.getVatRate())
|
||||
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
||||
vatAmount = vatAmount.add(itemVat);
|
||||
}
|
||||
|
||||
invoiceData.setSubtotal(subtotal);
|
||||
invoiceData.setVatAmount(vatAmount);
|
||||
invoiceData.setTotalAmount(subtotal.add(vatAmount));
|
||||
}
|
||||
|
||||
private void addHeader(Document document, InvoiceData invoiceData, PdfFont boldFont, PdfFont font) {
|
||||
// Create header table with company info (left) and customer info (right)
|
||||
Table headerTable = new Table(UnitValue.createPercentArray(new float[]{50, 50}))
|
||||
.setWidth(UnitValue.createPercentValue(100))
|
||||
.setMarginBottom(20);
|
||||
|
||||
// Company information (left side)
|
||||
Cell companyCell = new Cell()
|
||||
.setBorder(Border.NO_BORDER)
|
||||
.setVerticalAlignment(com.itextpdf.layout.properties.VerticalAlignment.TOP);
|
||||
|
||||
companyCell.add(new Paragraph(invoiceData.getCompanyName())
|
||||
.setFont(boldFont).setFontSize(14));
|
||||
companyCell.add(new Paragraph(invoiceData.getCompanyStreet() + " " +
|
||||
invoiceData.getCompanyHouseNumber()).setFont(font).setFontSize(10));
|
||||
companyCell.add(new Paragraph(invoiceData.getCompanyZip() + " " +
|
||||
invoiceData.getCompanyCity()).setFont(font).setFontSize(10));
|
||||
|
||||
if (invoiceData.getCompanyPhone() != null) {
|
||||
companyCell.add(new Paragraph("Tel: " + invoiceData.getCompanyPhone())
|
||||
.setFont(font).setFontSize(10));
|
||||
}
|
||||
if (invoiceData.getCompanyEmail() != null) {
|
||||
companyCell.add(new Paragraph("E-Mail: " + invoiceData.getCompanyEmail())
|
||||
.setFont(font).setFontSize(10));
|
||||
}
|
||||
|
||||
// Customer information (right side)
|
||||
Cell customerCell = new Cell()
|
||||
.setBorder(Border.NO_BORDER)
|
||||
.setVerticalAlignment(com.itextpdf.layout.properties.VerticalAlignment.TOP);
|
||||
|
||||
customerCell.add(new Paragraph("Rechnungsempfänger:")
|
||||
.setFont(boldFont).setFontSize(10));
|
||||
customerCell.add(new Paragraph(invoiceData.getCustomerName())
|
||||
.setFont(boldFont).setFontSize(12));
|
||||
customerCell.add(new Paragraph(invoiceData.getCustomerStreet() + " " +
|
||||
invoiceData.getCustomerHouseNumber()).setFont(font).setFontSize(10));
|
||||
customerCell.add(new Paragraph(invoiceData.getCustomerZip() + " " +
|
||||
invoiceData.getCustomerCity()).setFont(font).setFontSize(10));
|
||||
|
||||
if (invoiceData.getCustomerCountry() != null &&
|
||||
!invoiceData.getCustomerCountry().equalsIgnoreCase("Deutschland")) {
|
||||
customerCell.add(new Paragraph(invoiceData.getCustomerCountry())
|
||||
.setFont(font).setFontSize(10));
|
||||
}
|
||||
|
||||
headerTable.addCell(companyCell);
|
||||
headerTable.addCell(customerCell);
|
||||
document.add(headerTable);
|
||||
}
|
||||
|
||||
private void addInvoiceDetails(Document document, InvoiceData invoiceData, PdfFont boldFont, PdfFont font) {
|
||||
// Invoice title
|
||||
document.add(new Paragraph("RECHNUNG")
|
||||
.setFont(boldFont)
|
||||
.setFontSize(20)
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setMarginTop(20)
|
||||
.setMarginBottom(20));
|
||||
|
||||
// Invoice details table
|
||||
Table detailsTable = new Table(UnitValue.createPercentArray(new float[]{30, 20, 30, 20}))
|
||||
.setWidth(UnitValue.createPercentValue(100))
|
||||
.setMarginBottom(20);
|
||||
|
||||
detailsTable.addCell(new Cell().setBorder(Border.NO_BORDER)
|
||||
.add(new Paragraph("Rechnungsnummer:").setFont(boldFont).setFontSize(10)));
|
||||
detailsTable.addCell(new Cell().setBorder(Border.NO_BORDER)
|
||||
.add(new Paragraph(invoiceData.getInvoiceNumber()).setFont(font).setFontSize(10)));
|
||||
|
||||
detailsTable.addCell(new Cell().setBorder(Border.NO_BORDER)
|
||||
.add(new Paragraph("Rechnungsdatum:").setFont(boldFont).setFontSize(10)));
|
||||
detailsTable.addCell(new Cell().setBorder(Border.NO_BORDER)
|
||||
.add(new Paragraph(invoiceData.getInvoiceDate().format(DATE_FORMATTER)).setFont(font).setFontSize(10)));
|
||||
|
||||
if (invoiceData.getDueDate() != null) {
|
||||
detailsTable.addCell(new Cell().setBorder(Border.NO_BORDER)
|
||||
.add(new Paragraph("Fälligkeitsdatum:").setFont(boldFont).setFontSize(10)));
|
||||
detailsTable.addCell(new Cell().setBorder(Border.NO_BORDER)
|
||||
.add(new Paragraph(invoiceData.getDueDate().format(DATE_FORMATTER)).setFont(font).setFontSize(10)));
|
||||
}
|
||||
|
||||
document.add(detailsTable);
|
||||
}
|
||||
|
||||
private void addInvoiceItems(Document document, InvoiceData invoiceData, PdfFont boldFont, PdfFont font) {
|
||||
// Items table header
|
||||
Table itemsTable = new Table(UnitValue.createPercentArray(new float[]{40, 10, 10, 15, 10, 15}))
|
||||
.setWidth(UnitValue.createPercentValue(100))
|
||||
.setMarginBottom(10);
|
||||
|
||||
// Header row
|
||||
itemsTable.addHeaderCell(new Cell().setBackgroundColor(ColorConstants.LIGHT_GRAY)
|
||||
.add(new Paragraph("Beschreibung").setFont(boldFont).setFontSize(10)));
|
||||
itemsTable.addHeaderCell(new Cell().setBackgroundColor(ColorConstants.LIGHT_GRAY)
|
||||
.add(new Paragraph("Menge").setFont(boldFont).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
itemsTable.addHeaderCell(new Cell().setBackgroundColor(ColorConstants.LIGHT_GRAY)
|
||||
.add(new Paragraph("Einheit").setFont(boldFont).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.CENTER));
|
||||
itemsTable.addHeaderCell(new Cell().setBackgroundColor(ColorConstants.LIGHT_GRAY)
|
||||
.add(new Paragraph("Einzelpreis").setFont(boldFont).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
itemsTable.addHeaderCell(new Cell().setBackgroundColor(ColorConstants.LIGHT_GRAY)
|
||||
.add(new Paragraph("MwSt%").setFont(boldFont).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
itemsTable.addHeaderCell(new Cell().setBackgroundColor(ColorConstants.LIGHT_GRAY)
|
||||
.add(new Paragraph("Gesamtpreis").setFont(boldFont).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
|
||||
// Add items
|
||||
int itemCount = 0;
|
||||
for (InvoiceData.InvoiceItem item : invoiceData.getItems()) {
|
||||
if (itemCount > 0 && itemCount % ITEMS_PER_PAGE == 0) {
|
||||
// Add current table and start new page
|
||||
document.add(itemsTable);
|
||||
document.add(new com.itextpdf.layout.element.AreaBreak());
|
||||
|
||||
// Create new table with header
|
||||
itemsTable = new Table(UnitValue.createPercentArray(new float[]{40, 10, 10, 15, 10, 15}))
|
||||
.setWidth(UnitValue.createPercentValue(100))
|
||||
.setMarginBottom(10);
|
||||
|
||||
// Re-add header
|
||||
itemsTable.addHeaderCell(new Cell().setBackgroundColor(ColorConstants.LIGHT_GRAY)
|
||||
.add(new Paragraph("Beschreibung").setFont(boldFont).setFontSize(10)));
|
||||
itemsTable.addHeaderCell(new Cell().setBackgroundColor(ColorConstants.LIGHT_GRAY)
|
||||
.add(new Paragraph("Menge").setFont(boldFont).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
itemsTable.addHeaderCell(new Cell().setBackgroundColor(ColorConstants.LIGHT_GRAY)
|
||||
.add(new Paragraph("Einheit").setFont(boldFont).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.CENTER));
|
||||
itemsTable.addHeaderCell(new Cell().setBackgroundColor(ColorConstants.LIGHT_GRAY)
|
||||
.add(new Paragraph("Einzelpreis").setFont(boldFont).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
itemsTable.addHeaderCell(new Cell().setBackgroundColor(ColorConstants.LIGHT_GRAY)
|
||||
.add(new Paragraph("MwSt%").setFont(boldFont).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
itemsTable.addHeaderCell(new Cell().setBackgroundColor(ColorConstants.LIGHT_GRAY)
|
||||
.add(new Paragraph("Gesamtpreis").setFont(boldFont).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
}
|
||||
|
||||
itemsTable.addCell(new Cell().add(new Paragraph(item.getDescription()).setFont(font).setFontSize(9)));
|
||||
itemsTable.addCell(new Cell().add(new Paragraph(formatDecimal(item.getQuantity())).setFont(font).setFontSize(9))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
itemsTable.addCell(new Cell().add(new Paragraph(item.getUnit() != null ? item.getUnit() : "").setFont(font).setFontSize(9))
|
||||
.setTextAlignment(TextAlignment.CENTER));
|
||||
itemsTable.addCell(new Cell().add(new Paragraph(formatCurrency(item.getUnitPrice())).setFont(font).setFontSize(9))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
itemsTable.addCell(new Cell().add(new Paragraph(formatDecimal(item.getVatRate()) + "%").setFont(font).setFontSize(9))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
itemsTable.addCell(new Cell().add(new Paragraph(formatCurrency(item.getTotalPrice())).setFont(font).setFontSize(9))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
|
||||
itemCount++;
|
||||
}
|
||||
|
||||
document.add(itemsTable);
|
||||
}
|
||||
|
||||
private void addTotals(Document document, InvoiceData invoiceData, PdfFont boldFont, PdfFont font) {
|
||||
// Totals table (right-aligned)
|
||||
Table totalsTable = new Table(UnitValue.createPercentArray(new float[]{70, 30}))
|
||||
.setWidth(UnitValue.createPercentValue(100))
|
||||
.setMarginTop(20);
|
||||
|
||||
// Empty cell for spacing
|
||||
totalsTable.addCell(new Cell().setBorder(Border.NO_BORDER));
|
||||
|
||||
// Totals cell
|
||||
Cell totalsCell = new Cell().setBorder(Border.NO_BORDER);
|
||||
|
||||
Table innerTotalsTable = new Table(UnitValue.createPercentArray(new float[]{70, 30}))
|
||||
.setWidth(UnitValue.createPercentValue(100));
|
||||
|
||||
innerTotalsTable.addCell(new Cell().setBorder(Border.NO_BORDER)
|
||||
.add(new Paragraph("Nettobetrag:").setFont(font).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
innerTotalsTable.addCell(new Cell().setBorder(Border.NO_BORDER)
|
||||
.add(new Paragraph(formatCurrency(invoiceData.getSubtotal())).setFont(font).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
|
||||
innerTotalsTable.addCell(new Cell().setBorder(Border.NO_BORDER)
|
||||
.add(new Paragraph("MwSt (" + formatDecimal(invoiceData.getVatRate()) + "%):").setFont(font).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
innerTotalsTable.addCell(new Cell().setBorder(Border.NO_BORDER)
|
||||
.add(new Paragraph(formatCurrency(invoiceData.getVatAmount())).setFont(font).setFontSize(10))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
|
||||
innerTotalsTable.addCell(new Cell().setBorder(Border.NO_BORDER)
|
||||
.add(new Paragraph("Rechnungsbetrag:").setFont(boldFont).setFontSize(12))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
innerTotalsTable.addCell(new Cell().setBorder(Border.NO_BORDER)
|
||||
.add(new Paragraph(formatCurrency(invoiceData.getTotalAmount())).setFont(boldFont).setFontSize(12))
|
||||
.setTextAlignment(TextAlignment.RIGHT));
|
||||
|
||||
totalsCell.add(innerTotalsTable);
|
||||
totalsTable.addCell(totalsCell);
|
||||
|
||||
document.add(totalsTable);
|
||||
}
|
||||
|
||||
private void addFooter(PdfDocument pdfDoc, InvoiceData invoiceData, PdfFont font) {
|
||||
int numberOfPages = pdfDoc.getNumberOfPages();
|
||||
|
||||
for (int i = 1; i <= numberOfPages; i++) {
|
||||
com.itextpdf.kernel.pdf.PdfPage page = pdfDoc.getPage(i);
|
||||
|
||||
Document footerDoc = new Document(pdfDoc, PageSize.A4);
|
||||
footerDoc.setFont(font);
|
||||
|
||||
// Footer content
|
||||
Table footerTable = new Table(UnitValue.createPercentArray(new float[]{33, 33, 34}))
|
||||
.setWidth(UnitValue.createPercentValue(100))
|
||||
.setFixedPosition(50, 20, 495); // x, y, width
|
||||
|
||||
// Bank details
|
||||
Cell bankCell = new Cell().setBorder(Border.NO_BORDER);
|
||||
bankCell.add(new Paragraph("Bankverbindung:").setFont(font).setFontSize(8).setBold());
|
||||
bankCell.add(new Paragraph(invoiceData.getBankName()).setFont(font).setFontSize(7));
|
||||
bankCell.add(new Paragraph("IBAN: " + invoiceData.getIban()).setFont(font).setFontSize(7));
|
||||
bankCell.add(new Paragraph("BIC: " + invoiceData.getBic()).setFont(font).setFontSize(7));
|
||||
|
||||
// Tax information
|
||||
Cell taxCell = new Cell().setBorder(Border.NO_BORDER);
|
||||
taxCell.add(new Paragraph("Steuerliche Angaben:").setFont(font).setFontSize(8).setBold());
|
||||
if (invoiceData.getTaxNumber() != null) {
|
||||
taxCell.add(new Paragraph("Steuernr.: " + invoiceData.getTaxNumber()).setFont(font).setFontSize(7));
|
||||
}
|
||||
if (invoiceData.getVatId() != null) {
|
||||
taxCell.add(new Paragraph("USt-IdNr.: " + invoiceData.getVatId()).setFont(font).setFontSize(7));
|
||||
}
|
||||
|
||||
// Company details
|
||||
Cell companyCell = new Cell().setBorder(Border.NO_BORDER);
|
||||
if (invoiceData.getCommercialRegister() != null) {
|
||||
companyCell.add(new Paragraph(invoiceData.getCommercialRegister()).setFont(font).setFontSize(7));
|
||||
}
|
||||
if (invoiceData.getManagingDirector() != null) {
|
||||
companyCell.add(new Paragraph("Geschäftsführer: " + invoiceData.getManagingDirector()).setFont(font).setFontSize(7));
|
||||
}
|
||||
|
||||
footerTable.addCell(bankCell);
|
||||
footerTable.addCell(taxCell);
|
||||
footerTable.addCell(companyCell);
|
||||
|
||||
footerDoc.add(footerTable);
|
||||
footerDoc.close();
|
||||
}
|
||||
}
|
||||
|
||||
private String formatCurrency(BigDecimal amount) {
|
||||
return String.format(Locale.GERMANY, "%.2f €", amount);
|
||||
}
|
||||
|
||||
private String formatDecimal(BigDecimal amount) {
|
||||
return String.format(Locale.GERMANY, "%.2f", amount);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user