|
|
@@ -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 >= total_count 判定「已领完」)失真。
|
|
|
+ *
|
|
|
+ * <p>原子更新(used_count > 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,
|