feat: refresh job summary after task updates
- broadcast job updates after mobile task completions are persisted\n- rerender job_summary live for the affected job via UI access\n- show pickup and delivery tile detail lines like on add_job\n- highlight delivery tiles in light green when all station tasks are completed
This commit is contained in:
@@ -22,6 +22,7 @@ import de.assecutor.votianlt.model.Barcode;
|
|||||||
import de.assecutor.votianlt.model.Signature;
|
import de.assecutor.votianlt.model.Signature;
|
||||||
import de.assecutor.votianlt.model.Comment;
|
import de.assecutor.votianlt.model.Comment;
|
||||||
import de.assecutor.votianlt.service.JobHistoryService;
|
import de.assecutor.votianlt.service.JobHistoryService;
|
||||||
|
import de.assecutor.votianlt.service.JobUpdateBroadcaster;
|
||||||
import de.assecutor.votianlt.service.EmailService;
|
import de.assecutor.votianlt.service.EmailService;
|
||||||
import de.assecutor.votianlt.service.MessageService;
|
import de.assecutor.votianlt.service.MessageService;
|
||||||
import de.assecutor.votianlt.model.JobStatus;
|
import de.assecutor.votianlt.model.JobStatus;
|
||||||
@@ -59,6 +60,7 @@ public class MessageController {
|
|||||||
private final SignatureRepository signatureRepository;
|
private final SignatureRepository signatureRepository;
|
||||||
private final CommentRepository commentRepository;
|
private final CommentRepository commentRepository;
|
||||||
private final JobHistoryService jobHistoryService;
|
private final JobHistoryService jobHistoryService;
|
||||||
|
private final JobUpdateBroadcaster jobUpdateBroadcaster;
|
||||||
private final EmailService emailService;
|
private final EmailService emailService;
|
||||||
private final MessageService messageService;
|
private final MessageService messageService;
|
||||||
|
|
||||||
@@ -66,7 +68,8 @@ public class MessageController {
|
|||||||
AppUserService appUserService, JobRepository jobRepository, CargoItemRepository cargoItemRepository,
|
AppUserService appUserService, JobRepository jobRepository, CargoItemRepository cargoItemRepository,
|
||||||
TaskRepository taskRepository, PhotoRepository photoRepository, BarcodeRepository barcodeRepository,
|
TaskRepository taskRepository, PhotoRepository photoRepository, BarcodeRepository barcodeRepository,
|
||||||
SignatureRepository signatureRepository, CommentRepository commentRepository,
|
SignatureRepository signatureRepository, CommentRepository commentRepository,
|
||||||
JobHistoryService jobHistoryService, EmailService emailService, MessageService messageService) {
|
JobHistoryService jobHistoryService, JobUpdateBroadcaster jobUpdateBroadcaster, EmailService emailService,
|
||||||
|
MessageService messageService) {
|
||||||
this.messagingPublisher = messagingPublisher;
|
this.messagingPublisher = messagingPublisher;
|
||||||
this.appUserRepository = appUserRepository;
|
this.appUserRepository = appUserRepository;
|
||||||
this.appUserService = appUserService;
|
this.appUserService = appUserService;
|
||||||
@@ -78,6 +81,7 @@ public class MessageController {
|
|||||||
this.signatureRepository = signatureRepository;
|
this.signatureRepository = signatureRepository;
|
||||||
this.commentRepository = commentRepository;
|
this.commentRepository = commentRepository;
|
||||||
this.jobHistoryService = jobHistoryService;
|
this.jobHistoryService = jobHistoryService;
|
||||||
|
this.jobUpdateBroadcaster = jobUpdateBroadcaster;
|
||||||
this.emailService = emailService;
|
this.emailService = emailService;
|
||||||
this.messageService = messageService;
|
this.messageService = messageService;
|
||||||
}
|
}
|
||||||
@@ -345,10 +349,10 @@ public class MessageController {
|
|||||||
task.setCompleted(true);
|
task.setCompleted(true);
|
||||||
task.setCompletedAt(LocalDateTime.now());
|
task.setCompletedAt(LocalDateTime.now());
|
||||||
taskRepository.save(task);
|
taskRepository.save(task);
|
||||||
|
ObjectId jobId = new ObjectId(task.getJobIdAsString());
|
||||||
|
|
||||||
// Log detailed task completion in job history
|
// Log detailed task completion in job history
|
||||||
try {
|
try {
|
||||||
ObjectId jobId = new ObjectId(task.getJobIdAsString());
|
|
||||||
String taskType = task.getTaskType() != null ? task.getTaskType().toString() : "Unknown";
|
String taskType = task.getTaskType() != null ? task.getTaskType().toString() : "Unknown";
|
||||||
String taskDisplayName = task.getDisplayName() != null ? task.getDisplayName() : taskType;
|
String taskDisplayName = task.getDisplayName() != null ? task.getDisplayName() : taskType;
|
||||||
String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
|
String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
|
||||||
@@ -360,7 +364,6 @@ public class MessageController {
|
|||||||
|
|
||||||
// Send email notification for task completion
|
// Send email notification for task completion
|
||||||
try {
|
try {
|
||||||
ObjectId jobId = new ObjectId(task.getJobIdAsString());
|
|
||||||
String taskType = task.getTaskType() != null ? task.getTaskType().toString() : "Unknown";
|
String taskType = task.getTaskType() != null ? task.getTaskType().toString() : "Unknown";
|
||||||
String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
|
String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
|
||||||
emailService.sendTaskCompletionNotification(jobId, taskType, taskIdStr, completedBy);
|
emailService.sendTaskCompletionNotification(jobId, taskType, taskIdStr, completedBy);
|
||||||
@@ -368,6 +371,8 @@ public class MessageController {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Ignore email notification errors
|
// Ignore email notification errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jobUpdateBroadcaster.broadcast(jobId);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.error("[TASK] Completion error: {}", ex.getMessage());
|
log.error("[TASK] Completion error: {}", ex.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,14 @@ import com.vaadin.flow.component.notification.Notification;
|
|||||||
import com.vaadin.flow.component.notification.NotificationVariant;
|
import com.vaadin.flow.component.notification.NotificationVariant;
|
||||||
import com.vaadin.flow.component.icon.Icon;
|
import com.vaadin.flow.component.icon.Icon;
|
||||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||||
|
import com.vaadin.flow.component.AttachEvent;
|
||||||
|
import com.vaadin.flow.component.DetachEvent;
|
||||||
|
import com.vaadin.flow.component.UI;
|
||||||
import com.vaadin.flow.router.BeforeEvent;
|
import com.vaadin.flow.router.BeforeEvent;
|
||||||
import com.vaadin.flow.router.HasUrlParameter;
|
import com.vaadin.flow.router.HasUrlParameter;
|
||||||
import com.vaadin.flow.router.HasDynamicTitle;
|
import com.vaadin.flow.router.HasDynamicTitle;
|
||||||
import com.vaadin.flow.router.Route;
|
import com.vaadin.flow.router.Route;
|
||||||
|
import com.vaadin.flow.shared.Registration;
|
||||||
import com.vaadin.flow.theme.lumo.LumoUtility;
|
import com.vaadin.flow.theme.lumo.LumoUtility;
|
||||||
import de.assecutor.votianlt.model.CargoItem;
|
import de.assecutor.votianlt.model.CargoItem;
|
||||||
import de.assecutor.votianlt.model.DeliveryStation;
|
import de.assecutor.votianlt.model.DeliveryStation;
|
||||||
@@ -51,6 +55,7 @@ import de.assecutor.votianlt.model.Comment;
|
|||||||
import de.assecutor.votianlt.model.JobStatus;
|
import de.assecutor.votianlt.model.JobStatus;
|
||||||
import de.assecutor.votianlt.pages.service.AppUserService;
|
import de.assecutor.votianlt.pages.service.AppUserService;
|
||||||
import de.assecutor.votianlt.service.JobHistoryService;
|
import de.assecutor.votianlt.service.JobHistoryService;
|
||||||
|
import de.assecutor.votianlt.service.JobUpdateBroadcaster;
|
||||||
import de.assecutor.votianlt.service.LocationService;
|
import de.assecutor.votianlt.service.LocationService;
|
||||||
import de.assecutor.votianlt.service.MessageService;
|
import de.assecutor.votianlt.service.MessageService;
|
||||||
import de.assecutor.votianlt.util.DateTimeFormatUtil;
|
import de.assecutor.votianlt.util.DateTimeFormatUtil;
|
||||||
@@ -80,6 +85,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
private final CommentRepository commentRepository;
|
private final CommentRepository commentRepository;
|
||||||
private final AppUserService appUserService;
|
private final AppUserService appUserService;
|
||||||
private final JobHistoryService jobHistoryService;
|
private final JobHistoryService jobHistoryService;
|
||||||
|
private final JobUpdateBroadcaster jobUpdateBroadcaster;
|
||||||
private final LocationService locationService;
|
private final LocationService locationService;
|
||||||
private final ServiceRepository serviceRepository;
|
private final ServiceRepository serviceRepository;
|
||||||
|
|
||||||
@@ -88,11 +94,14 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
|
|
||||||
private final VerticalLayout content;
|
private final VerticalLayout content;
|
||||||
private final List<Div> taskCards = new ArrayList<>();
|
private final List<Div> taskCards = new ArrayList<>();
|
||||||
|
private Registration jobUpdateRegistration;
|
||||||
|
private ObjectId currentJobId;
|
||||||
|
|
||||||
public JobSummaryView(JobRepository jobRepository, CargoItemRepository cargoItemRepository,
|
public JobSummaryView(JobRepository jobRepository, CargoItemRepository cargoItemRepository,
|
||||||
TaskRepository taskRepository, SignatureRepository signatureRepository, BarcodeRepository barcodeRepository,
|
TaskRepository taskRepository, SignatureRepository signatureRepository, BarcodeRepository barcodeRepository,
|
||||||
PhotoRepository photoRepository, CommentRepository commentRepository, AppUserService appUserService,
|
PhotoRepository photoRepository, CommentRepository commentRepository, AppUserService appUserService,
|
||||||
MessageService messageService, JobHistoryService jobHistoryService, LocationService locationService,
|
MessageService messageService, JobHistoryService jobHistoryService,
|
||||||
|
JobUpdateBroadcaster jobUpdateBroadcaster, LocationService locationService,
|
||||||
ServiceRepository serviceRepository) {
|
ServiceRepository serviceRepository) {
|
||||||
this.jobRepository = jobRepository;
|
this.jobRepository = jobRepository;
|
||||||
this.cargoItemRepository = cargoItemRepository;
|
this.cargoItemRepository = cargoItemRepository;
|
||||||
@@ -103,6 +112,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
this.commentRepository = commentRepository;
|
this.commentRepository = commentRepository;
|
||||||
this.appUserService = appUserService;
|
this.appUserService = appUserService;
|
||||||
this.jobHistoryService = jobHistoryService;
|
this.jobHistoryService = jobHistoryService;
|
||||||
|
this.jobUpdateBroadcaster = jobUpdateBroadcaster;
|
||||||
this.locationService = locationService;
|
this.locationService = locationService;
|
||||||
this.serviceRepository = serviceRepository;
|
this.serviceRepository = serviceRepository;
|
||||||
|
|
||||||
@@ -118,30 +128,62 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setParameter(BeforeEvent event, String parameter) {
|
public void setParameter(BeforeEvent event, String parameter) {
|
||||||
content.removeAll();
|
|
||||||
removeAll(); // Remove existing toolbar
|
|
||||||
|
|
||||||
if (parameter == null || parameter.isBlank()) {
|
if (parameter == null || parameter.isBlank()) {
|
||||||
|
content.removeAll();
|
||||||
|
removeAll();
|
||||||
add(new ViewToolbar("Zusammenfassung"));
|
add(new ViewToolbar("Zusammenfassung"));
|
||||||
content.add(new Span("Fehler: Keine Job-ID angegeben"));
|
content.add(new Span("Fehler: Keine Job-ID angegeben"));
|
||||||
add(content);
|
add(content);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectId jobId;
|
|
||||||
try {
|
try {
|
||||||
jobId = new ObjectId(parameter);
|
currentJobId = new ObjectId(parameter);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
content.removeAll();
|
||||||
|
removeAll();
|
||||||
add(new ViewToolbar("Zusammenfassung"));
|
add(new ViewToolbar("Zusammenfassung"));
|
||||||
content.add(new Span("Fehler: Ungültige Job-ID Format: " + parameter));
|
content.add(new Span("Fehler: Ungültige Job-ID Format: " + parameter));
|
||||||
add(content);
|
add(content);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Job job = jobRepository.findById(jobId).orElse(null);
|
refreshCurrentJobSummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttach(AttachEvent attachEvent) {
|
||||||
|
super.onAttach(attachEvent);
|
||||||
|
UI ui = attachEvent.getUI();
|
||||||
|
jobUpdateRegistration = jobUpdateBroadcaster.register(jobId -> {
|
||||||
|
if (currentJobId == null || jobId == null || !currentJobId.equals(jobId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ui.access(this::refreshCurrentJobSummary);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetach(DetachEvent detachEvent) {
|
||||||
|
if (jobUpdateRegistration != null) {
|
||||||
|
jobUpdateRegistration.remove();
|
||||||
|
jobUpdateRegistration = null;
|
||||||
|
}
|
||||||
|
super.onDetach(detachEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshCurrentJobSummary() {
|
||||||
|
if (currentJobId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
content.removeAll();
|
||||||
|
removeAll();
|
||||||
|
|
||||||
|
Job job = jobRepository.findById(currentJobId).orElse(null);
|
||||||
if (job == null) {
|
if (job == null) {
|
||||||
add(new ViewToolbar("Zusammenfassung"));
|
add(new ViewToolbar("Zusammenfassung"));
|
||||||
content.add(new Span("Fehler: Job mit ID " + parameter + " nicht gefunden"));
|
content.add(new Span("Fehler: Job mit ID " + currentJobId.toHexString() + " nicht gefunden"));
|
||||||
add(content);
|
add(content);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -177,8 +219,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
// Add toolbar with both buttons in top right (Send Message button on the left)
|
// Add toolbar with both buttons in top right (Send Message button on the left)
|
||||||
add(new ViewToolbar("Zusammenfassung", sendMessageButton, jobHistoryButton));
|
add(new ViewToolbar("Zusammenfassung", sendMessageButton, jobHistoryButton));
|
||||||
|
|
||||||
List<CargoItem> cargo = cargoItemRepository.findByJobId(jobId);
|
List<CargoItem> cargo = cargoItemRepository.findByJobId(currentJobId);
|
||||||
List<BaseTask> tasks = taskRepository.findByJobIdOrderByTaskOrderAsc(jobId);
|
List<BaseTask> tasks = taskRepository.findByJobIdOrderByTaskOrderAsc(currentJobId);
|
||||||
|
|
||||||
render(job, cargo, tasks);
|
render(job, cargo, tasks);
|
||||||
add(content);
|
add(content);
|
||||||
@@ -187,7 +229,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
private void render(Job job, List<CargoItem> cargoItems, List<BaseTask> tasks) {
|
private void render(Job job, List<CargoItem> cargoItems, List<BaseTask> tasks) {
|
||||||
content.removeAll();
|
content.removeAll();
|
||||||
|
|
||||||
content.add(createStationTilesSection(job, tasks));
|
content.add(createStationTilesSection(job, cargoItems, tasks));
|
||||||
|
|
||||||
// Fracht und weitere Infos
|
// Fracht und weitere Infos
|
||||||
HorizontalLayout midRow = new HorizontalLayout();
|
HorizontalLayout midRow = new HorizontalLayout();
|
||||||
@@ -305,14 +347,14 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Div createStationTilesSection(Job job, List<BaseTask> tasks) {
|
private Div createStationTilesSection(Job job, List<CargoItem> cargoItems, List<BaseTask> tasks) {
|
||||||
Div stationGrid = new Div();
|
Div stationGrid = new Div();
|
||||||
stationGrid.getStyle().set("display", "grid");
|
stationGrid.getStyle().set("display", "grid");
|
||||||
stationGrid.getStyle().set("grid-template-columns", "repeat(auto-fit, minmax(220px, 1fr))");
|
stationGrid.getStyle().set("grid-template-columns", "repeat(auto-fit, minmax(220px, 1fr))");
|
||||||
stationGrid.getStyle().set("gap", "var(--lumo-space-m)");
|
stationGrid.getStyle().set("gap", "var(--lumo-space-m)");
|
||||||
stationGrid.setWidthFull();
|
stationGrid.setWidthFull();
|
||||||
|
|
||||||
stationGrid.add(createPickupSummaryTile(job));
|
stationGrid.add(createPickupSummaryTile(job, cargoItems));
|
||||||
|
|
||||||
List<DeliveryStation> stations = job.getDeliveryStations();
|
List<DeliveryStation> stations = job.getDeliveryStations();
|
||||||
if (stations != null && !stations.isEmpty()) {
|
if (stations != null && !stations.isEmpty()) {
|
||||||
@@ -326,10 +368,10 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
return stationGrid;
|
return stationGrid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private StationTile createPickupSummaryTile(Job job) {
|
private StationTile createPickupSummaryTile(Job job, List<CargoItem> cargoItems) {
|
||||||
String title = getTranslation("jobsummary.section.pickup") + " "
|
String title = getTranslation("jobsummary.section.pickup") + " "
|
||||||
+ formatDateWithTime(job.getPickupDate(), job.getPickupTime());
|
+ formatDateWithTime(job.getPickupDate(), job.getPickupTime());
|
||||||
List<String> additionalLines = new ArrayList<>();
|
List<String> additionalLines = buildPickupSummaryDetails(job, cargoItems);
|
||||||
if (job.getPickupPhone() != null && !job.getPickupPhone().isBlank()) {
|
if (job.getPickupPhone() != null && !job.getPickupPhone().isBlank()) {
|
||||||
additionalLines.add(getTranslation("jobsummary.station.phone") + ": " + job.getPickupPhone());
|
additionalLines.add(getTranslation("jobsummary.station.phone") + ": " + job.getPickupPhone());
|
||||||
}
|
}
|
||||||
@@ -345,7 +387,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
String title = getTranslation("jobsummary.section.delivery") + " "
|
String title = getTranslation("jobsummary.section.delivery") + " "
|
||||||
+ (stationCount > 1 ? (index + 1) + " " : "")
|
+ (stationCount > 1 ? (index + 1) + " " : "")
|
||||||
+ formatDateWithTime(station.getDeliveryDate(), station.getDeliveryTime());
|
+ formatDateWithTime(station.getDeliveryDate(), station.getDeliveryTime());
|
||||||
List<String> additionalLines = new ArrayList<>();
|
List<BaseTask> stationTasks = getTasksForStation(station, tasks, false);
|
||||||
|
List<String> additionalLines = buildDeliverySummaryDetails(stationTasks);
|
||||||
if (station.getPhone() != null && !station.getPhone().isBlank()) {
|
if (station.getPhone() != null && !station.getPhone().isBlank()) {
|
||||||
additionalLines.add(getTranslation("jobsummary.station.phone") + ": " + station.getPhone());
|
additionalLines.add(getTranslation("jobsummary.station.phone") + ": " + station.getPhone());
|
||||||
}
|
}
|
||||||
@@ -353,7 +396,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
StationTile tile = createSummaryTile(StationTile.StationType.DELIVERY, index + 1, title, station.getCompany(),
|
StationTile tile = createSummaryTile(StationTile.StationType.DELIVERY, index + 1, title, station.getCompany(),
|
||||||
buildDisplayName(station.getSalutation(), station.getFirstName(), station.getLastName()),
|
buildDisplayName(station.getSalutation(), station.getFirstName(), station.getLastName()),
|
||||||
station.getStreet(), station.getHouseNumber(), station.getZip(), station.getCity(), additionalLines);
|
station.getStreet(), station.getHouseNumber(), station.getZip(), station.getCity(), additionalLines);
|
||||||
List<BaseTask> stationTasks = getTasksForStation(station, tasks, false);
|
tile.setAddressValidated(areAllTasksCompleted(stationTasks));
|
||||||
tile.setInteractive(true);
|
tile.setInteractive(true);
|
||||||
tile.setClickListener(clickedTile -> showStationTasksDialog(title, stationTasks));
|
tile.setClickListener(clickedTile -> showStationTasksDialog(title, stationTasks));
|
||||||
return tile;
|
return tile;
|
||||||
@@ -362,7 +405,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
private StationTile createLegacyDeliverySummaryTile(Job job, List<BaseTask> tasks) {
|
private StationTile createLegacyDeliverySummaryTile(Job job, List<BaseTask> tasks) {
|
||||||
String title = getTranslation("jobsummary.section.delivery") + " "
|
String title = getTranslation("jobsummary.section.delivery") + " "
|
||||||
+ formatDateWithTime(job.getDeliveryDate(), job.getDeliveryTime());
|
+ formatDateWithTime(job.getDeliveryDate(), job.getDeliveryTime());
|
||||||
List<String> additionalLines = new ArrayList<>();
|
List<BaseTask> stationTasks = getTasksForStation(null, tasks, true);
|
||||||
|
List<String> additionalLines = buildDeliverySummaryDetails(stationTasks);
|
||||||
if (job.getDeliveryPhone() != null && !job.getDeliveryPhone().isBlank()) {
|
if (job.getDeliveryPhone() != null && !job.getDeliveryPhone().isBlank()) {
|
||||||
additionalLines.add(getTranslation("jobsummary.station.phone") + ": " + job.getDeliveryPhone());
|
additionalLines.add(getTranslation("jobsummary.station.phone") + ": " + job.getDeliveryPhone());
|
||||||
}
|
}
|
||||||
@@ -371,7 +415,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
buildDisplayName(job.getDeliverySalutation(), job.getDeliveryFirstName(), job.getDeliveryLastName()),
|
buildDisplayName(job.getDeliverySalutation(), job.getDeliveryFirstName(), job.getDeliveryLastName()),
|
||||||
job.getDeliveryStreet(), job.getDeliveryHouseNumber(), job.getDeliveryZip(), job.getDeliveryCity(),
|
job.getDeliveryStreet(), job.getDeliveryHouseNumber(), job.getDeliveryZip(), job.getDeliveryCity(),
|
||||||
additionalLines);
|
additionalLines);
|
||||||
List<BaseTask> stationTasks = getTasksForStation(null, tasks, true);
|
tile.setAddressValidated(areAllTasksCompleted(stationTasks));
|
||||||
tile.setInteractive(true);
|
tile.setInteractive(true);
|
||||||
tile.setClickListener(clickedTile -> showStationTasksDialog(title, stationTasks));
|
tile.setClickListener(clickedTile -> showStationTasksDialog(title, stationTasks));
|
||||||
return tile;
|
return tile;
|
||||||
@@ -386,6 +430,161 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
return tile;
|
return tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<String> buildPickupSummaryDetails(Job job, List<CargoItem> cargoItems) {
|
||||||
|
List<String> additionalLines = new ArrayList<>();
|
||||||
|
additionalLines.add(getTranslation("addjob.tab.cargo") + ": " + summarizeCargoItems(cargoItems));
|
||||||
|
|
||||||
|
String pickupAppointment = formatPickupAppointment(job.getPickupDate(), job.getPickupTime());
|
||||||
|
if (pickupAppointment != null) {
|
||||||
|
additionalLines.add(getTranslation("addjob.appointment.pickup") + ": " + pickupAppointment);
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalLines.add(buildDigitalProcessingPreview(job.isDigitalProcessing(), job.getAppUser()));
|
||||||
|
return additionalLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String summarizeCargoItems(List<CargoItem> cargoItems) {
|
||||||
|
if (cargoItems == null || cargoItems.isEmpty()) {
|
||||||
|
return getTranslation("jobsummary.cargo.none");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> summaries = new ArrayList<>();
|
||||||
|
for (CargoItem cargoItem : cargoItems) {
|
||||||
|
if (cargoItem == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String description = cargoItem.getDescription() != null ? cargoItem.getDescription().trim() : "";
|
||||||
|
Integer quantity = cargoItem.getQuantity();
|
||||||
|
if (description.isEmpty() && quantity == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder summary = new StringBuilder();
|
||||||
|
if (quantity != null) {
|
||||||
|
summary.append(quantity).append("x ");
|
||||||
|
}
|
||||||
|
summary.append(description.isEmpty() ? getTranslation("addjob.tab.cargo") : description);
|
||||||
|
summaries.add(summary.toString().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summaries.isEmpty()) {
|
||||||
|
return getTranslation("jobsummary.cargo.none");
|
||||||
|
}
|
||||||
|
if (summaries.size() <= 2) {
|
||||||
|
return String.join(", ", summaries);
|
||||||
|
}
|
||||||
|
return String.join(", ", summaries.subList(0, 2)) + " +" + (summaries.size() - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatPickupAppointment(java.time.LocalDate appointmentDate, java.time.LocalTime appointmentTime) {
|
||||||
|
if (appointmentDate == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String formattedDate = formatLocalDate(appointmentDate);
|
||||||
|
if (appointmentTime == null) {
|
||||||
|
return formattedDate;
|
||||||
|
}
|
||||||
|
return formattedDate + " " + formatLocalTime(appointmentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildDigitalProcessingPreview(boolean digitalProcessingEnabled, String appUserId) {
|
||||||
|
StringBuilder preview = new StringBuilder();
|
||||||
|
preview.append(getTranslation("profile.settings.digitalprocess")).append(": ")
|
||||||
|
.append(getTranslation(digitalProcessingEnabled ? "common.yes" : "common.no"));
|
||||||
|
|
||||||
|
if (digitalProcessingEnabled && appUserId != null && !appUserId.isBlank()) {
|
||||||
|
preview.append(" (").append(resolveAppUserName(appUserId)).append(")");
|
||||||
|
}
|
||||||
|
return preview.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> buildDeliverySummaryDetails(List<BaseTask> tasks) {
|
||||||
|
if (tasks == null || tasks.isEmpty()) {
|
||||||
|
return List.of(getTranslation("addjob.tab.tasks") + ": " + getTranslation("jobsummary.tasks.none"));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> summaries = new ArrayList<>();
|
||||||
|
for (BaseTask task : tasks) {
|
||||||
|
if (task != null) {
|
||||||
|
summaries.add(summarizeDeliveryTask(task));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summaries.isEmpty()) {
|
||||||
|
return List.of(getTranslation("addjob.tab.tasks") + ": " + getTranslation("jobsummary.tasks.none"));
|
||||||
|
}
|
||||||
|
return summaries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String summarizeDeliveryTask(BaseTask task) {
|
||||||
|
if (task instanceof ConfirmationTask confirmationTask) {
|
||||||
|
String buttonText = trimToNull(confirmationTask.getButtonText());
|
||||||
|
if (buttonText != null) {
|
||||||
|
return confirmationTask.getDisplayName() + " \"" + buttonText + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
String description = trimToNull(confirmationTask.getDescription());
|
||||||
|
return description != null ? confirmationTask.getDisplayName() + " \"" + description + "\""
|
||||||
|
: confirmationTask.getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task instanceof TodoListTask todoListTask) {
|
||||||
|
long itemCount = todoListTask.getTodoItems() == null ? 0
|
||||||
|
: todoListTask.getTodoItems().stream().filter(item -> item != null && !item.trim().isEmpty())
|
||||||
|
.count();
|
||||||
|
return itemCount > 0 ? task.getDisplayName() + " (" + itemCount + ")" : task.getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task instanceof PhotoTask photoTask) {
|
||||||
|
String range = formatMinMaxRange(photoTask.getMinPhotoCount(), photoTask.getMaxPhotoCount());
|
||||||
|
return range.isBlank() ? task.getDisplayName() : task.getDisplayName() + " " + range;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task instanceof BarcodeTask barcodeTask) {
|
||||||
|
String range = formatMinMaxRange(barcodeTask.getMinBarcodeCount(), barcodeTask.getMaxBarcodeCount());
|
||||||
|
return range.isBlank() ? task.getDisplayName() : task.getDisplayName() + " " + range;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task instanceof CommentTask commentTask) {
|
||||||
|
String commentText = trimToNull(commentTask.getCommentText());
|
||||||
|
if (commentText != null) {
|
||||||
|
return task.getDisplayName() + " \"" + commentText + "\"";
|
||||||
|
}
|
||||||
|
return commentTask.isRequired() ? task.getDisplayName() + " (" + getTranslation("common.required") + ")"
|
||||||
|
: task.getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task instanceof SignatureTask) {
|
||||||
|
return task.getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
String description = trimToNull(task.getDescription());
|
||||||
|
return description != null ? task.getDisplayName() + " \"" + description + "\"" : task.getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatMinMaxRange(Integer minValue, Integer maxValue) {
|
||||||
|
if (minValue != null && maxValue != null) {
|
||||||
|
return minValue.equals(maxValue) ? String.valueOf(minValue) : minValue + "-" + maxValue;
|
||||||
|
}
|
||||||
|
if (minValue != null) {
|
||||||
|
return String.valueOf(minValue);
|
||||||
|
}
|
||||||
|
if (maxValue != null) {
|
||||||
|
return String.valueOf(maxValue);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimToNull(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = value.trim();
|
||||||
|
return trimmed.isEmpty() ? null : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
private String buildDisplayName(String salutation, String firstName, String lastName) {
|
private String buildDisplayName(String salutation, String firstName, String lastName) {
|
||||||
List<String> parts = new ArrayList<>();
|
List<String> parts = new ArrayList<>();
|
||||||
if (salutation != null && !salutation.isBlank()) {
|
if (salutation != null && !salutation.isBlank()) {
|
||||||
@@ -432,6 +631,10 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
return stationTasks;
|
return stationTasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean areAllTasksCompleted(List<BaseTask> tasks) {
|
||||||
|
return tasks != null && !tasks.isEmpty() && tasks.stream().allMatch(task -> task != null && task.isCompleted());
|
||||||
|
}
|
||||||
|
|
||||||
private void showStationTasksDialog(String stationTitle, List<BaseTask> tasks) {
|
private void showStationTasksDialog(String stationTitle, List<BaseTask> tasks) {
|
||||||
Dialog dialog = new Dialog();
|
Dialog dialog = new Dialog();
|
||||||
dialog.setWidth("720px");
|
dialog.setWidth("720px");
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package de.assecutor.votianlt.service;
|
||||||
|
|
||||||
|
import com.vaadin.flow.shared.Registration;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class JobUpdateBroadcaster {
|
||||||
|
|
||||||
|
private final Executor executor = Executors.newSingleThreadExecutor();
|
||||||
|
private final LinkedHashSet<Consumer<ObjectId>> listeners = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
public synchronized Registration register(Consumer<ObjectId> listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
return () -> {
|
||||||
|
synchronized (JobUpdateBroadcaster.this) {
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void broadcast(ObjectId jobId) {
|
||||||
|
if (jobId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Consumer<ObjectId> listener : listeners) {
|
||||||
|
executor.execute(() -> {
|
||||||
|
try {
|
||||||
|
listener.accept(jobId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error broadcasting job update for {}", jobId.toHexString(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user