Erweiterungen

This commit is contained in:
2025-08-18 13:19:49 +02:00
parent b2b3661195
commit 2a7c0d1895
6 changed files with 386 additions and 53 deletions

View File

@@ -51,4 +51,7 @@ public class Customer
@Field("created_by") @Field("created_by")
private ObjectId createdBy; private ObjectId createdBy;
@Field("owner")
private ObjectId owner;
} }

View File

@@ -5,9 +5,12 @@ import org.bson.types.ObjectId;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List;
public interface CustomerRepository extends MongoRepository<Customer, ObjectId> { public interface CustomerRepository extends MongoRepository<Customer, ObjectId> {
// If you don't need a total row count, Slice is better than Page. // If you don't need a total row count, Slice is better than Page.
Slice<Customer> findAllBy(Pageable pageable); Slice<Customer> findAllBy(Pageable pageable);
List<Customer> findByOwner(ObjectId owner);
} }

View File

@@ -24,6 +24,7 @@ public class AddCustomerService {
// 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());
customer.setOwner(currentUser.getId());
addCustomerRepository.save(customer); addCustomerRepository.save(customer);
} }

View File

@@ -8,15 +8,18 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import de.assecutor.votianlt.security.SecurityService;
@Service @Service
@Transactional(propagation = Propagation.REQUIRES_NEW) @Transactional(propagation = Propagation.REQUIRES_NEW)
public class CustomerService { public class CustomerService {
private final CustomerRepository todoRepository; private final CustomerRepository todoRepository;
private final SecurityService securityService;
CustomerService(CustomerRepository todoRepository) { CustomerService(CustomerRepository todoRepository, SecurityService securityService) {
this.todoRepository = todoRepository; this.todoRepository = todoRepository;
this.securityService = securityService;
} }
public List<Customer> list(org.springframework.data.domain.Pageable pageable) { public List<Customer> list(org.springframework.data.domain.Pageable pageable) {
@@ -29,6 +32,11 @@ public class CustomerService {
return todoRepository.findAll(); return todoRepository.findAll();
} }
public List<Customer> findAllForCurrentOwner() {
ObjectId ownerId = securityService.getCurrentUserId();
return todoRepository.findByOwner(ownerId);
}
public Customer save(Customer customer) { public Customer save(Customer customer) {
return todoRepository.save(customer); return todoRepository.save(customer);
} }

View File

@@ -32,7 +32,10 @@ import com.vaadin.flow.theme.lumo.LumoUtility;
import de.assecutor.votianlt.model.Job; import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.model.TaskEntry; import de.assecutor.votianlt.model.TaskEntry;
import de.assecutor.votianlt.pages.service.AddJobService; import de.assecutor.votianlt.pages.service.AddJobService;
import de.assecutor.votianlt.pages.service.CustomerService;
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar; import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
import de.assecutor.votianlt.pages.service.AddCustomerService;
import de.assecutor.votianlt.model.Customer;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -49,6 +52,8 @@ import java.util.Optional;
public class AddJobView extends Main { public class AddJobView extends Main {
private final AddJobService addJobService; private final AddJobService addJobService;
private final CustomerService customerService;
private final AddCustomerService addCustomerService;
// Customer selection // Customer selection
private ComboBox<String> customerSelection; private ComboBox<String> customerSelection;
@@ -116,10 +121,14 @@ public class AddJobView extends Main {
private final Binder<Job> binder = new Binder<>(Job.class); private final Binder<Job> binder = new Binder<>(Job.class);
public AddJobView(AddJobService addJobService) { // Mapping für die Anzeige-Labels der Kunden zur Entität
private Map<String, Customer> customerLabelToEntity = new LinkedHashMap<>();
public AddJobView(AddJobService addJobService, AddCustomerService addCustomerService, CustomerService customerService) {
this.addJobService = addJobService; this.addJobService = addJobService;
this.addCustomerService = addCustomerService;
this.customerService = customerService;
initializeComponents(); initializeComponents();
populateTestData(); // Pre-populate all required fields with test data
setupLayout(); setupLayout();
setupValidation(); setupValidation();
loadDraftIfExists(); loadDraftIfExists();
@@ -128,9 +137,56 @@ public class AddJobView extends Main {
private void initializeComponents() { private void initializeComponents() {
// Customer selection // Customer selection
customerSelection = new ComboBox<>("Auftraggeber/Rechnungsempfänger"); customerSelection = new ComboBox<>("Auftraggeber/Rechnungsempfänger");
customerSelection.setItems("Kunde01 | KOTVor K01Nach");
customerSelection.setPlaceholder("Wählen Sie einen Auftraggeber aus..."); customerSelection.setPlaceholder("Wählen Sie einen Auftraggeber aus...");
customerSelection.setWidthFull(); customerSelection.setWidthFull();
// Mit Kunden des angemeldeten Benutzers befüllen und Mapping aufbauen
List<Customer> ownerCustomers = customerService.findAllForCurrentOwner();
customerLabelToEntity.clear();
for (Customer c : ownerCustomers) {
String label = (c.getCompanyName() != null && !c.getCompanyName().isBlank())
? c.getCompanyName() + " | " +
((c.getFirstname() != null ? c.getFirstname() : "") + " " + (c.getLastName() != null ? c.getLastName() : "")).trim()
: ((c.getFirstname() != null ? c.getFirstname() : "") + " " + (c.getLastName() != null ? c.getLastName() : "")).trim();
if (label.isBlank()) {
label = "Unbenannter Kunde";
}
// Bei Duplikaten Label einzigartig machen
String uniqueLabel = label;
int counter = 2;
while (customerLabelToEntity.containsKey(uniqueLabel)) {
uniqueLabel = label + " (" + counter++ + ")";
}
customerLabelToEntity.put(uniqueLabel, c);
}
customerSelection.setItems(new ArrayList<>(customerLabelToEntity.keySet()));
// Bei Auswahl eines Kunden Abholfelder befüllen
customerSelection.addValueChangeListener(ev -> {
String selected = ev.getValue();
if (selected == null) return;
Customer c = customerLabelToEntity.get(selected);
if (c == null) return;
// Firma
if (c.getCompanyName() != null) { pickupCompany.setValue(c.getCompanyName()); } else { pickupCompany.clear(); }
// Anrede (nur setzen, wenn vorhanden und zulässig)
if (c.getTitle() != null && ("Herr".equalsIgnoreCase(c.getTitle()) || "Frau".equalsIgnoreCase(c.getTitle()) || "Divers".equalsIgnoreCase(c.getTitle()))) {
pickupSalutation.setValue(c.getTitle());
} else {
pickupSalutation.clear();
}
// Namen
if (c.getFirstname() != null) { pickupFirstName.setValue(c.getFirstname()); } else { pickupFirstName.clear(); }
if (c.getLastName() != null) { pickupLastName.setValue(c.getLastName()); } else { pickupLastName.clear(); }
// Telefon
if (c.getTelephone() != null) { pickupPhone.setValue(c.getTelephone()); } else { pickupPhone.clear(); }
// Adresse
if (c.getStreet() != null) { pickupStreet.setValue(c.getStreet()); } else { pickupStreet.clear(); }
if (c.getHouseNumber() != null) { pickupHouseNumber.setValue(c.getHouseNumber()); } else { pickupHouseNumber.clear(); }
if (c.getAddressAddition() != null) { pickupAddressAddition.setValue(c.getAddressAddition()); } else { pickupAddressAddition.clear(); }
if (c.getZip() != null) { pickupZip.setValue(c.getZip()); } else { pickupZip.clear(); }
if (c.getCity() != null) { pickupCity.setValue(c.getCity()); } else { pickupCity.clear(); }
});
preloadAddressButton = new Button("Vorbelegte Adressfelder leeren"); preloadAddressButton = new Button("Vorbelegte Adressfelder leeren");
preloadAddressButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); preloadAddressButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
@@ -138,30 +194,29 @@ public class AddJobView extends Main {
// Pickup address // Pickup address
pickupCompany = new TextField("Firma"); pickupCompany = new TextField("Firma");
pickupCompany.setPlaceholder("z.B. IKEA, McDonald's, DHL..."); pickupCompany.setPlaceholder("Firmenname");
pickupCompany.setValue("Test Abholfirma GmbH");
addGooglePlacesAutocomplete(pickupCompany, 0); // Stage 0 für Pickup addGooglePlacesAutocomplete(pickupCompany, 0); // Stage 0 für Pickup
pickupSalutation = new ComboBox<>("Anrede"); pickupSalutation = new ComboBox<>("Anrede");
pickupSalutation.setItems("Herr", "Frau", "Divers"); pickupSalutation.setItems("Herr", "Frau", "Divers");
pickupSalutation.setPlaceholder("Anrede wählen..."); pickupSalutation.setPlaceholder("Anrede wählen...");
pickupFirstName = new TextField("Vorname"); pickupFirstName = new TextField("Vorname");
pickupFirstName.setPlaceholder("Max"); pickupFirstName.setPlaceholder("Vorname");
pickupFirstName.setRequiredIndicatorVisible(true); pickupFirstName.setRequiredIndicatorVisible(true);
pickupLastName = new TextField("Nachname"); pickupLastName = new TextField("Nachname");
pickupLastName.setPlaceholder("Mustermann"); pickupLastName.setPlaceholder("Nachname");
pickupLastName.setRequiredIndicatorVisible(true); pickupLastName.setRequiredIndicatorVisible(true);
pickupPhone = new TextField("Telefonnummer"); pickupPhone = new TextField("Telefonnummer");
pickupPhone.setPlaceholder("+49 123 456789"); pickupPhone.setPlaceholder("Telefonnummer");
pickupStreet = new TextField("Straße"); pickupStreet = new TextField("Straße");
pickupStreet.setPlaceholder("Musterstraße"); pickupStreet.setPlaceholder("Musterstraße");
pickupStreet.setRequiredIndicatorVisible(true); pickupStreet.setRequiredIndicatorVisible(true);
pickupHouseNumber = new TextField("Hausnummer"); pickupHouseNumber = new TextField("Hausnummer");
pickupHouseNumber.setPlaceholder("123"); pickupHouseNumber.setPlaceholder("Hausnummer");
pickupHouseNumber.setRequiredIndicatorVisible(true); pickupHouseNumber.setRequiredIndicatorVisible(true);
pickupAddressAddition = new TextField("Adresszusatz"); pickupAddressAddition = new TextField("Adresszusatz");
pickupAddressAddition.setPlaceholder("2. OG, Hinterhaus..."); pickupAddressAddition.setPlaceholder("2. OG, Hinterhaus...");
pickupZip = new TextField("Postleitzahl"); pickupZip = new TextField("Postleitzahl");
pickupZip.setPlaceholder("12345"); pickupZip.setPlaceholder("Postleitzahl");
pickupZip.setRequiredIndicatorVisible(true); pickupZip.setRequiredIndicatorVisible(true);
pickupCity = new TextField("Ort"); pickupCity = new TextField("Ort");
pickupCity.setPlaceholder("Hamburg"); pickupCity.setPlaceholder("Hamburg");
@@ -170,30 +225,29 @@ public class AddJobView extends Main {
// Delivery address // Delivery address
deliveryCompany = new TextField("Firma"); deliveryCompany = new TextField("Firma");
deliveryCompany.setPlaceholder("z.B. EDEKA, Bauhaus, Amazon..."); deliveryCompany.setPlaceholder("Firmenname");
deliveryCompany.setValue("Test Lieferfirma AG");
addGooglePlacesAutocomplete(deliveryCompany, 1); // Stage 1 für Delivery addGooglePlacesAutocomplete(deliveryCompany, 1); // Stage 1 für Delivery
deliverySalutation = new ComboBox<>("Anrede"); deliverySalutation = new ComboBox<>("Anrede");
deliverySalutation.setItems("Herr", "Frau", "Divers"); deliverySalutation.setItems("Herr", "Frau", "Divers");
deliverySalutation.setPlaceholder("Anrede wählen..."); deliverySalutation.setPlaceholder("Anrede wählen...");
deliveryFirstName = new TextField("Vorname"); deliveryFirstName = new TextField("Vorname");
deliveryFirstName.setPlaceholder("Anna"); deliveryFirstName.setPlaceholder("Vorname");
deliveryFirstName.setRequiredIndicatorVisible(true); deliveryFirstName.setRequiredIndicatorVisible(true);
deliveryLastName = new TextField("Nachname"); deliveryLastName = new TextField("Nachname");
deliveryLastName.setPlaceholder("Beispiel"); deliveryLastName.setPlaceholder("Nachname");
deliveryLastName.setRequiredIndicatorVisible(true); deliveryLastName.setRequiredIndicatorVisible(true);
deliveryPhone = new TextField("Telefonnummer"); deliveryPhone = new TextField("Telefonnummer");
deliveryPhone.setPlaceholder("+49 987 654321"); deliveryPhone.setPlaceholder("Telefonnummer");
deliveryStreet = new TextField("Straße"); deliveryStreet = new TextField("Straße");
deliveryStreet.setPlaceholder("Beispielweg"); deliveryStreet.setPlaceholder("Beispielweg");
deliveryStreet.setRequiredIndicatorVisible(true); deliveryStreet.setRequiredIndicatorVisible(true);
deliveryHouseNumber = new TextField("Hausnr"); deliveryHouseNumber = new TextField("Hausnr");
deliveryHouseNumber.setPlaceholder("456"); deliveryHouseNumber.setPlaceholder("Hausnummer");
deliveryHouseNumber.setRequiredIndicatorVisible(true); deliveryHouseNumber.setRequiredIndicatorVisible(true);
deliveryAddressAddition = new TextField("Adresszusatz"); deliveryAddressAddition = new TextField("Adresszusatz");
deliveryAddressAddition.setPlaceholder("Erdgeschoss, links..."); deliveryAddressAddition.setPlaceholder("Erdgeschoss, links...");
deliveryZip = new TextField("Postleitzahl"); deliveryZip = new TextField("Postleitzahl");
deliveryZip.setPlaceholder("54321"); deliveryZip.setPlaceholder("Postleitzahl");
deliveryZip.setRequiredIndicatorVisible(true); deliveryZip.setRequiredIndicatorVisible(true);
deliveryCity = new TextField("Ort"); deliveryCity = new TextField("Ort");
deliveryCity.setPlaceholder("Berlin"); deliveryCity.setPlaceholder("Berlin");
@@ -208,7 +262,7 @@ public class AddJobView extends Main {
// Price field // Price field
price = new TextField("Preis"); price = new TextField("Preis");
price.setPlaceholder("z.B. 150.00"); price.setPlaceholder("Betrag eingeben");
price.setRequiredIndicatorVisible(true); price.setRequiredIndicatorVisible(true);
// Erzwinge Komma als Dezimaltrennzeichen: ersetze Punkt beim Tippen // Erzwinge Komma als Dezimaltrennzeichen: ersetze Punkt beim Tippen
@@ -230,32 +284,7 @@ public class AddJobView extends Main {
submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
} }
private void populateTestData() { // Testdaten entfernt
// Populate pickup address required fields
pickupCompany.setValue("Firma 1");
pickupFirstName.setValue("Max");
pickupLastName.setValue("Mustermann");
pickupStreet.setValue("Musterstraße");
pickupHouseNumber.setValue("123");
pickupZip.setValue("20095");
pickupCity.setValue("Hamburg");
// Populate delivery address required fields
deliveryFirstName.setValue("Anna");
deliveryLastName.setValue("Beispiel");
deliveryStreet.setValue("Beispielweg");
deliveryHouseNumber.setValue("456");
deliveryZip.setValue("10115");
deliveryCity.setValue("Berlin");
// Populate price field
price.setValue("150.00");
// Populate date fields with current date + 1 day for pickup, +2 days for delivery
java.time.LocalDate today = java.time.LocalDate.now();
pickupDate.setValue(today.plusDays(1));
deliveryDate.setValue(today.plusDays(2));
}
private void setupLayout() { private void setupLayout() {
setSizeFull(); setSizeFull();
@@ -805,16 +834,44 @@ public class AddJobView extends Main {
cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
} }
// NEU: Kunden anlegen, wenn Checkboxen aktiviert
if (savePickupAddress.getValue()) {
Customer pickupCustomer = new Customer();
pickupCustomer.setCompanyName(pickupCompany.getValue());
pickupCustomer.setTitle(pickupSalutation.getValue());
pickupCustomer.setFirstname(pickupFirstName.getValue());
pickupCustomer.setLastName(pickupLastName.getValue());
pickupCustomer.setTelephone(pickupPhone.getValue());
pickupCustomer.setStreet(pickupStreet.getValue());
pickupCustomer.setHouseNumber(pickupHouseNumber.getValue());
pickupCustomer.setAddressAddition(pickupAddressAddition.getValue());
pickupCustomer.setZip(pickupZip.getValue());
pickupCustomer.setCity(pickupCity.getValue());
addCustomerService.addCustomer(pickupCustomer);
}
if (saveDeliveryAddress.getValue()) {
Customer deliveryCustomer = new Customer();
deliveryCustomer.setCompanyName(deliveryCompany.getValue());
deliveryCustomer.setTitle(deliverySalutation.getValue());
deliveryCustomer.setFirstname(deliveryFirstName.getValue());
deliveryCustomer.setLastName(deliveryLastName.getValue());
deliveryCustomer.setTelephone(deliveryPhone.getValue());
deliveryCustomer.setStreet(deliveryStreet.getValue());
deliveryCustomer.setHouseNumber(deliveryHouseNumber.getValue());
deliveryCustomer.setAddressAddition(deliveryAddressAddition.getValue());
deliveryCustomer.setZip(deliveryZip.getValue());
deliveryCustomer.setCity(deliveryCity.getValue());
addCustomerService.addCustomer(deliveryCustomer);
}
// All validations passed, save the job with cargo items and tasks (tasks may be empty) // All validations passed, save the job with cargo items and tasks (tasks may be empty)
Job savedJob = addJobService.addJobWithCargo(job, cargoFilled, tasksState); Job savedJob = addJobService.addJobWithCargo(job, cargoFilled, tasksState);
// Erfolgsmeldung anzeigen // Erfolgsmeldung und Navigation zur Zusammenfassung
Notification successNotification = Notification.show( Notification successNotification = Notification.show(
"Auftrag erfolgreich erstellt! Auftragsnummer: " + savedJob.getJobNumber()); "Auftrag erfolgreich erstellt! Auftragsnummer: " + savedJob.getJobNumber());
successNotification.setDuration(5000); successNotification.setDuration(2000);
getUI().ifPresent(ui -> ui.navigate(JobSummaryView.class, savedJob.getId().toHexString()));
// Formular zurücksetzen
clearForm();
} else { } else {
// Validation failed, show error message // Validation failed, show error message
Notification errorNotification = Notification.show( Notification errorNotification = Notification.show(
@@ -824,15 +881,19 @@ public class AddJobView extends Main {
} catch (Exception e) { } catch (Exception e) {
// Other errors // Other errors
// Reset cargo error // Reset cargo error
if (cargoError != null) cargoError.setVisible(false); if (cargoError != null) cargoError.setVisible(false);
if (cargoAreaContainer != null) cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); if (cargoAreaContainer != null) cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
Notification errorNotification = Notification.show( Notification errorNotification = Notification.show(
"Fehler beim Erstellen des Auftrags: " + e.getMessage()); "Fehler beim Erstellen des Auftrags: " + e.getMessage());
errorNotification.setDuration(5000); errorNotification.setDuration(5000);
} }
} }
// showSummary entfernt; Zusammenfassung als eigene Route
// Zusammenfassungs-Helfer entfernt (Route übernimmt Darstellung)
/** /**
cargoItemsState.clear(); cargoItemsState.clear();
cargoList.removeAll(); cargoList.removeAll();

View File

@@ -0,0 +1,257 @@
package de.assecutor.votianlt.pages.view;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.lumo.LumoUtility;
import de.assecutor.votianlt.model.CargoItem;
import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.model.TaskEntry;
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
import de.assecutor.votianlt.repository.CargoItemRepository;
import de.assecutor.votianlt.repository.JobRepository;
import de.assecutor.votianlt.repository.TaskRepository;
import jakarta.annotation.security.RolesAllowed;
import org.bson.types.ObjectId;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@Route(value = "job_summary", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@PageTitle("Zusammenfassung")
@RolesAllowed("USER")
public class JobSummaryView extends Main implements HasUrlParameter<String> {
private final JobRepository jobRepository;
private final CargoItemRepository cargoItemRepository;
private final TaskRepository taskRepository;
private VerticalLayout content;
public JobSummaryView(JobRepository jobRepository,
CargoItemRepository cargoItemRepository,
TaskRepository taskRepository) {
this.jobRepository = jobRepository;
this.cargoItemRepository = cargoItemRepository;
this.taskRepository = taskRepository;
setSizeFull();
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX,
LumoUtility.FlexDirection.COLUMN, LumoUtility.Padding.MEDIUM,
LumoUtility.Gap.SMALL);
add(new ViewToolbar("Zusammenfassung"));
content = new VerticalLayout();
content.setSpacing(true);
content.setPadding(true);
content.setWidthFull();
add(content);
}
@Override
public void setParameter(BeforeEvent event, String parameter) {
if (parameter == null || parameter.isBlank()) return;
ObjectId jobId;
try { jobId = new ObjectId(parameter); } catch (Exception e) { return; }
Job job = jobRepository.findById(jobId).orElse(null);
if (job == null) return;
List<CargoItem> cargo = cargoItemRepository.findByJobId(jobId);
List<TaskEntry> tasks = taskRepository.findByJobId(jobId);
render(job, cargo, tasks);
}
private void render(Job job, List<CargoItem> cargoItems, List<TaskEntry> tasks) {
content.removeAll();
// Kopfzeile: Abholung/Lieferung
HorizontalLayout topRow = new HorizontalLayout();
topRow.setWidthFull();
topRow.setSpacing(true);
VerticalLayout pickupBox = borderedBox();
pickupBox.add(new H3("Abholung " + (job.getPickupDate() != null ? formatLocalDate(job.getPickupDate()) : "")));
pickupBox.add(new Span(valueOrEmpty(job.getPickupCompany())));
pickupBox.add(new Span(valueOrEmpty(job.getPickupSalutation())
+ (job.getPickupSalutation() != null ? " " : "")
+ valueOrEmpty(job.getPickupFirstName())
+ (job.getPickupFirstName() != null ? " " : "")
+ valueOrEmpty(job.getPickupLastName())));
pickupBox.add(new Span(concatAddress(job.getPickupStreet(), job.getPickupHouseNumber())));
pickupBox.add(new Span(concatZipCity(job.getPickupZip(), job.getPickupCity())));
VerticalLayout deliveryBox = borderedBox();
deliveryBox.add(new H3("Lieferung " + (job.getDeliveryDate() != null ? formatLocalDate(job.getDeliveryDate()) : "")));
deliveryBox.add(new Span(valueOrEmpty(job.getDeliveryCompany())));
deliveryBox.add(new Span(valueOrEmpty(job.getDeliverySalutation())
+ (job.getDeliverySalutation() != null ? " " : "")
+ valueOrEmpty(job.getDeliveryFirstName())
+ (job.getDeliveryFirstName() != null ? " " : "")
+ valueOrEmpty(job.getDeliveryLastName())));
deliveryBox.add(new Span(concatAddress(job.getDeliveryStreet(), job.getDeliveryHouseNumber())));
deliveryBox.add(new Span(concatZipCity(job.getDeliveryZip(), job.getDeliveryCity())));
pickupBox.setWidth("50%");
deliveryBox.setWidth("50%");
topRow.add(pickupBox, deliveryBox);
content.add(topRow);
// Aufgaben
VerticalLayout tasksBox = borderedBox();
tasksBox.add(new H3("Zu quittierende Aufgaben"));
if (tasks == null || tasks.stream().filter(Objects::nonNull).map(TaskEntry::getText).filter(t -> t != null && !t.isBlank()).findAny().isEmpty()) {
tasksBox.add(new Span("Keine Aufgaben"));
} else {
tasks.stream().filter(Objects::nonNull).map(TaskEntry::getText).filter(t -> t != null && !t.isBlank()).forEach(t -> tasksBox.add(new Span("" + t)));
}
content.add(tasksBox);
// Fracht und weitere Infos
HorizontalLayout midRow = new HorizontalLayout();
midRow.setWidthFull();
midRow.setSpacing(true);
VerticalLayout cargoBox = borderedBox();
cargoBox.add(new H3("Zu transportierende Fracht"));
if (cargoItems == null || cargoItems.isEmpty()) {
cargoBox.add(new Span("Keine Frachtangaben"));
} else {
for (CargoItem ci : cargoItems) {
if (ci == null) continue;
String desc = ci.getDescription();
Integer qty = ci.getQuantity();
String dims = dimString(ci);
String weight = ci.getWeightKg() != null ? ci.getWeightKg() + " kg" : "";
String line = (qty != null ? qty + " x " : "") + (desc != null ? desc : "") + (dims.isBlank() ? "" : " " + dims) + (weight.isBlank() ? "" : " " + weight);
if (!line.isBlank()) cargoBox.add(new Span(line));
}
}
VerticalLayout infoBox = borderedBox();
infoBox.add(new H3("Weitere Informationen"));
infoBox.add(new Span("Preis: " + (job.getPrice() != null ? formatPrice(job.getPrice()) : "-")));
if (job.getRemark() != null && !job.getRemark().isBlank()) {
infoBox.add(new Span("Bemerkung: " + job.getRemark()));
}
if (job.isDigitalProcessing()) {
infoBox.add(new Span("Digitale Abwicklung per App: aktiviert"));
}
if (job.getAppUser() != null && !job.getAppUser().isBlank()) {
infoBox.add(new Span("App-Nutzer: " + job.getAppUser()));
}
cargoBox.setWidth("50%");
infoBox.setWidth("50%");
midRow.add(cargoBox, infoBox);
content.add(midRow);
// Google Maps Karte mit Route
addRouteMap(job);
}
private VerticalLayout borderedBox() {
VerticalLayout box = new VerticalLayout();
box.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
box.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
box.getStyle().set("background-color", "var(--lumo-base-color)");
box.setPadding(true);
box.setSpacing(false);
return box;
}
private String formatLocalDate(java.time.LocalDate date) {
try {
java.time.format.DateTimeFormatter fmt = java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy").withLocale(Locale.GERMANY);
return date.format(fmt);
} catch (Exception e) { return ""; }
}
private String valueOrEmpty(String v) { return v == null ? "" : v; }
private String concatAddress(String street, String house) {
String s = valueOrEmpty(street);
String h = valueOrEmpty(house);
return (s + (h.isBlank() ? "" : " " + h)).trim();
}
private String concatZipCity(String zip, String city) {
String z = valueOrEmpty(zip);
String c = valueOrEmpty(city);
if (!z.isBlank() && !c.isBlank()) return z + " " + c;
return (z + " " + c).trim();
}
private String dimString(CargoItem ci) {
String len = ci.getLengthMm() != null ? ci.getLengthMm().intValue() + " mm" : "";
String wid = ci.getWidthMm() != null ? ci.getWidthMm().intValue() + " mm" : "";
String hei = ci.getHeightMm() != null ? ci.getHeightMm().intValue() + " mm" : "";
String combined = String.join(" x ", java.util.stream.Stream.of(len, wid, hei)
.filter(s -> s != null && !s.isBlank()).toList());
return combined.isBlank() ? "" : combined;
}
private String formatPrice(java.math.BigDecimal price) {
java.text.NumberFormat nf = java.text.NumberFormat.getCurrencyInstance(Locale.GERMANY);
return nf.format(price);
}
private void addRouteMap(Job job) {
// Baue Adress-Strings
String origin = (concatAddress(job.getPickupStreet(), job.getPickupHouseNumber()) + ", " + concatZipCity(job.getPickupZip(), job.getPickupCity())).trim();
String destination = (concatAddress(job.getDeliveryStreet(), job.getDeliveryHouseNumber()) + ", " + concatZipCity(job.getDeliveryZip(), job.getDeliveryCity())).trim();
if (origin.isBlank() || destination.isBlank()) {
// Wenn nicht genug Daten vorhanden sind, Karte nicht anzeigen
return;
}
Div map = new Div();
map.setWidthFull();
map.setHeight("520px");
map.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
map.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
content.add(map);
String js = (
"(function(){" +
" var host = $0;" +
" function init(){" +
" var map = new google.maps.Map(host, {center: {lat: 51.163, lng: 10.447}, zoom: 6});" +
" var ds = new google.maps.DirectionsService();" +
" var dr = new google.maps.DirectionsRenderer({map: map});" +
" ds.route({" +
" origin: '" + escapeJs(origin) + "'," +
" destination: '" + escapeJs(destination) + "'," +
" travelMode: google.maps.TravelMode.DRIVING" +
" }, function(res, status){ if(status==='OK'){ dr.setDirections(res); } });" +
" }" +
" if (!(window.google && window.google.maps)) {" +
" var s=document.createElement('script');" +
" s.src='https://maps.googleapis.com/maps/api/js?key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE&libraries=places';" +
" s.onload=init; document.head.appendChild(s);" +
" } else { init(); }" +
"})();"
);
// Ausführen im UI-Kontext
map.getElement().executeJs(js, map.getElement());
}
// Hilfsfunktion zum einfachen Escapen von JS-Zeichen in Strings
private String escapeJs(String s) {
if (s == null) return "";
return s.replace("\\", "\\\\").replace("'", "\\'").replace("\n", " ").replace("\r", " ");
}
}