Просмотр исходного кода

fix(coupon): 会员JOIN赠券结算可选可用(家庭券状态/ID契约/下单核销三处修复)

- CouponService.listAvailable: 改为返回 family_coupon VO(id=family_coupon.id,
  status=AVAILABLE),修复前端 status!=AVAILABLE 过滤导致券不可见 + 前端传券ID
  与 apply/markUsed 契约不一致
- ProductOrderService.create/createMultiItem: 下单时校验并计入优惠券抵扣金额
- ProductOrderService 支付成功 markUsed 核销、取消订单 revert 归还
- checkout.vue: 下单请求携带 couponId=selectedCoupon.id
Xiaogang Liao 1 неделя назад
Родитель
Сommit
d168772885

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/CouponController.java

@@ -24,7 +24,7 @@ public class CouponController {
     private PointsExchangeService pointsExchangeService;
 
     @PostMapping("/list")
-    public Result<List<Coupon>> list(@RequestAttribute("userId") Long userId) {
+    public Result<List<Map<String, Object>>> list(@RequestAttribute("userId") Long userId) {
         return Result.success(couponService.listAvailable(userId));
     }
 

+ 16 - 12
cfc-backend/src/main/java/com/etotem/cfc/service/CouponService.java

@@ -94,28 +94,32 @@ public class CouponService {
     // 对外保留接口(签名不变,内部改为读 family_coupon)
     // ----------------------------------------------------------------
 
-    public List<Coupon> listAvailable(Long userId) {
+    public List<Map<String, Object>> listAvailable(Long userId) {
         Long familyId = getFamilyId(userId);
         if (familyId == null) return new ArrayList<>();
         List<FamilyCoupon> familyCoupons = familyCouponMapper.selectList(
                 new LambdaQueryWrapper<FamilyCoupon>()
                         .eq(FamilyCoupon::getFamilyId, familyId)
-                        .eq(FamilyCoupon::getStatus, "AVAILABLE"));
+                        .eq(FamilyCoupon::getStatus, "AVAILABLE")
+                        .orderByAsc(FamilyCoupon::getId));
         if (familyCoupons.isEmpty()) {
             return new ArrayList<>();
         }
-        List<Long> couponIds = new ArrayList<>();
+        Date now = new Date();
+        List<Map<String, Object>> result = new ArrayList<>();
         for (FamilyCoupon fc : familyCoupons) {
-            couponIds.add(fc.getCouponId());
+            Coupon coupon = couponMapper.selectById(fc.getCouponId());
+            if (coupon == null) continue;
+            if (!"ACTIVE".equals(coupon.getStatus())) continue;
+            if (coupon.getValidFrom() != null && now.before(coupon.getValidFrom())) continue;
+            if (coupon.getValidUntil() != null && now.after(coupon.getValidUntil())) continue;
+            Map<String, Object> vo = toCouponVO(fc);
+            if (vo != null) result.add(vo);
         }
-        Date now = new Date();
-        return couponMapper.selectList(
-                new LambdaQueryWrapper<Coupon>()
-                        .in(Coupon::getId, couponIds)
-                        .and(w -> w.isNull(Coupon::getStatus).or().eq(Coupon::getStatus, "ACTIVE"))
-                        .le(Coupon::getValidFrom, now)
-                        .ge(Coupon::getValidUntil, now)
-                        .orderByAsc(Coupon::getValue));
+        result.sort((a, b) -> Integer.compare(
+                ((Number) a.getOrDefault("value", 0)).intValue(),
+                ((Number) b.getOrDefault("value", 0)).intValue()));
+        return result;
     }
 
     @Transactional

+ 41 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java

@@ -33,6 +33,7 @@ import com.etotem.cfc.entity.SupplyCustomerRelation;
 import com.etotem.cfc.mapper.SupplySystemMemberMapper;
 import com.etotem.cfc.mapper.SupplyCustomerRelationMapper;
 import com.etotem.cfc.service.SupplyHierarchyService;
+import com.etotem.cfc.service.CouponService;
 import com.etotem.cfc.mapper.CouponMapper;
 import com.etotem.cfc.mapper.FamilyCouponMapper;
 import com.etotem.cfc.mapper.ProductMapper;
@@ -198,6 +199,9 @@ public class ProductOrderService {
     @Resource
     private FamilyCouponMapper familyCouponMapper;
 
+    @Resource
+    private CouponService couponService;
+
     public Result<ProductOrderDTO> create(CreateProductOrderDTO dto, Long buyerId) {
         if (buyerId == null) {
             return Result.error("请先登录");
@@ -251,6 +255,15 @@ public class ProductOrderService {
         int pointsCost = dto.getPointsUsed() != null ? dto.getPointsUsed() : 0;
         order.setPointsCost(pointsCost);
         int discountAmount = dto.getDiscountAmount() != null ? dto.getDiscountAmount() : 0;
+        // 优惠券抵扣:校验并计入优惠金额(orderType=PRODUCT)
+        Long appliedCouponId = null;
+        if (dto.getCouponId() != null) {
+            Integer couponDiscount = couponService.apply(buyerId, dto.getCouponId(), "PRODUCT", order.getTotalAmount());
+            if (couponDiscount != null && couponDiscount > 0) {
+                discountAmount += couponDiscount;
+                appliedCouponId = dto.getCouponId();
+            }
+        }
         int moneyAmount = order.getTotalAmount() - pointsCost - discountAmount;
         order.setMoneyAmount(moneyAmount > 0 ? moneyAmount : 0);
         order.setGiftAmount(productGiftRuleService.getActiveByProductId(product.getId()) != null
@@ -260,7 +273,7 @@ public class ProductOrderService {
         order.setRemark(dto.getRemark());
         order.setCoverImage(product.getCoverImage());
         order.setDiscountAmount(discountAmount);
-        order.setCouponId(dto.getCouponId());
+        order.setCouponId(appliedCouponId);
         resolveOrderAddress(order, dto, buyerId);
         if (dto.getPurchaseInfo() != null && !dto.getPurchaseInfo().isEmpty()) {
             order.setPurchaseInfo(JSON.toJSONString(dto.getPurchaseInfo()));
@@ -601,6 +614,15 @@ public class ProductOrderService {
         int pointsCost = dto.getPointsUsed() != null ? dto.getPointsUsed() : 0;
         order.setPointsCost(pointsCost);
         int multiDiscountAmount = dto.getDiscountAmount() != null ? dto.getDiscountAmount() : 0;
+        // 优惠券抵扣:校验并计入优惠金额(orderType=PRODUCT)
+        Long appliedCouponId = null;
+        if (dto.getCouponId() != null) {
+            Integer couponDiscount = couponService.apply(buyerId, dto.getCouponId(), "PRODUCT", totalAmount);
+            if (couponDiscount != null && couponDiscount > 0) {
+                multiDiscountAmount += couponDiscount;
+                appliedCouponId = dto.getCouponId();
+            }
+        }
         int moneyAmount = totalAmount - pointsCost - multiDiscountAmount;
         order.setMoneyAmount(moneyAmount > 0 ? moneyAmount : 0);
         order.setStatus("pending");
@@ -608,7 +630,7 @@ public class ProductOrderService {
         order.setRemark(dto.getRemark());
         order.setCoverImage(firstItem.getCoverImage() != null ? firstItem.getCoverImage() : firstProduct.getCoverImage());
         order.setDiscountAmount(multiDiscountAmount);
-        order.setCouponId(dto.getCouponId());
+        order.setCouponId(appliedCouponId);
         resolveOrderAddress(order, dto, buyerId);
         if (dto.getPurchaseInfo() != null && !dto.getPurchaseInfo().isEmpty()) {
             order.setPurchaseInfo(JSON.toJSONString(dto.getPurchaseInfo()));
@@ -812,6 +834,15 @@ public class ProductOrderService {
         order.setUpdatedAt(new Date());
         orderMapper.updateById(order);
 
+        // 核销优惠券:订单支付成功后标记为已使用
+        if (order.getCouponId() != null) {
+            try {
+                couponService.markUsed(order.getCouponId(), order.getId());
+            } catch (Exception e) {
+                log.error("优惠券核销失败: orderNo={}, couponId={}, error={}", orderNo, order.getCouponId(), e.getMessage());
+            }
+        }
+
         // CF值:统一分佣(当前人 + 推荐人)
         try {
             cfCommissionService.settle(order.getId(), "product", order.getBuyerId(),
@@ -970,6 +1001,14 @@ public class ProductOrderService {
         order.setStatus("cancelled");
         order.setUpdatedAt(new Date());
         orderMapper.updateById(order);
+        // 取消订单:归还优惠券
+        if (order.getCouponId() != null) {
+            try {
+                couponService.revert(order.getCouponId());
+            } catch (Exception e) {
+                log.error("取消订单归还优惠券失败: orderNo={}, couponId={}, error={}", orderNo, order.getCouponId(), e.getMessage());
+            }
+        }
         // Restore stock on cancel
         productService.increaseStock(order.getProductId(), order.getQuantity());
         // 记录退款入库流水

+ 1 - 0
cfc-frontend/pages/shop/checkout/checkout.vue

@@ -1026,6 +1026,7 @@ export default {
           purchaseInfo: that.purchaseForm,
           pointsUsed: that.pointsUsed,
           discountAmount: (that.discountInfo ? that.discountInfo.discountAmount : 0) + that.giftPromoDiscount,
+          couponId: that.selectedCoupon ? that.selectedCoupon.id : null,
           deliveryMethod: that.deliveryMethod,
           pickupLocationId: that.selectedPickupLocation ? that.selectedPickupLocation.id : null,
           timeSlotId: that.selectedTimeSlot ? that.selectedTimeSlot.id : null,