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

merge: COUPON-C2 合入 cfclub(清死代码 + used_count 修复)

- 删除 CouponGrantLog/UserCoupon 实体、CouponGrantLogMapper/UserCouponMapper 死代码
- AdminCouponController 清除 CouponGrantLogMapper 死引用(保留 CouponGiftService)
- CouponService used_count 漂移修复(+39)
- 冲突:仅 AdminCouponController.java import 区,已解
Agnes Agent 1 неделя назад
Родитель
Сommit
20a768f497

+ 0 - 5
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminCouponController.java

@@ -4,10 +4,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.entity.Coupon;
-import com.etotem.cfc.entity.CouponGrantLog;
 import com.etotem.cfc.entity.FamilyCoupon;
 import com.etotem.cfc.entity.FamilyCouponGrantLog;
-import com.etotem.cfc.mapper.CouponGrantLogMapper;
 import com.etotem.cfc.service.CouponGiftService;
 import com.etotem.cfc.mapper.CouponMapper;
 import com.etotem.cfc.mapper.FamilyCouponGrantLogMapper;
@@ -36,9 +34,6 @@ public class AdminCouponController {
     @Resource
     private CouponMapper couponMapper;
 
-    @Resource
-    private CouponGrantLogMapper couponGrantLogMapper;
-
     @Resource
     private FamilyCouponGrantLogMapper familyCouponGrantLogMapper;
 

+ 0 - 34
cfc-backend/src/main/java/com/etotem/cfc/entity/CouponGrantLog.java

@@ -1,34 +0,0 @@
-package com.etotem.cfc.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.util.Date;
-
-@Data
-@TableName("coupon_grant_log")
-public class CouponGrantLog implements Serializable {
-
-    @TableId(type = IdType.AUTO)
-    private Long id;
-
-    private Long userId;
-
-    private Long couponId;
-
-    /** JOIN/PERIODIC/POPULATION/EXCHANGE */
-    private String grantType;
-
-    /** 周期标识(YYYY-MM或YYYY-Qn), PERIODIC防重用 */
-    private String period;
-
-    private Integer quantity;
-
-    /** 触发来源(订单号/成员ID等) */
-    private String source;
-
-    private Date createdAt;
-}

+ 0 - 33
cfc-backend/src/main/java/com/etotem/cfc/entity/UserCoupon.java

@@ -1,33 +0,0 @@
-package com.etotem.cfc.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.util.Date;
-
-@Data
-@TableName("user_coupon")
-public class UserCoupon implements Serializable {
-
-    @TableId(type = IdType.AUTO)
-    private Long id;
-
-    private Long userId;
-
-    private Long couponId;
-
-    /** 状态: AVAILABLE / USED */
-    private String status;
-
-    private Date receivedAt;
-
-    private Date usedAt;
-
-    private Long orderId;
-
-    /** 券实例归属家庭ID(优惠券家庭化 N3,家庭内任意成员可用) */
-    private Long familyId;
-}

+ 0 - 7
cfc-backend/src/main/java/com/etotem/cfc/mapper/CouponGrantLogMapper.java

@@ -1,7 +0,0 @@
-package com.etotem.cfc.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.etotem.cfc.entity.CouponGrantLog;
-
-public interface CouponGrantLogMapper extends BaseMapper<CouponGrantLog> {
-}

+ 0 - 9
cfc-backend/src/main/java/com/etotem/cfc/mapper/UserCouponMapper.java

@@ -1,9 +0,0 @@
-package com.etotem.cfc.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.etotem.cfc.entity.UserCoupon;
-import org.apache.ibatis.annotations.Mapper;
-
-@Mapper
-public interface UserCouponMapper extends BaseMapper<UserCoupon> {
-}

+ 39 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/CouponService.java

@@ -2,6 +2,7 @@ package com.etotem.cfc.service;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.etotem.cfc.entity.Coupon;
 import com.etotem.cfc.entity.FamilyCoupon;
 import com.etotem.cfc.entity.FamilyCouponGrantLog;
@@ -64,6 +65,36 @@ public class CouponService {
         return user.getFamilyId();
     }
 
+    /** 券实例是否处于已核销/占用中状态(决定还券时是否需要递减 used_count) */
+    private boolean isConsumedCoupon(String status) {
+        return "USED".equals(status) || "RESERVED".equals(status);
+    }
+
+    /**
+     * 还券/收回时同步递减券模板 used_count。
+     *
+     * <p>背景:{@link #claim} 领取时对 coupon.used_count 做 +1,但订单取消/超时还券
+     * ({@link #revert})与家庭人口券收回({@link #revokeFamilyPopulationCoupons})原先不减,
+     * 导致 used_count 只增不减、与家庭维度实际券实例数漂移,并使 claim() 的库存校验
+     * (used_count &gt;= total_count 判定「已领完」)失真。
+     *
+     * <p>原子更新(used_count &gt; 0 才 -1),避免并发下减成负数;NULL 视为未知,不做处理。
+     *
+     * @param couponId 券模板ID(coupon.id)
+     */
+    private void decrementUsedCount(Long couponId) {
+        if (couponId == null) {
+            return;
+        }
+        int updated = couponMapper.update(null, new LambdaUpdateWrapper<Coupon>()
+                .eq(Coupon::getId, couponId)
+                .gt(Coupon::getUsedCount, 0)
+                .setSql("used_count = used_count - 1"));
+        if (updated > 0) {
+            logger.info("还券/收回:券模板 used_count 递减 couponId={}, updated={}", couponId, updated);
+        }
+    }
+
     /** 构建与 CouponService.listMyCoupons 相同格式的响应 Map */
     private Map<String, Object> toCouponVO(FamilyCoupon fc) {
         Coupon coupon = couponMapper.selectById(fc.getCouponId());
@@ -260,10 +291,16 @@ public class CouponService {
     public void revert(Long userCouponId) {
         FamilyCoupon fc = familyCouponMapper.selectById(userCouponId);
         if (fc != null) {
+            // 先记录还券前状态:仅从 USED/RESERVED 归还时才递减 used_count,避免重复还券误减
+            boolean consumed = isConsumedCoupon(fc.getStatus());
             fc.setStatus("AVAILABLE");
             fc.setUsedAt(null);
             fc.setOrderId(null);
             familyCouponMapper.updateById(fc);
+            if (consumed) {
+                // 还券:同步递减券模板 used_count,与 claim() 的递增配平
+                decrementUsedCount(fc.getCouponId());
+            }
         }
     }
 
@@ -560,6 +597,8 @@ public class CouponService {
             for (FamilyCoupon fc : coupons) {
                 fc.setStatus("REVOKED");
                 familyCouponMapper.updateById(fc);
+                // 收回人口券:同步递减券模板 used_count,与 revert() 口径一致
+                decrementUsedCount(fc.getCouponId());
             }
         }
         logger.info("收回家庭人口券完成: familyId={}, memberId={}, 作废券数={}", familyId, memberId,