Add chat takeover
This commit is contained in:
@@ -86,4 +86,13 @@ public class ChatController {
|
|||||||
List<MessageResponse> messages = chatService.getMessages(id, user.getId(), user.getRole());
|
List<MessageResponse> messages = chatService.getMessages(id, user.getId(), user.getRole());
|
||||||
return ResponseEntity.ok(messages);
|
return ResponseEntity.ok(messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/conversations/{id}/request-human")
|
||||||
|
@PreAuthorize("hasRole('CUSTOMER')")
|
||||||
|
public ResponseEntity<ConversationResponse> requestHumanTakeover(@PathVariable Long id) {
|
||||||
|
User user = getCurrentUser();
|
||||||
|
ConversationResponse conversation = chatService.requestHumanTakeover(id, user.getId(), user.getRole());
|
||||||
|
chatRealtimeService.publishConversationUpdate(id);
|
||||||
|
return ResponseEntity.ok(conversation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,19 +9,23 @@ public class ConversationResponse {
|
|||||||
private Long customerId;
|
private Long customerId;
|
||||||
private Long staffId;
|
private Long staffId;
|
||||||
private String status;
|
private String status;
|
||||||
|
private String mode;
|
||||||
private String lastMessage;
|
private String lastMessage;
|
||||||
|
private LocalDateTime humanRequestedAt;
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
public ConversationResponse() {
|
public ConversationResponse() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConversationResponse(Long id, Long customerId, Long staffId, String status, String lastMessage, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
public ConversationResponse(Long id, Long customerId, Long staffId, String status, String mode, String lastMessage, LocalDateTime humanRequestedAt, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.customerId = customerId;
|
this.customerId = customerId;
|
||||||
this.staffId = staffId;
|
this.staffId = staffId;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
this.mode = mode;
|
||||||
this.lastMessage = lastMessage;
|
this.lastMessage = lastMessage;
|
||||||
|
this.humanRequestedAt = humanRequestedAt;
|
||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
this.updatedAt = updatedAt;
|
this.updatedAt = updatedAt;
|
||||||
}
|
}
|
||||||
@@ -32,7 +36,9 @@ public class ConversationResponse {
|
|||||||
response.setCustomerId(conversation.getCustomerId());
|
response.setCustomerId(conversation.getCustomerId());
|
||||||
response.setStaffId(conversation.getStaffId());
|
response.setStaffId(conversation.getStaffId());
|
||||||
response.setStatus(conversation.getStatus().name());
|
response.setStatus(conversation.getStatus().name());
|
||||||
|
response.setMode(conversation.getMode().name());
|
||||||
response.setLastMessage(lastMessage);
|
response.setLastMessage(lastMessage);
|
||||||
|
response.setHumanRequestedAt(conversation.getHumanRequestedAt());
|
||||||
response.setCreatedAt(conversation.getCreatedAt());
|
response.setCreatedAt(conversation.getCreatedAt());
|
||||||
response.setUpdatedAt(conversation.getUpdatedAt());
|
response.setUpdatedAt(conversation.getUpdatedAt());
|
||||||
return response;
|
return response;
|
||||||
@@ -70,6 +76,14 @@ public class ConversationResponse {
|
|||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMode(String mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
public String getLastMessage() {
|
public String getLastMessage() {
|
||||||
return lastMessage;
|
return lastMessage;
|
||||||
}
|
}
|
||||||
@@ -78,6 +92,14 @@ public class ConversationResponse {
|
|||||||
this.lastMessage = lastMessage;
|
this.lastMessage = lastMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getHumanRequestedAt() {
|
||||||
|
return humanRequestedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHumanRequestedAt(LocalDateTime humanRequestedAt) {
|
||||||
|
this.humanRequestedAt = humanRequestedAt;
|
||||||
|
}
|
||||||
|
|
||||||
public LocalDateTime getCreatedAt() {
|
public LocalDateTime getCreatedAt() {
|
||||||
return createdAt;
|
return createdAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ public class Conversation {
|
|||||||
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20)")
|
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20)")
|
||||||
private ConversationStatus status = ConversationStatus.OPEN;
|
private ConversationStatus status = ConversationStatus.OPEN;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20)")
|
||||||
|
private ConversationMode mode = ConversationMode.AUTOMATED;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private LocalDateTime humanRequestedAt;
|
||||||
|
|
||||||
@CreationTimestamp
|
@CreationTimestamp
|
||||||
@Column(name = "created_at", nullable = false, updatable = false)
|
@Column(name = "created_at", nullable = false, updatable = false)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
@@ -36,14 +43,20 @@ public class Conversation {
|
|||||||
OPEN, CLOSED
|
OPEN, CLOSED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ConversationMode {
|
||||||
|
AUTOMATED, HUMAN
|
||||||
|
}
|
||||||
|
|
||||||
public Conversation() {
|
public Conversation() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Conversation(Long id, Long customerId, Long staffId, ConversationStatus status, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
public Conversation(Long id, Long customerId, Long staffId, ConversationStatus status, ConversationMode mode, LocalDateTime humanRequestedAt, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.customerId = customerId;
|
this.customerId = customerId;
|
||||||
this.staffId = staffId;
|
this.staffId = staffId;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
this.mode = mode;
|
||||||
|
this.humanRequestedAt = humanRequestedAt;
|
||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
this.updatedAt = updatedAt;
|
this.updatedAt = updatedAt;
|
||||||
}
|
}
|
||||||
@@ -80,6 +93,22 @@ public class Conversation {
|
|||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ConversationMode getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMode(ConversationMode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getHumanRequestedAt() {
|
||||||
|
return humanRequestedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHumanRequestedAt(LocalDateTime humanRequestedAt) {
|
||||||
|
this.humanRequestedAt = humanRequestedAt;
|
||||||
|
}
|
||||||
|
|
||||||
public LocalDateTime getCreatedAt() {
|
public LocalDateTime getCreatedAt() {
|
||||||
return createdAt;
|
return createdAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.springframework.security.access.AccessDeniedException;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ public class ChatService {
|
|||||||
Conversation conversation = new Conversation();
|
Conversation conversation = new Conversation();
|
||||||
conversation.setCustomerId(customer.getCustomerId());
|
conversation.setCustomerId(customer.getCustomerId());
|
||||||
conversation.setStatus(Conversation.ConversationStatus.OPEN);
|
conversation.setStatus(Conversation.ConversationStatus.OPEN);
|
||||||
|
conversation.setMode(Conversation.ConversationMode.AUTOMATED);
|
||||||
conversation = conversationRepository.save(conversation);
|
conversation = conversationRepository.save(conversation);
|
||||||
|
|
||||||
Message message = new Message();
|
Message message = new Message();
|
||||||
@@ -132,12 +134,35 @@ public class ChatService {
|
|||||||
|
|
||||||
if (role == User.Role.STAFF && conversation.getStaffId() == null) {
|
if (role == User.Role.STAFF && conversation.getStaffId() == null) {
|
||||||
conversation.setStaffId(userId);
|
conversation.setStaffId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == User.Role.STAFF) {
|
||||||
|
conversation.setMode(Conversation.ConversationMode.HUMAN);
|
||||||
conversationRepository.save(conversation);
|
conversationRepository.save(conversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MessageResponse.fromEntity(message);
|
return MessageResponse.fromEntity(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public ConversationResponse requestHumanTakeover(Long conversationId, Long userId, User.Role role) {
|
||||||
|
Conversation conversation = conversationRepository.findById(conversationId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||||
|
|
||||||
|
if (role != User.Role.CUSTOMER || !hasConversationAccess(conversation, userId, role)) {
|
||||||
|
throw new AccessDeniedException("You can only request human takeover for your own conversations");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conversation.getHumanRequestedAt() == null) {
|
||||||
|
conversation.setHumanRequestedAt(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
conversationRepository.save(conversation);
|
||||||
|
|
||||||
|
List<Message> messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId);
|
||||||
|
String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1).getContent();
|
||||||
|
return ConversationResponse.fromEntity(conversation, lastMessage);
|
||||||
|
}
|
||||||
|
|
||||||
public List<MessageResponse> getMessages(Long conversationId, Long userId, User.Role role) {
|
public List<MessageResponse> getMessages(Long conversationId, Long userId, User.Role role) {
|
||||||
Conversation conversation = conversationRepository.findById(conversationId)
|
Conversation conversation = conversationRepository.findById(conversationId)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
ALTER TABLE conversation
|
||||||
|
ADD COLUMN mode VARCHAR(20) NOT NULL DEFAULT 'AUTOMATED' AFTER status,
|
||||||
|
ADD COLUMN humanRequestedAt TIMESTAMP NULL AFTER mode;
|
||||||
|
|
||||||
|
UPDATE conversation
|
||||||
|
SET mode = CASE
|
||||||
|
WHEN staffId IS NULL THEN 'AUTOMATED'
|
||||||
|
ELSE 'HUMAN'
|
||||||
|
END;
|
||||||
Reference in New Issue
Block a user