Erweiterungen

This commit is contained in:
2025-09-14 20:53:51 +02:00
parent 07cf7c9f07
commit 47c663c31a
13 changed files with 320 additions and 306 deletions

View File

@@ -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;
}

View File

@@ -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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> payload) {
Map<String, Object> 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<BaseTask> 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<String, Object> 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<String, Object> payload) {
Object taskId = payload.get("taskId");
completeTask(taskId);
}
private void processTodoListTaskCompletion(Map<String, Object> payload) {
Object taskId = payload.get("taskId");
completeTask(taskId);
}
private void processBarcodeTaskCompletion(Map<String, Object> 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<String, Object> extraData = (Map<String, Object>) payload.get("extraData");
List<String> barcodes = (List<String>) barcodesList;
if (extraData.containsKey("photos") && extraData.get("photos") != null) {
@SuppressWarnings("unchecked")
List<String> base64Photos = (List<String>) 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<String, Object> 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<String, Object> payload, String expectedTaskType) {
Map<String, Object> 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<String, Object> payload) {
Object taskId = payload.get("taskId");
try {
org.bson.types.ObjectId taskId = new org.bson.types.ObjectId(taskIdStr);
java.util.Optional<BaseTask> 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<String, Object> 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<String> photos = (List<String>) 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<String, Object> 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<String> 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<String> 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<String> getPhotos() {
return photos;
}
public void setPhotos(List<String> 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 + '\'' +
'}';
}
}

View File

@@ -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;
}
}

View File

@@ -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.

View File

@@ -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.
*/

View File

@@ -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<String, Object> 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);

View File

@@ -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) {

View File

@@ -386,9 +386,6 @@ public class JobSummaryView extends Main implements HasUrlParameter<String> {
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

View File

@@ -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<Barcode, ObjectId> {
}

View File

@@ -13,14 +13,6 @@ import java.util.List;
*/
@Repository
public interface PhotoRepository extends MongoRepository<Photo, ObjectId> {
/**
* Find all photos associated with a specific job ID.
* @param jobId The ObjectId of the job
* @return List of photos for the job
*/
List<Photo> 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<Photo, ObjectId> {
*/
List<Photo> 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<Photo> findByJobId(String jobId) {
return findByJobId(new ObjectId(jobId));
}
/**
* Find photos by task ID as string.
* @param taskId The task ID as string

View File

@@ -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<Signature, ObjectId> {
}