Erweiterungen
This commit is contained in:
@@ -154,6 +154,10 @@ public class Job {
|
||||
@Field("route_duration_seconds")
|
||||
private Integer routeDurationSeconds;
|
||||
|
||||
// Referenz auf die erstellte Rechnung
|
||||
@Field("invoice_id")
|
||||
private String invoiceId;
|
||||
|
||||
/**
|
||||
* Returns the ObjectId as string for JSON serialization. This ensures that the
|
||||
* job id is returned as a string when jobs are retrieved via API.
|
||||
|
||||
@@ -28,6 +28,8 @@ public class UserInvoiceData {
|
||||
private String introText;
|
||||
private String paymentTerms;
|
||||
|
||||
private long nextInvoiceNumber = 0;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
|
||||
@@ -59,6 +59,10 @@ public class CustomerInvoice {
|
||||
private String legalNotes; // Rechtliche Hinweise
|
||||
private String reverseChargeNote; // Hinweis auf Reverse Charge (falls zutreffend)
|
||||
|
||||
// Verknüpfung mit Auftrag und Benutzer
|
||||
private String jobId; // Referenz auf den Auftrag
|
||||
private String userId; // Referenz auf den Benutzer (Rechnungsersteller)
|
||||
|
||||
// Constructors
|
||||
public CustomerInvoice() {
|
||||
}
|
||||
@@ -341,4 +345,20 @@ public class CustomerInvoice {
|
||||
public void setReverseChargeNote(String reverseChargeNote) {
|
||||
this.reverseChargeNote = reverseChargeNote;
|
||||
}
|
||||
|
||||
public String getJobId() {
|
||||
return jobId;
|
||||
}
|
||||
|
||||
public void setJobId(String jobId) {
|
||||
this.jobId = jobId;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@ package de.assecutor.votianlt.pages.service;
|
||||
import de.assecutor.votianlt.model.UserInvoiceData;
|
||||
import de.assecutor.votianlt.repository.UserInvoiceDataRepository;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mongodb.core.FindAndModifyOptions;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -11,9 +16,11 @@ import java.util.Optional;
|
||||
public class UserInvoiceDataService {
|
||||
|
||||
private final UserInvoiceDataRepository userInvoiceDataRepository;
|
||||
private final MongoTemplate mongoTemplate;
|
||||
|
||||
public UserInvoiceDataService(UserInvoiceDataRepository userInvoiceDataRepository) {
|
||||
public UserInvoiceDataService(UserInvoiceDataRepository userInvoiceDataRepository, MongoTemplate mongoTemplate) {
|
||||
this.userInvoiceDataRepository = userInvoiceDataRepository;
|
||||
this.mongoTemplate = mongoTemplate;
|
||||
}
|
||||
|
||||
public Optional<UserInvoiceData> findByUserId(ObjectId userId) {
|
||||
@@ -53,4 +60,26 @@ public class UserInvoiceDataService {
|
||||
public void deleteByUserId(ObjectId userId) {
|
||||
userInvoiceDataRepository.deleteByUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert atomar die nächste Rechnungsnummer für den Benutzer und erhöht den
|
||||
* Zähler um 1. Gibt die vollständige Rechnungsnummer zurück (Präfix + Nummer).
|
||||
*/
|
||||
public String generateNextInvoiceNumber(ObjectId userId) {
|
||||
Query query = Query.query(Criteria.where("userId").is(userId));
|
||||
Update update = new Update().inc("nextInvoiceNumber", 1);
|
||||
FindAndModifyOptions options = FindAndModifyOptions.options().returnNew(false).upsert(false);
|
||||
|
||||
UserInvoiceData before = mongoTemplate.findAndModify(query, update, options, UserInvoiceData.class);
|
||||
if (before == null) {
|
||||
// Kein Eintrag vorhanden - Fallback auf aktuelle Daten
|
||||
return findByUserId(userId).map(d -> {
|
||||
String prefix = d.getPrefix() != null ? d.getPrefix() : "";
|
||||
return prefix + String.format("%06d", d.getNextInvoiceNumber());
|
||||
}).orElse("000000");
|
||||
}
|
||||
|
||||
String prefix = before.getPrefix() != null ? before.getPrefix() : "";
|
||||
return prefix + String.format("%06d", before.getNextInvoiceNumber());
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,15 @@ import com.vaadin.flow.router.HasDynamicTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.router.BeforeEvent;
|
||||
import com.vaadin.flow.router.HasUrlParameter;
|
||||
import de.assecutor.votianlt.model.Customer;
|
||||
import de.assecutor.votianlt.model.Job;
|
||||
import de.assecutor.votianlt.model.Service;
|
||||
import de.assecutor.votianlt.model.User;
|
||||
import de.assecutor.votianlt.model.InvoiceTemplate;
|
||||
import de.assecutor.votianlt.model.invoices.CustomerInvoice;
|
||||
import de.assecutor.votianlt.pages.service.CustomerService;
|
||||
import de.assecutor.votianlt.pages.service.UserInvoiceDataService;
|
||||
import de.assecutor.votianlt.repository.CustomerInvoiceRepository;
|
||||
import de.assecutor.votianlt.repository.JobRepository;
|
||||
import de.assecutor.votianlt.repository.ServiceRepository;
|
||||
import de.assecutor.votianlt.repository.UserRepository;
|
||||
@@ -55,6 +60,9 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
private final CustomerInvoiceService customerInvoiceService;
|
||||
private final InvoiceTemplateService invoiceTemplateService;
|
||||
private final SecurityService securityService;
|
||||
private final UserInvoiceDataService userInvoiceDataService;
|
||||
private final CustomerInvoiceRepository customerInvoiceRepository;
|
||||
private final CustomerService customerService;
|
||||
private User currentUser;
|
||||
private Job currentJob;
|
||||
private List<ServiceRow> gridRows = new ArrayList<>();
|
||||
@@ -91,13 +99,18 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
@Autowired
|
||||
public CreateInvoiceView(JobRepository jobRepository, ServiceRepository serviceRepository,
|
||||
UserRepository userRepository, CustomerInvoiceService customerInvoiceService,
|
||||
InvoiceTemplateService invoiceTemplateService, SecurityService securityService) {
|
||||
InvoiceTemplateService invoiceTemplateService, SecurityService securityService,
|
||||
UserInvoiceDataService userInvoiceDataService, CustomerInvoiceRepository customerInvoiceRepository,
|
||||
CustomerService customerService) {
|
||||
this.jobRepository = jobRepository;
|
||||
this.serviceRepository = serviceRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.customerInvoiceService = customerInvoiceService;
|
||||
this.invoiceTemplateService = invoiceTemplateService;
|
||||
this.securityService = securityService;
|
||||
this.userInvoiceDataService = userInvoiceDataService;
|
||||
this.customerInvoiceRepository = customerInvoiceRepository;
|
||||
this.customerService = customerService;
|
||||
setSizeFull();
|
||||
setPadding(true);
|
||||
setSpacing(true);
|
||||
@@ -405,9 +418,6 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the updated job with kilometers and time
|
||||
jobRepository.save(currentJob);
|
||||
|
||||
try {
|
||||
// Get current user
|
||||
Optional<User> currentUserOpt = securityService.getAuthenticatedUser()
|
||||
@@ -431,25 +441,41 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
}
|
||||
|
||||
String templateData = templateOpt.get().getTemplateData();
|
||||
System.out.println("DEBUG CreateInvoiceView: Template data length: "
|
||||
+ (templateData != null ? templateData.length() : 0));
|
||||
System.out.println("DEBUG CreateInvoiceView: Template data preview: "
|
||||
+ (templateData != null ? templateData.substring(0, Math.min(200, templateData.length()))
|
||||
: "null"));
|
||||
|
||||
if (templateData == null || templateData.isBlank()) {
|
||||
Notification.show(getTranslation("createinvoice.notification.notemplate"), 3000,
|
||||
Notification.Position.BOTTOM_END);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate PDF with template and actual job data
|
||||
byte[] pdfBytes = generateInvoicePdfFromTemplate(templateData, currentUser);
|
||||
System.out.println(
|
||||
"DEBUG CreateInvoiceView: PDF bytes generated: " + (pdfBytes != null ? pdfBytes.length : 0));
|
||||
// Rechnungsnummer generieren (atomar, Präfix + Zähler)
|
||||
String invoiceNumber = userInvoiceDataService.generateNextInvoiceNumber(currentUser.getId());
|
||||
|
||||
// Show PDF in dialog
|
||||
showPdfInDialog(pdfBytes, "Rechnung " + currentJob.getJobNumber());
|
||||
// Rechnung in MongoDB speichern
|
||||
BigDecimal netAmount = calculateNetAmount();
|
||||
BigDecimal vatRate = calculateAverageVatRate();
|
||||
BigDecimal vatAmount = netAmount.multiply(vatRate);
|
||||
BigDecimal totalAmount = netAmount.add(vatAmount);
|
||||
|
||||
CustomerInvoice invoice = new CustomerInvoice();
|
||||
invoice.setInvoiceNumber(invoiceNumber);
|
||||
invoice.setInvoiceDate(java.time.LocalDate.now());
|
||||
invoice.setJobId(currentJob.getId().toHexString());
|
||||
invoice.setUserId(currentUser.getId().toHexString());
|
||||
invoice.setNetAmount(netAmount);
|
||||
invoice.setVatRate(vatRate);
|
||||
invoice.setVatAmount(vatAmount);
|
||||
invoice.setTotalAmount(totalAmount);
|
||||
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);
|
||||
|
||||
} catch (Exception ex) {
|
||||
log.error("Fehler beim Erstellen der Rechnung", ex);
|
||||
@@ -461,7 +487,8 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
/**
|
||||
* Generates an invoice PDF from the template with actual job data.
|
||||
*/
|
||||
private byte[] generateInvoicePdfFromTemplate(String templateData, User currentUser) throws Exception {
|
||||
private byte[] generateInvoicePdfFromTemplate(String templateData, User currentUser, String invoiceNumber)
|
||||
throws Exception {
|
||||
// Calculate totals
|
||||
BigDecimal netAmount = calculateNetAmount();
|
||||
BigDecimal vatRate = calculateAverageVatRate();
|
||||
@@ -479,34 +506,29 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
variables.put("masterdata.email", safe(currentUser.getEmail()));
|
||||
variables.put("masterdata.phone", safe(currentUser.getPhone()));
|
||||
|
||||
// Customer data (from job)
|
||||
// Customer data (from job - look up Customer entity for full address)
|
||||
String customerSelection = currentJob.getCustomerSelection();
|
||||
if (customerSelection != null && !customerSelection.isBlank()) {
|
||||
// Format: "Firmenname | Vorname Nachname, Straße Hausnummer, PLZ Ort"
|
||||
// Format: "Firmenname | Vorname Nachname"
|
||||
String[] parts = customerSelection.split("\\|");
|
||||
String companyName = parts.length > 0 ? parts[0].trim() : "";
|
||||
variables.put("customer.company_name", companyName);
|
||||
String contactName = parts.length > 1 ? parts[1].trim() : "";
|
||||
|
||||
// Extract other customer info if available
|
||||
if (parts.length > 1) {
|
||||
String remaining = parts[1].trim();
|
||||
// Try to extract contact name (before first comma)
|
||||
int firstComma = remaining.indexOf(",");
|
||||
if (firstComma > 0) {
|
||||
variables.put("customer.contact_name", remaining.substring(0, firstComma).trim());
|
||||
// Rest is address
|
||||
String addressPart = remaining.substring(firstComma + 1).trim();
|
||||
// Split address into street and city
|
||||
String[] addressParts = addressPart.split(",");
|
||||
if (addressParts.length >= 1) {
|
||||
variables.put("customer.street", addressParts[0].trim());
|
||||
}
|
||||
if (addressParts.length >= 2) {
|
||||
variables.put("customer.city", addressParts[1].trim());
|
||||
}
|
||||
} else {
|
||||
variables.put("customer.contact_name", remaining);
|
||||
}
|
||||
variables.put("customer.company_name", companyName);
|
||||
variables.put("customer.contact_name", contactName);
|
||||
|
||||
// Look up full address from Customer entity
|
||||
Customer matchedCustomer = customerService.findAllForCurrentOwner().stream()
|
||||
.filter(c -> companyName.equalsIgnoreCase(c.getCompanyName())).findFirst().orElse(null);
|
||||
|
||||
if (matchedCustomer != null) {
|
||||
String street = safe(matchedCustomer.getStreet()) + " " + safe(matchedCustomer.getHouseNumber());
|
||||
String city = safe(matchedCustomer.getZip()) + " " + safe(matchedCustomer.getCity());
|
||||
variables.put("customer.street", street.trim());
|
||||
variables.put("customer.city", city.trim());
|
||||
} else {
|
||||
variables.put("customer.street", "");
|
||||
variables.put("customer.city", "");
|
||||
}
|
||||
} else {
|
||||
variables.put("customer.company_name", "");
|
||||
@@ -516,7 +538,9 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
||||
}
|
||||
|
||||
// Invoice data
|
||||
variables.put("invoice.number", currentJob.getJobNumber() + "-" + System.currentTimeMillis());
|
||||
variables.put("invoice.number", invoiceNumber);
|
||||
variables.put("invoice_number", invoiceNumber);
|
||||
variables.put("masterdata.invoice_number", invoiceNumber);
|
||||
variables.put("invoice.date", java.time.LocalDate.now().toString());
|
||||
variables.put("invoice.net_total",
|
||||
netAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.assecutor.votianlt.repository;
|
||||
|
||||
import de.assecutor.votianlt.model.invoices.CustomerInvoice;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface CustomerInvoiceRepository extends MongoRepository<CustomerInvoice, String> {
|
||||
|
||||
Optional<CustomerInvoice> findByJobId(String jobId);
|
||||
|
||||
List<CustomerInvoice> findByUserId(String userId);
|
||||
}
|
||||
@@ -705,6 +705,14 @@ public class CustomerInvoiceService {
|
||||
text = "[" + variable.replace("masterdata.", "").replace("customer.", "").replace("invoice.", "")
|
||||
.replace("job.", "") + "]";
|
||||
System.out.println("DEBUG: No value for variable " + variable + ", using placeholder");
|
||||
} else if ("customer".equals(type)) {
|
||||
// Customer-type element without variable: compose address from customer variables
|
||||
String line1 = variables.getOrDefault("customer.company_name",
|
||||
variables.getOrDefault("customer.contact_name", ""));
|
||||
String line2 = variables.getOrDefault("customer.street", "");
|
||||
String line3 = variables.getOrDefault("customer.city", "");
|
||||
text = line1 + "\n" + line2 + "\n" + line3;
|
||||
System.out.println("DEBUG: Customer element - composed address: " + text);
|
||||
} else {
|
||||
System.out.println("DEBUG: Using static text: " + text);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user