From d6132fabe1bc78f66b96066433d04d841862ca63 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Mon, 30 Mar 2026 10:40:42 +0200 Subject: [PATCH] Version 0.9.14: E-Mail-Feld in Stationsdialogen und Kundenvalidierung MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - E-Mail-Feld in Abhol- und Zustellstationsdialogen hinzugefügt - E-Mail-Pflichtfeld bei "Adresse speichern" mit Validierung - Kundenvalidierung im Backend (E-Mail Pflicht und Formatprüfung) - "Adresse speichern" wird bei Auswahl existierender Kunden deaktiviert - Verbessertes Kunden-Matching über alle Felder inkl. E-Mail - Übersetzung "Template" → "Vorlage" in messages_de.properties --- backend/pom.xml | 2 +- .../ui/component/DeliveryStationDialog.java | 226 ++++++++++++++---- .../ui/component/PickupStationDialog.java | 172 +++++++++++-- .../pages/service/AddCustomerService.java | 18 ++ .../votianlt/pages/view/AddJobView.java | 12 + .../src/main/resources/messages_de.properties | 18 +- 6 files changed, 377 insertions(+), 71 deletions(-) diff --git a/backend/pom.xml b/backend/pom.xml index 5293388..e6f1cef 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -11,7 +11,7 @@ jar - 0.9.13 + 0.9.14 21 21 21 diff --git a/backend/src/main/java/de/assecutor/votianlt/pages/base/ui/component/DeliveryStationDialog.java b/backend/src/main/java/de/assecutor/votianlt/pages/base/ui/component/DeliveryStationDialog.java index 3c2a1e8..8e5d8a6 100644 --- a/backend/src/main/java/de/assecutor/votianlt/pages/base/ui/component/DeliveryStationDialog.java +++ b/backend/src/main/java/de/assecutor/votianlt/pages/base/ui/component/DeliveryStationDialog.java @@ -46,6 +46,7 @@ public class DeliveryStationDialog extends Dialog { private String firstName; private String lastName; private String phone; + private String mail; private String street; private String houseNumber; private String addressAddition; @@ -112,6 +113,14 @@ public class DeliveryStationDialog extends Dialog { this.phone = phone; } + public String getMail() { + return mail; + } + + public void setMail(String mail) { + this.mail = mail; + } + public String getStreet() { return street; } @@ -185,6 +194,7 @@ public class DeliveryStationDialog extends Dialog { private final TextField firstName; private final TextField lastName; private final TextField phone; + private final TextField mail; private final TextField street; private final TextField houseNumber; private final TextField addressAddition; @@ -258,6 +268,12 @@ public class DeliveryStationDialog extends Dialog { phone.setWidthFull(); formLayout.add(phone); + // E-Mail + mail = new TextField(translationHelper.getTranslation("customers.column.email")); + mail.setPlaceholder(translationHelper.getTranslation("customers.column.email")); + mail.setWidthFull(); + formLayout.add(mail); + // Street + house number street = new TextField(translationHelper.getTranslation("profile.street")); street.setPlaceholder(translationHelper.getTranslation("profile.street")); @@ -307,12 +323,41 @@ public class DeliveryStationDialog extends Dialog { // Clear error styling on value change for required fields and update tab // indicators - firstName.addValueChangeListener(ev -> validateRequiredFields()); - lastName.addValueChangeListener(ev -> validateRequiredFields()); - street.addValueChangeListener(ev -> validateRequiredFields()); - houseNumber.addValueChangeListener(ev -> validateRequiredFields()); - zip.addValueChangeListener(ev -> validateRequiredFields()); - city.addValueChangeListener(ev -> validateRequiredFields()); + firstName.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + lastName.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + street.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + houseNumber.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + zip.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + city.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + salutation.addValueChangeListener(ev -> updateSaveAddressState()); + phone.addValueChangeListener(ev -> updateSaveAddressState()); + addressAddition.addValueChangeListener(ev -> updateSaveAddressState()); + mail.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + saveAddress.addValueChangeListener(ev -> { + updateMailRequirement(); + validateRequiredFields(); + }); // TabSheet with address and tasks tabs TabSheet tabSheet = new TabSheet(); @@ -453,6 +498,10 @@ public class DeliveryStationDialog extends Dialog { lastName.setValue(data.getLastName()); if (data.getPhone() != null) phone.setValue(data.getPhone()); + if (data.getMail() != null) + mail.setValue(data.getMail()); + else + mail.clear(); if (data.getStreet() != null) street.setValue(data.getStreet()); if (data.getHouseNumber() != null) @@ -464,7 +513,7 @@ public class DeliveryStationDialog extends Dialog { if (data.getCity() != null) city.setValue(data.getCity()); saveAddress.setValue(customerSelectedFromOptions ? false : data.isSaveAddress()); - updateSaveAddressState(customerSelectedFromOptions); + updateSaveAddressState(); // Load tasks into dialog state if (data.getTasks() != null && !data.getTasks().isEmpty()) { @@ -482,6 +531,7 @@ public class DeliveryStationDialog extends Dialog { } } } + } private DeliveryData collectData() { @@ -491,6 +541,7 @@ public class DeliveryStationDialog extends Dialog { data.setFirstName(firstName.getValue()); data.setLastName(lastName.getValue()); data.setPhone(phone.getValue()); + data.setMail(mail.getValue()); data.setStreet(street.getValue()); data.setHouseNumber(houseNumber.getValue()); data.setAddressAddition(addressAddition.getValue()); @@ -510,6 +561,7 @@ public class DeliveryStationDialog extends Dialog { addressValid &= validateTextField(houseNumber); addressValid &= validateTextField(zip); addressValid &= validateTextField(city); + addressValid &= validateMailField(); addressTabError.setVisible(!addressValid); // Tasks tab validation @@ -545,6 +597,17 @@ public class DeliveryStationDialog extends Dialog { return !empty; } + private boolean validateMailField() { + String value = mail.getValue(); + String normalizedValue = value == null ? "" : value.trim(); + boolean empty = normalizedValue.isEmpty(); + boolean required = Boolean.TRUE.equals(saveAddress.getValue()); + boolean invalid = !empty && !normalizedValue.contains("@"); + boolean hasError = invalid || (required && empty); + applyErrorStyling(mail, hasError); + return !hasError; + } + private void applyErrorStyling(com.vaadin.flow.component.Component field, boolean error) { if (error) { field.getElement().getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)"); @@ -584,8 +647,8 @@ public class DeliveryStationDialog extends Dialog { companyField.addValueChangeListener(event -> { Customer customer = companyAddressOptions.get(event.getValue()); - updateSaveAddressState(customer != null); if (customer == null) { + updateSaveAddressState(); return; } @@ -600,6 +663,8 @@ public class DeliveryStationDialog extends Dialog { lastName.setValue(customer.getLastName()); if (customer.getTelephone() != null) phone.setValue(customer.getTelephone()); + if (customer.getMail() != null) + mail.setValue(customer.getMail()); if (customer.getStreet() != null) street.setValue(customer.getStreet()); if (customer.getHouseNumber() != null) @@ -610,22 +675,32 @@ public class DeliveryStationDialog extends Dialog { zip.setValue(customer.getZip()); if (customer.getCity() != null) city.setValue(customer.getCity()); + updateSaveAddressState(); }); companyField.addCustomValueSetListener(event -> { companyField.setValue(event.getDetail()); - updateSaveAddressState(false); + updateSaveAddressState(); }); } - private void updateSaveAddressState(boolean customerSelectedFromOptions) { + private void updateSaveAddressState() { + Customer selectedCustomer = companyAddressOptions.get(company.getValue()); + boolean customerSelectedFromOptions = selectedCustomer != null && matchesCurrentCustomer(selectedCustomer); + if (customerSelectedFromOptions) { saveAddress.setValue(false); saveAddress.setEnabled(false); + updateMailRequirement(); return; } saveAddress.setEnabled(true); + updateMailRequirement(); + } + + private void updateMailRequirement() { + mail.setRequiredIndicatorVisible(Boolean.TRUE.equals(saveAddress.getValue())); } private String buildCompanyAddressLabel(Customer customer) { @@ -687,12 +762,32 @@ public class DeliveryStationDialog extends Dialog { private boolean matchesCustomer(Customer customer, DeliveryData data) { return equalsNormalized(customer.getCompanyName(), data.getCompany()) + && equalsNormalized(customer.getTitle(), data.getSalutation()) + && equalsNormalized(customer.getFirstname(), data.getFirstName()) + && equalsNormalized(customer.getLastName(), data.getLastName()) + && equalsNormalized(customer.getTelephone(), data.getPhone()) + && equalsNormalized(customer.getMail(), data.getMail()) && equalsNormalized(customer.getStreet(), data.getStreet()) + && equalsNormalized(customer.getAddressAddition(), data.getAddressAddition()) && equalsNormalized(customer.getHouseNumber(), data.getHouseNumber()) && equalsNormalized(customer.getZip(), data.getZip()) && equalsNormalized(customer.getCity(), data.getCity()); } + private boolean matchesCurrentCustomer(Customer customer) { + return equalsNormalized(customer.getCompanyName(), resolveCompanyValue(company.getValue())) + && equalsNormalized(customer.getTitle(), salutation.getValue()) + && equalsNormalized(customer.getFirstname(), firstName.getValue()) + && equalsNormalized(customer.getLastName(), lastName.getValue()) + && equalsNormalized(customer.getTelephone(), phone.getValue()) + && equalsNormalized(customer.getMail(), mail.getValue()) + && equalsNormalized(customer.getStreet(), street.getValue()) + && equalsNormalized(customer.getAddressAddition(), addressAddition.getValue()) + && equalsNormalized(customer.getHouseNumber(), houseNumber.getValue()) + && equalsNormalized(customer.getZip(), zip.getValue()) + && equalsNormalized(customer.getCity(), city.getValue()); + } + private boolean equalsNormalized(String left, String right) { String normalizedLeft = left != null ? left.trim() : ""; String normalizedRight = right != null ? right.trim() : ""; @@ -756,8 +851,9 @@ public class DeliveryStationDialog extends Dialog { tasksList.setPadding(false); tasksList.setSpacing(true); - // Add 1 example row + // Add 1 example row, then append a signature task once on initial setup createTaskRow(); + ensureTrailingSignatureTask(); Button addTaskBtn = new Button(translationHelper.getTranslation("addjob.tasks.add"), new Icon(VaadinIcon.PLUS)); addTaskBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY); @@ -791,14 +887,7 @@ public class DeliveryStationDialog extends Dialog { Button deleteXButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL)); deleteXButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY); deleteXButton.addClassName("dialog-floating-delete"); - deleteXButton.addClickListener(e -> { - int idx = tasksList.getChildren().toList().indexOf(taskContainer); - if (idx >= 0 && idx < tasksState.size()) { - tasksState.remove(idx); - reorderTasksAfterDeletion(); - } - tasksList.remove(taskContainer); - }); + deleteXButton.addClickListener(e -> removeTaskRow(taskContainer)); taskContainer.add(taskTypeCombo, configContainer); taskContainer.add(deleteXButton); @@ -810,6 +899,9 @@ public class DeliveryStationDialog extends Dialog { final BaseTask[] currentTask = { task }; + taskTypeCombo.setValue(TaskType.CONFIRMATION); + updateTaskConfiguration(configContainer, currentTask[0]); + taskTypeCombo.addValueChangeListener(ev -> { TaskType selectedType = ev.getValue(); if (selectedType != null) { @@ -856,11 +948,8 @@ public class DeliveryStationDialog extends Dialog { } }); - // Set initial configuration - taskTypeCombo.setValue(TaskType.CONFIRMATION); - updateTaskConfiguration(configContainer, currentTask[0]); - tasksList.add(taskContainer); + updateTaskDeleteAvailability(); } private void createTaskRowFromTask(BaseTask task) { @@ -882,14 +971,7 @@ public class DeliveryStationDialog extends Dialog { Button deleteXButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL)); deleteXButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY); deleteXButton.addClassName("dialog-floating-delete"); - deleteXButton.addClickListener(e -> { - int idx = tasksList.getChildren().toList().indexOf(taskContainer); - if (idx >= 0 && idx < tasksState.size()) { - tasksState.remove(idx); - reorderTasksAfterDeletion(); - } - tasksList.remove(taskContainer); - }); + deleteXButton.addClickListener(e -> removeTaskRow(taskContainer)); taskContainer.add(taskTypeCombo, configContainer); taskContainer.add(deleteXButton); @@ -951,6 +1033,7 @@ public class DeliveryStationDialog extends Dialog { updateTaskConfiguration(configContainer, task); tasksList.add(taskContainer); + updateTaskDeleteAvailability(); } private BaseTask createTaskByType(TaskType taskType) { @@ -973,6 +1056,56 @@ public class DeliveryStationDialog extends Dialog { } } + private void removeTaskRow(VerticalLayout taskContainer) { + if (tasksList == null) { + return; + } + + List taskRows = tasksList.getChildren().toList(); + int idx = taskRows.indexOf(taskContainer); + if (idx < 0) { + return; + } + + if (idx < tasksState.size()) { + tasksState.remove(idx); + } + tasksList.remove(taskContainer); + reorderTasksAfterDeletion(); + updateTaskDeleteAvailability(); + } + + private void ensureTrailingSignatureTask() { + BaseTask lastTask = tasksState.isEmpty() ? null : tasksState.get(tasksState.size() - 1); + if (lastTask instanceof SignatureTask) { + return; + } + + SignatureTask signatureTask = new SignatureTask(); + signatureTask.setTaskOrder(tasksState.size()); + tasksState.add(signatureTask); + if (tasksList != null) { + createTaskRowFromTask(signatureTask); + } + } + + private void updateTaskDeleteAvailability() { + if (tasksList == null) { + return; + } + + boolean deletable = tasksList.getChildren().count() > 1; + tasksList.getChildren() + .filter(VerticalLayout.class::isInstance) + .map(VerticalLayout.class::cast) + .forEach(taskContainer -> taskContainer.getChildren() + .filter(Button.class::isInstance) + .map(Button.class::cast) + .filter(button -> button.getClassNames().contains("dialog-floating-delete")) + .findFirst() + .ifPresent(button -> button.setEnabled(deletable))); + } + private void updateTaskConfiguration(VerticalLayout configContainer, BaseTask task) { configContainer.removeAll(); @@ -1315,16 +1448,22 @@ public class DeliveryStationDialog extends Dialog { } private void loadTasksFromTemplate(TaskTemplate template, ComboBox templateComboBox) { - ConfirmDialog confirmDialog = new ConfirmDialog(); - confirmDialog.setHeader(translationHelper.getTranslation("addjob.tasks.template.load.title")); - confirmDialog.setText( - translationHelper.getTranslation("addjob.tasks.template.load.text", template.getTemplateName())); - confirmDialog.setCancelable(true); - confirmDialog.setCancelText(translationHelper.getTranslation("button.cancel")); - confirmDialog.setConfirmText(translationHelper.getTranslation("addjob.tasks.template.load.confirm")); - confirmDialog.setConfirmButtonTheme("primary"); + Dialog confirmDialog = DialogStylingHelper + .createStyledDialog(translationHelper.getTranslation("addjob.tasks.template.load.title"), "560px"); + confirmDialog.setCloseOnOutsideClick(false); + confirmDialog.addDialogCloseActionListener(e -> templateComboBox.clear()); - confirmDialog.addConfirmListener(e -> { + VerticalLayout dialogContent = DialogStylingHelper.createContentLayout("420px"); + dialogContent.add(new Span( + translationHelper.getTranslation("addjob.tasks.template.load.text", template.getTemplateName()))); + + Button cancelButton = new Button(translationHelper.getTranslation("button.cancel"), e -> { + templateComboBox.clear(); + confirmDialog.close(); + }); + cancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + + Button confirmButton = new Button(translationHelper.getTranslation("addjob.tasks.template.load.confirm"), e -> { tasksState.clear(); tasksList.removeAll(); @@ -1338,16 +1477,19 @@ public class DeliveryStationDialog extends Dialog { } } + ensureTrailingSignatureTask(); templateComboBox.clear(); validateRequiredFields(); Notification.show( translationHelper.getTranslation("addjob.tasks.template.loaded", template.getTemplateName()), 3000, Notification.Position.BOTTOM_END); + confirmDialog.close(); }); + confirmButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); - confirmDialog.addCancelListener(e -> templateComboBox.clear()); - + confirmDialog.add(DialogStylingHelper.wrapContent(dialogContent)); + confirmDialog.getFooter().add(cancelButton, confirmButton); confirmDialog.open(); } } diff --git a/backend/src/main/java/de/assecutor/votianlt/pages/base/ui/component/PickupStationDialog.java b/backend/src/main/java/de/assecutor/votianlt/pages/base/ui/component/PickupStationDialog.java index af6d098..00897e3 100644 --- a/backend/src/main/java/de/assecutor/votianlt/pages/base/ui/component/PickupStationDialog.java +++ b/backend/src/main/java/de/assecutor/votianlt/pages/base/ui/component/PickupStationDialog.java @@ -54,6 +54,7 @@ public class PickupStationDialog extends Dialog { private String firstName; private String lastName; private String phone; + private String mail; private String street; private String houseNumber; private String addressAddition; @@ -125,6 +126,14 @@ public class PickupStationDialog extends Dialog { this.phone = phone; } + public String getMail() { + return mail; + } + + public void setMail(String mail) { + this.mail = mail; + } + public String getStreet() { return street; } @@ -231,6 +240,7 @@ public class PickupStationDialog extends Dialog { private final TextField firstName; private final TextField lastName; private final TextField phone; + private final TextField mail; private final TextField street; private final TextField houseNumber; private final TextField addressAddition; @@ -239,6 +249,8 @@ public class PickupStationDialog extends Dialog { private final Checkbox saveAddress; private final ComboBox customerComboBox; + private final Map customerLabelMap = new LinkedHashMap<>(); + private final Map companyCustomerMap = new LinkedHashMap<>(); private DatePicker appointmentDatePicker; private TimePicker appointmentTimePicker; private Checkbox digitalProcessingCheckbox; @@ -278,7 +290,7 @@ public class PickupStationDialog extends Dialog { customerComboBox.setRequiredIndicatorVisible(true); customerComboBox.setWidthFull(); - Map customerLabelMap = new LinkedHashMap<>(); + customerLabelMap.clear(); for (Customer c : customers) { String label = (c.getCompanyName() != null && !c.getCompanyName().isBlank()) ? c.getCompanyName() + " | " @@ -330,6 +342,11 @@ public class PickupStationDialog extends Dialog { phone.setPlaceholder(translationHelper.getTranslation("profile.phone")); phone.setWidthFull(); + // E-Mail + mail = new TextField(translationHelper.getTranslation("customers.column.email")); + mail.setPlaceholder(translationHelper.getTranslation("customers.column.email")); + mail.setWidthFull(); + // Street + house number street = new TextField(translationHelper.getTranslation("profile.street")); street.setPlaceholder(translationHelper.getTranslation("profile.street")); @@ -375,22 +392,54 @@ public class PickupStationDialog extends Dialog { // Clear error styling on value change for required fields and update tab // indicators - firstName.addValueChangeListener(ev -> validateRequiredFields()); - lastName.addValueChangeListener(ev -> validateRequiredFields()); - street.addValueChangeListener(ev -> validateRequiredFields()); - houseNumber.addValueChangeListener(ev -> validateRequiredFields()); - zip.addValueChangeListener(ev -> validateRequiredFields()); - city.addValueChangeListener(ev -> validateRequiredFields()); + firstName.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + lastName.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + street.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + houseNumber.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + zip.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + city.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + salutation.addValueChangeListener(ev -> updateSaveAddressState()); + phone.addValueChangeListener(ev -> updateSaveAddressState()); + addressAddition.addValueChangeListener(ev -> updateSaveAddressState()); + mail.addValueChangeListener(ev -> { + validateRequiredFields(); + updateSaveAddressState(); + }); + saveAddress.addValueChangeListener(ev -> { + updateMailRequirement(); + validateRequiredFields(); + }); // Customer selection fills address fields customerComboBox.addValueChangeListener(ev -> { String selected = ev.getValue(); - if (selected == null) + if (selected == null) { + updateSaveAddressState(); return; + } Customer c = customerLabelMap.get(selected); - if (c == null) + if (c == null) { + updateSaveAddressState(); return; - saveAddress.setValue(false); + } if (c.getCompanyName() != null) company.setValue(c.getCompanyName()); else @@ -412,6 +461,10 @@ public class PickupStationDialog extends Dialog { phone.setValue(c.getTelephone()); else phone.clear(); + if (c.getMail() != null) + mail.setValue(c.getMail()); + else + mail.clear(); if (c.getStreet() != null) street.setValue(c.getStreet()); else @@ -432,10 +485,12 @@ public class PickupStationDialog extends Dialog { city.setValue(c.getCity()); else city.clear(); + + updateSaveAddressState(); }); - formLayout.add(customerComboBox, company, salutation, firstName, lastName, phone, streetLayout, addressAddition, - zipCityLayout, saveAddress); + formLayout.add(customerComboBox, company, salutation, firstName, lastName, phone, mail, streetLayout, + addressAddition, zipCityLayout, saveAddress); // TabSheet with address, appointments, and cargo tabs TabSheet tabSheet = new TabSheet(); @@ -564,6 +619,11 @@ public class PickupStationDialog extends Dialog { public void setData(PickupData data) { if (data == null) return; + if (data.getCustomerSelection() != null) { + customerComboBox.setValue(data.getCustomerSelection()); + } else { + customerComboBox.clear(); + } if (data.getCompany() != null) company.setValue(data.getCompany()); if (data.getSalutation() != null) @@ -574,6 +634,10 @@ public class PickupStationDialog extends Dialog { lastName.setValue(data.getLastName()); if (data.getPhone() != null) phone.setValue(data.getPhone()); + if (data.getMail() != null) + mail.setValue(data.getMail()); + else + mail.clear(); if (data.getStreet() != null) street.setValue(data.getStreet()); if (data.getHouseNumber() != null) @@ -584,11 +648,6 @@ public class PickupStationDialog extends Dialog { zip.setValue(data.getZip()); if (data.getCity() != null) city.setValue(data.getCity()); - saveAddress.setValue(data.isSaveAddress()); - - if (data.getCustomerSelection() != null) { - customerComboBox.setValue(data.getCustomerSelection()); - } if (data.getAppointmentDate() != null && appointmentDatePicker != null) { appointmentDatePicker.setValue(data.getAppointmentDate()); } @@ -608,6 +667,9 @@ public class PickupStationDialog extends Dialog { addCargoRowWithData(item); } } + + saveAddress.setValue(data.isSaveAddress()); + updateSaveAddressState(); } private PickupData collectData() { @@ -617,6 +679,7 @@ public class PickupStationDialog extends Dialog { data.setFirstName(firstName.getValue()); data.setLastName(lastName.getValue()); data.setPhone(phone.getValue()); + data.setMail(mail.getValue()); data.setStreet(street.getValue()); data.setHouseNumber(houseNumber.getValue()); data.setAddressAddition(addressAddition.getValue()); @@ -649,6 +712,7 @@ public class PickupStationDialog extends Dialog { addressValid &= validateTextField(houseNumber); addressValid &= validateTextField(zip); addressValid &= validateTextField(city); + addressValid &= validateMailField(); if (addressTabError != null) { addressTabError.setVisible(!addressValid); } @@ -685,6 +749,17 @@ public class PickupStationDialog extends Dialog { return !empty; } + private boolean validateMailField() { + String value = mail.getValue(); + String normalizedValue = value == null ? "" : value.trim(); + boolean empty = normalizedValue.isEmpty(); + boolean required = Boolean.TRUE.equals(saveAddress.getValue()); + boolean invalid = !empty && !normalizedValue.contains("@"); + boolean hasError = invalid || (required && empty); + applyErrorStyling(mail, hasError); + return !hasError; + } + private boolean validateCargoItems() { boolean valid = true; if (cargoList == null) @@ -737,16 +812,25 @@ public class PickupStationDialog extends Dialog { List companyNames = customers.stream().map(Customer::getCompanyName) .filter(name -> name != null && !name.trim().isEmpty()).distinct().sorted().toList(); + companyCustomerMap.clear(); + for (Customer customer : customers) { + String companyName = normalizeValue(customer.getCompanyName()); + if (companyName.isEmpty() || companyCustomerMap.containsKey(companyName)) { + continue; + } + companyCustomerMap.put(companyName, customer); + } companyField.setItems(companyNames); companyField.addValueChangeListener(event -> { String selectedCompany = event.getValue(); if (selectedCompany == null || selectedCompany.trim().isEmpty()) { + updateSaveAddressState(); return; } Optional matchingCustomer = customers.stream() - .filter(c -> selectedCompany.equals(c.getCompanyName())).findFirst(); + .filter(c -> sameValue(selectedCompany, c.getCompanyName())).findFirst(); if (matchingCustomer.isPresent()) { Customer customer = matchingCustomer.get(); @@ -761,6 +845,8 @@ public class PickupStationDialog extends Dialog { lastName.setValue(customer.getLastName()); if (customer.getTelephone() != null) phone.setValue(customer.getTelephone()); + if (customer.getMail() != null) + mail.setValue(customer.getMail()); if (customer.getStreet() != null) street.setValue(customer.getStreet()); if (customer.getHouseNumber() != null) @@ -772,9 +858,57 @@ public class PickupStationDialog extends Dialog { if (customer.getCity() != null) city.setValue(customer.getCity()); } + + updateSaveAddressState(); }); - companyField.addCustomValueSetListener(event -> companyField.setValue(event.getDetail())); + companyField.addCustomValueSetListener(event -> { + companyField.setValue(event.getDetail()); + updateSaveAddressState(); + }); + } + + private void updateSaveAddressState() { + Customer selectedCustomer = customerLabelMap.get(customerComboBox.getValue()); + Customer selectedCompanyCustomer = companyCustomerMap.get(normalizeValue(company.getValue())); + boolean existingCustomerSelected = selectedCustomer != null && matchesCustomer(selectedCustomer); + boolean existingCompanySelected = selectedCompanyCustomer != null && matchesCustomer(selectedCompanyCustomer); + + if (existingCustomerSelected || existingCompanySelected) { + saveAddress.setValue(false); + saveAddress.setEnabled(false); + updateMailRequirement(); + return; + } + + saveAddress.setEnabled(true); + updateMailRequirement(); + } + + private void updateMailRequirement() { + mail.setRequiredIndicatorVisible(Boolean.TRUE.equals(saveAddress.getValue())); + } + + private boolean matchesCustomer(Customer customer) { + return sameValue(company.getValue(), customer.getCompanyName()) + && sameValue(salutation.getValue(), customer.getTitle()) + && sameValue(firstName.getValue(), customer.getFirstname()) + && sameValue(lastName.getValue(), customer.getLastName()) + && sameValue(phone.getValue(), customer.getTelephone()) + && sameValue(mail.getValue(), customer.getMail()) + && sameValue(street.getValue(), customer.getStreet()) + && sameValue(houseNumber.getValue(), customer.getHouseNumber()) + && sameValue(addressAddition.getValue(), customer.getAddressAddition()) + && sameValue(zip.getValue(), customer.getZip()) + && sameValue(city.getValue(), customer.getCity()); + } + + private boolean sameValue(String left, String right) { + return normalizeValue(left).equals(normalizeValue(right)); + } + + private String normalizeValue(String value) { + return value == null ? "" : value.trim(); } // ============================================ diff --git a/backend/src/main/java/de/assecutor/votianlt/pages/service/AddCustomerService.java b/backend/src/main/java/de/assecutor/votianlt/pages/service/AddCustomerService.java index d1d5dda..3af1a7b 100644 --- a/backend/src/main/java/de/assecutor/votianlt/pages/service/AddCustomerService.java +++ b/backend/src/main/java/de/assecutor/votianlt/pages/service/AddCustomerService.java @@ -19,6 +19,8 @@ public class AddCustomerService { } public void addCustomer(Customer customer) { + validateCustomer(customer); + // Setze den aktuellen Benutzer als Ersteller - jetzt direkt aus der Session de.assecutor.votianlt.model.User currentUser = securityService.getCurrentDatabaseUser(); customer.setCreatedBy(currentUser.getId()); @@ -26,4 +28,20 @@ public class AddCustomerService { addCustomerRepository.save(customer); } + + private void validateCustomer(Customer customer) { + if (customer == null) { + throw new IllegalArgumentException("Kunde darf nicht leer sein"); + } + + String mail = customer.getMail() != null ? customer.getMail().trim() : ""; + if (mail.isEmpty()) { + throw new IllegalArgumentException("E-Mail-Adresse ist ein Pflichtfeld"); + } + if (!mail.contains("@")) { + throw new IllegalArgumentException("Bitte geben Sie eine gültige E-Mail-Adresse ein"); + } + + customer.setMail(mail); + } } diff --git a/backend/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java b/backend/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java index e8ba73b..7373166 100644 --- a/backend/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java +++ b/backend/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java @@ -131,6 +131,7 @@ public class AddJobView extends Main implements HasDynamicTitle { private TextField pickupFirstName; private TextField pickupLastName; private TextField pickupPhone; + private String pickupMail; private TextField pickupStreet; private TextField pickupHouseNumber; private TextField pickupAddressAddition; @@ -143,6 +144,7 @@ public class AddJobView extends Main implements HasDynamicTitle { private final List deliveryStationTilesList = new ArrayList<>(); private final List deliveryStationsState = new ArrayList<>(); private final List deliveryStationsSaveAddress = new ArrayList<>(); + private final List deliveryStationsMailState = new ArrayList<>(); private final List
deliveryStationSlotList = new ArrayList<>(); private final List deliveryStationDistanceChips = new ArrayList<>(); private Div stationsGridContainer; @@ -720,6 +722,7 @@ public class AddJobView extends Main implements HasDynamicTitle { // Add empty state for this station deliveryStationsState.add(new DeliveryStation()); deliveryStationsSaveAddress.add(true); + deliveryStationsMailState.add(null); deliveryStationsValidatedByGoogle.add(false); int stationIndex = deliveryStationTilesList.size(); @@ -766,6 +769,7 @@ public class AddJobView extends Main implements HasDynamicTitle { deliveryStationTilesList.remove(removeIdx); deliveryStationsState.remove(removeIdx); deliveryStationsSaveAddress.remove(removeIdx); + deliveryStationsMailState.remove(removeIdx); deliveryStationsValidatedByGoogle.remove(removeIdx); deliveryStationTasksState.remove(removeIdx); Div removedSlot = deliveryStationSlotList.remove(removeIdx); @@ -854,6 +858,7 @@ public class AddJobView extends Main implements HasDynamicTitle { pickupFirstName.setValue(data.getFirstName() != null ? data.getFirstName() : ""); pickupLastName.setValue(data.getLastName() != null ? data.getLastName() : ""); pickupPhone.setValue(data.getPhone() != null ? data.getPhone() : ""); + pickupMail = trimToNull(data.getMail()); pickupStreet.setValue(data.getStreet() != null ? data.getStreet() : ""); pickupHouseNumber.setValue(data.getHouseNumber() != null ? data.getHouseNumber() : ""); pickupAddressAddition.setValue(data.getAddressAddition() != null ? data.getAddressAddition() : ""); @@ -899,6 +904,7 @@ public class AddJobView extends Main implements HasDynamicTitle { currentData.setFirstName(pickupFirstName.getValue()); currentData.setLastName(pickupLastName.getValue()); currentData.setPhone(pickupPhone.getValue()); + currentData.setMail(pickupMail); currentData.setStreet(pickupStreet.getValue()); currentData.setHouseNumber(pickupHouseNumber.getValue()); currentData.setAddressAddition(pickupAddressAddition.getValue()); @@ -1129,6 +1135,7 @@ public class AddJobView extends Main implements HasDynamicTitle { station.setCity(data.getCity()); station.setTasks(data.getTasks() != null ? new ArrayList<>(data.getTasks()) : new ArrayList<>()); deliveryStationsSaveAddress.set(idx, data.isSaveAddress()); + deliveryStationsMailState.set(idx, trimToNull(data.getMail())); // Store tasks for this delivery station deliveryStationTasksState.put(idx, data.getTasks() != null ? data.getTasks() : new ArrayList<>()); @@ -1166,6 +1173,7 @@ public class AddJobView extends Main implements HasDynamicTitle { currentData.setFirstName(station.getFirstName()); currentData.setLastName(station.getLastName()); currentData.setPhone(station.getPhone()); + currentData.setMail(actualIndex < deliveryStationsMailState.size() ? deliveryStationsMailState.get(actualIndex) : null); currentData.setStreet(station.getStreet()); currentData.setHouseNumber(station.getHouseNumber()); currentData.setAddressAddition(station.getAddressAddition()); @@ -1417,6 +1425,7 @@ public class AddJobView extends Main implements HasDynamicTitle { pickupLastName.setValue(customer.getLastName()); if (customer.getTelephone() != null) pickupPhone.setValue(customer.getTelephone()); + pickupMail = trimToNull(customer.getMail()); if (customer.getStreet() != null) pickupStreet.setValue(customer.getStreet()); if (customer.getHouseNumber() != null) @@ -1443,6 +1452,7 @@ public class AddJobView extends Main implements HasDynamicTitle { // Reactivate save checkbox for custom values savePickupAddress.setValue(true); + pickupMail = null; }); } @@ -1813,6 +1823,7 @@ public class AddJobView extends Main implements HasDynamicTitle { pickupCustomer.setFirstname(pickupFirstName.getValue()); pickupCustomer.setLastName(pickupLastName.getValue()); pickupCustomer.setTelephone(pickupPhone.getValue()); + pickupCustomer.setMail(pickupMail); pickupCustomer.setStreet(pickupStreet.getValue()); pickupCustomer.setHouseNumber(pickupHouseNumber.getValue()); pickupCustomer.setAddressAddition(pickupAddressAddition.getValue()); @@ -1830,6 +1841,7 @@ public class AddJobView extends Main implements HasDynamicTitle { deliveryCustomer.setFirstname(ds.getFirstName()); deliveryCustomer.setLastName(ds.getLastName()); deliveryCustomer.setTelephone(ds.getPhone()); + deliveryCustomer.setMail(i < deliveryStationsMailState.size() ? deliveryStationsMailState.get(i) : null); deliveryCustomer.setStreet(ds.getStreet()); deliveryCustomer.setHouseNumber(ds.getHouseNumber()); deliveryCustomer.setAddressAddition(ds.getAddressAddition()); diff --git a/backend/src/main/resources/messages_de.properties b/backend/src/main/resources/messages_de.properties index b80bb6a..784e794 100644 --- a/backend/src/main/resources/messages_de.properties +++ b/backend/src/main/resources/messages_de.properties @@ -478,22 +478,22 @@ addjob.cargo.gridcart=Gitterwagen addjob.cargo.parcel=Paket addjob.cargo.add=Fracht hinzufügen addjob.tasks.title=Aufgaben -addjob.tasks.template.placeholder=Template auswählen -addjob.tasks.template.save.tooltip=Als Template speichern -addjob.tasks.template.save.title=Template speichern -addjob.tasks.template.name=Template-Name +addjob.tasks.template.placeholder=Vorlage auswählen +addjob.tasks.template.save.tooltip=Als Vorlage speichern +addjob.tasks.template.save.title=Vorlage speichern +addjob.tasks.template.name=Vorlagen-Name addjob.tasks.template.name.placeholder=Name eingeben addjob.tasks.template.name.required=Name ist erforderlich -addjob.tasks.template.saved=Template "{0}" gespeichert +addjob.tasks.template.saved=Vorlage "{0}" gespeichert addjob.tasks.template.save.error=Fehler beim Speichern: {0} addjob.tasks.template.dialog.error=Fehler beim Öffnen des Dialogs: {0} addjob.tasks.template.no.tasks=Keine Aufgaben zum Speichern -addjob.tasks.template.load.title=Template laden -addjob.tasks.template.load.text=Möchten Sie das Template "{0}" laden? Diese Aktion ersetzt alle aktuellen Aufgaben. +addjob.tasks.template.load.title=Vorlage laden +addjob.tasks.template.load.text=Möchten Sie die Vorlage "{0}" laden? Diese Aktion ersetzt alle aktuellen Aufgaben. addjob.tasks.template.load.confirm=Laden -addjob.tasks.template.loaded=Template "{0}" geladen +addjob.tasks.template.loaded=Vorlage "{0}" geladen addjob.tasks.template.load.error=Fehler beim Laden: {0} -addjob.tasks.template.load.templates.error=Fehler beim Laden der Templates: {0} +addjob.tasks.template.load.templates.error=Fehler beim Laden der Vorlagen: {0} addjob.tasks.add=Aufgabe hinzufügen addjob.tasks.tasktype=Aufgabentyp addjob.tasks.tasktype.placeholder=Typ wählen