Merge branch 'main' into AttachmentsToChat
This commit is contained in:
@@ -564,7 +564,7 @@
|
||||
"name": "Get All Pets",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "{{baseUrl}}/api/v1/pets",
|
||||
"url": "{{baseUrl}}/api/v1/pets?status=available&storeId=1",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
@@ -585,7 +585,10 @@
|
||||
"exec": [
|
||||
"pm.test('Status code is 200', function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});"
|
||||
"});",
|
||||
"var json = pm.response.json();",
|
||||
"pm.test('is page response', function () { pm.expect(json.content).to.be.an('array'); });",
|
||||
"pm.test('all pets have storeName', function () { json.content.forEach(function(p) { pm.expect(p).to.have.property('storeName'); }); });"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -616,7 +619,10 @@
|
||||
"exec": [
|
||||
"pm.test('Status code is 200', function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});"
|
||||
"});",
|
||||
"var json = pm.response.json();",
|
||||
"pm.test('has petId', function () { pm.expect(json.petId).to.be.a('number'); });",
|
||||
"pm.test('has owner fields', function () { pm.expect(json).to.have.all.keys('petId','petName','petSpecies','petBreed','petAge','petStatus','petPrice','imageUrl','createdAt','updatedAt','customerId','customerName','storeId','storeName'); });"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -671,7 +677,7 @@
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"petName\": \"Postman Pet\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Mixed\",\n \"petAge\": 2,\n \"petStatus\": \"Available\",\n \"petPrice\": 350.00\n}",
|
||||
"raw": "{\n \"petName\": \"Postman Pet\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Mixed\",\n \"petAge\": 2,\n \"petStatus\": \"Available\",\n \"petPrice\": 350.00,\n \"storeId\": 1\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
@@ -689,7 +695,11 @@
|
||||
" pm.response.to.have.status(201);",
|
||||
"});",
|
||||
"var jsonData = pm.response.json();",
|
||||
"if (jsonData.petId !== undefined) pm.collectionVariables.set('petId', jsonData.petId);"
|
||||
"if (jsonData.petId !== undefined) pm.collectionVariables.set('petId', jsonData.petId);",
|
||||
"pm.test('has petId', function () { pm.expect(jsonData.petId).to.be.a('number'); });",
|
||||
"pm.test('has storeId', function () { pm.expect(jsonData.storeId).to.equal(1); });",
|
||||
"pm.test('has storeName', function () { pm.expect(jsonData.storeName).to.be.a('string'); });",
|
||||
"pm.test('customerId is null', function () { pm.expect(jsonData.customerId).to.be.null; });"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -713,7 +723,7 @@
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"petName\": \"Postman Pet Updated\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Mixed\",\n \"petAge\": 3,\n \"petStatus\": \"Available\",\n \"petPrice\": 375.00\n}",
|
||||
"raw": "{\n \"petName\": \"Postman Pet Updated\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Mixed\",\n \"petAge\": 3,\n \"petStatus\": \"Owned\",\n \"petPrice\": 375.00,\n \"customerId\": 1\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
@@ -729,7 +739,12 @@
|
||||
"exec": [
|
||||
"pm.test('Status code is 200', function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});"
|
||||
"});",
|
||||
"var json = pm.response.json();",
|
||||
"pm.test('status is Owned', function () { pm.expect(json.petStatus).to.equal('Owned'); });",
|
||||
"pm.test('has customerId', function () { pm.expect(json.customerId).to.be.a('number'); });",
|
||||
"pm.test('has customerName', function () { pm.expect(json.customerName).to.be.a('string'); });",
|
||||
"pm.test('storeId is null', function () { pm.expect(json.storeId).to.be.null; });"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,9 @@ public class PetController {
|
||||
@RequestParam(required = false) String q,
|
||||
@RequestParam(required = false) String species,
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(required = false) Long storeId,
|
||||
Pageable pageable) {
|
||||
return ResponseEntity.ok(petService.getAllPets(q, species, status, pageable));
|
||||
return ResponseEntity.ok(petService.getAllPets(q, species, status, storeId, pageable));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
|
||||
@@ -23,6 +23,10 @@ public class PetRequest {
|
||||
|
||||
private BigDecimal petPrice;
|
||||
|
||||
private Long customerId;
|
||||
|
||||
private Long storeId;
|
||||
|
||||
public String getPetName() {
|
||||
return petName;
|
||||
}
|
||||
@@ -71,6 +75,22 @@ public class PetRequest {
|
||||
this.petPrice = petPrice;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
||||
@@ -15,11 +15,15 @@ public class PetResponse {
|
||||
private String imageUrl;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
private Long customerId;
|
||||
private String customerName;
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
|
||||
public PetResponse() {
|
||||
}
|
||||
|
||||
public PetResponse(Long petId, String petName, String petSpecies, String petBreed, Integer petAge, String petStatus, BigDecimal petPrice, String imageUrl, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||
public PetResponse(Long petId, String petName, String petSpecies, String petBreed, Integer petAge, String petStatus, BigDecimal petPrice, String imageUrl, LocalDateTime createdAt, LocalDateTime updatedAt, Long customerId, String customerName, Long storeId, String storeName) {
|
||||
this.petId = petId;
|
||||
this.petName = petName;
|
||||
this.petSpecies = petSpecies;
|
||||
@@ -30,6 +34,10 @@ public class PetResponse {
|
||||
this.imageUrl = imageUrl;
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
this.customerId = customerId;
|
||||
this.customerName = customerName;
|
||||
this.storeId = storeId;
|
||||
this.storeName = storeName;
|
||||
}
|
||||
|
||||
public Long getPetId() {
|
||||
@@ -112,17 +120,49 @@ public class PetResponse {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public void setCustomerName(String customerName) {
|
||||
this.customerName = customerName;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
|
||||
public String getStoreName() {
|
||||
return storeName;
|
||||
}
|
||||
|
||||
public void setStoreName(String storeName) {
|
||||
this.storeName = storeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
PetResponse that = (PetResponse) o;
|
||||
return Objects.equals(petId, that.petId) && Objects.equals(petName, that.petName) && Objects.equals(petSpecies, that.petSpecies) && Objects.equals(petBreed, that.petBreed) && Objects.equals(petAge, that.petAge) && Objects.equals(petStatus, that.petStatus) && Objects.equals(petPrice, that.petPrice) && Objects.equals(imageUrl, that.imageUrl) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
|
||||
return Objects.equals(petId, that.petId) && Objects.equals(petName, that.petName) && Objects.equals(petSpecies, that.petSpecies) && Objects.equals(petBreed, that.petBreed) && Objects.equals(petAge, that.petAge) && Objects.equals(petStatus, that.petStatus) && Objects.equals(petPrice, that.petPrice) && Objects.equals(imageUrl, that.imageUrl) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(petId, petName, petSpecies, petBreed, petAge, petStatus, petPrice, imageUrl, createdAt, updatedAt);
|
||||
return Objects.hash(petId, petName, petSpecies, petBreed, petAge, petStatus, petPrice, imageUrl, createdAt, updatedAt, customerId, customerName, storeId, storeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,6 +178,10 @@ public class PetResponse {
|
||||
", imageUrl='" + imageUrl + '\'' +
|
||||
", createdAt=" + createdAt +
|
||||
", updatedAt=" + updatedAt +
|
||||
", customerId=" + customerId +
|
||||
", customerName='" + customerName + '\'' +
|
||||
", storeId=" + storeId +
|
||||
", storeName='" + storeName + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,14 @@ public class Pet {
|
||||
@Column(length = 255)
|
||||
private String imageUrl;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "customerId")
|
||||
private Customer customer;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "storeId")
|
||||
private StoreLocation store;
|
||||
|
||||
@CreationTimestamp
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
@@ -142,6 +150,22 @@ public class Pet {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public Customer getCustomer() {
|
||||
return customer;
|
||||
}
|
||||
|
||||
public void setCustomer(Customer customer) {
|
||||
this.customer = customer;
|
||||
}
|
||||
|
||||
public StoreLocation getStore() {
|
||||
return store;
|
||||
}
|
||||
|
||||
public void setStore(StoreLocation store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
||||
@@ -18,16 +18,18 @@ public interface PetRepository extends JpaRepository<Pet, Long> {
|
||||
@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 " +
|
||||
"(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " +
|
||||
"(: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);
|
||||
"(:status IS NULL OR LOWER(p.petStatus) = LOWER(:status)) AND " +
|
||||
"(:storeId IS NULL OR p.store.storeId = :storeId)")
|
||||
Page<Pet> searchPets(@Param("q") String query, @Param("species") String species, @Param("status") String status, @Param("storeId") Long storeId, 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);
|
||||
"(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " +
|
||||
"(:storeId IS NULL OR p.store.storeId = :storeId)")
|
||||
Page<Pet> searchPublicPets(@Param("q") String query, @Param("species") String species, @Param("storeId") Long storeId, 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 " +
|
||||
"(LOWER(p.petStatus) = 'available' OR a.customer.userId = :userId OR (LOWER(p.petStatus) = 'owned' AND p.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))")
|
||||
|
||||
@@ -4,12 +4,16 @@ import com.petshop.backend.dto.common.BulkDeleteRequest;
|
||||
import com.petshop.backend.dto.pet.PetRequest;
|
||||
import com.petshop.backend.dto.pet.PetResponse;
|
||||
import com.petshop.backend.entity.Adoption;
|
||||
import com.petshop.backend.entity.Customer;
|
||||
import com.petshop.backend.entity.Pet;
|
||||
import com.petshop.backend.entity.StoreLocation;
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||
import com.petshop.backend.security.AppPrincipal;
|
||||
import com.petshop.backend.repository.AdoptionRepository;
|
||||
import com.petshop.backend.repository.CustomerRepository;
|
||||
import com.petshop.backend.repository.PetRepository;
|
||||
import com.petshop.backend.repository.StoreRepository;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -29,15 +33,20 @@ public class PetService {
|
||||
|
||||
private final PetRepository petRepository;
|
||||
private final AdoptionRepository adoptionRepository;
|
||||
private final CustomerRepository customerRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
private final CatalogImageStorageService catalogImageStorageService;
|
||||
|
||||
public PetService(PetRepository petRepository, AdoptionRepository adoptionRepository, CatalogImageStorageService catalogImageStorageService) {
|
||||
public PetService(PetRepository petRepository, AdoptionRepository adoptionRepository, CustomerRepository customerRepository, StoreRepository storeRepository, CatalogImageStorageService catalogImageStorageService) {
|
||||
this.petRepository = petRepository;
|
||||
this.adoptionRepository = adoptionRepository;
|
||||
this.customerRepository = customerRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
this.catalogImageStorageService = catalogImageStorageService;
|
||||
}
|
||||
|
||||
public Page<PetResponse> getAllPets(String query, String species, String status, Pageable pageable) {
|
||||
@Transactional(readOnly = true)
|
||||
public Page<PetResponse> getAllPets(String query, String species, String status, Long storeId, Pageable pageable) {
|
||||
String normalizedQuery = normalizeFilter(query);
|
||||
String normalizedSpecies = normalizeFilter(species);
|
||||
String normalizedStatus = normalizeFilter(status);
|
||||
@@ -48,22 +57,23 @@ public class PetService {
|
||||
if (!isAllowedPublicStatus(normalizedStatus)) {
|
||||
return new PageImpl<>(java.util.List.of(), pageable, 0);
|
||||
}
|
||||
pets = petRepository.searchPublicPets(normalizedQuery, normalizedSpecies, pageable);
|
||||
pets = petRepository.searchPublicPets(normalizedQuery, normalizedSpecies, storeId, pageable);
|
||||
} else if (viewer.role() == User.Role.STAFF || viewer.role() == User.Role.ADMIN) {
|
||||
pets = petRepository.searchPets(normalizedQuery, normalizedSpecies, normalizedStatus, pageable);
|
||||
pets = petRepository.searchPets(normalizedQuery, normalizedSpecies, normalizedStatus, storeId, 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);
|
||||
pets = petRepository.searchPublicPets(normalizedQuery, normalizedSpecies, storeId, pageable);
|
||||
}
|
||||
|
||||
return pets
|
||||
.map(this::mapToResponse);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public PetResponse getPetById(Long id) {
|
||||
Pet pet = petRepository.findById(id)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id));
|
||||
@@ -82,6 +92,7 @@ public class PetService {
|
||||
pet.setPetAge(request.getPetAge());
|
||||
pet.setPetStatus(request.getPetStatus());
|
||||
pet.setPetPrice(request.getPetPrice());
|
||||
applyOwnerAndStore(pet, request);
|
||||
|
||||
pet = petRepository.save(pet);
|
||||
return mapToResponse(pet);
|
||||
@@ -98,6 +109,7 @@ public class PetService {
|
||||
pet.setPetAge(request.getPetAge());
|
||||
pet.setPetStatus(request.getPetStatus());
|
||||
pet.setPetPrice(request.getPetPrice());
|
||||
applyOwnerAndStore(pet, request);
|
||||
|
||||
pet = petRepository.save(pet);
|
||||
return mapToResponse(pet);
|
||||
@@ -161,6 +173,9 @@ public class PetService {
|
||||
if (viewer == null || viewer.userId() == null) {
|
||||
return false;
|
||||
}
|
||||
if (isOwnedByUser(pet, viewer.userId())) {
|
||||
return true;
|
||||
}
|
||||
return isAdoptedByUser(pet, viewer.userId());
|
||||
}
|
||||
|
||||
@@ -230,7 +245,7 @@ public class PetService {
|
||||
}
|
||||
|
||||
private boolean isAllowedCustomerStatus(String status) {
|
||||
return status == null || "available".equalsIgnoreCase(status) || "adopted".equalsIgnoreCase(status);
|
||||
return status == null || "available".equalsIgnoreCase(status) || "adopted".equalsIgnoreCase(status) || "owned".equalsIgnoreCase(status);
|
||||
}
|
||||
|
||||
private String normalizeFilter(String value) {
|
||||
@@ -242,6 +257,8 @@ public class PetService {
|
||||
}
|
||||
|
||||
private PetResponse mapToResponse(Pet pet) {
|
||||
Customer customer = pet.getCustomer();
|
||||
StoreLocation store = pet.getStore();
|
||||
return new PetResponse(
|
||||
pet.getPetId(),
|
||||
pet.getPetName(),
|
||||
@@ -252,10 +269,59 @@ public class PetService {
|
||||
pet.getPetPrice(),
|
||||
pet.getImageUrl() != null && !pet.getImageUrl().isBlank() ? "/api/v1/pets/" + pet.getPetId() + "/image" : null,
|
||||
pet.getCreatedAt(),
|
||||
pet.getUpdatedAt()
|
||||
pet.getUpdatedAt(),
|
||||
customer != null ? customer.getCustomerId() : null,
|
||||
customer != null ? customer.getFirstName() + " " + customer.getLastName() : null,
|
||||
store != null ? store.getStoreId() : null,
|
||||
store != null ? store.getStoreName() : null
|
||||
);
|
||||
}
|
||||
|
||||
private void applyOwnerAndStore(Pet pet, PetRequest request) {
|
||||
if ("owned".equalsIgnoreCase(request.getPetStatus())) {
|
||||
if (request.getCustomerId() != null) {
|
||||
Customer customer = customerRepository.findById(request.getCustomerId())
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
|
||||
pet.setCustomer(customer);
|
||||
} else {
|
||||
pet.setCustomer(null);
|
||||
}
|
||||
pet.setStore(null);
|
||||
} else if ("available".equalsIgnoreCase(request.getPetStatus()) || "unadopted".equalsIgnoreCase(request.getPetStatus())) {
|
||||
if (request.getStoreId() != null) {
|
||||
StoreLocation store = storeRepository.findById(request.getStoreId())
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId()));
|
||||
pet.setStore(store);
|
||||
} else {
|
||||
pet.setStore(null);
|
||||
}
|
||||
pet.setCustomer(null);
|
||||
} else {
|
||||
if (request.getCustomerId() != null) {
|
||||
Customer customer = customerRepository.findById(request.getCustomerId())
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
|
||||
pet.setCustomer(customer);
|
||||
} else {
|
||||
pet.setCustomer(null);
|
||||
}
|
||||
if (request.getStoreId() != null) {
|
||||
StoreLocation store = storeRepository.findById(request.getStoreId())
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId()));
|
||||
pet.setStore(store);
|
||||
} else {
|
||||
pet.setStore(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOwnedByUser(Pet pet, Long userId) {
|
||||
if (!"owned".equalsIgnoreCase(normalizeStatus(pet.getPetStatus()))) {
|
||||
return false;
|
||||
}
|
||||
Customer customer = pet.getCustomer();
|
||||
return customer != null && userId.equals(customer.getUserId());
|
||||
}
|
||||
|
||||
public record ImagePayload(Resource resource, MediaType mediaType) {
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
ALTER TABLE pet ADD COLUMN customerId BIGINT NULL;
|
||||
ALTER TABLE pet ADD COLUMN storeId BIGINT NULL;
|
||||
|
||||
ALTER TABLE pet ADD CONSTRAINT fk_pet_customer
|
||||
FOREIGN KEY (customerId) REFERENCES customer(customerId);
|
||||
ALTER TABLE pet ADD CONSTRAINT fk_pet_store
|
||||
FOREIGN KEY (storeId) REFERENCES storeLocation(storeId);
|
||||
|
||||
CREATE INDEX idx_pet_customerId ON pet(customerId);
|
||||
CREATE INDEX idx_pet_storeId ON pet(storeId);
|
||||
|
||||
UPDATE pet
|
||||
SET storeId = (SELECT storeId FROM storeLocation ORDER BY storeId ASC LIMIT 1)
|
||||
WHERE LOWER(petStatus) IN ('available', 'unadopted');
|
||||
|
||||
UPDATE pet p
|
||||
JOIN (
|
||||
SELECT a.petId, a.customerId
|
||||
FROM adoption a
|
||||
WHERE LOWER(a.adoptionStatus) = 'completed'
|
||||
) latest ON latest.petId = p.petId
|
||||
SET p.customerId = latest.customerId
|
||||
WHERE LOWER(p.petStatus) = 'adopted';
|
||||
@@ -0,0 +1,5 @@
|
||||
INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice, customerId)
|
||||
VALUES
|
||||
('Pepper', 'Cat', 'Tabby', 3, 'Owned', 0.00, 1),
|
||||
('Coco', 'Dog', 'Pomeranian', 2, 'Owned', 0.00, 4),
|
||||
('Finn', 'Dog', 'Border Collie', 5, 'Owned', 0.00, 6);
|
||||
@@ -0,0 +1,144 @@
|
||||
INSERT INTO customer (firstName, lastName, email) VALUES
|
||||
('Noah', 'Parker', 'noah@gmail.com'),
|
||||
('Mia', 'Evans', 'mia@gmail.com'),
|
||||
('Ethan', 'Scott', 'ethan@gmail.com'),
|
||||
('Chloe', 'Adams', 'chloe@gmail.com'),
|
||||
('Lucas', 'Baker', 'lucas@gmail.com'),
|
||||
('Lily', 'Hall', 'lily@gmail.com'),
|
||||
('Mason', 'Rivera', 'mason@gmail.com'),
|
||||
('Ella', 'Mitchell', 'ella@gmail.com'),
|
||||
('James', 'Carter', 'jcarter@gmail.com'),
|
||||
('Harper', 'Collins', 'harper@gmail.com');
|
||||
|
||||
INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice, storeId) VALUES
|
||||
('Rocky', 'Dog', 'German Shepherd', 1, 'Available', 475.00, 1),
|
||||
('Daisy', 'Dog', 'Poodle', 2, 'Available', 512.00, 1),
|
||||
('Cooper', 'Dog', 'Bulldog', 3, 'Available', 560.00, 1),
|
||||
('Ruby', 'Dog', 'Boxer', 4, 'Available', 575.00, 1),
|
||||
('Tucker', 'Dog', 'Dachshund', 5, 'Available', 634.00, 1),
|
||||
('Rosie', 'Dog', 'Shih Tzu', 1, 'Available', 660.00, 2),
|
||||
('Bear', 'Dog', 'Rottweiler', 2, 'Available', 686.00, 2),
|
||||
('Maggie', 'Dog', 'Corgi', 3, 'Available', 745.00, 2),
|
||||
('Leo', 'Dog', 'Husky', 4, 'Available', 749.00, 2),
|
||||
('Zoey', 'Cat', 'Ragdoll', 1, 'Available', 420.00, 1),
|
||||
('Oliver', 'Cat', 'British Shorthair', 2, 'Available', 395.00, 1),
|
||||
('Lola', 'Cat', 'Bengal', 3, 'Available', 465.00, 3),
|
||||
('Buster', 'Dog', 'Beagle', 2, 'Available', 440.00, 3),
|
||||
('Sadie', 'Dog', 'Golden Retriever', 1, 'Available', 535.00, 3),
|
||||
('Toby', 'Dog', 'Labrador', 5, 'Available', 490.00, 1),
|
||||
('Cleo', 'Cat', 'Abyssinian', 2, 'Available', 375.00, 2),
|
||||
('Harley', 'Dog', 'Dalmatian', 3, 'Available', 520.00, 1),
|
||||
('Mocha', 'Cat', 'Burmese', 1, 'Available', 345.00, 3),
|
||||
('Rex', 'Dog', 'Doberman', 4, 'Available', 610.00, 1),
|
||||
('Willow', 'Cat', 'Scottish Fold', 2, 'Available', 480.00, 2),
|
||||
('Gizmo', 'Dog', 'Pomeranian', 1, 'Available', 530.00, 1),
|
||||
('Nala', 'Cat', 'Siamese', 3, 'Available', 360.00, 2),
|
||||
('Duke', 'Dog', 'Great Dane', 2, 'Available', 720.00, 3),
|
||||
('Misty', 'Cat', 'Russian Blue', 4, 'Available', 410.00, 1),
|
||||
('Ace', 'Dog', 'Australian Shepherd', 1, 'Available', 555.00, 1);
|
||||
|
||||
INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice, customerId) VALUES
|
||||
('Shadow', 'Dog', 'Labrador', 3, 'Adopted', 500.00, 1),
|
||||
('Kitty', 'Cat', 'Persian', 2, 'Adopted', 320.00, 2),
|
||||
('Bruno', 'Dog', 'Rottweiler', 4, 'Adopted', 580.00, 3),
|
||||
('Snowball', 'Cat', 'Turkish Angora', 1, 'Adopted', 390.00, 4),
|
||||
('Zeus', 'Dog', 'Husky', 3, 'Adopted', 640.00, 5);
|
||||
|
||||
INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice, customerId) VALUES
|
||||
('Biscuit', 'Dog', 'Beagle', 2, 'Owned', 0.00, 6),
|
||||
('Patches', 'Cat', 'Calico', 5, 'Owned', 0.00, 7),
|
||||
('Scout', 'Dog', 'Border Collie', 3, 'Owned', 0.00, 8),
|
||||
('Mittens', 'Cat', 'Domestic Short', 4, 'Owned', 0.00, 9),
|
||||
('Thor', 'Dog', 'German Shepherd', 2, 'Owned', 0.00, 10);
|
||||
|
||||
INSERT INTO adoption (petId, customerId, employeeId, adoptionDate, adoptionStatus)
|
||||
SELECT p.petId, p.customerId,
|
||||
(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 LIMIT 1),
|
||||
'2026-01-10', 'Completed'
|
||||
FROM pet p WHERE p.petName = 'Shadow' AND p.petStatus = 'Adopted';
|
||||
|
||||
INSERT INTO adoption (petId, customerId, employeeId, adoptionDate, adoptionStatus)
|
||||
SELECT p.petId, p.customerId,
|
||||
(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 LIMIT 1),
|
||||
'2026-01-18', 'Completed'
|
||||
FROM pet p WHERE p.petName = 'Kitty' AND p.petStatus = 'Adopted';
|
||||
|
||||
INSERT INTO adoption (petId, customerId, employeeId, adoptionDate, adoptionStatus)
|
||||
SELECT p.petId, p.customerId,
|
||||
(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 LIMIT 1),
|
||||
'2026-02-03', 'Completed'
|
||||
FROM pet p WHERE p.petName = 'Bruno' AND p.petStatus = 'Adopted';
|
||||
|
||||
INSERT INTO adoption (petId, customerId, employeeId, adoptionDate, adoptionStatus)
|
||||
SELECT p.petId, p.customerId,
|
||||
(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 LIMIT 1),
|
||||
'2026-02-14', 'Completed'
|
||||
FROM pet p WHERE p.petName = 'Snowball' AND p.petStatus = 'Adopted';
|
||||
|
||||
INSERT INTO adoption (petId, customerId, employeeId, adoptionDate, adoptionStatus)
|
||||
SELECT p.petId, p.customerId,
|
||||
(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 LIMIT 1),
|
||||
'2026-02-21', 'Completed'
|
||||
FROM pet p WHERE p.petName = 'Zeus' AND p.petStatus = 'Adopted';
|
||||
|
||||
INSERT INTO customer_pet (customer_id, pet_name, species, breed) VALUES
|
||||
(1, 'Rex', 'Dog', 'German Shepherd'),
|
||||
(2, 'Whiskers', 'Cat', 'Tabby'),
|
||||
(3, 'Goldie', 'Dog', 'Golden Retriever'),
|
||||
(4, 'Midnight', 'Cat', 'Black'),
|
||||
(5, 'Storm', 'Dog', 'Husky'),
|
||||
(6, 'Peanut', 'Dog', 'Poodle'),
|
||||
(7, 'Snowball', 'Cat', 'Persian'),
|
||||
(8, 'Duke', 'Dog', 'Labrador'),
|
||||
(9, 'Luna', 'Cat', 'Siamese'),
|
||||
(10, 'Buster', 'Dog', 'Beagle'),
|
||||
(11, 'Daisy', 'Dog', 'Corgi'),
|
||||
(12, 'Cleo', 'Cat', 'Ragdoll');
|
||||
|
||||
INSERT INTO appointment (serviceId, customerId, appointmentDate, appointmentTime, appointmentStatus, storeId, employeeId) VALUES
|
||||
(1, 1, '2026-01-10', '09:00:00', 'Completed', 1, 1),
|
||||
(2, 2, '2026-01-10', '11:00:00', 'Completed', 1, 1),
|
||||
(3, 3, '2026-01-17', '09:00:00', 'Missed', 1, 1),
|
||||
(4, 4, '2026-01-17', '14:00:00', 'Completed', 1, 1),
|
||||
(5, 5, '2026-01-24', '10:00:00', 'Completed', 1, 1),
|
||||
(1, 6, '2026-01-24', '13:00:00', 'Missed', 1, 1),
|
||||
(2, 7, '2026-02-07', '09:00:00', 'Completed', 1, 1),
|
||||
(3, 8, '2026-02-07', '11:00:00', 'Completed', 1, 1),
|
||||
(1, 9, '2026-01-11', '09:00:00', 'Completed', 1, 2),
|
||||
(2, 10, '2026-01-11', '11:00:00', 'Missed', 1, 2),
|
||||
(3, 11, '2026-01-18', '10:00:00', 'Completed', 1, 2),
|
||||
(4, 12, '2026-01-18', '13:00:00', 'Completed', 1, 2),
|
||||
(5, 1, '2026-02-01', '09:00:00', 'Completed', 1, 2),
|
||||
(1, 2, '2026-02-01', '14:00:00', 'Missed', 1, 2),
|
||||
(2, 3, '2026-02-08', '10:00:00', 'Completed', 1, 2),
|
||||
(3, 4, '2026-02-08', '13:00:00', 'Completed', 1, 2),
|
||||
(4, 5, '2026-01-12', '09:00:00', 'Completed', 1, 5),
|
||||
(5, 6, '2026-01-12', '11:00:00', 'Completed', 1, 5),
|
||||
(1, 7, '2026-01-19', '09:00:00', 'Missed', 1, 5),
|
||||
(2, 8, '2026-01-19', '14:00:00', 'Completed', 1, 5),
|
||||
(3, 9, '2026-02-09', '10:00:00', 'Completed', 1, 5),
|
||||
(4, 10, '2026-02-09', '13:00:00', 'Completed', 1, 5),
|
||||
(1, 11, '2026-01-13', '09:00:00', 'Completed', 2, 3),
|
||||
(2, 12, '2026-01-13', '11:00:00', 'Completed', 2, 3),
|
||||
(3, 1, '2026-02-10', '09:00:00', 'Missed', 2, 3),
|
||||
(4, 2, '2026-02-10', '13:00:00', 'Completed', 2, 3),
|
||||
(1, 3, '2026-01-14', '10:00:00', 'Completed', 3, 4),
|
||||
(2, 4, '2026-01-14', '13:00:00', 'Completed', 3, 4),
|
||||
(3, 5, '2026-02-11', '10:00:00', 'Missed', 3, 4),
|
||||
(4, 6, '2026-02-11', '14:00:00', 'Completed', 3, 4),
|
||||
(1, 7, '2026-04-15', '09:00:00', 'Booked', 1, 1),
|
||||
(2, 8, '2026-04-15', '11:00:00', 'Booked', 1, 2),
|
||||
(3, 9, '2026-04-16', '10:00:00', 'Booked', 1, 5),
|
||||
(4, 10, '2026-04-17', '09:00:00', 'Booked', 2, 3),
|
||||
(5, 11, '2026-04-18', '14:00:00', 'Booked', 3, 4);
|
||||
|
||||
INSERT INTO appointment_customer_pet (appointment_id, customer_pet_id)
|
||||
SELECT a.appointmentId,
|
||||
(((a.appointmentId - 6) % 12) + 1)
|
||||
FROM appointment a
|
||||
WHERE a.appointmentId BETWEEN 6 AND 40;
|
||||
@@ -6,7 +6,9 @@ import com.petshop.backend.entity.Pet;
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||
import com.petshop.backend.repository.AdoptionRepository;
|
||||
import com.petshop.backend.repository.CustomerRepository;
|
||||
import com.petshop.backend.repository.PetRepository;
|
||||
import com.petshop.backend.repository.StoreRepository;
|
||||
import com.petshop.backend.security.AppPrincipal;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -39,6 +41,12 @@ class PetServiceTest {
|
||||
@Mock
|
||||
private AdoptionRepository adoptionRepository;
|
||||
|
||||
@Mock
|
||||
private CustomerRepository customerRepository;
|
||||
|
||||
@Mock
|
||||
private StoreRepository storeRepository;
|
||||
|
||||
@Mock
|
||||
private CatalogImageStorageService catalogImageStorageService;
|
||||
|
||||
@@ -54,24 +62,24 @@ class PetServiceTest {
|
||||
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));
|
||||
when(petRepository.searchPublicPets(null, null, null, pageable)).thenReturn(new PageImpl<>(List.of(availablePet), pageable, 1));
|
||||
|
||||
var result = petService.getAllPets(null, null, null, pageable);
|
||||
var result = petService.getAllPets(null, 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);
|
||||
verify(petRepository).searchPublicPets(null, null, null, pageable);
|
||||
verify(petRepository, never()).searchPets(null, null, null, null, pageable);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllPetsAnonymousWithAdoptedStatusReturnsEmptyPage() {
|
||||
Pageable pageable = PageRequest.of(0, 10);
|
||||
|
||||
var result = petService.getAllPets(null, null, "Adopted", pageable);
|
||||
var result = petService.getAllPets(null, null, "Adopted", null, pageable);
|
||||
|
||||
assertEquals(0, result.getTotalElements());
|
||||
verify(petRepository, never()).searchPublicPets(null, null, pageable);
|
||||
verify(petRepository, never()).searchPublicPets(null, null, null, pageable);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -83,7 +91,7 @@ class PetServiceTest {
|
||||
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);
|
||||
var result = petService.getAllPets(null, null, null, null, pageable);
|
||||
|
||||
assertEquals(2, result.getTotalElements());
|
||||
verify(petRepository).searchCustomerVisiblePets(25L, null, null, null, pageable);
|
||||
@@ -95,13 +103,13 @@ class PetServiceTest {
|
||||
setAuthentication(99L, User.Role.ADMIN);
|
||||
Pet availablePet = pet(1L, "Buddy", "Available");
|
||||
Pet adoptedPet = pet(2L, "Luna", "Adopted");
|
||||
when(petRepository.searchPets(null, null, null, pageable))
|
||||
when(petRepository.searchPets(null, null, null, null, pageable))
|
||||
.thenReturn(new PageImpl<>(List.of(availablePet, adoptedPet), pageable, 2));
|
||||
|
||||
var result = petService.getAllPets(null, null, null, pageable);
|
||||
var result = petService.getAllPets(null, null, null, null, pageable);
|
||||
|
||||
assertEquals(2, result.getTotalElements());
|
||||
verify(petRepository).searchPets(null, null, null, pageable);
|
||||
verify(petRepository).searchPets(null, null, null, null, pageable);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -9,6 +9,8 @@ public class PetRequest {
|
||||
private Integer petAge;
|
||||
private String petStatus;
|
||||
private BigDecimal petPrice;
|
||||
private Long customerId;
|
||||
private Long storeId;
|
||||
|
||||
public PetRequest() {
|
||||
}
|
||||
@@ -60,4 +62,20 @@ public class PetRequest {
|
||||
public void setPetPrice(BigDecimal petPrice) {
|
||||
this.petPrice = petPrice;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ public class PetResponse {
|
||||
private String imageUrl;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
private Long customerId;
|
||||
private String customerName;
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
|
||||
public PetResponse() {
|
||||
}
|
||||
@@ -97,4 +101,36 @@ public class PetResponse {
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public void setCustomerName(String customerName) {
|
||||
this.customerName = customerName;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
|
||||
public String getStoreName() {
|
||||
return storeName;
|
||||
}
|
||||
|
||||
public void setStoreName(String storeName) {
|
||||
this.storeName = storeName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class PetApi {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<PetResponse> listPets(String query, String species, String status) throws Exception {
|
||||
public List<PetResponse> listPets(String query, String species, String status, Long storeId) throws Exception {
|
||||
String path = "/api/v1/pets?page=0&size=1000";
|
||||
if (query != null && !query.isEmpty()) {
|
||||
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
@@ -35,6 +35,9 @@ public class PetApi {
|
||||
if (status != null && !status.isEmpty()) {
|
||||
path += "&status=" + URLEncoder.encode(status, StandardCharsets.UTF_8);
|
||||
}
|
||||
if (storeId != null) {
|
||||
path += "&storeId=" + storeId;
|
||||
}
|
||||
String response = apiClient.getRawResponse(path);
|
||||
PageResponse<PetResponse> pageResponse = apiClient.getObjectMapper().readValue(
|
||||
response,
|
||||
@@ -46,8 +49,12 @@ public class PetApi {
|
||||
return pageResponse.getContent();
|
||||
}
|
||||
|
||||
public List<PetResponse> listPets(String query, String species, String status) throws Exception {
|
||||
return listPets(query, species, status, null);
|
||||
}
|
||||
|
||||
public List<PetResponse> listPets(String query) throws Exception {
|
||||
return listPets(query, null, null);
|
||||
return listPets(query, null, null, null);
|
||||
}
|
||||
|
||||
public PetResponse createPet(PetRequest request) throws Exception {
|
||||
|
||||
@@ -63,6 +63,12 @@ public class PetController {
|
||||
@FXML
|
||||
private TableColumn<Pet, String> colPetStatus;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Pet, String> colCustomerName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Pet, String> colStoreName;
|
||||
|
||||
@FXML
|
||||
private TableView<Pet> tvPets;
|
||||
|
||||
@@ -156,11 +162,13 @@ public class PetController {
|
||||
colPetAge.setCellValueFactory(new PropertyValueFactory<Pet,Integer>("petAge"));
|
||||
colPetStatus.setCellValueFactory(new PropertyValueFactory<Pet,String>("petStatus"));
|
||||
colPetPrice.setCellValueFactory(new PropertyValueFactory<Pet,Double>("petPrice"));
|
||||
colCustomerName.setCellValueFactory(new PropertyValueFactory<Pet,String>("customerName"));
|
||||
colStoreName.setCellValueFactory(new PropertyValueFactory<Pet,String>("storeName"));
|
||||
configureImageColumn(colPetImage);
|
||||
|
||||
loadSpeciesFilter();
|
||||
|
||||
cbStatusFilter.setItems(FXCollections.observableArrayList("All Statuses", "Available", "Adopted", "Pending"));
|
||||
cbStatusFilter.setItems(FXCollections.observableArrayList("All Statuses", "Available", "Adopted", "Owned", "Pending"));
|
||||
cbStatusFilter.getSelectionModel().selectFirst();
|
||||
|
||||
displayPets();
|
||||
@@ -316,7 +324,7 @@ public class PetController {
|
||||
}
|
||||
|
||||
private Pet mapToPet(PetResponse response) {
|
||||
return new Pet(
|
||||
Pet pet = new Pet(
|
||||
response.getPetId().intValue(),
|
||||
response.getPetName(),
|
||||
response.getPetSpecies(),
|
||||
@@ -326,6 +334,11 @@ public class PetController {
|
||||
response.getPetPrice().doubleValue(),
|
||||
response.getImageUrl()
|
||||
);
|
||||
pet.setCustomerName(response.getCustomerName());
|
||||
pet.setStoreName(response.getStoreName());
|
||||
pet.setCustomerId(response.getCustomerId() != null ? response.getCustomerId() : 0L);
|
||||
pet.setStoreId(response.getStoreId() != null ? response.getStoreId() : 0L);
|
||||
return pet;
|
||||
}
|
||||
|
||||
private void configureImageColumn(TableColumn<Pet, String> column) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.EventHandler;
|
||||
@@ -10,8 +11,10 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.Validator;
|
||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||
import org.example.petshopdesktop.api.dto.pet.PetRequest;
|
||||
import org.example.petshopdesktop.api.dto.pet.PetResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
||||
import org.example.petshopdesktop.api.endpoints.PetApi;
|
||||
import org.example.petshopdesktop.models.Pet;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
@@ -20,6 +23,7 @@ import org.example.petshopdesktop.util.FilePickerSupport;
|
||||
|
||||
import java.io.File;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public class PetDialogController {
|
||||
|
||||
@@ -38,6 +42,12 @@ public class PetDialogController {
|
||||
@FXML
|
||||
private ComboBox<String> cbPetStatus;
|
||||
|
||||
@FXML
|
||||
private ComboBox<DropdownOption> cbCustomer;
|
||||
|
||||
@FXML
|
||||
private ComboBox<DropdownOption> cbStore;
|
||||
|
||||
@FXML
|
||||
private Label lblMode;
|
||||
|
||||
@@ -70,16 +80,54 @@ public class PetDialogController {
|
||||
private String currentImageUrl;
|
||||
private boolean removeImageRequested;
|
||||
|
||||
private Long pendingCustomerId = null;
|
||||
private Long pendingStoreId = null;
|
||||
|
||||
private ObservableList<String> statusList = FXCollections.observableArrayList(
|
||||
"Available", "Adopted"
|
||||
"Available", "Adopted", "Owned"
|
||||
);
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
|
||||
cbPetStatus.setItems(statusList); //set status combobox
|
||||
cbPetStatus.setItems(statusList);
|
||||
|
||||
cbCustomer.setCellFactory(param -> new ListCell<>() {
|
||||
@Override protected void updateItem(DropdownOption o, boolean empty) {
|
||||
super.updateItem(o, empty);
|
||||
setText(empty || o == null ? null : o.getLabel());
|
||||
}
|
||||
});
|
||||
cbCustomer.setButtonCell(new ListCell<>() {
|
||||
@Override protected void updateItem(DropdownOption o, boolean empty) {
|
||||
super.updateItem(o, empty);
|
||||
setText(empty || o == null ? null : o.getLabel());
|
||||
}
|
||||
});
|
||||
|
||||
cbStore.setCellFactory(param -> new ListCell<>() {
|
||||
@Override protected void updateItem(DropdownOption o, boolean empty) {
|
||||
super.updateItem(o, empty);
|
||||
setText(empty || o == null ? null : o.getLabel());
|
||||
}
|
||||
});
|
||||
cbStore.setButtonCell(new ListCell<>() {
|
||||
@Override protected void updateItem(DropdownOption o, boolean empty) {
|
||||
super.updateItem(o, empty);
|
||||
setText(empty || o == null ? null : o.getLabel());
|
||||
}
|
||||
});
|
||||
|
||||
cbCustomer.setVisible(false);
|
||||
cbStore.setVisible(false);
|
||||
|
||||
cbPetStatus.valueProperty().addListener((obs, oldVal, newVal) -> {
|
||||
boolean isOwned = "Owned".equalsIgnoreCase(newVal);
|
||||
boolean isAvailable = "Available".equalsIgnoreCase(newVal) || "Unadopted".equalsIgnoreCase(newVal);
|
||||
cbCustomer.setVisible(isOwned);
|
||||
cbStore.setVisible(isAvailable);
|
||||
});
|
||||
|
||||
//Set up mouse handlers for buttons
|
||||
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
@@ -97,6 +145,9 @@ public class PetDialogController {
|
||||
btnChangeImage.setOnMouseClicked(mouseEvent -> handleChangeImage());
|
||||
btnRemoveImage.setOnMouseClicked(mouseEvent -> handleRemoveImage());
|
||||
refreshImagePreview();
|
||||
|
||||
loadCustomers();
|
||||
loadStores();
|
||||
}
|
||||
|
||||
private void buttonSaveClicked(MouseEvent mouseEvent) {
|
||||
@@ -111,6 +162,10 @@ public class PetDialogController {
|
||||
if (cbPetStatus.getSelectionModel().getSelectedItem() == null){
|
||||
errorMsg += "Status is required";
|
||||
}
|
||||
String selectedStatus = cbPetStatus.getValue();
|
||||
if ("Owned".equalsIgnoreCase(selectedStatus) && cbCustomer.getValue() == null) {
|
||||
errorMsg += "Customer is required for Owned status\n";
|
||||
}
|
||||
|
||||
//Check validation (length size)
|
||||
errorMsg += Validator.isLessThanVarChars(txtPetName.getText(), "Pet Name", 50);
|
||||
@@ -184,9 +239,81 @@ public class PetDialogController {
|
||||
}
|
||||
request.setPetAge(age);
|
||||
|
||||
String status = cbPetStatus.getValue();
|
||||
if ("Owned".equalsIgnoreCase(status) && cbCustomer.getValue() != null) {
|
||||
request.setCustomerId(cbCustomer.getValue().getId());
|
||||
}
|
||||
if (("Available".equalsIgnoreCase(status) || "Unadopted".equalsIgnoreCase(status)) && cbStore.getValue() != null) {
|
||||
request.setStoreId(cbStore.getValue().getId());
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private void loadCustomers() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
|
||||
Platform.runLater(() -> {
|
||||
cbCustomer.setItems(FXCollections.observableArrayList(customers));
|
||||
applySelectedCustomer();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"PetDialogController.loadCustomers", e, "Loading customers");
|
||||
cbCustomer.setDisable(true);
|
||||
cbCustomer.setPromptText("Unable to load customers");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadStores() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<DropdownOption> stores = DropdownApi.getInstance().getStores();
|
||||
Platform.runLater(() -> {
|
||||
cbStore.setItems(FXCollections.observableArrayList(stores));
|
||||
applySelectedStore();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"PetDialogController.loadStores", e, "Loading stores");
|
||||
cbStore.setDisable(true);
|
||||
cbStore.setPromptText("Unable to load stores");
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void applySelectedCustomer() {
|
||||
if (pendingCustomerId == null) return;
|
||||
DropdownOption selected = findOptionById(cbCustomer.getItems(), pendingCustomerId);
|
||||
if (selected != null) {
|
||||
cbCustomer.setValue(selected);
|
||||
pendingCustomerId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void applySelectedStore() {
|
||||
if (pendingStoreId == null) return;
|
||||
DropdownOption selected = findOptionById(cbStore.getItems(), pendingStoreId);
|
||||
if (selected != null) {
|
||||
cbStore.setValue(selected);
|
||||
pendingStoreId = null;
|
||||
}
|
||||
}
|
||||
|
||||
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 closeStage(MouseEvent mouseEvent) {
|
||||
Node node = (Node) mouseEvent.getSource();
|
||||
Stage stage = (Stage) node.getScene().getWindow();
|
||||
@@ -206,14 +333,14 @@ public class PetDialogController {
|
||||
removeImageRequested = false;
|
||||
refreshImagePreview();
|
||||
|
||||
//get the right combobox selection
|
||||
pendingCustomerId = pet.getCustomerId() > 0 ? pet.getCustomerId() : null;
|
||||
pendingStoreId = pet.getStoreId() > 0 ? pet.getStoreId() : null;
|
||||
|
||||
for (String status : cbPetStatus.getItems()) {
|
||||
if(status.equals(pet.getPetStatus())){
|
||||
cbPetStatus.getSelectionModel().select(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ public class Pet {
|
||||
private SimpleStringProperty petStatus;
|
||||
private SimpleDoubleProperty petPrice;
|
||||
private SimpleStringProperty imageUrl;
|
||||
private SimpleStringProperty customerName = new SimpleStringProperty("");
|
||||
private SimpleStringProperty storeName = new SimpleStringProperty("");
|
||||
private long customerId = 0L;
|
||||
private long storeId = 0L;
|
||||
|
||||
public Pet(int petId, String petName, String petSpecies, String petBreed, int petAge, String petStatus, double petPrice, String imageUrl) {
|
||||
this.petId = new SimpleIntegerProperty(petId);
|
||||
@@ -120,4 +124,44 @@ public class Pet {
|
||||
public SimpleStringProperty imageUrlProperty() {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName.get();
|
||||
}
|
||||
|
||||
public void setCustomerName(String customerName) {
|
||||
this.customerName.set(customerName != null ? customerName : "");
|
||||
}
|
||||
|
||||
public SimpleStringProperty customerNameProperty() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public String getStoreName() {
|
||||
return storeName.get();
|
||||
}
|
||||
|
||||
public void setStoreName(String storeName) {
|
||||
this.storeName.set(storeName != null ? storeName : "");
|
||||
}
|
||||
|
||||
public SimpleStringProperty storeNameProperty() {
|
||||
return storeName;
|
||||
}
|
||||
|
||||
public long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="560.0" prefWidth="790.0" spacing="20.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.PetDialogController">
|
||||
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="660.0" prefWidth="790.0" spacing="20.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.PetDialogController">
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT" prefHeight="79.0" prefWidth="727.0" spacing="20.0" style="-fx-background-color: #2C3E50; -fx-background-radius: 14;">
|
||||
<children>
|
||||
@@ -153,6 +153,34 @@
|
||||
</TextField>
|
||||
</children>
|
||||
</VBox>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="3">
|
||||
<children>
|
||||
<Label text="Customer:" textFill="#2c3e50">
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<ComboBox fx:id="cbCustomer" prefHeight="29.0" prefWidth="336.0" promptText="Select Customer" 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>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1" GridPane.rowIndex="3">
|
||||
<children>
|
||||
<Label text="Store:" textFill="#2c3e50">
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<ComboBox fx:id="cbStore" prefHeight="29.0" prefWidth="336.0" promptText="Select Store" 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>
|
||||
<VBox.margin>
|
||||
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
|
||||
|
||||
@@ -79,6 +79,8 @@
|
||||
<TableColumn fx:id="colPetAge" prefWidth="60.0" text="Age" />
|
||||
<TableColumn fx:id="colPetStatus" prefWidth="110.0" text="Status" />
|
||||
<TableColumn fx:id="colPetPrice" prefWidth="80.0" text="Price" />
|
||||
<TableColumn fx:id="colCustomerName" prefWidth="130.0" text="Owner" />
|
||||
<TableColumn fx:id="colStoreName" prefWidth="130.0" text="Store" />
|
||||
</columns>
|
||||
</TableView>
|
||||
</children>
|
||||
|
||||
Reference in New Issue
Block a user