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> <packaging>jar</packaging>
<properties> <properties>
<revision>0.9.13</revision> <revision>0.9.14</revision>
<java.version>21</java.version> <java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target> <maven.compiler.target>21</maven.compiler.target>

View File

@@ -46,6 +46,7 @@ public class DeliveryStationDialog extends Dialog {
private String firstName; private String firstName;
private String lastName; private String lastName;
private String phone; private String phone;
private String mail;
private String street; private String street;
private String houseNumber; private String houseNumber;
private String addressAddition; private String addressAddition;
@@ -112,6 +113,14 @@ public class DeliveryStationDialog extends Dialog {
this.phone = phone; this.phone = phone;
} }
public String getMail() {
return mail;
}
public void setMail(String mail) {
this.mail = mail;
}
public String getStreet() { public String getStreet() {
return street; return street;
} }
@@ -185,6 +194,7 @@ public class DeliveryStationDialog extends Dialog {
private final TextField firstName; private final TextField firstName;
private final TextField lastName; private final TextField lastName;
private final TextField phone; private final TextField phone;
private final TextField mail;
private final TextField street; private final TextField street;
private final TextField houseNumber; private final TextField houseNumber;
private final TextField addressAddition; private final TextField addressAddition;
@@ -258,6 +268,12 @@ public class DeliveryStationDialog extends Dialog {
phone.setWidthFull(); phone.setWidthFull();
formLayout.add(phone); 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 + house number
street = new TextField(translationHelper.getTranslation("profile.street")); street = new TextField(translationHelper.getTranslation("profile.street"));
street.setPlaceholder(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 // Clear error styling on value change for required fields and update tab
// indicators // indicators
firstName.addValueChangeListener(ev -> validateRequiredFields()); firstName.addValueChangeListener(ev -> {
lastName.addValueChangeListener(ev -> validateRequiredFields()); validateRequiredFields();
street.addValueChangeListener(ev -> validateRequiredFields()); updateSaveAddressState();
houseNumber.addValueChangeListener(ev -> validateRequiredFields()); });
zip.addValueChangeListener(ev -> validateRequiredFields()); lastName.addValueChangeListener(ev -> {
city.addValueChangeListener(ev -> validateRequiredFields()); 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 with address and tasks tabs
TabSheet tabSheet = new TabSheet(); TabSheet tabSheet = new TabSheet();
@@ -453,6 +498,10 @@ public class DeliveryStationDialog extends Dialog {
lastName.setValue(data.getLastName()); lastName.setValue(data.getLastName());
if (data.getPhone() != null) if (data.getPhone() != null)
phone.setValue(data.getPhone()); phone.setValue(data.getPhone());
if (data.getMail() != null)
mail.setValue(data.getMail());
else
mail.clear();
if (data.getStreet() != null) if (data.getStreet() != null)
street.setValue(data.getStreet()); street.setValue(data.getStreet());
if (data.getHouseNumber() != null) if (data.getHouseNumber() != null)
@@ -464,7 +513,7 @@ public class DeliveryStationDialog extends Dialog {
if (data.getCity() != null) if (data.getCity() != null)
city.setValue(data.getCity()); city.setValue(data.getCity());
saveAddress.setValue(customerSelectedFromOptions ? false : data.isSaveAddress()); saveAddress.setValue(customerSelectedFromOptions ? false : data.isSaveAddress());
updateSaveAddressState(customerSelectedFromOptions); updateSaveAddressState();
// Load tasks into dialog state // Load tasks into dialog state
if (data.getTasks() != null && !data.getTasks().isEmpty()) { if (data.getTasks() != null && !data.getTasks().isEmpty()) {
@@ -482,6 +531,7 @@ public class DeliveryStationDialog extends Dialog {
} }
} }
} }
} }
private DeliveryData collectData() { private DeliveryData collectData() {
@@ -491,6 +541,7 @@ public class DeliveryStationDialog extends Dialog {
data.setFirstName(firstName.getValue()); data.setFirstName(firstName.getValue());
data.setLastName(lastName.getValue()); data.setLastName(lastName.getValue());
data.setPhone(phone.getValue()); data.setPhone(phone.getValue());
data.setMail(mail.getValue());
data.setStreet(street.getValue()); data.setStreet(street.getValue());
data.setHouseNumber(houseNumber.getValue()); data.setHouseNumber(houseNumber.getValue());
data.setAddressAddition(addressAddition.getValue()); data.setAddressAddition(addressAddition.getValue());
@@ -510,6 +561,7 @@ public class DeliveryStationDialog extends Dialog {
addressValid &= validateTextField(houseNumber); addressValid &= validateTextField(houseNumber);
addressValid &= validateTextField(zip); addressValid &= validateTextField(zip);
addressValid &= validateTextField(city); addressValid &= validateTextField(city);
addressValid &= validateMailField();
addressTabError.setVisible(!addressValid); addressTabError.setVisible(!addressValid);
// Tasks tab validation // Tasks tab validation
@@ -545,6 +597,17 @@ public class DeliveryStationDialog extends Dialog {
return !empty; 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) { private void applyErrorStyling(com.vaadin.flow.component.Component field, boolean error) {
if (error) { if (error) {
field.getElement().getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)"); 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 -> { companyField.addValueChangeListener(event -> {
Customer customer = companyAddressOptions.get(event.getValue()); Customer customer = companyAddressOptions.get(event.getValue());
updateSaveAddressState(customer != null);
if (customer == null) { if (customer == null) {
updateSaveAddressState();
return; return;
} }
@@ -600,6 +663,8 @@ public class DeliveryStationDialog extends Dialog {
lastName.setValue(customer.getLastName()); lastName.setValue(customer.getLastName());
if (customer.getTelephone() != null) if (customer.getTelephone() != null)
phone.setValue(customer.getTelephone()); phone.setValue(customer.getTelephone());
if (customer.getMail() != null)
mail.setValue(customer.getMail());
if (customer.getStreet() != null) if (customer.getStreet() != null)
street.setValue(customer.getStreet()); street.setValue(customer.getStreet());
if (customer.getHouseNumber() != null) if (customer.getHouseNumber() != null)
@@ -610,22 +675,32 @@ public class DeliveryStationDialog extends Dialog {
zip.setValue(customer.getZip()); zip.setValue(customer.getZip());
if (customer.getCity() != null) if (customer.getCity() != null)
city.setValue(customer.getCity()); city.setValue(customer.getCity());
updateSaveAddressState();
}); });
companyField.addCustomValueSetListener(event -> { companyField.addCustomValueSetListener(event -> {
companyField.setValue(event.getDetail()); 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) { if (customerSelectedFromOptions) {
saveAddress.setValue(false); saveAddress.setValue(false);
saveAddress.setEnabled(false); saveAddress.setEnabled(false);
updateMailRequirement();
return; return;
} }
saveAddress.setEnabled(true); saveAddress.setEnabled(true);
updateMailRequirement();
}
private void updateMailRequirement() {
mail.setRequiredIndicatorVisible(Boolean.TRUE.equals(saveAddress.getValue()));
} }
private String buildCompanyAddressLabel(Customer customer) { private String buildCompanyAddressLabel(Customer customer) {
@@ -687,12 +762,32 @@ public class DeliveryStationDialog extends Dialog {
private boolean matchesCustomer(Customer customer, DeliveryData data) { private boolean matchesCustomer(Customer customer, DeliveryData data) {
return equalsNormalized(customer.getCompanyName(), data.getCompany()) 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.getStreet(), data.getStreet())
&& equalsNormalized(customer.getAddressAddition(), data.getAddressAddition())
&& equalsNormalized(customer.getHouseNumber(), data.getHouseNumber()) && equalsNormalized(customer.getHouseNumber(), data.getHouseNumber())
&& equalsNormalized(customer.getZip(), data.getZip()) && equalsNormalized(customer.getZip(), data.getZip())
&& equalsNormalized(customer.getCity(), data.getCity()); && 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) { private boolean equalsNormalized(String left, String right) {
String normalizedLeft = left != null ? left.trim() : ""; String normalizedLeft = left != null ? left.trim() : "";
String normalizedRight = right != null ? right.trim() : ""; String normalizedRight = right != null ? right.trim() : "";
@@ -756,8 +851,9 @@ public class DeliveryStationDialog extends Dialog {
tasksList.setPadding(false); tasksList.setPadding(false);
tasksList.setSpacing(true); tasksList.setSpacing(true);
// Add 1 example row // Add 1 example row, then append a signature task once on initial setup
createTaskRow(); createTaskRow();
ensureTrailingSignatureTask();
Button addTaskBtn = new Button(translationHelper.getTranslation("addjob.tasks.add"), new Icon(VaadinIcon.PLUS)); Button addTaskBtn = new Button(translationHelper.getTranslation("addjob.tasks.add"), new Icon(VaadinIcon.PLUS));
addTaskBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY); addTaskBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
@@ -791,14 +887,7 @@ public class DeliveryStationDialog extends Dialog {
Button deleteXButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL)); Button deleteXButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
deleteXButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY); deleteXButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
deleteXButton.addClassName("dialog-floating-delete"); deleteXButton.addClassName("dialog-floating-delete");
deleteXButton.addClickListener(e -> { deleteXButton.addClickListener(e -> removeTaskRow(taskContainer));
int idx = tasksList.getChildren().toList().indexOf(taskContainer);
if (idx >= 0 && idx < tasksState.size()) {
tasksState.remove(idx);
reorderTasksAfterDeletion();
}
tasksList.remove(taskContainer);
});
taskContainer.add(taskTypeCombo, configContainer); taskContainer.add(taskTypeCombo, configContainer);
taskContainer.add(deleteXButton); taskContainer.add(deleteXButton);
@@ -810,6 +899,9 @@ public class DeliveryStationDialog extends Dialog {
final BaseTask[] currentTask = { task }; final BaseTask[] currentTask = { task };
taskTypeCombo.setValue(TaskType.CONFIRMATION);
updateTaskConfiguration(configContainer, currentTask[0]);
taskTypeCombo.addValueChangeListener(ev -> { taskTypeCombo.addValueChangeListener(ev -> {
TaskType selectedType = ev.getValue(); TaskType selectedType = ev.getValue();
if (selectedType != null) { 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); tasksList.add(taskContainer);
updateTaskDeleteAvailability();
} }
private void createTaskRowFromTask(BaseTask task) { private void createTaskRowFromTask(BaseTask task) {
@@ -882,14 +971,7 @@ public class DeliveryStationDialog extends Dialog {
Button deleteXButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL)); Button deleteXButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
deleteXButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY); deleteXButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
deleteXButton.addClassName("dialog-floating-delete"); deleteXButton.addClassName("dialog-floating-delete");
deleteXButton.addClickListener(e -> { deleteXButton.addClickListener(e -> removeTaskRow(taskContainer));
int idx = tasksList.getChildren().toList().indexOf(taskContainer);
if (idx >= 0 && idx < tasksState.size()) {
tasksState.remove(idx);
reorderTasksAfterDeletion();
}
tasksList.remove(taskContainer);
});
taskContainer.add(taskTypeCombo, configContainer); taskContainer.add(taskTypeCombo, configContainer);
taskContainer.add(deleteXButton); taskContainer.add(deleteXButton);
@@ -951,6 +1033,7 @@ public class DeliveryStationDialog extends Dialog {
updateTaskConfiguration(configContainer, task); updateTaskConfiguration(configContainer, task);
tasksList.add(taskContainer); tasksList.add(taskContainer);
updateTaskDeleteAvailability();
} }
private BaseTask createTaskByType(TaskType taskType) { 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) { private void updateTaskConfiguration(VerticalLayout configContainer, BaseTask task) {
configContainer.removeAll(); configContainer.removeAll();
@@ -1315,16 +1448,22 @@ public class DeliveryStationDialog extends Dialog {
} }
private void loadTasksFromTemplate(TaskTemplate template, ComboBox<TaskTemplate> templateComboBox) { private void loadTasksFromTemplate(TaskTemplate template, ComboBox<TaskTemplate> templateComboBox) {
ConfirmDialog confirmDialog = new ConfirmDialog(); Dialog confirmDialog = DialogStylingHelper
confirmDialog.setHeader(translationHelper.getTranslation("addjob.tasks.template.load.title")); .createStyledDialog(translationHelper.getTranslation("addjob.tasks.template.load.title"), "560px");
confirmDialog.setText( confirmDialog.setCloseOnOutsideClick(false);
translationHelper.getTranslation("addjob.tasks.template.load.text", template.getTemplateName())); confirmDialog.addDialogCloseActionListener(e -> templateComboBox.clear());
confirmDialog.setCancelable(true);
confirmDialog.setCancelText(translationHelper.getTranslation("button.cancel"));
confirmDialog.setConfirmText(translationHelper.getTranslation("addjob.tasks.template.load.confirm"));
confirmDialog.setConfirmButtonTheme("primary");
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(); tasksState.clear();
tasksList.removeAll(); tasksList.removeAll();
@@ -1338,16 +1477,19 @@ public class DeliveryStationDialog extends Dialog {
} }
} }
ensureTrailingSignatureTask();
templateComboBox.clear(); templateComboBox.clear();
validateRequiredFields(); validateRequiredFields();
Notification.show( Notification.show(
translationHelper.getTranslation("addjob.tasks.template.loaded", template.getTemplateName()), 3000, translationHelper.getTranslation("addjob.tasks.template.loaded", template.getTemplateName()), 3000,
Notification.Position.BOTTOM_END); 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(); confirmDialog.open();
} }
} }

View File

@@ -54,6 +54,7 @@ public class PickupStationDialog extends Dialog {
private String firstName; private String firstName;
private String lastName; private String lastName;
private String phone; private String phone;
private String mail;
private String street; private String street;
private String houseNumber; private String houseNumber;
private String addressAddition; private String addressAddition;
@@ -125,6 +126,14 @@ public class PickupStationDialog extends Dialog {
this.phone = phone; this.phone = phone;
} }
public String getMail() {
return mail;
}
public void setMail(String mail) {
this.mail = mail;
}
public String getStreet() { public String getStreet() {
return street; return street;
} }
@@ -231,6 +240,7 @@ public class PickupStationDialog extends Dialog {
private final TextField firstName; private final TextField firstName;
private final TextField lastName; private final TextField lastName;
private final TextField phone; private final TextField phone;
private final TextField mail;
private final TextField street; private final TextField street;
private final TextField houseNumber; private final TextField houseNumber;
private final TextField addressAddition; private final TextField addressAddition;
@@ -239,6 +249,8 @@ public class PickupStationDialog extends Dialog {
private final Checkbox saveAddress; private final Checkbox saveAddress;
private final ComboBox<String> customerComboBox; 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 DatePicker appointmentDatePicker;
private TimePicker appointmentTimePicker; private TimePicker appointmentTimePicker;
private Checkbox digitalProcessingCheckbox; private Checkbox digitalProcessingCheckbox;
@@ -278,7 +290,7 @@ public class PickupStationDialog extends Dialog {
customerComboBox.setRequiredIndicatorVisible(true); customerComboBox.setRequiredIndicatorVisible(true);
customerComboBox.setWidthFull(); customerComboBox.setWidthFull();
Map<String, Customer> customerLabelMap = new LinkedHashMap<>(); customerLabelMap.clear();
for (Customer c : customers) { for (Customer c : customers) {
String label = (c.getCompanyName() != null && !c.getCompanyName().isBlank()) String label = (c.getCompanyName() != null && !c.getCompanyName().isBlank())
? c.getCompanyName() + " | " ? c.getCompanyName() + " | "
@@ -330,6 +342,11 @@ public class PickupStationDialog extends Dialog {
phone.setPlaceholder(translationHelper.getTranslation("profile.phone")); phone.setPlaceholder(translationHelper.getTranslation("profile.phone"));
phone.setWidthFull(); 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 + house number
street = new TextField(translationHelper.getTranslation("profile.street")); street = new TextField(translationHelper.getTranslation("profile.street"));
street.setPlaceholder(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 // Clear error styling on value change for required fields and update tab
// indicators // indicators
firstName.addValueChangeListener(ev -> validateRequiredFields()); firstName.addValueChangeListener(ev -> {
lastName.addValueChangeListener(ev -> validateRequiredFields()); validateRequiredFields();
street.addValueChangeListener(ev -> validateRequiredFields()); updateSaveAddressState();
houseNumber.addValueChangeListener(ev -> validateRequiredFields()); });
zip.addValueChangeListener(ev -> validateRequiredFields()); lastName.addValueChangeListener(ev -> {
city.addValueChangeListener(ev -> validateRequiredFields()); 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 // Customer selection fills address fields
customerComboBox.addValueChangeListener(ev -> { customerComboBox.addValueChangeListener(ev -> {
String selected = ev.getValue(); String selected = ev.getValue();
if (selected == null) if (selected == null) {
updateSaveAddressState();
return; return;
}
Customer c = customerLabelMap.get(selected); Customer c = customerLabelMap.get(selected);
if (c == null) if (c == null) {
updateSaveAddressState();
return; return;
saveAddress.setValue(false); }
if (c.getCompanyName() != null) if (c.getCompanyName() != null)
company.setValue(c.getCompanyName()); company.setValue(c.getCompanyName());
else else
@@ -412,6 +461,10 @@ public class PickupStationDialog extends Dialog {
phone.setValue(c.getTelephone()); phone.setValue(c.getTelephone());
else else
phone.clear(); phone.clear();
if (c.getMail() != null)
mail.setValue(c.getMail());
else
mail.clear();
if (c.getStreet() != null) if (c.getStreet() != null)
street.setValue(c.getStreet()); street.setValue(c.getStreet());
else else
@@ -432,10 +485,12 @@ public class PickupStationDialog extends Dialog {
city.setValue(c.getCity()); city.setValue(c.getCity());
else else
city.clear(); city.clear();
updateSaveAddressState();
}); });
formLayout.add(customerComboBox, company, salutation, firstName, lastName, phone, streetLayout, addressAddition, formLayout.add(customerComboBox, company, salutation, firstName, lastName, phone, mail, streetLayout,
zipCityLayout, saveAddress); addressAddition, zipCityLayout, saveAddress);
// TabSheet with address, appointments, and cargo tabs // TabSheet with address, appointments, and cargo tabs
TabSheet tabSheet = new TabSheet(); TabSheet tabSheet = new TabSheet();
@@ -564,6 +619,11 @@ public class PickupStationDialog extends Dialog {
public void setData(PickupData data) { public void setData(PickupData data) {
if (data == null) if (data == null)
return; return;
if (data.getCustomerSelection() != null) {
customerComboBox.setValue(data.getCustomerSelection());
} else {
customerComboBox.clear();
}
if (data.getCompany() != null) if (data.getCompany() != null)
company.setValue(data.getCompany()); company.setValue(data.getCompany());
if (data.getSalutation() != null) if (data.getSalutation() != null)
@@ -574,6 +634,10 @@ public class PickupStationDialog extends Dialog {
lastName.setValue(data.getLastName()); lastName.setValue(data.getLastName());
if (data.getPhone() != null) if (data.getPhone() != null)
phone.setValue(data.getPhone()); phone.setValue(data.getPhone());
if (data.getMail() != null)
mail.setValue(data.getMail());
else
mail.clear();
if (data.getStreet() != null) if (data.getStreet() != null)
street.setValue(data.getStreet()); street.setValue(data.getStreet());
if (data.getHouseNumber() != null) if (data.getHouseNumber() != null)
@@ -584,11 +648,6 @@ public class PickupStationDialog extends Dialog {
zip.setValue(data.getZip()); zip.setValue(data.getZip());
if (data.getCity() != null) if (data.getCity() != null)
city.setValue(data.getCity()); city.setValue(data.getCity());
saveAddress.setValue(data.isSaveAddress());
if (data.getCustomerSelection() != null) {
customerComboBox.setValue(data.getCustomerSelection());
}
if (data.getAppointmentDate() != null && appointmentDatePicker != null) { if (data.getAppointmentDate() != null && appointmentDatePicker != null) {
appointmentDatePicker.setValue(data.getAppointmentDate()); appointmentDatePicker.setValue(data.getAppointmentDate());
} }
@@ -608,6 +667,9 @@ public class PickupStationDialog extends Dialog {
addCargoRowWithData(item); addCargoRowWithData(item);
} }
} }
saveAddress.setValue(data.isSaveAddress());
updateSaveAddressState();
} }
private PickupData collectData() { private PickupData collectData() {
@@ -617,6 +679,7 @@ public class PickupStationDialog extends Dialog {
data.setFirstName(firstName.getValue()); data.setFirstName(firstName.getValue());
data.setLastName(lastName.getValue()); data.setLastName(lastName.getValue());
data.setPhone(phone.getValue()); data.setPhone(phone.getValue());
data.setMail(mail.getValue());
data.setStreet(street.getValue()); data.setStreet(street.getValue());
data.setHouseNumber(houseNumber.getValue()); data.setHouseNumber(houseNumber.getValue());
data.setAddressAddition(addressAddition.getValue()); data.setAddressAddition(addressAddition.getValue());
@@ -649,6 +712,7 @@ public class PickupStationDialog extends Dialog {
addressValid &= validateTextField(houseNumber); addressValid &= validateTextField(houseNumber);
addressValid &= validateTextField(zip); addressValid &= validateTextField(zip);
addressValid &= validateTextField(city); addressValid &= validateTextField(city);
addressValid &= validateMailField();
if (addressTabError != null) { if (addressTabError != null) {
addressTabError.setVisible(!addressValid); addressTabError.setVisible(!addressValid);
} }
@@ -685,6 +749,17 @@ public class PickupStationDialog extends Dialog {
return !empty; 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() { private boolean validateCargoItems() {
boolean valid = true; boolean valid = true;
if (cargoList == null) if (cargoList == null)
@@ -737,16 +812,25 @@ public class PickupStationDialog extends Dialog {
List<String> companyNames = customers.stream().map(Customer::getCompanyName) List<String> companyNames = customers.stream().map(Customer::getCompanyName)
.filter(name -> name != null && !name.trim().isEmpty()).distinct().sorted().toList(); .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.setItems(companyNames);
companyField.addValueChangeListener(event -> { companyField.addValueChangeListener(event -> {
String selectedCompany = event.getValue(); String selectedCompany = event.getValue();
if (selectedCompany == null || selectedCompany.trim().isEmpty()) { if (selectedCompany == null || selectedCompany.trim().isEmpty()) {
updateSaveAddressState();
return; return;
} }
Optional<Customer> matchingCustomer = customers.stream() Optional<Customer> matchingCustomer = customers.stream()
.filter(c -> selectedCompany.equals(c.getCompanyName())).findFirst(); .filter(c -> sameValue(selectedCompany, c.getCompanyName())).findFirst();
if (matchingCustomer.isPresent()) { if (matchingCustomer.isPresent()) {
Customer customer = matchingCustomer.get(); Customer customer = matchingCustomer.get();
@@ -761,6 +845,8 @@ public class PickupStationDialog extends Dialog {
lastName.setValue(customer.getLastName()); lastName.setValue(customer.getLastName());
if (customer.getTelephone() != null) if (customer.getTelephone() != null)
phone.setValue(customer.getTelephone()); phone.setValue(customer.getTelephone());
if (customer.getMail() != null)
mail.setValue(customer.getMail());
if (customer.getStreet() != null) if (customer.getStreet() != null)
street.setValue(customer.getStreet()); street.setValue(customer.getStreet());
if (customer.getHouseNumber() != null) if (customer.getHouseNumber() != null)
@@ -772,9 +858,57 @@ public class PickupStationDialog extends Dialog {
if (customer.getCity() != null) if (customer.getCity() != null)
city.setValue(customer.getCity()); 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) { public void addCustomer(Customer customer) {
validateCustomer(customer);
// Setze den aktuellen Benutzer als Ersteller - jetzt direkt aus der Session // Setze den aktuellen Benutzer als Ersteller - jetzt direkt aus der Session
de.assecutor.votianlt.model.User currentUser = securityService.getCurrentDatabaseUser(); de.assecutor.votianlt.model.User currentUser = securityService.getCurrentDatabaseUser();
customer.setCreatedBy(currentUser.getId()); customer.setCreatedBy(currentUser.getId());
@@ -26,4 +28,20 @@ public class AddCustomerService {
addCustomerRepository.save(customer); 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 pickupFirstName;
private TextField pickupLastName; private TextField pickupLastName;
private TextField pickupPhone; private TextField pickupPhone;
private String pickupMail;
private TextField pickupStreet; private TextField pickupStreet;
private TextField pickupHouseNumber; private TextField pickupHouseNumber;
private TextField pickupAddressAddition; private TextField pickupAddressAddition;
@@ -143,6 +144,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
private final List<StationTile> deliveryStationTilesList = new ArrayList<>(); private final List<StationTile> deliveryStationTilesList = new ArrayList<>();
private final List<DeliveryStation> deliveryStationsState = new ArrayList<>(); private final List<DeliveryStation> deliveryStationsState = new ArrayList<>();
private final List<Boolean> deliveryStationsSaveAddress = 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<Div> deliveryStationSlotList = new ArrayList<>();
private final List<Span> deliveryStationDistanceChips = new ArrayList<>(); private final List<Span> deliveryStationDistanceChips = new ArrayList<>();
private Div stationsGridContainer; private Div stationsGridContainer;
@@ -720,6 +722,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
// Add empty state for this station // Add empty state for this station
deliveryStationsState.add(new DeliveryStation()); deliveryStationsState.add(new DeliveryStation());
deliveryStationsSaveAddress.add(true); deliveryStationsSaveAddress.add(true);
deliveryStationsMailState.add(null);
deliveryStationsValidatedByGoogle.add(false); deliveryStationsValidatedByGoogle.add(false);
int stationIndex = deliveryStationTilesList.size(); int stationIndex = deliveryStationTilesList.size();
@@ -766,6 +769,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
deliveryStationTilesList.remove(removeIdx); deliveryStationTilesList.remove(removeIdx);
deliveryStationsState.remove(removeIdx); deliveryStationsState.remove(removeIdx);
deliveryStationsSaveAddress.remove(removeIdx); deliveryStationsSaveAddress.remove(removeIdx);
deliveryStationsMailState.remove(removeIdx);
deliveryStationsValidatedByGoogle.remove(removeIdx); deliveryStationsValidatedByGoogle.remove(removeIdx);
deliveryStationTasksState.remove(removeIdx); deliveryStationTasksState.remove(removeIdx);
Div removedSlot = deliveryStationSlotList.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() : ""); pickupFirstName.setValue(data.getFirstName() != null ? data.getFirstName() : "");
pickupLastName.setValue(data.getLastName() != null ? data.getLastName() : ""); pickupLastName.setValue(data.getLastName() != null ? data.getLastName() : "");
pickupPhone.setValue(data.getPhone() != null ? data.getPhone() : ""); pickupPhone.setValue(data.getPhone() != null ? data.getPhone() : "");
pickupMail = trimToNull(data.getMail());
pickupStreet.setValue(data.getStreet() != null ? data.getStreet() : ""); pickupStreet.setValue(data.getStreet() != null ? data.getStreet() : "");
pickupHouseNumber.setValue(data.getHouseNumber() != null ? data.getHouseNumber() : ""); pickupHouseNumber.setValue(data.getHouseNumber() != null ? data.getHouseNumber() : "");
pickupAddressAddition.setValue(data.getAddressAddition() != null ? data.getAddressAddition() : ""); pickupAddressAddition.setValue(data.getAddressAddition() != null ? data.getAddressAddition() : "");
@@ -899,6 +904,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
currentData.setFirstName(pickupFirstName.getValue()); currentData.setFirstName(pickupFirstName.getValue());
currentData.setLastName(pickupLastName.getValue()); currentData.setLastName(pickupLastName.getValue());
currentData.setPhone(pickupPhone.getValue()); currentData.setPhone(pickupPhone.getValue());
currentData.setMail(pickupMail);
currentData.setStreet(pickupStreet.getValue()); currentData.setStreet(pickupStreet.getValue());
currentData.setHouseNumber(pickupHouseNumber.getValue()); currentData.setHouseNumber(pickupHouseNumber.getValue());
currentData.setAddressAddition(pickupAddressAddition.getValue()); currentData.setAddressAddition(pickupAddressAddition.getValue());
@@ -1129,6 +1135,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
station.setCity(data.getCity()); station.setCity(data.getCity());
station.setTasks(data.getTasks() != null ? new ArrayList<>(data.getTasks()) : new ArrayList<>()); station.setTasks(data.getTasks() != null ? new ArrayList<>(data.getTasks()) : new ArrayList<>());
deliveryStationsSaveAddress.set(idx, data.isSaveAddress()); deliveryStationsSaveAddress.set(idx, data.isSaveAddress());
deliveryStationsMailState.set(idx, trimToNull(data.getMail()));
// Store tasks for this delivery station // Store tasks for this delivery station
deliveryStationTasksState.put(idx, data.getTasks() != null ? data.getTasks() : new ArrayList<>()); 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.setFirstName(station.getFirstName());
currentData.setLastName(station.getLastName()); currentData.setLastName(station.getLastName());
currentData.setPhone(station.getPhone()); currentData.setPhone(station.getPhone());
currentData.setMail(actualIndex < deliveryStationsMailState.size() ? deliveryStationsMailState.get(actualIndex) : null);
currentData.setStreet(station.getStreet()); currentData.setStreet(station.getStreet());
currentData.setHouseNumber(station.getHouseNumber()); currentData.setHouseNumber(station.getHouseNumber());
currentData.setAddressAddition(station.getAddressAddition()); currentData.setAddressAddition(station.getAddressAddition());
@@ -1417,6 +1425,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
pickupLastName.setValue(customer.getLastName()); pickupLastName.setValue(customer.getLastName());
if (customer.getTelephone() != null) if (customer.getTelephone() != null)
pickupPhone.setValue(customer.getTelephone()); pickupPhone.setValue(customer.getTelephone());
pickupMail = trimToNull(customer.getMail());
if (customer.getStreet() != null) if (customer.getStreet() != null)
pickupStreet.setValue(customer.getStreet()); pickupStreet.setValue(customer.getStreet());
if (customer.getHouseNumber() != null) if (customer.getHouseNumber() != null)
@@ -1443,6 +1452,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
// Reactivate save checkbox for custom values // Reactivate save checkbox for custom values
savePickupAddress.setValue(true); savePickupAddress.setValue(true);
pickupMail = null;
}); });
} }
@@ -1813,6 +1823,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
pickupCustomer.setFirstname(pickupFirstName.getValue()); pickupCustomer.setFirstname(pickupFirstName.getValue());
pickupCustomer.setLastName(pickupLastName.getValue()); pickupCustomer.setLastName(pickupLastName.getValue());
pickupCustomer.setTelephone(pickupPhone.getValue()); pickupCustomer.setTelephone(pickupPhone.getValue());
pickupCustomer.setMail(pickupMail);
pickupCustomer.setStreet(pickupStreet.getValue()); pickupCustomer.setStreet(pickupStreet.getValue());
pickupCustomer.setHouseNumber(pickupHouseNumber.getValue()); pickupCustomer.setHouseNumber(pickupHouseNumber.getValue());
pickupCustomer.setAddressAddition(pickupAddressAddition.getValue()); pickupCustomer.setAddressAddition(pickupAddressAddition.getValue());
@@ -1830,6 +1841,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
deliveryCustomer.setFirstname(ds.getFirstName()); deliveryCustomer.setFirstname(ds.getFirstName());
deliveryCustomer.setLastName(ds.getLastName()); deliveryCustomer.setLastName(ds.getLastName());
deliveryCustomer.setTelephone(ds.getPhone()); deliveryCustomer.setTelephone(ds.getPhone());
deliveryCustomer.setMail(i < deliveryStationsMailState.size() ? deliveryStationsMailState.get(i) : null);
deliveryCustomer.setStreet(ds.getStreet()); deliveryCustomer.setStreet(ds.getStreet());
deliveryCustomer.setHouseNumber(ds.getHouseNumber()); deliveryCustomer.setHouseNumber(ds.getHouseNumber());
deliveryCustomer.setAddressAddition(ds.getAddressAddition()); deliveryCustomer.setAddressAddition(ds.getAddressAddition());

View File

@@ -478,22 +478,22 @@ addjob.cargo.gridcart=Gitterwagen
addjob.cargo.parcel=Paket addjob.cargo.parcel=Paket
addjob.cargo.add=Fracht hinzufügen addjob.cargo.add=Fracht hinzufügen
addjob.tasks.title=Aufgaben addjob.tasks.title=Aufgaben
addjob.tasks.template.placeholder=Template auswählen addjob.tasks.template.placeholder=Vorlage auswählen
addjob.tasks.template.save.tooltip=Als Template speichern addjob.tasks.template.save.tooltip=Als Vorlage speichern
addjob.tasks.template.save.title=Template speichern addjob.tasks.template.save.title=Vorlage speichern
addjob.tasks.template.name=Template-Name addjob.tasks.template.name=Vorlagen-Name
addjob.tasks.template.name.placeholder=Name eingeben addjob.tasks.template.name.placeholder=Name eingeben
addjob.tasks.template.name.required=Name ist erforderlich 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.save.error=Fehler beim Speichern: {0}
addjob.tasks.template.dialog.error=Fehler beim Öffnen des Dialogs: {0} addjob.tasks.template.dialog.error=Fehler beim Öffnen des Dialogs: {0}
addjob.tasks.template.no.tasks=Keine Aufgaben zum Speichern addjob.tasks.template.no.tasks=Keine Aufgaben zum Speichern
addjob.tasks.template.load.title=Template laden addjob.tasks.template.load.title=Vorlage laden
addjob.tasks.template.load.text=Möchten Sie das Template "{0}" laden? Diese Aktion ersetzt alle aktuellen Aufgaben. 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.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.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.add=Aufgabe hinzufügen
addjob.tasks.tasktype=Aufgabentyp addjob.tasks.tasktype=Aufgabentyp
addjob.tasks.tasktype.placeholder=Typ wählen addjob.tasks.tasktype.placeholder=Typ wählen