Erweiterungen
This commit is contained in:
@@ -23,12 +23,25 @@ public class User {
|
||||
|
||||
// Firmen-/Adressdaten
|
||||
private String company; // Firma
|
||||
private String companyAddition; // Firmenzusatz
|
||||
private String street; // Straße
|
||||
private String houseNumber; // Hausnr
|
||||
private String addressAddition; // Adresszusatz (optional)
|
||||
private String zip; // Postleitzahl
|
||||
private String city; // Stadt
|
||||
|
||||
// Abweichende Rechnungsadresse
|
||||
private boolean diffInvoiceAddress; // Checkbox für abweichende Rechnungsadresse
|
||||
private String invCompany; // Rechnungsadresse: Firma
|
||||
private String invCompanyAddition; // Rechnungsadresse: Firmenzusatz
|
||||
private String invFirstname; // Rechnungsadresse: Vorname
|
||||
private String invLastname; // Rechnungsadresse: Nachname
|
||||
private String invStreet; // Rechnungsadresse: Straße
|
||||
private String invHouseNumber; // Rechnungsadresse: Hausnr
|
||||
private String invAddressAddition; // Rechnungsadresse: Adresszusatz
|
||||
private String invZip; // Rechnungsadresse: Postleitzahl
|
||||
private String invCity; // Rechnungsadresse: Stadt
|
||||
|
||||
@Indexed(unique = true)
|
||||
private String email;
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package de.assecutor.votianlt.model;
|
||||
|
||||
import lombok.Data;
|
||||
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.index.Indexed;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Document(collection = "user_invoice_data")
|
||||
public class UserInvoiceData {
|
||||
|
||||
@Id
|
||||
private ObjectId id;
|
||||
|
||||
@Indexed
|
||||
private ObjectId userId;
|
||||
|
||||
private boolean billingEnabled;
|
||||
private String prefix;
|
||||
private String ustId;
|
||||
private String taxNumber;
|
||||
private String bankName;
|
||||
private String iban;
|
||||
private String taxRate;
|
||||
private String introText;
|
||||
private String paymentTerms;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public UserInvoiceData() {
|
||||
this.createdAt = LocalDateTime.now();
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public void updateTimestamp() {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,9 @@ import com.vaadin.flow.router.Layout;
|
||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||
import com.vaadin.flow.server.menu.MenuConfiguration;
|
||||
import com.vaadin.flow.server.menu.MenuEntry;
|
||||
import de.assecutor.votianlt.model.User;
|
||||
import de.assecutor.votianlt.model.UserInvoiceData;
|
||||
import de.assecutor.votianlt.pages.service.UserInvoiceDataService;
|
||||
import de.assecutor.votianlt.pages.view.EditProfileView;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
|
||||
@@ -31,12 +34,14 @@ import static com.vaadin.flow.theme.lumo.LumoUtility.*;
|
||||
public final class MainLayout extends AppLayout {
|
||||
|
||||
private final SecurityService securityService;
|
||||
private final UserInvoiceDataService userInvoiceDataService;
|
||||
private Div headerRef;
|
||||
private Scroller navRef;
|
||||
private Component userMenuRef;
|
||||
|
||||
public MainLayout(SecurityService securityService) {
|
||||
public MainLayout(SecurityService securityService, UserInvoiceDataService userInvoiceDataService) {
|
||||
this.securityService = securityService;
|
||||
this.userInvoiceDataService = userInvoiceDataService;
|
||||
setPrimarySection(Section.DRAWER);
|
||||
|
||||
// Always build the drawer; keep references and toggle visibility on attach and
|
||||
@@ -102,10 +107,17 @@ public final class MainLayout extends AppLayout {
|
||||
SideNavItem customers = new SideNavItem("Kunden", "customers", new Icon(VaadinIcon.USERS));
|
||||
SideNavItem appUsers = new SideNavItem("App-Nutzer", "app-user", new Icon(VaadinIcon.USERS));
|
||||
SideNavItem devices = new SideNavItem("Endgeräte", "app-devices", new Icon(VaadinIcon.MOBILE));
|
||||
SideNavItem invoices = new SideNavItem("Rechnungen", "invoices", new Icon(VaadinIcon.FILE_TEXT));
|
||||
SideNavItem statistics = new SideNavItem("Statistiken", "statistics", new Icon(VaadinIcon.BAR_CHART));
|
||||
|
||||
verwaltungContent.add(jobs, customers, appUsers, devices, invoices, statistics);
|
||||
verwaltungContent.add(jobs, customers, appUsers, devices);
|
||||
|
||||
// Only show invoices menu if billing is enabled for the current user
|
||||
if (isBillingEnabledForCurrentUser()) {
|
||||
SideNavItem invoices = new SideNavItem("Rechnungen", "invoices", new Icon(VaadinIcon.FILE_TEXT));
|
||||
verwaltungContent.add(invoices);
|
||||
}
|
||||
|
||||
verwaltungContent.add(statistics);
|
||||
verwaltungDetails.add(verwaltungContent);
|
||||
|
||||
// Create Details component for "Verwaltung" with collapsible list
|
||||
@@ -179,4 +191,18 @@ public final class MainLayout extends AppLayout {
|
||||
return userMenu;
|
||||
}
|
||||
|
||||
private boolean isBillingEnabledForCurrentUser() {
|
||||
try {
|
||||
User currentUser = securityService.getCurrentDatabaseUser();
|
||||
if (currentUser != null && currentUser.getId() != null) {
|
||||
UserInvoiceData invoiceData = userInvoiceDataService.findByUserId(currentUser.getId()).orElse(null);
|
||||
return invoiceData != null && invoiceData.isBillingEnabled();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Log error or handle appropriately
|
||||
// Return false as safe default if we can't determine billing status
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
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.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class UserInvoiceDataService {
|
||||
|
||||
private final UserInvoiceDataRepository userInvoiceDataRepository;
|
||||
|
||||
public UserInvoiceDataService(UserInvoiceDataRepository userInvoiceDataRepository) {
|
||||
this.userInvoiceDataRepository = userInvoiceDataRepository;
|
||||
}
|
||||
|
||||
public Optional<UserInvoiceData> findByUserId(ObjectId userId) {
|
||||
return userInvoiceDataRepository.findByUserId(userId);
|
||||
}
|
||||
|
||||
public UserInvoiceData save(UserInvoiceData userInvoiceData) {
|
||||
userInvoiceData.updateTimestamp();
|
||||
return userInvoiceDataRepository.save(userInvoiceData);
|
||||
}
|
||||
|
||||
public UserInvoiceData createOrUpdate(ObjectId userId, boolean billingEnabled, String prefix, String ustId,
|
||||
String taxNumber, String bankName, String iban, String taxRate,
|
||||
String introText, String paymentTerms) {
|
||||
// If billing is disabled, delete any existing record and return null
|
||||
if (!billingEnabled) {
|
||||
deleteByUserId(userId);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise, create or update the record
|
||||
UserInvoiceData invoiceData = findByUserId(userId).orElse(new UserInvoiceData());
|
||||
|
||||
invoiceData.setUserId(userId);
|
||||
invoiceData.setBillingEnabled(billingEnabled);
|
||||
invoiceData.setPrefix(prefix);
|
||||
invoiceData.setUstId(ustId);
|
||||
invoiceData.setTaxNumber(taxNumber);
|
||||
invoiceData.setBankName(bankName);
|
||||
invoiceData.setIban(iban);
|
||||
invoiceData.setTaxRate(taxRate);
|
||||
invoiceData.setIntroText(introText);
|
||||
invoiceData.setPaymentTerms(paymentTerms);
|
||||
|
||||
return save(invoiceData);
|
||||
}
|
||||
|
||||
public void deleteByUserId(ObjectId userId) {
|
||||
userInvoiceDataRepository.deleteByUserId(userId);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import com.vaadin.flow.component.icon.VaadinIcon;
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.textfield.EmailField;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -26,7 +27,9 @@ import com.vaadin.flow.data.value.ValueChangeMode;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.model.User;
|
||||
import de.assecutor.votianlt.model.UserInvoiceData;
|
||||
import de.assecutor.votianlt.pages.service.UserService;
|
||||
import de.assecutor.votianlt.pages.service.UserInvoiceDataService;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
|
||||
@@ -46,8 +49,12 @@ public class EditProfileView extends HorizontalLayout {
|
||||
|
||||
private final Binder<User> binder = new Binder<>(User.class);
|
||||
private final User currentUser;
|
||||
private final UserInvoiceDataService userInvoiceDataService;
|
||||
private UserInvoiceData currentInvoiceData;
|
||||
private Checkbox billingEnabled;
|
||||
|
||||
public EditProfileView(UserService userService, SecurityService securityService) {
|
||||
public EditProfileView(UserService userService, UserInvoiceDataService userInvoiceDataService, SecurityService securityService) {
|
||||
this.userInvoiceDataService = userInvoiceDataService;
|
||||
this.currentUser = securityService.getCurrentDatabaseUser();
|
||||
setSizeFull();
|
||||
setPadding(true);
|
||||
@@ -155,32 +162,43 @@ public class EditProfileView extends HorizontalLayout {
|
||||
form.add(mobileField, 2);
|
||||
form.add(emailField, 2);
|
||||
// Binder: Pflichtfelder und Bindings
|
||||
// UI-Pflichtfelder ohne Datenbindung (da nicht im User-Modell vorhanden)
|
||||
// Pflichtfelder markieren
|
||||
companyField.setRequiredIndicatorVisible(true);
|
||||
streetField.setRequiredIndicatorVisible(true);
|
||||
houseNumberField.setRequiredIndicatorVisible(true);
|
||||
zipField.setRequiredIndicatorVisible(true);
|
||||
cityField.setRequiredIndicatorVisible(true);
|
||||
|
||||
binder.forField(companyField).asRequired("").bind(user -> null, (user, v) -> {
|
||||
});
|
||||
binder.forField(streetField).asRequired("").bind(user -> null, (user, v) -> {
|
||||
});
|
||||
binder.forField(houseNumberField).asRequired("").bind(user -> null, (user, v) -> {
|
||||
});
|
||||
binder.forField(zipField).asRequired("").bind(user -> null, (user, v) -> {
|
||||
});
|
||||
binder.forField(cityField).asRequired("").bind(user -> null, (user, v) -> {
|
||||
});
|
||||
// Hauptadresse binden
|
||||
binder.forField(companyField).asRequired("Firma ist erforderlich").bind(User::getCompany, User::setCompany);
|
||||
binder.forField(companyAddField).bind(User::getCompanyAddition, User::setCompanyAddition);
|
||||
binder.forField(streetField).asRequired("Straße ist erforderlich").bind(User::getStreet, User::setStreet);
|
||||
binder.forField(houseNumberField).asRequired("Hausnummer ist erforderlich").bind(User::getHouseNumber, User::setHouseNumber);
|
||||
binder.forField(addressAddField).bind(User::getAddressAddition, User::setAddressAddition);
|
||||
binder.forField(zipField).asRequired("Postleitzahl ist erforderlich").bind(User::getZip, User::setZip);
|
||||
binder.forField(cityField).asRequired("Stadt ist erforderlich").bind(User::getCity, User::setCity);
|
||||
|
||||
binder.forField(firstnameField).asRequired("").bind(User::getFirstname, User::setFirstname);
|
||||
binder.forField(lastnameField).asRequired("").bind(User::getName, User::setName);
|
||||
binder.forField(phoneField).asRequired("").bind(User::getPhone, User::setPhone);
|
||||
binder.forField(emailField).asRequired("").withValidator(new EmailValidator("Ungültige E-Mail-Adresse"))
|
||||
// Personendaten binden
|
||||
binder.forField(firstnameField).asRequired("Vorname ist erforderlich").bind(User::getFirstname, User::setFirstname);
|
||||
binder.forField(lastnameField).asRequired("Nachname ist erforderlich").bind(User::getName, User::setName);
|
||||
binder.forField(phoneField).asRequired("Telefonnummer ist erforderlich").bind(User::getPhone, User::setPhone);
|
||||
binder.forField(emailField).asRequired("E-Mail ist erforderlich").withValidator(new EmailValidator("Ungültige E-Mail-Adresse"))
|
||||
.bind(User::getEmail, User::setEmail);
|
||||
// Optionale Felder
|
||||
binder.forField(mobileField).bind(User::getPhone2, User::setPhone2);
|
||||
binder.forField(faxField).bind(User::getFax, User::setFax);
|
||||
|
||||
// Abweichende Rechnungsadresse binden
|
||||
binder.forField(diffInvoiceAddress).bind(User::isDiffInvoiceAddress, User::setDiffInvoiceAddress);
|
||||
binder.forField(invCompanyField).bind(User::getInvCompany, User::setInvCompany);
|
||||
binder.forField(invCompanyAddField).bind(User::getInvCompanyAddition, User::setInvCompanyAddition);
|
||||
binder.forField(invFirstnameField).bind(User::getInvFirstname, User::setInvFirstname);
|
||||
binder.forField(invLastnameField).bind(User::getInvLastname, User::setInvLastname);
|
||||
binder.forField(invStreetField).bind(User::getInvStreet, User::setInvStreet);
|
||||
binder.forField(invHouseNumberField).bind(User::getInvHouseNumber, User::setInvHouseNumber);
|
||||
binder.forField(invAddressAddField).bind(User::getInvAddressAddition, User::setInvAddressAddition);
|
||||
binder.forField(invZipField).bind(User::getInvZip, User::setInvZip);
|
||||
binder.forField(invCityField).bind(User::getInvCity, User::setInvCity);
|
||||
// Pflichtindikator sichtbar machen
|
||||
firstnameField.setRequiredIndicatorVisible(true);
|
||||
lastnameField.setRequiredIndicatorVisible(true);
|
||||
@@ -232,7 +250,7 @@ public class EditProfileView extends HorizontalLayout {
|
||||
partsTitle.getStyle().set("margin", "0 0 var(--lumo-space-s) 0");
|
||||
|
||||
// Felder für Rechnungsstellung (für Live-Update)
|
||||
Checkbox billingEnabled = new Checkbox("Rechnungslegung über votianLT");
|
||||
billingEnabled = new Checkbox("Rechnungslegung über votianLT");
|
||||
billingEnabled.setValue(false);
|
||||
billingEnabled.addValueChangeListener(e -> toggleBilling(e.getValue()));
|
||||
|
||||
@@ -302,6 +320,9 @@ public class EditProfileView extends HorizontalLayout {
|
||||
billingTab.add(billingLeft, billingRight);
|
||||
tabSheet.add("Rechnungsstellung", billingTab);
|
||||
|
||||
// Bestehende Rechnungsdaten laden (nach Erstellung aller Felder)
|
||||
loadInvoiceData();
|
||||
|
||||
// Zweiter Tab: Einstellungen (Beispiel mit Schaltern)
|
||||
VerticalLayout switches = new VerticalLayout();
|
||||
switches.setPadding(false);
|
||||
@@ -342,9 +363,19 @@ public class EditProfileView extends HorizontalLayout {
|
||||
saveProfile.addClickListener(e -> {
|
||||
if (binder.validate().isOk()) {
|
||||
try {
|
||||
// Check if billing status changed
|
||||
boolean oldBillingStatus = currentInvoiceData != null && currentInvoiceData.isBillingEnabled();
|
||||
boolean newBillingStatus = billingEnabled.getValue();
|
||||
|
||||
binder.writeBean(currentUser);
|
||||
userService.save(currentUser);
|
||||
saveInvoiceData();
|
||||
Notification.show("Profil gespeichert", 3000, Notification.Position.BOTTOM_END);
|
||||
|
||||
// Always reload if billing status changed to update the sidebar
|
||||
if (oldBillingStatus != newBillingStatus) {
|
||||
UI.getCurrent().getPage().reload();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Notification.show("Fehler beim Speichern: " + ex.getMessage(), 4000, Notification.Position.MIDDLE);
|
||||
}
|
||||
@@ -445,4 +476,45 @@ public class EditProfileView extends HorizontalLayout {
|
||||
return f != null && f.getValue() != null ? f.getValue() : "";
|
||||
}
|
||||
|
||||
private void loadInvoiceData() {
|
||||
currentInvoiceData = userInvoiceDataService.findByUserId(currentUser.getId()).orElse(null);
|
||||
|
||||
if (currentInvoiceData != null) {
|
||||
billingEnabled.setValue(currentInvoiceData.isBillingEnabled());
|
||||
prefixField.setValue(safe(currentInvoiceData.getPrefix()));
|
||||
ustIdField.setValue(safe(currentInvoiceData.getUstId()));
|
||||
taxNumberField.setValue(safe(currentInvoiceData.getTaxNumber()));
|
||||
bankNameField.setValue(safe(currentInvoiceData.getBankName()));
|
||||
ibanField.setValue(safe(currentInvoiceData.getIban()));
|
||||
taxRateField.setValue(safe(currentInvoiceData.getTaxRate()));
|
||||
introTextArea.setValue(safe(currentInvoiceData.getIntroText()));
|
||||
termsTextArea.setValue(safe(currentInvoiceData.getPaymentTerms()));
|
||||
|
||||
// Update field enabled state and PDF preview based on loaded state
|
||||
setBillingFieldsEnabled(currentInvoiceData.isBillingEnabled());
|
||||
if (currentInvoiceData.isBillingEnabled()) {
|
||||
refreshPdf();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveInvoiceData() {
|
||||
currentInvoiceData = userInvoiceDataService.createOrUpdate(
|
||||
currentUser.getId(),
|
||||
billingEnabled.getValue(),
|
||||
prefixField.getValue(),
|
||||
ustIdField.getValue(),
|
||||
taxNumberField.getValue(),
|
||||
bankNameField.getValue(),
|
||||
ibanField.getValue(),
|
||||
taxRateField.getValue(),
|
||||
introTextArea.getValue(),
|
||||
termsTextArea.getValue()
|
||||
);
|
||||
}
|
||||
|
||||
private String safe(String value) {
|
||||
return value != null ? value : "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.assecutor.votianlt.repository;
|
||||
|
||||
import de.assecutor.votianlt.model.UserInvoiceData;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface UserInvoiceDataRepository extends MongoRepository<UserInvoiceData, ObjectId> {
|
||||
|
||||
Optional<UserInvoiceData> findByUserId(ObjectId userId);
|
||||
|
||||
void deleteByUserId(ObjectId userId);
|
||||
}
|
||||
Reference in New Issue
Block a user