Erweiterungen
This commit is contained in:
83
CLAUDE.md
Normal file
83
CLAUDE.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Development Commands
|
||||
|
||||
- **Start development server**: `./mvnw` (runs Spring Boot with Vaadin dev mode)
|
||||
- **Build for production**: `./mvnw -Pproduction package`
|
||||
- **Clean build**: `./mvnw clean compile`
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
This is a **Vaadin Spring Boot** application for job/task management with real-time mobile app communication via MQTT. The system manages logistics jobs with tasks that mobile app users complete.
|
||||
|
||||
### Core Architecture Layers
|
||||
|
||||
**Frontend**: Vaadin Flow views (server-side rendered)
|
||||
- `src/main/java/de/assecutor/votianlt/pages/view/` - Main UI views
|
||||
- `src/main/java/de/assecutor/votianlt/pages/base/ui/` - Shared UI components
|
||||
|
||||
**Backend Services**:
|
||||
- `src/main/java/de/assecutor/votianlt/service/` - Business logic
|
||||
- `src/main/java/de/assecutor/votianlt/controller/` - MQTT message handling
|
||||
- `src/main/java/de/assecutor/votianlt/repository/` - MongoDB data access
|
||||
|
||||
**Models**:
|
||||
- `src/main/java/de/assecutor/votianlt/model/` - Domain entities
|
||||
- Task hierarchy: `BaseTask` with subtypes (`PhotoTask`, `BarcodeTask`, `SignatureTask`, etc.)
|
||||
|
||||
### Key Architectural Patterns
|
||||
|
||||
**Job-Task Relationship**: Jobs contain multiple ordered tasks. Tasks have completion states and can store completion data (photos, barcodes, signatures).
|
||||
|
||||
**User Hierarchy**:
|
||||
- `User` - Web interface users (job managers)
|
||||
- `AppUser` - Mobile app users (task executors)
|
||||
- `AppUser.owner` field links to `User` for notifications
|
||||
|
||||
**MQTT Communication**:
|
||||
- `MqttV5ClientManager` handles bidirectional communication with mobile apps
|
||||
- `MessageController` routes inbound MQTT messages and processes task completions
|
||||
- Topics: `/server/{clientId}/task_completed`, `/server/login`, etc.
|
||||
|
||||
**History Tracking**: `JobHistoryService` logs all job/task changes with detailed audit trail displayed in `JobHistoryView`.
|
||||
|
||||
**Email Notifications**: `EmailService` sends notifications for job creation, task completion, and job completion using Spring Mail with SMTP.
|
||||
|
||||
## Data Storage
|
||||
|
||||
**MongoDB Collections**:
|
||||
- `jobs` - Main job entities with status tracking
|
||||
- `tasks` - Polymorphic task storage (discriminated by `taskType`)
|
||||
- `job_history` - Audit trail for all job changes
|
||||
- `photos`, `barcodes`, `signatures` - Task completion data
|
||||
- `users` - Web interface users
|
||||
- `app_user` - Mobile app users
|
||||
- `cargo_item` - Job cargo/delivery items
|
||||
|
||||
## Configuration
|
||||
|
||||
**Database**: MongoDB at `192.168.180.25:27017/votianlt`
|
||||
**MQTT**: HiveMQ client connects to `mqtt-2.assecutor.de` with credentials `app`/`apppwd`
|
||||
**Email**: SMTP via `mailhub.assecutor.org:587` using Spring Boot mail auto-configuration
|
||||
|
||||
## Development Environment
|
||||
|
||||
**Java 21** with **Spring Boot 3.4.3** and **Vaadin 24.7.0**
|
||||
**Security**: Spring Security with role-based access (`USER` role required)
|
||||
**Profiles**: `production` profile available for optimized builds
|
||||
|
||||
## Key Integration Points
|
||||
|
||||
When adding new task types:
|
||||
1. Extend `BaseTask` and add to `@JsonSubTypes`
|
||||
2. Add completion logic in `MessageController.handleTaskCompleted()`
|
||||
3. Update `JobHistoryView` for task-specific previews if needed
|
||||
|
||||
When modifying job status flow:
|
||||
1. Update `JobStatus` enum
|
||||
2. Modify `EmailService.updateJobStatusToCompleted()` logic
|
||||
3. Consider email notification templates
|
||||
|
||||
MQTT message routing follows pattern: extract `taskType` from payload, route to appropriate processor method in `MessageController`.
|
||||
6
pom.xml
6
pom.xml
@@ -103,10 +103,10 @@
|
||||
<artifactId>openpdf</artifactId>
|
||||
<version>1.3.30</version>
|
||||
</dependency>
|
||||
<!-- Spring Boot Mail Starter -->
|
||||
<dependency>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
<artifactId>jakarta.mail</artifactId>
|
||||
<version>2.0.1</version>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson JSR310 module for Java 8 date/time support -->
|
||||
|
||||
Binary file not shown.
@@ -19,6 +19,7 @@ import de.assecutor.votianlt.model.Photo;
|
||||
import de.assecutor.votianlt.model.Barcode;
|
||||
import de.assecutor.votianlt.model.Signature;
|
||||
import de.assecutor.votianlt.service.JobHistoryService;
|
||||
import de.assecutor.votianlt.service.EmailService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
@@ -57,8 +58,9 @@ public class MessageController {
|
||||
private final BarcodeRepository barcodeRepository;
|
||||
private final SignatureRepository signatureRepository;
|
||||
private final JobHistoryService jobHistoryService;
|
||||
private final EmailService emailService;
|
||||
|
||||
public MessageController(MqttPublisher mqttPublisher, AppUserRepository appUserRepository, AppUserService appUserService, JobRepository jobRepository, CargoItemRepository cargoItemRepository, TaskRepository taskRepository, PhotoRepository photoRepository, BarcodeRepository barcodeRepository, SignatureRepository signatureRepository, JobHistoryService jobHistoryService) {
|
||||
public MessageController(MqttPublisher mqttPublisher, AppUserRepository appUserRepository, AppUserService appUserService, JobRepository jobRepository, CargoItemRepository cargoItemRepository, TaskRepository taskRepository, PhotoRepository photoRepository, BarcodeRepository barcodeRepository, SignatureRepository signatureRepository, JobHistoryService jobHistoryService, EmailService emailService) {
|
||||
this.mqttPublisher = mqttPublisher;
|
||||
this.appUserRepository = appUserRepository;
|
||||
this.appUserService = appUserService;
|
||||
@@ -69,6 +71,7 @@ public class MessageController {
|
||||
this.barcodeRepository = barcodeRepository;
|
||||
this.signatureRepository = signatureRepository;
|
||||
this.jobHistoryService = jobHistoryService;
|
||||
this.emailService = emailService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -427,6 +430,20 @@ public class MessageController {
|
||||
log.warn("Failed to log task completion history for task {}: {}", taskIdStr, e.getMessage());
|
||||
}
|
||||
|
||||
// Send email notification for task completion
|
||||
try {
|
||||
ObjectId jobId = new ObjectId(task.getJobIdAsString());
|
||||
String taskType = task.getTaskType() != null ? task.getTaskType().toString() : "Unknown";
|
||||
String completedBy = task.getCompletedBy();
|
||||
|
||||
emailService.sendTaskCompletionNotification(jobId, taskType, taskIdStr, completedBy);
|
||||
|
||||
// Check if this was the last task and send job completion notification
|
||||
emailService.checkAndSendJobCompletionNotification(jobId, completedBy);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to send task completion email notification for task {}: {}", taskIdStr, e.getMessage());
|
||||
}
|
||||
|
||||
log.info("Task marked completed. taskId={}, completedBy={}, extraData={}",
|
||||
taskIdStr, task.getCompletedBy(), extraDataSummary);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import de.assecutor.votianlt.repository.TaskRepository;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
import de.assecutor.votianlt.repository.CargoItemRepository;
|
||||
import de.assecutor.votianlt.service.JobHistoryService;
|
||||
import de.assecutor.votianlt.service.EmailService;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -30,6 +31,7 @@ public class AddJobService {
|
||||
private final TaskRepository taskRepository;
|
||||
private final SecurityService securityService;
|
||||
private final JobHistoryService jobHistoryService;
|
||||
private final EmailService emailService;
|
||||
/**
|
||||
* Speichert einen neuen Auftrag samt CargoItems und Tasks
|
||||
* @param job der Auftrag
|
||||
@@ -107,6 +109,13 @@ public class AddJobService {
|
||||
log.warn("Failed to log job creation history for job {}: {}", savedJob.getIdAsString(), e.getMessage());
|
||||
}
|
||||
|
||||
// E-Mail-Benachrichtigung für Job-Erstellung senden
|
||||
try {
|
||||
emailService.sendJobCreationNotification(savedJob.getId(), savedJob.getCreatedBy());
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to send job creation email notification for job {}: {}", savedJob.getIdAsString(), e.getMessage());
|
||||
}
|
||||
|
||||
log.info("Auftrag erfolgreich gespeichert: {}", savedJob.getJobNumber());
|
||||
return savedJob;
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ public interface AppUserRepository extends MongoRepository<AppUser, ObjectId> {
|
||||
|
||||
AppUser findByPasswordCode(String passwordCode);
|
||||
|
||||
// Find AppUser by appCode for task completion notifications
|
||||
java.util.Optional<AppUser> findByAppCode(String appCode);
|
||||
|
||||
// Custom query methods can be added here if needed
|
||||
// List<AppUser> findByBezeichnung(String bezeichnung);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package de.assecutor.votianlt.repository;
|
||||
|
||||
import de.assecutor.votianlt.model.User;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface UserRepository extends MongoRepository<User, String> {
|
||||
public interface UserRepository extends MongoRepository<User, ObjectId> {
|
||||
|
||||
Optional<User> findByEmail(String email);
|
||||
|
||||
|
||||
408
src/main/java/de/assecutor/votianlt/service/EmailService.java
Normal file
408
src/main/java/de/assecutor/votianlt/service/EmailService.java
Normal file
@@ -0,0 +1,408 @@
|
||||
package de.assecutor.votianlt.service;
|
||||
|
||||
import de.assecutor.votianlt.model.AppUser;
|
||||
import de.assecutor.votianlt.model.Job;
|
||||
import de.assecutor.votianlt.model.JobStatus;
|
||||
import de.assecutor.votianlt.model.User;
|
||||
import de.assecutor.votianlt.repository.AppUserRepository;
|
||||
import de.assecutor.votianlt.repository.JobRepository;
|
||||
import de.assecutor.votianlt.repository.TaskRepository;
|
||||
import de.assecutor.votianlt.repository.UserRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class EmailService {
|
||||
|
||||
private final AppUserRepository appUserRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final JobRepository jobRepository;
|
||||
private final TaskRepository taskRepository;
|
||||
private final JavaMailSender mailSender;
|
||||
|
||||
@Value("${spring.mail.username}")
|
||||
private String smtpUsername;
|
||||
|
||||
public void sendTaskCompletionNotification(ObjectId jobId, String taskType, String taskId, String completedBy) {
|
||||
try {
|
||||
// Load job to get context information
|
||||
Optional<Job> jobOpt = jobRepository.findById(jobId);
|
||||
if (jobOpt.isEmpty()) {
|
||||
log.warn("Job not found for task completion notification: {}", jobId);
|
||||
return;
|
||||
}
|
||||
Job job = jobOpt.get();
|
||||
|
||||
// Find the app user who completed the task
|
||||
Optional<AppUser> appUserOpt = appUserRepository.findByAppCode(completedBy);
|
||||
if (appUserOpt.isEmpty()) {
|
||||
log.warn("AppUser not found for task completion notification: {}", completedBy);
|
||||
return;
|
||||
}
|
||||
AppUser appUser = appUserOpt.get();
|
||||
|
||||
// Find the owner (User) of the AppUser
|
||||
if (appUser.getOwner() == null) {
|
||||
log.warn("AppUser has no owner for task completion notification: {}", completedBy);
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<User> userOpt = userRepository.findById(appUser.getOwner());
|
||||
if (userOpt.isEmpty()) {
|
||||
log.warn("Owner user not found for task completion notification: {}", appUser.getOwner());
|
||||
return;
|
||||
}
|
||||
User user = userOpt.get();
|
||||
|
||||
// Check if user has email
|
||||
if (user.getEmail() == null || user.getEmail().isBlank()) {
|
||||
log.warn("User has no email address for task completion notification: {}", user.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// Send email
|
||||
sendEmail(user, job, taskType, taskId, appUser);
|
||||
log.info("Task completion notification sent to {} for job {} task {}", user.getEmail(), job.getJobNumber(), taskId);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send task completion notification for job {} task {}: {}", jobId, taskId, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendEmail(User user, Job job, String taskType, String taskId, AppUser appUser) {
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setFrom(smtpUsername);
|
||||
message.setTo(user.getEmail());
|
||||
message.setSubject("Aufgabe abgeschlossen - " + (job.getJobNumber() != null ? job.getJobNumber() : "Job " + job.getId()));
|
||||
|
||||
String fullName = buildFullName(user);
|
||||
String appUserName = buildAppUserName(appUser);
|
||||
String taskTypeName = getTaskTypeDisplayName(taskType);
|
||||
|
||||
StringBuilder body = new StringBuilder();
|
||||
body.append("Hallo ").append(fullName).append(",\n\n");
|
||||
body.append("eine Aufgabe wurde von ").append(appUserName).append(" abgeschlossen:\n\n");
|
||||
body.append("Job: ").append(job.getJobNumber() != null ? job.getJobNumber() : "Unbekannt").append("\n");
|
||||
if (job.getDeliveryCompany() != null) {
|
||||
body.append("Kunde: ").append(job.getDeliveryCompany()).append("\n");
|
||||
}
|
||||
body.append("Aufgabe: ").append(taskTypeName).append("\n");
|
||||
body.append("Abgeschlossen von: ").append(appUserName).append("\n\n");
|
||||
|
||||
if (job.getPickupCity() != null || job.getDeliveryCity() != null) {
|
||||
body.append("Route: ");
|
||||
if (job.getPickupCity() != null) {
|
||||
body.append(job.getPickupCity());
|
||||
}
|
||||
if (job.getPickupCity() != null && job.getDeliveryCity() != null) {
|
||||
body.append(" → ");
|
||||
}
|
||||
if (job.getDeliveryCity() != null) {
|
||||
body.append(job.getDeliveryCity());
|
||||
}
|
||||
body.append("\n\n");
|
||||
}
|
||||
|
||||
body.append("Mit freundlichen Grüßen,\n");
|
||||
body.append("Ihr Votianlt-System");
|
||||
|
||||
message.setText(body.toString());
|
||||
mailSender.send(message);
|
||||
}
|
||||
|
||||
private String buildFullName(User user) {
|
||||
StringBuilder name = new StringBuilder();
|
||||
if (user.getTitle() != null && !user.getTitle().isBlank()) {
|
||||
name.append(user.getTitle()).append(" ");
|
||||
}
|
||||
if (user.getFirstname() != null && !user.getFirstname().isBlank()) {
|
||||
name.append(user.getFirstname()).append(" ");
|
||||
}
|
||||
if (user.getName() != null && !user.getName().isBlank()) {
|
||||
name.append(user.getName());
|
||||
}
|
||||
String fullName = name.toString().trim();
|
||||
return fullName.isEmpty() ? "Benutzer" : fullName;
|
||||
}
|
||||
|
||||
private String buildAppUserName(AppUser appUser) {
|
||||
StringBuilder name = new StringBuilder();
|
||||
if (appUser.getVorname() != null && !appUser.getVorname().isBlank()) {
|
||||
name.append(appUser.getVorname()).append(" ");
|
||||
}
|
||||
if (appUser.getNachname() != null && !appUser.getNachname().isBlank()) {
|
||||
name.append(appUser.getNachname());
|
||||
}
|
||||
String fullName = name.toString().trim();
|
||||
if (fullName.isEmpty() && appUser.getBezeichnung() != null && !appUser.getBezeichnung().isBlank()) {
|
||||
return appUser.getBezeichnung();
|
||||
}
|
||||
return fullName.isEmpty() ? "App-Benutzer" : fullName;
|
||||
}
|
||||
|
||||
private String getTaskTypeDisplayName(String taskType) {
|
||||
if (taskType == null) return "Unbekannte Aufgabe";
|
||||
|
||||
return switch (taskType.toUpperCase()) {
|
||||
case "PHOTO" -> "Foto-Aufgabe";
|
||||
case "SIGNATURE" -> "Unterschrift";
|
||||
case "BARCODE" -> "Barcode scannen";
|
||||
case "CONFIRMATION" -> "Bestätigung";
|
||||
case "TODO_LIST" -> "Checkliste";
|
||||
default -> taskType;
|
||||
};
|
||||
}
|
||||
|
||||
public void checkAndSendJobCompletionNotification(ObjectId jobId, String completedBy) {
|
||||
try {
|
||||
// Check if all tasks for this job are completed
|
||||
var allTasks = taskRepository.findByJobIdOrderByTaskOrderAsc(jobId);
|
||||
if (allTasks.isEmpty()) {
|
||||
log.debug("No tasks found for job {}", jobId);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean allCompleted = allTasks.stream().allMatch(task -> task.isCompleted());
|
||||
|
||||
if (allCompleted) {
|
||||
log.info("All tasks completed for job {}, updating job status and sending completion notification", jobId);
|
||||
|
||||
// Update job status to COMPLETED
|
||||
updateJobStatusToCompleted(jobId);
|
||||
|
||||
// Send completion notification
|
||||
sendJobCompletionNotification(jobId, completedBy);
|
||||
} else {
|
||||
long completedCount = allTasks.stream().mapToLong(task -> task.isCompleted() ? 1L : 0L).sum();
|
||||
log.debug("Job {} not yet complete: {}/{} tasks completed", jobId, completedCount, allTasks.size());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to check job completion for job {}: {}", jobId, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendJobCompletionNotification(ObjectId jobId, String completedBy) {
|
||||
try {
|
||||
// Load job
|
||||
Optional<Job> jobOpt = jobRepository.findById(jobId);
|
||||
if (jobOpt.isEmpty()) {
|
||||
log.warn("Job not found for completion notification: {}", jobId);
|
||||
return;
|
||||
}
|
||||
Job job = jobOpt.get();
|
||||
|
||||
// Find the app user who completed the last task
|
||||
Optional<AppUser> appUserOpt = appUserRepository.findByAppCode(completedBy);
|
||||
if (appUserOpt.isEmpty()) {
|
||||
log.warn("AppUser not found for job completion notification: {}", completedBy);
|
||||
return;
|
||||
}
|
||||
AppUser appUser = appUserOpt.get();
|
||||
|
||||
// Find the owner (User) of the AppUser
|
||||
if (appUser.getOwner() == null) {
|
||||
log.warn("AppUser has no owner for job completion notification: {}", completedBy);
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<User> userOpt = userRepository.findById(appUser.getOwner());
|
||||
if (userOpt.isEmpty()) {
|
||||
log.warn("Owner user not found for job completion notification: {}", appUser.getOwner());
|
||||
return;
|
||||
}
|
||||
User user = userOpt.get();
|
||||
|
||||
// Check if user has email
|
||||
if (user.getEmail() == null || user.getEmail().isBlank()) {
|
||||
log.warn("User has no email address for job completion notification: {}", user.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// Send job completion email
|
||||
sendJobCompletionEmail(user, job, appUser);
|
||||
log.info("Job completion notification sent to {} for job {}", user.getEmail(), job.getJobNumber());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send job completion notification for job {}: {}", jobId, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendJobCompletionEmail(User user, Job job, AppUser appUser) {
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setFrom(smtpUsername);
|
||||
message.setTo(user.getEmail());
|
||||
message.setSubject("Job abgeschlossen - " + (job.getJobNumber() != null ? job.getJobNumber() : "Job " + job.getId()));
|
||||
|
||||
String fullName = buildFullName(user);
|
||||
String appUserName = buildAppUserName(appUser);
|
||||
|
||||
// Count completed tasks
|
||||
var allTasks = taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId());
|
||||
int taskCount = allTasks.size();
|
||||
|
||||
StringBuilder body = new StringBuilder();
|
||||
body.append("Hallo ").append(fullName).append(",\n\n");
|
||||
body.append("alle Aufgaben für den folgenden Job wurden erfolgreich abgeschlossen:\n\n");
|
||||
body.append("Job: ").append(job.getJobNumber() != null ? job.getJobNumber() : "Unbekannt").append("\n");
|
||||
if (job.getDeliveryCompany() != null) {
|
||||
body.append("Kunde: ").append(job.getDeliveryCompany()).append("\n");
|
||||
}
|
||||
|
||||
if (job.getPickupCity() != null || job.getDeliveryCity() != null) {
|
||||
body.append("Route: ");
|
||||
if (job.getPickupCity() != null) {
|
||||
body.append(job.getPickupCity());
|
||||
}
|
||||
if (job.getPickupCity() != null && job.getDeliveryCity() != null) {
|
||||
body.append(" → ");
|
||||
}
|
||||
if (job.getDeliveryCity() != null) {
|
||||
body.append(job.getDeliveryCity());
|
||||
}
|
||||
body.append("\n");
|
||||
}
|
||||
|
||||
body.append("Anzahl erledigter Aufgaben: ").append(taskCount).append("\n");
|
||||
body.append("Abgeschlossen von: ").append(appUserName).append("\n\n");
|
||||
|
||||
body.append("Der Job ist nun vollständig erledigt und kann weiterverarbeitet werden.\n\n");
|
||||
body.append("Mit freundlichen Grüßen,\n");
|
||||
body.append("Ihr Votianlt-System");
|
||||
|
||||
message.setText(body.toString());
|
||||
mailSender.send(message);
|
||||
}
|
||||
|
||||
private void updateJobStatusToCompleted(ObjectId jobId) {
|
||||
try {
|
||||
Optional<Job> jobOpt = jobRepository.findById(jobId);
|
||||
if (jobOpt.isEmpty()) {
|
||||
log.warn("Job not found for status update: {}", jobId);
|
||||
return;
|
||||
}
|
||||
|
||||
Job job = jobOpt.get();
|
||||
JobStatus oldStatus = job.getStatus();
|
||||
|
||||
// Only update if not already completed
|
||||
if (job.getStatus() != JobStatus.COMPLETED) {
|
||||
job.setStatus(JobStatus.COMPLETED);
|
||||
job.setUpdatedAt(java.time.LocalDateTime.now());
|
||||
jobRepository.save(job);
|
||||
|
||||
log.info("Job status updated from {} to COMPLETED for job {}",
|
||||
oldStatus != null ? oldStatus : "null", job.getJobNumber());
|
||||
} else {
|
||||
log.debug("Job {} already has COMPLETED status", job.getJobNumber());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to update job status to COMPLETED for job {}: {}", jobId, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendJobCreationNotification(ObjectId jobId, String createdBy) {
|
||||
try {
|
||||
// Load job
|
||||
Optional<Job> jobOpt = jobRepository.findById(jobId);
|
||||
if (jobOpt.isEmpty()) {
|
||||
log.warn("Job not found for creation notification: {}", jobId);
|
||||
return;
|
||||
}
|
||||
Job job = jobOpt.get();
|
||||
|
||||
// Find the user who created the job (using createdBy as User ID string)
|
||||
ObjectId createdByUserId;
|
||||
try {
|
||||
createdByUserId = new ObjectId(createdBy);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Invalid createdBy format for job creation notification: {}", createdBy);
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<User> userOpt = userRepository.findById(createdByUserId);
|
||||
if (userOpt.isEmpty()) {
|
||||
log.warn("User not found for job creation notification: {}", createdBy);
|
||||
return;
|
||||
}
|
||||
User user = userOpt.get();
|
||||
|
||||
// Check if user has email
|
||||
if (user.getEmail() == null || user.getEmail().isBlank()) {
|
||||
log.warn("User has no email address for job creation notification: {}", user.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// Send job creation email
|
||||
sendJobCreationEmail(user, job);
|
||||
log.info("Job creation notification sent to {} for job {}", user.getEmail(), job.getJobNumber());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send job creation notification for job {}: {}", jobId, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendJobCreationEmail(User user, Job job) {
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setFrom(smtpUsername);
|
||||
message.setTo(user.getEmail());
|
||||
message.setSubject("Neuer Job erstellt - " + (job.getJobNumber() != null ? job.getJobNumber() : "Job " + job.getId()));
|
||||
|
||||
String fullName = buildFullName(user);
|
||||
|
||||
// Count tasks for this job
|
||||
var allTasks = taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId());
|
||||
int taskCount = allTasks.size();
|
||||
|
||||
StringBuilder body = new StringBuilder();
|
||||
body.append("Hallo ").append(fullName).append(",\n\n");
|
||||
body.append("ein neuer Job wurde erfolgreich erstellt:\n\n");
|
||||
body.append("Job: ").append(job.getJobNumber() != null ? job.getJobNumber() : "Unbekannt").append("\n");
|
||||
|
||||
if (job.getDeliveryCompany() != null) {
|
||||
body.append("Kunde: ").append(job.getDeliveryCompany()).append("\n");
|
||||
}
|
||||
|
||||
if (job.getPickupCity() != null || job.getDeliveryCity() != null) {
|
||||
body.append("Route: ");
|
||||
if (job.getPickupCity() != null) {
|
||||
body.append(job.getPickupCity());
|
||||
}
|
||||
if (job.getPickupCity() != null && job.getDeliveryCity() != null) {
|
||||
body.append(" → ");
|
||||
}
|
||||
if (job.getDeliveryCity() != null) {
|
||||
body.append(job.getDeliveryCity());
|
||||
}
|
||||
body.append("\n");
|
||||
}
|
||||
|
||||
if (taskCount > 0) {
|
||||
body.append("Anzahl Aufgaben: ").append(taskCount).append("\n");
|
||||
}
|
||||
|
||||
body.append("Status: ").append(job.getStatus() != null ? job.getStatus().getDisplayName() : "Unbekannt").append("\n");
|
||||
|
||||
if (job.getRemark() != null && !job.getRemark().isBlank()) {
|
||||
body.append("Bemerkung: ").append(job.getRemark()).append("\n");
|
||||
}
|
||||
|
||||
body.append("Erstellt am: ").append(job.getCreatedAt() != null ?
|
||||
job.getCreatedAt().format(java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")) : "Unbekannt").append("\n\n");
|
||||
|
||||
body.append("Der Job ist nun im System verfügbar und kann bearbeitet werden.\n\n");
|
||||
body.append("Mit freundlichen Grüßen,\n");
|
||||
body.append("Ihr Votianlt-System");
|
||||
|
||||
message.setText(body.toString());
|
||||
mailSender.send(message);
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,13 @@ 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
|
||||
mail.smtp.password=SV1705CA!noreply
|
||||
mail.smtp.host=smtp.ionos.de
|
||||
mail.smtp.port=587
|
||||
# Mail Configuration (Spring Boot Standard)
|
||||
spring.mail.host=mailhub.assecutor.org
|
||||
spring.mail.port=587
|
||||
spring.mail.username=noreply@assecutor.org
|
||||
spring.mail.password=OStRIL,_,31
|
||||
spring.mail.properties.mail.smtp.auth=true
|
||||
spring.mail.properties.mail.smtp.starttls.enable=true
|
||||
|
||||
# HTTP request size limits for large payloads
|
||||
server.max-http-request-header-size=8MB
|
||||
|
||||
Reference in New Issue
Block a user