refactor: assign tasks to delivery stations
This commit is contained in:
@@ -14,6 +14,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class MongoConfig {
|
public class MongoConfig {
|
||||||
@@ -118,8 +119,10 @@ public class MongoConfig {
|
|||||||
if (source.containsKey("_id")) {
|
if (source.containsKey("_id")) {
|
||||||
task.setId(source.getObjectId("_id"));
|
task.setId(source.getObjectId("_id"));
|
||||||
}
|
}
|
||||||
if (source.containsKey("job_id")) {
|
task.setStationId(readObjectId(source, "station_id"));
|
||||||
task.setJobId(source.getObjectId("job_id"));
|
task.setJobId(readObjectId(source, "job_id"));
|
||||||
|
if (source.containsKey("station_order")) {
|
||||||
|
task.setStationOrder(source.getInteger("station_order"));
|
||||||
}
|
}
|
||||||
if (source.containsKey("task_order")) {
|
if (source.containsKey("task_order")) {
|
||||||
task.setTaskOrder(source.getInteger("task_order", 0));
|
task.setTaskOrder(source.getInteger("task_order", 0));
|
||||||
@@ -150,6 +153,17 @@ public class MongoConfig {
|
|||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ObjectId readObjectId(Document source, String key) {
|
||||||
|
Object value = source.get(key);
|
||||||
|
if (value instanceof ObjectId objectId) {
|
||||||
|
return objectId;
|
||||||
|
}
|
||||||
|
if (value instanceof String stringValue && ObjectId.isValid(stringValue)) {
|
||||||
|
return new ObjectId(stringValue);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private String mapTaskTypeToClassName(String taskType) {
|
private String mapTaskTypeToClassName(String taskType) {
|
||||||
if (taskType == null) {
|
if (taskType == null) {
|
||||||
return "de.assecutor.votianlt.model.task.ConfirmationTask";
|
return "de.assecutor.votianlt.model.task.ConfirmationTask";
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import de.assecutor.votianlt.service.JobHistoryService;
|
|||||||
import de.assecutor.votianlt.service.JobUpdateBroadcaster;
|
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.service.TaskAssignmentService;
|
||||||
import de.assecutor.votianlt.model.JobStatus;
|
import de.assecutor.votianlt.model.JobStatus;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import de.assecutor.votianlt.messaging.MessagingPublisher;
|
import de.assecutor.votianlt.messaging.MessagingPublisher;
|
||||||
@@ -63,13 +64,14 @@ public class MessageController {
|
|||||||
private final JobUpdateBroadcaster jobUpdateBroadcaster;
|
private final JobUpdateBroadcaster jobUpdateBroadcaster;
|
||||||
private final EmailService emailService;
|
private final EmailService emailService;
|
||||||
private final MessageService messageService;
|
private final MessageService messageService;
|
||||||
|
private final TaskAssignmentService taskAssignmentService;
|
||||||
|
|
||||||
public MessageController(MessagingPublisher messagingPublisher, AppUserRepository appUserRepository,
|
public MessageController(MessagingPublisher messagingPublisher, AppUserRepository appUserRepository,
|
||||||
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, JobUpdateBroadcaster jobUpdateBroadcaster, EmailService emailService,
|
JobHistoryService jobHistoryService, JobUpdateBroadcaster jobUpdateBroadcaster, EmailService emailService,
|
||||||
MessageService messageService) {
|
MessageService messageService, TaskAssignmentService taskAssignmentService) {
|
||||||
this.messagingPublisher = messagingPublisher;
|
this.messagingPublisher = messagingPublisher;
|
||||||
this.appUserRepository = appUserRepository;
|
this.appUserRepository = appUserRepository;
|
||||||
this.appUserService = appUserService;
|
this.appUserService = appUserService;
|
||||||
@@ -84,6 +86,7 @@ public class MessageController {
|
|||||||
this.jobUpdateBroadcaster = jobUpdateBroadcaster;
|
this.jobUpdateBroadcaster = jobUpdateBroadcaster;
|
||||||
this.emailService = emailService;
|
this.emailService = emailService;
|
||||||
this.messageService = messageService;
|
this.messageService = messageService;
|
||||||
|
this.taskAssignmentService = taskAssignmentService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,7 +132,7 @@ public class MessageController {
|
|||||||
|
|
||||||
List<JobWithRelatedDataDTO> jobsWithRelatedData = assignedJobs.stream().map(job -> {
|
List<JobWithRelatedDataDTO> jobsWithRelatedData = assignedJobs.stream().map(job -> {
|
||||||
List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId());
|
List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId());
|
||||||
List<BaseTask> tasks = taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId());
|
List<BaseTask> tasks = taskAssignmentService.findTasksForJob(job);
|
||||||
return new JobWithRelatedDataDTO(job, cargoItems, tasks);
|
return new JobWithRelatedDataDTO(job, cargoItems, tasks);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
@@ -349,7 +352,12 @@ 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());
|
Optional<Job> jobOpt = taskAssignmentService.findJobForTask(task);
|
||||||
|
if (jobOpt.isEmpty()) {
|
||||||
|
log.warn("[TASK] Could not resolve job for task {}", taskIdStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ObjectId jobId = jobOpt.get().getId();
|
||||||
|
|
||||||
// Log detailed task completion in job history
|
// Log detailed task completion in job history
|
||||||
try {
|
try {
|
||||||
@@ -380,7 +388,7 @@ public class MessageController {
|
|||||||
|
|
||||||
private void checkAndHandleJobCompletion(ObjectId jobId, String completedBy) {
|
private void checkAndHandleJobCompletion(ObjectId jobId, String completedBy) {
|
||||||
try {
|
try {
|
||||||
var allTasks = taskRepository.findByJobIdOrderByTaskOrderAsc(jobId);
|
var allTasks = taskAssignmentService.findTasksForJob(jobId);
|
||||||
if (allTasks.isEmpty()) {
|
if (allTasks.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package de.assecutor.votianlt.model;
|
package de.assecutor.votianlt.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonGetter;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import de.assecutor.votianlt.model.task.BaseTask;
|
import de.assecutor.votianlt.model.task.BaseTask;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
import org.springframework.data.mongodb.core.mapping.Field;
|
import org.springframework.data.mongodb.core.mapping.Field;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -21,6 +24,10 @@ import java.util.List;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class DeliveryStation {
|
public class DeliveryStation {
|
||||||
|
|
||||||
|
@Field("station_id")
|
||||||
|
@JsonIgnore
|
||||||
|
private ObjectId stationId;
|
||||||
|
|
||||||
@Field("station_order")
|
@Field("station_order")
|
||||||
private int stationOrder;
|
private int stationOrder;
|
||||||
|
|
||||||
@@ -62,4 +69,16 @@ public class DeliveryStation {
|
|||||||
|
|
||||||
@Field("tasks")
|
@Field("tasks")
|
||||||
private List<BaseTask> tasks = new ArrayList<>();
|
private List<BaseTask> tasks = new ArrayList<>();
|
||||||
|
|
||||||
|
@JsonGetter("stationId")
|
||||||
|
public String getStationIdAsString() {
|
||||||
|
return stationId != null ? stationId.toHexString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectId ensureStationId() {
|
||||||
|
if (stationId == null) {
|
||||||
|
stationId = new ObjectId();
|
||||||
|
}
|
||||||
|
return stationId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ public class TaskEntry {
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private ObjectId id;
|
private ObjectId id;
|
||||||
|
|
||||||
|
@Field("station_id")
|
||||||
|
@JsonIgnore
|
||||||
|
private ObjectId stationId;
|
||||||
|
|
||||||
@Field("job_id")
|
@Field("job_id")
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private ObjectId jobId;
|
private ObjectId jobId;
|
||||||
@@ -54,10 +58,16 @@ public class TaskEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the job ObjectId as string for JSON serialization. This ensures that
|
* Returns the station ObjectId as string for JSON serialization.
|
||||||
* the job id is returned as a string instead of ObjectId object.
|
*/
|
||||||
|
@JsonGetter("stationId")
|
||||||
|
public String getStationIdAsString() {
|
||||||
|
return stationId != null ? stationId.toHexString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the legacy job ObjectId as string for internal fallback handling.
|
||||||
*/
|
*/
|
||||||
@JsonGetter("jobId")
|
|
||||||
public String getJobIdAsString() {
|
public String getJobIdAsString() {
|
||||||
return jobId != null ? jobId.toString() : null;
|
return jobId != null ? jobId.toString() : null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ public abstract class BaseTask {
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private ObjectId id;
|
private ObjectId id;
|
||||||
|
|
||||||
|
@Field("station_id")
|
||||||
|
@JsonIgnore
|
||||||
|
private ObjectId stationId;
|
||||||
|
|
||||||
@Field("job_id")
|
@Field("job_id")
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private ObjectId jobId;
|
private ObjectId jobId;
|
||||||
@@ -62,9 +66,16 @@ public abstract class BaseTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the job ObjectId as string for JSON serialization.
|
* Returns the station ObjectId as string for JSON serialization.
|
||||||
|
*/
|
||||||
|
@JsonGetter("stationId")
|
||||||
|
public String getStationIdAsString() {
|
||||||
|
return stationId != null ? stationId.toHexString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the legacy job ObjectId as string for internal fallback handling.
|
||||||
*/
|
*/
|
||||||
@JsonGetter("jobId")
|
|
||||||
public String getJobIdAsString() {
|
public String getJobIdAsString() {
|
||||||
return jobId != null ? jobId.toString() : null;
|
return jobId != null ? jobId.toString() : null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,6 +129,8 @@ public final class MainLayout extends AppLayout {
|
|||||||
// Add children to "Verwaltung"
|
// Add children to "Verwaltung"
|
||||||
treeData.addItem(verwaltungItem,
|
treeData.addItem(verwaltungItem,
|
||||||
new MenuTreeItem(getTranslation("nav.jobs"), "jobs", VaadinIcon.CLIPBOARD_TEXT));
|
new MenuTreeItem(getTranslation("nav.jobs"), "jobs", VaadinIcon.CLIPBOARD_TEXT));
|
||||||
|
treeData.addItem(verwaltungItem,
|
||||||
|
new MenuTreeItem(getTranslation("nav.invoices"), "invoices", VaadinIcon.FILE_TEXT));
|
||||||
treeData.addItem(verwaltungItem,
|
treeData.addItem(verwaltungItem,
|
||||||
new MenuTreeItem(getTranslation("nav.customers"), "customers", VaadinIcon.USERS));
|
new MenuTreeItem(getTranslation("nav.customers"), "customers", VaadinIcon.USERS));
|
||||||
treeData.addItem(verwaltungItem,
|
treeData.addItem(verwaltungItem,
|
||||||
@@ -139,8 +141,6 @@ public final class MainLayout extends AppLayout {
|
|||||||
// Add children to "Benutzer"
|
// Add children to "Benutzer"
|
||||||
treeData.addItem(benutzerItem,
|
treeData.addItem(benutzerItem,
|
||||||
new MenuTreeItem(getTranslation("nav.profile"), "edit-profile", VaadinIcon.USER));
|
new MenuTreeItem(getTranslation("nav.profile"), "edit-profile", VaadinIcon.USER));
|
||||||
treeData.addItem(benutzerItem,
|
|
||||||
new MenuTreeItem(getTranslation("nav.myinvoices"), "my-invoices", VaadinIcon.FILE_TEXT));
|
|
||||||
treeData.addItem(benutzerItem,
|
treeData.addItem(benutzerItem,
|
||||||
new MenuTreeItem(getTranslation("nav.imprint"), "impressum", VaadinIcon.INFO_CIRCLE));
|
new MenuTreeItem(getTranslation("nav.imprint"), "impressum", VaadinIcon.INFO_CIRCLE));
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import de.assecutor.votianlt.repository.CargoItemRepository;
|
|||||||
import de.assecutor.votianlt.service.ClientConnectionService;
|
import de.assecutor.votianlt.service.ClientConnectionService;
|
||||||
import de.assecutor.votianlt.service.JobHistoryService;
|
import de.assecutor.votianlt.service.JobHistoryService;
|
||||||
import de.assecutor.votianlt.service.EmailService;
|
import de.assecutor.votianlt.service.EmailService;
|
||||||
|
import de.assecutor.votianlt.service.TaskAssignmentService;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -42,6 +43,7 @@ public class AddJobService {
|
|||||||
private final EmailService emailService;
|
private final EmailService emailService;
|
||||||
private final ClientConnectionService clientConnectionService;
|
private final ClientConnectionService clientConnectionService;
|
||||||
private final MessagingPublisher messagingPublisher;
|
private final MessagingPublisher messagingPublisher;
|
||||||
|
private final TaskAssignmentService taskAssignmentService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Speichert einen neuen Auftrag samt CargoItems und Tasks
|
* Speichert einen neuen Auftrag samt CargoItems und Tasks
|
||||||
@@ -65,6 +67,8 @@ public class AddJobService {
|
|||||||
job.setJobNumber(generateJobNumber());
|
job.setJobNumber(generateJobNumber());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureDeliveryStationIds(job);
|
||||||
|
|
||||||
// Auftrag speichern
|
// Auftrag speichern
|
||||||
Job savedJob = jobRepository.save(job);
|
Job savedJob = jobRepository.save(job);
|
||||||
final ObjectId jobId = savedJob.getId();
|
final ObjectId jobId = savedJob.getId();
|
||||||
@@ -91,15 +95,24 @@ public class AddJobService {
|
|||||||
// Tasks separat speichern und referenzieren mit korrekter Nummerierung
|
// Tasks separat speichern und referenzieren mit korrekter Nummerierung
|
||||||
if (transientTasks != null && !transientTasks.isEmpty()) {
|
if (transientTasks != null && !transientTasks.isEmpty()) {
|
||||||
var filteredTasks = transientTasks.stream().filter(Objects::nonNull)
|
var filteredTasks = transientTasks.stream().filter(Objects::nonNull)
|
||||||
.filter(task -> task.getTaskType() != null) // Filter nach TaskType statt Text
|
.filter(task -> task.getTaskType() != null).toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
Map<Integer, Integer> taskOrderByStation = new HashMap<>();
|
Map<Integer, Integer> taskOrderByStation = new HashMap<>();
|
||||||
|
Map<Integer, ObjectId> stationIdByOrder = buildStationIdByOrder(savedJob);
|
||||||
|
List<BaseTask> tasksToPersist = new ArrayList<>();
|
||||||
|
|
||||||
// Setze JobId und stelle sicher, dass taskOrder je Lieferstation korrekt ist
|
// Setze stationId und stelle sicher, dass taskOrder je Lieferstation korrekt ist
|
||||||
for (BaseTask task : filteredTasks) {
|
for (BaseTask task : filteredTasks) {
|
||||||
task.setJobId(jobId);
|
|
||||||
int stationOrder = task.getStationOrder() != null ? task.getStationOrder() : 0;
|
int stationOrder = task.getStationOrder() != null ? task.getStationOrder() : 0;
|
||||||
|
ObjectId stationId = task.getStationId() != null ? task.getStationId() : stationIdByOrder.get(stationOrder);
|
||||||
|
if (stationId == null) {
|
||||||
|
log.warn("Skipping task without resolvable stationId for job {} and stationOrder {}", jobId,
|
||||||
|
stationOrder);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
task.setStationId(stationId);
|
||||||
|
task.setJobId(null);
|
||||||
if (task.getTaskOrder() == null) {
|
if (task.getTaskOrder() == null) {
|
||||||
int nextTaskOrder = taskOrderByStation.getOrDefault(stationOrder, 0);
|
int nextTaskOrder = taskOrderByStation.getOrDefault(stationOrder, 0);
|
||||||
task.setTaskOrder(nextTaskOrder);
|
task.setTaskOrder(nextTaskOrder);
|
||||||
@@ -109,12 +122,13 @@ public class AddJobService {
|
|||||||
task.getTaskOrder() + 1);
|
task.getTaskOrder() + 1);
|
||||||
taskOrderByStation.put(stationOrder, nextTaskOrder);
|
taskOrderByStation.put(stationOrder, nextTaskOrder);
|
||||||
}
|
}
|
||||||
|
tasksToPersist.add(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
taskRepository.saveAll(filteredTasks);
|
taskRepository.saveAll(tasksToPersist);
|
||||||
attachTasksToDeliveryStations(savedJob, filteredTasks);
|
attachTasksToDeliveryStations(savedJob, tasksToPersist);
|
||||||
savedJob = jobRepository.save(savedJob);
|
savedJob = jobRepository.save(savedJob);
|
||||||
log.info("Saved {} tasks for job {} with ordering", filteredTasks.size(), jobId);
|
log.info("Saved {} tasks for job {} with station-based assignment", tasksToPersist.size(), jobId);
|
||||||
} else if (savedJob.getDeliveryStations() != null && !savedJob.getDeliveryStations().isEmpty()) {
|
} else if (savedJob.getDeliveryStations() != null && !savedJob.getDeliveryStations().isEmpty()) {
|
||||||
attachTasksToDeliveryStations(savedJob, List.of());
|
attachTasksToDeliveryStations(savedJob, List.of());
|
||||||
savedJob = jobRepository.save(savedJob);
|
savedJob = jobRepository.save(savedJob);
|
||||||
@@ -220,7 +234,7 @@ public class AddJobService {
|
|||||||
try {
|
try {
|
||||||
// Lade CargoItems und Tasks für den Job
|
// Lade CargoItems und Tasks für den Job
|
||||||
List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId());
|
List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId());
|
||||||
List<BaseTask> tasks = taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId());
|
List<BaseTask> tasks = taskAssignmentService.findTasksForJob(job);
|
||||||
|
|
||||||
// Erstelle DTO mit allen Daten
|
// Erstelle DTO mit allen Daten
|
||||||
JobWithRelatedDataDTO jobData = new JobWithRelatedDataDTO(job, cargoItems, tasks);
|
JobWithRelatedDataDTO jobData = new JobWithRelatedDataDTO(job, cargoItems, tasks);
|
||||||
@@ -238,20 +252,54 @@ public class AddJobService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<Integer, List<BaseTask>> tasksByStation = new HashMap<>();
|
Map<String, List<BaseTask>> tasksByStationId = new HashMap<>();
|
||||||
|
Map<Integer, List<BaseTask>> legacyTasksByStationOrder = new HashMap<>();
|
||||||
for (BaseTask task : tasks) {
|
for (BaseTask task : tasks) {
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (task.getStationId() != null) {
|
||||||
|
tasksByStationId.computeIfAbsent(task.getStationId().toHexString(), ignored -> new ArrayList<>()).add(task);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
int stationOrder = task.getStationOrder() != null ? task.getStationOrder() : 0;
|
int stationOrder = task.getStationOrder() != null ? task.getStationOrder() : 0;
|
||||||
tasksByStation.computeIfAbsent(stationOrder, ignored -> new ArrayList<>()).add(task);
|
legacyTasksByStationOrder.computeIfAbsent(stationOrder, ignored -> new ArrayList<>()).add(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (DeliveryStation station : job.getDeliveryStations()) {
|
for (DeliveryStation station : job.getDeliveryStations()) {
|
||||||
int stationOrder = station.getStationOrder();
|
String stationKey = station.getStationId() != null ? station.getStationId().toHexString() : null;
|
||||||
List<BaseTask> stationTasks = new ArrayList<>(tasksByStation.getOrDefault(stationOrder, List.of()));
|
List<BaseTask> stationTasks = stationKey != null
|
||||||
|
? new ArrayList<>(tasksByStationId.getOrDefault(stationKey, List.of()))
|
||||||
|
: new ArrayList<>(legacyTasksByStationOrder.getOrDefault(station.getStationOrder(), List.of()));
|
||||||
stationTasks.sort(Comparator.comparing(task -> task.getTaskOrder() != null ? task.getTaskOrder() : 0));
|
stationTasks.sort(Comparator.comparing(task -> task.getTaskOrder() != null ? task.getTaskOrder() : 0));
|
||||||
station.setTasks(stationTasks);
|
station.setTasks(stationTasks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ensureDeliveryStationIds(Job job) {
|
||||||
|
if (job.getDeliveryStations() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (DeliveryStation station : job.getDeliveryStations()) {
|
||||||
|
if (station != null) {
|
||||||
|
station.ensureStationId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, ObjectId> buildStationIdByOrder(Job job) {
|
||||||
|
Map<Integer, ObjectId> stationIdByOrder = new HashMap<>();
|
||||||
|
if (job.getDeliveryStations() == null) {
|
||||||
|
return stationIdByOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (DeliveryStation station : job.getDeliveryStations()) {
|
||||||
|
if (station != null && station.getStationId() != null) {
|
||||||
|
stationIdByOrder.put(station.getStationOrder(), station.getStationId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stationIdByOrder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,25 +2,22 @@ package de.assecutor.votianlt.pages.view;
|
|||||||
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
import com.vaadin.flow.component.grid.Grid;
|
||||||
import com.vaadin.flow.component.html.H2;
|
import com.vaadin.flow.component.html.H2;
|
||||||
|
import com.vaadin.flow.component.html.Span;
|
||||||
import com.vaadin.flow.component.notification.Notification;
|
import com.vaadin.flow.component.notification.Notification;
|
||||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
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.component.UI;
|
import com.vaadin.flow.component.UI;
|
||||||
import de.assecutor.votianlt.model.invoices.SystemInvoice;
|
import de.assecutor.votianlt.model.invoices.CustomerInvoice;
|
||||||
import de.assecutor.votianlt.model.invoices.SystemInvoiceData;
|
import de.assecutor.votianlt.repository.CustomerInvoiceRepository;
|
||||||
import de.assecutor.votianlt.model.invoices.SystemInvoiceItem;
|
import de.assecutor.votianlt.security.SecurityService;
|
||||||
import de.assecutor.votianlt.service.SystemInvoiceService;
|
|
||||||
import de.assecutor.votianlt.util.DateTimeFormatUtil;
|
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import com.vaadin.flow.server.StreamResource;
|
import com.vaadin.flow.server.StreamResource;
|
||||||
import com.vaadin.flow.server.StreamRegistration;
|
import com.vaadin.flow.server.StreamRegistration;
|
||||||
@@ -29,12 +26,13 @@ import com.vaadin.flow.server.StreamRegistration;
|
|||||||
@RolesAllowed({ "USER", "ADMIN" })
|
@RolesAllowed({ "USER", "ADMIN" })
|
||||||
public class InvoicesView extends VerticalLayout implements HasDynamicTitle {
|
public class InvoicesView extends VerticalLayout implements HasDynamicTitle {
|
||||||
|
|
||||||
private final Grid<SystemInvoice> invoiceGrid;
|
private final Grid<CustomerInvoice> invoiceGrid;
|
||||||
|
private final CustomerInvoiceRepository customerInvoiceRepository;
|
||||||
|
private final SecurityService securityService;
|
||||||
|
|
||||||
private final SystemInvoiceService systemInvoiceService;
|
public InvoicesView(CustomerInvoiceRepository customerInvoiceRepository, SecurityService securityService) {
|
||||||
|
this.customerInvoiceRepository = customerInvoiceRepository;
|
||||||
public InvoicesView(SystemInvoiceService systemInvoiceService) {
|
this.securityService = securityService;
|
||||||
this.systemInvoiceService = systemInvoiceService;
|
|
||||||
|
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
setPadding(true);
|
setPadding(true);
|
||||||
@@ -45,49 +43,73 @@ public class InvoicesView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
H2 title = new H2(getTranslation("invoices.title"));
|
H2 title = new H2(getTranslation("invoices.title"));
|
||||||
add(title);
|
add(title);
|
||||||
|
|
||||||
invoiceGrid = new Grid<>(SystemInvoice.class, false);
|
invoiceGrid = new Grid<>(CustomerInvoice.class, false);
|
||||||
invoiceGrid.addColumn(SystemInvoice::getId).setHeader(getTranslation("invoices.column.number"))
|
invoiceGrid.setWidthFull();
|
||||||
|
invoiceGrid.addColumn(invoice -> firstNonBlank(invoice.getInvoiceNumber(), invoice.getId()))
|
||||||
|
.setHeader(getTranslation("invoices.column.number"))
|
||||||
.setAutoWidth(true);
|
.setAutoWidth(true);
|
||||||
invoiceGrid.addColumn(SystemInvoice::getKunde).setHeader(getTranslation("invoices.column.customer"))
|
invoiceGrid.addColumn(this::getRecipientLabel).setHeader(getTranslation("invoices.column.customer"))
|
||||||
.setAutoWidth(true);
|
.setAutoWidth(true);
|
||||||
invoiceGrid.addColumn(SystemInvoice::getDatum).setHeader(getTranslation("invoices.column.date"))
|
invoiceGrid.addColumn(invoice -> Optional.ofNullable(invoice.getInvoiceDate()).map(Object::toString).orElse(""))
|
||||||
|
.setHeader(getTranslation("invoices.column.date"))
|
||||||
.setAutoWidth(true);
|
.setAutoWidth(true);
|
||||||
invoiceGrid.addColumn(SystemInvoice::getBetrag).setHeader(getTranslation("invoices.column.amount"))
|
invoiceGrid.addColumn(this::formatAmount).setHeader(getTranslation("invoices.column.amount"))
|
||||||
.setAutoWidth(true);
|
.setAutoWidth(true);
|
||||||
invoiceGrid.addColumn(SystemInvoice::getBeschreibung).setHeader(getTranslation("invoices.column.description"))
|
invoiceGrid.addColumn(invoice -> firstNonBlank(invoice.getDescription(), ""))
|
||||||
|
.setHeader(getTranslation("invoices.column.description"))
|
||||||
.setAutoWidth(true);
|
.setAutoWidth(true);
|
||||||
invoiceGrid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
invoiceGrid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
||||||
invoiceGrid.getStyle().set("cursor", "pointer");
|
invoiceGrid.getStyle().set("cursor", "pointer");
|
||||||
|
|
||||||
// Testdaten
|
|
||||||
List<SystemInvoice> testSystemInvoices = List.of(
|
|
||||||
new SystemInvoice("R-2024-001", "Max Mustermann", LocalDate.now().minusDays(2), 199.99,
|
|
||||||
"Transport Hamburg-Berlin"),
|
|
||||||
new SystemInvoice("R-2024-002", "Erika Musterfrau", LocalDate.now().minusDays(1), 299.49,
|
|
||||||
"Express München-Köln"),
|
|
||||||
new SystemInvoice("R-2024-003", "Hans Beispiel", LocalDate.now(), 149.00, "Standard Leipzig-Dresden"));
|
|
||||||
invoiceGrid.setItems(testSystemInvoices);
|
|
||||||
|
|
||||||
invoiceGrid.addItemClickListener(event -> {
|
invoiceGrid.addItemClickListener(event -> {
|
||||||
SystemInvoice systemInvoice = event.getItem();
|
CustomerInvoice invoice = event.getItem();
|
||||||
if (systemInvoice != null) {
|
if (invoice != null) {
|
||||||
downloadInvoicePdf(systemInvoice);
|
downloadInvoicePdf(invoice);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
loadInvoices();
|
||||||
add(invoiceGrid);
|
add(invoiceGrid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void downloadInvoicePdf(SystemInvoice systemInvoice) {
|
private void loadInvoices() {
|
||||||
|
String currentUserId = securityService.getCurrentUserId().toHexString();
|
||||||
|
List<CustomerInvoice> invoices = customerInvoiceRepository.findByUserId(currentUserId).stream()
|
||||||
|
.sorted((left, right) -> {
|
||||||
|
if (left.getInvoiceDate() == null && right.getInvoiceDate() == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (left.getInvoiceDate() == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (right.getInvoiceDate() == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return right.getInvoiceDate().compareTo(left.getInvoiceDate());
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
invoiceGrid.setItems(invoices);
|
||||||
|
|
||||||
|
if (invoices.isEmpty()) {
|
||||||
|
Span emptyState = new Span(getTranslation("invoices.empty"));
|
||||||
|
emptyState.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||||
|
add(emptyState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadInvoicePdf(CustomerInvoice invoice) {
|
||||||
try {
|
try {
|
||||||
// PDF generieren mit SystemInvoice (HTML Template)
|
if (invoice.getPdfData() == null || invoice.getPdfData().length == 0) {
|
||||||
byte[] pdfBytes = generateSystemInvoicePdf(systemInvoice);
|
Notification.show(getTranslation("invoices.notification.pdf.missing"), 4000,
|
||||||
StreamResource resource = new StreamResource(systemInvoice.getId() + ".pdf",
|
Notification.Position.MIDDLE);
|
||||||
() -> new ByteArrayInputStream(pdfBytes));
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamResource resource = new StreamResource(firstNonBlank(invoice.getInvoiceNumber(), invoice.getId()) + ".pdf",
|
||||||
|
() -> new ByteArrayInputStream(invoice.getPdfData()));
|
||||||
resource.setContentType("application/pdf");
|
resource.setContentType("application/pdf");
|
||||||
resource.setCacheTime(0);
|
resource.setCacheTime(0);
|
||||||
|
|
||||||
// Direkter Download über UI
|
|
||||||
StreamRegistration registration = UI.getCurrent().getSession().getResourceRegistry()
|
StreamRegistration registration = UI.getCurrent().getSession().getResourceRegistry()
|
||||||
.registerResource(resource);
|
.registerResource(resource);
|
||||||
UI.getCurrent().getPage().open(registration.getResourceUri().toString());
|
UI.getCurrent().getPage().open(registration.getResourceUri().toString());
|
||||||
@@ -98,35 +120,25 @@ public class InvoicesView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] generateSystemInvoicePdf(SystemInvoice systemInvoice) throws Exception {
|
private String getRecipientLabel(CustomerInvoice invoice) {
|
||||||
NumberFormat CURRENCY_FMT = NumberFormat.getCurrencyInstance(Locale.GERMANY);
|
return firstNonBlank(invoice.getRecipientCompany(), invoice.getRecipientName(), "");
|
||||||
|
}
|
||||||
|
|
||||||
SystemInvoiceData data = new SystemInvoiceData();
|
private String formatAmount(CustomerInvoice invoice) {
|
||||||
data.setInvoiceNumber(systemInvoice.getId());
|
var amount = invoice.getTotalAmount() != null ? invoice.getTotalAmount() : invoice.getNetAmount();
|
||||||
data.setInvoiceDate(DateTimeFormatUtil.formatDate(systemInvoice.getDatum()));
|
if (amount == null) {
|
||||||
data.setInvoiceText(systemInvoice.getBeschreibung());
|
return "";
|
||||||
|
}
|
||||||
|
return java.text.NumberFormat.getCurrencyInstance(Locale.GERMANY).format(amount);
|
||||||
|
}
|
||||||
|
|
||||||
// Empfänger aus der Zeile (nur Name in den Testdaten vorhanden)
|
private String firstNonBlank(String... values) {
|
||||||
data.setRecipientName(systemInvoice.getKunde());
|
for (String value : values) {
|
||||||
data.setRecipientDepartment("");
|
if (value != null && !value.isBlank()) {
|
||||||
data.setRecipientStreet("");
|
return value;
|
||||||
data.setRecipientCity("");
|
}
|
||||||
|
}
|
||||||
// Eine Position mit dem Betrag/Beschreibung
|
return "";
|
||||||
List<SystemInvoiceItem> items = new ArrayList<>();
|
|
||||||
String netStr = CURRENCY_FMT.format(systemInvoice.getBetrag());
|
|
||||||
items.add(new SystemInvoiceItem("1", systemInvoice.getBeschreibung(), netStr, netStr));
|
|
||||||
data.setInvoiceItems(items);
|
|
||||||
|
|
||||||
// Summen berechnen (Betrag als Nettobetrag interpretieren)
|
|
||||||
double net = systemInvoice.getBetrag();
|
|
||||||
double vat = Math.round(net * 0.19 * 100.0) / 100.0;
|
|
||||||
double total = net + vat;
|
|
||||||
data.setNetAmount(CURRENCY_FMT.format(net));
|
|
||||||
data.setVatAmount(CURRENCY_FMT.format(vat));
|
|
||||||
data.setTotalAmount(CURRENCY_FMT.format(total));
|
|
||||||
|
|
||||||
return systemInvoiceService.generateInvoicePdfFromHtml(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import de.assecutor.votianlt.repository.CargoItemRepository;
|
import de.assecutor.votianlt.repository.CargoItemRepository;
|
||||||
import de.assecutor.votianlt.repository.JobRepository;
|
import de.assecutor.votianlt.repository.JobRepository;
|
||||||
import de.assecutor.votianlt.repository.TaskRepository;
|
|
||||||
import de.assecutor.votianlt.repository.SignatureRepository;
|
import de.assecutor.votianlt.repository.SignatureRepository;
|
||||||
import de.assecutor.votianlt.repository.BarcodeRepository;
|
import de.assecutor.votianlt.repository.BarcodeRepository;
|
||||||
import de.assecutor.votianlt.repository.PhotoRepository;
|
import de.assecutor.votianlt.repository.PhotoRepository;
|
||||||
@@ -58,6 +57,7 @@ import de.assecutor.votianlt.service.JobHistoryService;
|
|||||||
import de.assecutor.votianlt.service.JobUpdateBroadcaster;
|
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.service.TaskAssignmentService;
|
||||||
import de.assecutor.votianlt.util.DateTimeFormatUtil;
|
import de.assecutor.votianlt.util.DateTimeFormatUtil;
|
||||||
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
|
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
@@ -78,7 +78,6 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
|
|
||||||
private final JobRepository jobRepository;
|
private final JobRepository jobRepository;
|
||||||
private final CargoItemRepository cargoItemRepository;
|
private final CargoItemRepository cargoItemRepository;
|
||||||
private final TaskRepository taskRepository;
|
|
||||||
private final SignatureRepository signatureRepository;
|
private final SignatureRepository signatureRepository;
|
||||||
private final BarcodeRepository barcodeRepository;
|
private final BarcodeRepository barcodeRepository;
|
||||||
private final PhotoRepository photoRepository;
|
private final PhotoRepository photoRepository;
|
||||||
@@ -88,6 +87,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
private final JobUpdateBroadcaster jobUpdateBroadcaster;
|
private final JobUpdateBroadcaster jobUpdateBroadcaster;
|
||||||
private final LocationService locationService;
|
private final LocationService locationService;
|
||||||
private final ServiceRepository serviceRepository;
|
private final ServiceRepository serviceRepository;
|
||||||
|
private final TaskAssignmentService taskAssignmentService;
|
||||||
|
|
||||||
@Value("${app.google.maps.api-key}")
|
@Value("${app.google.maps.api-key}")
|
||||||
private String googleMapsApiKey;
|
private String googleMapsApiKey;
|
||||||
@@ -96,16 +96,16 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
private final List<Div> taskCards = new ArrayList<>();
|
private final List<Div> taskCards = new ArrayList<>();
|
||||||
private Registration jobUpdateRegistration;
|
private Registration jobUpdateRegistration;
|
||||||
private ObjectId currentJobId;
|
private ObjectId currentJobId;
|
||||||
|
private Div stationTilesSection;
|
||||||
|
|
||||||
public JobSummaryView(JobRepository jobRepository, CargoItemRepository cargoItemRepository,
|
public JobSummaryView(JobRepository jobRepository, CargoItemRepository cargoItemRepository,
|
||||||
TaskRepository taskRepository, SignatureRepository signatureRepository, BarcodeRepository barcodeRepository,
|
SignatureRepository signatureRepository, BarcodeRepository barcodeRepository,
|
||||||
PhotoRepository photoRepository, CommentRepository commentRepository, AppUserService appUserService,
|
PhotoRepository photoRepository, CommentRepository commentRepository, AppUserService appUserService,
|
||||||
MessageService messageService, JobHistoryService jobHistoryService,
|
MessageService messageService, JobHistoryService jobHistoryService,
|
||||||
JobUpdateBroadcaster jobUpdateBroadcaster, LocationService locationService,
|
JobUpdateBroadcaster jobUpdateBroadcaster, LocationService locationService,
|
||||||
ServiceRepository serviceRepository) {
|
ServiceRepository serviceRepository, TaskAssignmentService taskAssignmentService) {
|
||||||
this.jobRepository = jobRepository;
|
this.jobRepository = jobRepository;
|
||||||
this.cargoItemRepository = cargoItemRepository;
|
this.cargoItemRepository = cargoItemRepository;
|
||||||
this.taskRepository = taskRepository;
|
|
||||||
this.signatureRepository = signatureRepository;
|
this.signatureRepository = signatureRepository;
|
||||||
this.barcodeRepository = barcodeRepository;
|
this.barcodeRepository = barcodeRepository;
|
||||||
this.photoRepository = photoRepository;
|
this.photoRepository = photoRepository;
|
||||||
@@ -115,6 +115,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
this.jobUpdateBroadcaster = jobUpdateBroadcaster;
|
this.jobUpdateBroadcaster = jobUpdateBroadcaster;
|
||||||
this.locationService = locationService;
|
this.locationService = locationService;
|
||||||
this.serviceRepository = serviceRepository;
|
this.serviceRepository = serviceRepository;
|
||||||
|
this.taskAssignmentService = taskAssignmentService;
|
||||||
|
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
|
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
|
||||||
@@ -159,7 +160,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
if (currentJobId == null || jobId == null || !currentJobId.equals(jobId)) {
|
if (currentJobId == null || jobId == null || !currentJobId.equals(jobId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ui.access(this::refreshCurrentJobSummary);
|
ui.access(this::refreshStationTilesOnly);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,16 +221,35 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
add(new ViewToolbar("Zusammenfassung", sendMessageButton, jobHistoryButton));
|
add(new ViewToolbar("Zusammenfassung", sendMessageButton, jobHistoryButton));
|
||||||
|
|
||||||
List<CargoItem> cargo = cargoItemRepository.findByJobId(currentJobId);
|
List<CargoItem> cargo = cargoItemRepository.findByJobId(currentJobId);
|
||||||
List<BaseTask> tasks = taskRepository.findByJobIdOrderByTaskOrderAsc(currentJobId);
|
List<BaseTask> tasks = taskAssignmentService.findTasksForJob(job);
|
||||||
|
|
||||||
render(job, cargo, tasks);
|
render(job, cargo, tasks);
|
||||||
add(content);
|
add(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void refreshStationTilesOnly() {
|
||||||
|
if (currentJobId == null || stationTilesSection == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Job job = jobRepository.findById(currentJobId).orElse(null);
|
||||||
|
if (job == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CargoItem> cargo = cargoItemRepository.findByJobId(currentJobId);
|
||||||
|
List<BaseTask> tasks = taskAssignmentService.findTasksForJob(job);
|
||||||
|
|
||||||
|
Div updatedSection = createStationTilesSection(job, cargo, tasks);
|
||||||
|
content.replace(stationTilesSection, updatedSection);
|
||||||
|
stationTilesSection = updatedSection;
|
||||||
|
}
|
||||||
|
|
||||||
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, cargoItems, tasks));
|
stationTilesSection = createStationTilesSection(job, cargoItems, tasks);
|
||||||
|
content.add(stationTilesSection);
|
||||||
|
|
||||||
// Fracht und weitere Infos
|
// Fracht und weitere Infos
|
||||||
HorizontalLayout midRow = new HorizontalLayout();
|
HorizontalLayout midRow = new HorizontalLayout();
|
||||||
@@ -600,27 +620,21 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<BaseTask> getTasksForStation(DeliveryStation station, List<BaseTask> tasks, boolean legacyMode) {
|
private List<BaseTask> getTasksForStation(DeliveryStation station, List<BaseTask> tasks, boolean legacyMode) {
|
||||||
if (!legacyMode && station != null && station.getTasks() != null && !station.getTasks().isEmpty()) {
|
|
||||||
return station.getTasks().stream()
|
|
||||||
.filter(task -> task != null && task.getDisplayName() != null && !task.getDisplayName().isBlank())
|
|
||||||
.sorted((left, right) -> Integer.compare(left.getTaskOrder() != null ? left.getTaskOrder() : 0,
|
|
||||||
right.getTaskOrder() != null ? right.getTaskOrder() : 0))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tasks == null || tasks.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<BaseTask> stationTasks = new ArrayList<>();
|
|
||||||
int stationOrder = station != null ? station.getStationOrder() : 0;
|
int stationOrder = station != null ? station.getStationOrder() : 0;
|
||||||
|
String stationId = station != null ? station.getStationIdAsString() : null;
|
||||||
|
|
||||||
|
if (tasks != null && !tasks.isEmpty()) {
|
||||||
|
List<BaseTask> stationTasks = new ArrayList<>();
|
||||||
for (BaseTask task : tasks) {
|
for (BaseTask task : tasks) {
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String taskStationId = task.getStationIdAsString();
|
||||||
Integer taskStationOrder = task.getStationOrder();
|
Integer taskStationOrder = task.getStationOrder();
|
||||||
if (legacyMode) {
|
if (stationId != null && stationId.equals(taskStationId)) {
|
||||||
|
stationTasks.add(task);
|
||||||
|
} else if (legacyMode) {
|
||||||
if (taskStationOrder == null || taskStationOrder == stationOrder) {
|
if (taskStationOrder == null || taskStationOrder == stationOrder) {
|
||||||
stationTasks.add(task);
|
stationTasks.add(task);
|
||||||
}
|
}
|
||||||
@@ -628,7 +642,24 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
stationTasks.add(task);
|
stationTasks.add(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return stationTasks;
|
if (!stationTasks.isEmpty()) {
|
||||||
|
return sortVisibleTasks(stationTasks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!legacyMode && station != null && station.getTasks() != null && !station.getTasks().isEmpty()) {
|
||||||
|
return sortVisibleTasks(station.getTasks());
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BaseTask> sortVisibleTasks(List<BaseTask> tasks) {
|
||||||
|
return tasks.stream()
|
||||||
|
.filter(task -> task != null && task.getDisplayName() != null && !task.getDisplayName().isBlank())
|
||||||
|
.sorted((left, right) -> Integer.compare(left.getTaskOrder() != null ? left.getTaskOrder() : 0,
|
||||||
|
right.getTaskOrder() != null ? right.getTaskOrder() : 0))
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean areAllTasksCompleted(List<BaseTask> tasks) {
|
private boolean areAllTasksCompleted(List<BaseTask> tasks) {
|
||||||
|
|||||||
@@ -100,6 +100,9 @@ public interface JobRepository extends MongoRepository<Job, ObjectId> {
|
|||||||
@Query("{'app_user': ?0, 'status': {'$nin': ['COMPLETED', 'CANCELLED']}}")
|
@Query("{'app_user': ?0, 'status': {'$nin': ['COMPLETED', 'CANCELLED']}}")
|
||||||
List<Job> findByAppUser(String appUser);
|
List<Job> findByAppUser(String appUser);
|
||||||
|
|
||||||
|
@Query("{'delivery_stations.station_id': ?0}")
|
||||||
|
Optional<Job> findByDeliveryStationsStationId(ObjectId stationId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Findet Aufträge anhand einer partiellen Auftragsnummer (case-insensitive)
|
* Findet Aufträge anhand einer partiellen Auftragsnummer (case-insensitive)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import org.springframework.data.mongodb.repository.MongoRepository;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface TaskRepository extends MongoRepository<BaseTask, ObjectId> {
|
public interface TaskRepository extends MongoRepository<BaseTask, ObjectId> {
|
||||||
|
List<BaseTask> findByStationIdOrderByTaskOrderAsc(ObjectId stationId);
|
||||||
|
|
||||||
|
List<BaseTask> findByStationIdIn(List<ObjectId> stationIds);
|
||||||
|
|
||||||
List<BaseTask> findByJobIdOrderByTaskOrderAsc(ObjectId jobId);
|
List<BaseTask> findByJobIdOrderByTaskOrderAsc(ObjectId jobId);
|
||||||
|
|
||||||
List<BaseTask> findByJobIdAndStationOrderOrderByTaskOrderAsc(ObjectId jobId, int stationOrder);
|
List<BaseTask> findByJobIdAndStationOrderOrderByTaskOrderAsc(ObjectId jobId, int stationOrder);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public class EmailService {
|
|||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final JobRepository jobRepository;
|
private final JobRepository jobRepository;
|
||||||
private final TaskRepository taskRepository;
|
private final TaskRepository taskRepository;
|
||||||
|
private final TaskAssignmentService taskAssignmentService;
|
||||||
private final JavaMailSender mailSender;
|
private final JavaMailSender mailSender;
|
||||||
|
|
||||||
@Value("${spring.mail.username}")
|
@Value("${spring.mail.username}")
|
||||||
@@ -194,7 +195,7 @@ public class EmailService {
|
|||||||
String appUserName = buildAppUserName(user);
|
String appUserName = buildAppUserName(user);
|
||||||
|
|
||||||
// Count completed tasks
|
// Count completed tasks
|
||||||
var allTasks = taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId());
|
var allTasks = taskAssignmentService.findTasksForJob(job);
|
||||||
int taskCount = allTasks.size();
|
int taskCount = allTasks.size();
|
||||||
|
|
||||||
StringBuilder body = new StringBuilder();
|
StringBuilder body = new StringBuilder();
|
||||||
@@ -283,7 +284,7 @@ public class EmailService {
|
|||||||
String fullName = buildFullName(user);
|
String fullName = buildFullName(user);
|
||||||
|
|
||||||
// Count tasks for this job
|
// Count tasks for this job
|
||||||
var allTasks = taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId());
|
var allTasks = taskAssignmentService.findTasksForJob(job);
|
||||||
int taskCount = allTasks.size();
|
int taskCount = allTasks.size();
|
||||||
|
|
||||||
StringBuilder body = new StringBuilder();
|
StringBuilder body = new StringBuilder();
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package de.assecutor.votianlt.service;
|
||||||
|
|
||||||
|
import de.assecutor.votianlt.model.DeliveryStation;
|
||||||
|
import de.assecutor.votianlt.model.Job;
|
||||||
|
import de.assecutor.votianlt.model.task.BaseTask;
|
||||||
|
import de.assecutor.votianlt.repository.JobRepository;
|
||||||
|
import de.assecutor.votianlt.repository.TaskRepository;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TaskAssignmentService {
|
||||||
|
|
||||||
|
private final TaskRepository taskRepository;
|
||||||
|
private final JobRepository jobRepository;
|
||||||
|
|
||||||
|
public List<BaseTask> findTasksForJob(Job job) {
|
||||||
|
if (job == null || job.getId() == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, BaseTask> uniqueTasks = new LinkedHashMap<>();
|
||||||
|
List<ObjectId> stationIds = extractStationIds(job);
|
||||||
|
|
||||||
|
if (!stationIds.isEmpty()) {
|
||||||
|
for (BaseTask task : taskRepository.findByStationIdIn(stationIds)) {
|
||||||
|
putIfAbsent(uniqueTasks, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (BaseTask task : taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId())) {
|
||||||
|
putIfAbsent(uniqueTasks, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortTasksForJob(job, new ArrayList<>(uniqueTasks.values()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BaseTask> findTasksForJob(ObjectId jobId) {
|
||||||
|
if (jobId == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return jobRepository.findById(jobId).map(this::findTasksForJob)
|
||||||
|
.orElseGet(() -> sortTasksForJob(null, taskRepository.findByJobIdOrderByTaskOrderAsc(jobId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Job> findJobForTask(BaseTask task) {
|
||||||
|
if (task == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.getStationId() != null) {
|
||||||
|
Optional<Job> jobByStation = jobRepository.findByDeliveryStationsStationId(task.getStationId());
|
||||||
|
if (jobByStation.isPresent()) {
|
||||||
|
return jobByStation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.ofNullable(task.getJobId()).flatMap(jobRepository::findById);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ObjectId> extractStationIds(Job job) {
|
||||||
|
if (job.getDeliveryStations() == null || job.getDeliveryStations().isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return job.getDeliveryStations().stream().filter(Objects::nonNull).map(DeliveryStation::getStationId)
|
||||||
|
.filter(Objects::nonNull).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BaseTask> sortTasksForJob(Job job, List<BaseTask> tasks) {
|
||||||
|
Map<String, Integer> stationOrderById = new LinkedHashMap<>();
|
||||||
|
if (job != null && job.getDeliveryStations() != null) {
|
||||||
|
for (DeliveryStation station : job.getDeliveryStations()) {
|
||||||
|
if (station != null && station.getStationId() != null) {
|
||||||
|
stationOrderById.put(station.getStationId().toHexString(), station.getStationOrder());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tasks.stream().filter(Objects::nonNull)
|
||||||
|
.sorted(Comparator.<BaseTask>comparingInt(task -> resolveStationOrder(task, stationOrderById))
|
||||||
|
.thenComparingInt(task -> task.getTaskOrder() != null ? task.getTaskOrder() : 0))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int resolveStationOrder(BaseTask task, Map<String, Integer> stationOrderById) {
|
||||||
|
if (task.getStationId() != null) {
|
||||||
|
Integer stationOrder = stationOrderById.get(task.getStationId().toHexString());
|
||||||
|
if (stationOrder != null) {
|
||||||
|
return stationOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return task.getStationOrder() != null ? task.getStationOrder() : Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putIfAbsent(Map<String, BaseTask> uniqueTasks, BaseTask task) {
|
||||||
|
if (task == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = task.getId() != null ? task.getId().toHexString()
|
||||||
|
: String.join(":", Optional.ofNullable(task.getStationIdAsString()).orElse(""),
|
||||||
|
Optional.ofNullable(task.getJobIdAsString()).orElse(""),
|
||||||
|
String.valueOf(task.getTaskOrder() != null ? task.getTaskOrder() : 0),
|
||||||
|
Optional.ofNullable(task.getDescription()).orElse(""),
|
||||||
|
Optional.ofNullable(task.getTaskType()).orElse(""));
|
||||||
|
uniqueTasks.putIfAbsent(key, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -682,6 +682,8 @@ invoices.column.customer=Kunde
|
|||||||
invoices.column.date=Datum
|
invoices.column.date=Datum
|
||||||
invoices.column.amount=Betrag
|
invoices.column.amount=Betrag
|
||||||
invoices.column.description=Beschreibung
|
invoices.column.description=Beschreibung
|
||||||
|
invoices.empty=Es wurden noch keine Rechnungen erstellt.
|
||||||
|
invoices.notification.pdf.missing=Für diese Rechnung ist kein PDF gespeichert.
|
||||||
|
|
||||||
# My Invoices
|
# My Invoices
|
||||||
myinvoices.title=Rechnungen
|
myinvoices.title=Rechnungen
|
||||||
@@ -906,6 +908,13 @@ jobhistory.status.pickedup=Abgeholt
|
|||||||
jobhistory.status.intransit=Unterwegs
|
jobhistory.status.intransit=Unterwegs
|
||||||
jobhistory.status.delivered=Zugestellt
|
jobhistory.status.delivered=Zugestellt
|
||||||
jobhistory.image.alt=Vergrößertes Foto
|
jobhistory.image.alt=Vergrößertes Foto
|
||||||
|
jobhistory.title=Jobhistorie
|
||||||
|
jobhistory.header=Jobhistorie für {0}
|
||||||
|
jobhistory.info.customer=Kunde: {0}
|
||||||
|
jobhistory.info.createdat=Erstellt am: {0}
|
||||||
|
jobhistory.info.status=Status: {0}
|
||||||
|
jobhistory.count={0} Einträge in der Historie
|
||||||
|
jobhistory.changedby=Geändert von: {0}
|
||||||
|
|
||||||
# Version
|
# Version
|
||||||
version.label=Version
|
version.label=Version
|
||||||
|
|||||||
@@ -682,6 +682,8 @@ invoices.column.customer=Customer
|
|||||||
invoices.column.date=Date
|
invoices.column.date=Date
|
||||||
invoices.column.amount=Amount
|
invoices.column.amount=Amount
|
||||||
invoices.column.description=Description
|
invoices.column.description=Description
|
||||||
|
invoices.empty=No invoices have been created yet.
|
||||||
|
invoices.notification.pdf.missing=No PDF is stored for this invoice.
|
||||||
|
|
||||||
# My Invoices
|
# My Invoices
|
||||||
myinvoices.title=My Invoices
|
myinvoices.title=My Invoices
|
||||||
@@ -905,6 +907,13 @@ jobhistory.status.pickedup=Picked Up
|
|||||||
jobhistory.status.intransit=In Transit
|
jobhistory.status.intransit=In Transit
|
||||||
jobhistory.status.delivered=Delivered
|
jobhistory.status.delivered=Delivered
|
||||||
jobhistory.image.alt=Enlarged Photo
|
jobhistory.image.alt=Enlarged Photo
|
||||||
|
jobhistory.title=Job History
|
||||||
|
jobhistory.header=Job history for {0}
|
||||||
|
jobhistory.info.customer=Customer: {0}
|
||||||
|
jobhistory.info.createdat=Created at: {0}
|
||||||
|
jobhistory.info.status=Status: {0}
|
||||||
|
jobhistory.count={0} history entries
|
||||||
|
jobhistory.changedby=Changed by: {0}
|
||||||
|
|
||||||
# Version
|
# Version
|
||||||
version.label=Version
|
version.label=Version
|
||||||
|
|||||||
Reference in New Issue
Block a user