From c244e5742a2f3fb614733425190369d4a7f3ee44 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Mon, 13 Apr 2026 19:46:28 -0600 Subject: [PATCH] added points to sale and logic backend --- .../petshop/backend/dto/sale/SaleRequest.java | 21 ++++++++ .../backend/dto/sale/SaleResponse.java | 18 +++++++ .../java/com/petshop/backend/entity/Sale.java | 22 ++++++++ .../petshop/backend/service/SaleService.java | 53 +++++++++++++++++-- .../V5__add_points_columns_to_sale.sql | 3 ++ .../dev/final-target/final_target_schema.sql | 2 + 6 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 backend/src/main/resources/db/migration/V5__add_points_columns_to_sale.sql diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java index 081ab05d..97248688 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java @@ -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,6 +29,10 @@ public class SaleRequest { private Long cartId; + private Integer pointsUsed; + + private BigDecimal pointsDiscountAmount; + public Long getStoreId() { return storeId; } @@ -100,6 +105,22 @@ public class SaleRequest { this.cartId = cartId; } + 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; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java index c1f2357f..eaebd4e8 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java @@ -19,6 +19,8 @@ public class SaleResponse { private BigDecimal couponDiscountAmount; private BigDecimal employeeDiscountAmount; private Integer pointsEarned; + private Integer pointsUsed; + private BigDecimal pointsDiscountAmount; private String channel; private Long couponId; private Long cartId; @@ -135,6 +137,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; } diff --git a/backend/src/main/java/com/petshop/backend/entity/Sale.java b/backend/src/main/java/com/petshop/backend/entity/Sale.java index 3bf4d8bf..f994a855 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Sale.java +++ b/backend/src/main/java/com/petshop/backend/entity/Sale.java @@ -69,6 +69,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 items = new ArrayList<>(); @@ -211,6 +217,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 getItems() { return items; } diff --git a/backend/src/main/java/com/petshop/backend/service/SaleService.java b/backend/src/main/java/com/petshop/backend/service/SaleService.java index 9d49972f..526f5a57 100644 --- a/backend/src/main/java/com/petshop/backend/service/SaleService.java +++ b/backend/src/main/java/com/petshop/backend/service/SaleService.java @@ -152,9 +152,33 @@ public class SaleService { saleItems.add(saleItem); subtotalAmount = subtotalAmount.add(itemTotal); } - subtotalAmount = subtotalAmount.negate(); - sale.setSubtotalAmount(subtotalAmount); - sale.setTotalAmount(subtotalAmount); + + 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 { for (var itemRequest : request.getItems()) { Product product = productRepository.findById(itemRequest.getProdId()) @@ -188,10 +212,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 finalTotal = subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount); + BigDecimal finalTotal = subtotalAmount.subtract(couponDiscount).subtract(pointsDiscount).subtract(employeeDiscount); sale.setTotalAmount(finalTotal.max(BigDecimal.ZERO)); sale.setPointsEarned(sale.getTotalAmount().setScale(0, RoundingMode.FLOOR).intValue()); @@ -240,6 +277,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; @@ -274,6 +315,8 @@ public class SaleService { response.setCouponDiscountAmount(sale.getCouponDiscountAmount()); response.setEmployeeDiscountAmount(sale.getEmployeeDiscountAmount()); 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()); diff --git a/backend/src/main/resources/db/migration/V5__add_points_columns_to_sale.sql b/backend/src/main/resources/db/migration/V5__add_points_columns_to_sale.sql new file mode 100644 index 00000000..288561ef --- /dev/null +++ b/backend/src/main/resources/db/migration/V5__add_points_columns_to_sale.sql @@ -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; diff --git a/backend/src/main/resources/dev/final-target/final_target_schema.sql b/backend/src/main/resources/dev/final-target/final_target_schema.sql index 9b8d279d..f515888e 100644 --- a/backend/src/main/resources/dev/final-target/final_target_schema.sql +++ b/backend/src/main/resources/dev/final-target/final_target_schema.sql @@ -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),