Fix backend appointments and chat #54
@@ -4,6 +4,7 @@ import com.petshop.backend.dto.chat.ConversationRequest;
|
||||
import com.petshop.backend.dto.chat.ConversationResponse;
|
||||
import com.petshop.backend.dto.chat.MessageRequest;
|
||||
import com.petshop.backend.dto.chat.MessageResponse;
|
||||
import com.petshop.backend.dto.chat.UpdateConversationRequest;
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.repository.CustomerRepository;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
@@ -96,4 +97,13 @@ public class ChatController {
|
||||
chatRealtimeService.publishConversationUpdate(id);
|
||||
return ResponseEntity.ok(conversation);
|
||||
}
|
||||
|
||||
@PutMapping("/conversations/{id}")
|
||||
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||
public ResponseEntity<ConversationResponse> updateConversation(@PathVariable Long id, @Valid @RequestBody UpdateConversationRequest request) {
|
||||
User user = getCurrentUser();
|
||||
ConversationResponse conversation = chatService.updateConversation(id, user.getId(), user.getRole(), request);
|
||||
chatRealtimeService.publishConversationUpdate(id);
|
||||
return ResponseEntity.ok(conversation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.petshop.backend.dto.chat;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
public class UpdateConversationRequest {
|
||||
@NotBlank(message = "Status is required")
|
||||
@Pattern(regexp = "^(OPEN|CLOSED)$", message = "Status must be OPEN or CLOSED")
|
||||
private String status;
|
||||
|
||||
public UpdateConversationRequest() {
|
||||
}
|
||||
|
||||
public UpdateConversationRequest(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.petshop.backend.dto.chat.ConversationRequest;
|
||||
import com.petshop.backend.dto.chat.ConversationResponse;
|
||||
import com.petshop.backend.dto.chat.MessageRequest;
|
||||
import com.petshop.backend.dto.chat.MessageResponse;
|
||||
import com.petshop.backend.dto.chat.UpdateConversationRequest;
|
||||
import com.petshop.backend.entity.Conversation;
|
||||
import com.petshop.backend.entity.Customer;
|
||||
import com.petshop.backend.entity.Message;
|
||||
@@ -116,6 +117,10 @@ public class ChatService {
|
||||
Conversation conversation = conversationRepository.findById(conversationId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||
|
||||
if (conversation.getStatus() == Conversation.ConversationStatus.CLOSED) {
|
||||
throw new AccessDeniedException("Conversation is closed");
|
||||
}
|
||||
|
||||
if (!hasConversationAccess(conversation, userId, role)) {
|
||||
if (role == User.Role.CUSTOMER) {
|
||||
throw new AccessDeniedException("You can only send messages to your own conversations");
|
||||
@@ -149,6 +154,10 @@ public class ChatService {
|
||||
Conversation conversation = conversationRepository.findById(conversationId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||
|
||||
if (conversation.getStatus() == Conversation.ConversationStatus.CLOSED) {
|
||||
throw new AccessDeniedException("Conversation is closed");
|
||||
}
|
||||
|
||||
if (role != User.Role.CUSTOMER || !hasConversationAccess(conversation, userId, role)) {
|
||||
throw new AccessDeniedException("You can only request human takeover for your own conversations");
|
||||
}
|
||||
@@ -163,6 +172,28 @@ public class ChatService {
|
||||
return ConversationResponse.fromEntity(conversation, lastMessage);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ConversationResponse updateConversation(Long conversationId, Long userId, User.Role role, UpdateConversationRequest request) {
|
||||
Conversation conversation = conversationRepository.findById(conversationId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||
|
||||
if (!hasConversationAccess(conversation, userId, role)) {
|
||||
if (role == User.Role.CUSTOMER) {
|
||||
throw new AccessDeniedException("You can only close your own conversations");
|
||||
}
|
||||
if (role == User.Role.STAFF) {
|
||||
throw new AccessDeniedException("You can only close conversations assigned to you or unassigned conversations");
|
||||
}
|
||||
}
|
||||
|
||||
conversation.setStatus(Conversation.ConversationStatus.valueOf(request.getStatus()));
|
||||
conversation = 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) {
|
||||
Conversation conversation = conversationRepository.findById(conversationId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
package com.petshop.backend.service;
|
||||
|
||||
import com.petshop.backend.entity.Appointment;
|
||||
import com.petshop.backend.entity.Customer;
|
||||
import com.petshop.backend.entity.Pet;
|
||||
import com.petshop.backend.entity.Service;
|
||||
import com.petshop.backend.entity.StoreLocation;
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.repository.AppointmentRepository;
|
||||
import com.petshop.backend.repository.CustomerRepository;
|
||||
import com.petshop.backend.repository.PetRepository;
|
||||
import com.petshop.backend.repository.ServiceRepository;
|
||||
import com.petshop.backend.repository.StoreRepository;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AppointmentServiceTest {
|
||||
|
||||
@Mock
|
||||
private AppointmentRepository appointmentRepository;
|
||||
|
||||
@Mock
|
||||
private CustomerRepository customerRepository;
|
||||
|
||||
@Mock
|
||||
private PetRepository petRepository;
|
||||
|
||||
@Mock
|
||||
private ServiceRepository serviceRepository;
|
||||
|
||||
@Mock
|
||||
private StoreRepository storeRepository;
|
||||
|
||||
@Mock
|
||||
private UserRepository userRepository;
|
||||
|
||||
@InjectMocks
|
||||
private AppointmentService appointmentService;
|
||||
|
||||
private Customer customer;
|
||||
private StoreLocation store;
|
||||
private Service grooming;
|
||||
private Service nailTrim;
|
||||
private Pet pet;
|
||||
private LocalDate date;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
customer = new Customer();
|
||||
customer.setCustomerId(1L);
|
||||
customer.setFirstName("Pat");
|
||||
customer.setLastName("Owner");
|
||||
|
||||
store = new StoreLocation();
|
||||
store.setStoreId(1L);
|
||||
store.setStoreName("Main Store");
|
||||
|
||||
grooming = new Service();
|
||||
grooming.setServiceId(1L);
|
||||
grooming.setServiceName("Grooming");
|
||||
grooming.setServiceDuration(30);
|
||||
|
||||
nailTrim = new Service();
|
||||
nailTrim.setServiceId(2L);
|
||||
nailTrim.setServiceName("Nail Trim");
|
||||
nailTrim.setServiceDuration(30);
|
||||
|
||||
pet = new Pet();
|
||||
pet.setPetId(1L);
|
||||
pet.setPetName("Milo");
|
||||
|
||||
date = LocalDate.now().plusDays(1);
|
||||
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkAvailabilityAllowsDifferentServicesAtSameTime() {
|
||||
Appointment existing = appointment(1L, date, LocalTime.of(10, 0), grooming, store);
|
||||
when(storeRepository.findById(1L)).thenReturn(Optional.of(store));
|
||||
when(serviceRepository.findById(2L)).thenReturn(Optional.of(nailTrim));
|
||||
when(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of(existing));
|
||||
|
||||
List<String> slots = appointmentService.checkAvailability(1L, 2L, date);
|
||||
|
||||
assertTrue(slots.contains("10:00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkAvailabilityBlocksSameServiceAtSameTime() {
|
||||
Appointment existing = appointment(1L, date, LocalTime.of(10, 0), grooming, store);
|
||||
when(storeRepository.findById(1L)).thenReturn(Optional.of(store));
|
||||
when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming));
|
||||
when(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of(existing));
|
||||
|
||||
List<String> slots = appointmentService.checkAvailability(1L, 1L, date);
|
||||
|
||||
assertFalse(slots.contains("10:00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelledAppointmentsDoNotBlockAvailability() {
|
||||
when(storeRepository.findById(1L)).thenReturn(Optional.of(store));
|
||||
when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming));
|
||||
when(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of());
|
||||
|
||||
List<String> slots = appointmentService.checkAvailability(1L, 1L, date);
|
||||
|
||||
assertTrue(slots.contains("10:00"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateAppointmentDoesNotConflictWithItself() {
|
||||
Appointment existing = appointment(1L, date, LocalTime.of(10, 0), grooming, store);
|
||||
User user = new User();
|
||||
user.setId(10L);
|
||||
user.setUsername("pat");
|
||||
user.setRole(User.Role.CUSTOMER);
|
||||
user.setTokenVersion(0);
|
||||
when(userRepository.findById(10L)).thenReturn(Optional.of(user));
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
new com.petshop.backend.security.AppPrincipal(10L, "pat", User.Role.CUSTOMER, 0),
|
||||
"n/a",
|
||||
List.of(new SimpleGrantedAuthority("ROLE_CUSTOMER"))
|
||||
)
|
||||
);
|
||||
|
||||
when(appointmentRepository.findById(1L)).thenReturn(Optional.of(existing));
|
||||
when(customerRepository.findById(1L)).thenReturn(Optional.of(customer));
|
||||
when(storeRepository.findById(1L)).thenReturn(Optional.of(store));
|
||||
when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming));
|
||||
when(petRepository.findById(1L)).thenReturn(Optional.of(pet));
|
||||
when(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of(existing));
|
||||
when(appointmentRepository.save(any(Appointment.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
var request = new com.petshop.backend.dto.appointment.AppointmentRequest();
|
||||
request.setCustomerId(1L);
|
||||
request.setStoreId(1L);
|
||||
request.setServiceId(1L);
|
||||
request.setAppointmentDate(date);
|
||||
request.setAppointmentTime(LocalTime.of(10, 0));
|
||||
request.setAppointmentStatus("Booked");
|
||||
request.setPetIds(List.of(1L));
|
||||
|
||||
var response = appointmentService.updateAppointment(1L, request);
|
||||
|
||||
assertEquals(1L, response.getAppointmentId());
|
||||
assertEquals("Booked", response.getAppointmentStatus());
|
||||
}
|
||||
|
||||
private Appointment appointment(Long id, LocalDate date, LocalTime time, Service service, StoreLocation storeLocation) {
|
||||
Appointment appointment = new Appointment();
|
||||
appointment.setAppointmentId(id);
|
||||
appointment.setAppointmentDate(date);
|
||||
appointment.setAppointmentTime(time);
|
||||
appointment.setAppointmentStatus("Booked");
|
||||
appointment.setService(service);
|
||||
appointment.setStore(storeLocation);
|
||||
appointment.setCustomer(customer);
|
||||
appointment.setPets(Set.of());
|
||||
return appointment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package com.petshop.backend.service;
|
||||
|
||||
import com.petshop.backend.dto.chat.MessageRequest;
|
||||
import com.petshop.backend.dto.chat.UpdateConversationRequest;
|
||||
import com.petshop.backend.entity.Conversation;
|
||||
import com.petshop.backend.entity.Customer;
|
||||
import com.petshop.backend.entity.Message;
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.repository.ConversationRepository;
|
||||
import com.petshop.backend.repository.CustomerRepository;
|
||||
import com.petshop.backend.repository.MessageRepository;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ChatServiceTest {
|
||||
|
||||
@Mock
|
||||
private ConversationRepository conversationRepository;
|
||||
|
||||
@Mock
|
||||
private MessageRepository messageRepository;
|
||||
|
||||
@Mock
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Mock
|
||||
private CustomerRepository customerRepository;
|
||||
|
||||
@InjectMocks
|
||||
private ChatService chatService;
|
||||
|
||||
private Customer customer;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
customer = new Customer();
|
||||
customer.setCustomerId(1L);
|
||||
customer.setUserId(10L);
|
||||
customer.setFirstName("Pat");
|
||||
customer.setLastName("Owner");
|
||||
customer.setEmail("pat@example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateConversationMarksConversationClosed() {
|
||||
Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.OPEN);
|
||||
when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation));
|
||||
when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer));
|
||||
when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(messageRepository.findByConversationIdOrderByTimestampAsc(99L))
|
||||
.thenReturn(List.of(message("hello")));
|
||||
|
||||
var response = chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("CLOSED"));
|
||||
|
||||
assertEquals("CLOSED", response.getStatus());
|
||||
assertEquals("hello", response.getLastMessage());
|
||||
verify(conversationRepository).save(conversation);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateConversationRejectsOtherCustomer() {
|
||||
Conversation conversation = conversation(99L, 2L, null, Conversation.ConversationStatus.OPEN);
|
||||
when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation));
|
||||
when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer));
|
||||
|
||||
assertThrows(AccessDeniedException.class,
|
||||
() -> chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("CLOSED")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateConversationIsIdempotent() {
|
||||
Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.CLOSED);
|
||||
when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation));
|
||||
when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer));
|
||||
when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of());
|
||||
|
||||
var response = chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("CLOSED"));
|
||||
|
||||
assertEquals("CLOSED", response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void staffCanCloseAssignedConversation() {
|
||||
Conversation conversation = conversation(99L, 1L, 77L, Conversation.ConversationStatus.OPEN);
|
||||
when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation));
|
||||
when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of());
|
||||
|
||||
var response = chatService.updateConversation(99L, 77L, User.Role.STAFF, new UpdateConversationRequest("CLOSED"));
|
||||
|
||||
assertEquals("CLOSED", response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void staffCanCloseUnassignedConversation() {
|
||||
Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.OPEN);
|
||||
when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation));
|
||||
when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of());
|
||||
|
||||
var response = chatService.updateConversation(99L, 77L, User.Role.STAFF, new UpdateConversationRequest("CLOSED"));
|
||||
|
||||
assertEquals("CLOSED", response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void adminCanCloseAnyConversation() {
|
||||
Conversation conversation = conversation(99L, 2L, 88L, Conversation.ConversationStatus.OPEN);
|
||||
when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation));
|
||||
when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of());
|
||||
|
||||
var response = chatService.updateConversation(99L, 1L, User.Role.ADMIN, new UpdateConversationRequest("CLOSED"));
|
||||
|
||||
assertEquals("CLOSED", response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateConversationCanReopenClosedConversation() {
|
||||
Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.CLOSED);
|
||||
when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation));
|
||||
when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer));
|
||||
when(conversationRepository.save(any(Conversation.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(messageRepository.findByConversationIdOrderByTimestampAsc(99L)).thenReturn(List.of());
|
||||
|
||||
var response = chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("OPEN"));
|
||||
|
||||
assertEquals("OPEN", response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateConversationRejectsInvalidStatus() {
|
||||
Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.OPEN);
|
||||
when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation));
|
||||
when(customerRepository.findByUserId(10L)).thenReturn(Optional.of(customer));
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> chatService.updateConversation(99L, 10L, User.Role.CUSTOMER, new UpdateConversationRequest("INVALID")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendMessageRejectsClosedConversation() {
|
||||
Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.CLOSED);
|
||||
when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation));
|
||||
|
||||
assertThrows(AccessDeniedException.class,
|
||||
() -> chatService.sendMessage(99L, 10L, User.Role.CUSTOMER, new MessageRequest("hello")));
|
||||
|
||||
verify(messageRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestHumanTakeoverRejectsClosedConversation() {
|
||||
Conversation conversation = conversation(99L, 1L, null, Conversation.ConversationStatus.CLOSED);
|
||||
when(conversationRepository.findById(99L)).thenReturn(Optional.of(conversation));
|
||||
|
||||
assertThrows(AccessDeniedException.class,
|
||||
() -> chatService.requestHumanTakeover(99L, 10L, User.Role.CUSTOMER));
|
||||
}
|
||||
|
||||
private Conversation conversation(Long id, Long customerId, Long staffId, Conversation.ConversationStatus status) {
|
||||
Conversation conversation = new Conversation();
|
||||
conversation.setId(id);
|
||||
conversation.setCustomerId(customerId);
|
||||
conversation.setStaffId(staffId);
|
||||
conversation.setStatus(status);
|
||||
conversation.setMode(Conversation.ConversationMode.AUTOMATED);
|
||||
conversation.setHumanRequestedAt(LocalDateTime.now());
|
||||
return conversation;
|
||||
}
|
||||
|
||||
private Message message(String content) {
|
||||
Message message = new Message();
|
||||
message.setConversationId(99L);
|
||||
message.setSenderId(10L);
|
||||
message.setContent(content);
|
||||
message.setIsRead(false);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user