Erweiterungen
This commit is contained in:
@@ -63,6 +63,9 @@ public class CustomerInvoice {
|
||||
private String jobId; // Referenz auf den Auftrag
|
||||
private String userId; // Referenz auf den Benutzer (Rechnungsersteller)
|
||||
|
||||
// Gespeicherte PDF-Daten (Base64-kodiert)
|
||||
private byte[] pdfData;
|
||||
|
||||
// Constructors
|
||||
public CustomerInvoice() {
|
||||
}
|
||||
@@ -361,4 +364,12 @@ public class CustomerInvoice {
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public byte[] getPdfData() {
|
||||
return pdfData;
|
||||
}
|
||||
|
||||
public void setPdfData(byte[] pdfData) {
|
||||
this.pdfData = pdfData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
|
||||
import com.vaadin.flow.component.dialog.Dialog;
|
||||
import com.vaadin.flow.component.html.IFrame;
|
||||
import com.vaadin.flow.server.StreamResource;
|
||||
@@ -435,7 +436,6 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
}
|
||||
|
||||
try {
|
||||
// Get current user
|
||||
Optional<User> currentUserOpt = securityService.getAuthenticatedUser()
|
||||
.flatMap(auth -> userRepository.findByEmail(auth.getUsername()));
|
||||
|
||||
@@ -447,7 +447,6 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
|
||||
currentUser = currentUserOpt.get();
|
||||
|
||||
// Load invoice template from service
|
||||
Optional<InvoiceTemplate> templateOpt = invoiceTemplateService
|
||||
.getTemplateByUserId(currentUser.getId().toString());
|
||||
if (templateOpt.isEmpty()) {
|
||||
@@ -463,38 +462,51 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
return;
|
||||
}
|
||||
|
||||
// Rechnungsnummer generieren (atomar, Präfix + Zähler)
|
||||
String invoiceNumber = userInvoiceDataService.generateNextInvoiceNumber(currentUser.getId());
|
||||
// Vorschau-PDF mit Platzhalter-Nummer generieren (noch kein Speichern)
|
||||
byte[] pdfBytes = generateInvoicePdfFromTemplate(templateData, currentUser,
|
||||
getTranslation("createinvoice.preview.number"));
|
||||
|
||||
showPdfPreviewDialog(pdfBytes, templateData, currentUser);
|
||||
|
||||
} catch (Exception ex) {
|
||||
log.error("Fehler beim Erstellen der Rechnungsvorschau", ex);
|
||||
Notification.show(getTranslation("createinvoice.notification.error", ex.getMessage()), 5000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveInvoice(String templateData, User user, Dialog previewDialog) {
|
||||
try {
|
||||
String invoiceNumber = userInvoiceDataService.generateNextInvoiceNumber(user.getId());
|
||||
|
||||
// Rechnung in MongoDB speichern
|
||||
BigDecimal netAmount = calculateNetAmount();
|
||||
BigDecimal vatRate = calculateAverageVatRate();
|
||||
BigDecimal vatAmount = netAmount.multiply(vatRate);
|
||||
BigDecimal totalAmount = netAmount.add(vatAmount);
|
||||
|
||||
byte[] pdfBytes = generateInvoicePdfFromTemplate(templateData, user, invoiceNumber);
|
||||
|
||||
CustomerInvoice invoice = new CustomerInvoice();
|
||||
invoice.setInvoiceNumber(invoiceNumber);
|
||||
invoice.setInvoiceDate(java.time.LocalDate.now());
|
||||
invoice.setJobId(currentJob.getId().toHexString());
|
||||
invoice.setUserId(currentUser.getId().toHexString());
|
||||
invoice.setUserId(user.getId().toHexString());
|
||||
invoice.setNetAmount(netAmount);
|
||||
invoice.setVatRate(vatRate);
|
||||
invoice.setVatAmount(vatAmount);
|
||||
invoice.setTotalAmount(totalAmount);
|
||||
invoice.setPdfData(pdfBytes);
|
||||
CustomerInvoice savedInvoice = customerInvoiceRepository.save(invoice);
|
||||
|
||||
// Job mit Rechnungs-ID verknüpfen und speichern
|
||||
currentJob.setInvoiceId(savedInvoice.getId());
|
||||
jobRepository.save(currentJob);
|
||||
|
||||
// PDF mit Rechnungsnummer generieren
|
||||
byte[] pdfBytes = generateInvoicePdfFromTemplate(templateData, currentUser, invoiceNumber);
|
||||
|
||||
// PDF im Dialog anzeigen
|
||||
showPdfInDialog(pdfBytes, "Rechnung " + invoiceNumber);
|
||||
previewDialog.close();
|
||||
Notification.show(getTranslation("createinvoice.notification.saved", invoiceNumber), 4000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
|
||||
} catch (Exception ex) {
|
||||
log.error("Fehler beim Erstellen der Rechnung", ex);
|
||||
log.error("Fehler beim Speichern der Rechnung", ex);
|
||||
Notification.show(getTranslation("createinvoice.notification.error", ex.getMessage()), 5000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
}
|
||||
@@ -617,45 +629,70 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
return value != null ? value : "";
|
||||
}
|
||||
|
||||
private void showPdfInDialog(byte[] pdfBytes, String title) {
|
||||
// Create a stream resource for the PDF
|
||||
StreamResource resource = new StreamResource(title.replaceAll("[^a-zA-Z0-9\\-]", "_") + ".pdf",
|
||||
() -> new java.io.ByteArrayInputStream(pdfBytes));
|
||||
resource.setContentType("application/pdf");
|
||||
resource.setCacheTime(0);
|
||||
private void showPdfPreviewDialog(byte[] pdfBytes, String templateData, User user) {
|
||||
String title = getTranslation("createinvoice.preview.title");
|
||||
|
||||
// Store resource in session for access
|
||||
VaadinSession.getCurrent().setAttribute("currentInvoicePdf", resource);
|
||||
|
||||
// Create dialog
|
||||
Dialog pdfDialog = new Dialog();
|
||||
pdfDialog.setHeaderTitle(title);
|
||||
pdfDialog.setWidth("90vw");
|
||||
pdfDialog.setHeight("90vh");
|
||||
|
||||
// Create iframe for PDF
|
||||
IFrame pdfFrame = new IFrame();
|
||||
pdfFrame.setWidth("100%");
|
||||
pdfFrame.setHeight("100%");
|
||||
|
||||
// Use data URL for PDF display
|
||||
String base64Pdf = java.util.Base64.getEncoder().encodeToString(pdfBytes);
|
||||
String dataUrl = "data:application/pdf;base64," + base64Pdf;
|
||||
pdfFrame.getElement().setAttribute("src", dataUrl);
|
||||
pdfFrame.getElement().setAttribute("src", "data:application/pdf;base64," + base64Pdf);
|
||||
pdfFrame.getStyle().set("border", "none");
|
||||
|
||||
// Close button
|
||||
Button closeButton = new Button(getTranslation("button.close"), e -> pdfDialog.close());
|
||||
closeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
closeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
|
||||
// Download button
|
||||
Button downloadButton = new Button(getTranslation("button.download"), e -> {
|
||||
getElement().executeJs("const link = document.createElement('a');"
|
||||
+ "link.href = 'data:application/pdf;base64," + base64Pdf + "';" + "link.download = '"
|
||||
+ title.replaceAll("[^a-zA-Z0-9\\-]", "_") + ".pdf';" + "link.click();");
|
||||
Button saveButton = new Button(getTranslation("createinvoice.button.save"), e -> {
|
||||
ConfirmDialog confirm = new ConfirmDialog();
|
||||
confirm.setHeader(getTranslation("createinvoice.confirm.save.title"));
|
||||
confirm.setText(getTranslation("createinvoice.confirm.save.message"));
|
||||
confirm.setConfirmText(getTranslation("createinvoice.confirm.save.confirm"));
|
||||
confirm.setConfirmButtonTheme("primary");
|
||||
confirm.setCancelText(getTranslation("button.cancel"));
|
||||
confirm.setCancelable(true);
|
||||
confirm.addConfirmListener(ev -> saveInvoice(templateData, user, pdfDialog));
|
||||
confirm.open();
|
||||
});
|
||||
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
|
||||
|
||||
pdfDialog.add(pdfFrame);
|
||||
pdfDialog.getFooter().add(closeButton, saveButton);
|
||||
pdfDialog.open();
|
||||
}
|
||||
|
||||
public static void showSavedInvoiceDialog(byte[] pdfBytes, String invoiceNumber,
|
||||
com.vaadin.flow.component.Component parent) {
|
||||
String title = "Rechnung " + invoiceNumber;
|
||||
Dialog pdfDialog = new Dialog();
|
||||
pdfDialog.setHeaderTitle(title);
|
||||
pdfDialog.setWidth("90vw");
|
||||
pdfDialog.setHeight("90vh");
|
||||
|
||||
IFrame pdfFrame = new IFrame();
|
||||
pdfFrame.setWidth("100%");
|
||||
pdfFrame.setHeight("100%");
|
||||
|
||||
String base64Pdf = java.util.Base64.getEncoder().encodeToString(pdfBytes);
|
||||
pdfFrame.getElement().setAttribute("src", "data:application/pdf;base64," + base64Pdf);
|
||||
pdfFrame.getStyle().set("border", "none");
|
||||
|
||||
Button downloadButton = new Button("Herunterladen", e -> {
|
||||
parent.getElement().executeJs("const link = document.createElement('a');"
|
||||
+ "link.href = 'data:application/pdf;base64," + base64Pdf + "';"
|
||||
+ "link.download = '" + title.replaceAll("[^a-zA-Z0-9\\-]", "_") + ".pdf';"
|
||||
+ "link.click();");
|
||||
});
|
||||
downloadButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
|
||||
Button closeButton = new Button("Schließen", e -> pdfDialog.close());
|
||||
closeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
|
||||
pdfDialog.add(pdfFrame);
|
||||
pdfDialog.getFooter().add(downloadButton, closeButton);
|
||||
pdfDialog.open();
|
||||
|
||||
@@ -22,6 +22,8 @@ import de.assecutor.votianlt.model.Job;
|
||||
import de.assecutor.votianlt.model.JobStatus;
|
||||
import de.assecutor.votianlt.messaging.MessagingPublisher;
|
||||
import de.assecutor.votianlt.util.DateTimeFormatUtil;
|
||||
import de.assecutor.votianlt.model.invoices.CustomerInvoice;
|
||||
import de.assecutor.votianlt.repository.CustomerInvoiceRepository;
|
||||
import de.assecutor.votianlt.repository.JobRepository;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
import de.assecutor.votianlt.service.ClientConnectionService;
|
||||
@@ -47,17 +49,19 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
||||
private final SecurityService securityService;
|
||||
private final ClientConnectionService clientConnectionService;
|
||||
private final MessagingPublisher messagingPublisher;
|
||||
private final CustomerInvoiceRepository customerInvoiceRepository;
|
||||
private final Grid<Job> grid = new Grid<>(Job.class, false);
|
||||
|
||||
@Autowired
|
||||
public ShowJobsView(JobRepository jobRepository, JobHistoryService jobHistoryService,
|
||||
SecurityService securityService, ClientConnectionService clientConnectionService,
|
||||
MessagingPublisher messagingPublisher) {
|
||||
MessagingPublisher messagingPublisher, CustomerInvoiceRepository customerInvoiceRepository) {
|
||||
this.jobRepository = jobRepository;
|
||||
this.jobHistoryService = jobHistoryService;
|
||||
this.securityService = securityService;
|
||||
this.clientConnectionService = clientConnectionService;
|
||||
this.messagingPublisher = messagingPublisher;
|
||||
this.customerInvoiceRepository = customerInvoiceRepository;
|
||||
setSizeFull();
|
||||
setPadding(true);
|
||||
setSpacing(true);
|
||||
@@ -139,11 +143,28 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
||||
if (job.getStatus() == JobStatus.COMPLETED) {
|
||||
Button invoiceBtn = new Button(new Icon(VaadinIcon.DOLLAR));
|
||||
invoiceBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SUCCESS);
|
||||
if (job.getInvoiceId() != null) {
|
||||
invoiceBtn.setTooltipText(getTranslation("jobs.tooltip.showinvoice"));
|
||||
invoiceBtn.addClickListener(e -> {
|
||||
e.getSource().getElement().getNode();
|
||||
customerInvoiceRepository.findById(job.getInvoiceId()).ifPresentOrElse(
|
||||
invoice -> {
|
||||
if (invoice.getPdfData() != null) {
|
||||
CreateInvoiceView.showSavedInvoiceDialog(invoice.getPdfData(),
|
||||
invoice.getInvoiceNumber(), this);
|
||||
} else {
|
||||
getUI().ifPresent(ui -> ui.navigate("create_invoice/" + job.getId().toHexString()));
|
||||
}
|
||||
},
|
||||
() -> getUI().ifPresent(ui -> ui.navigate("create_invoice/" + job.getId().toHexString())));
|
||||
});
|
||||
} else {
|
||||
invoiceBtn.setTooltipText(getTranslation("jobs.tooltip.createinvoice"));
|
||||
invoiceBtn.addClickListener(e -> {
|
||||
e.getSource().getElement().getNode(); // prevent row click
|
||||
e.getSource().getElement().getNode();
|
||||
getUI().ifPresent(ui -> ui.navigate("create_invoice/" + job.getId().toHexString()));
|
||||
});
|
||||
}
|
||||
return invoiceBtn;
|
||||
}
|
||||
return new com.vaadin.flow.component.html.Span();
|
||||
|
||||
@@ -613,6 +613,7 @@ jobs.historie.manuell=Manuell
|
||||
jobs.button.csvexport=CSV Export
|
||||
jobs.tooltip.complete=Auftrag abschließen
|
||||
jobs.tooltip.createinvoice=Rechnung erstellen
|
||||
jobs.tooltip.showinvoice=Rechnung anzeigen
|
||||
jobs.tooltip.delete=Auftrag löschen
|
||||
jobs.dialog.complete.title=Auftrag abschließen
|
||||
jobs.dialog.complete.text=Möchten Sie den Auftrag {0} manuell abschließen?
|
||||
@@ -648,6 +649,13 @@ createinvoice.notification.noservices=Bitte wählen Sie mindestens eine Leistung
|
||||
createinvoice.notification.nouser=Benutzer nicht gefunden
|
||||
createinvoice.notification.notemplate=Kein Rechnungstemplate gefunden
|
||||
createinvoice.notification.error=Fehler beim Erstellen der Rechnung: {0}
|
||||
createinvoice.notification.saved=Rechnung {0} wurde gespeichert
|
||||
createinvoice.preview.title=Rechnungsvorschau
|
||||
createinvoice.preview.number=VORSCHAU
|
||||
createinvoice.button.save=Speichern
|
||||
createinvoice.confirm.save.title=Rechnung speichern
|
||||
createinvoice.confirm.save.message=Diese Rechnung wird dauerhaft gespeichert und kann anschlie\u00dfend nicht mehr ver\u00e4ndert werden. Fortfahren?
|
||||
createinvoice.confirm.save.confirm=Ja, speichern
|
||||
|
||||
# Invoices
|
||||
invoices.title=Rechnungen
|
||||
|
||||
@@ -613,6 +613,7 @@ jobs.historie.manuell=Manual
|
||||
jobs.button.csvexport=CSV Export
|
||||
jobs.tooltip.complete=Complete Job
|
||||
jobs.tooltip.createinvoice=Create Invoice
|
||||
jobs.tooltip.showinvoice=Show Invoice
|
||||
jobs.tooltip.delete=Delete Job
|
||||
jobs.dialog.complete.title=Complete Job
|
||||
jobs.dialog.complete.text=Do you want to manually complete job {0}?
|
||||
@@ -648,6 +649,13 @@ createinvoice.notification.noservices=Please select at least one service
|
||||
createinvoice.notification.nouser=User not found
|
||||
createinvoice.notification.notemplate=No invoice template found
|
||||
createinvoice.notification.error=Error creating invoice: {0}
|
||||
createinvoice.notification.saved=Invoice {0} has been saved
|
||||
createinvoice.preview.title=Invoice Preview
|
||||
createinvoice.preview.number=PREVIEW
|
||||
createinvoice.button.save=Save
|
||||
createinvoice.confirm.save.title=Save Invoice
|
||||
createinvoice.confirm.save.message=This invoice will be permanently saved and can no longer be modified. Continue?
|
||||
createinvoice.confirm.save.confirm=Yes, save
|
||||
|
||||
# Invoices
|
||||
invoices.title=Invoices
|
||||
|
||||
Reference in New Issue
Block a user