Erweiterungen

This commit is contained in:
2025-09-15 17:49:22 +02:00
parent 3fa56da257
commit ba9b47be89
9 changed files with 534 additions and 11 deletions

83
CLAUDE.md Normal file
View 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`.

View File

@@ -103,10 +103,10 @@
<artifactId>openpdf</artifactId> <artifactId>openpdf</artifactId>
<version>1.3.30</version> <version>1.3.30</version>
</dependency> </dependency>
<!-- Spring Boot Mail Starter -->
<dependency> <dependency>
<groupId>com.sun.mail</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>jakarta.mail</artifactId> <artifactId>spring-boot-starter-mail</artifactId>
<version>2.0.1</version>
</dependency> </dependency>
<!-- Jackson JSR310 module for Java 8 date/time support --> <!-- Jackson JSR310 module for Java 8 date/time support -->

Binary file not shown.

View File

@@ -19,6 +19,7 @@ import de.assecutor.votianlt.model.Photo;
import de.assecutor.votianlt.model.Barcode; import de.assecutor.votianlt.model.Barcode;
import de.assecutor.votianlt.model.Signature; import de.assecutor.votianlt.model.Signature;
import de.assecutor.votianlt.service.JobHistoryService; import de.assecutor.votianlt.service.JobHistoryService;
import de.assecutor.votianlt.service.EmailService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
@@ -57,8 +58,9 @@ public class MessageController {
private final BarcodeRepository barcodeRepository; private final BarcodeRepository barcodeRepository;
private final SignatureRepository signatureRepository; private final SignatureRepository signatureRepository;
private final JobHistoryService jobHistoryService; 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.mqttPublisher = mqttPublisher;
this.appUserRepository = appUserRepository; this.appUserRepository = appUserRepository;
this.appUserService = appUserService; this.appUserService = appUserService;
@@ -69,6 +71,7 @@ public class MessageController {
this.barcodeRepository = barcodeRepository; this.barcodeRepository = barcodeRepository;
this.signatureRepository = signatureRepository; this.signatureRepository = signatureRepository;
this.jobHistoryService = jobHistoryService; 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()); 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={}", log.info("Task marked completed. taskId={}, completedBy={}, extraData={}",
taskIdStr, task.getCompletedBy(), extraDataSummary); taskIdStr, task.getCompletedBy(), extraDataSummary);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {

View File

@@ -9,6 +9,7 @@ import de.assecutor.votianlt.repository.TaskRepository;
import de.assecutor.votianlt.security.SecurityService; import de.assecutor.votianlt.security.SecurityService;
import de.assecutor.votianlt.repository.CargoItemRepository; import de.assecutor.votianlt.repository.CargoItemRepository;
import de.assecutor.votianlt.service.JobHistoryService; import de.assecutor.votianlt.service.JobHistoryService;
import de.assecutor.votianlt.service.EmailService;
import java.util.Objects; import java.util.Objects;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -30,6 +31,7 @@ public class AddJobService {
private final TaskRepository taskRepository; private final TaskRepository taskRepository;
private final SecurityService securityService; private final SecurityService securityService;
private final JobHistoryService jobHistoryService; private final JobHistoryService jobHistoryService;
private final EmailService emailService;
/** /**
* Speichert einen neuen Auftrag samt CargoItems und Tasks * Speichert einen neuen Auftrag samt CargoItems und Tasks
* @param job der Auftrag * @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()); 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()); log.info("Auftrag erfolgreich gespeichert: {}", savedJob.getJobNumber());
return savedJob; return savedJob;

View File

@@ -18,6 +18,9 @@ public interface AppUserRepository extends MongoRepository<AppUser, ObjectId> {
AppUser findByPasswordCode(String passwordCode); 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 // Custom query methods can be added here if needed
// List<AppUser> findByBezeichnung(String bezeichnung); // List<AppUser> findByBezeichnung(String bezeichnung);
} }

View File

@@ -1,13 +1,14 @@
package de.assecutor.votianlt.repository; package de.assecutor.votianlt.repository;
import de.assecutor.votianlt.model.User; import de.assecutor.votianlt.model.User;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.Optional; import java.util.Optional;
@Repository @Repository
public interface UserRepository extends MongoRepository<User, String> { public interface UserRepository extends MongoRepository<User, ObjectId> {
Optional<User> findByEmail(String email); Optional<User> findByEmail(String email);

View 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);
}
}

View File

@@ -22,11 +22,13 @@ spring.data.mongodb.socket-timeout=30000
spring.data.mongodb.connect-timeout=10000 spring.data.mongodb.connect-timeout=10000
spring.data.mongodb.server-selection-timeout=5000 spring.data.mongodb.server-selection-timeout=5000
# Mail Configuration # Mail Configuration (Spring Boot Standard)
mail.smtp.username=no-reply@appcreation.de spring.mail.host=mailhub.assecutor.org
mail.smtp.password=SV1705CA!noreply spring.mail.port=587
mail.smtp.host=smtp.ionos.de spring.mail.username=noreply@assecutor.org
mail.smtp.port=587 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 # HTTP request size limits for large payloads
server.max-http-request-header-size=8MB server.max-http-request-header-size=8MB