Harden staff assignment

This commit is contained in:
2026-04-05 12:17:37 -06:00
parent 072c9aadea
commit 5d95613786
33 changed files with 887 additions and 30 deletions

View File

@@ -2,6 +2,8 @@ package com.petshop.backend.controller;
import com.petshop.backend.dto.common.DropdownOption;
import com.petshop.backend.entity.CustomerPet;
import com.petshop.backend.entity.EmployeeStore;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.*;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -25,12 +27,15 @@ public class DropdownController {
private final CategoryRepository categoryRepository;
private final StoreRepository storeRepository;
private final SupplierRepository supplierRepository;
private final EmployeeStoreRepository employeeStoreRepository;
private final UserRepository userRepository;
public DropdownController(PetRepository petRepository, CustomerRepository customerRepository,
CustomerPetRepository customerPetRepository,
ServiceRepository serviceRepository, ProductRepository productRepository,
CategoryRepository categoryRepository, StoreRepository storeRepository,
SupplierRepository supplierRepository) {
SupplierRepository supplierRepository, EmployeeStoreRepository employeeStoreRepository,
UserRepository userRepository) {
this.petRepository = petRepository;
this.customerRepository = customerRepository;
this.customerPetRepository = customerPetRepository;
@@ -39,6 +44,8 @@ public class DropdownController {
this.categoryRepository = categoryRepository;
this.storeRepository = storeRepository;
this.supplierRepository = supplierRepository;
this.employeeStoreRepository = employeeStoreRepository;
this.userRepository = userRepository;
}
@GetMapping("/pets")
@@ -129,6 +136,17 @@ public class DropdownController {
);
}
@GetMapping("/stores/{storeId}/employees")
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity<List<DropdownOption>> getStoreEmployees(@PathVariable Long storeId) {
return ResponseEntity.ok(
employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(storeId).stream()
.filter(this::isAssignableEmployee)
.map(this::toEmployeeOption)
.collect(Collectors.toList())
);
}
@GetMapping("/suppliers")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<List<DropdownOption>> getSuppliers() {
@@ -144,4 +162,20 @@ public class DropdownController {
String breed = pet.getBreed() == null || pet.getBreed().isBlank() ? "" : " · " + pet.getBreed();
return new DropdownOption(pet.getCustomerPetId(), pet.getPetName() + " (" + species + breed + ")");
}
private DropdownOption toEmployeeOption(EmployeeStore employeeStore) {
var employee = employeeStore.getEmployee();
return new DropdownOption(employee.getEmployeeId(), employee.getFirstName() + " " + employee.getLastName());
}
private boolean isAssignableEmployee(EmployeeStore employeeStore) {
Long userId = employeeStore.getEmployee().getUserId();
if (userId == null) {
return false;
}
return userRepository.findById(userId)
.map(User::getRole)
.filter(role -> role == User.Role.STAFF)
.isPresent();
}
}

View File

@@ -18,6 +18,8 @@ public class AdoptionRequest {
@NotBlank(message = "Adoption status is required")
private String adoptionStatus;
private Long employeeId;
public Long getPetId() {
return petId;
}
@@ -50,6 +52,14 @@ public class AdoptionRequest {
this.adoptionStatus = adoptionStatus;
}
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -58,12 +68,13 @@ public class AdoptionRequest {
return Objects.equals(petId, that.petId) &&
Objects.equals(customerId, that.customerId) &&
Objects.equals(adoptionDate, that.adoptionDate) &&
Objects.equals(adoptionStatus, that.adoptionStatus);
Objects.equals(adoptionStatus, that.adoptionStatus) &&
Objects.equals(employeeId, that.employeeId);
}
@Override
public int hashCode() {
return Objects.hash(petId, customerId, adoptionDate, adoptionStatus);
return Objects.hash(petId, customerId, adoptionDate, adoptionStatus, employeeId);
}
@Override
@@ -73,6 +84,7 @@ public class AdoptionRequest {
", customerId=" + customerId +
", adoptionDate=" + adoptionDate +
", adoptionStatus='" + adoptionStatus + '\'' +
", employeeId=" + employeeId +
'}';
}
}

View File

@@ -11,6 +11,8 @@ public class AdoptionResponse {
private String petName;
private Long customerId;
private String customerName;
private Long employeeId;
private String employeeName;
private LocalDate adoptionDate;
private String adoptionStatus;
private BigDecimal adoptionFee;
@@ -20,12 +22,14 @@ public class AdoptionResponse {
public AdoptionResponse() {
}
public AdoptionResponse(Long adoptionId, Long petId, String petName, Long customerId, String customerName, LocalDate adoptionDate, String adoptionStatus, BigDecimal adoptionFee, LocalDateTime createdAt, LocalDateTime updatedAt) {
public AdoptionResponse(Long adoptionId, Long petId, String petName, Long customerId, String customerName, Long employeeId, String employeeName, LocalDate adoptionDate, String adoptionStatus, BigDecimal adoptionFee, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.adoptionId = adoptionId;
this.petId = petId;
this.petName = petName;
this.customerId = customerId;
this.customerName = customerName;
this.employeeId = employeeId;
this.employeeName = employeeName;
this.adoptionDate = adoptionDate;
this.adoptionStatus = adoptionStatus;
this.adoptionFee = adoptionFee;
@@ -73,6 +77,22 @@ public class AdoptionResponse {
this.customerName = customerName;
}
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public LocalDate getAdoptionDate() {
return adoptionDate;
}

View File

@@ -29,6 +29,8 @@ public class AppointmentRequest {
private List<Long> customerPetIds;
private Long employeeId;
public Long getCustomerId() {
return customerId;
}
@@ -93,6 +95,14 @@ public class AppointmentRequest {
this.customerPetIds = customerPetIds;
}
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -105,12 +115,13 @@ public class AppointmentRequest {
Objects.equals(appointmentTime, that.appointmentTime) &&
Objects.equals(appointmentStatus, that.appointmentStatus) &&
Objects.equals(petIds, that.petIds) &&
Objects.equals(customerPetIds, that.customerPetIds);
Objects.equals(customerPetIds, that.customerPetIds) &&
Objects.equals(employeeId, that.employeeId);
}
@Override
public int hashCode() {
return Objects.hash(customerId, storeId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds, customerPetIds);
return Objects.hash(customerId, storeId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds, customerPetIds, employeeId);
}
@Override
@@ -124,6 +135,7 @@ public class AppointmentRequest {
", appointmentStatus='" + appointmentStatus + '\'' +
", petIds=" + petIds +
", customerPetIds=" + customerPetIds +
", employeeId=" + employeeId +
'}';
}
}

View File

@@ -17,6 +17,8 @@ public class AppointmentResponse {
private LocalDate appointmentDate;
private LocalTime appointmentTime;
private String appointmentStatus;
private Long employeeId;
private String employeeName;
private List<String> petNames;
private List<Long> petIds;
private List<String> customerPetNames;
@@ -124,6 +126,22 @@ public class AppointmentResponse {
this.appointmentStatus = appointmentStatus;
}
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public List<String> getPetNames() {
return petNames;
}

View File

@@ -25,6 +25,10 @@ public class Adoption {
@JoinColumn(name = "customerId", nullable = false)
private Customer customer;
@ManyToOne
@JoinColumn(name = "employeeId", nullable = false)
private Employee employee;
@Column(nullable = false)
private LocalDate adoptionDate;
@@ -42,10 +46,11 @@ public class Adoption {
public Adoption() {
}
public Adoption(Long adoptionId, Pet pet, Customer customer, LocalDate adoptionDate, String adoptionStatus, LocalDateTime createdAt, LocalDateTime updatedAt) {
public Adoption(Long adoptionId, Pet pet, Customer customer, Employee employee, LocalDate adoptionDate, String adoptionStatus, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.adoptionId = adoptionId;
this.pet = pet;
this.customer = customer;
this.employee = employee;
this.adoptionDate = adoptionDate;
this.adoptionStatus = adoptionStatus;
this.createdAt = createdAt;
@@ -76,6 +81,14 @@ public class Adoption {
this.customer = customer;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public LocalDate getAdoptionDate() {
return adoptionDate;
}
@@ -127,6 +140,7 @@ public class Adoption {
"adoptionId=" + adoptionId +
", pet=" + pet +
", customer=" + customer +
", employee=" + employee +
", adoptionDate=" + adoptionDate +
", adoptionStatus='" + adoptionStatus + '\'' +
", createdAt=" + createdAt +

View File

@@ -31,6 +31,10 @@ public class Appointment {
@JoinColumn(name = "serviceId", nullable = false)
private Service service;
@ManyToOne
@JoinColumn(name = "employeeId", nullable = false)
private Employee employee;
@Column(nullable = false)
private LocalDate appointmentDate;
@@ -67,11 +71,12 @@ public class Appointment {
public Appointment() {
}
public Appointment(Long appointmentId, Customer customer, StoreLocation store, Service service, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, Set<Pet> pets, LocalDateTime createdAt, LocalDateTime updatedAt) {
public Appointment(Long appointmentId, Customer customer, StoreLocation store, Service service, Employee employee, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, Set<Pet> pets, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.appointmentId = appointmentId;
this.customer = customer;
this.store = store;
this.service = service;
this.employee = employee;
this.appointmentDate = appointmentDate;
this.appointmentTime = appointmentTime;
this.appointmentStatus = appointmentStatus;
@@ -112,6 +117,14 @@ public class Appointment {
this.service = service;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public LocalDate getAppointmentDate() {
return appointmentDate;
}
@@ -189,6 +202,7 @@ public class Appointment {
", customer=" + customer +
", store=" + store +
", service=" + service +
", employee=" + employee +
", appointmentDate=" + appointmentDate +
", appointmentTime=" + appointmentTime +
", appointmentStatus='" + appointmentStatus + '\'' +

View File

@@ -15,6 +15,8 @@ import java.util.Optional;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
Optional<Employee> findByUserId(Long userId);
List<Employee> findAllByEmail(String email);
Optional<Employee> findFirstByIsActiveTrueOrderByEmployeeIdAsc();
List<Employee> findAllByIsActiveTrueOrderByEmployeeIdAsc();
@Query("SELECT e FROM Employee e WHERE " +
"LOWER(e.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +

View File

@@ -2,11 +2,17 @@ package com.petshop.backend.repository;
import com.petshop.backend.entity.EmployeeStore;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface EmployeeStoreRepository extends JpaRepository<EmployeeStore, EmployeeStore.EmployeeStoreId> {
Optional<EmployeeStore> findByEmployeeEmployeeId(Long employeeId);
@Query("SELECT es FROM EmployeeStore es WHERE es.store.storeId = :storeId AND es.employee.isActive = true ORDER BY es.employee.employeeId ASC")
List<EmployeeStore> findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(@Param("storeId") Long storeId);
}

View File

@@ -5,11 +5,15 @@ import com.petshop.backend.dto.adoption.AdoptionResponse;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.entity.Adoption;
import com.petshop.backend.entity.Customer;
import com.petshop.backend.entity.Employee;
import com.petshop.backend.entity.Pet;
import com.petshop.backend.entity.User;
import com.petshop.backend.exception.ResourceNotFoundException;
import com.petshop.backend.repository.AdoptionRepository;
import com.petshop.backend.repository.CustomerRepository;
import com.petshop.backend.repository.EmployeeRepository;
import com.petshop.backend.repository.PetRepository;
import com.petshop.backend.repository.UserRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@@ -21,11 +25,15 @@ public class AdoptionService {
private final AdoptionRepository adoptionRepository;
private final PetRepository petRepository;
private final CustomerRepository customerRepository;
private final EmployeeRepository employeeRepository;
private final UserRepository userRepository;
public AdoptionService(AdoptionRepository adoptionRepository, PetRepository petRepository, CustomerRepository customerRepository) {
public AdoptionService(AdoptionRepository adoptionRepository, PetRepository petRepository, CustomerRepository customerRepository, EmployeeRepository employeeRepository, UserRepository userRepository) {
this.adoptionRepository = adoptionRepository;
this.petRepository = petRepository;
this.customerRepository = customerRepository;
this.employeeRepository = employeeRepository;
this.userRepository = userRepository;
}
public Page<AdoptionResponse> getAllAdoptions(String query, Pageable pageable, Long customerId) {
@@ -66,10 +74,12 @@ public class AdoptionService {
Customer customer = customerRepository.findById(request.getCustomerId())
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
Employee employee = resolveAdoptionEmployee(request.getEmployeeId());
Adoption adoption = new Adoption();
adoption.setPet(pet);
adoption.setCustomer(customer);
adoption.setEmployee(employee);
adoption.setAdoptionDate(request.getAdoptionDate());
adoption.setAdoptionStatus(request.getAdoptionStatus());
@@ -87,9 +97,11 @@ public class AdoptionService {
Customer customer = customerRepository.findById(request.getCustomerId())
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
Employee employee = resolveAdoptionEmployee(request.getEmployeeId());
adoption.setPet(pet);
adoption.setCustomer(customer);
adoption.setEmployee(employee);
adoption.setAdoptionDate(request.getAdoptionDate());
adoption.setAdoptionStatus(request.getAdoptionStatus());
@@ -117,6 +129,8 @@ public class AdoptionService {
adoption.getPet().getPetName(),
adoption.getCustomer().getCustomerId(),
adoption.getCustomer().getFirstName() + " " + adoption.getCustomer().getLastName(),
adoption.getEmployee().getEmployeeId(),
adoption.getEmployee().getFirstName() + " " + adoption.getEmployee().getLastName(),
adoption.getAdoptionDate(),
adoption.getAdoptionStatus(),
adoption.getPet().getPetPrice(),
@@ -124,4 +138,31 @@ public class AdoptionService {
adoption.getUpdatedAt()
);
}
private Employee resolveAdoptionEmployee(Long requestedEmployeeId) {
if (requestedEmployeeId != null) {
Employee employee = employeeRepository.findById(requestedEmployeeId)
.orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + requestedEmployeeId));
if (!isAssignableEmployee(employee)) {
throw new IllegalArgumentException("Selected employee is not assignable for adoption work");
}
return employee;
}
return employeeRepository.findAllByIsActiveTrueOrderByEmployeeIdAsc().stream()
.filter(this::isAssignableEmployee)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No assignable staff member is available for adoption assignment"));
}
private boolean isAssignableEmployee(Employee employee) {
Long userId = employee.getUserId();
if (userId == null || !Boolean.TRUE.equals(employee.getIsActive())) {
return false;
}
return userRepository.findById(userId)
.map(User::getRole)
.filter(role -> role == User.Role.STAFF)
.isPresent();
}
}

View File

@@ -121,6 +121,7 @@ public class AppointmentService {
Set<Pet> pets = hasPetIds ? fetchPets(request.getPetIds()) : new HashSet<>();
Set<CustomerPet> customerPets = hasCustomerPetIds ? fetchCustomerPets(request.getCustomerPetIds(), customer.getCustomerId()) : new HashSet<>();
Employee employee = resolveAppointmentEmployee(request.getEmployeeId(), store.getStoreId());
Appointment appointment = new Appointment();
appointment.setCustomer(customer);
@@ -131,6 +132,7 @@ public class AppointmentService {
appointment.setAppointmentStatus(request.getAppointmentStatus());
appointment.setPets(pets);
appointment.setCustomerPets(customerPets);
appointment.setEmployee(employee);
appointment = appointmentRepository.save(appointment);
return mapToResponse(appointment);
@@ -165,6 +167,7 @@ public class AppointmentService {
Set<Pet> pets = hasPetIds ? fetchPets(request.getPetIds()) : new HashSet<>();
Set<CustomerPet> customerPets = hasCustomerPetIds ? fetchCustomerPets(request.getCustomerPetIds(), customer.getCustomerId()) : new HashSet<>();
Employee employee = resolveAppointmentEmployee(request.getEmployeeId(), store.getStoreId());
appointment.setCustomer(customer);
appointment.setStore(store);
@@ -174,6 +177,7 @@ public class AppointmentService {
appointment.setAppointmentStatus(request.getAppointmentStatus());
appointment.setPets(pets);
appointment.setCustomerPets(customerPets);
appointment.setEmployee(employee);
appointment = appointmentRepository.save(appointment);
return mapToResponse(appointment);
@@ -289,6 +293,8 @@ public class AppointmentService {
response.setAppointmentDate(appointment.getAppointmentDate());
response.setAppointmentTime(appointment.getAppointmentTime());
response.setAppointmentStatus(appointment.getAppointmentStatus());
response.setEmployeeId(appointment.getEmployee().getEmployeeId());
response.setEmployeeName(appointment.getEmployee().getFirstName() + " " + appointment.getEmployee().getLastName());
response.setPetNames(petNames);
response.setPetIds(petIds);
response.setCustomerPetNames(customerPetNames);
@@ -299,6 +305,39 @@ public class AppointmentService {
return response;
}
private Employee resolveAppointmentEmployee(Long requestedEmployeeId, Long storeId) {
List<EmployeeStore> assignableEmployees = employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(storeId).stream()
.filter(es -> isAssignableEmployee(es.getEmployee()))
.collect(Collectors.toList());
if (requestedEmployeeId != null) {
Employee employee = employeeRepository.findById(requestedEmployeeId)
.orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + requestedEmployeeId));
boolean assignedToStore = assignableEmployees.stream()
.anyMatch(es -> es.getEmployee().getEmployeeId().equals(requestedEmployeeId));
if (!assignedToStore) {
throw new IllegalArgumentException("Selected employee is not assignable for the selected store");
}
return employee;
}
return assignableEmployees.stream()
.map(EmployeeStore::getEmployee)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No assignable staff member is assigned to the selected store"));
}
private boolean isAssignableEmployee(Employee employee) {
Long userId = employee.getUserId();
if (userId == null || !Boolean.TRUE.equals(employee.getIsActive())) {
return false;
}
return userRepository.findById(userId)
.map(User::getRole)
.filter(role -> role == User.Role.STAFF)
.isPresent();
}
//------------------------------------
private void validateAvailability(StoreLocation store, com.petshop.backend.entity.Service service, LocalDate date, LocalTime time, Long appointmentIdToIgnore) {
// Filter by same service only - different services can run at same time

View File

@@ -0,0 +1,61 @@
ALTER TABLE appointment
ADD COLUMN employeeId BIGINT NULL;
UPDATE appointment a
SET a.employeeId = (
SELECT es.employeeId
FROM employeeStore es
JOIN employee e ON e.employeeId = es.employeeId
JOIN users u ON u.id = e.user_id
WHERE es.storeId = a.storeId
AND e.isActive = TRUE
AND u.role = 'STAFF'
ORDER BY es.employeeId ASC
LIMIT 1
)
WHERE a.employeeId IS NULL;
UPDATE appointment a
SET a.employeeId = (
SELECT e.employeeId
FROM employee e
JOIN users u ON u.id = e.user_id
WHERE e.isActive = TRUE
AND u.role = 'STAFF'
ORDER BY e.employeeId ASC
LIMIT 1
)
WHERE a.employeeId IS NULL;
ALTER TABLE appointment
ADD CONSTRAINT fk_appointment_employee
FOREIGN KEY (employeeId) REFERENCES employee(employeeId);
CREATE INDEX idx_appointment_employeeId ON appointment(employeeId);
ALTER TABLE appointment
MODIFY employeeId BIGINT NOT NULL;
ALTER TABLE adoption
ADD COLUMN employeeId BIGINT NULL;
UPDATE adoption a
SET a.employeeId = (
SELECT e.employeeId
FROM employee e
JOIN users u ON u.id = e.user_id
WHERE e.isActive = TRUE
AND u.role = 'STAFF'
ORDER BY e.employeeId ASC
LIMIT 1
)
WHERE a.employeeId IS NULL;
ALTER TABLE adoption
ADD CONSTRAINT fk_adoption_employee
FOREIGN KEY (employeeId) REFERENCES employee(employeeId);
CREATE INDEX idx_adoption_employeeId ON adoption(employeeId);
ALTER TABLE adoption
MODIFY employeeId BIGINT NOT NULL;

View File

@@ -0,0 +1,90 @@
package com.petshop.backend.controller;
import com.petshop.backend.entity.Employee;
import com.petshop.backend.entity.EmployeeStore;
import com.petshop.backend.entity.StoreLocation;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.CategoryRepository;
import com.petshop.backend.repository.CustomerPetRepository;
import com.petshop.backend.repository.CustomerRepository;
import com.petshop.backend.repository.EmployeeStoreRepository;
import com.petshop.backend.repository.PetRepository;
import com.petshop.backend.repository.ProductRepository;
import com.petshop.backend.repository.ServiceRepository;
import com.petshop.backend.repository.StoreRepository;
import com.petshop.backend.repository.SupplierRepository;
import com.petshop.backend.repository.UserRepository;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class DropdownControllerTest {
@Test
void getStoreEmployeesReturnsOnlyStaffLinkedEmployees() {
PetRepository petRepository = mock(PetRepository.class);
CustomerRepository customerRepository = mock(CustomerRepository.class);
CustomerPetRepository customerPetRepository = mock(CustomerPetRepository.class);
ServiceRepository serviceRepository = mock(ServiceRepository.class);
ProductRepository productRepository = mock(ProductRepository.class);
CategoryRepository categoryRepository = mock(CategoryRepository.class);
StoreRepository storeRepository = mock(StoreRepository.class);
SupplierRepository supplierRepository = mock(SupplierRepository.class);
EmployeeStoreRepository employeeStoreRepository = mock(EmployeeStoreRepository.class);
UserRepository userRepository = mock(UserRepository.class);
DropdownController controller = new DropdownController(
petRepository,
customerRepository,
customerPetRepository,
serviceRepository,
productRepository,
categoryRepository,
storeRepository,
supplierRepository,
employeeStoreRepository,
userRepository
);
StoreLocation store = new StoreLocation();
store.setStoreId(1L);
Employee staffEmployee = new Employee();
staffEmployee.setEmployeeId(7L);
staffEmployee.setUserId(7L);
staffEmployee.setFirstName("Alex");
staffEmployee.setLastName("Jones");
staffEmployee.setIsActive(true);
Employee adminEmployee = new Employee();
adminEmployee.setEmployeeId(8L);
adminEmployee.setUserId(8L);
adminEmployee.setFirstName("Admin");
adminEmployee.setLastName("Helper");
adminEmployee.setIsActive(true);
User staffUser = new User();
staffUser.setId(7L);
staffUser.setRole(User.Role.STAFF);
User adminUser = new User();
adminUser.setId(8L);
adminUser.setRole(User.Role.ADMIN);
when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L))
.thenReturn(List.of(new EmployeeStore(staffEmployee, store), new EmployeeStore(adminEmployee, store)));
when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser));
when(userRepository.findById(8L)).thenReturn(Optional.of(adminUser));
var response = controller.getStoreEmployees(1L);
assertEquals(1, response.getBody().size());
assertEquals(Long.valueOf(7L), response.getBody().get(0).getId());
assertEquals("Alex Jones", response.getBody().get(0).getLabel());
}
}

View File

@@ -0,0 +1,124 @@
package com.petshop.backend.service;
import com.petshop.backend.dto.adoption.AdoptionRequest;
import com.petshop.backend.entity.Adoption;
import com.petshop.backend.entity.Customer;
import com.petshop.backend.entity.Employee;
import com.petshop.backend.entity.Pet;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.AdoptionRepository;
import com.petshop.backend.repository.CustomerRepository;
import com.petshop.backend.repository.EmployeeRepository;
import com.petshop.backend.repository.PetRepository;
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.MockitoSettings;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.quality.Strictness;
import java.time.LocalDate;
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.when;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class AdoptionServiceTest {
@Mock private AdoptionRepository adoptionRepository;
@Mock private PetRepository petRepository;
@Mock private CustomerRepository customerRepository;
@Mock private EmployeeRepository employeeRepository;
@Mock private UserRepository userRepository;
@InjectMocks
private AdoptionService adoptionService;
private Pet pet;
private Customer customer;
private Employee staffEmployee;
private Employee adminEmployee;
@BeforeEach
void setUp() {
pet = new Pet();
pet.setPetId(1L);
pet.setPetName("Buddy");
customer = new Customer();
customer.setCustomerId(1L);
customer.setFirstName("Pat");
customer.setLastName("Owner");
staffEmployee = new Employee();
staffEmployee.setEmployeeId(7L);
staffEmployee.setUserId(7L);
staffEmployee.setFirstName("Alex");
staffEmployee.setLastName("Jones");
staffEmployee.setIsActive(true);
adminEmployee = new Employee();
adminEmployee.setEmployeeId(8L);
adminEmployee.setUserId(8L);
adminEmployee.setFirstName("Admin");
adminEmployee.setLastName("Helper");
adminEmployee.setIsActive(true);
User staffUser = new User();
staffUser.setId(7L);
staffUser.setRole(User.Role.STAFF);
when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser));
User adminUser = new User();
adminUser.setId(8L);
adminUser.setRole(User.Role.ADMIN);
when(userRepository.findById(8L)).thenReturn(Optional.of(adminUser));
}
@Test
void createAdoptionAutoAssignsFirstStaffEmployee() {
when(petRepository.findById(1L)).thenReturn(Optional.of(pet));
when(customerRepository.findById(1L)).thenReturn(Optional.of(customer));
when(employeeRepository.findAllByIsActiveTrueOrderByEmployeeIdAsc()).thenReturn(List.of(adminEmployee, staffEmployee));
when(adoptionRepository.save(any(Adoption.class))).thenAnswer(invocation -> {
Adoption adoption = invocation.getArgument(0);
adoption.setAdoptionId(10L);
return adoption;
});
AdoptionRequest request = new AdoptionRequest();
request.setPetId(1L);
request.setCustomerId(1L);
request.setAdoptionDate(LocalDate.now());
request.setAdoptionStatus("Pending");
var response = adoptionService.createAdoption(request);
assertEquals(7L, response.getEmployeeId());
assertEquals("Alex Jones", response.getEmployeeName());
}
@Test
void createAdoptionRejectsAdminEmployeeSelection() {
when(petRepository.findById(1L)).thenReturn(Optional.of(pet));
when(customerRepository.findById(1L)).thenReturn(Optional.of(customer));
when(employeeRepository.findById(8L)).thenReturn(Optional.of(adminEmployee));
AdoptionRequest request = new AdoptionRequest();
request.setPetId(1L);
request.setCustomerId(1L);
request.setEmployeeId(8L);
request.setAdoptionDate(LocalDate.now());
request.setAdoptionStatus("Pending");
assertThrows(IllegalArgumentException.class, () -> adoptionService.createAdoption(request));
}
}

View File

@@ -3,6 +3,8 @@ package com.petshop.backend.service;
import com.petshop.backend.entity.Appointment;
import com.petshop.backend.entity.Customer;
import com.petshop.backend.entity.CustomerPet;
import com.petshop.backend.entity.Employee;
import com.petshop.backend.entity.EmployeeStore;
import com.petshop.backend.entity.Pet;
import com.petshop.backend.entity.Service;
import com.petshop.backend.entity.StoreLocation;
@@ -22,7 +24,9 @@ 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.MockitoSettings;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.quality.Strictness;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -41,6 +45,7 @@ import static org.mockito.Mockito.any;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class AppointmentServiceTest {
@Mock
@@ -79,6 +84,7 @@ class AppointmentServiceTest {
private Service nailTrim;
private Pet pet;
private CustomerPet customerPet;
private Employee employee;
private LocalDate date;
@BeforeEach
@@ -111,6 +117,18 @@ class AppointmentServiceTest {
customerPet.setPetName("Milo Jr");
customerPet.setCustomer(customer);
employee = new Employee();
employee.setEmployeeId(7L);
employee.setUserId(7L);
employee.setFirstName("Alex");
employee.setLastName("Jones");
employee.setIsActive(true);
User staffUser = new User();
staffUser.setId(7L);
staffUser.setRole(User.Role.STAFF);
when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser));
date = LocalDate.now().plusDays(1);
}
@@ -171,6 +189,8 @@ class AppointmentServiceTest {
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(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L))
.thenReturn(List.of(new EmployeeStore(employee, store)));
when(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of(existing));
when(appointmentRepository.save(any(Appointment.class))).thenAnswer(invocation -> invocation.getArgument(0));
@@ -210,6 +230,8 @@ class AppointmentServiceTest {
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(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L))
.thenReturn(List.of(new EmployeeStore(employee, store)));
when(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of());
when(customerPetRepository.findById(22L)).thenReturn(Optional.of(otherCustomerPet));
@@ -238,6 +260,8 @@ class AppointmentServiceTest {
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(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L))
.thenReturn(List.of(new EmployeeStore(employee, store)));
when(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of());
when(customerPetRepository.findById(11L)).thenReturn(Optional.of(customerPet));
when(appointmentRepository.save(any(Appointment.class))).thenAnswer(invocation -> {
@@ -259,6 +283,51 @@ class AppointmentServiceTest {
assertEquals(99L, response.getAppointmentId());
assertEquals(1L, response.getCustomerId());
assertEquals(7L, response.getEmployeeId());
}
@Test
void createAppointmentRejectsAdminEmployeeSelection() {
User adminUser = new User();
adminUser.setId(99L);
adminUser.setUsername("admin");
adminUser.setRole(User.Role.ADMIN);
adminUser.setTokenVersion(0);
when(userRepository.findById(99L)).thenReturn(Optional.of(adminUser));
setAuthentication(99L, User.Role.ADMIN);
Employee adminEmployee = new Employee();
adminEmployee.setEmployeeId(8L);
adminEmployee.setUserId(8L);
adminEmployee.setFirstName("Admin");
adminEmployee.setLastName("Helper");
adminEmployee.setIsActive(true);
User adminLinkedUser = new User();
adminLinkedUser.setId(8L);
adminLinkedUser.setRole(User.Role.ADMIN);
when(userRepository.findById(8L)).thenReturn(Optional.of(adminLinkedUser));
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(employeeRepository.findById(8L)).thenReturn(Optional.of(adminEmployee));
when(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of());
when(customerPetRepository.findById(11L)).thenReturn(Optional.of(customerPet));
when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L))
.thenReturn(List.of(new EmployeeStore(adminEmployee, store), new EmployeeStore(employee, store)));
var request = new com.petshop.backend.dto.appointment.AppointmentRequest();
request.setCustomerId(1L);
request.setStoreId(1L);
request.setServiceId(1L);
request.setEmployeeId(8L);
request.setAppointmentDate(date);
request.setAppointmentTime(LocalTime.of(10, 0));
request.setAppointmentStatus("Booked");
request.setCustomerPetIds(List.of(11L));
assertThrows(IllegalArgumentException.class, () -> appointmentService.createAppointment(request));
}
private Appointment appointment(Long id, LocalDate date, LocalTime time, Service service, StoreLocation storeLocation) {
@@ -269,6 +338,7 @@ class AppointmentServiceTest {
appointment.setAppointmentStatus("Booked");
appointment.setService(service);
appointment.setStore(storeLocation);
appointment.setEmployee(employee);
appointment.setCustomer(customer);
appointment.setPets(Set.of());
return appointment;