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

@@ -8,6 +8,8 @@ public class AdoptionDTO {
private String petName; private String petName;
private Long customerId; private Long customerId;
private String customerName; private String customerName;
private Long employeeId;
private String employeeName;
private String adoptionDate; private String adoptionDate;
private String adoptionStatus; private String adoptionStatus;
private BigDecimal adoptionFee; private BigDecimal adoptionFee;
@@ -16,8 +18,13 @@ public class AdoptionDTO {
// Constructor for create/update requests // Constructor for create/update requests
public AdoptionDTO(Long petId, Long customerId, String adoptionDate, String adoptionStatus) { public AdoptionDTO(Long petId, Long customerId, String adoptionDate, String adoptionStatus) {
this(petId, customerId, null, adoptionDate, adoptionStatus);
}
public AdoptionDTO(Long petId, Long customerId, Long employeeId, String adoptionDate, String adoptionStatus) {
this.petId = petId; this.petId = petId;
this.customerId = customerId; this.customerId = customerId;
this.employeeId = employeeId;
this.adoptionDate = adoptionDate; this.adoptionDate = adoptionDate;
this.adoptionStatus = adoptionStatus; this.adoptionStatus = adoptionStatus;
} }
@@ -42,6 +49,14 @@ public class AdoptionDTO {
return customerName; return customerName;
} }
public Long getEmployeeId() {
return employeeId;
}
public String getEmployeeName() {
return employeeName;
}
public String getAdoptionDate() { public String getAdoptionDate() {
return adoptionDate; return adoptionDate;
} }

View File

@@ -12,6 +12,8 @@ public class AppointmentDTO {
private String storeName; private String storeName;
private Long serviceId; private Long serviceId;
private String serviceName; private String serviceName;
private Long employeeId;
private String employeeName;
private String appointmentDate; private String appointmentDate;
private String appointmentTime; private String appointmentTime;
private String appointmentStatus; private String appointmentStatus;
@@ -25,9 +27,16 @@ public class AppointmentDTO {
public AppointmentDTO(Long customerId, Long storeId, Long serviceId, public AppointmentDTO(Long customerId, Long storeId, Long serviceId,
String appointmentDate, String appointmentTime, String appointmentDate, String appointmentTime,
String appointmentStatus, List<Long> petIds) { String appointmentStatus, List<Long> petIds) {
this(customerId, storeId, serviceId, null, appointmentDate, appointmentTime, appointmentStatus, petIds);
}
public AppointmentDTO(Long customerId, Long storeId, Long serviceId, Long employeeId,
String appointmentDate, String appointmentTime,
String appointmentStatus, List<Long> petIds) {
this.customerId = customerId; this.customerId = customerId;
this.storeId = storeId; this.storeId = storeId;
this.serviceId = serviceId; this.serviceId = serviceId;
this.employeeId = employeeId;
this.appointmentDate = appointmentDate; this.appointmentDate = appointmentDate;
this.appointmentTime = appointmentTime; this.appointmentTime = appointmentTime;
this.appointmentStatus = appointmentStatus; this.appointmentStatus = appointmentStatus;
@@ -63,6 +72,14 @@ public class AppointmentDTO {
return serviceName; return serviceName;
} }
public Long getEmployeeId() {
return employeeId;
}
public String getEmployeeName() {
return employeeName;
}
public String getAppointmentDate() { public String getAppointmentDate() {
return appointmentDate; return appointmentDate;
} }

View File

@@ -2,6 +2,8 @@ package com.petshop.backend.controller;
import com.petshop.backend.dto.common.DropdownOption; import com.petshop.backend.dto.common.DropdownOption;
import com.petshop.backend.entity.CustomerPet; import com.petshop.backend.entity.CustomerPet;
import com.petshop.backend.entity.EmployeeStore;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.*; import com.petshop.backend.repository.*;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@@ -25,12 +27,15 @@ public class DropdownController {
private final CategoryRepository categoryRepository; private final CategoryRepository categoryRepository;
private final StoreRepository storeRepository; private final StoreRepository storeRepository;
private final SupplierRepository supplierRepository; private final SupplierRepository supplierRepository;
private final EmployeeStoreRepository employeeStoreRepository;
private final UserRepository userRepository;
public DropdownController(PetRepository petRepository, CustomerRepository customerRepository, public DropdownController(PetRepository petRepository, CustomerRepository customerRepository,
CustomerPetRepository customerPetRepository, CustomerPetRepository customerPetRepository,
ServiceRepository serviceRepository, ProductRepository productRepository, ServiceRepository serviceRepository, ProductRepository productRepository,
CategoryRepository categoryRepository, StoreRepository storeRepository, CategoryRepository categoryRepository, StoreRepository storeRepository,
SupplierRepository supplierRepository) { SupplierRepository supplierRepository, EmployeeStoreRepository employeeStoreRepository,
UserRepository userRepository) {
this.petRepository = petRepository; this.petRepository = petRepository;
this.customerRepository = customerRepository; this.customerRepository = customerRepository;
this.customerPetRepository = customerPetRepository; this.customerPetRepository = customerPetRepository;
@@ -39,6 +44,8 @@ public class DropdownController {
this.categoryRepository = categoryRepository; this.categoryRepository = categoryRepository;
this.storeRepository = storeRepository; this.storeRepository = storeRepository;
this.supplierRepository = supplierRepository; this.supplierRepository = supplierRepository;
this.employeeStoreRepository = employeeStoreRepository;
this.userRepository = userRepository;
} }
@GetMapping("/pets") @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") @GetMapping("/suppliers")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<List<DropdownOption>> getSuppliers() { public ResponseEntity<List<DropdownOption>> getSuppliers() {
@@ -144,4 +162,20 @@ public class DropdownController {
String breed = pet.getBreed() == null || pet.getBreed().isBlank() ? "" : " · " + pet.getBreed(); String breed = pet.getBreed() == null || pet.getBreed().isBlank() ? "" : " · " + pet.getBreed();
return new DropdownOption(pet.getCustomerPetId(), pet.getPetName() + " (" + species + breed + ")"); 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") @NotBlank(message = "Adoption status is required")
private String adoptionStatus; private String adoptionStatus;
private Long employeeId;
public Long getPetId() { public Long getPetId() {
return petId; return petId;
} }
@@ -50,6 +52,14 @@ public class AdoptionRequest {
this.adoptionStatus = adoptionStatus; this.adoptionStatus = adoptionStatus;
} }
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@@ -58,12 +68,13 @@ public class AdoptionRequest {
return Objects.equals(petId, that.petId) && return Objects.equals(petId, that.petId) &&
Objects.equals(customerId, that.customerId) && Objects.equals(customerId, that.customerId) &&
Objects.equals(adoptionDate, that.adoptionDate) && Objects.equals(adoptionDate, that.adoptionDate) &&
Objects.equals(adoptionStatus, that.adoptionStatus); Objects.equals(adoptionStatus, that.adoptionStatus) &&
Objects.equals(employeeId, that.employeeId);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(petId, customerId, adoptionDate, adoptionStatus); return Objects.hash(petId, customerId, adoptionDate, adoptionStatus, employeeId);
} }
@Override @Override
@@ -73,6 +84,7 @@ public class AdoptionRequest {
", customerId=" + customerId + ", customerId=" + customerId +
", adoptionDate=" + adoptionDate + ", adoptionDate=" + adoptionDate +
", adoptionStatus='" + adoptionStatus + '\'' + ", adoptionStatus='" + adoptionStatus + '\'' +
", employeeId=" + employeeId +
'}'; '}';
} }
} }

View File

@@ -11,6 +11,8 @@ public class AdoptionResponse {
private String petName; private String petName;
private Long customerId; private Long customerId;
private String customerName; private String customerName;
private Long employeeId;
private String employeeName;
private LocalDate adoptionDate; private LocalDate adoptionDate;
private String adoptionStatus; private String adoptionStatus;
private BigDecimal adoptionFee; private BigDecimal adoptionFee;
@@ -20,12 +22,14 @@ public class AdoptionResponse {
public 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.adoptionId = adoptionId;
this.petId = petId; this.petId = petId;
this.petName = petName; this.petName = petName;
this.customerId = customerId; this.customerId = customerId;
this.customerName = customerName; this.customerName = customerName;
this.employeeId = employeeId;
this.employeeName = employeeName;
this.adoptionDate = adoptionDate; this.adoptionDate = adoptionDate;
this.adoptionStatus = adoptionStatus; this.adoptionStatus = adoptionStatus;
this.adoptionFee = adoptionFee; this.adoptionFee = adoptionFee;
@@ -73,6 +77,22 @@ public class AdoptionResponse {
this.customerName = customerName; 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() { public LocalDate getAdoptionDate() {
return adoptionDate; return adoptionDate;
} }

View File

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

View File

@@ -17,6 +17,8 @@ public class AppointmentResponse {
private LocalDate appointmentDate; private LocalDate appointmentDate;
private LocalTime appointmentTime; private LocalTime appointmentTime;
private String appointmentStatus; private String appointmentStatus;
private Long employeeId;
private String employeeName;
private List<String> petNames; private List<String> petNames;
private List<Long> petIds; private List<Long> petIds;
private List<String> customerPetNames; private List<String> customerPetNames;
@@ -124,6 +126,22 @@ public class AppointmentResponse {
this.appointmentStatus = appointmentStatus; 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() { public List<String> getPetNames() {
return petNames; return petNames;
} }

View File

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

View File

@@ -31,6 +31,10 @@ public class Appointment {
@JoinColumn(name = "serviceId", nullable = false) @JoinColumn(name = "serviceId", nullable = false)
private Service service; private Service service;
@ManyToOne
@JoinColumn(name = "employeeId", nullable = false)
private Employee employee;
@Column(nullable = false) @Column(nullable = false)
private LocalDate appointmentDate; private LocalDate appointmentDate;
@@ -67,11 +71,12 @@ public class Appointment {
public 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.appointmentId = appointmentId;
this.customer = customer; this.customer = customer;
this.store = store; this.store = store;
this.service = service; this.service = service;
this.employee = employee;
this.appointmentDate = appointmentDate; this.appointmentDate = appointmentDate;
this.appointmentTime = appointmentTime; this.appointmentTime = appointmentTime;
this.appointmentStatus = appointmentStatus; this.appointmentStatus = appointmentStatus;
@@ -112,6 +117,14 @@ public class Appointment {
this.service = service; this.service = service;
} }
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public LocalDate getAppointmentDate() { public LocalDate getAppointmentDate() {
return appointmentDate; return appointmentDate;
} }
@@ -189,6 +202,7 @@ public class Appointment {
", customer=" + customer + ", customer=" + customer +
", store=" + store + ", store=" + store +
", service=" + service + ", service=" + service +
", employee=" + employee +
", appointmentDate=" + appointmentDate + ", appointmentDate=" + appointmentDate +
", appointmentTime=" + appointmentTime + ", appointmentTime=" + appointmentTime +
", appointmentStatus='" + appointmentStatus + '\'' + ", appointmentStatus='" + appointmentStatus + '\'' +

View File

@@ -15,6 +15,8 @@ import java.util.Optional;
public interface EmployeeRepository extends JpaRepository<Employee, Long> { public interface EmployeeRepository extends JpaRepository<Employee, Long> {
Optional<Employee> findByUserId(Long userId); Optional<Employee> findByUserId(Long userId);
List<Employee> findAllByEmail(String email); List<Employee> findAllByEmail(String email);
Optional<Employee> findFirstByIsActiveTrueOrderByEmployeeIdAsc();
List<Employee> findAllByIsActiveTrueOrderByEmployeeIdAsc();
@Query("SELECT e FROM Employee e WHERE " + @Query("SELECT e FROM Employee e WHERE " +
"LOWER(e.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "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 com.petshop.backend.entity.EmployeeStore;
import org.springframework.data.jpa.repository.JpaRepository; 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 org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional; import java.util.Optional;
@Repository @Repository
public interface EmployeeStoreRepository extends JpaRepository<EmployeeStore, EmployeeStore.EmployeeStoreId> { public interface EmployeeStoreRepository extends JpaRepository<EmployeeStore, EmployeeStore.EmployeeStoreId> {
Optional<EmployeeStore> findByEmployeeEmployeeId(Long employeeId); 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.dto.common.BulkDeleteRequest;
import com.petshop.backend.entity.Adoption; import com.petshop.backend.entity.Adoption;
import com.petshop.backend.entity.Customer; import com.petshop.backend.entity.Customer;
import com.petshop.backend.entity.Employee;
import com.petshop.backend.entity.Pet; import com.petshop.backend.entity.Pet;
import com.petshop.backend.entity.User;
import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.exception.ResourceNotFoundException;
import com.petshop.backend.repository.AdoptionRepository; import com.petshop.backend.repository.AdoptionRepository;
import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.CustomerRepository;
import com.petshop.backend.repository.EmployeeRepository;
import com.petshop.backend.repository.PetRepository; import com.petshop.backend.repository.PetRepository;
import com.petshop.backend.repository.UserRepository;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -21,11 +25,15 @@ public class AdoptionService {
private final AdoptionRepository adoptionRepository; private final AdoptionRepository adoptionRepository;
private final PetRepository petRepository; private final PetRepository petRepository;
private final CustomerRepository customerRepository; 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.adoptionRepository = adoptionRepository;
this.petRepository = petRepository; this.petRepository = petRepository;
this.customerRepository = customerRepository; this.customerRepository = customerRepository;
this.employeeRepository = employeeRepository;
this.userRepository = userRepository;
} }
public Page<AdoptionResponse> getAllAdoptions(String query, Pageable pageable, Long customerId) { public Page<AdoptionResponse> getAllAdoptions(String query, Pageable pageable, Long customerId) {
@@ -66,10 +74,12 @@ public class AdoptionService {
Customer customer = customerRepository.findById(request.getCustomerId()) Customer customer = customerRepository.findById(request.getCustomerId())
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
Employee employee = resolveAdoptionEmployee(request.getEmployeeId());
Adoption adoption = new Adoption(); Adoption adoption = new Adoption();
adoption.setPet(pet); adoption.setPet(pet);
adoption.setCustomer(customer); adoption.setCustomer(customer);
adoption.setEmployee(employee);
adoption.setAdoptionDate(request.getAdoptionDate()); adoption.setAdoptionDate(request.getAdoptionDate());
adoption.setAdoptionStatus(request.getAdoptionStatus()); adoption.setAdoptionStatus(request.getAdoptionStatus());
@@ -87,9 +97,11 @@ public class AdoptionService {
Customer customer = customerRepository.findById(request.getCustomerId()) Customer customer = customerRepository.findById(request.getCustomerId())
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
Employee employee = resolveAdoptionEmployee(request.getEmployeeId());
adoption.setPet(pet); adoption.setPet(pet);
adoption.setCustomer(customer); adoption.setCustomer(customer);
adoption.setEmployee(employee);
adoption.setAdoptionDate(request.getAdoptionDate()); adoption.setAdoptionDate(request.getAdoptionDate());
adoption.setAdoptionStatus(request.getAdoptionStatus()); adoption.setAdoptionStatus(request.getAdoptionStatus());
@@ -117,6 +129,8 @@ public class AdoptionService {
adoption.getPet().getPetName(), adoption.getPet().getPetName(),
adoption.getCustomer().getCustomerId(), adoption.getCustomer().getCustomerId(),
adoption.getCustomer().getFirstName() + " " + adoption.getCustomer().getLastName(), adoption.getCustomer().getFirstName() + " " + adoption.getCustomer().getLastName(),
adoption.getEmployee().getEmployeeId(),
adoption.getEmployee().getFirstName() + " " + adoption.getEmployee().getLastName(),
adoption.getAdoptionDate(), adoption.getAdoptionDate(),
adoption.getAdoptionStatus(), adoption.getAdoptionStatus(),
adoption.getPet().getPetPrice(), adoption.getPet().getPetPrice(),
@@ -124,4 +138,31 @@ public class AdoptionService {
adoption.getUpdatedAt() 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<Pet> pets = hasPetIds ? fetchPets(request.getPetIds()) : new HashSet<>();
Set<CustomerPet> customerPets = hasCustomerPetIds ? fetchCustomerPets(request.getCustomerPetIds(), customer.getCustomerId()) : 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 appointment = new Appointment();
appointment.setCustomer(customer); appointment.setCustomer(customer);
@@ -131,6 +132,7 @@ public class AppointmentService {
appointment.setAppointmentStatus(request.getAppointmentStatus()); appointment.setAppointmentStatus(request.getAppointmentStatus());
appointment.setPets(pets); appointment.setPets(pets);
appointment.setCustomerPets(customerPets); appointment.setCustomerPets(customerPets);
appointment.setEmployee(employee);
appointment = appointmentRepository.save(appointment); appointment = appointmentRepository.save(appointment);
return mapToResponse(appointment); return mapToResponse(appointment);
@@ -165,6 +167,7 @@ public class AppointmentService {
Set<Pet> pets = hasPetIds ? fetchPets(request.getPetIds()) : new HashSet<>(); Set<Pet> pets = hasPetIds ? fetchPets(request.getPetIds()) : new HashSet<>();
Set<CustomerPet> customerPets = hasCustomerPetIds ? fetchCustomerPets(request.getCustomerPetIds(), customer.getCustomerId()) : new HashSet<>(); Set<CustomerPet> customerPets = hasCustomerPetIds ? fetchCustomerPets(request.getCustomerPetIds(), customer.getCustomerId()) : new HashSet<>();
Employee employee = resolveAppointmentEmployee(request.getEmployeeId(), store.getStoreId());
appointment.setCustomer(customer); appointment.setCustomer(customer);
appointment.setStore(store); appointment.setStore(store);
@@ -174,6 +177,7 @@ public class AppointmentService {
appointment.setAppointmentStatus(request.getAppointmentStatus()); appointment.setAppointmentStatus(request.getAppointmentStatus());
appointment.setPets(pets); appointment.setPets(pets);
appointment.setCustomerPets(customerPets); appointment.setCustomerPets(customerPets);
appointment.setEmployee(employee);
appointment = appointmentRepository.save(appointment); appointment = appointmentRepository.save(appointment);
return mapToResponse(appointment); return mapToResponse(appointment);
@@ -289,6 +293,8 @@ public class AppointmentService {
response.setAppointmentDate(appointment.getAppointmentDate()); response.setAppointmentDate(appointment.getAppointmentDate());
response.setAppointmentTime(appointment.getAppointmentTime()); response.setAppointmentTime(appointment.getAppointmentTime());
response.setAppointmentStatus(appointment.getAppointmentStatus()); response.setAppointmentStatus(appointment.getAppointmentStatus());
response.setEmployeeId(appointment.getEmployee().getEmployeeId());
response.setEmployeeName(appointment.getEmployee().getFirstName() + " " + appointment.getEmployee().getLastName());
response.setPetNames(petNames); response.setPetNames(petNames);
response.setPetIds(petIds); response.setPetIds(petIds);
response.setCustomerPetNames(customerPetNames); response.setCustomerPetNames(customerPetNames);
@@ -299,6 +305,39 @@ public class AppointmentService {
return response; 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) { 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 // 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.Appointment;
import com.petshop.backend.entity.Customer; import com.petshop.backend.entity.Customer;
import com.petshop.backend.entity.CustomerPet; 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.Pet;
import com.petshop.backend.entity.Service; import com.petshop.backend.entity.Service;
import com.petshop.backend.entity.StoreLocation; 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.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.quality.Strictness;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@@ -41,6 +45,7 @@ import static org.mockito.Mockito.any;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class AppointmentServiceTest { class AppointmentServiceTest {
@Mock @Mock
@@ -79,6 +84,7 @@ class AppointmentServiceTest {
private Service nailTrim; private Service nailTrim;
private Pet pet; private Pet pet;
private CustomerPet customerPet; private CustomerPet customerPet;
private Employee employee;
private LocalDate date; private LocalDate date;
@BeforeEach @BeforeEach
@@ -111,6 +117,18 @@ class AppointmentServiceTest {
customerPet.setPetName("Milo Jr"); customerPet.setPetName("Milo Jr");
customerPet.setCustomer(customer); 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); date = LocalDate.now().plusDays(1);
} }
@@ -171,6 +189,8 @@ class AppointmentServiceTest {
when(storeRepository.findById(1L)).thenReturn(Optional.of(store)); when(storeRepository.findById(1L)).thenReturn(Optional.of(store));
when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming)); when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming));
when(petRepository.findById(1L)).thenReturn(Optional.of(pet)); 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.findByStoreAndDate(1L, date)).thenReturn(List.of(existing));
when(appointmentRepository.save(any(Appointment.class))).thenAnswer(invocation -> invocation.getArgument(0)); 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(customerRepository.findById(1L)).thenReturn(Optional.of(customer));
when(storeRepository.findById(1L)).thenReturn(Optional.of(store)); when(storeRepository.findById(1L)).thenReturn(Optional.of(store));
when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming)); 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(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of());
when(customerPetRepository.findById(22L)).thenReturn(Optional.of(otherCustomerPet)); when(customerPetRepository.findById(22L)).thenReturn(Optional.of(otherCustomerPet));
@@ -238,6 +260,8 @@ class AppointmentServiceTest {
when(customerRepository.findById(1L)).thenReturn(Optional.of(customer)); when(customerRepository.findById(1L)).thenReturn(Optional.of(customer));
when(storeRepository.findById(1L)).thenReturn(Optional.of(store)); when(storeRepository.findById(1L)).thenReturn(Optional.of(store));
when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming)); 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(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of());
when(customerPetRepository.findById(11L)).thenReturn(Optional.of(customerPet)); when(customerPetRepository.findById(11L)).thenReturn(Optional.of(customerPet));
when(appointmentRepository.save(any(Appointment.class))).thenAnswer(invocation -> { when(appointmentRepository.save(any(Appointment.class))).thenAnswer(invocation -> {
@@ -259,6 +283,51 @@ class AppointmentServiceTest {
assertEquals(99L, response.getAppointmentId()); assertEquals(99L, response.getAppointmentId());
assertEquals(1L, response.getCustomerId()); 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) { private Appointment appointment(Long id, LocalDate date, LocalTime time, Service service, StoreLocation storeLocation) {
@@ -269,6 +338,7 @@ class AppointmentServiceTest {
appointment.setAppointmentStatus("Booked"); appointment.setAppointmentStatus("Booked");
appointment.setService(service); appointment.setService(service);
appointment.setStore(storeLocation); appointment.setStore(storeLocation);
appointment.setEmployee(employee);
appointment.setCustomer(customer); appointment.setCustomer(customer);
appointment.setPets(Set.of()); appointment.setPets(Set.of());
return appointment; return appointment;

View File

@@ -15,6 +15,8 @@ public class AppointmentDTO {
private SimpleIntegerProperty serviceId; private SimpleIntegerProperty serviceId;
private SimpleStringProperty serviceName; private SimpleStringProperty serviceName;
private SimpleIntegerProperty employeeId;
private SimpleStringProperty employeeName;
private SimpleStringProperty appointmentDate; private SimpleStringProperty appointmentDate;
private SimpleStringProperty appointmentTime; private SimpleStringProperty appointmentTime;
@@ -25,6 +27,8 @@ public class AppointmentDTO {
int customerId, String customerName, int customerId, String customerName,
int petId, String petName, int petId, String petName,
int serviceId, String serviceName, int serviceId, String serviceName,
int employeeId,
String employeeName,
String appointmentDate, String appointmentDate,
String appointmentTime, String appointmentTime,
String appointmentStatus) { String appointmentStatus) {
@@ -36,6 +40,8 @@ public class AppointmentDTO {
this.petName = new SimpleStringProperty(petName); this.petName = new SimpleStringProperty(petName);
this.serviceId = new SimpleIntegerProperty(serviceId); this.serviceId = new SimpleIntegerProperty(serviceId);
this.serviceName = new SimpleStringProperty(serviceName); this.serviceName = new SimpleStringProperty(serviceName);
this.employeeId = new SimpleIntegerProperty(employeeId);
this.employeeName = new SimpleStringProperty(employeeName);
this.appointmentDate = new SimpleStringProperty(appointmentDate); this.appointmentDate = new SimpleStringProperty(appointmentDate);
this.appointmentTime = new SimpleStringProperty(appointmentTime); this.appointmentTime = new SimpleStringProperty(appointmentTime);
this.appointmentStatus = new SimpleStringProperty(appointmentStatus); this.appointmentStatus = new SimpleStringProperty(appointmentStatus);
@@ -52,6 +58,8 @@ public class AppointmentDTO {
public int getServiceId() { return serviceId.get(); } public int getServiceId() { return serviceId.get(); }
public String getServiceName() { return serviceName.get(); } public String getServiceName() { return serviceName.get(); }
public int getEmployeeId() { return employeeId.get(); }
public String getEmployeeName() { return employeeName.get(); }
public String getAppointmentDate() { return appointmentDate.get(); } public String getAppointmentDate() { return appointmentDate.get(); }
public String getAppointmentTime() { return appointmentTime.get(); } public String getAppointmentTime() { return appointmentTime.get(); }

View File

@@ -5,6 +5,7 @@ import java.time.LocalDate;
public class AdoptionRequest { public class AdoptionRequest {
private Long petId; private Long petId;
private Long customerId; private Long customerId;
private Long employeeId;
private LocalDate adoptionDate; private LocalDate adoptionDate;
private String adoptionStatus; private String adoptionStatus;
@@ -27,6 +28,14 @@ public class AdoptionRequest {
this.customerId = customerId; this.customerId = customerId;
} }
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public LocalDate getAdoptionDate() { public LocalDate getAdoptionDate() {
return adoptionDate; return adoptionDate;
} }

View File

@@ -6,8 +6,10 @@ public class AdoptionResponse {
private Long adoptionId; private Long adoptionId;
private Long petId; private Long petId;
private Long customerId; private Long customerId;
private Long employeeId;
private String petName; private String petName;
private String customerName; private String customerName;
private String employeeName;
private LocalDate adoptionDate; private LocalDate adoptionDate;
private java.math.BigDecimal adoptionFee; private java.math.BigDecimal adoptionFee;
private String adoptionStatus; private String adoptionStatus;
@@ -39,6 +41,14 @@ public class AdoptionResponse {
this.customerId = customerId; this.customerId = customerId;
} }
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public String getPetName() { public String getPetName() {
return petName; return petName;
} }
@@ -55,6 +65,14 @@ public class AdoptionResponse {
this.customerName = customerName; this.customerName = customerName;
} }
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public LocalDate getAdoptionDate() { public LocalDate getAdoptionDate() {
return adoptionDate; return adoptionDate;
} }

View File

@@ -10,6 +10,7 @@ public class AppointmentRequest {
private Long customerId; private Long customerId;
private Long storeId; private Long storeId;
private Long serviceId; private Long serviceId;
private Long employeeId;
private LocalDate appointmentDate; private LocalDate appointmentDate;
private LocalTime appointmentTime; private LocalTime appointmentTime;
private String appointmentStatus; private String appointmentStatus;
@@ -57,6 +58,14 @@ public class AppointmentRequest {
this.serviceId = serviceId; this.serviceId = serviceId;
} }
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public LocalDate getAppointmentDate() { public LocalDate getAppointmentDate() {
return appointmentDate; return appointmentDate;
} }

View File

@@ -15,6 +15,8 @@ public class AppointmentResponse {
private java.util.List<String> customerPetNames; private java.util.List<String> customerPetNames;
private java.util.List<Long> customerPetIds; private java.util.List<Long> customerPetIds;
private String serviceName; private String serviceName;
private Long employeeId;
private String employeeName;
private LocalDate appointmentDate; private LocalDate appointmentDate;
private LocalTime appointmentTime; private LocalTime appointmentTime;
private String appointmentStatus; private String appointmentStatus;
@@ -110,6 +112,22 @@ public class AppointmentResponse {
this.serviceName = serviceName; this.serviceName = serviceName;
} }
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 getAppointmentDate() { public LocalDate getAppointmentDate() {
return appointmentDate; return appointmentDate;
} }

View File

@@ -97,4 +97,12 @@ public class DropdownApi {
} }
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {}); return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
} }
public List<DropdownOption> getStoreEmployees(Long storeId) throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/stores/" + storeId + "/employees");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from store employees endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
} }

View File

@@ -43,6 +43,9 @@ public class AdoptionController {
@FXML @FXML
private TableColumn<Adoption, String> colCustomerName; private TableColumn<Adoption, String> colCustomerName;
@FXML
private TableColumn<Adoption, String> colEmployeeName;
@FXML @FXML
private TableColumn<Adoption, String> colAdoptionDate; private TableColumn<Adoption, String> colAdoptionDate;
@@ -71,6 +74,7 @@ public class AdoptionController {
colAdoptionId.setCellValueFactory(new PropertyValueFactory<>("adoptionId")); colAdoptionId.setCellValueFactory(new PropertyValueFactory<>("adoptionId"));
colPetId.setCellValueFactory(new PropertyValueFactory<>("petName")); colPetId.setCellValueFactory(new PropertyValueFactory<>("petName"));
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName")); colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
colEmployeeName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
colAdoptionDate.setCellValueFactory(new PropertyValueFactory<>("adoptionDate")); colAdoptionDate.setCellValueFactory(new PropertyValueFactory<>("adoptionDate"));
colAdoptionFee.setCellValueFactory(new PropertyValueFactory<>("adoptionFee")); colAdoptionFee.setCellValueFactory(new PropertyValueFactory<>("adoptionFee"));
colAdoptionStatus.setCellValueFactory(new PropertyValueFactory<>("adoptionStatus")); colAdoptionStatus.setCellValueFactory(new PropertyValueFactory<>("adoptionStatus"));
@@ -252,8 +256,10 @@ public class AdoptionController {
response.getAdoptionId().intValue(), response.getAdoptionId().intValue(),
response.getPetId() != null ? response.getPetId().intValue() : 0, response.getPetId() != null ? response.getPetId().intValue() : 0,
response.getCustomerId() != null ? response.getCustomerId().intValue() : 0, response.getCustomerId() != null ? response.getCustomerId().intValue() : 0,
response.getEmployeeId() != null ? response.getEmployeeId().intValue() : 0,
response.getPetName(), response.getPetName(),
response.getCustomerName(), response.getCustomerName(),
response.getEmployeeName(),
response.getAdoptionDate() != null ? response.getAdoptionDate().toString() : "", response.getAdoptionDate() != null ? response.getAdoptionDate().toString() : "",
response.getAdoptionFee() != null ? response.getAdoptionFee().doubleValue() : 0.0, response.getAdoptionFee() != null ? response.getAdoptionFee().doubleValue() : 0.0,
response.getAdoptionStatus() response.getAdoptionStatus()

View File

@@ -33,6 +33,7 @@ public class AppointmentController {
@FXML private TableColumn<AppointmentDTO,String> colAppointmentDate; @FXML private TableColumn<AppointmentDTO,String> colAppointmentDate;
@FXML private TableColumn<AppointmentDTO,String> colAppointmentTime; @FXML private TableColumn<AppointmentDTO,String> colAppointmentTime;
@FXML private TableColumn<AppointmentDTO,String> colCustomerName; @FXML private TableColumn<AppointmentDTO,String> colCustomerName;
@FXML private TableColumn<AppointmentDTO,String> colEmployeeName;
@FXML private TableColumn<AppointmentDTO,String> colAppointmentStatus; @FXML private TableColumn<AppointmentDTO,String> colAppointmentStatus;
@FXML private Button btnAdd; @FXML private Button btnAdd;
@@ -55,6 +56,7 @@ public class AppointmentController {
colAppointmentDate.setCellValueFactory(new PropertyValueFactory<>("appointmentDate")); colAppointmentDate.setCellValueFactory(new PropertyValueFactory<>("appointmentDate"));
colAppointmentTime.setCellValueFactory(new PropertyValueFactory<>("appointmentTime")); colAppointmentTime.setCellValueFactory(new PropertyValueFactory<>("appointmentTime"));
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName")); colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
colEmployeeName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
colAppointmentStatus.setCellValueFactory(new PropertyValueFactory<>("appointmentStatus")); colAppointmentStatus.setCellValueFactory(new PropertyValueFactory<>("appointmentStatus"));
filtered = new FilteredList<>(appointments, a -> true); filtered = new FilteredList<>(appointments, a -> true);
@@ -247,6 +249,8 @@ public class AppointmentController {
petName, petName,
response.getServiceId() != null ? response.getServiceId().intValue() : 0, response.getServiceId() != null ? response.getServiceId().intValue() : 0,
response.getServiceName(), response.getServiceName(),
response.getEmployeeId() != null ? response.getEmployeeId().intValue() : 0,
response.getEmployeeName(),
response.getAppointmentDate().toString(), response.getAppointmentDate().toString(),
response.getAppointmentTime().toString(), response.getAppointmentTime().toString(),
response.getAppointmentStatus() response.getAppointmentStatus()

View File

@@ -11,6 +11,7 @@ import javafx.scene.control.Button;
import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBox;
import javafx.scene.control.DatePicker; import javafx.scene.control.DatePicker;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.adoption.AdoptionRequest; import org.example.petshopdesktop.api.dto.adoption.AdoptionRequest;
@@ -38,6 +39,9 @@ public class AdoptionDialogController {
@FXML @FXML
private ComboBox<DropdownOption> cbCustomer; private ComboBox<DropdownOption> cbCustomer;
@FXML
private ComboBox<DropdownOption> cbEmployee;
@FXML @FXML
private ComboBox<DropdownOption> cbPet; private ComboBox<DropdownOption> cbPet;
@@ -62,6 +66,7 @@ public class AdoptionDialogController {
void initialize() { void initialize() {
cbAdoptionStatus.setItems(statusList); cbAdoptionStatus.setItems(statusList);
cbEmployee.setPromptText("Select an employee");
new Thread(() -> { new Thread(() -> {
try { try {
@@ -83,6 +88,38 @@ public class AdoptionDialogController {
} }
}).start(); }).start();
new Thread(() -> {
try {
Long storeId = org.example.petshopdesktop.auth.UserSession.getInstance().getStoreId();
List<DropdownOption> employees = storeId != null && storeId > 0 ? DropdownApi.getInstance().getStoreEmployees(storeId) : List.of();
Platform.runLater(() -> cbEmployee.setItems(FXCollections.observableArrayList(employees)));
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.initialize",
e,
"Loading employees for combo box");
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Unable to load employees");
});
}
}).start();
cbEmployee.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbEmployee.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
new Thread(() -> { new Thread(() -> {
try { try {
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers(); List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
@@ -129,6 +166,10 @@ public class AdoptionDialogController {
errorMsg += "Customer is required.\n"; errorMsg += "Customer is required.\n";
} }
if (cbEmployee.getSelectionModel().getSelectedItem() == null) {
errorMsg += "Employee is required.\n";
}
if (dpAdoptionDate.getValue() == null) { if (dpAdoptionDate.getValue() == null) {
errorMsg += "Adoption Date is required.\n"; errorMsg += "Adoption Date is required.\n";
} }
@@ -142,6 +183,7 @@ public class AdoptionDialogController {
AdoptionRequest request = new AdoptionRequest(); AdoptionRequest request = new AdoptionRequest();
request.setPetId(cbPet.getSelectionModel().getSelectedItem().getId()); request.setPetId(cbPet.getSelectionModel().getSelectedItem().getId());
request.setCustomerId(cbCustomer.getSelectionModel().getSelectedItem().getId()); request.setCustomerId(cbCustomer.getSelectionModel().getSelectedItem().getId());
request.setEmployeeId(cbEmployee.getSelectionModel().getSelectedItem().getId());
request.setAdoptionDate(dpAdoptionDate.getValue()); request.setAdoptionDate(dpAdoptionDate.getValue());
request.setAdoptionStatus(cbAdoptionStatus.getValue()); request.setAdoptionStatus(cbAdoptionStatus.getValue());
@@ -204,6 +246,15 @@ public class AdoptionDialogController {
} }
} }
if (adoption.getEmployeeId() > 0) {
for (DropdownOption employee : cbEmployee.getItems()) {
if (employee.getId() != null && employee.getId().equals(adoption.getEmployeeId())) {
cbEmployee.getSelectionModel().select(employee);
break;
}
}
}
if (adoption.getAdoptionDate() != null && !adoption.getAdoptionDate().isEmpty()) { if (adoption.getAdoptionDate() != null && !adoption.getAdoptionDate().isEmpty()) {
try { try {
dpAdoptionDate.setValue(LocalDate.parse(adoption.getAdoptionDate())); dpAdoptionDate.setValue(LocalDate.parse(adoption.getAdoptionDate()));

View File

@@ -35,6 +35,7 @@ public class AppointmentDialogController {
@FXML private ComboBox<DropdownOption> cbService; @FXML private ComboBox<DropdownOption> cbService;
@FXML private ComboBox<DropdownOption> cbCustomer; @FXML private ComboBox<DropdownOption> cbCustomer;
@FXML private ComboBox<DropdownOption> cbPet; @FXML private ComboBox<DropdownOption> cbPet;
@FXML private ComboBox<DropdownOption> cbEmployee;
@FXML private ComboBox<Integer> cbHour; @FXML private ComboBox<Integer> cbHour;
@FXML private ComboBox<Integer> cbMinute; @FXML private ComboBox<Integer> cbMinute;
@@ -94,14 +95,32 @@ public class AppointmentDialogController {
ActivityLogger.getInstance().logException( ActivityLogger.getInstance().logException(
"AppointmentDialogController.initialize", "AppointmentDialogController.initialize",
e, e,
"Loading combo box data for services, customers, and pets"); "Loading services/customers for appointment dialog");
e.printStackTrace(); e.printStackTrace();
}); });
} }
}).start(); }).start();
new Thread(() -> {
try {
Long storeId = UserSession.getInstance().getStoreId();
List<DropdownOption> employees = storeId != null && storeId > 0 ? DropdownApi.getInstance().getStoreEmployees(storeId) : List.of();
Platform.runLater(() -> cbEmployee.setItems(FXCollections.observableArrayList(employees)));
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.initialize",
e,
"Loading employees for appointment dialog");
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Unable to load employees");
});
}
}).start();
cbAppointmentStatus.setItems(statusList); cbAppointmentStatus.setItems(statusList);
cbPet.setDisable(true); cbPet.setDisable(true);
cbEmployee.setPromptText("Select an employee");
cbPet.setPromptText("Select a customer first"); cbPet.setPromptText("Select a customer first");
cbCustomer.setPromptText("Select a customer"); cbCustomer.setPromptText("Select a customer");
cbService.setPromptText("Select a service"); cbService.setPromptText("Select a service");
@@ -161,6 +180,21 @@ public class AppointmentDialogController {
} }
}); });
cbEmployee.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbEmployee.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbCustomer.valueProperty().addListener((obs, oldValue, newValue) -> { cbCustomer.valueProperty().addListener((obs, oldValue, newValue) -> {
Long customerId = newValue != null ? newValue.getId() : null; Long customerId = newValue != null ? newValue.getId() : null;
cbPet.setValue(null); cbPet.setValue(null);
@@ -222,6 +256,14 @@ public class AppointmentDialogController {
cbCustomer.setValue(c); cbCustomer.setValue(c);
} }
}); });
if (appt.getEmployeeId() > 0) {
cbEmployee.getItems().forEach(employee -> {
if (employee.getId() != null && employee.getId().longValue() == appt.getEmployeeId()) {
cbEmployee.setValue(employee);
}
});
}
} }
// //
@@ -233,6 +275,7 @@ public class AppointmentDialogController {
if (cbService.getValue() == null || if (cbService.getValue() == null ||
cbCustomer.getValue() == null || cbCustomer.getValue() == null ||
cbPet.getValue() == null || cbPet.getValue() == null ||
cbEmployee.getValue() == null ||
dpAppointmentDate.getValue() == null || dpAppointmentDate.getValue() == null ||
cbHour.getValue() == null || cbHour.getValue() == null ||
cbMinute.getValue() == null || cbMinute.getValue() == null ||
@@ -254,6 +297,7 @@ public class AppointmentDialogController {
request.setCustomerId(cbCustomer.getValue().getId()); request.setCustomerId(cbCustomer.getValue().getId());
request.setStoreId(storeId); request.setStoreId(storeId);
request.setServiceId(cbService.getValue().getId()); request.setServiceId(cbService.getValue().getId());
request.setEmployeeId(cbEmployee.getValue().getId());
request.setAppointmentDate(dpAppointmentDate.getValue()); request.setAppointmentDate(dpAppointmentDate.getValue());
request.setAppointmentTime(appointmentTime); request.setAppointmentTime(appointmentTime);
request.setAppointmentStatus(cbAppointmentStatus.getValue()); request.setAppointmentStatus(cbAppointmentStatus.getValue());

View File

@@ -8,18 +8,22 @@ public class Adoption {
private SimpleIntegerProperty adoptionId; private SimpleIntegerProperty adoptionId;
private SimpleIntegerProperty petId; private SimpleIntegerProperty petId;
private SimpleIntegerProperty customerId; private SimpleIntegerProperty customerId;
private SimpleIntegerProperty employeeId;
private SimpleStringProperty petName; private SimpleStringProperty petName;
private SimpleStringProperty customerName; private SimpleStringProperty customerName;
private SimpleStringProperty employeeName;
private SimpleStringProperty adoptionDate; private SimpleStringProperty adoptionDate;
private SimpleDoubleProperty adoptionFee; private SimpleDoubleProperty adoptionFee;
private SimpleStringProperty adoptionStatus; private SimpleStringProperty adoptionStatus;
public Adoption(int adoptionId, int petId, int customerId, String petName, String customerName, String adoptionDate, double adoptionFee, String adoptionStatus) { public Adoption(int adoptionId, int petId, int customerId, int employeeId, String petName, String customerName, String employeeName, String adoptionDate, double adoptionFee, String adoptionStatus) {
this.adoptionId = new SimpleIntegerProperty(adoptionId); this.adoptionId = new SimpleIntegerProperty(adoptionId);
this.petId = new SimpleIntegerProperty(petId); this.petId = new SimpleIntegerProperty(petId);
this.customerId = new SimpleIntegerProperty(customerId); this.customerId = new SimpleIntegerProperty(customerId);
this.employeeId = new SimpleIntegerProperty(employeeId);
this.petName = new SimpleStringProperty(petName); this.petName = new SimpleStringProperty(petName);
this.customerName = new SimpleStringProperty(customerName); this.customerName = new SimpleStringProperty(customerName);
this.employeeName = new SimpleStringProperty(employeeName);
this.adoptionDate = new SimpleStringProperty(adoptionDate); this.adoptionDate = new SimpleStringProperty(adoptionDate);
this.adoptionFee = new SimpleDoubleProperty(adoptionFee); this.adoptionFee = new SimpleDoubleProperty(adoptionFee);
this.adoptionStatus = new SimpleStringProperty(adoptionStatus); this.adoptionStatus = new SimpleStringProperty(adoptionStatus);
@@ -43,6 +47,12 @@ public class Adoption {
public SimpleIntegerProperty customerIdProperty() { return customerId; } public SimpleIntegerProperty customerIdProperty() { return customerId; }
public int getEmployeeId() { return employeeId.get(); }
public void setEmployeeId(int employeeId) { this.employeeId.set(employeeId); }
public SimpleIntegerProperty employeeIdProperty() { return employeeId; }
public String getPetName() { return petName.get(); } public String getPetName() { return petName.get(); }
public void setPetName(String petName) { this.petName.set(petName); } public void setPetName(String petName) { this.petName.set(petName); }
@@ -55,6 +65,12 @@ public class Adoption {
public SimpleStringProperty customerNameProperty() { return customerName; } public SimpleStringProperty customerNameProperty() { return customerName; }
public String getEmployeeName() { return employeeName.get(); }
public void setEmployeeName(String employeeName) { this.employeeName.set(employeeName); }
public SimpleStringProperty employeeNameProperty() { return employeeName; }
public String getAdoptionDate() { return adoptionDate.get(); } public String getAdoptionDate() { return adoptionDate.get(); }
public void setAdoptionDate(String adoptionDate) { this.adoptionDate.set(adoptionDate); } public void setAdoptionDate(String adoptionDate) { this.adoptionDate.set(adoptionDate); }

View File

@@ -73,6 +73,7 @@
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints> </rowConstraints>
<children> <children>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0"> <VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0">
@@ -131,6 +132,20 @@
</ComboBox> </ComboBox>
</children> </children>
</VBox> </VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="2">
<children>
<Label text="Employee:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<ComboBox fx:id="cbEmployee" prefHeight="29.0" prefWidth="336.0" promptText="Select Employee" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
</children>
</VBox>
</children> </children>
<VBox.margin> <VBox.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" /> <Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />

View File

@@ -83,6 +83,7 @@
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints> </rowConstraints>
<children> <children>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0"> <VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0">
@@ -204,6 +205,20 @@
</ComboBox> </ComboBox>
</children> </children>
</VBox> </VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1" GridPane.rowIndex="3">
<children>
<Label text="Employee:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<ComboBox fx:id="cbEmployee" prefHeight="29.0" prefWidth="336.0" promptText="Select Employee" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
</children>
</VBox>
</children> </children>
<VBox.margin> <VBox.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" /> <Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />

View File

@@ -70,9 +70,10 @@
<TableColumn fx:id="colAdoptionId" prefWidth="60.0" text="ID" /> <TableColumn fx:id="colAdoptionId" prefWidth="60.0" text="ID" />
<TableColumn fx:id="colPetId" prefWidth="66.2857666015625" text="Pet ID" /> <TableColumn fx:id="colPetId" prefWidth="66.2857666015625" text="Pet ID" />
<TableColumn fx:id="colCustomerName" prefWidth="200.57147216796875" text="Customer Name" /> <TableColumn fx:id="colCustomerName" prefWidth="200.57147216796875" text="Customer Name" />
<TableColumn fx:id="colEmployeeName" prefWidth="160.0" text="Employee" />
<TableColumn fx:id="colAdoptionDate" prefWidth="190.85711669921875" text="Adoption Date" /> <TableColumn fx:id="colAdoptionDate" prefWidth="190.85711669921875" text="Adoption Date" />
<TableColumn fx:id="colAdoptionFee" prefWidth="91.4285888671875" text="Fee" /> <TableColumn fx:id="colAdoptionFee" prefWidth="91.4285888671875" text="Fee" />
<TableColumn fx:id="colAdoptionStatus" prefWidth="142.28570556640625" text="Status" /> <TableColumn fx:id="colAdoptionStatus" prefWidth="120.0" text="Status" />
</columns> </columns>
</TableView> </TableView>
</children> </children>

View File

@@ -70,9 +70,10 @@
<TableColumn fx:id="colAppointmentId" prefWidth="53.14288330078125" text="ID" /> <TableColumn fx:id="colAppointmentId" prefWidth="53.14288330078125" text="ID" />
<TableColumn fx:id="colPetName" prefWidth="108.00003051757812" text="Pet Name" /> <TableColumn fx:id="colPetName" prefWidth="108.00003051757812" text="Pet Name" />
<TableColumn fx:id="colServiceName" prefWidth="132.0" text="Service" /> <TableColumn fx:id="colServiceName" prefWidth="132.0" text="Service" />
<TableColumn fx:id="colEmployeeName" prefWidth="132.0" text="Employee" />
<TableColumn fx:id="colAppointmentDate" prefWidth="101.14288330078125" text="Date" /> <TableColumn fx:id="colAppointmentDate" prefWidth="101.14288330078125" text="Date" />
<TableColumn fx:id="colAppointmentTime" prefWidth="89.7142333984375" text="Time" /> <TableColumn fx:id="colAppointmentTime" prefWidth="89.7142333984375" text="Time" />
<TableColumn fx:id="colCustomerName" prefWidth="168.57147216796875" text="Customer" /> <TableColumn fx:id="colCustomerName" prefWidth="140.0" text="Customer" />
<TableColumn fx:id="colAppointmentStatus" prefWidth="98.28570556640625" text="Status" /> <TableColumn fx:id="colAppointmentStatus" prefWidth="98.28570556640625" text="Status" />
</columns> </columns>
</TableView> </TableView>

View File

@@ -203,6 +203,7 @@ function AppointmentsPage() {
const didPreselectRef = useRef(false); const didPreselectRef = useRef(false);
const [stores, setStores] = useState([]); const [stores, setStores] = useState([]);
const [employees, setEmployees] = useState([]);
const [services, setServices] = useState([]); const [services, setServices] = useState([]);
const [allPets, setAllPets] = useState([]); const [allPets, setAllPets] = useState([]);
const [customerPets, setCustomerPets] = useState([]); const [customerPets, setCustomerPets] = useState([]);
@@ -210,6 +211,7 @@ function AppointmentsPage() {
const [storeId, setStoreId] = useState(""); const [storeId, setStoreId] = useState("");
const [serviceId, setServiceId] = useState(""); const [serviceId, setServiceId] = useState("");
const [employeeId, setEmployeeId] = useState("");
const [appointmentDate, setAppointmentDate] = useState(""); const [appointmentDate, setAppointmentDate] = useState("");
const [appointmentTime, setAppointmentTime] = useState(""); const [appointmentTime, setAppointmentTime] = useState("");
const [selectedPetIds, setSelectedPetIds] = useState([]); const [selectedPetIds, setSelectedPetIds] = useState([]);
@@ -302,6 +304,33 @@ function AppointmentsPage() {
loadAppointments(); loadAppointments();
}, [loadAppointments]); }, [loadAppointments]);
useEffect(() => {
if (!token || !storeId) {
setEmployees([]);
setEmployeeId("");
return;
}
fetch(`${API_BASE}/api/v1/dropdowns/stores/${storeId}/employees`, {
headers: { Authorization: `Bearer ${token}` },
})
.then((r) => r.json())
.then((data) => setEmployees(Array.isArray(data) ? data : []))
.catch(() => setEmployees([]));
}, [token, storeId]);
useEffect(() => {
if (!employees.length) {
setEmployeeId("");
return;
}
const currentExists = employees.some((employee) => String(employee.id) === String(employeeId));
if (!currentExists) {
setEmployeeId(String(employees[0].id));
}
}, [employees, employeeId]);
useEffect(() => { useEffect(() => {
if (!storeId || !serviceId || !appointmentDate) { if (!storeId || !serviceId || !appointmentDate) {
setAvailableSlots([]); setAvailableSlots([]);
@@ -401,6 +430,7 @@ function AppointmentsPage() {
customerId: user.customerId, customerId: user.customerId,
storeId: Number(storeId), storeId: Number(storeId),
serviceId: Number(serviceId), serviceId: Number(serviceId),
employeeId: employeeId ? Number(employeeId) : undefined,
appointmentDate, appointmentDate,
appointmentTime: appointmentTime + ":00", appointmentTime: appointmentTime + ":00",
appointmentStatus: "Booked", appointmentStatus: "Booked",
@@ -513,6 +543,21 @@ function AppointmentsPage() {
</select> </select>
</label> </label>
{employees.length > 0 && (
<label className="appt-label">
Employee
<select
className="appt-select"
value={employeeId}
onChange={(e) => setEmployeeId(e.target.value)}
>
{employees.map((employee) => (
<option key={employee.id} value={employee.id}>{employee.label}</option>
))}
</select>
</label>
)}
{selectedService && ( {selectedService && (
<div className="appt-service-info"> <div className="appt-service-info">
<p>{selectedService.serviceDesc}</p> <p>{selectedService.serviceDesc}</p>