diff --git a/Dockerfile b/Dockerfile index 8a40812..608c3bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ FROM eclipse-temurin:21-jre COPY target/*.jar app.jar EXPOSE 8080 -ENTRYPOINT ["java", "-jar", "/app.jar"] +ENTRYPOINT ["java", "-jar", "/app.jar", "--spring.profiles.active=production"] diff --git a/STOMP_README.md b/STOMP_README.md index 3a98068..191137a 100644 --- a/STOMP_README.md +++ b/STOMP_README.md @@ -214,4 +214,89 @@ Zum Testen der STOMP-Funktionalität können Sie: 2. Browser-Entwicklertools für WebSocket-Verbindungen nutzen 3. Spezialisierte STOMP-Testing-Tools einsetzen -Die Implementierung ist vollständig und bereit für die Integration mit externen Apps. \ No newline at end of file +Die Implementierung ist vollständig und bereit für die Integration mit externen Apps. + +## Neue STOMP-Schnittstelle: Task-Erledigung melden + +Mit dieser Schnittstelle kann ein Client die Erledigung eines Tasks melden. + +- Senden (Client → Server): `/app/task/completed` +- Broadcasts (Server → Client): + - Global: `/topic/task-updates` + - Task-spezifisch: `/topic/tasks/{taskId}` + +Payload (JSON): + +```json +{ + "taskId": "", + "completedBy": "", + "note": "" +} +``` + +Antwort (Beispiel): + +```json +{ + "timestamp": "2025-09-05T09:25:00", + "type": "taskCompletedAck", + "success": true, + "taskId": "...", + "jobId": "...", + "completed": true, + "completedAt": "2025-09-05T09:25:00", + "completedBy": "driver01", + "note": "Übergabe erfolgreich", + "event": "taskCompleted" +} +``` + +JavaScript Beispiel: + +```javascript +// Abonnieren der globalen Updates +stompClient.subscribe('/topic/task-updates', (frame) => { + console.log('Task update:', JSON.parse(frame.body)); +}); + +// Abonnieren eines spezifischen Tasks +stompClient.subscribe('/topic/tasks/' + taskId, (frame) => { + console.log('Task-specific update:', JSON.parse(frame.body)); +}); + +// Task als erledigt melden +stompClient.send('/app/task/completed', {}, JSON.stringify({ + taskId: taskId, + completedBy: 'driver01', + note: 'Übergabe erfolgreich' +})); +``` + +Flutter/Dart Beispiel: + +```dart +stompClient.subscribe( + destination: '/topic/task-updates', + callback: (frame) => print('Task update: ${frame.body}'), +); + +stompClient.subscribe( + destination: '/topic/tasks/$taskId', + callback: (frame) => print('Task-specific update: ${frame.body}'), +); + +stompClient.send( + destination: '/app/task/completed', + body: jsonEncode({ + 'taskId': taskId, + 'completedBy': 'driver01', + 'note': 'Übergabe erfolgreich', + }), +); +``` + +Hinweise: +- `taskId` ist Pflicht. Bei ungültiger oder unbekannter `taskId` wird `success=false` zurückgegeben. +- Der Server setzt `completed=true` und `completedAt` automatisch. +- Zusätzlich zum globalen Broadcast wird ein task-spezifisches Event auf `/topic/tasks/{taskId}` versendet. diff --git a/src/main/bundles/prod.bundle b/src/main/bundles/prod.bundle index a8949fb..01d0be8 100644 Binary files a/src/main/bundles/prod.bundle and b/src/main/bundles/prod.bundle differ diff --git a/src/main/java/de/assecutor/votianlt/controller/MessageController.java b/src/main/java/de/assecutor/votianlt/controller/MessageController.java index d4de0b6..6dcad17 100644 --- a/src/main/java/de/assecutor/votianlt/controller/MessageController.java +++ b/src/main/java/de/assecutor/votianlt/controller/MessageController.java @@ -206,4 +206,71 @@ public class MessageController { return jobsWithRelatedData; } + + /** + * Report task completion from apps. + * Client sends to /app/task/completed with payload { taskId, completedBy?, note? }. + * Broadcasts to /topic/task-updates and /topic/tasks/{taskId}. + */ + @MessageMapping("/task/completed") + @SendTo("/topic/task-updates") + public Map handleTaskCompleted(Map payload) { + log.info("STOMP Endpoint '/app/task/completed' called with data: {}", 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("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 opt = taskRepository.findById(taskId); + if (opt.isEmpty()) { + response.put("success", false); + response.put("message", "Task nicht gefunden"); + return response; + } + TaskEntry 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"); + + // 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={}", taskIdStr); + 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; + } + } } \ 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 0a797ac..be9f0a4 100644 --- a/src/main/java/de/assecutor/votianlt/model/TaskEntry.java +++ b/src/main/java/de/assecutor/votianlt/model/TaskEntry.java @@ -10,6 +10,8 @@ import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; +import java.time.LocalDateTime; + @Data @NoArgsConstructor @AllArgsConstructor @@ -26,6 +28,19 @@ public class TaskEntry { @Field("text") private String text; + // Completion tracking + @Field("completed") + private boolean completed = false; + + @Field("completed_at") + private LocalDateTime completedAt; + + @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.