Merge branch 'main' into AttachmentsToChat
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,5 @@
|
|||||||
*.zip
|
*.zip
|
||||||
.local/
|
.local/
|
||||||
|
commit-patches/
|
||||||
|
temp_photos/
|
||||||
|
uploads/
|
||||||
|
|||||||
@@ -8,16 +8,22 @@ 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;
|
||||||
private String createdAt;
|
private String createdAt;
|
||||||
private String updatedAt;
|
private String updatedAt;
|
||||||
|
|
||||||
// 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 +48,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;
|
||||||
}
|
}
|
||||||
@@ -65,4 +79,4 @@ public class AdoptionDTO {
|
|||||||
public String getUpdatedAt() {
|
public String getUpdatedAt() {
|
||||||
return updatedAt;
|
return updatedAt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import java.math.BigDecimal;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class AppointmentDTO {
|
public class AppointmentDTO {
|
||||||
// Response fields (from server)
|
|
||||||
private Long appointmentId;
|
private Long appointmentId;
|
||||||
private Long customerId;
|
private Long customerId;
|
||||||
private String customerName;
|
private String customerName;
|
||||||
@@ -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;
|
||||||
@@ -20,21 +22,25 @@ public class AppointmentDTO {
|
|||||||
private String createdAt;
|
private String createdAt;
|
||||||
private String updatedAt;
|
private String updatedAt;
|
||||||
|
|
||||||
// Constructor for CREATE/UPDATE request body
|
|
||||||
// Matches AppointmentRequest exactly
|
|
||||||
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;
|
||||||
this.petIds = petIds;
|
this.petIds = petIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters
|
|
||||||
public Long getAppointmentId() {
|
public Long getAppointmentId() {
|
||||||
return appointmentId;
|
return appointmentId;
|
||||||
}
|
}
|
||||||
@@ -63,6 +69,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;
|
||||||
}
|
}
|
||||||
@@ -91,7 +105,6 @@ public class AppointmentDTO {
|
|||||||
return updatedAt;
|
return updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience getters for adapter/list display
|
|
||||||
public String getPetName() {
|
public String getPetName() {
|
||||||
return (petNames != null && !petNames.isEmpty()) ? petNames.get(0) : "";
|
return (petNames != null && !petNames.isEmpty()) ? petNames.get(0) : "";
|
||||||
}
|
}
|
||||||
@@ -104,7 +117,6 @@ public class AppointmentDTO {
|
|||||||
return getPetID();
|
return getPetID();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep old name so adapter doesn't break
|
|
||||||
public String getServiceType() {
|
public String getServiceType() {
|
||||||
return serviceName;
|
return serviceName;
|
||||||
}
|
}
|
||||||
@@ -113,7 +125,6 @@ public class AppointmentDTO {
|
|||||||
return serviceId;
|
return serviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status alias
|
|
||||||
public String getStatus() {
|
public String getStatus() {
|
||||||
return appointmentStatus;
|
return appointmentStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2069,6 +2069,37 @@
|
|||||||
{
|
{
|
||||||
"name": "Appointments",
|
"name": "Appointments",
|
||||||
"item": [
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Get Appointment Customers Dropdown",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{baseUrl}}/api/v1/dropdowns/appointment-customers",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{staffToken}}",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"pm.test('Status code is 200', function () {",
|
||||||
|
" pm.response.to.have.status(200);",
|
||||||
|
"});"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Check Appointment Availability",
|
"name": "Check Appointment Availability",
|
||||||
"request": {
|
"request": {
|
||||||
@@ -2180,7 +2211,7 @@
|
|||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"10:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [1]\n}",
|
"raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"10:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [\n 1\n ],\n \"employeeId\": 1\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
@@ -2222,7 +2253,7 @@
|
|||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"11:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [1]\n}",
|
"raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"11:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [\n 1\n ],\n \"employeeId\": 1\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
@@ -2315,6 +2346,37 @@
|
|||||||
{
|
{
|
||||||
"name": "Adoptions",
|
"name": "Adoptions",
|
||||||
"item": [
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Get Adoption Pets Dropdown",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{baseUrl}}/api/v1/dropdowns/adoption-pets",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{staffToken}}",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"pm.test('Status code is 200', function () {",
|
||||||
|
" pm.response.to.have.status(200);",
|
||||||
|
"});"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "List Adoptions",
|
"name": "List Adoptions",
|
||||||
"request": {
|
"request": {
|
||||||
@@ -2395,7 +2457,7 @@
|
|||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"petId\": 3,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-12-21\",\n \"adoptionStatus\": \"Pending\"\n}",
|
"raw": "{\n \"petId\": 3,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-12-21\",\n \"adoptionStatus\": \"Pending\",\n \"employeeId\": 1\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
@@ -2437,7 +2499,7 @@
|
|||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"petId\": 3,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-12-22\",\n \"adoptionStatus\": \"Completed\"\n}",
|
"raw": "{\n \"petId\": 3,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-12-22\",\n \"adoptionStatus\": \"Completed\",\n \"employeeId\": 1\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
@@ -3719,6 +3781,68 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Get Store Employees Dropdown",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{baseUrl}}/api/v1/dropdowns/stores/{{storeId}}/employees",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{staffToken}}",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"pm.test('Status code is 200', function () {",
|
||||||
|
" pm.response.to.have.status(200);",
|
||||||
|
"});"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Get All Employees Dropdown",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{baseUrl}}/api/v1/dropdowns/employees",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{staffToken}}",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"pm.test('Status code is 200', function () {",
|
||||||
|
" pm.response.to.have.status(200);",
|
||||||
|
"});"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "List Stores",
|
"name": "List Stores",
|
||||||
"request": {
|
"request": {
|
||||||
|
|||||||
@@ -35,13 +35,14 @@ public class FlywayContextInitializer implements ApplicationContextInitializer<C
|
|||||||
RuntimeException lastFailure = null;
|
RuntimeException lastFailure = null;
|
||||||
for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
||||||
try {
|
try {
|
||||||
Flyway.configure()
|
Flyway flyway = Flyway.configure()
|
||||||
.dataSource(url, username, password)
|
.dataSource(url, username, password)
|
||||||
.locations(locations)
|
.locations(locations)
|
||||||
.baselineOnMigrate(environment.getProperty("spring.flyway.baseline-on-migrate", Boolean.class, false))
|
.baselineOnMigrate(environment.getProperty("spring.flyway.baseline-on-migrate", Boolean.class, false))
|
||||||
.baselineVersion(MigrationVersion.fromVersion(environment.getProperty("spring.flyway.baseline-version", "1")))
|
.baselineVersion(MigrationVersion.fromVersion(environment.getProperty("spring.flyway.baseline-version", "1")))
|
||||||
.load()
|
.load();
|
||||||
.migrate();
|
flyway.repair();
|
||||||
|
flyway.migrate();
|
||||||
return;
|
return;
|
||||||
} catch (RuntimeException ex) {
|
} catch (RuntimeException ex) {
|
||||||
lastFailure = ex;
|
lastFailure = ex;
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.petshop.backend.config;
|
||||||
|
|
||||||
|
import com.petshop.backend.repository.CustomerPetRepository;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Profile("local")
|
||||||
|
public class LocalAppointmentCustomerSeedInitializer implements CommandLineRunner {
|
||||||
|
|
||||||
|
private final DataSource dataSource;
|
||||||
|
private final CustomerPetRepository customerPetRepository;
|
||||||
|
|
||||||
|
public LocalAppointmentCustomerSeedInitializer(DataSource dataSource, CustomerPetRepository customerPetRepository) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
this.customerPetRepository = customerPetRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(String... args) {
|
||||||
|
if (customerPetRepository.count() > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceDatabasePopulator populator = new ResourceDatabasePopulator(false, false, "UTF-8",
|
||||||
|
new ClassPathResource("dev/seed_demo_customer_pets.sql"));
|
||||||
|
populator.execute(dataSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -71,21 +71,8 @@ public class AdoptionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<AdoptionResponse> createAdoption(@Valid @RequestBody AdoptionRequest request) {
|
public ResponseEntity<AdoptionResponse> createAdoption(@Valid @RequestBody AdoptionRequest request) {
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
String role = authentication.getAuthorities().stream()
|
|
||||||
.findFirst()
|
|
||||||
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
if (role != null && role.equals("CUSTOMER")) {
|
|
||||||
Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
|
|
||||||
if (!request.getCustomerId().equals(customer.getCustomerId())) {
|
|
||||||
throw new org.springframework.security.access.AccessDeniedException("You can only create adoptions for yourself");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(adoptionService.createAdoption(request));
|
return ResponseEntity.status(HttpStatus.CREATED).body(adoptionService.createAdoption(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package com.petshop.backend.controller;
|
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.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;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@@ -17,23 +21,31 @@ public class DropdownController {
|
|||||||
|
|
||||||
private final PetRepository petRepository;
|
private final PetRepository petRepository;
|
||||||
private final CustomerRepository customerRepository;
|
private final CustomerRepository customerRepository;
|
||||||
|
private final CustomerPetRepository customerPetRepository;
|
||||||
private final ServiceRepository serviceRepository;
|
private final ServiceRepository serviceRepository;
|
||||||
private final ProductRepository productRepository;
|
private final ProductRepository productRepository;
|
||||||
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,
|
||||||
ServiceRepository serviceRepository, ProductRepository productRepository,
|
CustomerPetRepository customerPetRepository,
|
||||||
CategoryRepository categoryRepository, StoreRepository storeRepository,
|
ServiceRepository serviceRepository, ProductRepository productRepository,
|
||||||
SupplierRepository supplierRepository) {
|
CategoryRepository categoryRepository, StoreRepository storeRepository,
|
||||||
|
SupplierRepository supplierRepository, EmployeeStoreRepository employeeStoreRepository,
|
||||||
|
UserRepository userRepository) {
|
||||||
this.petRepository = petRepository;
|
this.petRepository = petRepository;
|
||||||
this.customerRepository = customerRepository;
|
this.customerRepository = customerRepository;
|
||||||
|
this.customerPetRepository = customerPetRepository;
|
||||||
this.serviceRepository = serviceRepository;
|
this.serviceRepository = serviceRepository;
|
||||||
this.productRepository = productRepository;
|
this.productRepository = productRepository;
|
||||||
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")
|
||||||
@@ -45,6 +57,16 @@ public class DropdownController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/adoption-pets")
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
|
public ResponseEntity<List<DropdownOption>> getAdoptionPets() {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
petRepository.findAllByPetStatusIgnoreCaseOrderByPetNameAsc("Available").stream()
|
||||||
|
.map(p -> new DropdownOption(p.getPetId(), p.getPetName()))
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/customers")
|
@GetMapping("/customers")
|
||||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<List<DropdownOption>> getCustomers() {
|
public ResponseEntity<List<DropdownOption>> getCustomers() {
|
||||||
@@ -55,6 +77,26 @@ public class DropdownController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/appointment-customers")
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
|
public ResponseEntity<List<DropdownOption>> getAppointmentCustomers() {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
customerRepository.findAllWithPets().stream()
|
||||||
|
.map(c -> new DropdownOption(c.getCustomerId(), c.getFirstName() + " " + c.getLastName()))
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/customers/{customerId}/pets")
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
|
public ResponseEntity<List<DropdownOption>> getCustomerPets(@PathVariable Long customerId) {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
customerPetRepository.findByCustomerCustomerIdOrderByPetNameAsc(customerId).stream()
|
||||||
|
.map(this::toCustomerPetOption)
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/services")
|
@GetMapping("/services")
|
||||||
public ResponseEntity<List<DropdownOption>> getServices() {
|
public ResponseEntity<List<DropdownOption>> getServices() {
|
||||||
return ResponseEntity.ok(
|
return ResponseEntity.ok(
|
||||||
@@ -114,6 +156,24 @@ public class DropdownController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping({"/stores/{storeId}/employees", "/employees"})
|
||||||
|
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||||
|
public ResponseEntity<List<DropdownOption>> getStoreEmployees(@PathVariable(required = false) Long storeId) {
|
||||||
|
List<EmployeeStore> employees;
|
||||||
|
if (storeId == null || storeId == 0) {
|
||||||
|
employees = employeeStoreRepository.findActiveAllOrderByEmployeeEmployeeIdAsc();
|
||||||
|
} else {
|
||||||
|
employees = employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(storeId);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
employees.stream()
|
||||||
|
.filter(this::isAssignableEmployee)
|
||||||
|
.map(this::toEmployeeOption)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/suppliers")
|
@GetMapping("/suppliers")
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
public ResponseEntity<List<DropdownOption>> getSuppliers() {
|
public ResponseEntity<List<DropdownOption>> getSuppliers() {
|
||||||
@@ -123,4 +183,26 @@ public class DropdownController {
|
|||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DropdownOption toCustomerPetOption(CustomerPet pet) {
|
||||||
|
String species = pet.getSpecies() == null || pet.getSpecies().isBlank() ? "Pet" : pet.getSpecies();
|
||||||
|
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)
|
||||||
|
.filter(user -> user.getRole() == User.Role.STAFF)
|
||||||
|
.filter(user -> Boolean.TRUE.equals(user.getActive()))
|
||||||
|
.isPresent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 +
|
||||||
|
|||||||
@@ -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 + '\'' +
|
||||||
|
|||||||
@@ -28,4 +28,8 @@ public interface AdoptionRepository extends JpaRepository<Adoption, Long> {
|
|||||||
Page<Adoption> searchAdoptionsByCustomer(@Param("customerId") Long customerId, @Param("q") String query, Pageable pageable);
|
Page<Adoption> searchAdoptionsByCustomer(@Param("customerId") Long customerId, @Param("q") String query, Pageable pageable);
|
||||||
|
|
||||||
Optional<Adoption> findFirstByPet_IdAndAdoptionStatusOrderByAdoptionDateDesc(Long petId, String adoptionStatus);
|
Optional<Adoption> findFirstByPet_IdAndAdoptionStatusOrderByAdoptionDateDesc(Long petId, String adoptionStatus);
|
||||||
|
|
||||||
|
boolean existsByPetPetIdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(Long petId, String adoptionStatus, Long adoptionId);
|
||||||
|
|
||||||
|
boolean existsByPetPetIdAndAdoptionStatusIgnoreCase(Long petId, String adoptionStatus);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public interface AppointmentRepository extends JpaRepository<Appointment, Long>
|
|||||||
@Query("SELECT a FROM Appointment a WHERE a.appointmentDate = :date AND a.appointmentTime = :time")
|
@Query("SELECT a FROM Appointment a WHERE a.appointmentDate = :date AND a.appointmentTime = :time")
|
||||||
List<Appointment> findByDateAndTime(@Param("date") LocalDate date, @Param("time") LocalTime time);
|
List<Appointment> findByDateAndTime(@Param("date") LocalDate date, @Param("time") LocalTime time);
|
||||||
|
|
||||||
@Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.store.storeId = :storeId AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) <> 'cancelled'")
|
@Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.store.storeId = :storeId AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')")
|
||||||
List<Appointment> findByStoreAndDate(@Param("storeId") Long storeId, @Param("date") LocalDate date);
|
List<Appointment> findByStoreAndDate(@Param("storeId") Long storeId, @Param("date") LocalDate date);
|
||||||
|
|
||||||
@Query("SELECT DISTINCT a FROM Appointment a LEFT JOIN a.pets p WHERE " +
|
@Query("SELECT DISTINCT a FROM Appointment a LEFT JOIN a.pets p WHERE " +
|
||||||
@@ -36,4 +36,10 @@ public interface AppointmentRepository extends JpaRepository<Appointment, Long>
|
|||||||
"LOWER(a.service.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"LOWER(a.service.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
"LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')))")
|
"LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')))")
|
||||||
Page<Appointment> searchAppointmentsByCustomer(@Param("customerId") Long customerId, @Param("q") String query, Pageable pageable);
|
Page<Appointment> searchAppointmentsByCustomer(@Param("customerId") Long customerId, @Param("q") String query, Pageable pageable);
|
||||||
|
|
||||||
|
@Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.employee.employeeId = :employeeId AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')")
|
||||||
|
List<Appointment> findByEmployeeEmployeeIdAndAppointmentDate(@Param("employeeId") Long employeeId, @Param("date") LocalDate date);
|
||||||
|
|
||||||
|
@Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.employee.employeeId IN :employeeIds AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) NOT IN ('cancelled', 'missed')")
|
||||||
|
List<Appointment> findByEmployeeEmployeeIdInAndAppointmentDate(@Param("employeeIds") List<Long> employeeIds, @Param("date") LocalDate date);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,5 +12,7 @@ public interface CustomerPetRepository extends JpaRepository<CustomerPet, Long>
|
|||||||
|
|
||||||
List<CustomerPet> findByCustomerCustomerIdOrderByCreatedAtDesc(Long customerId);
|
List<CustomerPet> findByCustomerCustomerIdOrderByCreatedAtDesc(Long customerId);
|
||||||
|
|
||||||
|
List<CustomerPet> findByCustomerCustomerIdOrderByPetNameAsc(Long customerId);
|
||||||
|
|
||||||
Optional<CustomerPet> findByCustomerPetIdAndCustomerCustomerId(Long customerPetId, Long customerId);
|
Optional<CustomerPet> findByCustomerPetIdAndCustomerCustomerId(Long customerPetId, Long customerId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ public interface CustomerRepository extends JpaRepository<Customer, Long> {
|
|||||||
|
|
||||||
Optional<Customer> findByUserId(Long userId);
|
Optional<Customer> findByUserId(Long userId);
|
||||||
List<Customer> findAllByEmail(String email);
|
List<Customer> findAllByEmail(String email);
|
||||||
|
|
||||||
|
@Query("SELECT DISTINCT c FROM Customer c WHERE EXISTS (SELECT cp FROM CustomerPet cp WHERE cp.customer = c) ORDER BY c.firstName ASC, c.lastName ASC")
|
||||||
|
List<Customer> findAllWithPets();
|
||||||
|
|
||||||
@Query("SELECT c FROM Customer c WHERE " +
|
@Query("SELECT c FROM Customer c WHERE " +
|
||||||
"LOWER(c.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"LOWER(c.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
|||||||
@@ -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 " +
|
||||||
|
|||||||
@@ -2,11 +2,20 @@ 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);
|
||||||
|
|
||||||
|
@Query("SELECT es FROM EmployeeStore es WHERE es.employee.isActive = true ORDER BY es.employee.employeeId ASC")
|
||||||
|
List<EmployeeStore> findActiveAllOrderByEmployeeEmployeeIdAsc();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,28 @@ import org.springframework.data.jpa.repository.Query;
|
|||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface PetRepository extends JpaRepository<Pet, Long> {
|
public interface PetRepository extends JpaRepository<Pet, Long> {
|
||||||
|
|
||||||
|
List<Pet> findAllByPetStatusIgnoreCaseOrderByPetNameAsc(String petStatus);
|
||||||
|
|
||||||
@Query("SELECT p FROM Pet p WHERE " +
|
@Query("SELECT p FROM Pet p WHERE " +
|
||||||
"(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petBreed) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
"(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petBreed) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||||
"(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " +
|
"(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " +
|
||||||
"(:status IS NULL OR LOWER(p.petStatus) = LOWER(:status))")
|
"(:status IS NULL OR LOWER(p.petStatus) = LOWER(:status))")
|
||||||
Page<Pet> searchPets(@Param("q") String query, @Param("species") String species, @Param("status") String status, Pageable pageable);
|
Page<Pet> searchPets(@Param("q") String query, @Param("species") String species, @Param("status") String status, Pageable pageable);
|
||||||
|
|
||||||
|
@Query("SELECT p FROM Pet p WHERE LOWER(p.petStatus) = 'available' AND " +
|
||||||
|
"(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petBreed) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||||
|
"(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species))")
|
||||||
|
Page<Pet> searchPublicPets(@Param("q") String query, @Param("species") String species, Pageable pageable);
|
||||||
|
|
||||||
|
@Query("SELECT DISTINCT p FROM Pet p LEFT JOIN Adoption a ON a.pet = p AND LOWER(a.adoptionStatus) = 'completed' WHERE " +
|
||||||
|
"(LOWER(p.petStatus) = 'available' OR a.customer.userId = :userId) AND " +
|
||||||
|
"(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petBreed) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||||
|
"(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " +
|
||||||
|
"(:status IS NULL OR LOWER(p.petStatus) = LOWER(:status))")
|
||||||
|
Page<Pet> searchCustomerVisiblePets(@Param("userId") Long userId, @Param("q") String query, @Param("species") String species, @Param("status") String status, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -18,14 +22,24 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
@Service
|
@Service
|
||||||
public class AdoptionService {
|
public class AdoptionService {
|
||||||
|
|
||||||
|
private static final String ADOPTION_STATUS_PENDING = "Pending";
|
||||||
|
private static final String ADOPTION_STATUS_COMPLETED = "Completed";
|
||||||
|
private static final String ADOPTION_STATUS_CANCELLED = "Cancelled";
|
||||||
|
private static final String PET_STATUS_AVAILABLE = "Available";
|
||||||
|
private static final String PET_STATUS_ADOPTED = "Adopted";
|
||||||
|
|
||||||
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,14 +80,19 @@ 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());
|
||||||
|
String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus());
|
||||||
|
validatePetAvailability(pet, null);
|
||||||
|
|
||||||
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(adoptionStatus);
|
||||||
|
|
||||||
adoption = adoptionRepository.save(adoption);
|
adoption = adoptionRepository.save(adoption);
|
||||||
|
syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId());
|
||||||
return mapToResponse(adoption);
|
return mapToResponse(adoption);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,13 +106,18 @@ 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());
|
||||||
|
String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus());
|
||||||
|
validatePetAvailability(pet, adoption.getAdoptionId());
|
||||||
|
|
||||||
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(adoptionStatus);
|
||||||
|
|
||||||
adoption = adoptionRepository.save(adoption);
|
adoption = adoptionRepository.save(adoption);
|
||||||
|
syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId());
|
||||||
return mapToResponse(adoption);
|
return mapToResponse(adoption);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +141,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 +150,72 @@ 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)
|
||||||
|
.filter(user -> user.getRole() == User.Role.STAFF)
|
||||||
|
.filter(user -> Boolean.TRUE.equals(user.getActive()))
|
||||||
|
.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeAdoptionStatus(String adoptionStatus) {
|
||||||
|
if (adoptionStatus == null) {
|
||||||
|
throw new IllegalArgumentException("Adoption status is required");
|
||||||
|
}
|
||||||
|
String trimmedStatus = adoptionStatus.trim();
|
||||||
|
if (ADOPTION_STATUS_PENDING.equalsIgnoreCase(trimmedStatus)) {
|
||||||
|
return ADOPTION_STATUS_PENDING;
|
||||||
|
}
|
||||||
|
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(trimmedStatus)) {
|
||||||
|
return ADOPTION_STATUS_COMPLETED;
|
||||||
|
}
|
||||||
|
if (ADOPTION_STATUS_CANCELLED.equalsIgnoreCase(trimmedStatus)) {
|
||||||
|
return ADOPTION_STATUS_CANCELLED;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Adoption status must be Pending, Completed, or Cancelled");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validatePetAvailability(Pet pet, Long adoptionId) {
|
||||||
|
boolean adoptedElsewhere = adoptionId == null
|
||||||
|
? adoptionRepository.existsByPetPetIdAndAdoptionStatusIgnoreCase(pet.getPetId(), ADOPTION_STATUS_COMPLETED)
|
||||||
|
: adoptionRepository.existsByPetPetIdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId);
|
||||||
|
if (adoptedElsewhere) {
|
||||||
|
throw new IllegalArgumentException("Selected pet has already been adopted");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PET_STATUS_AVAILABLE.equalsIgnoreCase(pet.getPetStatus()) && adoptionId == null) {
|
||||||
|
throw new IllegalArgumentException("Selected pet is not available for adoption");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncPetStatus(Pet pet, String adoptionStatus, Long adoptionId) {
|
||||||
|
boolean completedElsewhere = adoptionId != null
|
||||||
|
&& adoptionRepository.existsByPetPetIdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId);
|
||||||
|
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) || completedElsewhere) {
|
||||||
|
pet.setPetStatus(PET_STATUS_ADOPTED);
|
||||||
|
} else {
|
||||||
|
pet.setPetStatus(PET_STATUS_AVAILABLE);
|
||||||
|
}
|
||||||
|
petRepository.save(pet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ public class AppointmentService {
|
|||||||
public AppointmentResponse createAppointment(AppointmentRequest request) {
|
public AppointmentResponse createAppointment(AppointmentRequest request) {
|
||||||
validateAppointmentRequest(request);
|
validateAppointmentRequest(request);
|
||||||
|
|
||||||
|
User authenticatedUser = AuthenticationHelper.getAuthenticatedUser(userRepository);
|
||||||
|
|
||||||
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()));
|
||||||
|
|
||||||
@@ -108,19 +110,19 @@ public class AppointmentService {
|
|||||||
com.petshop.backend.entity.Service service = serviceRepository.findById(request.getServiceId())
|
com.petshop.backend.entity.Service service = serviceRepository.findById(request.getServiceId())
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId()));
|
.orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId()));
|
||||||
|
|
||||||
validateStoreAccess(store.getStoreId());
|
|
||||||
validateAvailability(store, service, request.getAppointmentDate(), request.getAppointmentTime(), null);
|
|
||||||
|
|
||||||
boolean hasPetIds = request.getPetIds() != null && !request.getPetIds().isEmpty();
|
boolean hasPetIds = request.getPetIds() != null && !request.getPetIds().isEmpty();
|
||||||
boolean hasCustomerPetIds = request.getCustomerPetIds() != null && !request.getCustomerPetIds().isEmpty();
|
boolean hasCustomerPetIds = request.getCustomerPetIds() != null && !request.getCustomerPetIds().isEmpty();
|
||||||
|
|
||||||
if (!hasPetIds && !hasCustomerPetIds) {
|
if (!hasPetIds && !hasCustomerPetIds) {
|
||||||
|
|
||||||
throw new IllegalArgumentException("Please specify at least one pet.");
|
throw new IllegalArgumentException("Please specify at least one pet.");
|
||||||
}
|
}
|
||||||
|
|
||||||
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()) : new HashSet<>();
|
Set<CustomerPet> customerPets = hasCustomerPetIds ? fetchCustomerPets(request.getCustomerPetIds(), customer.getCustomerId()) : new HashSet<>();
|
||||||
|
Employee employee = resolveAppointmentEmployee(request.getEmployeeId(), store.getStoreId());
|
||||||
|
|
||||||
|
validateStoreAccess(store.getStoreId(), authenticatedUser);
|
||||||
|
validateAvailability(employee, service, request.getAppointmentDate(), request.getAppointmentTime(), null);
|
||||||
|
|
||||||
Appointment appointment = new Appointment();
|
Appointment appointment = new Appointment();
|
||||||
appointment.setCustomer(customer);
|
appointment.setCustomer(customer);
|
||||||
@@ -131,6 +133,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);
|
||||||
@@ -140,6 +143,8 @@ public class AppointmentService {
|
|||||||
public AppointmentResponse updateAppointment(Long id, AppointmentRequest request) {
|
public AppointmentResponse updateAppointment(Long id, AppointmentRequest request) {
|
||||||
validateAppointmentRequest(request);
|
validateAppointmentRequest(request);
|
||||||
|
|
||||||
|
User authenticatedUser = AuthenticationHelper.getAuthenticatedUser(userRepository);
|
||||||
|
|
||||||
Appointment appointment = appointmentRepository.findById(id)
|
Appointment appointment = appointmentRepository.findById(id)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Appointment not found with id: " + id));
|
.orElseThrow(() -> new ResourceNotFoundException("Appointment not found with id: " + id));
|
||||||
|
|
||||||
@@ -152,19 +157,19 @@ public class AppointmentService {
|
|||||||
com.petshop.backend.entity.Service service = serviceRepository.findById(request.getServiceId())
|
com.petshop.backend.entity.Service service = serviceRepository.findById(request.getServiceId())
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId()));
|
.orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId()));
|
||||||
|
|
||||||
validateStoreAccess(store.getStoreId());
|
|
||||||
validateAvailability(store, service, request.getAppointmentDate(), request.getAppointmentTime(), id);
|
|
||||||
|
|
||||||
boolean hasPetIds = request.getPetIds() != null && !request.getPetIds().isEmpty();
|
boolean hasPetIds = request.getPetIds() != null && !request.getPetIds().isEmpty();
|
||||||
boolean hasCustomerPetIds = request.getCustomerPetIds() != null && !request.getCustomerPetIds().isEmpty();
|
boolean hasCustomerPetIds = request.getCustomerPetIds() != null && !request.getCustomerPetIds().isEmpty();
|
||||||
|
|
||||||
if (!hasPetIds && !hasCustomerPetIds) {
|
if (!hasPetIds && !hasCustomerPetIds) {
|
||||||
|
|
||||||
throw new IllegalArgumentException("Please specify at least one pet.");
|
throw new IllegalArgumentException("Please specify at least one pet.");
|
||||||
}
|
}
|
||||||
|
|
||||||
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()) : new HashSet<>();
|
Set<CustomerPet> customerPets = hasCustomerPetIds ? fetchCustomerPets(request.getCustomerPetIds(), customer.getCustomerId()) : new HashSet<>();
|
||||||
|
Employee employee = resolveAppointmentEmployee(request.getEmployeeId(), store.getStoreId());
|
||||||
|
|
||||||
|
validateStoreAccess(store.getStoreId(), authenticatedUser);
|
||||||
|
validateAvailability(employee, service, request.getAppointmentDate(), request.getAppointmentTime(), id);
|
||||||
|
|
||||||
appointment.setCustomer(customer);
|
appointment.setCustomer(customer);
|
||||||
appointment.setStore(store);
|
appointment.setStore(store);
|
||||||
@@ -174,6 +179,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);
|
||||||
@@ -193,7 +199,6 @@ public class AppointmentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
|
|
||||||
public List<String> checkAvailability(Long storeId, Long serviceId, LocalDate date) {
|
public List<String> checkAvailability(Long storeId, Long serviceId, LocalDate date) {
|
||||||
storeRepository.findById(storeId)
|
storeRepository.findById(storeId)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId));
|
.orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId));
|
||||||
@@ -201,16 +206,20 @@ public class AppointmentService {
|
|||||||
com.petshop.backend.entity.Service service = serviceRepository.findById(serviceId)
|
com.petshop.backend.entity.Service service = serviceRepository.findById(serviceId)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + serviceId));
|
.orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + serviceId));
|
||||||
|
|
||||||
|
List<Employee> assignableEmployees = employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(storeId).stream()
|
||||||
//--------------------------------------------------------------------------
|
.filter(es -> isAssignableEmployee(es.getEmployee()))
|
||||||
// CHANGED: filter by serviceId too
|
.map(EmployeeStore::getEmployee)
|
||||||
List<Appointment> existingAppointments = appointmentRepository
|
|
||||||
.findByStoreAndDate(storeId, date)
|
|
||||||
.stream()
|
|
||||||
.filter(a -> a.getService().getServiceId().equals(serviceId))
|
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
// -------------------------------------------------------
|
|
||||||
|
|
||||||
|
if (assignableEmployees.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Long> employeeIds = assignableEmployees.stream().map(Employee::getEmployeeId).collect(Collectors.toList());
|
||||||
|
List<Appointment> allAppointments = appointmentRepository.findByEmployeeEmployeeIdInAndAppointmentDate(employeeIds, date);
|
||||||
|
|
||||||
|
java.util.Map<Long, List<Appointment>> appointmentsByEmployee = allAppointments.stream()
|
||||||
|
.collect(Collectors.groupingBy(a -> a.getEmployee().getEmployeeId()));
|
||||||
|
|
||||||
List<String> availableSlots = new ArrayList<>();
|
List<String> availableSlots = new ArrayList<>();
|
||||||
LocalTime startTime = LocalTime.of(9, 0);
|
LocalTime startTime = LocalTime.of(9, 0);
|
||||||
@@ -219,7 +228,13 @@ public class AppointmentService {
|
|||||||
|
|
||||||
LocalTime currentTime = startTime;
|
LocalTime currentTime = startTime;
|
||||||
while (!currentTime.isAfter(latestStart)) {
|
while (!currentTime.isAfter(latestStart)) {
|
||||||
if (isSlotAvailable(existingAppointments, service, currentTime, null)) {
|
final LocalTime slotTime = currentTime;
|
||||||
|
boolean anyEmployeeAvailable = assignableEmployees.stream().anyMatch(emp -> {
|
||||||
|
List<Appointment> empAppointments = appointmentsByEmployee.getOrDefault(emp.getEmployeeId(), List.of());
|
||||||
|
return isSlotAvailable(empAppointments, service, slotTime, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (anyEmployeeAvailable) {
|
||||||
availableSlots.add(currentTime.toString());
|
availableSlots.add(currentTime.toString());
|
||||||
}
|
}
|
||||||
currentTime = currentTime.plusMinutes(30);
|
currentTime = currentTime.plusMinutes(30);
|
||||||
@@ -247,11 +262,14 @@ public class AppointmentService {
|
|||||||
return pets;
|
return pets;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<CustomerPet> fetchCustomerPets(List<Long> customerPetIds) {
|
private Set<CustomerPet> fetchCustomerPets(List<Long> customerPetIds, Long customerId) {
|
||||||
Set<CustomerPet> customerPets = new HashSet<>();
|
Set<CustomerPet> customerPets = new HashSet<>();
|
||||||
for (Long customerPetId : customerPetIds) {
|
for (Long customerPetId : customerPetIds) {
|
||||||
CustomerPet customerPet = customerPetRepository.findById(customerPetId)
|
CustomerPet customerPet = customerPetRepository.findById(customerPetId)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Customer pet not found with id: " + customerPetId));
|
.orElseThrow(() -> new ResourceNotFoundException("Customer pet not found with id: " + customerPetId));
|
||||||
|
if (!customerPet.getCustomer().getCustomerId().equals(customerId)) {
|
||||||
|
throw new IllegalArgumentException("Selected pet does not belong to the selected customer");
|
||||||
|
}
|
||||||
customerPets.add(customerPet);
|
customerPets.add(customerPet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +304,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);
|
||||||
@@ -296,20 +316,46 @@ public class AppointmentService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------
|
private Employee resolveAppointmentEmployee(Long requestedEmployeeId, Long storeId) {
|
||||||
private void validateAvailability(StoreLocation store, com.petshop.backend.entity.Service service, LocalDate date, LocalTime time, Long appointmentIdToIgnore) {
|
List<EmployeeStore> assignableEmployees = employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(storeId).stream()
|
||||||
// Filter by same service only - different services can run at same time
|
.filter(es -> isAssignableEmployee(es.getEmployee()))
|
||||||
List<Appointment> existingAppointments = appointmentRepository
|
|
||||||
.findByStoreAndDate(store.getStoreId(), date)
|
|
||||||
.stream()
|
|
||||||
.filter(a -> a.getService().getServiceId().equals(service.getServiceId()))
|
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
if (!isSlotAvailable(existingAppointments, service, time, appointmentIdToIgnore)) {
|
|
||||||
throw new IllegalArgumentException("Appointment time is not available for the selected store and service");
|
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)
|
||||||
|
.filter(user -> user.getRole() == User.Role.STAFF)
|
||||||
|
.filter(user -> Boolean.TRUE.equals(user.getActive()))
|
||||||
|
.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateAvailability(Employee employee, com.petshop.backend.entity.Service service, LocalDate date, LocalTime time, Long appointmentIdToIgnore) {
|
||||||
|
List<Appointment> existingAppointments = appointmentRepository
|
||||||
|
.findByEmployeeEmployeeIdAndAppointmentDate(employee.getEmployeeId(), date);
|
||||||
|
if (!isSlotAvailable(existingAppointments, service, time, appointmentIdToIgnore)) {
|
||||||
|
throw new IllegalArgumentException("The selected employee is already booked for this time slot");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isSlotAvailable(List<Appointment> existingAppointments, com.petshop.backend.entity.Service requestedService, LocalTime requestedStart, Long appointmentIdToIgnore) {
|
private boolean isSlotAvailable(List<Appointment> existingAppointments, com.petshop.backend.entity.Service requestedService, LocalTime requestedStart, Long appointmentIdToIgnore) {
|
||||||
LocalTime requestedEnd = requestedStart.plusMinutes(requestedService.getServiceDuration());
|
LocalTime requestedEnd = requestedStart.plusMinutes(requestedService.getServiceDuration());
|
||||||
@@ -326,8 +372,7 @@ public class AppointmentService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateStoreAccess(Long requestedStoreId) {
|
private void validateStoreAccess(Long requestedStoreId, User user) {
|
||||||
User user = AuthenticationHelper.getAuthenticatedUser(userRepository);
|
|
||||||
if (user.getRole() != User.Role.STAFF) {
|
if (user.getRole() != User.Role.STAFF) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,16 @@ import com.petshop.backend.entity.Adoption;
|
|||||||
import com.petshop.backend.entity.Pet;
|
import com.petshop.backend.entity.Pet;
|
||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
import com.petshop.backend.exception.ResourceNotFoundException;
|
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||||
|
import com.petshop.backend.security.AppPrincipal;
|
||||||
import com.petshop.backend.repository.AdoptionRepository;
|
import com.petshop.backend.repository.AdoptionRepository;
|
||||||
import com.petshop.backend.repository.PetRepository;
|
import com.petshop.backend.repository.PetRepository;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageImpl;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@@ -34,13 +38,38 @@ public class PetService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Page<PetResponse> getAllPets(String query, String species, String status, Pageable pageable) {
|
public Page<PetResponse> getAllPets(String query, String species, String status, Pageable pageable) {
|
||||||
return petRepository.searchPets(normalizeFilter(query), normalizeFilter(species), normalizeFilter(status), pageable)
|
String normalizedQuery = normalizeFilter(query);
|
||||||
|
String normalizedSpecies = normalizeFilter(species);
|
||||||
|
String normalizedStatus = normalizeFilter(status);
|
||||||
|
CurrentViewer viewer = getCurrentViewer();
|
||||||
|
|
||||||
|
Page<Pet> pets;
|
||||||
|
if (viewer == null) {
|
||||||
|
if (!isAllowedPublicStatus(normalizedStatus)) {
|
||||||
|
return new PageImpl<>(java.util.List.of(), pageable, 0);
|
||||||
|
}
|
||||||
|
pets = petRepository.searchPublicPets(normalizedQuery, normalizedSpecies, pageable);
|
||||||
|
} else if (viewer.role() == User.Role.STAFF || viewer.role() == User.Role.ADMIN) {
|
||||||
|
pets = petRepository.searchPets(normalizedQuery, normalizedSpecies, normalizedStatus, pageable);
|
||||||
|
} else if (viewer.role() == User.Role.CUSTOMER) {
|
||||||
|
if (!isAllowedCustomerStatus(normalizedStatus)) {
|
||||||
|
return new PageImpl<>(java.util.List.of(), pageable, 0);
|
||||||
|
}
|
||||||
|
pets = petRepository.searchCustomerVisiblePets(viewer.userId(), normalizedQuery, normalizedSpecies, normalizedStatus, pageable);
|
||||||
|
} else {
|
||||||
|
pets = petRepository.searchPublicPets(normalizedQuery, normalizedSpecies, pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pets
|
||||||
.map(this::mapToResponse);
|
.map(this::mapToResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PetResponse getPetById(Long id) {
|
public PetResponse getPetById(Long id) {
|
||||||
Pet pet = petRepository.findById(id)
|
Pet pet = petRepository.findById(id)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id));
|
.orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id));
|
||||||
|
if (!canViewPet(pet, getCurrentViewer())) {
|
||||||
|
throw new ResourceNotFoundException("Pet not found with id: " + id);
|
||||||
|
}
|
||||||
return mapToResponse(pet);
|
return mapToResponse(pet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +139,7 @@ public class PetService {
|
|||||||
if (pet.getImageUrl() == null || pet.getImageUrl().isBlank()) {
|
if (pet.getImageUrl() == null || pet.getImageUrl().isBlank()) {
|
||||||
throw new ResourceNotFoundException("Pet image not found for id: " + id);
|
throw new ResourceNotFoundException("Pet image not found for id: " + id);
|
||||||
}
|
}
|
||||||
if (!canViewPetImage(pet, requesterUserId, requesterRole)) {
|
if (!canViewPet(pet, new CurrentViewer(requesterUserId, requesterRole))) {
|
||||||
throw new ForbiddenImageAccessException();
|
throw new ForbiddenImageAccessException();
|
||||||
}
|
}
|
||||||
Resource resource = catalogImageStorageService.loadPetImage(pet.getImageUrl());
|
Resource resource = catalogImageStorageService.loadPetImage(pet.getImageUrl());
|
||||||
@@ -122,14 +151,21 @@ public class PetService {
|
|||||||
return "available".equalsIgnoreCase(normalizeStatus(pet.getPetStatus()));
|
return "available".equalsIgnoreCase(normalizeStatus(pet.getPetStatus()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canViewPetImage(Pet pet, Long requesterUserId, User.Role requesterRole) {
|
private boolean canViewPet(Pet pet, CurrentViewer viewer) {
|
||||||
if (isPubliclyVisible(pet)) {
|
if (isPubliclyVisible(pet)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (requesterRole == User.Role.STAFF || requesterRole == User.Role.ADMIN) {
|
if (viewer != null && (viewer.role() == User.Role.STAFF || viewer.role() == User.Role.ADMIN)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (requesterUserId == null) {
|
if (viewer == null || viewer.userId() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isAdoptedByUser(pet, viewer.userId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAdoptedByUser(Pet pet, Long userId) {
|
||||||
|
if (userId == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!"adopted".equalsIgnoreCase(normalizeStatus(pet.getPetStatus()))) {
|
if (!"adopted".equalsIgnoreCase(normalizeStatus(pet.getPetStatus()))) {
|
||||||
@@ -137,10 +173,22 @@ public class PetService {
|
|||||||
}
|
}
|
||||||
return adoptionRepository.findFirstByPet_IdAndAdoptionStatusOrderByAdoptionDateDesc(pet.getPetId(), "Completed")
|
return adoptionRepository.findFirstByPet_IdAndAdoptionStatusOrderByAdoptionDateDesc(pet.getPetId(), "Completed")
|
||||||
.map(Adoption::getCustomer)
|
.map(Adoption::getCustomer)
|
||||||
.map(customer -> requesterUserId.equals(customer.getUserId()))
|
.map(customer -> userId.equals(customer.getUserId()))
|
||||||
.orElse(false);
|
.orElse(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CurrentViewer getCurrentViewer() {
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object principal = authentication.getPrincipal();
|
||||||
|
if (principal instanceof AppPrincipal appPrincipal) {
|
||||||
|
return new CurrentViewer(appPrincipal.getUserId(), appPrincipal.getRole());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private Pet findPet(Long id) {
|
private Pet findPet(Long id) {
|
||||||
return petRepository.findById(id)
|
return petRepository.findById(id)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id));
|
.orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id));
|
||||||
@@ -177,6 +225,14 @@ public class PetService {
|
|||||||
return status == null ? "" : status.trim();
|
return status == null ? "" : status.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isAllowedPublicStatus(String status) {
|
||||||
|
return status == null || "available".equalsIgnoreCase(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAllowedCustomerStatus(String status) {
|
||||||
|
return status == null || "available".equalsIgnoreCase(status) || "adopted".equalsIgnoreCase(status);
|
||||||
|
}
|
||||||
|
|
||||||
private String normalizeFilter(String value) {
|
private String normalizeFilter(String value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -203,6 +259,9 @@ public class PetService {
|
|||||||
public record ImagePayload(Resource resource, MediaType mediaType) {
|
public record ImagePayload(Resource resource, MediaType mediaType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record CurrentViewer(Long userId, User.Role role) {
|
||||||
|
}
|
||||||
|
|
||||||
public static class ForbiddenImageAccessException extends RuntimeException {
|
public static class ForbiddenImageAccessException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
UPDATE users u
|
||||||
|
SET u.active = TRUE
|
||||||
|
WHERE u.role IN ('STAFF', 'ADMIN')
|
||||||
|
AND EXISTS (SELECT 1 FROM employee e WHERE e.user_id = u.id);
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
INSERT INTO customer_pet (customer_id, pet_name, species, breed)
|
||||||
|
SELECT DISTINCT a.customerId, p.petName, p.petSpecies, p.petBreed
|
||||||
|
FROM appointmentPet ap
|
||||||
|
JOIN appointment a ON a.appointmentId = ap.appointmentId
|
||||||
|
JOIN pet p ON p.petId = ap.petId
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM customer_pet cp
|
||||||
|
WHERE cp.customer_id = a.customerId AND cp.pet_name = p.petName
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO appointment_customer_pet (appointment_id, customer_pet_id)
|
||||||
|
SELECT ap.appointmentId, cp.customer_pet_id
|
||||||
|
FROM appointmentPet ap
|
||||||
|
JOIN appointment a ON a.appointmentId = ap.appointmentId
|
||||||
|
JOIN pet p ON p.petId = ap.petId
|
||||||
|
JOIN customer_pet cp ON cp.customer_id = a.customerId AND cp.pet_name = p.petName
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM appointment_customer_pet acp
|
||||||
|
WHERE acp.appointment_id = ap.appointmentId AND acp.customer_pet_id = cp.customer_pet_id
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM appointmentPet;
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
UPDATE appointment
|
||||||
|
SET appointmentStatus = 'Missed'
|
||||||
|
WHERE LOWER(appointmentStatus) = 'booked'
|
||||||
|
AND (
|
||||||
|
appointmentDate < CURRENT_DATE
|
||||||
|
OR (appointmentDate = CURRENT_DATE AND appointmentTime < CURRENT_TIME)
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE appointment a1
|
||||||
|
JOIN (
|
||||||
|
SELECT a3.appointmentId
|
||||||
|
FROM appointment a3
|
||||||
|
INNER JOIN appointment a4
|
||||||
|
ON a4.employeeId = a3.employeeId
|
||||||
|
AND a4.appointmentDate = a3.appointmentDate
|
||||||
|
AND a4.appointmentTime = a3.appointmentTime
|
||||||
|
AND a4.appointmentId < a3.appointmentId
|
||||||
|
WHERE LOWER(a3.appointmentStatus) NOT IN ('cancelled', 'missed')
|
||||||
|
) conflicting ON conflicting.appointmentId = a1.appointmentId
|
||||||
|
SET a1.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 = a1.storeId
|
||||||
|
AND e.isActive = TRUE
|
||||||
|
AND u.role = 'STAFF'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM (
|
||||||
|
SELECT employeeId, appointmentDate, appointmentTime, appointmentId
|
||||||
|
FROM appointment
|
||||||
|
) snap
|
||||||
|
WHERE snap.employeeId = es.employeeId
|
||||||
|
AND snap.appointmentDate = a1.appointmentDate
|
||||||
|
AND snap.appointmentTime = a1.appointmentTime
|
||||||
|
AND snap.appointmentId <> a1.appointmentId
|
||||||
|
)
|
||||||
|
ORDER BY es.employeeId ASC
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
53
backend/src/main/resources/dev/seed_demo_customer_pets.sql
Normal file
53
backend/src/main/resources/dev/seed_demo_customer_pets.sql
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
INSERT INTO customer_pet (customer_id, pet_name, species, breed, image_url)
|
||||||
|
SELECT c.customerId, 'Rocky', 'dog', 'Labrador', NULL
|
||||||
|
FROM customer c
|
||||||
|
WHERE c.email = 'alex@gmail.com'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM customer_pet cp
|
||||||
|
WHERE cp.customer_id = c.customerId AND cp.pet_name = 'Rocky'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO customer_pet (customer_id, pet_name, species, breed, image_url)
|
||||||
|
SELECT c.customerId, 'Whiskers', 'cat', 'Persian', NULL
|
||||||
|
FROM customer c
|
||||||
|
WHERE c.email = 'emily@gmail.com'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM customer_pet cp
|
||||||
|
WHERE cp.customer_id = c.customerId AND cp.pet_name = 'Whiskers'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO customer_pet (customer_id, pet_name, species, breed, image_url)
|
||||||
|
SELECT c.customerId, 'Daisy', 'dog', 'Beagle', NULL
|
||||||
|
FROM customer c
|
||||||
|
WHERE c.email = 'james@gmail.com'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM customer_pet cp
|
||||||
|
WHERE cp.customer_id = c.customerId AND cp.pet_name = 'Daisy'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO customer_pet (customer_id, pet_name, species, breed, image_url)
|
||||||
|
SELECT c.customerId, 'Pepper', 'cat', 'Domestic Shorthair', NULL
|
||||||
|
FROM customer c
|
||||||
|
WHERE c.email = 'olivia@gmail.com'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM customer_pet cp
|
||||||
|
WHERE cp.customer_id = c.customerId AND cp.pet_name = 'Pepper'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO customer_pet (customer_id, pet_name, species, breed, image_url)
|
||||||
|
SELECT c.customerId, 'Cooper', 'dog', 'Golden Retriever', NULL
|
||||||
|
FROM customer c
|
||||||
|
WHERE c.email = 'william@gmail.com'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM customer_pet cp
|
||||||
|
WHERE cp.customer_id = c.customerId AND cp.pet_name = 'Cooper'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO customer_pet (customer_id, pet_name, species, breed, image_url)
|
||||||
|
SELECT c.customerId, 'Mittens', 'cat', 'Siamese', NULL
|
||||||
|
FROM customer c
|
||||||
|
WHERE c.email = 'sophia@gmail.com'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM customer_pet cp
|
||||||
|
WHERE cp.customer_id = c.customerId AND cp.pet_name = 'Mittens'
|
||||||
|
);
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
package com.petshop.backend.controller;
|
||||||
|
|
||||||
|
import com.petshop.backend.entity.Employee;
|
||||||
|
import com.petshop.backend.entity.EmployeeStore;
|
||||||
|
import com.petshop.backend.entity.Customer;
|
||||||
|
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 com.petshop.backend.security.AppPrincipal;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private PetRepository petRepository;
|
||||||
|
private CustomerRepository customerRepository;
|
||||||
|
private CustomerPetRepository customerPetRepository;
|
||||||
|
private ServiceRepository serviceRepository;
|
||||||
|
private ProductRepository productRepository;
|
||||||
|
private CategoryRepository categoryRepository;
|
||||||
|
private StoreRepository storeRepository;
|
||||||
|
private SupplierRepository supplierRepository;
|
||||||
|
private EmployeeStoreRepository employeeStoreRepository;
|
||||||
|
private UserRepository userRepository;
|
||||||
|
private DropdownController controller;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
petRepository = mock(PetRepository.class);
|
||||||
|
customerRepository = mock(CustomerRepository.class);
|
||||||
|
customerPetRepository = mock(CustomerPetRepository.class);
|
||||||
|
serviceRepository = mock(ServiceRepository.class);
|
||||||
|
productRepository = mock(ProductRepository.class);
|
||||||
|
categoryRepository = mock(CategoryRepository.class);
|
||||||
|
storeRepository = mock(StoreRepository.class);
|
||||||
|
supplierRepository = mock(SupplierRepository.class);
|
||||||
|
employeeStoreRepository = mock(EmployeeStoreRepository.class);
|
||||||
|
userRepository = mock(UserRepository.class);
|
||||||
|
|
||||||
|
controller = new DropdownController(
|
||||||
|
petRepository,
|
||||||
|
customerRepository,
|
||||||
|
customerPetRepository,
|
||||||
|
serviceRepository,
|
||||||
|
productRepository,
|
||||||
|
categoryRepository,
|
||||||
|
storeRepository,
|
||||||
|
supplierRepository,
|
||||||
|
employeeStoreRepository,
|
||||||
|
userRepository
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAuthentication(Long userId, User.Role role) {
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
|
new UsernamePasswordAuthenticationToken(
|
||||||
|
new AppPrincipal(userId, "user", role, 0),
|
||||||
|
null,
|
||||||
|
List.of()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getStoreEmployeesReturnsOnlyStaffLinkedEmployees() {
|
||||||
|
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);
|
||||||
|
staffUser.setActive(true);
|
||||||
|
|
||||||
|
User adminUser = new User();
|
||||||
|
adminUser.setId(8L);
|
||||||
|
adminUser.setRole(User.Role.ADMIN);
|
||||||
|
adminUser.setActive(true);
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getStoreEmployeesReturnsAllStaffWhenStoreIdIsNull() {
|
||||||
|
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);
|
||||||
|
|
||||||
|
User staffUser = new User();
|
||||||
|
staffUser.setId(7L);
|
||||||
|
staffUser.setRole(User.Role.STAFF);
|
||||||
|
staffUser.setActive(true);
|
||||||
|
|
||||||
|
when(employeeStoreRepository.findActiveAllOrderByEmployeeEmployeeIdAsc())
|
||||||
|
.thenReturn(List.of(new EmployeeStore(staffEmployee, store)));
|
||||||
|
when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser));
|
||||||
|
|
||||||
|
var response = controller.getStoreEmployees(null);
|
||||||
|
|
||||||
|
assertEquals(1, response.getBody().size());
|
||||||
|
assertEquals(Long.valueOf(7L), response.getBody().get(0).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getStoreEmployeesExcludesInactiveStaffUsers() {
|
||||||
|
StoreLocation store = new StoreLocation();
|
||||||
|
store.setStoreId(1L);
|
||||||
|
|
||||||
|
Employee inactiveStaffEmployee = new Employee();
|
||||||
|
inactiveStaffEmployee.setEmployeeId(7L);
|
||||||
|
inactiveStaffEmployee.setUserId(7L);
|
||||||
|
inactiveStaffEmployee.setFirstName("Alex");
|
||||||
|
inactiveStaffEmployee.setLastName("Jones");
|
||||||
|
inactiveStaffEmployee.setIsActive(true);
|
||||||
|
|
||||||
|
User inactiveStaffUser = new User();
|
||||||
|
inactiveStaffUser.setId(7L);
|
||||||
|
inactiveStaffUser.setRole(User.Role.STAFF);
|
||||||
|
inactiveStaffUser.setActive(false);
|
||||||
|
|
||||||
|
when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L))
|
||||||
|
.thenReturn(List.of(new EmployeeStore(inactiveStaffEmployee, store)));
|
||||||
|
when(userRepository.findById(7L)).thenReturn(Optional.of(inactiveStaffUser));
|
||||||
|
|
||||||
|
var response = controller.getStoreEmployees(1L);
|
||||||
|
|
||||||
|
assertEquals(0, response.getBody().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAppointmentCustomersReturnsOnlyCustomersWithPetsForStaff() {
|
||||||
|
User staffUser = new User();
|
||||||
|
staffUser.setId(99L);
|
||||||
|
staffUser.setRole(User.Role.STAFF);
|
||||||
|
when(userRepository.findById(99L)).thenReturn(Optional.of(staffUser));
|
||||||
|
setAuthentication(99L, User.Role.STAFF);
|
||||||
|
|
||||||
|
Customer one = new Customer();
|
||||||
|
one.setCustomerId(1L);
|
||||||
|
one.setFirstName("Alex");
|
||||||
|
one.setLastName("Brown");
|
||||||
|
|
||||||
|
when(customerRepository.findAllWithPets()).thenReturn(List.of(one));
|
||||||
|
|
||||||
|
var response = controller.getAppointmentCustomers();
|
||||||
|
|
||||||
|
assertEquals(1, response.getBody().size());
|
||||||
|
assertEquals(Long.valueOf(1L), response.getBody().get(0).getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
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");
|
||||||
|
pet.setPetStatus("Available");
|
||||||
|
|
||||||
|
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);
|
||||||
|
staffUser.setActive(true);
|
||||||
|
when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser));
|
||||||
|
|
||||||
|
User adminUser = new User();
|
||||||
|
adminUser.setId(8L);
|
||||||
|
adminUser.setRole(User.Role.ADMIN);
|
||||||
|
adminUser.setActive(true);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createAdoptionRejectsInactiveStaffUserSelection() {
|
||||||
|
User inactiveStaffUser = new User();
|
||||||
|
inactiveStaffUser.setId(7L);
|
||||||
|
inactiveStaffUser.setRole(User.Role.STAFF);
|
||||||
|
inactiveStaffUser.setActive(false);
|
||||||
|
when(userRepository.findById(7L)).thenReturn(Optional.of(inactiveStaffUser));
|
||||||
|
|
||||||
|
when(petRepository.findById(1L)).thenReturn(Optional.of(pet));
|
||||||
|
when(customerRepository.findById(1L)).thenReturn(Optional.of(customer));
|
||||||
|
when(employeeRepository.findById(7L)).thenReturn(Optional.of(staffEmployee));
|
||||||
|
|
||||||
|
AdoptionRequest request = new AdoptionRequest();
|
||||||
|
request.setPetId(1L);
|
||||||
|
request.setCustomerId(1L);
|
||||||
|
request.setEmployeeId(7L);
|
||||||
|
request.setAdoptionDate(LocalDate.now());
|
||||||
|
request.setAdoptionStatus("Pending");
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> adoptionService.createAdoption(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,23 +2,32 @@ 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.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;
|
||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
import com.petshop.backend.repository.AppointmentRepository;
|
import com.petshop.backend.repository.AppointmentRepository;
|
||||||
|
import com.petshop.backend.repository.CustomerPetRepository;
|
||||||
import com.petshop.backend.repository.CustomerRepository;
|
import com.petshop.backend.repository.CustomerRepository;
|
||||||
|
import com.petshop.backend.repository.EmployeeRepository;
|
||||||
|
import com.petshop.backend.repository.EmployeeStoreRepository;
|
||||||
import com.petshop.backend.repository.PetRepository;
|
import com.petshop.backend.repository.PetRepository;
|
||||||
import com.petshop.backend.repository.ServiceRepository;
|
import com.petshop.backend.repository.ServiceRepository;
|
||||||
import com.petshop.backend.repository.StoreRepository;
|
import com.petshop.backend.repository.StoreRepository;
|
||||||
import com.petshop.backend.repository.UserRepository;
|
import com.petshop.backend.repository.UserRepository;
|
||||||
|
import com.petshop.backend.security.AppPrincipal;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Test;
|
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;
|
||||||
@@ -29,32 +38,26 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.Mockito.any;
|
import static org.mockito.ArgumentMatchers.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 private AppointmentRepository appointmentRepository;
|
||||||
private AppointmentRepository appointmentRepository;
|
@Mock private CustomerRepository customerRepository;
|
||||||
|
@Mock private CustomerPetRepository customerPetRepository;
|
||||||
@Mock
|
@Mock private ServiceRepository serviceRepository;
|
||||||
private CustomerRepository customerRepository;
|
@Mock private PetRepository petRepository;
|
||||||
|
@Mock private StoreRepository storeRepository;
|
||||||
@Mock
|
@Mock private UserRepository userRepository;
|
||||||
private PetRepository petRepository;
|
@Mock private EmployeeRepository employeeRepository;
|
||||||
|
@Mock private EmployeeStoreRepository employeeStoreRepository;
|
||||||
@Mock
|
|
||||||
private ServiceRepository serviceRepository;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private StoreRepository storeRepository;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private UserRepository userRepository;
|
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private AppointmentService appointmentService;
|
private AppointmentService appointmentService;
|
||||||
@@ -64,10 +67,19 @@ class AppointmentServiceTest {
|
|||||||
private Service grooming;
|
private Service grooming;
|
||||||
private Service nailTrim;
|
private Service nailTrim;
|
||||||
private Pet pet;
|
private Pet pet;
|
||||||
|
private CustomerPet customerPet;
|
||||||
|
private Employee employee;
|
||||||
private LocalDate date;
|
private LocalDate date;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
|
setAuthentication(99L, User.Role.ADMIN);
|
||||||
|
User adminUser = new User();
|
||||||
|
adminUser.setId(99L);
|
||||||
|
adminUser.setRole(User.Role.ADMIN);
|
||||||
|
adminUser.setActive(true);
|
||||||
|
when(userRepository.findById(99L)).thenReturn(Optional.of(adminUser));
|
||||||
|
|
||||||
customer = new Customer();
|
customer = new Customer();
|
||||||
customer.setCustomerId(1L);
|
customer.setCustomerId(1L);
|
||||||
customer.setFirstName("Pat");
|
customer.setFirstName("Pat");
|
||||||
@@ -91,8 +103,25 @@ class AppointmentServiceTest {
|
|||||||
pet.setPetId(1L);
|
pet.setPetId(1L);
|
||||||
pet.setPetName("Milo");
|
pet.setPetName("Milo");
|
||||||
|
|
||||||
date = LocalDate.now().plusDays(1);
|
customerPet = new CustomerPet();
|
||||||
|
customerPet.setCustomerPetId(11L);
|
||||||
|
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);
|
||||||
|
staffUser.setActive(true);
|
||||||
|
when(userRepository.findById(7L)).thenReturn(Optional.of(staffUser));
|
||||||
|
|
||||||
|
date = LocalDate.now().plusDays(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
@@ -101,11 +130,28 @@ class AppointmentServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void checkAvailabilityAllowsDifferentServicesAtSameTime() {
|
void checkAvailabilityAllowsConcurrentAppointmentsIfAnotherEmployeeFree() {
|
||||||
|
Employee employee2 = new Employee();
|
||||||
|
employee2.setEmployeeId(8L);
|
||||||
|
employee2.setUserId(8L);
|
||||||
|
employee2.setFirstName("Bob");
|
||||||
|
employee2.setIsActive(true);
|
||||||
|
|
||||||
|
User staffUser2 = new User();
|
||||||
|
staffUser2.setId(8L);
|
||||||
|
staffUser2.setRole(User.Role.STAFF);
|
||||||
|
staffUser2.setActive(true);
|
||||||
|
when(userRepository.findById(8L)).thenReturn(Optional.of(staffUser2));
|
||||||
|
|
||||||
Appointment existing = appointment(1L, date, LocalTime.of(10, 0), grooming, store);
|
Appointment existing = appointment(1L, date, LocalTime.of(10, 0), grooming, store);
|
||||||
when(storeRepository.findById(1L)).thenReturn(Optional.of(store));
|
when(storeRepository.findById(1L)).thenReturn(Optional.of(store));
|
||||||
when(serviceRepository.findById(2L)).thenReturn(Optional.of(nailTrim));
|
when(serviceRepository.findById(2L)).thenReturn(Optional.of(nailTrim));
|
||||||
when(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of(existing));
|
|
||||||
|
when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L))
|
||||||
|
.thenReturn(List.of(new EmployeeStore(employee, store), new EmployeeStore(employee2, store)));
|
||||||
|
|
||||||
|
when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of(existing));
|
||||||
|
when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(8L, date)).thenReturn(List.of());
|
||||||
|
|
||||||
List<String> slots = appointmentService.checkAvailability(1L, 2L, date);
|
List<String> slots = appointmentService.checkAvailability(1L, 2L, date);
|
||||||
|
|
||||||
@@ -113,53 +159,26 @@ class AppointmentServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void checkAvailabilityBlocksSameServiceAtSameTime() {
|
void createAppointmentRejectsCustomerPetOwnedByDifferentCustomerForStaff() {
|
||||||
Appointment existing = appointment(1L, date, LocalTime.of(10, 0), grooming, store);
|
setAuthentication(7L, User.Role.STAFF);
|
||||||
when(storeRepository.findById(1L)).thenReturn(Optional.of(store));
|
when(employeeRepository.findByUserId(7L)).thenReturn(Optional.of(employee));
|
||||||
when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming));
|
when(employeeStoreRepository.findByEmployeeEmployeeId(7L)).thenReturn(Optional.of(new EmployeeStore(employee, store)));
|
||||||
when(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of(existing));
|
|
||||||
|
|
||||||
List<String> slots = appointmentService.checkAvailability(1L, 1L, date);
|
Customer otherCustomer = new Customer();
|
||||||
|
otherCustomer.setCustomerId(2L);
|
||||||
|
|
||||||
assertFalse(slots.contains("10:00"));
|
CustomerPet otherCustomerPet = new CustomerPet();
|
||||||
}
|
otherCustomerPet.setCustomerPetId(22L);
|
||||||
|
otherCustomerPet.setCustomer(otherCustomer);
|
||||||
|
otherCustomerPet.setPetName("Not Yours");
|
||||||
|
|
||||||
@Test
|
|
||||||
void cancelledAppointmentsDoNotBlockAvailability() {
|
|
||||||
when(storeRepository.findById(1L)).thenReturn(Optional.of(store));
|
|
||||||
when(serviceRepository.findById(1L)).thenReturn(Optional.of(grooming));
|
|
||||||
when(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of());
|
|
||||||
|
|
||||||
List<String> slots = appointmentService.checkAvailability(1L, 1L, date);
|
|
||||||
|
|
||||||
assertTrue(slots.contains("10:00"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void updateAppointmentDoesNotConflictWithItself() {
|
|
||||||
Appointment existing = appointment(1L, date, LocalTime.of(10, 0), grooming, store);
|
|
||||||
User user = new User();
|
|
||||||
user.setId(10L);
|
|
||||||
user.setUsername("pat");
|
|
||||||
user.setRole(User.Role.CUSTOMER);
|
|
||||||
user.setTokenVersion(0);
|
|
||||||
when(userRepository.findById(10L)).thenReturn(Optional.of(user));
|
|
||||||
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(
|
|
||||||
new UsernamePasswordAuthenticationToken(
|
|
||||||
new com.petshop.backend.security.AppPrincipal(10L, "pat", User.Role.CUSTOMER, 0),
|
|
||||||
"n/a",
|
|
||||||
List.of(new SimpleGrantedAuthority("ROLE_CUSTOMER"))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
when(appointmentRepository.findById(1L)).thenReturn(Optional.of(existing));
|
|
||||||
when(customerRepository.findById(1L)).thenReturn(Optional.of(customer));
|
when(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(petRepository.findById(1L)).thenReturn(Optional.of(pet));
|
when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L))
|
||||||
when(appointmentRepository.findByStoreAndDate(1L, date)).thenReturn(List.of(existing));
|
.thenReturn(List.of(new EmployeeStore(employee, store)));
|
||||||
when(appointmentRepository.save(any(Appointment.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of());
|
||||||
|
when(customerPetRepository.findById(22L)).thenReturn(Optional.of(otherCustomerPet));
|
||||||
|
|
||||||
var request = new com.petshop.backend.dto.appointment.AppointmentRequest();
|
var request = new com.petshop.backend.dto.appointment.AppointmentRequest();
|
||||||
request.setCustomerId(1L);
|
request.setCustomerId(1L);
|
||||||
@@ -168,12 +187,122 @@ class AppointmentServiceTest {
|
|||||||
request.setAppointmentDate(date);
|
request.setAppointmentDate(date);
|
||||||
request.setAppointmentTime(LocalTime.of(10, 0));
|
request.setAppointmentTime(LocalTime.of(10, 0));
|
||||||
request.setAppointmentStatus("Booked");
|
request.setAppointmentStatus("Booked");
|
||||||
request.setPetIds(List.of(1L));
|
request.setCustomerPetIds(List.of(22L));
|
||||||
|
|
||||||
var response = appointmentService.updateAppointment(1L, request);
|
assertThrows(IllegalArgumentException.class, () -> appointmentService.createAppointment(request));
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals(1L, response.getAppointmentId());
|
@Test
|
||||||
assertEquals("Booked", response.getAppointmentStatus());
|
void createAppointmentRejectsCustomerPetOwnedByDifferentCustomer() {
|
||||||
|
setAuthentication(99L, User.Role.ADMIN);
|
||||||
|
|
||||||
|
Customer otherCustomer = new Customer();
|
||||||
|
otherCustomer.setCustomerId(2L);
|
||||||
|
|
||||||
|
CustomerPet otherCustomerPet = new CustomerPet();
|
||||||
|
otherCustomerPet.setCustomerPetId(22L);
|
||||||
|
otherCustomerPet.setCustomer(otherCustomer);
|
||||||
|
otherCustomerPet.setPetName("Not Yours");
|
||||||
|
|
||||||
|
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.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of());
|
||||||
|
when(customerPetRepository.findById(22L)).thenReturn(Optional.of(otherCustomerPet));
|
||||||
|
|
||||||
|
var request = new com.petshop.backend.dto.appointment.AppointmentRequest();
|
||||||
|
request.setCustomerId(1L);
|
||||||
|
request.setStoreId(1L);
|
||||||
|
request.setServiceId(1L);
|
||||||
|
request.setAppointmentDate(date);
|
||||||
|
request.setAppointmentTime(LocalTime.of(10, 0));
|
||||||
|
request.setAppointmentStatus("Booked");
|
||||||
|
request.setCustomerPetIds(List.of(22L));
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> appointmentService.createAppointment(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createAppointmentRejectsAdminEmployeeSelection() {
|
||||||
|
setAuthentication(99L, User.Role.ADMIN);
|
||||||
|
User adminUser = new User();
|
||||||
|
adminUser.setId(99L);
|
||||||
|
adminUser.setRole(User.Role.ADMIN);
|
||||||
|
adminUser.setActive(true);
|
||||||
|
when(userRepository.findById(99L)).thenReturn(Optional.of(adminUser));
|
||||||
|
|
||||||
|
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);
|
||||||
|
adminLinkedUser.setActive(true);
|
||||||
|
|
||||||
|
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.findByEmployeeEmployeeIdAndAppointmentDate(7L, 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createAppointmentRejectsInactiveStaffUserSelection() {
|
||||||
|
setAuthentication(99L, User.Role.ADMIN);
|
||||||
|
User adminUser = new User();
|
||||||
|
adminUser.setId(99L);
|
||||||
|
adminUser.setRole(User.Role.ADMIN);
|
||||||
|
adminUser.setActive(true);
|
||||||
|
when(userRepository.findById(99L)).thenReturn(Optional.of(adminUser));
|
||||||
|
|
||||||
|
User inactiveStaffUser = new User();
|
||||||
|
inactiveStaffUser.setId(7L);
|
||||||
|
inactiveStaffUser.setRole(User.Role.STAFF);
|
||||||
|
inactiveStaffUser.setActive(false);
|
||||||
|
when(userRepository.findById(7L)).thenReturn(Optional.of(inactiveStaffUser));
|
||||||
|
|
||||||
|
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(7L)).thenReturn(Optional.of(employee));
|
||||||
|
when(appointmentRepository.findByEmployeeEmployeeIdAndAppointmentDate(7L, date)).thenReturn(List.of());
|
||||||
|
when(customerPetRepository.findById(11L)).thenReturn(Optional.of(customerPet));
|
||||||
|
when(employeeStoreRepository.findActiveByStoreStoreIdOrderByEmployeeEmployeeIdAsc(1L))
|
||||||
|
.thenReturn(List.of(new EmployeeStore(employee, store)));
|
||||||
|
|
||||||
|
var request = new com.petshop.backend.dto.appointment.AppointmentRequest();
|
||||||
|
request.setCustomerId(1L);
|
||||||
|
request.setStoreId(1L);
|
||||||
|
request.setServiceId(1L);
|
||||||
|
request.setEmployeeId(7L);
|
||||||
|
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) {
|
||||||
@@ -184,8 +313,20 @@ 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());
|
||||||
|
appointment.setCustomerPets(Set.of());
|
||||||
return appointment;
|
return appointment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setAuthentication(Long userId, User.Role role) {
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
|
new UsernamePasswordAuthenticationToken(
|
||||||
|
new AppPrincipal(userId, "user", role, 0),
|
||||||
|
"n/a",
|
||||||
|
List.of(new SimpleGrantedAuthority("ROLE_" + role.name()))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package com.petshop.backend.service;
|
||||||
|
|
||||||
|
import com.petshop.backend.entity.Adoption;
|
||||||
|
import com.petshop.backend.entity.Customer;
|
||||||
|
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.PetRepository;
|
||||||
|
import com.petshop.backend.security.AppPrincipal;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.data.domain.PageImpl;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
|
||||||
|
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.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class PetServiceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PetRepository petRepository;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private AdoptionRepository adoptionRepository;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CatalogImageStorageService catalogImageStorageService;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private PetService petService;
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAllPetsAnonymousReturnsOnlyPublicPets() {
|
||||||
|
Pageable pageable = PageRequest.of(0, 10);
|
||||||
|
Pet availablePet = pet(1L, "Buddy", "Available");
|
||||||
|
when(petRepository.searchPublicPets(null, null, pageable)).thenReturn(new PageImpl<>(List.of(availablePet), pageable, 1));
|
||||||
|
|
||||||
|
var result = petService.getAllPets(null, null, null, pageable);
|
||||||
|
|
||||||
|
assertEquals(1, result.getTotalElements());
|
||||||
|
assertEquals("Buddy", result.getContent().get(0).getPetName());
|
||||||
|
verify(petRepository).searchPublicPets(null, null, pageable);
|
||||||
|
verify(petRepository, never()).searchPets(null, null, null, pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAllPetsAnonymousWithAdoptedStatusReturnsEmptyPage() {
|
||||||
|
Pageable pageable = PageRequest.of(0, 10);
|
||||||
|
|
||||||
|
var result = petService.getAllPets(null, null, "Adopted", pageable);
|
||||||
|
|
||||||
|
assertEquals(0, result.getTotalElements());
|
||||||
|
verify(petRepository, never()).searchPublicPets(null, null, pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAllPetsCustomerReturnsVisiblePetsOnly() {
|
||||||
|
Pageable pageable = PageRequest.of(0, 10);
|
||||||
|
setAuthentication(25L, User.Role.CUSTOMER);
|
||||||
|
Pet availablePet = pet(1L, "Buddy", "Available");
|
||||||
|
Pet adoptedPet = pet(2L, "Luna", "Adopted");
|
||||||
|
when(petRepository.searchCustomerVisiblePets(25L, null, null, null, pageable))
|
||||||
|
.thenReturn(new PageImpl<>(List.of(availablePet, adoptedPet), pageable, 2));
|
||||||
|
|
||||||
|
var result = petService.getAllPets(null, null, null, pageable);
|
||||||
|
|
||||||
|
assertEquals(2, result.getTotalElements());
|
||||||
|
verify(petRepository).searchCustomerVisiblePets(25L, null, null, null, pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAllPetsAdminReturnsAllPets() {
|
||||||
|
Pageable pageable = PageRequest.of(0, 10);
|
||||||
|
setAuthentication(99L, User.Role.ADMIN);
|
||||||
|
Pet availablePet = pet(1L, "Buddy", "Available");
|
||||||
|
Pet adoptedPet = pet(2L, "Luna", "Adopted");
|
||||||
|
when(petRepository.searchPets(null, null, null, pageable))
|
||||||
|
.thenReturn(new PageImpl<>(List.of(availablePet, adoptedPet), pageable, 2));
|
||||||
|
|
||||||
|
var result = petService.getAllPets(null, null, null, pageable);
|
||||||
|
|
||||||
|
assertEquals(2, result.getTotalElements());
|
||||||
|
verify(petRepository).searchPets(null, null, null, pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getPetByIdHidesAdoptedPetFromUnrelatedCustomer() {
|
||||||
|
setAuthentication(50L, User.Role.CUSTOMER);
|
||||||
|
Pet adoptedPet = pet(2L, "Luna", "Adopted");
|
||||||
|
when(petRepository.findById(2L)).thenReturn(Optional.of(adoptedPet));
|
||||||
|
when(adoptionRepository.findFirstByPet_IdAndAdoptionStatusOrderByAdoptionDateDesc(2L, "Completed"))
|
||||||
|
.thenReturn(Optional.of(adoption(2L, 25L)));
|
||||||
|
|
||||||
|
assertThrows(ResourceNotFoundException.class, () -> petService.getPetById(2L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getPetByIdAllowsOwnerToSeeAdoptedPet() {
|
||||||
|
setAuthentication(25L, User.Role.CUSTOMER);
|
||||||
|
Pet adoptedPet = pet(2L, "Luna", "Adopted");
|
||||||
|
when(petRepository.findById(2L)).thenReturn(Optional.of(adoptedPet));
|
||||||
|
when(adoptionRepository.findFirstByPet_IdAndAdoptionStatusOrderByAdoptionDateDesc(2L, "Completed"))
|
||||||
|
.thenReturn(Optional.of(adoption(2L, 25L)));
|
||||||
|
|
||||||
|
var result = petService.getPetById(2L);
|
||||||
|
|
||||||
|
assertEquals(2L, result.getPetId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAuthentication(Long userId, User.Role role) {
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
|
new UsernamePasswordAuthenticationToken(
|
||||||
|
new AppPrincipal(userId, "user", role, 0),
|
||||||
|
"n/a",
|
||||||
|
List.of(new SimpleGrantedAuthority("ROLE_" + role.name()))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pet pet(Long id, String name, String status) {
|
||||||
|
Pet pet = new Pet();
|
||||||
|
pet.setPetId(id);
|
||||||
|
pet.setPetName(name);
|
||||||
|
pet.setPetSpecies("Cat");
|
||||||
|
pet.setPetBreed("Mixed");
|
||||||
|
pet.setPetAge(2);
|
||||||
|
pet.setPetStatus(status);
|
||||||
|
pet.setPetPrice(java.math.BigDecimal.TEN);
|
||||||
|
return pet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Adoption adoption(Long petId, Long userId) {
|
||||||
|
Adoption adoption = new Adoption();
|
||||||
|
Pet pet = new Pet();
|
||||||
|
pet.setPetId(petId);
|
||||||
|
adoption.setPet(pet);
|
||||||
|
Customer customer = new Customer();
|
||||||
|
customer.setCustomerId(1L);
|
||||||
|
customer.setUserId(userId);
|
||||||
|
adoption.setCustomer(customer);
|
||||||
|
adoption.setAdoptionStatus("Completed");
|
||||||
|
return adoption;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,16 +15,19 @@ 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;
|
||||||
private SimpleStringProperty appointmentStatus;
|
private SimpleStringProperty appointmentStatus;
|
||||||
|
|
||||||
// Constructor
|
|
||||||
public AppointmentDTO(int appointmentId,
|
public AppointmentDTO(int appointmentId,
|
||||||
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,12 +39,13 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters
|
|
||||||
public int getAppointmentId() { return appointmentId.get(); }
|
public int getAppointmentId() { return appointmentId.get(); }
|
||||||
|
|
||||||
public int getCustomerId() { return customerId.get(); }
|
public int getCustomerId() { return customerId.get(); }
|
||||||
@@ -52,8 +56,10 @@ 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(); }
|
||||||
public String getAppointmentStatus() { return appointmentStatus.get(); }
|
public String getAppointmentStatus() { return appointmentStatus.get(); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import java.util.List;
|
|||||||
|
|
||||||
public class AppointmentRequest {
|
public class AppointmentRequest {
|
||||||
private List<Long> petIds;
|
private List<Long> petIds;
|
||||||
|
private List<Long> customerPetIds;
|
||||||
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;
|
||||||
@@ -24,6 +26,14 @@ public class AppointmentRequest {
|
|||||||
this.petIds = petIds;
|
this.petIds = petIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Long> getCustomerPetIds() {
|
||||||
|
return customerPetIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomerPetIds(List<Long> customerPetIds) {
|
||||||
|
this.customerPetIds = customerPetIds;
|
||||||
|
}
|
||||||
|
|
||||||
public Long getCustomerId() {
|
public Long getCustomerId() {
|
||||||
return customerId;
|
return customerId;
|
||||||
}
|
}
|
||||||
@@ -48,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ public class AppointmentResponse {
|
|||||||
private Long serviceId;
|
private Long serviceId;
|
||||||
private java.util.List<String> petNames;
|
private java.util.List<String> petNames;
|
||||||
private java.util.List<Long> petIds;
|
private java.util.List<Long> petIds;
|
||||||
|
private java.util.List<String> customerPetNames;
|
||||||
|
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;
|
||||||
@@ -84,6 +88,22 @@ public class AppointmentResponse {
|
|||||||
this.petIds = petIds;
|
this.petIds = petIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public java.util.List<String> getCustomerPetNames() {
|
||||||
|
return customerPetNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomerPetNames(java.util.List<String> customerPetNames) {
|
||||||
|
this.customerPetNames = customerPetNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public java.util.List<Long> getCustomerPetIds() {
|
||||||
|
return customerPetIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomerPetIds(java.util.List<Long> customerPetIds) {
|
||||||
|
this.customerPetIds = customerPetIds;
|
||||||
|
}
|
||||||
|
|
||||||
public String getServiceName() {
|
public String getServiceName() {
|
||||||
return serviceName;
|
return serviceName;
|
||||||
}
|
}
|
||||||
@@ -92,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,14 @@ public class DropdownApi {
|
|||||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropdownOption> getAppointmentCustomers() throws Exception {
|
||||||
|
String response = apiClient.getRawResponse("/api/v1/dropdowns/appointment-customers");
|
||||||
|
if (response == null || response.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Empty response from appointment customers endpoint");
|
||||||
|
}
|
||||||
|
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropdownOption> getPets() throws Exception {
|
public List<DropdownOption> getPets() throws Exception {
|
||||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/pets");
|
String response = apiClient.getRawResponse("/api/v1/dropdowns/pets");
|
||||||
if (response == null || response.isEmpty()) {
|
if (response == null || response.isEmpty()) {
|
||||||
@@ -82,6 +90,22 @@ public class DropdownApi {
|
|||||||
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropdownOption> getAdoptionPets() throws Exception {
|
||||||
|
String response = apiClient.getRawResponse("/api/v1/dropdowns/adoption-pets");
|
||||||
|
if (response == null || response.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Empty response from adoption pets endpoint");
|
||||||
|
}
|
||||||
|
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropdownOption> getCustomerPets(Long customerId) throws Exception {
|
||||||
|
String response = apiClient.getRawResponse("/api/v1/dropdowns/customers/" + customerId + "/pets");
|
||||||
|
if (response == null || response.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Empty response from customer pets endpoint");
|
||||||
|
}
|
||||||
|
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropdownOption> getStores() throws Exception {
|
public List<DropdownOption> getStores() throws Exception {
|
||||||
String response = apiClient.getRawResponse("/api/v1/dropdowns/stores");
|
String response = apiClient.getRawResponse("/api/v1/dropdowns/stores");
|
||||||
if (response == null || response.isEmpty()) {
|
if (response == null || response.isEmpty()) {
|
||||||
@@ -89,4 +113,20 @@ 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>>() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropdownOption> getEmployees() throws Exception {
|
||||||
|
String response = apiClient.getRawResponse("/api/v1/dropdowns/employees");
|
||||||
|
if (response == null || response.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Empty response from all employees endpoint");
|
||||||
|
}
|
||||||
|
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -65,12 +68,13 @@ public class AdoptionController {
|
|||||||
void initialize() {
|
void initialize() {
|
||||||
btnEdit.setDisable(true);
|
btnEdit.setDisable(true);
|
||||||
btnDelete.setDisable(true);
|
btnDelete.setDisable(true);
|
||||||
//Enable multiple selection
|
|
||||||
tvAdoptions.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
tvAdoptions.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
||||||
|
|
||||||
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"));
|
||||||
@@ -87,7 +91,6 @@ public class AdoptionController {
|
|||||||
displayFilteredAdoptions(newValue);
|
displayFilteredAdoptions(newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
//EventListener for DELETE key
|
|
||||||
tvAdoptions.setOnKeyPressed(event -> {
|
tvAdoptions.setOnKeyPressed(event -> {
|
||||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||||
if (tvAdoptions.getSelectionModel().getSelectedItem() != null) {
|
if (tvAdoptions.getSelectionModel().getSelectedItem() != null) {
|
||||||
@@ -105,11 +108,10 @@ public class AdoptionController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void btnDeleteClicked(ActionEvent event) {
|
void btnDeleteClicked(ActionEvent event) {
|
||||||
//get selected adoptions
|
|
||||||
var selectedAdoptions = tvAdoptions.getSelectionModel().getSelectedItems();
|
var selectedAdoptions = tvAdoptions.getSelectionModel().getSelectedItems();
|
||||||
if (selectedAdoptions.isEmpty()) return;
|
if (selectedAdoptions.isEmpty()) return;
|
||||||
|
|
||||||
//ask user to confirm
|
|
||||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||||
question.setHeaderText("Please confirm delete");
|
question.setHeaderText("Please confirm delete");
|
||||||
String message = selectedAdoptions.size() == 1
|
String message = selectedAdoptions.size() == 1
|
||||||
@@ -119,7 +121,6 @@ public class AdoptionController {
|
|||||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||||
Optional<ButtonType> result = question.showAndWait();
|
Optional<ButtonType> result = question.showAndWait();
|
||||||
|
|
||||||
//if confirmed, start deletion
|
|
||||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||||
List<Long> ids = selectedAdoptions.stream()
|
List<Long> ids = selectedAdoptions.stream()
|
||||||
.map(a -> (long) a.getAdoptionId())
|
.map(a -> (long) a.getAdoptionId())
|
||||||
@@ -142,7 +143,6 @@ public class AdoptionController {
|
|||||||
alert.showAndWait();
|
alert.showAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
//refresh display and reset inputs
|
|
||||||
displayAdoptions();
|
displayAdoptions();
|
||||||
btnDelete.setDisable(true);
|
btnDelete.setDisable(true);
|
||||||
btnEdit.setDisable(true);
|
btnEdit.setDisable(true);
|
||||||
@@ -252,8 +252,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()
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -46,7 +47,7 @@ public class AppointmentController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize(){
|
public void initialize(){
|
||||||
//Enable multiple selection
|
|
||||||
tvAppointments.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
tvAppointments.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
|
||||||
|
|
||||||
colAppointmentId.setCellValueFactory(new PropertyValueFactory<>("appointmentId"));
|
colAppointmentId.setCellValueFactory(new PropertyValueFactory<>("appointmentId"));
|
||||||
@@ -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);
|
||||||
@@ -64,7 +66,6 @@ public class AppointmentController {
|
|||||||
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
|
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
|
||||||
}
|
}
|
||||||
|
|
||||||
//EventListener for DELETE key
|
|
||||||
tvAppointments.setOnKeyPressed(event -> {
|
tvAppointments.setOnKeyPressed(event -> {
|
||||||
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
|
||||||
if (tvAppointments.getSelectionModel().getSelectedItem() != null) {
|
if (tvAppointments.getSelectionModel().getSelectedItem() != null) {
|
||||||
@@ -146,11 +147,10 @@ public class AppointmentController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void btnDeleteClicked(ActionEvent event){
|
void btnDeleteClicked(ActionEvent event){
|
||||||
//get selected appointments
|
|
||||||
var selectedAppointments = tvAppointments.getSelectionModel().getSelectedItems();
|
var selectedAppointments = tvAppointments.getSelectionModel().getSelectedItems();
|
||||||
if (selectedAppointments.isEmpty()) return;
|
if (selectedAppointments.isEmpty()) return;
|
||||||
|
|
||||||
//ask user to confirm
|
|
||||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||||
question.setHeaderText("Please confirm delete");
|
question.setHeaderText("Please confirm delete");
|
||||||
String message = selectedAppointments.size() == 1
|
String message = selectedAppointments.size() == 1
|
||||||
@@ -160,7 +160,6 @@ public class AppointmentController {
|
|||||||
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
|
||||||
java.util.Optional<ButtonType> result = question.showAndWait();
|
java.util.Optional<ButtonType> result = question.showAndWait();
|
||||||
|
|
||||||
//if confirmed, start deletion
|
|
||||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||||
List<Long> ids = selectedAppointments.stream()
|
List<Long> ids = selectedAppointments.stream()
|
||||||
.map(a -> (long) a.getAppointmentId())
|
.map(a -> (long) a.getAppointmentId())
|
||||||
@@ -183,7 +182,6 @@ public class AppointmentController {
|
|||||||
alert.showAndWait();
|
alert.showAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
//refresh display
|
|
||||||
loadAppointments();
|
loadAppointments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,15 +231,22 @@ public class AppointmentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AppointmentDTO mapToAppointmentDTO(AppointmentResponse response) {
|
private AppointmentDTO mapToAppointmentDTO(AppointmentResponse response) {
|
||||||
Long petId = response.getPetIds() != null && !response.getPetIds().isEmpty() ? response.getPetIds().get(0) : null;
|
Long petId = response.getCustomerPetIds() != null && !response.getCustomerPetIds().isEmpty()
|
||||||
|
? response.getCustomerPetIds().get(0)
|
||||||
|
: response.getPetIds() != null && !response.getPetIds().isEmpty() ? response.getPetIds().get(0) : null;
|
||||||
|
String petName = response.getCustomerPetNames() != null && !response.getCustomerPetNames().isEmpty()
|
||||||
|
? String.join(", ", response.getCustomerPetNames())
|
||||||
|
: String.join(", ", response.getPetNames());
|
||||||
return new AppointmentDTO(
|
return new AppointmentDTO(
|
||||||
response.getAppointmentId().intValue(),
|
response.getAppointmentId().intValue(),
|
||||||
response.getCustomerId() != null ? response.getCustomerId().intValue() : 0,
|
response.getCustomerId() != null ? response.getCustomerId().intValue() : 0,
|
||||||
response.getCustomerName(),
|
response.getCustomerName(),
|
||||||
petId != null ? petId.intValue() : 0,
|
petId != null ? petId.intValue() : 0,
|
||||||
String.join(", ", response.getPetNames()),
|
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()
|
||||||
|
|||||||
@@ -11,21 +11,23 @@ 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;
|
||||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||||
import org.example.petshopdesktop.api.endpoints.AdoptionApi;
|
import org.example.petshopdesktop.api.endpoints.AdoptionApi;
|
||||||
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
||||||
|
import org.example.petshopdesktop.auth.UserSession;
|
||||||
import org.example.petshopdesktop.models.Adoption;
|
import org.example.petshopdesktop.models.Adoption;
|
||||||
import org.example.petshopdesktop.util.ActivityLogger;
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class AdoptionDialogController {
|
public class AdoptionDialogController {
|
||||||
|
|
||||||
//FXML elements
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button btnCancel;
|
private Button btnCancel;
|
||||||
|
|
||||||
@@ -38,6 +40,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;
|
||||||
|
|
||||||
@@ -50,10 +55,9 @@ public class AdoptionDialogController {
|
|||||||
@FXML
|
@FXML
|
||||||
private Label lblMode;
|
private Label lblMode;
|
||||||
|
|
||||||
//Stores if the dialog view is in add/edit mode
|
|
||||||
private String mode = null;
|
private String mode = null;
|
||||||
|
private Adoption selectedAdoption = null;
|
||||||
|
|
||||||
//Adoption statuses
|
|
||||||
private ObservableList<String> statusList = FXCollections.observableArrayList(
|
private ObservableList<String> statusList = FXCollections.observableArrayList(
|
||||||
"Pending", "Completed", "Cancelled"
|
"Pending", "Completed", "Cancelled"
|
||||||
);
|
);
|
||||||
@@ -62,14 +66,16 @@ public class AdoptionDialogController {
|
|||||||
void initialize() {
|
void initialize() {
|
||||||
|
|
||||||
cbAdoptionStatus.setItems(statusList);
|
cbAdoptionStatus.setItems(statusList);
|
||||||
|
cbEmployee.setPromptText("Select an employee");
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
List<DropdownOption> pets = DropdownApi.getInstance().getPets();
|
List<DropdownOption> pets = DropdownApi.getInstance().getAdoptionPets();
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if (pets != null) {
|
if (pets != null) {
|
||||||
ObservableList<DropdownOption> petsObs = FXCollections.observableArrayList(pets);
|
ObservableList<DropdownOption> petsObs = FXCollections.observableArrayList(pets);
|
||||||
cbPet.setItems(petsObs);
|
cbPet.setItems(petsObs);
|
||||||
|
applySelectedPet();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -83,6 +89,46 @@ public class AdoptionDialogController {
|
|||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
Long storeId = UserSession.getInstance().getStoreId();
|
||||||
|
List<DropdownOption> employees;
|
||||||
|
if (storeId != null && storeId > 0) {
|
||||||
|
employees = DropdownApi.getInstance().getStoreEmployees(storeId);
|
||||||
|
} else {
|
||||||
|
employees = DropdownApi.getInstance().getEmployees();
|
||||||
|
}
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
cbEmployee.setItems(FXCollections.observableArrayList(employees));
|
||||||
|
applySelectedEmployee();
|
||||||
|
});
|
||||||
|
} 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();
|
||||||
@@ -90,6 +136,7 @@ public class AdoptionDialogController {
|
|||||||
if (customers != null) {
|
if (customers != null) {
|
||||||
ObservableList<DropdownOption> customersObs = FXCollections.observableArrayList(customers);
|
ObservableList<DropdownOption> customersObs = FXCollections.observableArrayList(customers);
|
||||||
cbCustomer.setItems(customersObs);
|
cbCustomer.setItems(customersObs);
|
||||||
|
applySelectedCustomer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -129,6 +176,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 +193,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());
|
||||||
|
|
||||||
@@ -179,7 +231,6 @@ public class AdoptionDialogController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void closeStage(MouseEvent mouseEvent) {
|
private void closeStage(MouseEvent mouseEvent) {
|
||||||
Node node = (Node) mouseEvent.getSource();
|
Node node = (Node) mouseEvent.getSource();
|
||||||
Stage stage = (Stage) node.getScene().getWindow();
|
Stage stage = (Stage) node.getScene().getWindow();
|
||||||
@@ -188,21 +239,11 @@ public class AdoptionDialogController {
|
|||||||
|
|
||||||
public void displayAdoptionDetails(Adoption adoption) {
|
public void displayAdoptionDetails(Adoption adoption) {
|
||||||
if (adoption != null) {
|
if (adoption != null) {
|
||||||
|
selectedAdoption = adoption;
|
||||||
lblAdoptionId.setText("ID: " + adoption.getAdoptionId());
|
lblAdoptionId.setText("ID: " + adoption.getAdoptionId());
|
||||||
|
applySelectedPet();
|
||||||
for (DropdownOption pet : cbPet.getItems()) {
|
applySelectedCustomer();
|
||||||
if (pet.getLabel().equals(adoption.getPetName())) {
|
applySelectedEmployee();
|
||||||
cbPet.getSelectionModel().select(pet);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (DropdownOption customer : cbCustomer.getItems()) {
|
|
||||||
if (customer.getLabel().equals(adoption.getCustomerName())) {
|
|
||||||
cbCustomer.getSelectionModel().select(customer);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adoption.getAdoptionDate() != null && !adoption.getAdoptionDate().isEmpty()) {
|
if (adoption.getAdoptionDate() != null && !adoption.getAdoptionDate().isEmpty()) {
|
||||||
try {
|
try {
|
||||||
@@ -229,4 +270,46 @@ public class AdoptionDialogController {
|
|||||||
lblMode.setText(mode + " Adoption");
|
lblMode.setText(mode + " Adoption");
|
||||||
lblAdoptionId.setVisible(mode.equals("Edit"));
|
lblAdoptionId.setVisible(mode.equals("Edit"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applySelectedPet() {
|
||||||
|
if (selectedAdoption == null || selectedAdoption.getPetId() <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DropdownOption selected = findOptionById(cbPet.getItems(), (long) selectedAdoption.getPetId());
|
||||||
|
if (selected != null && !Objects.equals(cbPet.getValue(), selected)) {
|
||||||
|
cbPet.setValue(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySelectedCustomer() {
|
||||||
|
if (selectedAdoption == null || selectedAdoption.getCustomerId() <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DropdownOption selected = findOptionById(cbCustomer.getItems(), (long) selectedAdoption.getCustomerId());
|
||||||
|
if (selected != null && !Objects.equals(cbCustomer.getValue(), selected)) {
|
||||||
|
cbCustomer.setValue(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySelectedEmployee() {
|
||||||
|
if (selectedAdoption == null || selectedAdoption.getEmployeeId() <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DropdownOption selected = findOptionById(cbEmployee.getItems(), (long) selectedAdoption.getEmployeeId());
|
||||||
|
if (selected != null && !Objects.equals(cbEmployee.getValue(), selected)) {
|
||||||
|
cbEmployee.setValue(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DropdownOption findOptionById(List<DropdownOption> options, Long id) {
|
||||||
|
if (id == null || options == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (DropdownOption option : options) {
|
||||||
|
if (option.getId() != null && option.getId().equals(id)) {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,21 +19,20 @@ import org.example.petshopdesktop.auth.UserSession;
|
|||||||
import org.example.petshopdesktop.util.ActivityLogger;
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
import java.util.Collections;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class AppointmentDialogController {
|
public class AppointmentDialogController {
|
||||||
|
|
||||||
// ============================
|
|
||||||
// FXML
|
|
||||||
// ============================
|
|
||||||
|
|
||||||
@FXML private Button btnCancel;
|
@FXML private Button btnCancel;
|
||||||
@FXML private Button btnSave;
|
@FXML private Button btnSave;
|
||||||
|
|
||||||
@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;
|
||||||
@@ -44,74 +43,38 @@ public class AppointmentDialogController {
|
|||||||
@FXML private Label lblAppointmentId;
|
@FXML private Label lblAppointmentId;
|
||||||
@FXML private Label lblMode;
|
@FXML private Label lblMode;
|
||||||
|
|
||||||
// ============================
|
private String mode = null;
|
||||||
// DATA
|
|
||||||
// ============================
|
|
||||||
|
|
||||||
private String mode = null; // Add | Edit
|
|
||||||
private AppointmentDTO selectedAppointment = null;
|
private AppointmentDTO selectedAppointment = null;
|
||||||
|
private Long pendingPetSelectionId = null;
|
||||||
|
|
||||||
private ObservableList<String> statusList =
|
private ObservableList<String> statusList =
|
||||||
FXCollections.observableArrayList(
|
FXCollections.observableArrayList(
|
||||||
"Booked", "Completed", "Cancelled"
|
"Booked", "Completed", "Cancelled", "Missed"
|
||||||
);
|
);
|
||||||
|
|
||||||
//
|
|
||||||
// MODE
|
|
||||||
//
|
|
||||||
|
|
||||||
public void setMode(String mode) {
|
public void setMode(String mode) {
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
lblMode.setText(mode + " Appointment");
|
lblMode.setText(mode + " Appointment");
|
||||||
lblAppointmentId.setVisible(!mode.equals("Add"));
|
lblAppointmentId.setVisible(!mode.equals("Add"));
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// INITIALIZE
|
|
||||||
//
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
|
||||||
new Thread(() -> {
|
|
||||||
try {
|
|
||||||
List<DropdownOption> services = DropdownApi.getInstance().getServices();
|
|
||||||
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
|
|
||||||
List<DropdownOption> pets = DropdownApi.getInstance().getPets();
|
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
if (services != null) {
|
|
||||||
cbService.setItems(FXCollections.observableArrayList(services));
|
|
||||||
}
|
|
||||||
if (customers != null) {
|
|
||||||
cbCustomer.setItems(FXCollections.observableArrayList(customers));
|
|
||||||
}
|
|
||||||
if (pets != null) {
|
|
||||||
cbPet.setItems(FXCollections.observableArrayList(pets));
|
|
||||||
}
|
|
||||||
syncSelectedAppointment();
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
ActivityLogger.getInstance().logException(
|
|
||||||
"AppointmentDialogController.initialize",
|
|
||||||
e,
|
|
||||||
"Loading combo box data for services, customers, and pets");
|
|
||||||
e.printStackTrace();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
|
|
||||||
cbAppointmentStatus.setItems(statusList);
|
cbAppointmentStatus.setItems(statusList);
|
||||||
|
cbPet.setDisable(true);
|
||||||
|
cbEmployee.setPromptText("Select an employee");
|
||||||
|
cbPet.setPromptText("Select a customer first");
|
||||||
|
cbCustomer.setPromptText("Select a customer");
|
||||||
|
cbService.setPromptText("Select a service");
|
||||||
|
dpAppointmentDate.setValue(LocalDate.now().plusDays(1));
|
||||||
|
cbAppointmentStatus.setValue("Booked");
|
||||||
|
|
||||||
// Hours 9 AM - 5 PM
|
|
||||||
for (int i = 9; i <= 17; i++) {
|
for (int i = 9; i <= 17; i++) {
|
||||||
cbHour.getItems().add(i);
|
cbHour.getItems().add(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
cbMinute.getItems().addAll(0, 15, 30, 45);
|
cbMinute.getItems().addAll(0, 15, 30, 45);
|
||||||
|
|
||||||
// Show dropdown labels
|
|
||||||
cbService.setCellFactory(param -> new ListCell<>() {
|
cbService.setCellFactory(param -> new ListCell<>() {
|
||||||
@Override
|
@Override
|
||||||
protected void updateItem(DropdownOption option, boolean empty) {
|
protected void updateItem(DropdownOption option, boolean empty) {
|
||||||
@@ -157,18 +120,48 @@ 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) -> {
|
||||||
|
Long customerId = newValue != null ? newValue.getId() : null;
|
||||||
|
cbPet.setValue(null);
|
||||||
|
cbPet.setItems(FXCollections.observableArrayList());
|
||||||
|
cbPet.setDisable(customerId == null);
|
||||||
|
if (customerId != null) {
|
||||||
|
cbPet.setPromptText("Loading customer pets...");
|
||||||
|
loadCustomerPets(customerId);
|
||||||
|
} else {
|
||||||
|
cbPet.setPromptText("Select a customer first");
|
||||||
|
pendingPetSelectionId = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
btnSave.setOnMouseClicked(this::buttonSaveClicked);
|
btnSave.setOnMouseClicked(this::buttonSaveClicked);
|
||||||
btnCancel.setOnMouseClicked(this::closeStage);
|
btnCancel.setOnMouseClicked(this::closeStage);
|
||||||
}
|
|
||||||
|
|
||||||
//
|
loadServices();
|
||||||
// DISPLAY FOR EDIT
|
loadAppointmentCustomers();
|
||||||
//
|
loadEmployees();
|
||||||
|
}
|
||||||
|
|
||||||
public void displayAppointmentDetails(AppointmentDTO appt) {
|
public void displayAppointmentDetails(AppointmentDTO appt) {
|
||||||
|
|
||||||
selectedAppointment = appt;
|
selectedAppointment = appt;
|
||||||
lblAppointmentId.setText("ID: " + appt.getAppointmentId());
|
lblAppointmentId.setText("ID: " + appt.getAppointmentId());
|
||||||
|
pendingPetSelectionId = appt.getPetId() > 0 ? (long) appt.getPetId() : null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
dpAppointmentDate.setValue(
|
dpAppointmentDate.setValue(
|
||||||
@@ -194,28 +187,17 @@ public class AppointmentDialogController {
|
|||||||
"Parsing appointment time");
|
"Parsing appointment time");
|
||||||
}
|
}
|
||||||
|
|
||||||
cbService.getItems().forEach(s -> {
|
applySelectedService();
|
||||||
if (s.getId() != null && s.getId().longValue() == appt.getServiceId()) cbService.setValue(s);
|
applySelectedCustomer();
|
||||||
});
|
applySelectedEmployee();
|
||||||
|
|
||||||
cbCustomer.getItems().forEach(c -> {
|
|
||||||
if (c.getId() != null && c.getId().longValue() == appt.getCustomerId()) cbCustomer.setValue(c);
|
|
||||||
});
|
|
||||||
|
|
||||||
cbPet.getItems().forEach(p -> {
|
|
||||||
if (p.getId() != null && p.getId().longValue() == appt.getPetId()) cbPet.setValue(p);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// SAVE
|
|
||||||
//
|
|
||||||
|
|
||||||
private void buttonSaveClicked(MouseEvent e) {
|
private void buttonSaveClicked(MouseEvent e) {
|
||||||
|
|
||||||
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 ||
|
||||||
@@ -233,10 +215,11 @@ public class AppointmentDialogController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AppointmentRequest request = new AppointmentRequest();
|
AppointmentRequest request = new AppointmentRequest();
|
||||||
request.setPetIds(Collections.singletonList(cbPet.getValue().getId()));
|
request.setCustomerPetIds(Collections.singletonList(cbPet.getValue().getId()));
|
||||||
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());
|
||||||
@@ -267,10 +250,6 @@ public class AppointmentDialogController {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// UTIL
|
|
||||||
//
|
|
||||||
|
|
||||||
private void closeStage(MouseEvent e) {
|
private void closeStage(MouseEvent e) {
|
||||||
Stage stage = (Stage) ((Node) e.getSource()).getScene().getWindow();
|
Stage stage = (Stage) ((Node) e.getSource()).getScene().getWindow();
|
||||||
stage.close();
|
stage.close();
|
||||||
@@ -288,4 +267,156 @@ public class AppointmentDialogController {
|
|||||||
displayAppointmentDetails(selectedAppointment);
|
displayAppointmentDetails(selectedAppointment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applySelectedService() {
|
||||||
|
if (selectedAppointment == null || selectedAppointment.getServiceId() <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DropdownOption selected = findOptionById(cbService.getItems(), (long) selectedAppointment.getServiceId());
|
||||||
|
if (selected != null && !Objects.equals(cbService.getValue(), selected)) {
|
||||||
|
cbService.setValue(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySelectedCustomer() {
|
||||||
|
if (selectedAppointment == null || selectedAppointment.getCustomerId() <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DropdownOption selected = findOptionById(cbCustomer.getItems(), (long) selectedAppointment.getCustomerId());
|
||||||
|
if (selected != null && !Objects.equals(cbCustomer.getValue(), selected)) {
|
||||||
|
cbCustomer.setValue(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySelectedEmployee() {
|
||||||
|
if (selectedAppointment == null || selectedAppointment.getEmployeeId() <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DropdownOption selected = findOptionById(cbEmployee.getItems(), (long) selectedAppointment.getEmployeeId());
|
||||||
|
if (selected != null && !Objects.equals(cbEmployee.getValue(), selected)) {
|
||||||
|
cbEmployee.setValue(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DropdownOption findOptionById(List<DropdownOption> options, Long id) {
|
||||||
|
if (id == null || options == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (DropdownOption option : options) {
|
||||||
|
if (option.getId() != null && option.getId().equals(id)) {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadCustomerPets(Long customerId) {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
List<DropdownOption> pets = DropdownApi.getInstance().getCustomerPets(customerId);
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
cbPet.setItems(FXCollections.observableArrayList(pets));
|
||||||
|
cbPet.setDisable(pets == null || pets.isEmpty());
|
||||||
|
cbPet.setPromptText(pets == null || pets.isEmpty() ? "No pets for selected customer" : "Select a pet");
|
||||||
|
if (pendingPetSelectionId != null) {
|
||||||
|
for (DropdownOption pet : cbPet.getItems()) {
|
||||||
|
if (pet.getId() != null && pet.getId().equals(pendingPetSelectionId)) {
|
||||||
|
cbPet.setValue(pet);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pendingPetSelectionId = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
ActivityLogger.getInstance().logException(
|
||||||
|
"AppointmentDialogController.loadCustomerPets",
|
||||||
|
ex,
|
||||||
|
"Loading customer pets for appointment dialog");
|
||||||
|
cbPet.setDisable(true);
|
||||||
|
cbPet.setPromptText("Unable to load pets");
|
||||||
|
showError("Error loading pets for selected customer");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadServices() {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
List<DropdownOption> services = DropdownApi.getInstance().getServices();
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
cbService.setItems(FXCollections.observableArrayList(services));
|
||||||
|
applySelectedService();
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
ActivityLogger.getInstance().logException(
|
||||||
|
"AppointmentDialogController.loadServices",
|
||||||
|
e,
|
||||||
|
"Loading services for appointment dialog");
|
||||||
|
cbService.setDisable(true);
|
||||||
|
cbService.setPromptText("Unable to load services");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAppointmentCustomers() {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
List<DropdownOption> customers = DropdownApi.getInstance().getAppointmentCustomers();
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
cbCustomer.setItems(FXCollections.observableArrayList(customers));
|
||||||
|
boolean hasCustomers = customers != null && !customers.isEmpty();
|
||||||
|
cbCustomer.setDisable(!hasCustomers);
|
||||||
|
cbPet.setDisable(true);
|
||||||
|
cbPet.setItems(FXCollections.observableArrayList());
|
||||||
|
cbCustomer.setPromptText(hasCustomers ? "Select a customer" : "No customers with pets yet");
|
||||||
|
cbPet.setPromptText(hasCustomers ? "Select a customer first" : "No customer pets available");
|
||||||
|
applySelectedCustomer();
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
ActivityLogger.getInstance().logException(
|
||||||
|
"AppointmentDialogController.loadAppointmentCustomers",
|
||||||
|
e,
|
||||||
|
"Loading appointment customers for appointment dialog");
|
||||||
|
cbCustomer.setDisable(true);
|
||||||
|
cbPet.setDisable(true);
|
||||||
|
cbCustomer.setPromptText("Unable to load customers");
|
||||||
|
cbPet.setPromptText("Unable to load pets");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadEmployees() {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
Long storeId = UserSession.getInstance().getStoreId();
|
||||||
|
List<DropdownOption> employees;
|
||||||
|
if (storeId != null && storeId > 0) {
|
||||||
|
employees = DropdownApi.getInstance().getStoreEmployees(storeId);
|
||||||
|
} else {
|
||||||
|
employees = DropdownApi.getInstance().getEmployees();
|
||||||
|
}
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
cbEmployee.setItems(FXCollections.observableArrayList(employees));
|
||||||
|
applySelectedEmployee();
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
ActivityLogger.getInstance().logException(
|
||||||
|
"AppointmentDialogController.loadEmployees",
|
||||||
|
e,
|
||||||
|
"Loading employees for appointment dialog");
|
||||||
|
cbEmployee.setDisable(true);
|
||||||
|
cbEmployee.setPromptText("Unable to load employees");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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); }
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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">
|
||||||
@@ -190,9 +191,9 @@
|
|||||||
</children>
|
</children>
|
||||||
|
|
||||||
</VBox>
|
</VBox>
|
||||||
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="2">
|
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="2">
|
||||||
<children>
|
<children>
|
||||||
<Label text="Pet:" textFill="#2c3e50">
|
<Label text="Pet:" textFill="#2c3e50">
|
||||||
<font>
|
<font>
|
||||||
<Font name="System Bold" size="16.0" />
|
<Font name="System Bold" size="16.0" />
|
||||||
</font>
|
</font>
|
||||||
@@ -201,9 +202,23 @@
|
|||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
|
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
|
||||||
</padding>
|
</padding>
|
||||||
</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" />
|
||||||
|
|||||||
@@ -68,11 +68,12 @@
|
|||||||
<TableView fx:id="tvAdoptions" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
<TableView fx:id="tvAdoptions" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
||||||
<columns>
|
<columns>
|
||||||
<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="colAdoptionDate" prefWidth="190.85711669921875" text="Adoption Date" />
|
<TableColumn fx:id="colEmployeeName" prefWidth="160.0" text="Employee" />
|
||||||
<TableColumn fx:id="colAdoptionFee" prefWidth="91.4285888671875" text="Fee" />
|
<TableColumn fx:id="colAdoptionDate" prefWidth="190.85711669921875" text="Adoption Date" />
|
||||||
<TableColumn fx:id="colAdoptionStatus" prefWidth="142.28570556640625" text="Status" />
|
<TableColumn fx:id="colAdoptionFee" prefWidth="91.4285888671875" text="Fee" />
|
||||||
|
<TableColumn fx:id="colAdoptionStatus" prefWidth="120.0" text="Status" />
|
||||||
</columns>
|
</columns>
|
||||||
</TableView>
|
</TableView>
|
||||||
</children>
|
</children>
|
||||||
|
|||||||
@@ -68,12 +68,13 @@
|
|||||||
<TableView fx:id="tvAppointments" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
<TableView fx:id="tvAppointments" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
||||||
<columns>
|
<columns>
|
||||||
<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="colAppointmentDate" prefWidth="101.14288330078125" text="Date" />
|
<TableColumn fx:id="colEmployeeName" prefWidth="132.0" text="Employee" />
|
||||||
<TableColumn fx:id="colAppointmentTime" prefWidth="89.7142333984375" text="Time" />
|
<TableColumn fx:id="colAppointmentDate" prefWidth="101.14288330078125" text="Date" />
|
||||||
<TableColumn fx:id="colCustomerName" prefWidth="168.57147216796875" text="Customer" />
|
<TableColumn fx:id="colAppointmentTime" prefWidth="89.7142333984375" text="Time" />
|
||||||
<TableColumn fx:id="colAppointmentStatus" prefWidth="98.28570556640625" text="Status" />
|
<TableColumn fx:id="colCustomerName" prefWidth="140.0" text="Customer" />
|
||||||
|
<TableColumn fx:id="colAppointmentStatus" prefWidth="98.28570556640625" text="Status" />
|
||||||
</columns>
|
</columns>
|
||||||
</TableView>
|
</TableView>
|
||||||
</children>
|
</children>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user