From aa28a7044ccf6b940fd5d702b896c0a241835bad Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Tue, 12 Aug 2025 21:43:51 +0200 Subject: [PATCH] Erweiterungen --- .../java/de/assecutor/votianlt/model/Job.java | 72 +++ .../assecutor/votianlt/model/JobStatus.java | 30 + .../pages/add_job/service/AddJobService.java | 238 +++++++- .../pages/add_job/ui/view/AddJobView.java | 532 ++++++++++++++++-- .../votianlt/repository/JobRepository.java | 87 +++ 5 files changed, 898 insertions(+), 61 deletions(-) create mode 100644 src/main/java/de/assecutor/votianlt/model/JobStatus.java create mode 100644 src/main/java/de/assecutor/votianlt/repository/JobRepository.java diff --git a/src/main/java/de/assecutor/votianlt/model/Job.java b/src/main/java/de/assecutor/votianlt/model/Job.java index a371aff..06fd1a3 100644 --- a/src/main/java/de/assecutor/votianlt/model/Job.java +++ b/src/main/java/de/assecutor/votianlt/model/Job.java @@ -2,41 +2,113 @@ package de.assecutor.votianlt.model; import lombok.Data; import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +import java.time.LocalDateTime; @Data +@Document(collection = "jobs") public class Job { + @Id private ObjectId id; + // Metadaten + @Field("job_number") + private String jobNumber; // Eindeutige Auftragsnummer + + @Field("status") + private JobStatus status = JobStatus.CREATED; // Status des Auftrags + + @Field("created_at") + private LocalDateTime createdAt; + + @Field("updated_at") + private LocalDateTime updatedAt; + + @Field("created_by") + private String createdBy; // Benutzer, der den Auftrag erstellt hat + + @Field("is_draft") + private boolean isDraft = false; // Kennzeichnet Entwürfe + // Auftraggeber/Rechnungsempfänger + @Field("customer_selection") private String customerSelection; // Kunde01 | KOTVor K01Nach // Abholadresse + @Field("pickup_company") private String pickupCompany; + + @Field("pickup_salutation") private String pickupSalutation; + + @Field("pickup_first_name") private String pickupFirstName; + + @Field("pickup_last_name") private String pickupLastName; + + @Field("pickup_phone") private String pickupPhone; + + @Field("pickup_street") private String pickupStreet; + + @Field("pickup_house_number") private String pickupHouseNumber; + + @Field("pickup_address_addition") private String pickupAddressAddition; + + @Field("pickup_zip") private String pickupZip; + + @Field("pickup_city") private String pickupCity; + + @Field("save_pickup_address") private boolean savePickupAddress; // Lieferadresse + @Field("delivery_company") private String deliveryCompany; + + @Field("delivery_salutation") private String deliverySalutation; + + @Field("delivery_first_name") private String deliveryFirstName; + + @Field("delivery_last_name") private String deliveryLastName; + + @Field("delivery_phone") private String deliveryPhone; + + @Field("delivery_street") private String deliveryStreet; + + @Field("delivery_house_number") private String deliveryHouseNumber; + + @Field("delivery_address_addition") private String deliveryAddressAddition; + + @Field("delivery_zip") private String deliveryZip; + + @Field("delivery_city") private String deliveryCity; + + @Field("save_delivery_address") private boolean saveDeliveryAddress; // Digitale Abwicklung per App + @Field("digital_processing") private boolean digitalProcessing; + + @Field("app_user") private String appUser; } diff --git a/src/main/java/de/assecutor/votianlt/model/JobStatus.java b/src/main/java/de/assecutor/votianlt/model/JobStatus.java new file mode 100644 index 0000000..f1fc674 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/model/JobStatus.java @@ -0,0 +1,30 @@ +package de.assecutor.votianlt.model; + +/** + * Status-Enum für Aufträge + */ +public enum JobStatus { + CREATED("Erstellt"), + IN_PROGRESS("In Bearbeitung"), + PICKUP_SCHEDULED("Abholung geplant"), + PICKED_UP("Abgeholt"), + IN_TRANSIT("Unterwegs"), + DELIVERED("Zugestellt"), + COMPLETED("Abgeschlossen"), + CANCELLED("Storniert"); + + private final String displayName; + + JobStatus(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + @Override + public String toString() { + return displayName; + } +} diff --git a/src/main/java/de/assecutor/votianlt/pages/add_job/service/AddJobService.java b/src/main/java/de/assecutor/votianlt/pages/add_job/service/AddJobService.java index f99bba4..898e185 100644 --- a/src/main/java/de/assecutor/votianlt/pages/add_job/service/AddJobService.java +++ b/src/main/java/de/assecutor/votianlt/pages/add_job/service/AddJobService.java @@ -1,13 +1,245 @@ package de.assecutor.votianlt.pages.add_job.service; import de.assecutor.votianlt.model.Job; +import de.assecutor.votianlt.model.JobStatus; +import de.assecutor.votianlt.repository.JobRepository; +import de.assecutor.votianlt.security.SecurityService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Optional; + @Service +@RequiredArgsConstructor +@Slf4j public class AddJobService { - public void addJob(Job job) { - // TODO: Implement job persistence logic - System.out.println("Job would be saved: " + job.toString()); + private final JobRepository jobRepository; + private final SecurityService securityService; + + /** + * Speichert einen neuen Auftrag in der MongoDB + */ + public Job addJob(Job job) { + try { + // Metadaten setzen + LocalDateTime now = LocalDateTime.now(); + job.setCreatedAt(now); + job.setUpdatedAt(now); + job.setStatus(JobStatus.CREATED); + job.setCreatedBy(securityService.getCurrentUsername()); + + // Auftragsnummer generieren, falls nicht vorhanden + if (job.getJobNumber() == null || job.getJobNumber().isEmpty()) { + job.setJobNumber(generateJobNumber()); + } + + // Auftrag speichern + Job savedJob = jobRepository.save(job); + log.info("Auftrag erfolgreich gespeichert: {}", savedJob.getJobNumber()); + + return savedJob; + + } catch (Exception e) { + log.error("Fehler beim Speichern des Auftrags: {}", e.getMessage(), e); + throw new RuntimeException("Auftrag konnte nicht gespeichert werden: " + e.getMessage()); + } + } + + /** + * Aktualisiert einen bestehenden Auftrag + */ + public Job updateJob(Job job) { + try { + job.setUpdatedAt(LocalDateTime.now()); + Job updatedJob = jobRepository.save(job); + log.info("Auftrag erfolgreich aktualisiert: {}", updatedJob.getJobNumber()); + return updatedJob; + } catch (Exception e) { + log.error("Fehler beim Aktualisieren des Auftrags: {}", e.getMessage(), e); + throw new RuntimeException("Auftrag konnte nicht aktualisiert werden: " + e.getMessage()); + } + } + + /** + * Findet einen Auftrag anhand der ID + */ + public Optional findById(String id) { + try { + return jobRepository.findById(new org.bson.types.ObjectId(id)); + } catch (Exception e) { + log.error("Fehler beim Suchen des Auftrags mit ID {}: {}", id, e.getMessage()); + return Optional.empty(); + } + } + + /** + * Findet einen Auftrag anhand der Auftragsnummer + */ + public Optional findByJobNumber(String jobNumber) { + return jobRepository.findByJobNumber(jobNumber); + } + + /** + * Findet alle Aufträge eines Benutzers + */ + public List findJobsByUser(String username) { + return jobRepository.findByCreatedBy(username); + } + + /** + * Findet alle Aufträge mit einem bestimmten Status + */ + public List findJobsByStatus(JobStatus status) { + return jobRepository.findByStatus(status); + } + + /** + * Löscht einen Auftrag + */ + public void deleteJob(String id) { + try { + jobRepository.deleteById(new org.bson.types.ObjectId(id)); + log.info("Auftrag mit ID {} erfolgreich gelöscht", id); + } catch (Exception e) { + log.error("Fehler beim Löschen des Auftrags mit ID {}: {}", id, e.getMessage()); + throw new RuntimeException("Auftrag konnte nicht gelöscht werden: " + e.getMessage()); + } + } + + /** + * Generiert eine eindeutige Auftragsnummer + */ + private String generateJobNumber() { + String prefix = "JOB"; + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); + + // Zähle Aufträge des aktuellen Tages + String todayPrefix = prefix + timestamp; + long todayCount = jobRepository.findAll().stream() + .filter(job -> job.getJobNumber() != null && job.getJobNumber().startsWith(todayPrefix)) + .count(); + + // Generiere neue Nummer + String jobNumber; + do { + todayCount++; + jobNumber = String.format("%s%s%03d", prefix, timestamp, todayCount); + } while (jobRepository.existsByJobNumber(jobNumber)); + + return jobNumber; + } + + /** + * Zählt alle Aufträge + */ + public long countAllJobs() { + return jobRepository.count(); + } + + /** + * Zählt Aufträge nach Status + */ + public long countJobsByStatus(JobStatus status) { + return jobRepository.countByStatus(status); + } + + /** + * Speichert einen Auftrag als Entwurf (für automatisches Speichern) + */ + public Job saveDraft(Job job) { + try { + // Prüfen ob bereits ein Entwurf für diesen Benutzer existiert + String currentUser = securityService.getCurrentUsername(); + List existingDrafts = jobRepository.findByCreatedByAndIsDraftTrue(currentUser); + + Job draftJob; + if (!existingDrafts.isEmpty()) { + // Bestehenden Entwurf aktualisieren + draftJob = existingDrafts.get(0); + updateJobFromForm(draftJob, job); + draftJob.setUpdatedAt(LocalDateTime.now()); + } else { + // Neuen Entwurf erstellen + draftJob = job; + LocalDateTime now = LocalDateTime.now(); + draftJob.setCreatedAt(now); + draftJob.setUpdatedAt(now); + draftJob.setStatus(JobStatus.CREATED); + draftJob.setCreatedBy(currentUser); + draftJob.setDraft(true); + + // Spezielle Entwurfs-Auftragsnummer + draftJob.setJobNumber("DRAFT_" + currentUser.replace("@", "_") + "_" + + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"))); + } + + Job savedDraft = jobRepository.save(draftJob); + log.info("Entwurf automatisch gespeichert für Benutzer: {}", currentUser); + + return savedDraft; + + } catch (Exception e) { + log.error("Fehler beim Speichern des Entwurfs: {}", e.getMessage(), e); + throw new RuntimeException("Entwurf konnte nicht gespeichert werden: " + e.getMessage()); + } + } + + /** + * Aktualisiert einen bestehenden Job mit neuen Formulardaten + */ + private void updateJobFromForm(Job existingJob, Job formJob) { + existingJob.setCustomerSelection(formJob.getCustomerSelection()); + + // Pickup address + existingJob.setPickupCompany(formJob.getPickupCompany()); + existingJob.setPickupSalutation(formJob.getPickupSalutation()); + existingJob.setPickupFirstName(formJob.getPickupFirstName()); + existingJob.setPickupLastName(formJob.getPickupLastName()); + existingJob.setPickupPhone(formJob.getPickupPhone()); + existingJob.setPickupStreet(formJob.getPickupStreet()); + existingJob.setPickupHouseNumber(formJob.getPickupHouseNumber()); + existingJob.setPickupAddressAddition(formJob.getPickupAddressAddition()); + existingJob.setPickupZip(formJob.getPickupZip()); + existingJob.setPickupCity(formJob.getPickupCity()); + existingJob.setSavePickupAddress(formJob.isSavePickupAddress()); + + // Delivery address + existingJob.setDeliveryCompany(formJob.getDeliveryCompany()); + existingJob.setDeliverySalutation(formJob.getDeliverySalutation()); + existingJob.setDeliveryFirstName(formJob.getDeliveryFirstName()); + existingJob.setDeliveryLastName(formJob.getDeliveryLastName()); + existingJob.setDeliveryPhone(formJob.getDeliveryPhone()); + existingJob.setDeliveryStreet(formJob.getDeliveryStreet()); + existingJob.setDeliveryHouseNumber(formJob.getDeliveryHouseNumber()); + existingJob.setDeliveryAddressAddition(formJob.getDeliveryAddressAddition()); + existingJob.setDeliveryZip(formJob.getDeliveryZip()); + existingJob.setDeliveryCity(formJob.getDeliveryCity()); + existingJob.setSaveDeliveryAddress(formJob.isSaveDeliveryAddress()); + + // Digital processing + existingJob.setDigitalProcessing(formJob.isDigitalProcessing()); + existingJob.setAppUser(formJob.getAppUser()); + } + + /** + * Findet den aktuellen Entwurf eines Benutzers + */ + public Optional findCurrentDraft(String username) { + List drafts = jobRepository.findByCreatedByAndIsDraftTrue(username); + return drafts.isEmpty() ? Optional.empty() : Optional.of(drafts.get(0)); + } + + /** + * Löscht alle Entwürfe eines Benutzers + */ + public void deleteUserDrafts(String username) { + List drafts = jobRepository.findByCreatedByAndIsDraftTrue(username); + jobRepository.deleteAll(drafts); + log.info("Entwürfe für Benutzer {} gelöscht", username); } } diff --git a/src/main/java/de/assecutor/votianlt/pages/add_job/ui/view/AddJobView.java b/src/main/java/de/assecutor/votianlt/pages/add_job/ui/view/AddJobView.java index 1d93983..2dd9235 100644 --- a/src/main/java/de/assecutor/votianlt/pages/add_job/ui/view/AddJobView.java +++ b/src/main/java/de/assecutor/votianlt/pages/add_job/ui/view/AddJobView.java @@ -4,16 +4,20 @@ import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.checkbox.Checkbox; import com.vaadin.flow.component.combobox.ComboBox; + import com.vaadin.flow.component.html.Div; 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.icon.Icon; import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.component.ClientCallable; +import com.vaadin.flow.component.UI; import com.vaadin.flow.data.binder.Binder; import com.vaadin.flow.data.binder.ValidationException; import com.vaadin.flow.router.Menu; @@ -25,11 +29,14 @@ import de.assecutor.votianlt.pages.add_job.service.AddJobService; import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar; import jakarta.annotation.security.RolesAllowed; +import lombok.extern.slf4j.Slf4j; +import java.util.Optional; @Route(value = "add_job", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class) @PageTitle("Neuen Auftrag anlegen") @Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neuen Auftrag anlegen") @RolesAllowed("USER") +@Slf4j public class AddJobView extends Main { private final AddJobService addJobService; @@ -76,83 +83,94 @@ public class AddJobView extends Main { private final Binder binder = new Binder<>(Job.class); + + public AddJobView(AddJobService addJobService) { this.addJobService = addJobService; initializeComponents(); setupLayout(); setupValidation(); + loadDraftIfExists(); } private void initializeComponents() { // Customer selection customerSelection = new ComboBox<>("Auftraggeber/Rechnungsempfänger"); customerSelection.setItems("Kunde01 | KOTVor K01Nach"); - customerSelection.setValue("Kunde01 | KOTVor K01Nach"); + customerSelection.setPlaceholder("Wählen Sie einen Auftraggeber aus..."); customerSelection.setWidthFull(); preloadAddressButton = new Button("Vorbelegte Adressfelder leeren"); preloadAddressButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); - preloadAddressButton.setIcon(new Icon(VaadinIcon.QUESTION_CIRCLE)); - - // Required fields notice - requiredFieldsNotice = new Span("Die mit (*) gekennzeichneten Felder sind Pflichtfelder."); - requiredFieldsNotice.getStyle().set("color", "red"); - requiredFieldsNotice.getStyle().set("font-size", "14px"); + preloadAddressButton.addClickListener(event -> clearAllFields()); // Pickup address pickupCompany = new TextField("Firma"); - pickupCompany.setValue("Kunde01"); + pickupCompany.setPlaceholder("z.B. IKEA, McDonald's, DHL..."); + addGooglePlacesAutocomplete(pickupCompany, 0); // Stage 0 für Pickup pickupSalutation = new ComboBox<>("Anrede"); pickupSalutation.setItems("Herr", "Frau", "Divers"); - pickupFirstName = new TextField("Vorname*"); - pickupFirstName.setValue("K01Vor"); + pickupSalutation.setPlaceholder("Anrede wählen..."); + pickupFirstName = new TextField("Vorname"); + pickupFirstName.setPlaceholder("Max"); pickupFirstName.setRequiredIndicatorVisible(true); - pickupLastName = new TextField("Nachname*"); - pickupLastName.setValue("K01Nach"); + pickupLastName = new TextField("Nachname"); + pickupLastName.setPlaceholder("Mustermann"); pickupLastName.setRequiredIndicatorVisible(true); pickupPhone = new TextField("Telefonnummer"); - pickupPhone.setValue("01"); - pickupStreet = new TextField("Straße*"); - pickupStreet.setValue("Ottensener Str."); + pickupPhone.setPlaceholder("+49 123 456789"); + pickupStreet = new TextField("Straße"); + pickupStreet.setPlaceholder("Musterstraße"); pickupStreet.setRequiredIndicatorVisible(true); - pickupHouseNumber = new TextField("Hausnr*"); - pickupHouseNumber.setValue("8"); + pickupHouseNumber = new TextField("Hausnummer"); + pickupHouseNumber.setPlaceholder("123"); pickupHouseNumber.setRequiredIndicatorVisible(true); pickupAddressAddition = new TextField("Adresszusatz"); - pickupZip = new TextField("Postleitzahl*"); - pickupZip.setValue("22525"); + pickupAddressAddition.setPlaceholder("2. OG, Hinterhaus..."); + pickupZip = new TextField("Postleitzahl"); + pickupZip.setPlaceholder("12345"); pickupZip.setRequiredIndicatorVisible(true); - pickupCity = new TextField("Ort*"); - pickupCity.setValue("Hamburg"); + pickupCity = new TextField("Ort"); + pickupCity.setPlaceholder("Hamburg"); pickupCity.setRequiredIndicatorVisible(true); savePickupAddress = new Checkbox("Die Adresse für zukünftige Aufträge speichern."); // Delivery address deliveryCompany = new TextField("Firma"); + deliveryCompany.setPlaceholder("z.B. EDEKA, Bauhaus, Amazon..."); + addGooglePlacesAutocomplete(deliveryCompany, 1); // Stage 1 für Delivery deliverySalutation = new ComboBox<>("Anrede"); deliverySalutation.setItems("Herr", "Frau", "Divers"); - deliveryFirstName = new TextField("Vorname*"); + deliverySalutation.setPlaceholder("Anrede wählen..."); + deliveryFirstName = new TextField("Vorname"); + deliveryFirstName.setPlaceholder("Anna"); deliveryFirstName.setRequiredIndicatorVisible(true); - deliveryLastName = new TextField("Nachname*"); + deliveryLastName = new TextField("Nachname"); + deliveryLastName.setPlaceholder("Beispiel"); deliveryLastName.setRequiredIndicatorVisible(true); deliveryPhone = new TextField("Telefonnummer"); - deliveryStreet = new TextField("Straße*"); + deliveryPhone.setPlaceholder("+49 987 654321"); + deliveryStreet = new TextField("Straße"); + deliveryStreet.setPlaceholder("Beispielweg"); deliveryStreet.setRequiredIndicatorVisible(true); - deliveryHouseNumber = new TextField("Hausnr*"); + deliveryHouseNumber = new TextField("Hausnr"); + deliveryHouseNumber.setPlaceholder("456"); deliveryHouseNumber.setRequiredIndicatorVisible(true); deliveryAddressAddition = new TextField("Adresszusatz"); - deliveryZip = new TextField("Postleitzahl*"); + deliveryAddressAddition.setPlaceholder("Erdgeschoss, links..."); + deliveryZip = new TextField("Postleitzahl"); + deliveryZip.setPlaceholder("54321"); deliveryZip.setRequiredIndicatorVisible(true); - deliveryCity = new TextField("Ort*"); + deliveryCity = new TextField("Ort"); + deliveryCity.setPlaceholder("Berlin"); deliveryCity.setRequiredIndicatorVisible(true); saveDeliveryAddress = new Checkbox("Die Adresse für zukünftige Aufträge speichern."); - saveDeliveryAddress.setValue(true); - + // Digital processing digitalProcessing = new Checkbox("Digitale Abwicklung per App"); - digitalProcessing.setValue(true); appUser = new ComboBox<>("App-Nutzer"); appUser.setItems("App-Nutzer"); + appUser.setPlaceholder("App-Nutzer auswählen..."); // Submit button submitButton = new Button("Auftrag anlegen", event -> submit()); @@ -176,8 +194,7 @@ public class AddJobView extends Main { preloadAddressButton.setWidth("30%"); add(customerLayout); - add(requiredFieldsNotice); - + // Main content layout with two equal columns (50% each) HorizontalLayout mainLayout = new HorizontalLayout(); mainLayout.setWidthFull(); @@ -185,24 +202,14 @@ public class AddJobView extends Main { mainLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.START); // Left column (50%) - Pickup address section - VerticalLayout leftColumn = createPickupSection(); - leftColumn.setWidth("50%"); + VerticalLayout pickupSection = createPickupSection(); + pickupSection.setWidth("50%"); // Right column (50%) - Delivery address section - VerticalLayout rightColumn = createDeliverySection(); - rightColumn.setWidth("50%"); + VerticalLayout deliverySection = createDeliverySection(); + deliverySection.setWidth("50%"); - // Add copy button to the right column at the top - Button copyButton = new Button("Abholadresse kopieren"); - copyButton.setIcon(new Icon(VaadinIcon.ARROW_RIGHT)); - copyButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); - copyButton.addClickListener(e -> copyPickupToDelivery()); - copyButton.getStyle().set("margin-bottom", "var(--lumo-space-m)"); - - // Insert copy button at the beginning of right column - rightColumn.addComponentAsFirst(copyButton); - - mainLayout.add(leftColumn, rightColumn); + mainLayout.add(pickupSection, deliverySection); add(mainLayout); @@ -222,17 +229,35 @@ public class AddJobView extends Main { private VerticalLayout createPickupSection() { VerticalLayout section = new VerticalLayout(); section.setSpacing(true); - section.setPadding(false); + section.setPadding(true); + section.setWidthFull(); + + // Hellgrauer Rahmen hinzufügen + section.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); + section.getStyle().set("border-radius", "var(--lumo-border-radius-m)"); + section.getStyle().set("background-color", "var(--lumo-base-color)"); H3 title = new H3("Abholadresse"); title.getStyle().set("margin", "0"); - title.getStyle().set("display", "flex"); - title.getStyle().set("align-items", "center"); - Icon helpIcon = new Icon(VaadinIcon.QUESTION_CIRCLE); - helpIcon.getStyle().set("margin-left", "8px"); - title.add(helpIcon); - section.add(title); + + + HorizontalLayout titleLayout = new HorizontalLayout(); + titleLayout.setWidthFull(); + titleLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.START); + titleLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER); + titleLayout.add(title); + + // Alle einzelnen Controls auf volle Breite setzen + pickupCompany.setWidthFull(); + pickupSalutation.setWidthFull(); + pickupFirstName.setWidthFull(); + pickupLastName.setWidthFull(); + pickupPhone.setWidthFull(); + pickupAddressAddition.setWidthFull(); + savePickupAddress.setWidthFull(); + + section.add(titleLayout); section.add(pickupCompany); section.add(pickupSalutation); section.add(pickupFirstName); @@ -241,6 +266,7 @@ public class AddJobView extends Main { HorizontalLayout streetLayout = new HorizontalLayout(); streetLayout.setWidthFull(); + streetLayout.setSpacing(true); streetLayout.add(pickupStreet, pickupHouseNumber); pickupStreet.setWidth("70%"); pickupHouseNumber.setWidth("30%"); @@ -250,6 +276,7 @@ public class AddJobView extends Main { HorizontalLayout zipCityLayout = new HorizontalLayout(); zipCityLayout.setWidthFull(); + zipCityLayout.setSpacing(true); zipCityLayout.add(pickupZip, pickupCity); pickupZip.setWidth("30%"); pickupCity.setWidth("70%"); @@ -263,12 +290,35 @@ public class AddJobView extends Main { private VerticalLayout createDeliverySection() { VerticalLayout section = new VerticalLayout(); section.setSpacing(true); - section.setPadding(false); + section.setPadding(true); + section.setWidthFull(); + + // Hellgrauer Rahmen hinzufügen + section.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)"); + section.getStyle().set("border-radius", "var(--lumo-border-radius-m)"); + section.getStyle().set("background-color", "var(--lumo-base-color)"); H3 title = new H3("Lieferadresse"); title.getStyle().set("margin", "0"); - section.add(title); + + + HorizontalLayout titleLayout = new HorizontalLayout(); + titleLayout.setWidthFull(); + titleLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.START); + titleLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER); + titleLayout.add(title); + + // Alle einzelnen Controls auf volle Breite setzen + deliveryCompany.setWidthFull(); + deliverySalutation.setWidthFull(); + deliveryFirstName.setWidthFull(); + deliveryLastName.setWidthFull(); + deliveryPhone.setWidthFull(); + deliveryAddressAddition.setWidthFull(); + saveDeliveryAddress.setWidthFull(); + + section.add(titleLayout); section.add(deliveryCompany); section.add(deliverySalutation); section.add(deliveryFirstName); @@ -277,6 +327,7 @@ public class AddJobView extends Main { HorizontalLayout streetLayout = new HorizontalLayout(); streetLayout.setWidthFull(); + streetLayout.setSpacing(true); streetLayout.add(deliveryStreet, deliveryHouseNumber); deliveryStreet.setWidth("70%"); deliveryHouseNumber.setWidth("30%"); @@ -286,6 +337,7 @@ public class AddJobView extends Main { HorizontalLayout zipCityLayout = new HorizontalLayout(); zipCityLayout.setWidthFull(); + zipCityLayout.setSpacing(true); zipCityLayout.add(deliveryZip, deliveryCity); deliveryZip.setWidth("30%"); deliveryCity.setWidth("70%"); @@ -296,6 +348,10 @@ public class AddJobView extends Main { return section; } + + + + private void copyPickupToDelivery() { deliveryCompany.setValue(pickupCompany.getValue()); deliverySalutation.setValue(pickupSalutation.getValue()); @@ -347,7 +403,367 @@ public class AddJobView extends Main { job.setAppUser(appUser.getValue()); job.setCustomerSelection(customerSelection.getValue()); - addJobService.addJob(job); - System.out.println("Job created successfully"); + try { + Job savedJob = addJobService.addJob(job); + + // Erfolgsmeldung anzeigen + Notification successNotification = Notification.show( + "Auftrag erfolgreich erstellt! Auftragsnummer: " + savedJob.getJobNumber()); + successNotification.setDuration(5000); + + // Formular zurücksetzen + clearForm(); + + } catch (Exception e) { + // Fehlermeldung anzeigen + Notification errorNotification = Notification.show( + "Fehler beim Erstellen des Auftrags: " + e.getMessage()); + errorNotification.setDuration(5000); + } + } + + /** + * Setzt alle Formularfelder zurück + */ + private void clearForm() { + // Customer selection + customerSelection.clear(); + + // Pickup address + pickupCompany.clear(); + pickupSalutation.clear(); + pickupFirstName.clear(); + pickupLastName.clear(); + pickupPhone.clear(); + pickupStreet.clear(); + pickupHouseNumber.clear(); + pickupAddressAddition.clear(); + pickupZip.clear(); + pickupCity.clear(); + savePickupAddress.setValue(false); + + // Delivery address + deliveryCompany.clear(); + deliverySalutation.clear(); + deliveryFirstName.clear(); + deliveryLastName.clear(); + deliveryPhone.clear(); + deliveryStreet.clear(); + deliveryHouseNumber.clear(); + deliveryAddressAddition.clear(); + deliveryZip.clear(); + deliveryCity.clear(); + saveDeliveryAddress.setValue(false); + + // Digital processing + digitalProcessing.setValue(false); + appUser.clear(); + } + + /** + * Persistiert die aktuellen Formulardaten als Entwurf + */ + private void persistCurrentData() { + // Prüfen ob mindestens ein Feld ausgefüllt ist + if (isFormEmpty()) { + return; // Keine Daten zum Speichern + } + + Job job = new Job(); + + // Alle aktuellen Werte aus dem Formular lesen + job.setCustomerSelection(customerSelection.getValue()); + + // Pickup address + job.setPickupCompany(pickupCompany.getValue()); + job.setPickupSalutation(pickupSalutation.getValue()); + job.setPickupFirstName(pickupFirstName.getValue()); + job.setPickupLastName(pickupLastName.getValue()); + job.setPickupPhone(pickupPhone.getValue()); + job.setPickupStreet(pickupStreet.getValue()); + job.setPickupHouseNumber(pickupHouseNumber.getValue()); + job.setPickupAddressAddition(pickupAddressAddition.getValue()); + job.setPickupZip(pickupZip.getValue()); + job.setPickupCity(pickupCity.getValue()); + job.setSavePickupAddress(savePickupAddress.getValue()); + + // Delivery address + job.setDeliveryCompany(deliveryCompany.getValue()); + job.setDeliverySalutation(deliverySalutation.getValue()); + job.setDeliveryFirstName(deliveryFirstName.getValue()); + job.setDeliveryLastName(deliveryLastName.getValue()); + job.setDeliveryPhone(deliveryPhone.getValue()); + job.setDeliveryStreet(deliveryStreet.getValue()); + job.setDeliveryHouseNumber(deliveryHouseNumber.getValue()); + job.setDeliveryAddressAddition(deliveryAddressAddition.getValue()); + job.setDeliveryZip(deliveryZip.getValue()); + job.setDeliveryCity(deliveryCity.getValue()); + job.setSaveDeliveryAddress(saveDeliveryAddress.getValue()); + + // Digital processing + job.setDigitalProcessing(digitalProcessing.getValue()); + job.setAppUser(appUser.getValue()); + + // Als Entwurf speichern + addJobService.saveDraft(job); + } + + /** + * Prüft ob das Formular komplett leer ist + */ + private boolean isFormEmpty() { + return (customerSelection.getValue() == null || customerSelection.getValue().trim().isEmpty()) && + (pickupCompany.getValue() == null || pickupCompany.getValue().trim().isEmpty()) && + (pickupFirstName.getValue() == null || pickupFirstName.getValue().trim().isEmpty()) && + (pickupLastName.getValue() == null || pickupLastName.getValue().trim().isEmpty()) && + (pickupStreet.getValue() == null || pickupStreet.getValue().trim().isEmpty()) && + (pickupCity.getValue() == null || pickupCity.getValue().trim().isEmpty()) && + (deliveryCompany.getValue() == null || deliveryCompany.getValue().trim().isEmpty()) && + (deliveryFirstName.getValue() == null || deliveryFirstName.getValue().trim().isEmpty()) && + (deliveryLastName.getValue() == null || deliveryLastName.getValue().trim().isEmpty()) && + (deliveryStreet.getValue() == null || deliveryStreet.getValue().trim().isEmpty()) && + (deliveryCity.getValue() == null || deliveryCity.getValue().trim().isEmpty()); + } + + /** + * Lädt einen bestehenden Entwurf, falls vorhanden + */ + private void loadDraftIfExists() { + try { + String currentUser = getCurrentUsername(); + if (currentUser != null) { + Optional draftOpt = addJobService.findCurrentDraft(currentUser); + if (draftOpt.isPresent()) { + Job draft = draftOpt.get(); + loadJobIntoForm(draft); + + // Benutzer informieren + Notification notification = Notification.show( + "Entwurf wiederhergestellt. Sie können Ihre Arbeit fortsetzen."); + notification.setDuration(4000); + } + } + } catch (Exception e) { + // Fehler beim Laden des Entwurfs sollten nicht die Anwendung blockieren + System.err.println("Fehler beim Laden des Entwurfs: " + e.getMessage()); + } + } + + /** + * Lädt Job-Daten in das Formular + */ + private void loadJobIntoForm(Job job) { + if (job.getCustomerSelection() != null) { + customerSelection.setValue(job.getCustomerSelection()); + } + + // Pickup address + if (job.getPickupCompany() != null) pickupCompany.setValue(job.getPickupCompany()); + if (job.getPickupSalutation() != null) pickupSalutation.setValue(job.getPickupSalutation()); + if (job.getPickupFirstName() != null) pickupFirstName.setValue(job.getPickupFirstName()); + if (job.getPickupLastName() != null) pickupLastName.setValue(job.getPickupLastName()); + if (job.getPickupPhone() != null) pickupPhone.setValue(job.getPickupPhone()); + if (job.getPickupStreet() != null) pickupStreet.setValue(job.getPickupStreet()); + if (job.getPickupHouseNumber() != null) pickupHouseNumber.setValue(job.getPickupHouseNumber()); + if (job.getPickupAddressAddition() != null) pickupAddressAddition.setValue(job.getPickupAddressAddition()); + if (job.getPickupZip() != null) pickupZip.setValue(job.getPickupZip()); + if (job.getPickupCity() != null) pickupCity.setValue(job.getPickupCity()); + savePickupAddress.setValue(job.isSavePickupAddress()); + + // Delivery address + if (job.getDeliveryCompany() != null) deliveryCompany.setValue(job.getDeliveryCompany()); + if (job.getDeliverySalutation() != null) deliverySalutation.setValue(job.getDeliverySalutation()); + if (job.getDeliveryFirstName() != null) deliveryFirstName.setValue(job.getDeliveryFirstName()); + if (job.getDeliveryLastName() != null) deliveryLastName.setValue(job.getDeliveryLastName()); + if (job.getDeliveryPhone() != null) deliveryPhone.setValue(job.getDeliveryPhone()); + if (job.getDeliveryStreet() != null) deliveryStreet.setValue(job.getDeliveryStreet()); + if (job.getDeliveryHouseNumber() != null) deliveryHouseNumber.setValue(job.getDeliveryHouseNumber()); + if (job.getDeliveryAddressAddition() != null) deliveryAddressAddition.setValue(job.getDeliveryAddressAddition()); + if (job.getDeliveryZip() != null) deliveryZip.setValue(job.getDeliveryZip()); + if (job.getDeliveryCity() != null) deliveryCity.setValue(job.getDeliveryCity()); + saveDeliveryAddress.setValue(job.isSaveDeliveryAddress()); + + // Digital processing + digitalProcessing.setValue(job.isDigitalProcessing()); + if (job.getAppUser() != null) appUser.setValue(job.getAppUser()); + } + + /** + * Hilfsmethode zum Abrufen des aktuellen Benutzernamens + */ + private String getCurrentUsername() { + try { + // Hier würden Sie normalerweise den SecurityService verwenden + // Für diese Demo nehmen wir einen festen Wert + return "test@votianlt.de"; // TODO: SecurityService integrieren + } catch (Exception e) { + return null; + } + } + + /** + * Leert alle Felder im Formular + */ + private void clearAllFields() { + // Customer selection + customerSelection.clear(); + + // Pickup address + pickupCompany.clear(); + pickupSalutation.clear(); + pickupFirstName.clear(); + pickupLastName.clear(); + pickupPhone.clear(); + pickupStreet.clear(); + pickupHouseNumber.clear(); + pickupAddressAddition.clear(); + pickupZip.clear(); + pickupCity.clear(); + savePickupAddress.setValue(false); + + // Delivery address + deliveryCompany.clear(); + deliverySalutation.clear(); + deliveryFirstName.clear(); + deliveryLastName.clear(); + deliveryPhone.clear(); + deliveryStreet.clear(); + deliveryHouseNumber.clear(); + deliveryAddressAddition.clear(); + deliveryZip.clear(); + deliveryCity.clear(); + saveDeliveryAddress.setValue(false); + + // Digital processing + digitalProcessing.setValue(false); + appUser.clear(); + + // Benutzer-Feedback + Notification.show("Alle Felder wurden geleert", 2000, Notification.Position.BOTTOM_CENTER); + } + + /** + * Fügt Google Places Autocomplete zu einem TextField hinzu + */ + private void addGooglePlacesAutocomplete(TextField textField, int stageIndex) { + // Initialisierung als Runnable kapseln + Runnable initAutocomplete = () -> { + // Google Places API Script laden (falls noch nicht geladen) + UI.getCurrent().getPage().addJavaScript("https://maps.googleapis.com/maps/api/js?key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE&libraries=places"); + + // JavaScript für Google Places Autocomplete - nutzt direkt das Input-Element + String script = """ + setTimeout(function() { + var host = $0; // Vaadin TextField element + var input = host && host.inputElement ? host.inputElement : (host && host.shadowRoot ? host.shadowRoot.querySelector('input') : null); + if (input && window.google && window.google.maps && window.google.maps.places) { + var autocomplete = new google.maps.places.Autocomplete(input, { + types: ['establishment'], + componentRestrictions: {country: 'de'} + }); + + autocomplete.addListener('place_changed', function() { + var place = autocomplete.getPlace(); + if (place && place.address_components) { + var streetNumber = ''; + var route = ''; + var locality = ''; + var postalCode = ''; + var country = ''; + var companyName = place.name || ''; + + for (var i = 0; i < place.address_components.length; i++) { + var component = place.address_components[i]; + var types = component.types || []; + + if (types.indexOf('street_number') !== -1) { + streetNumber = component.long_name; + } else if (types.indexOf('route') !== -1) { + route = component.long_name; + } else if (types.indexOf('locality') !== -1) { + locality = component.long_name; + } else if (types.indexOf('postal_code') !== -1) { + postalCode = component.long_name; + } else if (types.indexOf('country') !== -1) { + country = component.long_name; + } + } + + // Setze den Firmennamen in das Feld (sichtbar und Vaadin-Wert) + input.value = companyName || ''; + host.value = companyName || ''; + + // Sende Daten an Server mit Stage-Index + $1.$server.handleGooglePlaceSelected(%d, companyName, route, streetNumber, postalCode, locality, country); + } + }); + + } else { + setTimeout(arguments.callee, 500); + } + }, 500); + """.formatted(stageIndex); + + // executeJs mit Referenzen: $0 => input element (shadowRoot), $1 => Server (this) + textField.getElement().executeJs(script, textField.getElement(), getElement()); + + log.debug("Google Places Autocomplete initialisiert (Stage {})", stageIndex); + }; + + // Wenn UI bereits existiert, direkt initialisieren; sonst beim Attach initialisieren + textField.getUI().ifPresentOrElse( + ui -> ui.access((com.vaadin.flow.server.Command) initAutocomplete::run), + () -> textField.addAttachListener(event -> event.getUI().access((com.vaadin.flow.server.Command) initAutocomplete::run)) + ); + } + + /** + * Handler für Google Places Auswahl - wird vom JavaScript aufgerufen + */ + @ClientCallable + public void handleGooglePlaceSelected(int stageIndex, String companyName, String route, String streetNumber, String postalCode, String locality, String country) { + log.debug("Google Place ausgewählt - Stage: {}, Company: {}, Route: {}, StreetNumber: {}, PostalCode: {}, Locality: {}, Country: {}", + stageIndex, companyName, route, streetNumber, postalCode, locality, country); + + if (stageIndex == 0) { + // Pickup address + // Firmenname wird bereits durch das TextField selbst gesetzt + if (route != null && !route.isEmpty()) { + pickupStreet.setValue(route); + } + if (streetNumber != null && !streetNumber.isEmpty()) { + pickupHouseNumber.setValue(streetNumber); + } + if (postalCode != null && !postalCode.isEmpty()) { + pickupZip.setValue(postalCode); + } + if (locality != null && !locality.isEmpty()) { + pickupCity.setValue(locality); + } + + // Notification für Benutzer + Notification.show("Abholadresse automatisch ausgefüllt: " + (companyName != null ? companyName : ""), + 3000, Notification.Position.BOTTOM_CENTER); + + } else if (stageIndex == 1) { + // Delivery address + // Firmenname wird bereits durch das TextField selbst gesetzt + if (route != null && !route.isEmpty()) { + deliveryStreet.setValue(route); + } + if (streetNumber != null && !streetNumber.isEmpty()) { + deliveryHouseNumber.setValue(streetNumber); + } + if (postalCode != null && !postalCode.isEmpty()) { + deliveryZip.setValue(postalCode); + } + if (locality != null && !locality.isEmpty()) { + deliveryCity.setValue(locality); + } + + // Notification für Benutzer + Notification.show("Lieferadresse automatisch ausgefüllt: " + (companyName != null ? companyName : ""), + 3000, Notification.Position.BOTTOM_CENTER); + } } } diff --git a/src/main/java/de/assecutor/votianlt/repository/JobRepository.java b/src/main/java/de/assecutor/votianlt/repository/JobRepository.java new file mode 100644 index 0000000..1b85d3c --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/repository/JobRepository.java @@ -0,0 +1,87 @@ +package de.assecutor.votianlt.repository; + +import de.assecutor.votianlt.model.Job; +import de.assecutor.votianlt.model.JobStatus; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Repository +public interface JobRepository extends MongoRepository { + + /** + * Findet einen Auftrag anhand der Auftragsnummer + */ + Optional findByJobNumber(String jobNumber); + + /** + * Findet alle Aufträge eines bestimmten Status + */ + List findByStatus(JobStatus status); + + /** + * Findet alle Aufträge, die von einem bestimmten Benutzer erstellt wurden + */ + List findByCreatedBy(String createdBy); + + /** + * Findet alle Aufträge in einem bestimmten Zeitraum + */ + List findByCreatedAtBetween(LocalDateTime start, LocalDateTime end); + + /** + * Findet alle Aufträge mit einer bestimmten Abholstadt + */ + List findByPickupCity(String pickupCity); + + /** + * Findet alle Aufträge mit einer bestimmten Lieferstadt + */ + List findByDeliveryCity(String deliveryCity); + + /** + * Findet alle Aufträge mit digitaler Abwicklung + */ + List findByDigitalProcessingTrue(); + + /** + * Zählt Aufträge nach Status + */ + long countByStatus(JobStatus status); + + /** + * Findet die neuesten Aufträge (sortiert nach Erstellungsdatum) + */ + @Query(value = "{}", sort = "{ 'created_at' : -1 }") + List findLatestJobs(); + + /** + * Findet Aufträge anhand einer Kundenauswahl + */ + List findByCustomerSelection(String customerSelection); + + /** + * Prüft, ob eine Auftragsnummer bereits existiert + */ + boolean existsByJobNumber(String jobNumber); + + /** + * Findet alle Entwürfe eines Benutzers + */ + List findByCreatedByAndIsDraftTrue(String createdBy); + + /** + * Findet alle finalen Aufträge (keine Entwürfe) + */ + List findByIsDraftFalse(); + + /** + * Zählt alle Entwürfe + */ + long countByIsDraftTrue(); +}