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

chore: auto bump version and changelog [skip ci]

iwt 2 недель назад
Родитель
Сommit
b92c78d6af

+ 8 - 2
cfc-backend/src/main/java/com/etotem/cfc/controller/FamilyPlatformBalanceController.java

@@ -6,6 +6,7 @@ import com.etotem.cfc.entity.FamilyPlatformBalance;
 import com.etotem.cfc.entity.FamilyPlatformBalanceLog;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.mapper.UserMapper;
+import com.etotem.cfc.service.CfSpendDispatchService;
 import com.etotem.cfc.service.FamilyPlatformPointsService;
 import org.springframework.web.bind.annotation.*;
 
@@ -24,6 +25,10 @@ public class FamilyPlatformBalanceController {
     @Resource
     private FamilyPlatformPointsService familyPlatformPointsService;
 
+    /** F-5:管理端家庭 CF 值调整改投统一账本 cf_balance(家庭创建者成员维度,留操作人) */
+    @Resource
+    private CfSpendDispatchService cfSpendDispatchService;
+
     @Resource
     private UserMapper userMapper;
 
@@ -107,8 +112,9 @@ public class FamilyPlatformBalanceController {
         String remark = (String) params.get("remark");
         if (familyId == null || amount == 0) return Result.error("参数错误");
         try {
-            familyPlatformPointsService.adjust(familyId, amount,
-                    remark != null ? remark : "管理员" + adminId + "手动调整");
+            // F-5:不再写家庭池,改投统一账本 cf_balance(家庭创建者成员的账本,ref_type=admin_adjust)
+            cfSpendDispatchService.adjustForFamily(familyId, amount,
+                    remark != null ? remark : "管理员" + adminId + "手动调整", adminId);
             return Result.success("调整成功");
         } catch (Exception e) {
             return Result.error(e.getMessage());

+ 10 - 6
cfc-backend/src/main/java/com/etotem/cfc/controller/family/ParentController.java

@@ -6,7 +6,7 @@ import com.etotem.cfc.dto.RewardDTO;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.Wish;
 import com.etotem.cfc.mapper.WishMapper;
-import com.etotem.cfc.service.PointsService;
+import com.etotem.cfc.service.CfSpendDispatchService;
 import com.etotem.cfc.service.RewardService;
 import com.etotem.cfc.service.UserService;
 import javax.annotation.Resource;
@@ -30,8 +30,9 @@ public class ParentController {
     @Resource
     private WishMapper wishMapper;
 
+    /** F-5:家长心愿兑换扣减改投统一账本 cf_balance(家长自己的成员维度,幂等 refId = 心愿 ID) */
     @Resource
-    private PointsService pointsService;
+    private CfSpendDispatchService cfSpendDispatchService;
 
     /**
      * 家长心愿单 - 创建心愿(直接进入可用列表)
@@ -115,14 +116,17 @@ public class ParentController {
             return Result.error("心愿未设置积分");
         }
 
-        int currentBalance = user.getTotalPoints() == null ? 0 : user.getTotalPoints();
+        // F-5:余额与扣减都改投统一账本 cf_balance(不再读/写 users.total_points)
+        int currentBalance = cfSpendDispatchService.getAvailableForUser(userId);
         if (currentBalance < wish.getPointsRequired()) {
             return Result.error("积分不足,需要" + wish.getPointsRequired() + "积分,当前" + currentBalance + "积分");
         }
 
-        boolean deducted = pointsService.deductUserPoints(userId, wish.getPointsRequired(), "心愿兑换:" + wish.getTitle());
-        if (!deducted) {
-            return Result.error("积分扣除失败");
+        try {
+            cfSpendDispatchService.spendForUser(userId, wish.getPointsRequired(),
+                    "wish_exchange", wish.getId(), "心愿兑换:" + wish.getTitle());
+        } catch (RuntimeException e) {
+            return Result.error("积分扣除失败:" + e.getMessage());
         }
 
         wish.setStatus("exchanging");

+ 0 - 4
cfc-backend/src/main/java/com/etotem/cfc/service/FamilyPlatformPointsService.java

@@ -19,7 +19,6 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.annotation.Resource;
 import java.util.Date;
 import com.etotem.cfc.service.CouponService;
-import com.etotem.cfc.service.SysConfigService;
 import com.etotem.cfc.mapper.FamilyPlatformExchangeRecordMapper;
 import com.etotem.cfc.entity.FamilyPlatformExchangeRecord;
 import java.util.Calendar;
@@ -59,9 +58,6 @@ public class FamilyPlatformPointsService {
     @Resource
     private ConfigService configService;
 
-    @Resource
-    private SysConfigService sysConfigService;
-
     @Resource
     private CouponService couponService;
 

+ 15 - 34
cfc-backend/src/main/java/com/etotem/cfc/service/PointsExchangeService.java

@@ -41,12 +41,6 @@ public class PointsExchangeService {
     @Resource
     private ConfigService configService;
 
-    @Resource
-    private SysConfigService sysConfigService;
-
-    @Resource
-    private PointsService pointsService;
-
     @Resource
     private FamilyMemberMapper familyMemberMapper;
 
@@ -226,16 +220,13 @@ public class PointsExchangeService {
         if (targetMember != null) targetFamilyId = targetMember.getFamilyId();
         if (targetFamilyId == null) throw new RuntimeException("未找到目标成员所在家庭");
 
-        // 单券模板日限次(coupon_exchange_daily_limit 默认1)
-        int couponDailyLimit = 1;
-        try {
-            String cdlStr = sysConfigService.getValue("coupon_exchange_daily_limit");
-            if (cdlStr != null && !cdlStr.isEmpty()) {
-                couponDailyLimit = Integer.parseInt(cdlStr);
-            }
-        } catch (Exception e) {
-            couponDailyLimit = 1;
+        // N3:券模板绑定家庭时仅该家庭可兑换(NULL = 全平台模板)
+        if (coupon.getFamilyId() != null && !coupon.getFamilyId().equals(targetFamilyId)) {
+            throw new RuntimeException("该优惠券仅限指定家庭使用");
         }
+
+        // 单券模板日限次(L2:cf.spend.coupon.coupon_daily_limit,默认1)
+        int couponDailyLimit = configService.getIntConfig(CfConfigKeys.SPEND_COUPON_COUPON_DAILY_LIMIT, 1);
         // 统计源必须是实际发券写入的 family_coupon_grant_log(EXCHANGE 由 grantFamily 落库),
         // 原 coupon_grant_log 为遗留死表,永远查不到记录导致日限次失效。
         long couponTodayCount = familyCouponGrantLogMapper.selectCount(
@@ -249,29 +240,13 @@ public class PointsExchangeService {
             throw new RuntimeException("该优惠券今日兑换次数已达上限");
         }
 
-        int minPoints = 100;
-        try {
-            String minStr = sysConfigService.getValue("exchange_points_min");
-            if (minStr != null && !minStr.isEmpty()) {
-                minPoints = Integer.parseInt(minStr);
-            }
-        } catch (Exception e) {
-            minPoints = 100;
-        }
+        int minPoints = configService.getIntConfig(CfConfigKeys.SPEND_EXCHANGE_MIN_POINTS, 100);
         if (coupon.getPointsPrice() < minPoints) {
             throw new RuntimeException("兑换积分不得低于最低限制");
         }
 
-        // 扣积分(system_points)
-        int deductResult = pointsService.deductCfPoints(targetFamilyMemberId, coupon.getPointsPrice(),
-            "积分兑换优惠券: " + coupon.getName());
-        if (deductResult < 0) {
-            throw new RuntimeException("积分不足");
-        }
-
-        // 发券 + 流水(EXCHANGE,period=null)— 发到用户所在家庭
-        couponService.grantFamily(targetFamilyId, couponId, "EXCHANGE", null, null);
-
+        // F-5:先落兑换记录(以记录 ID 作为扣减幂等 refId),再扣统一账本 cf_balance,最后发券到家庭;
+        // 扣减失败(余额不足)抛业务异常并由 @Transactional 回滚记录
         String redeemCode = UUID.randomUUID().toString().replace("-", "").substring(0, 12).toUpperCase();
         PointsExchangeRecord record = new PointsExchangeRecord();
         record.setUserId(userId);
@@ -285,6 +260,12 @@ public class PointsExchangeService {
         record.setCreatedAt(new Date());
         record.setUpdatedAt(new Date());
         pointsExchangeRecordMapper.insert(record);
+
+        cfSpendDispatchService.spendForMember(targetFamilyMemberId, coupon.getPointsPrice(),
+            "exchange_coupon", record.getId(), "积分兑换优惠券: " + coupon.getName());
+
+        // 发券 + 流水(EXCHANGE,period=null)— 发到用户所在家庭
+        couponService.grantFamily(targetFamilyId, couponId, "EXCHANGE", null, null);
         return record;
     }
 

+ 27 - 39
cfc-backend/src/main/java/com/etotem/cfc/service/PointsService.java

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.User;
+import com.etotem.cfc.dto.CfBalanceResult;
 import com.etotem.cfc.dto.CfEarnResult;
 import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
@@ -44,6 +45,10 @@ public class PointsService implements PointsServiceInterface {
     @Resource
     private CfPathwayEarnService cfPathwayEarnService;
 
+    /** F-5:旧 CF 兼容方法(award/deduct/查询)改投统一账本 cf_balance,不再写 family_members.system_points */
+    @Resource
+    private CfSpendDispatchService cfSpendDispatchService;
+
     public Map<String, Object> getBalance(Long memberId) {
         FamilyMember child = familyMemberMapper.selectById(memberId);
         if (child == null) {
@@ -117,7 +122,13 @@ public class PointsService implements PointsServiceInterface {
         return result;
     }
 
-    /** @return CF值余额 */
+    /**
+     * 发放 CF 值(F-5:改投统一账本 cf_balance,不再写 family_members.system_points)。
+     *
+     * <p>本方法为旧接口兼容入口,无稳定业务单据 ID,故不参与幂等键(refId 传空)。</p>
+     *
+     * @return 发放后可用 CF 值余额
+     */
     @Transactional
     public int awardCfPoints(Long memberId, int amount, String reason) {
         if (amount <= 0) {
@@ -129,20 +140,8 @@ public class PointsService implements PointsServiceInterface {
             throw new RuntimeException("孩子不存在");
         }
 
-        int newCfPoints = (child.getSystemPoints() == null ? 0 : child.getSystemPoints()) + amount;
-        child.setSystemPoints(newCfPoints);
-
-        child.setUpdatedAt(new Date());
-        familyMemberMapper.updateById(child);
-
-        PointsLog plog = new PointsLog();
-        plog.setChildId(memberId);
-        plog.setAmount(amount);
-        plog.setType("earn");
-        plog.setCategory("cf");
-        plog.setDescription(reason);
-        plog.setCreatedAt(new Date());
-        pointsLogMapper.insert(plog);
+        CfBalanceResult result = cfSpendDispatchService.earnForMember(memberId, amount, "points_cf_award", null, reason);
+        int newCfPoints = result != null && result.getAvailable() != null ? result.getAvailable() : amount;
         try { sendPointsNotification(memberId, "cf_earn", amount, reason); } catch (Exception e) { log.warn("cf notification failed", e); }
 
         return newCfPoints;
@@ -170,48 +169,37 @@ public class PointsService implements PointsServiceInterface {
         return deductCfPoints(memberId, amount, "心愿兑换扣除") >= 0;
     }
 
-    /** @return 扣除后余额,余额不足返回-1 */
+    /**
+     * 扣除 CF 值(F-5:改投统一账本 cf_balance,不再写 family_members.system_points)。
+     *
+     * <p>旧接口兼容入口,无稳定业务单据 ID,不做幂等(refId 传空);余额不足返回 -1。</p>
+     *
+     * @return 扣除后可用 CF 值余额,余额不足返回 -1
+     */
     @Transactional
     public int deductCfPoints(Long memberId, int amount, String reason) {
         if (amount <= 0) {
             throw new IllegalArgumentException("扣除数量必须为正数");
         }
 
-        FamilyMember child = familyMemberMapper.selectById(memberId);
-        if (child == null) {
+        if (memberId == null) {
             throw new RuntimeException("孩子不存在");
         }
-
-        int currentCf = child.getSystemPoints() == null ? 0 : child.getSystemPoints();
+        int currentCf = cfSpendDispatchService.getAvailableForMember(memberId);
         if (currentCf < amount) {
             return -1;
         }
 
-        int newCfPoints = currentCf - amount;
-        child.setSystemPoints(newCfPoints);
-
-        child.setUpdatedAt(new Date());
-        familyMemberMapper.updateById(child);
-
-        PointsLog plog = new PointsLog();
-        plog.setChildId(memberId);
-        plog.setAmount(-amount);
-        plog.setType("spend");
-        plog.setCategory("cf");
-        plog.setDescription(reason);
-        plog.setCreatedAt(new Date());
-        pointsLogMapper.insert(plog);
+        CfBalanceResult result = cfSpendDispatchService.spendForMember(memberId, amount, "points_cf_deduct", null, reason);
+        int newCfPoints = result != null && result.getAvailable() != null ? result.getAvailable() : (currentCf - amount);
         try { sendPointsNotification(memberId, "cf_spend", -amount, reason); } catch (Exception e) { log.warn("cf deduction notification failed", e); }
 
         return newCfPoints;
     }
 
+    /** 查询 CF 值余额(F-5:改读统一账本 cf_balance,不再读 family_members.system_points) */
     public int getCfPointsBalance(Long memberId) {
-        FamilyMember child = familyMemberMapper.selectById(memberId);
-        if (child == null) {
-            return 0;
-        }
-        return child.getSystemPoints() == null ? 0 : child.getSystemPoints();
+        return cfSpendDispatchService.getAvailableForMember(memberId);
     }
 
     public Page<PointsLog> getPointsLogsByCategory(Long memberId, String category, Integer page, Integer size) {

+ 11 - 26
cfc-backend/src/main/java/com/etotem/cfc/service/WishService.java

@@ -6,11 +6,9 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.dto.CreateWishDTO;
 import com.etotem.cfc.dto.WishDTO;
 import com.etotem.cfc.entity.FamilyMember;
-import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.Wish;
 import com.etotem.cfc.mapper.FamilyMemberMapper;
-import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.WishMapper;
 import lombok.extern.slf4j.Slf4j;
@@ -40,8 +38,9 @@ public class WishService {
     @Resource
     private UserMapper userMapper;
 
+    /** F-5:心愿兑换扣减改投统一账本 cf_balance(心愿所属成员维度,幂等 refId = 心愿 ID) */
     @Resource
-    private PointsLogMapper pointsLogMapper;
+    private CfSpendDispatchService cfSpendDispatchService;
 
     // ========== 创建心愿 ==========
 
@@ -198,9 +197,8 @@ public class WishService {
             return false;
         }
 
-        // 检查积分是否充足
-        FamilyMember child = familyMemberMapper.selectById(memberId);
-        if (child.getTotalPoints() < wish.getPointsRequired()) {
+        // 检查积分是否充足(F-5:改读统一账本 cf_balance,不再看 family_members.total_points)
+        if (cfSpendDispatchService.getAvailableForMember(memberId) < wish.getPointsRequired()) {
             throw new RuntimeException("积分不足,无法申请兑换");
         }
 
@@ -237,25 +235,11 @@ public class WishService {
         }
 
         if (approved) {
-            // 再次检查积分是否充足(防止并发问题)
-            FamilyMember child = familyMemberMapper.selectById(wish.getChildId());
-            if (child.getTotalPoints() < wish.getPointsRequired()) {
-                throw new RuntimeException("积分不足,无法兑换");
-            }
-
-            // 扣减积分
-            child.setTotalPoints(child.getTotalPoints() - wish.getPointsRequired());
-            child.setUpdatedAt(new Date());
-            familyMemberMapper.updateById(child);
-
-            // 记录积分消耗
-            PointsLog pointsLog = new PointsLog();
-            pointsLog.setChildId(wish.getChildId());
-            pointsLog.setAmount(-wish.getPointsRequired());
-            pointsLog.setType("wish_exchange");
-            pointsLog.setDescription("兑换心愿: " + wish.getTitle());
-            pointsLog.setCreatedAt(new Date());
-            pointsLogMapper.insert(pointsLog);
+            // F-5:改扣统一账本 cf_balance(心愿所属成员维度,幂等 refId = 心愿 ID),
+            // 扣减失败(余额不足)抛业务异常回滚整个审批;不再写 total_points / PointsLog
+            // (任务积点与 CF 已在任务完成时各自入账,这里只扣 CF,避免双重扣费)
+            cfSpendDispatchService.spendForMember(wish.getChildId(), wish.getPointsRequired(),
+                    "wish_exchange", wish.getId(), "兑换心愿: " + wish.getTitle());
 
             wish.setStatus("approved");
             wish.setExchangeApprovedBy(parentId);
@@ -365,7 +349,8 @@ public class WishService {
                 .orderByDesc(Wish::getCreatedAt));
 
         FamilyMember child = familyMemberMapper.selectById(memberId);
-        int currentPoints = child != null ? child.getTotalPoints() : 0;
+        // F-5:进度改读统一账本 cf_balance(统一「攒 CF」口径,不再看 total_points)
+        int currentPoints = child != null ? cfSpendDispatchService.getAvailableForMember(memberId) : 0;
 
         return wishes.stream().map(w -> toDTO(w, currentPoints)).collect(Collectors.toList());
     }

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-6191b66c2bb024cd286f30829245831c5dff9832
+0bd35113b12e56eac1c86d5cb6a92272041e8de2

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1790",
+  "version": "1.0.1791",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1790",
+      "version": "1.0.1791",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1791",
+  "version": "1.0.1792",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 6 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,12 @@
 
 ---
 
+## v1.0.1792 (2026-10-25)
+
+### Bug 修复
+- 支持通过环境变量关闭定时全量同步
+
+
 ## v1.0.1791 (2026-10-25)
 
 ### Bug 修复

+ 7 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1791
+> 当前版本: v1.0.1792
 
 ## 历史版本
 
@@ -8,6 +8,12 @@
 
 ---
 
+## v1.0.1792 (2026-10-25)
+
+### Bug 修复
+- 支持通过环境变量关闭定时全量同步
+
+
 ## v1.0.1791 (2026-10-25)
 
 ### Bug 修复