diff --git a/src/main/java/de/assecutor/votianlt/config/MongoConfig.java b/src/main/java/de/assecutor/votianlt/config/MongoConfig.java index 9710165..afdc787 100644 --- a/src/main/java/de/assecutor/votianlt/config/MongoConfig.java +++ b/src/main/java/de/assecutor/votianlt/config/MongoConfig.java @@ -128,9 +128,6 @@ public class MongoConfig { if (source.containsKey("completed_by")) { task.setCompletedBy(source.getString("completed_by")); } - if (source.containsKey("completion_note")) { - task.setCompletionNote(source.getString("completion_note")); - } return task; } diff --git a/src/main/java/de/assecutor/votianlt/controller/MessageController.java b/src/main/java/de/assecutor/votianlt/controller/MessageController.java index 770d996..dc4b5e0 100644 --- a/src/main/java/de/assecutor/votianlt/controller/MessageController.java +++ b/src/main/java/de/assecutor/votianlt/controller/MessageController.java @@ -6,7 +6,6 @@ import de.assecutor.votianlt.dto.JobWithRelatedDataDTO; import de.assecutor.votianlt.model.AppUser; import de.assecutor.votianlt.model.CargoItem; import de.assecutor.votianlt.model.Job; -import de.assecutor.votianlt.model.Photo; import de.assecutor.votianlt.model.task.BaseTask; import de.assecutor.votianlt.pages.service.AppUserService; import de.assecutor.votianlt.repository.AppUserRepository; @@ -14,6 +13,11 @@ import de.assecutor.votianlt.repository.CargoItemRepository; import de.assecutor.votianlt.repository.JobRepository; import de.assecutor.votianlt.repository.PhotoRepository; import de.assecutor.votianlt.repository.TaskRepository; +import de.assecutor.votianlt.repository.BarcodeRepository; +import de.assecutor.votianlt.repository.SignatureRepository; +import de.assecutor.votianlt.model.Photo; +import de.assecutor.votianlt.model.Barcode; +import de.assecutor.votianlt.model.Signature; import lombok.extern.slf4j.Slf4j; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; @@ -21,10 +25,10 @@ import de.assecutor.votianlt.mqtt.MqttPublisher; import org.springframework.stereotype.Component; import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.bson.types.ObjectId; /** * MQTT message controller for handling real-time communication with apps. @@ -48,10 +52,11 @@ public class MessageController { private final CargoItemRepository cargoItemRepository; private final TaskRepository taskRepository; - private final PhotoRepository photoRepository; + private final BarcodeRepository barcodeRepository; + private final SignatureRepository signatureRepository; - public MessageController(MqttPublisher mqttPublisher, AppUserRepository appUserRepository, AppUserService appUserService, JobRepository jobRepository, CargoItemRepository cargoItemRepository, TaskRepository taskRepository, PhotoRepository photoRepository) { + public MessageController(MqttPublisher mqttPublisher, AppUserRepository appUserRepository, AppUserService appUserService, JobRepository jobRepository, CargoItemRepository cargoItemRepository, TaskRepository taskRepository, PhotoRepository photoRepository, BarcodeRepository barcodeRepository, SignatureRepository signatureRepository) { this.mqttPublisher = mqttPublisher; this.appUserRepository = appUserRepository; this.appUserService = appUserService; @@ -59,6 +64,8 @@ public class MessageController { this.cargoItemRepository = cargoItemRepository; this.taskRepository = taskRepository; this.photoRepository = photoRepository; + this.barcodeRepository = barcodeRepository; + this.signatureRepository = signatureRepository; } /** @@ -182,197 +189,216 @@ public class MessageController { * This endpoint accepts any task type (fallback for GENERIC or unknown types). */ public void handleTaskCompleted(Map payload) { - log.info("MQTT Endpoint '/app/task/completed' called with data: {}", payload); - processTaskCompletion(payload, null); - } - - /** - * Report task confirmation completion from apps. - * Client sends to /app/task/confirm with payload { taskId, completedBy?, note? }. - * Broadcasts to /topic/task-updates and /topic/tasks/{taskId}. - */ - public void handleTaskConfirmation(Map payload) { - log.info("MQTT Endpoint '/app/task/confirm' called with data: {}", payload); - processTaskCompletion(payload, "CONFIRMATION"); - } - - /** - * Report photo task completion from apps. - * Client sends to /app/task/photo/completed with payload { taskId, completedBy?, note?, extraData? }. - * The extraData contains: { photos: base64List, count: base64List.length } - * Broadcasts to /topic/task-updates and /topic/tasks/{taskId}. - */ - public void handlePhotoTaskCompleted(Map payload) { - log.info("MQTT Endpoint '/app/task/photo/completed' called"); - processPhotoTaskCompletion(payload); - } - - /** - * Specialized method to process photo task completion with extraData handling. - * Saves photo data to the photos collection and processes task completion. - */ - private void processPhotoTaskCompletion(Map payload) { - Map response = new java.util.HashMap<>(); - response.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); - response.put("type", "taskCompletedAck"); - - if (payload == null || !payload.containsKey("taskId") || payload.get("taskId") == null || payload.get("taskId").toString().isBlank()) { - response.put("success", false); - response.put("message", "taskId ist erforderlich"); - log.info("Photo task completion failed: {}", response); - return; - } - - String taskIdStr = payload.get("taskId").toString(); - String completedBy = payload.get("completedBy") != null ? payload.get("completedBy").toString() : null; - String note = payload.get("note") != null ? payload.get("note").toString() : null; - + // Backward-compatible entry point: extract taskType from payload (if present) + // and delegate to the overloaded handler with explicit type. + String taskType = null; try { - org.bson.types.ObjectId taskId = new org.bson.types.ObjectId(taskIdStr); - java.util.Optional opt = taskRepository.findById(taskId); + Object tt = payload != null ? payload.get("taskType") : null; + if (tt != null) taskType = tt.toString(); + } catch (Exception ignored) {} + handleTaskCompleted(payload, taskType); + } + + /** + * Central dispatcher for task_completed messages. Decides handling based on taskType. + * PHOTO and CONFIRMATION are routed to specialized handlers; others go to generic processing. + */ + public void handleTaskCompleted(Map payload, String taskType) { + String key = taskType == null ? "" : taskType.trim().toUpperCase(); + + log.info("handleTaskCompleted called with taskType={}, data: {}", taskType, payload); + + switch (key) { + case "PHOTO" -> { + processPhotoTaskCompletion(payload); + } + case "CONFIRMATION" -> { + processConfirmationTaskCompletion(payload); + } + case "SIGNATURE" -> { + processSignatureTaskCompletion(payload); + } + case "TODOLIST" -> { + processTodoListTaskCompletion(payload); + } + case "BARCODE" -> { + processBarcodeTaskCompletion(payload); + } + default -> { + log.info("ERROR: handleTaskCompleted called with taskType={}, data: {}", taskType, payload); + } + } + } + + private void processConfirmationTaskCompletion(Map payload) { + Object taskId = payload.get("taskId"); + + completeTask(taskId); + } + + private void processTodoListTaskCompletion(Map payload) { + Object taskId = payload.get("taskId"); + + completeTask(taskId); + } + + private void processBarcodeTaskCompletion(Map payload) { + Object taskId = payload.get("taskId"); + try { + var opt = taskRepository.findById(new ObjectId(taskId.toString())); if (opt.isEmpty()) { - response.put("success", false); - response.put("message", "Task nicht gefunden"); + log.warn("Task not found for barcode completion. taskId={}", taskId); return; } - BaseTask task = opt.get(); - // Validate task type is PHOTO - if (!"PHOTO".equals(task.getTaskType())) { - response.put("success", false); - response.put("message", "Task-Typ stimmt nicht mit dem Endpunkt überein. Erwartet: PHOTO, Gefunden: " + task.getTaskType()); - log.warn("Task type mismatch for taskId={}: expected=PHOTO, actual={}", taskIdStr, task.getTaskType()); - return; - } - - // Process extraData if present - if (payload.containsKey("extraData") && payload.get("extraData") != null) { - try { + Object extra = payload.get("extraData"); + if (extra instanceof Map extraData) { + Object barcodesObj = extraData.get("barcodes"); + if (barcodesObj instanceof List barcodesList) { @SuppressWarnings("unchecked") - Map extraData = (Map) payload.get("extraData"); + List barcodes = (List) barcodesList; - if (extraData.containsKey("photos") && extraData.get("photos") != null) { - @SuppressWarnings("unchecked") - List base64Photos = (List) extraData.get("photos"); + if (!barcodes.isEmpty()) { + for (String barcodeString : barcodes) { + Barcode barcodeEntry = new Barcode( + new ObjectId(taskId.toString()), + barcodeString, + task.getCompletedBy() + ); - // Create and save Photo entity - Photo photo = new Photo(task.getJobId(), task.getId(), base64Photos, completedBy); - photoRepository.save(photo); + barcodeRepository.save(barcodeEntry); + } - log.info("Saved {} photos for taskId={}, jobId={}, photoId={}", - base64Photos.size(), taskIdStr, task.getJobIdAsString(), photo.getIdAsString()); - - response.put("photoId", photo.getIdAsString()); - response.put("photosCount", base64Photos.size()); + log.info("Saved {} barcodes for taskId={}", barcodes.size(), taskId); + } else { + log.info("No barcodes found in extraData for taskId={}", taskId); } - } catch (Exception e) { - log.error("Error processing photo extraData for taskId={}: {}", taskIdStr, e.getMessage(), e); - response.put("photoError", "Fehler beim Speichern der Fotos: " + e.getMessage()); + } else { + log.warn("extraData.barcodes is not a List for taskId={}", taskId); } + } else { + log.warn("extraData is not a Map for taskId={}", taskId); } - // Complete the task - task.setCompleted(true); - task.setCompletedAt(LocalDateTime.now()); - if (completedBy != null) task.setCompletedBy(completedBy); - if (note != null) task.setCompletionNote(note); - taskRepository.save(task); - - java.util.Map event = new java.util.HashMap<>(); - event.put("taskId", task.getIdAsString()); - event.put("jobId", task.getJobIdAsString()); - event.put("completed", task.isCompleted()); - event.put("completedAt", task.getCompletedAt() != null ? task.getCompletedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) : null); - event.put("completedBy", task.getCompletedBy()); - event.put("note", task.getCompletionNote()); - event.put("event", "taskCompleted"); - event.put("taskType", task.getTaskType()); - - // Task event publishing has been removed - log.info("Task completed: taskId={}, taskType={}, completed={}", task.getIdAsString(), task.getTaskType(), task.isCompleted()); - - response.put("success", true); - response.putAll(event); - log.info("Photo task completion processed successfully for taskId={}", taskIdStr); - } catch (IllegalArgumentException e) { - response.put("success", false); - response.put("message", "Ungültige taskId"); - } catch (Exception e) { - log.error("Error processing photo task completion", e); - response.put("success", false); - response.put("message", "Fehler bei der Verarbeitung"); + // Finally, mark the task as completed + completeTask(taskId); + } catch (IllegalArgumentException ex) { + log.error("Invalid taskId format for barcode completion: {}", taskId); + } catch (Exception ex) { + log.error("Error while processing barcode task completion (taskId={}): {}", taskId, ex.getMessage(), ex); } } - /** - * Common method to process task completion for different task types. - * This method contains the shared logic for all task completion endpoints. - */ - private void processTaskCompletion(Map payload, String expectedTaskType) { - Map response = new java.util.HashMap<>(); - response.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); - response.put("type", "taskCompletedAck"); - - if (payload == null || !payload.containsKey("taskId") || payload.get("taskId") == null || payload.get("taskId").toString().isBlank()) { - response.put("success", false); - response.put("message", "taskId ist erforderlich"); - log.info("Task completion failed: {}", response); - return; - } - - String taskIdStr = payload.get("taskId").toString(); - String completedBy = payload.get("completedBy") != null ? payload.get("completedBy").toString() : null; - String note = payload.get("note") != null ? payload.get("note").toString() : null; - + private void processSignatureTaskCompletion(Map payload) { + Object taskId = payload.get("taskId"); try { - org.bson.types.ObjectId taskId = new org.bson.types.ObjectId(taskIdStr); - java.util.Optional opt = taskRepository.findById(taskId); + var opt = taskRepository.findById(new ObjectId(taskId.toString())); if (opt.isEmpty()) { - response.put("success", false); - response.put("message", "Task nicht gefunden"); + log.warn("Task not found for signature completion. taskId={}", taskId); return; } - BaseTask task = opt.get(); - // Validate task type matches the endpoint - if (expectedTaskType != null && !expectedTaskType.equals(task.getTaskType())) { - response.put("success", false); - response.put("message", "Task-Typ stimmt nicht mit dem Endpunkt überein. Erwartet: " + expectedTaskType + ", Gefunden: " + task.getTaskType()); - log.warn("Task type mismatch for taskId={}: expected={}, actual={}", taskIdStr, expectedTaskType, task.getTaskType()); - return; + Object extra = payload.get("extraData"); + if (extra instanceof Map extraData) { + Object signatureSvgObj = extraData.get("signatureSvg"); + if (signatureSvgObj instanceof String signatureSvg) { + if (!signatureSvg.isBlank()) { + Signature signatureEntry = new Signature( + new ObjectId(taskId.toString()), + signatureSvg, + task.getCompletedBy() + ); + + signatureRepository.save(signatureEntry); + log.info("Saved signature for taskId={}", taskId); + } else { + log.info("Empty signature SVG found for taskId={}", taskId); + } + } else { + log.warn("extraData.signatureSvg is not a String for taskId={}", taskId); + } + } else { + log.warn("extraData is not a Map for taskId={}", taskId); } + // Finally, mark the task as completed + completeTask(taskId); + } catch (IllegalArgumentException ex) { + log.error("Invalid taskId format for signature completion: {}", taskId); + } catch (Exception ex) { + log.error("Error while processing signature task completion (taskId={}): {}", taskId, ex.getMessage(), ex); + } + } + + private void processPhotoTaskCompletion(Map payload) { + Object taskId = payload.get("taskId"); + try { + var opt = taskRepository.findById(new ObjectId(taskId.toString())); + if (opt.isEmpty()) { + log.warn("Task not found for photo completion. taskId={}", taskId); + return; + } + BaseTask task = opt.get(); + ObjectId jobId = new ObjectId(task.getJobIdAsString()); + + Object extra = payload.get("extraData"); + if (extra instanceof Map extraData) { + Object photosObj = extraData.get("photos"); + if (photosObj instanceof List photosList) { + @SuppressWarnings("unchecked") + List photos = (List) photosList; + + if (!photos.isEmpty()) { + for (String photoString: photos) { + Photo photoEntry = new Photo( + new ObjectId(taskId.toString()), + photoString, + task.getCompletedBy() + ); + + photoRepository.save(photoEntry); + } + + log.info("Saved {} photos for taskId={}, jobId={}", photos.size(), taskId, jobId); + } else { + log.info("No photos found in extraData for taskId={}", taskId); + } + } else { + log.warn("extraData.photos is not a List for taskId={}", taskId); + } + } else { + log.warn("extraData is not a Map for taskId={}", taskId); + } + + // Finally, mark the task as completed + completeTask(taskId); + } catch (IllegalArgumentException ex) { + log.error("Invalid taskId format for photo completion: {}", taskId); + } catch (Exception ex) { + log.error("Error while processing photo task completion (taskId={}): {}", taskId, ex.getMessage(), ex); + } + } + + private void completeTask(Object tid) { + String taskIdStr = tid.toString(); + try { + ObjectId taskId = new ObjectId(taskIdStr); + var opt = taskRepository.findById(taskId); + if (opt.isEmpty()) { + log.warn("Task not found for confirmation completion. taskId={}", taskIdStr); + return; + } + BaseTask task = opt.get(); task.setCompleted(true); task.setCompletedAt(LocalDateTime.now()); - if (completedBy != null) task.setCompletedBy(completedBy); - if (note != null) task.setCompletionNote(note); taskRepository.save(task); - - java.util.Map event = new java.util.HashMap<>(); - event.put("taskId", task.getIdAsString()); - event.put("jobId", task.getJobIdAsString()); - event.put("completed", task.isCompleted()); - event.put("completedAt", task.getCompletedAt() != null ? task.getCompletedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) : null); - event.put("completedBy", task.getCompletedBy()); - event.put("note", task.getCompletionNote()); - event.put("event", "taskCompleted"); - event.put("taskType", task.getTaskType()); - - // Task event publishing has been removed - log.info("Task completed: taskId={}, taskType={}, completed={}", task.getIdAsString(), task.getTaskType(), task.isCompleted()); - - response.put("success", true); - response.putAll(event); - log.info("Task completion processed successfully for taskId={}, taskType={}", taskIdStr, task.getTaskType()); - } catch (IllegalArgumentException e) { - response.put("success", false); - response.put("message", "Ungültige taskId"); - } catch (Exception e) { - log.error("Error processing task completion", e); - response.put("success", false); - response.put("message", "Fehler bei der Verarbeitung"); + log.info("Task marked completed. taskId={}, completedBy={}", taskIdStr, task.getCompletedBy()); + } catch (IllegalArgumentException ex) { + log.error("Invalid taskId format for completion: {}", taskIdStr); + } catch (Exception ex) { + log.error("Error while marking task completed (taskId={}): {}", taskIdStr, ex.getMessage(), ex); } } diff --git a/src/main/java/de/assecutor/votianlt/model/Barcode.java b/src/main/java/de/assecutor/votianlt/model/Barcode.java new file mode 100644 index 0000000..f07b334 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/model/Barcode.java @@ -0,0 +1,38 @@ +package de.assecutor.votianlt.model; + +import lombok.Data; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.bson.types.ObjectId; + +import java.time.LocalDateTime; + +/** + * Barcode entity for storing barcode data from task completions. + * References the task ObjectId and stores barcode strings. + */ +@Data +@Document(collection = "barcodes") +public class Barcode { + + @Id + private ObjectId id; + + private ObjectId taskId; + private String barcode; + private LocalDateTime createdAt; + private String completedBy; + + // Default constructor + public Barcode() { + this.createdAt = LocalDateTime.now(); + } + + // Constructor with parameters + public Barcode(ObjectId taskId, String barcode, String completedBy) { + this(); + this.taskId = taskId; + this.barcode = barcode; + this.completedBy = completedBy; + } +} \ No newline at end of file diff --git a/src/main/java/de/assecutor/votianlt/model/Photo.java b/src/main/java/de/assecutor/votianlt/model/Photo.java index 730e8c3..517ccb9 100644 --- a/src/main/java/de/assecutor/votianlt/model/Photo.java +++ b/src/main/java/de/assecutor/votianlt/model/Photo.java @@ -1,5 +1,6 @@ package de.assecutor.votianlt.model; +import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import org.bson.types.ObjectId; @@ -11,16 +12,15 @@ import java.util.List; * Photo entity for storing photo data from task completions. * References the job ObjectId and stores base64 encoded photos. */ +@Data @Document(collection = "photos") public class Photo { @Id private ObjectId id; - private ObjectId jobId; private ObjectId taskId; - private List photos; // base64 encoded photos - private int count; + private String photo; // base64 encoded photos private LocalDateTime createdAt; private String completedBy; @@ -30,94 +30,10 @@ public class Photo { } // Constructor with parameters - public Photo(ObjectId jobId, ObjectId taskId, List photos, String completedBy) { + public Photo(ObjectId taskId, String photo, String completedBy) { this(); - this.jobId = jobId; this.taskId = taskId; - this.photos = photos; - this.count = photos != null ? photos.size() : 0; + this.photo = photo; this.completedBy = completedBy; } - - // Getters and Setters - public ObjectId getId() { - return id; - } - - public void setId(ObjectId id) { - this.id = id; - } - - public String getIdAsString() { - return id != null ? id.toHexString() : null; - } - - public ObjectId getJobId() { - return jobId; - } - - public void setJobId(ObjectId jobId) { - this.jobId = jobId; - } - - public String getJobIdAsString() { - return jobId != null ? jobId.toHexString() : null; - } - - public ObjectId getTaskId() { - return taskId; - } - - public void setTaskId(ObjectId taskId) { - this.taskId = taskId; - } - - public String getTaskIdAsString() { - return taskId != null ? taskId.toHexString() : null; - } - - public List getPhotos() { - return photos; - } - - public void setPhotos(List photos) { - this.photos = photos; - this.count = photos != null ? photos.size() : 0; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public String getCompletedBy() { - return completedBy; - } - - public void setCompletedBy(String completedBy) { - this.completedBy = completedBy; - } - - @Override - public String toString() { - return "Photo{" + - "id=" + id + - ", jobId=" + jobId + - ", taskId=" + taskId + - ", count=" + count + - ", createdAt=" + createdAt + - ", completedBy='" + completedBy + '\'' + - '}'; - } } \ No newline at end of file diff --git a/src/main/java/de/assecutor/votianlt/model/Signature.java b/src/main/java/de/assecutor/votianlt/model/Signature.java new file mode 100644 index 0000000..317c163 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/model/Signature.java @@ -0,0 +1,38 @@ +package de.assecutor.votianlt.model; + +import lombok.Data; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.bson.types.ObjectId; + +import java.time.LocalDateTime; + +/** + * Signature entity for storing signature SVG data from task completions. + * References the task ObjectId and stores SVG signature strings. + */ +@Data +@Document(collection = "signatures") +public class Signature { + + @Id + private ObjectId id; + + private ObjectId taskId; + private String signatureSvg; + private LocalDateTime createdAt; + private String completedBy; + + // Default constructor + public Signature() { + this.createdAt = LocalDateTime.now(); + } + + // Constructor with parameters + public Signature(ObjectId taskId, String signatureSvg, String completedBy) { + this(); + this.taskId = taskId; + this.signatureSvg = signatureSvg; + this.completedBy = completedBy; + } +} \ No newline at end of file diff --git a/src/main/java/de/assecutor/votianlt/model/TaskEntry.java b/src/main/java/de/assecutor/votianlt/model/TaskEntry.java index ab087e9..48bdbb4 100644 --- a/src/main/java/de/assecutor/votianlt/model/TaskEntry.java +++ b/src/main/java/de/assecutor/votianlt/model/TaskEntry.java @@ -44,9 +44,6 @@ public class TaskEntry { @Field("completed_by") private String completedBy; - @Field("completion_note") - private String completionNote; - /** * Returns the ObjectId as string for JSON serialization. * This ensures that the task id is returned as a string when jobs are retrieved via API. diff --git a/src/main/java/de/assecutor/votianlt/model/task/BaseTask.java b/src/main/java/de/assecutor/votianlt/model/task/BaseTask.java index 65fc8f2..7661e2e 100644 --- a/src/main/java/de/assecutor/votianlt/model/task/BaseTask.java +++ b/src/main/java/de/assecutor/votianlt/model/task/BaseTask.java @@ -48,9 +48,6 @@ public abstract class BaseTask { @Field("completed_by") private String completedBy; - @Field("completion_note") - private String completionNote; - /** * Returns the ObjectId as string for JSON serialization. */ diff --git a/src/main/java/de/assecutor/votianlt/mqtt/MqttV5ClientManager.java b/src/main/java/de/assecutor/votianlt/mqtt/MqttV5ClientManager.java index 4454514..e6790c8 100644 --- a/src/main/java/de/assecutor/votianlt/mqtt/MqttV5ClientManager.java +++ b/src/main/java/de/assecutor/votianlt/mqtt/MqttV5ClientManager.java @@ -89,6 +89,7 @@ public class MqttV5ClientManager implements SmartLifecycle { "/server/+/task/photo/completed", "/server/+/task/confirm", "/server/+/task/completed", + "/server/+/task_completed", "/server/+/job/status", "/server/+/jobs/assigned", "/server/login" @@ -130,28 +131,33 @@ public class MqttV5ClientManager implements SmartLifecycle { private void routeInbound(String topic, Map payload) { try { - if (topic.matches("/server/.+/task/photo/completed")) { - messageController.handlePhotoTaskCompleted(payload); - } else if (topic.matches("/server/.+/task/confirm")) { - messageController.handleTaskConfirmation(payload); - } else if (topic.matches("/server/.+/task/completed")) { - messageController.handleTaskCompleted(payload); - } else if (topic.matches("/server/.+/job/status")) { - messageController.handleJobStatusUpdate(payload); - } else if (topic.matches("/server/.+/jobs/assigned")) { - // Extract clientId from topic: /server/{clientId}/jobs/assigned + // The consolidated topic /server/{clientId}/task_completed is used by apps to + // report completion of any task type. Only PHOTO and CONFIRMATION require + // specialized processing on the server side. All other task types are handled by the + // generic handler handleTaskCompleted(). This keeps routing simple while allowing + // special logic (e.g., photo persistence) where necessary. + if (topic.matches("/server/.+/task_completed")) { try { - String[] parts = topic.split("/"); - if (parts.length >= 5 && "server".equals(parts[1])) { - String clientId = parts[2]; - if (clientId != null && !clientId.isBlank()) { - payload.put("clientId", clientId); - } - } - } catch (Exception ignore) { - // ignore extraction errors + Object tt = payload.get("taskType"); + String taskType = tt != null ? tt.toString() : null; + messageController.handleTaskCompleted(payload, taskType); + } catch (Exception e) { + log.error("Error routing task_completed by taskType: {}", e.getMessage(), e); + } + } else if (topic.matches("/server/.+/jobs/assigned")) { + try { + // Extract clientId from topic: /server/{clientId}/jobs/assigned + String[] parts = topic.split("/"); + String clientId = parts.length > 2 ? parts[2] : null; + if (clientId != null && !clientId.isBlank()) { + payload.put("clientId", clientId); + } else { + log.warn("Couldn't extract clientId from topic {} for jobs/assigned", topic); + } + messageController.handleGetAssignedJobs(payload); + } catch (Exception e) { + log.error("Error handling jobs/assigned on {}: {}", topic, e.getMessage(), e); } - messageController.handleGetAssignedJobs(payload); } else if (topic.equals("/server/login")) { var om = new ObjectMapper(); de.assecutor.votianlt.dto.AppLoginRequest req = om.convertValue(payload, de.assecutor.votianlt.dto.AppLoginRequest.class); diff --git a/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java b/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java index ce15dff..3438913 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/AddJobView.java @@ -1523,7 +1523,6 @@ public class AddJobView extends Main { newTask.setCompleted(oldTask.isCompleted()); newTask.setCompletedAt(oldTask.getCompletedAt()); newTask.setCompletedBy(oldTask.getCompletedBy()); - newTask.setCompletionNote(oldTask.getCompletionNote()); // Preserve task-specific properties if (oldTask instanceof ConfirmationTask oldConfirmationTask && newTask instanceof ConfirmationTask newConfirmationTask) { diff --git a/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java b/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java index cc497d9..e5c8d3b 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/JobSummaryView.java @@ -386,9 +386,6 @@ public class JobSummaryView extends Main implements HasUrlParameter { if (task.getCompletedBy() != null && !task.getCompletedBy().isBlank()) { dialogContent.add(new Span("Abgeschlossen von: " + task.getCompletedBy())); } - if (task.getCompletionNote() != null && !task.getCompletionNote().isBlank()) { - dialogContent.add(new Span("Notiz: " + task.getCompletionNote())); - } } // Close button diff --git a/src/main/java/de/assecutor/votianlt/repository/BarcodeRepository.java b/src/main/java/de/assecutor/votianlt/repository/BarcodeRepository.java new file mode 100644 index 0000000..6c80e37 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/repository/BarcodeRepository.java @@ -0,0 +1,10 @@ +package de.assecutor.votianlt.repository; + +import de.assecutor.votianlt.model.Barcode; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BarcodeRepository extends MongoRepository { +} \ No newline at end of file diff --git a/src/main/java/de/assecutor/votianlt/repository/PhotoRepository.java b/src/main/java/de/assecutor/votianlt/repository/PhotoRepository.java index b08eb39..c4f14fa 100644 --- a/src/main/java/de/assecutor/votianlt/repository/PhotoRepository.java +++ b/src/main/java/de/assecutor/votianlt/repository/PhotoRepository.java @@ -13,14 +13,6 @@ import java.util.List; */ @Repository public interface PhotoRepository extends MongoRepository { - - /** - * Find all photos associated with a specific job ID. - * @param jobId The ObjectId of the job - * @return List of photos for the job - */ - List findByJobId(ObjectId jobId); - /** * Find all photos associated with a specific task ID. * @param taskId The ObjectId of the task @@ -28,15 +20,6 @@ public interface PhotoRepository extends MongoRepository { */ List findByTaskId(ObjectId taskId); - /** - * Find photos by job ID as string. - * @param jobId The job ID as string - * @return List of photos for the job - */ - default List findByJobId(String jobId) { - return findByJobId(new ObjectId(jobId)); - } - /** * Find photos by task ID as string. * @param taskId The task ID as string diff --git a/src/main/java/de/assecutor/votianlt/repository/SignatureRepository.java b/src/main/java/de/assecutor/votianlt/repository/SignatureRepository.java new file mode 100644 index 0000000..d0a4d10 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/repository/SignatureRepository.java @@ -0,0 +1,10 @@ +package de.assecutor.votianlt.repository; + +import de.assecutor.votianlt.model.Signature; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface SignatureRepository extends MongoRepository { +} \ No newline at end of file