Erweiterungen

This commit is contained in:
2025-10-14 10:25:26 +02:00
parent ad10e81326
commit 4440825024
2 changed files with 171 additions and 5 deletions

View File

@@ -29,6 +29,7 @@ import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouteParameters;
import com.vaadin.flow.shared.Registration;
import de.assecutor.votianlt.model.AppUser;
import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.model.Message;
import de.assecutor.votianlt.model.MessageContentType;
import de.assecutor.votianlt.model.MessageOrigin;
@@ -154,6 +155,8 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver {
this.jobNumberContext = filteredMessages.stream().map(Message::getJobNumber)
.filter(value -> value != null && !value.isBlank()).findFirst().orElse(null);
ensureJobContextForConversation(filteredMessages);
String conversationTitle = resolveConversationTitle(filteredMessages, conversationId);
HorizontalLayout headerLayout = createHeaderLayout(clientName, conversationTitle);
@@ -763,6 +766,8 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver {
String sender = Optional.ofNullable(securityService.getCurrentUsername()).filter(name -> !name.isBlank())
.orElse("System");
ensureJobContextForConversation(currentMessages);
try {
Message saved;
if (jobConversation) {
@@ -843,6 +848,60 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver {
return value.replaceAll("[^a-zA-Z0-9_-]", "_").toLowerCase();
}
private void ensureJobContextForConversation(List<Message> messages) {
if (!jobConversation) {
jobIdContext = null;
jobNumberContext = null;
return;
}
String token = extractJobConversationToken();
if (token == null || token.isBlank()) {
return;
}
if (jobIdContext == null && ObjectId.isValid(token)) {
try {
jobIdContext = new ObjectId(token);
} catch (IllegalArgumentException ex) {
log.debug("Conversation token {} could not be converted to ObjectId", token, ex);
}
}
if (messages != null && (jobNumberContext == null || jobNumberContext.isBlank())) {
jobNumberContext = messages.stream()
.map(Message::getJobNumber)
.filter(value -> value != null && !value.isBlank())
.findFirst()
.orElse(jobNumberContext);
}
if (jobIdContext == null || jobNumberContext == null || jobNumberContext.isBlank()) {
Optional<Job> jobOptional = messageService.findJobByIdentifier(token);
if (jobOptional.isEmpty() && jobNumberContext != null && !jobNumberContext.isBlank()) {
jobOptional = messageService.findJobByIdentifier(jobNumberContext);
}
if (jobOptional.isPresent()) {
Job job = jobOptional.get();
jobIdContext = Optional.ofNullable(job.getId()).orElse(jobIdContext);
jobNumberContext = Optional.ofNullable(job.getJobNumber())
.filter(value -> !value.isBlank())
.orElse(jobNumberContext);
}
}
if (jobNumberContext == null || jobNumberContext.isBlank()) {
jobNumberContext = token;
}
}
private String extractJobConversationToken() {
if (!jobConversation || conversationId == null || !conversationId.startsWith("job-")) {
return null;
}
return conversationId.length() > 4 ? conversationId.substring(4) : "";
}
private String resolveConversationTitle(List<Message> messages, String conversationId) {
if (conversationId == null) {
return "Konversation";
@@ -947,6 +1006,8 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver {
// Add new message to the list
currentMessages.add(message);
ensureJobContextForConversation(currentMessages);
// Re-render all messages with the new message included
renderMessages();

View File

@@ -1,5 +1,6 @@
package de.assecutor.votianlt.service;
import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.model.Message;
import de.assecutor.votianlt.model.MessageContentType;
import de.assecutor.votianlt.model.MessageOrigin;
@@ -8,6 +9,7 @@ import de.assecutor.votianlt.dto.ChatMessageInboundPayload;
import de.assecutor.votianlt.dto.ChatMessageOutboundPayload;
import de.assecutor.votianlt.event.MessageReceivedEvent;
import de.assecutor.votianlt.mqtt.MqttPublisher;
import de.assecutor.votianlt.repository.JobRepository;
import de.assecutor.votianlt.repository.MessageRepository;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
@@ -23,12 +25,14 @@ import java.util.Optional;
public class MessageService {
private final MessageRepository messageRepository;
private final JobRepository jobRepository;
private final MqttPublisher mqttPublisher;
private final ApplicationEventPublisher eventPublisher;
public MessageService(MessageRepository messageRepository, MqttPublisher mqttPublisher,
ApplicationEventPublisher eventPublisher) {
public MessageService(MessageRepository messageRepository, JobRepository jobRepository,
MqttPublisher mqttPublisher, ApplicationEventPublisher eventPublisher) {
this.messageRepository = messageRepository;
this.jobRepository = jobRepository;
this.mqttPublisher = mqttPublisher;
this.eventPublisher = eventPublisher;
}
@@ -66,7 +70,9 @@ public class MessageService {
public Message sendJobMessageToClient(String content, String sender, String receiver,
MessageContentType contentType, ObjectId jobId, String jobNumber) {
Message message = new Message(content, sender, receiver, MessageOrigin.SERVER, contentType, jobId, jobNumber);
JobContext context = resolveJobContext(jobId, jobNumber);
Message message = new Message(content, sender, receiver, MessageOrigin.SERVER, contentType,
context.jobId(), context.jobNumber());
message = saveMessage(message);
publishMessageToMqtt(message, receiver);
return message;
@@ -79,8 +85,9 @@ public class MessageService {
Message message;
MessageContentType contentType = payload.contentType();
if (payload.hasJobContext()) {
JobContext context = resolveJobContext(payload.jobId(), payload.jobNumber());
message = new Message(payload.content(), payload.sender(), payload.receiver(),
MessageOrigin.CLIENT, contentType, payload.jobId(), payload.jobNumber());
MessageOrigin.CLIENT, contentType, context.jobId(), context.jobNumber());
} else {
message = new Message(payload.content(), payload.sender(), payload.receiver(),
MessageOrigin.CLIENT, contentType);
@@ -222,4 +229,102 @@ public class MessageService {
messageRepository.deleteById(messageId);
log.info("Deleted message {}", messageId);
}
public Optional<Job> findJobByIdentifier(String identifier) {
if (identifier == null || identifier.isBlank()) {
return Optional.empty();
}
Optional<Job> jobOptional = Optional.empty();
if (ObjectId.isValid(identifier)) {
try {
jobOptional = jobRepository.findById(new ObjectId(identifier));
} catch (IllegalArgumentException ex) {
log.debug("Identifier {} could not be parsed as ObjectId: {}", identifier, ex.getMessage());
}
}
if (jobOptional.isPresent()) {
return jobOptional;
}
return lookupJobByNumber(identifier);
}
private JobContext resolveJobContext(ObjectId jobId, String jobNumber) {
ObjectId resolvedJobId = jobId;
String resolvedJobNumber = jobNumber;
Optional<Job> jobOptional = lookupJob(jobId, jobNumber);
if (jobOptional.isPresent()) {
Job job = jobOptional.get();
resolvedJobId = job.getId();
resolvedJobNumber = Optional.ofNullable(job.getJobNumber()).orElse(resolvedJobNumber);
}
return new JobContext(resolvedJobId, resolvedJobNumber);
}
private Optional<Job> lookupJob(ObjectId jobId, String jobNumber) {
Optional<Job> jobOptional = Optional.empty();
if (jobId != null) {
jobOptional = jobRepository.findById(jobId);
if (jobOptional.isPresent()) {
return jobOptional;
}
}
if (jobNumber == null || jobNumber.isBlank()) {
return jobOptional;
}
jobOptional = jobRepository.findByJobNumber(jobNumber);
if (jobOptional.isPresent()) {
return jobOptional;
}
return lookupJobByNumber(jobNumber);
}
private Optional<Job> lookupJobByNumber(String candidate) {
if (candidate == null || candidate.isBlank()) {
return Optional.empty();
}
String normalizedCandidate = normalizeJobToken(candidate);
Optional<Job> jobOptional = jobRepository.findByJobNumber(candidate);
if (jobOptional.isPresent()) {
return jobOptional;
}
String relaxedCandidate = candidate.replace('_', ' ');
if (!relaxedCandidate.equals(candidate)) {
jobOptional = jobRepository.findByJobNumber(relaxedCandidate);
if (jobOptional.isPresent()) {
return jobOptional;
}
}
List<Job> fuzzyMatches = jobRepository.findByJobNumberContainingIgnoreCase(relaxedCandidate);
if (fuzzyMatches.isEmpty() && !relaxedCandidate.equals(candidate)) {
fuzzyMatches = jobRepository.findByJobNumberContainingIgnoreCase(candidate);
}
if (fuzzyMatches.isEmpty()) {
fuzzyMatches = jobRepository.findAll();
}
return fuzzyMatches.stream()
.filter(job -> normalizeJobToken(job.getJobNumber()).equalsIgnoreCase(normalizedCandidate))
.findFirst();
}
private String normalizeJobToken(String value) {
return value == null ? "" : value.replaceAll("[^a-zA-Z0-9_-]", "_");
}
private record JobContext(ObjectId jobId, String jobNumber) {
}
}