Version 0.9.14: E-Mail-Feld in Stationsdialogen und Kundenvalidierung

- 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
This commit is contained in:
2026-03-30 10:40:42 +02:00
parent 2534d321cf
commit d6132fabe1
6 changed files with 377 additions and 71 deletions

View File

@@ -11,7 +11,7 @@
<packaging>jar</packaging>
<properties>
<revision>0.9.13</revision>
<revision>0.9.14</revision>
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>

View File

@@ -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<com.vaadin.flow.component.Component> 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<TaskTemplate> 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();
}
}

View File

@@ -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<String> customerComboBox;
private final Map<String, Customer> customerLabelMap = new LinkedHashMap<>();
private final Map<String, Customer> 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<String, Customer> 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<String> 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<Customer> 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();
}
// ============================================

View File

@@ -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);
}
}

View File

@@ -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<StationTile> deliveryStationTilesList = new ArrayList<>();
private final List<DeliveryStation> deliveryStationsState = new ArrayList<>();
private final List<Boolean> deliveryStationsSaveAddress = new ArrayList<>();
private final List<String> deliveryStationsMailState = new ArrayList<>();
private final List<Div> deliveryStationSlotList = new ArrayList<>();
private final List<Span> 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());

View File

@@ -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