Merge branch 'main' into AttachmentsToChat

This commit is contained in:
Alex
2026-04-09 13:44:24 -06:00
13 changed files with 270 additions and 73 deletions

View File

@@ -11,7 +11,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@RestController
@@ -25,12 +27,14 @@ public class DropdownController {
private final StoreRepository storeRepository;
private final SupplierRepository supplierRepository;
private final UserRepository userRepository;
private final AdoptionRepository adoptionRepository;
public DropdownController(PetRepository petRepository,
ServiceRepository serviceRepository, ProductRepository productRepository,
CategoryRepository categoryRepository, StoreRepository storeRepository,
SupplierRepository supplierRepository,
UserRepository userRepository) {
UserRepository userRepository,
AdoptionRepository adoptionRepository) {
this.petRepository = petRepository;
this.serviceRepository = serviceRepository;
this.productRepository = productRepository;
@@ -38,6 +42,7 @@ public class DropdownController {
this.storeRepository = storeRepository;
this.supplierRepository = supplierRepository;
this.userRepository = userRepository;
this.adoptionRepository = adoptionRepository;
}
@GetMapping("/pets")
@@ -74,8 +79,19 @@ public class DropdownController {
@GetMapping("/appointment-customers")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<List<DropdownOption>> getAppointmentCustomers() {
Set<Long> ownersWithPets = petRepository.findAll().stream()
.filter(p -> p.getOwner() != null)
.map(p -> p.getOwner().getId())
.collect(Collectors.toSet());
Set<Long> customersWithAdoptions = adoptionRepository.findAll().stream()
.filter(a -> "Completed".equalsIgnoreCase(a.getAdoptionStatus()))
.map(a -> a.getCustomer().getId())
.collect(Collectors.toSet());
Set<Long> customersWithPets = new HashSet<>(ownersWithPets);
customersWithPets.addAll(customersWithAdoptions);
return ResponseEntity.ok(
userRepository.findByRoleAndActiveTrue(User.Role.CUSTOMER).stream()
.filter(u -> customersWithPets.contains(u.getId()))
.map(u -> new DropdownOption(u.getId(), u.getFirstName() + " " + u.getLastName()))
.sorted(Comparator.comparing(DropdownOption::getLabel, String.CASE_INSENSITIVE_ORDER))
.collect(Collectors.toList())
@@ -166,11 +182,16 @@ public class DropdownController {
@GetMapping("/customers/{customerId}/pets")
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<List<DropdownOption>> getCustomerPets(@PathVariable Long customerId) {
return ResponseEntity.ok(
petRepository.findAllByOwner_IdOrderByPetNameAsc(customerId).stream()
Set<Long> seen = new HashSet<>();
List<DropdownOption> pets = new java.util.ArrayList<>();
petRepository.findAllByOwner_IdOrderByPetNameAsc(customerId).stream()
.map(p -> new DropdownOption(p.getPetId(), p.getPetName()))
.collect(Collectors.toList())
);
.forEach(o -> { if (seen.add(o.getId())) pets.add(o); });
adoptionRepository.findByCustomer_IdAndAdoptionStatusIgnoreCase(customerId, "Completed").stream()
.map(a -> new DropdownOption(a.getPet().getPetId(), a.getPet().getPetName()))
.forEach(o -> { if (seen.add(o.getId())) pets.add(o); });
pets.sort(java.util.Comparator.comparing(DropdownOption::getLabel, String.CASE_INSENSITIVE_ORDER));
return ResponseEntity.ok(pets);
}
@GetMapping("/suppliers")

View File

@@ -22,6 +22,8 @@ public class AdoptionRequest {
private Long sourceStoreId;
private String paymentMethod;
public Long getPetId() {
return petId;
}
@@ -70,6 +72,14 @@ public class AdoptionRequest {
this.sourceStoreId = sourceStoreId;
}
public String getPaymentMethod() {
return paymentMethod;
}
public void setPaymentMethod(String paymentMethod) {
this.paymentMethod = paymentMethod;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@@ -9,6 +9,7 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
@Repository
@@ -37,4 +38,6 @@ public interface AdoptionRepository extends JpaRepository<Adoption, Long> {
boolean existsByPet_IdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(Long petId, String adoptionStatus, Long adoptionId);
boolean existsByPet_IdAndAdoptionStatusIgnoreCase(Long petId, String adoptionStatus);
List<Adoption> findByCustomer_IdAndAdoptionStatusIgnoreCase(Long customerId, String adoptionStatus);
}

View File

@@ -5,11 +5,13 @@ import com.petshop.backend.dto.adoption.AdoptionResponse;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.entity.Adoption;
import com.petshop.backend.entity.Pet;
import com.petshop.backend.entity.Sale;
import com.petshop.backend.entity.StoreLocation;
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.repository.SaleRepository;
import com.petshop.backend.repository.StoreRepository;
import com.petshop.backend.repository.UserRepository;
import org.springframework.data.domain.Page;
@@ -17,7 +19,9 @@ import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Service
public class AdoptionService {
@@ -32,12 +36,14 @@ public class AdoptionService {
private final PetRepository petRepository;
private final UserRepository userRepository;
private final StoreRepository storeRepository;
private final SaleRepository saleRepository;
public AdoptionService(AdoptionRepository adoptionRepository, PetRepository petRepository, UserRepository userRepository, StoreRepository storeRepository) {
public AdoptionService(AdoptionRepository adoptionRepository, PetRepository petRepository, UserRepository userRepository, StoreRepository storeRepository, SaleRepository saleRepository) {
this.adoptionRepository = adoptionRepository;
this.petRepository = petRepository;
this.userRepository = userRepository;
this.storeRepository = storeRepository;
this.saleRepository = saleRepository;
}
public Page<AdoptionResponse> getAllAdoptions(String query, Long customerId, String status, Long storeId, LocalDate date, Pageable pageable) {
@@ -81,6 +87,9 @@ public class AdoptionService {
: null;
String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus());
validatePetAvailability(pet, null, null);
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) && request.getAdoptionDate() != null && request.getAdoptionDate().isAfter(LocalDate.now())) {
throw new IllegalArgumentException("Cannot mark a future-dated adoption as Completed");
}
Adoption adoption = new Adoption();
adoption.setPet(pet);
@@ -91,7 +100,10 @@ public class AdoptionService {
adoption.setAdoptionStatus(adoptionStatus);
adoption = adoptionRepository.save(adoption);
syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId());
syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId(), customer);
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus)) {
createSaleForAdoption(adoption, request.getPaymentMethod());
}
return mapToResponse(adoption);
}
@@ -110,9 +122,13 @@ public class AdoptionService {
? storeRepository.findById(request.getSourceStoreId())
.orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getSourceStoreId()))
: null;
boolean wasCompleted = ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoption.getAdoptionStatus());
String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus());
Long currentPetId = adoption.getPet() != null ? adoption.getPet().getPetId() : null;
validatePetAvailability(pet, adoption.getAdoptionId(), currentPetId);
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) && request.getAdoptionDate() != null && request.getAdoptionDate().isAfter(LocalDate.now())) {
throw new IllegalArgumentException("Cannot mark a future-dated adoption as Completed");
}
adoption.setPet(pet);
adoption.setCustomer(customer);
@@ -122,7 +138,10 @@ public class AdoptionService {
adoption.setAdoptionStatus(adoptionStatus);
adoption = adoptionRepository.save(adoption);
syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId());
syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId(), customer);
if (!wasCompleted && ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus)) {
createSaleForAdoption(adoption, request.getPaymentMethod());
}
return mapToResponse(adoption);
}
@@ -216,13 +235,37 @@ public class AdoptionService {
}
}
private void syncPetStatus(Pet pet, String adoptionStatus, Long adoptionId) {
private void createSaleForAdoption(Adoption adoption, String paymentMethod) {
if (adoption.getEmployee() == null || adoption.getSourceStore() == null) {
return;
}
BigDecimal amount = adoption.getPet().getPetPrice();
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
amount = BigDecimal.ZERO;
}
Sale sale = new Sale();
sale.setSaleDate(LocalDateTime.now());
sale.setEmployee(adoption.getEmployee());
sale.setStore(adoption.getSourceStore());
sale.setCustomer(adoption.getCustomer());
sale.setTotalAmount(amount);
sale.setSubtotalAmount(amount);
sale.setPaymentMethod(paymentMethod != null && !paymentMethod.isBlank() ? paymentMethod : "Cash");
sale.setIsRefund(false);
sale.setChannel("ADOPTION");
saleRepository.save(sale);
}
private void syncPetStatus(Pet pet, String adoptionStatus, Long adoptionId, User customer) {
boolean completedElsewhere = adoptionId != null
&& adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId);
if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) || completedElsewhere) {
pet.setPetStatus(PET_STATUS_ADOPTED);
pet.setOwner(customer);
pet.setStore(null);
} else {
pet.setPetStatus(PET_STATUS_AVAILABLE);
pet.setOwner(null);
}
petRepository.save(pet);
}