Erweiterungen
This commit is contained in:
158
src/main/java/de/assecutor/votianlt/config/MongoConfig.java
Normal file
158
src/main/java/de/assecutor/votianlt/config/MongoConfig.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package de.assecutor.votianlt.config;
|
||||
|
||||
import de.assecutor.votianlt.model.task.*;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
||||
import org.bson.Document;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
public class MongoConfig {
|
||||
|
||||
@Bean
|
||||
public MongoCustomConversions customConversions() {
|
||||
List<Converter<?, ?>> converters = new ArrayList<>();
|
||||
converters.add(new DocumentToBaseTaskConverter());
|
||||
return new MongoCustomConversions(converters);
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
@Slf4j
|
||||
public static class DocumentToBaseTaskConverter implements Converter<Document, BaseTask> {
|
||||
|
||||
@Override
|
||||
public BaseTask convert(Document source) {
|
||||
// Debug logging to see what's in the document
|
||||
log.debug("Converting MongoDB document to BaseTask. Document keys: {}", source.keySet());
|
||||
log.debug("Full document content: {}", source.toJson());
|
||||
|
||||
// Use _class field for type discrimination (MongoDB standard)
|
||||
String className = source.getString("_class");
|
||||
if (className == null) {
|
||||
// Fallback to taskType field if _class is not present
|
||||
String taskType = source.getString("taskType");
|
||||
if (taskType == null) {
|
||||
taskType = source.getString("task_type");
|
||||
}
|
||||
// Map taskType to class name
|
||||
className = mapTaskTypeToClassName(taskType);
|
||||
}
|
||||
|
||||
log.debug("Extracted className: '{}' from document", className);
|
||||
|
||||
BaseTask task;
|
||||
switch (className) {
|
||||
case "de.assecutor.votianlt.model.task.ConfirmationTask":
|
||||
case "ConfirmationTask":
|
||||
log.debug("Creating ConfirmationTask");
|
||||
task = new ConfirmationTask();
|
||||
if (source.containsKey("button_text")) {
|
||||
((ConfirmationTask) task).setButtonText(source.getString("button_text"));
|
||||
}
|
||||
break;
|
||||
case "de.assecutor.votianlt.model.task.SignatureTask":
|
||||
case "SignatureTask":
|
||||
log.debug("Creating SignatureTask");
|
||||
task = new SignatureTask();
|
||||
break;
|
||||
case "de.assecutor.votianlt.model.task.PhotoTask":
|
||||
case "PhotoTask":
|
||||
log.debug("Creating PhotoTask");
|
||||
task = new PhotoTask();
|
||||
if (source.containsKey("min_photo_count")) {
|
||||
((PhotoTask) task).setMinPhotoCount(source.getInteger("min_photo_count"));
|
||||
}
|
||||
if (source.containsKey("max_photo_count")) {
|
||||
((PhotoTask) task).setMaxPhotoCount(source.getInteger("max_photo_count"));
|
||||
}
|
||||
break;
|
||||
case "de.assecutor.votianlt.model.task.TodoListTask":
|
||||
case "TodoListTask":
|
||||
log.debug("Creating TodoListTask");
|
||||
task = new TodoListTask();
|
||||
if (source.containsKey("todo_items")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> todoItems = (List<String>) source.get("todo_items");
|
||||
((TodoListTask) task).setTodoItems(todoItems);
|
||||
}
|
||||
break;
|
||||
case "de.assecutor.votianlt.model.task.BarcodeTask":
|
||||
case "BarcodeTask":
|
||||
log.debug("Creating BarcodeTask");
|
||||
task = new BarcodeTask();
|
||||
if (source.containsKey("min_barcode_count")) {
|
||||
((BarcodeTask) task).setMinBarcodeCount(source.getInteger("min_barcode_count"));
|
||||
}
|
||||
if (source.containsKey("max_barcode_count")) {
|
||||
((BarcodeTask) task).setMaxBarcodeCount(source.getInteger("max_barcode_count"));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log.warn("Unknown className '{}', falling back to ConfirmationTask", className);
|
||||
task = new ConfirmationTask(); // fallback
|
||||
break;
|
||||
}
|
||||
|
||||
// Set common fields
|
||||
if (source.containsKey("_id")) {
|
||||
task.setId(source.getObjectId("_id"));
|
||||
}
|
||||
if (source.containsKey("job_id")) {
|
||||
task.setJobId(source.getObjectId("job_id"));
|
||||
}
|
||||
if (source.containsKey("text")) {
|
||||
task.setText(source.getString("text"));
|
||||
}
|
||||
if (source.containsKey("task_order")) {
|
||||
task.setTaskOrder(source.getInteger("task_order", 0));
|
||||
}
|
||||
if (source.containsKey("completed")) {
|
||||
task.setCompleted(source.getBoolean("completed", false));
|
||||
}
|
||||
if (source.containsKey("completed_at") && source.get("completed_at") != null) {
|
||||
Object completedAtObj = source.get("completed_at");
|
||||
if (completedAtObj instanceof String) {
|
||||
task.setCompletedAt(LocalDateTime.parse((String) completedAtObj, DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||
} else if (completedAtObj instanceof java.util.Date) {
|
||||
task.setCompletedAt(((java.util.Date) completedAtObj).toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime());
|
||||
}
|
||||
}
|
||||
if (source.containsKey("completed_by")) {
|
||||
task.setCompletedBy(source.getString("completed_by"));
|
||||
}
|
||||
if (source.containsKey("completion_note")) {
|
||||
task.setCompletionNote(source.getString("completion_note"));
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
private String mapTaskTypeToClassName(String taskType) {
|
||||
if (taskType == null) {
|
||||
return "de.assecutor.votianlt.model.task.ConfirmationTask";
|
||||
}
|
||||
switch (taskType) {
|
||||
case "CONFIRMATION":
|
||||
return "de.assecutor.votianlt.model.task.ConfirmationTask";
|
||||
case "SIGNATURE":
|
||||
return "de.assecutor.votianlt.model.task.SignatureTask";
|
||||
case "PHOTO":
|
||||
return "de.assecutor.votianlt.model.task.PhotoTask";
|
||||
case "TODOLIST":
|
||||
return "de.assecutor.votianlt.model.task.TodoListTask";
|
||||
case "BARCODE":
|
||||
return "de.assecutor.votianlt.model.task.BarcodeTask";
|
||||
default:
|
||||
return "de.assecutor.votianlt.model.task.ConfirmationTask";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ import de.assecutor.votianlt.repository.CargoItemRepository;
|
||||
import de.assecutor.votianlt.repository.JobRepository;
|
||||
import de.assecutor.votianlt.repository.TaskRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
@@ -177,6 +179,7 @@ public class MessageController {
|
||||
@SendToUser("/queue/jobs")
|
||||
public List<JobWithRelatedDataDTO> handleGetAssignedJobs(Map<String, Object> request) {
|
||||
log.info("STOMP Endpoint '/app/jobs/assigned' called with data: {}", request);
|
||||
log.debug("Starting to process jobs request for STOMP endpoint");
|
||||
|
||||
if (request == null || !request.containsKey("appUserId")) {
|
||||
log.info("STOMP Response for '/app/jobs/assigned' sent to '/user/queue/jobs': empty list (no appUserId provided)");
|
||||
@@ -191,6 +194,7 @@ public class MessageController {
|
||||
|
||||
// Find jobs assigned to this app user
|
||||
List<Job> assignedJobs = jobRepository.findByAppUser(appUserId);
|
||||
log.debug("Found {} jobs for appUserId: {}", assignedJobs.size(), appUserId);
|
||||
|
||||
// For each job, fetch related cargo items and tasks (ordered by task order)
|
||||
List<JobWithRelatedDataDTO> jobsWithRelatedData = assignedJobs.stream()
|
||||
@@ -211,6 +215,21 @@ public class MessageController {
|
||||
log.info("STOMP Response for '/app/jobs/assigned' sent to '/user/queue/jobs': {} jobs with related data found for appUserId='{}'",
|
||||
jobsWithRelatedData.size(), appUserId);
|
||||
|
||||
// Log complete JSON for debugging
|
||||
log.debug("About to serialize {} jobs to JSON for logging", jobsWithRelatedData.size());
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
String jsonOutput = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jobsWithRelatedData);
|
||||
log.info("=== COMPLETE JSON RESPONSE FOR STOMP CLIENT ===");
|
||||
log.info("AppUserId: {}", appUserId);
|
||||
log.info("Number of jobs: {}", jobsWithRelatedData.size());
|
||||
log.info("JSON Data:\n{}", jsonOutput);
|
||||
log.info("=== END JSON RESPONSE ===");
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to serialize jobs to JSON for logging: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
return jobsWithRelatedData;
|
||||
}
|
||||
|
||||
@@ -280,4 +299,138 @@ public class MessageController {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
@MessageMapping("/task/confirm")
|
||||
@SendTo("/topic/task-updates")
|
||||
public Map<String, Object> handleTaskConfirmation(Map<String, Object> payload) {
|
||||
log.info("STOMP Endpoint '/app/task/confirm' called with data: {}", payload);
|
||||
return processTaskCompletion(payload, "CONFIRMATION");
|
||||
}
|
||||
|
||||
/**
|
||||
* Report photo task completion from apps.
|
||||
* Client sends to /app/task/photo/completed with payload { taskId, completedBy?, note? }.
|
||||
* Broadcasts to /topic/task-updates and /topic/tasks/{taskId}.
|
||||
*/
|
||||
@MessageMapping("/task/photo/completed")
|
||||
@SendTo("/topic/task-updates")
|
||||
public Map<String, Object> handlePhotoTaskCompleted(Map<String, Object> payload) {
|
||||
log.info("STOMP Endpoint '/app/task/photo/completed' called with data: {}", payload);
|
||||
return processTaskCompletion(payload, "PHOTO");
|
||||
}
|
||||
|
||||
/**
|
||||
* Report signature task completion from apps.
|
||||
* Client sends to /app/task/signature/completed with payload { taskId, completedBy?, note? }.
|
||||
* Broadcasts to /topic/task-updates and /topic/tasks/{taskId}.
|
||||
*/
|
||||
@MessageMapping("/task/signature/completed")
|
||||
@SendTo("/topic/task-updates")
|
||||
public Map<String, Object> handleSignatureTaskCompleted(Map<String, Object> payload) {
|
||||
log.info("STOMP Endpoint '/app/task/signature/completed' called with data: {}", payload);
|
||||
return processTaskCompletion(payload, "SIGNATURE");
|
||||
}
|
||||
|
||||
/**
|
||||
* Report barcode task completion from apps.
|
||||
* Client sends to /app/task/barcode/completed with payload { taskId, completedBy?, note? }.
|
||||
* Broadcasts to /topic/task-updates and /topic/tasks/{taskId}.
|
||||
*/
|
||||
@MessageMapping("/task/barcode/completed")
|
||||
@SendTo("/topic/task-updates")
|
||||
public Map<String, Object> handleBarcodeTaskCompleted(Map<String, Object> payload) {
|
||||
log.info("STOMP Endpoint '/app/task/barcode/completed' called with data: {}", payload);
|
||||
return processTaskCompletion(payload, "BARCODE");
|
||||
}
|
||||
|
||||
/**
|
||||
* Report todolist task completion from apps.
|
||||
* Client sends to /app/task/todolist/completed with payload { taskId, completedBy?, note? }.
|
||||
* Broadcasts to /topic/task-updates and /topic/tasks/{taskId}.
|
||||
*/
|
||||
@MessageMapping("/task/todolist/completed")
|
||||
@SendTo("/topic/task-updates")
|
||||
public Map<String, Object> handleTodolistTaskCompleted(Map<String, Object> payload) {
|
||||
log.info("STOMP Endpoint '/app/task/todolist/completed' called with data: {}", payload);
|
||||
return processTaskCompletion(payload, "TODOLIST");
|
||||
}
|
||||
|
||||
/**
|
||||
* Common method to process task completion for different task types.
|
||||
* This method contains the shared logic for all task completion endpoints.
|
||||
*/
|
||||
private Map<String, Object> 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 response;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
try {
|
||||
org.bson.types.ObjectId taskId = new org.bson.types.ObjectId(taskIdStr);
|
||||
java.util.Optional<BaseTask> opt = taskRepository.findById(taskId);
|
||||
if (opt.isEmpty()) {
|
||||
response.put("success", false);
|
||||
response.put("message", "Task nicht gefunden");
|
||||
return response;
|
||||
}
|
||||
|
||||
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 response;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
// Send specific task topic
|
||||
messagingTemplate.convertAndSend("/topic/tasks/" + task.getIdAsString(), event);
|
||||
|
||||
response.put("success", true);
|
||||
response.putAll(event);
|
||||
log.info("Task completion processed successfully for taskId={}, taskType={}", taskIdStr, task.getTaskType());
|
||||
return response;
|
||||
} catch (IllegalArgumentException e) {
|
||||
response.put("success", false);
|
||||
response.put("message", "Ungültige taskId");
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("Error processing task completion", e);
|
||||
response.put("success", false);
|
||||
response.put("message", "Fehler bei der Verarbeitung");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,6 @@ public class TaskEntry {
|
||||
@JsonIgnore
|
||||
private ObjectId jobId;
|
||||
|
||||
@Field("text")
|
||||
private String text;
|
||||
|
||||
@Field("task_type")
|
||||
private TaskType taskType = TaskType.CONFIRMATION;
|
||||
|
||||
|
||||
@@ -30,4 +30,15 @@ public class BarcodeTask extends BaseTask {
|
||||
public String getDisplayName() {
|
||||
return "Barcode";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTaskSpecificData() {
|
||||
return new TaskSpecificData();
|
||||
}
|
||||
|
||||
public class TaskSpecificData {
|
||||
public String taskType = getTaskType();
|
||||
public Integer minBarcodeCount = BarcodeTask.this.minBarcodeCount;
|
||||
public Integer maxBarcodeCount = BarcodeTask.this.maxBarcodeCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,4 +77,10 @@ public abstract class BaseTask {
|
||||
* Returns the display name for this task type.
|
||||
*/
|
||||
public abstract String getDisplayName();
|
||||
|
||||
/**
|
||||
* Returns task-specific data for JSON serialization.
|
||||
*/
|
||||
@JsonGetter("taskSpecificData")
|
||||
public abstract Object getTaskSpecificData();
|
||||
}
|
||||
|
||||
@@ -26,4 +26,14 @@ public class ConfirmationTask extends BaseTask {
|
||||
public String getDisplayName() {
|
||||
return "Bestätigung";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTaskSpecificData() {
|
||||
return new TaskSpecificData();
|
||||
}
|
||||
|
||||
public class TaskSpecificData {
|
||||
public String taskType = getTaskType();
|
||||
public String buttonText = ConfirmationTask.this.buttonText;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,4 +30,15 @@ public class PhotoTask extends BaseTask {
|
||||
public String getDisplayName() {
|
||||
return "Foto";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTaskSpecificData() {
|
||||
return new TaskSpecificData();
|
||||
}
|
||||
|
||||
public class TaskSpecificData {
|
||||
public String taskType = getTaskType();
|
||||
public Integer minPhotoCount = PhotoTask.this.minPhotoCount;
|
||||
public Integer maxPhotoCount = PhotoTask.this.maxPhotoCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,4 +19,14 @@ public class SignatureTask extends BaseTask {
|
||||
public String getDisplayName() {
|
||||
return "Unterschrift";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTaskSpecificData() {
|
||||
return new TaskSpecificData();
|
||||
}
|
||||
|
||||
public class TaskSpecificData {
|
||||
public String taskType = getTaskType();
|
||||
// No specific data for signature task
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,14 @@ public class TodoListTask extends BaseTask {
|
||||
public String getDisplayName() {
|
||||
return "To-Do Liste";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTaskSpecificData() {
|
||||
return new TaskSpecificData();
|
||||
}
|
||||
|
||||
public class TaskSpecificData {
|
||||
public String taskType = getTaskType();
|
||||
public List<String> todoItems = TodoListTask.this.todoItems;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1504,7 +1504,7 @@ public class AddJobView extends Main {
|
||||
taskContainer.add(taskTypeCombo, configContainer);
|
||||
taskContainer.add(deleteXButton);
|
||||
|
||||
// Create TaskEntry and add to state with correct order
|
||||
// Create Task and add to state with correct order
|
||||
BaseTask task = new ConfirmationTask("");
|
||||
task.setTaskOrder(tasksState.size()); // Set order based on current position
|
||||
tasksState.add(task);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
server.port=${PORT:8080}
|
||||
server.address=0.0.0.0
|
||||
logging.level.org.atmosphere=warn
|
||||
logging.level.de.assecutor.votianlt=INFO
|
||||
logging.level.de.assecutor.votianlt.controller.MessageController=DEBUG
|
||||
logging.level.de.assecutor.votianlt.config.MongoConfig=DEBUG
|
||||
spring.mustache.check-template-location=false
|
||||
|
||||
# Launch the default browser when starting the application in development mode
|
||||
@@ -14,6 +17,10 @@ spring.jpa.open-in-view=false
|
||||
|
||||
# MongoDB
|
||||
spring.data.mongodb.uri=mongodb://192.168.180.25:27017/votianlt
|
||||
spring.data.mongodb.auto-index-creation=true
|
||||
spring.data.mongodb.socket-timeout=30000
|
||||
spring.data.mongodb.connect-timeout=10000
|
||||
spring.data.mongodb.server-selection-timeout=5000
|
||||
|
||||
# Mail Configuration
|
||||
mail.smtp.username=no-reply@appcreation.de
|
||||
|
||||
Reference in New Issue
Block a user