Merge branch 'AttachmentsToChat'

This commit is contained in:
Alex
2026-04-13 22:36:53 -06:00
53 changed files with 1285 additions and 390 deletions

View File

@@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Objects;
import java.math.BigDecimal;
public class SaleRequest {
@NotNull(message = "Store ID is required")
@@ -28,7 +29,9 @@ public class SaleRequest {
private Long cartId;
private Boolean useLoyaltyPoints = false;
private Integer pointsUsed;
private BigDecimal pointsDiscountAmount;
public Long getStoreId() {
return storeId;
@@ -102,12 +105,20 @@ public class SaleRequest {
this.cartId = cartId;
}
public Boolean getUseLoyaltyPoints() {
return useLoyaltyPoints;
public Integer getPointsUsed() {
return pointsUsed;
}
public void setUseLoyaltyPoints(Boolean useLoyaltyPoints) {
this.useLoyaltyPoints = useLoyaltyPoints;
public void setPointsUsed(Integer pointsUsed) {
this.pointsUsed = pointsUsed;
}
public BigDecimal getPointsDiscountAmount() {
return pointsDiscountAmount;
}
public void setPointsDiscountAmount(BigDecimal pointsDiscountAmount) {
this.pointsDiscountAmount = pointsDiscountAmount;
}
@Override

View File

@@ -21,6 +21,8 @@ public class SaleResponse {
private BigDecimal loyaltyDiscountAmount;
private Integer pointsUsed;
private Integer pointsEarned;
private Integer pointsUsed;
private BigDecimal pointsDiscountAmount;
private String channel;
private Long couponId;
private Long cartId;
@@ -153,6 +155,22 @@ public class SaleResponse {
this.pointsEarned = pointsEarned;
}
public Integer getPointsUsed() {
return pointsUsed;
}
public void setPointsUsed(Integer pointsUsed) {
this.pointsUsed = pointsUsed;
}
public BigDecimal getPointsDiscountAmount() {
return pointsDiscountAmount;
}
public void setPointsDiscountAmount(BigDecimal pointsDiscountAmount) {
this.pointsDiscountAmount = pointsDiscountAmount;
}
public String getChannel() {
return channel;
}

View File

@@ -39,6 +39,8 @@ public class UserRequest {
private Boolean active = true;
private Integer loyaltyPoints;
public String getUsername() {
return username;
}
@@ -127,6 +129,14 @@ public class UserRequest {
this.active = active;
}
public Integer getLoyaltyPoints() {
return loyaltyPoints;
}
public void setLoyaltyPoints(Integer loyaltyPoints) {
this.loyaltyPoints = loyaltyPoints;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@@ -75,6 +75,12 @@ public class Sale {
@Column(nullable = false)
private Integer pointsEarned = 0;
@Column(nullable = false)
private Integer pointsUsed = 0;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal pointsDiscountAmount = BigDecimal.ZERO;
@OneToMany(mappedBy = "sale", cascade = CascadeType.ALL)
private List<SaleItem> items = new ArrayList<>();
@@ -233,6 +239,22 @@ public class Sale {
this.pointsEarned = pointsEarned;
}
public Integer getPointsUsed() {
return pointsUsed;
}
public void setPointsUsed(Integer pointsUsed) {
this.pointsUsed = pointsUsed;
}
public BigDecimal getPointsDiscountAmount() {
return pointsDiscountAmount;
}
public void setPointsDiscountAmount(BigDecimal pointsDiscountAmount) {
this.pointsDiscountAmount = pointsDiscountAmount;
}
public List<SaleItem> getItems() {
return items;
}

View File

@@ -19,7 +19,7 @@ public interface PetRepository extends JpaRepository<Pet, Long> {
"WHERE LOWER(p.petStatus) = 'available' " +
"AND NOT EXISTS (" +
" SELECT 1 FROM Adoption a " +
" WHERE a.pet = p AND LOWER(a.adoptionStatus) = 'completed'" +
" WHERE a.pet = p AND (LOWER(a.adoptionStatus) = 'completed' OR LOWER(a.adoptionStatus) = 'pending')" +
") " +
"ORDER BY p.petName ASC")
List<Pet> findAdoptablePetsOrderByPetNameAsc();
@@ -29,7 +29,7 @@ public interface PetRepository extends JpaRepository<Pet, Long> {
"AND (:storeId IS NULL OR p.store.storeId = :storeId) " +
"AND NOT EXISTS (" +
" SELECT 1 FROM Adoption a " +
" WHERE a.pet = p AND LOWER(a.adoptionStatus) = 'completed'" +
" WHERE a.pet = p AND (LOWER(a.adoptionStatus) = 'completed' OR LOWER(a.adoptionStatus) = 'pending')" +
") " +
"ORDER BY p.petName ASC")
List<Pet> findAdoptablePetsByStore(@Param("storeId") Long storeId);

View File

@@ -32,6 +32,7 @@ public class AdoptionService {
private static final String ADOPTION_STATUS_MISSED = "Missed";
private static final String PET_STATUS_AVAILABLE = "Available";
private static final String PET_STATUS_ADOPTED = "Adopted";
private static final String PET_STATUS_PENDING = "Pending";
private final AdoptionRepository adoptionRepository;
private final PetRepository petRepository;
@@ -263,10 +264,14 @@ public class AdoptionService {
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 if (ADOPTION_STATUS_PENDING.equalsIgnoreCase(adoptionStatus)) {
pet.setPetStatus(PET_STATUS_PENDING);
pet.setOwner(customer);
} else {
pet.setPetStatus(PET_STATUS_AVAILABLE);
pet.setOwner(null);

View File

@@ -160,14 +160,33 @@ public class SaleService {
saleItems.add(saleItem);
subtotalAmount = subtotalAmount.add(itemTotal);
}
subtotalAmount = subtotalAmount.negate();
sale.setSubtotalAmount(subtotalAmount);
sale.setTotalAmount(subtotalAmount);
sale.setCouponDiscountAmount(BigDecimal.ZERO);
sale.setEmployeeDiscountAmount(BigDecimal.ZERO);
sale.setLoyaltyDiscountAmount(BigDecimal.ZERO);
sale.setPointsUsed(0);
sale.setPointsEarned(0);
Sale originalSale = sale.getOriginalSale();
BigDecimal originalSubtotal = originalSale.getSubtotalAmount() != null
? originalSale.getSubtotalAmount()
: originalSale.getItems().stream()
.map(i -> i.getUnitPrice().multiply(BigDecimal.valueOf(Math.abs(i.getQuantity()))))
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal refundRatio = originalSubtotal.compareTo(BigDecimal.ZERO) != 0
? subtotalAmount.divide(originalSubtotal, 10, RoundingMode.HALF_UP)
: BigDecimal.ONE;
BigDecimal refundTotal = originalSale.getTotalAmount().abs()
.multiply(refundRatio).setScale(2, RoundingMode.HALF_UP);
sale.setSubtotalAmount(subtotalAmount.negate());
sale.setTotalAmount(refundTotal.negate());
User refundCustomer = customer != null ? customer : originalSale.getCustomer();
if (refundCustomer != null) {
int pointsToRestore = BigDecimal.valueOf(originalSale.getPointsUsed())
.multiply(refundRatio).setScale(0, RoundingMode.FLOOR).intValue();
int pointsToDeduct = BigDecimal.valueOf(originalSale.getPointsEarned())
.multiply(refundRatio).setScale(0, RoundingMode.FLOOR).intValue();
refundCustomer.setLoyaltyPoints(refundCustomer.getLoyaltyPoints() + pointsToRestore - pointsToDeduct);
userRepository.save(refundCustomer);
}
} else {
if (request.getItems() == null || request.getItems().isEmpty()) {
throw new BusinessException("At least one item is required");
@@ -204,14 +223,23 @@ public class SaleService {
BigDecimal couponDiscount = calculateCouponDiscount(sale.getCoupon(), subtotalAmount);
sale.setCouponDiscountAmount(couponDiscount);
BigDecimal employeeDiscount = calculateEmployeeDiscount(customer, subtotalAmount.subtract(couponDiscount));
BigDecimal pointsDiscount = BigDecimal.ZERO;
int pointsUsed = 0;
if (customer != null && request.getPointsUsed() != null && request.getPointsUsed() > 0) {
if (customer.getLoyaltyPoints() < request.getPointsUsed()) {
throw new BusinessException("Customer does not have enough loyalty points");
}
pointsUsed = request.getPointsUsed();
pointsDiscount = calculatePointsDiscount(pointsUsed);
customer.setLoyaltyPoints(customer.getLoyaltyPoints() - pointsUsed);
}
sale.setPointsUsed(pointsUsed);
sale.setPointsDiscountAmount(pointsDiscount);
BigDecimal employeeDiscount = calculateEmployeeDiscount(customer, subtotalAmount.subtract(couponDiscount).subtract(pointsDiscount));
sale.setEmployeeDiscountAmount(employeeDiscount);
BigDecimal loyaltyDiscount = calculateLoyaltyDiscount(customer, subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount), Boolean.TRUE.equals(request.getUseLoyaltyPoints()));
sale.setLoyaltyDiscountAmount(loyaltyDiscount);
sale.setPointsUsed(toPointsUsed(loyaltyDiscount));
BigDecimal finalTotal = subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount).subtract(loyaltyDiscount);
BigDecimal finalTotal = subtotalAmount.subtract(couponDiscount).subtract(pointsDiscount).subtract(employeeDiscount);
sale.setTotalAmount(finalTotal.max(BigDecimal.ZERO));
sale.setPointsEarned(sale.getTotalAmount().setScale(0, RoundingMode.FLOOR).intValue());
@@ -262,6 +290,10 @@ public class SaleService {
return discount.min(subtotal).setScale(2, RoundingMode.HALF_UP);
}
private BigDecimal calculatePointsDiscount(int pointsUsed) {
return new BigDecimal(pointsUsed).divide(new BigDecimal("20"), 2, RoundingMode.HALF_UP);
}
private BigDecimal calculateEmployeeDiscount(User customer, BigDecimal remainingAmount) {
if (customer == null || remainingAmount.compareTo(BigDecimal.ZERO) <= 0) {
return BigDecimal.ZERO;
@@ -328,6 +360,8 @@ public class SaleService {
response.setLoyaltyDiscountAmount(sale.getLoyaltyDiscountAmount());
response.setPointsUsed(sale.getPointsUsed());
response.setPointsEarned(sale.getPointsEarned());
response.setPointsUsed(sale.getPointsUsed());
response.setPointsDiscountAmount(sale.getPointsDiscountAmount());
response.setChannel(sale.getChannel());
if (sale.getCoupon() != null) {
response.setCouponId(sale.getCoupon().getCouponId());

View File

@@ -75,6 +75,9 @@ public class UserService {
user.setStaffRole(trimToNull(request.getStaffRole()));
user.setPrimaryStore(resolveStore(request.getPrimaryStoreId()));
user.setActive(request.getActive() != null ? request.getActive() : true);
if (request.getLoyaltyPoints() != null) {
user.setLoyaltyPoints(request.getLoyaltyPoints());
}
validateUniquePhone(user.getPhone(), null);
@@ -111,6 +114,9 @@ public class UserService {
user.setStaffRole(trimToNull(request.getStaffRole()));
user.setPrimaryStore(resolveStore(request.getPrimaryStoreId()));
user.setActive(request.getActive() != null ? request.getActive() : true);
if (request.getLoyaltyPoints() != null) {
user.setLoyaltyPoints(request.getLoyaltyPoints());
}
if (invalidateToken) {
user.setTokenVersion(user.getTokenVersion() + 1);
}

View File

@@ -0,0 +1,3 @@
ALTER TABLE sale
ADD COLUMN pointsUsed INT NOT NULL DEFAULT 0,
ADD COLUMN pointsDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00;

View File

@@ -230,6 +230,8 @@ CREATE TABLE IF NOT EXISTS sale (
couponDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
employeeDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
pointsEarned INT NOT NULL DEFAULT 0,
pointsUsed INT NOT NULL DEFAULT 0,
pointsDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_sale_employee FOREIGN KEY (employeeId) REFERENCES users(id),