Bläddra i källkod

chore: auto bump version and changelog [skip ci]

iwt 2 veckor sedan
förälder
incheckning
d7ca0c99da

+ 43 - 39
cfc-backend/src/main/java/com/etotem/cfc/service/FamilyPlatformPointsService.java

@@ -131,7 +131,11 @@ public class FamilyPlatformPointsService {
                 refType, refId, remark);
                 refType, refId, remark);
     }
     }
 
 
-    /** 消费家庭可用 CF 值,余额不足抛异常 */
+    /**
+     * @deprecated F-5 起消费侧(券兑换 / 提现冻结 / 商品抵扣)已全部改投统一账本 {@link CfSpendDispatchService},
+     * 本方法不再作为消费入口使用;家庭池仅保留发放侧(F-8 清理时标注下线)。
+     */
+    @Deprecated
     @Transactional
     @Transactional
     public void spend(Long familyId, int amount, String refType, Long refId, String remark) {
     public void spend(Long familyId, int amount, String refType, Long refId, String remark) {
         if (amount <= 0) {
         if (amount <= 0) {
@@ -222,7 +226,10 @@ public class FamilyPlatformPointsService {
     }
     }
 
 
     /**
     /**
-     * 用家庭CF值兑换优惠券:校验 → 扣家庭池 → 发券 → 落记录
+     * 用个人 CF 值兑换优惠券(N3:券挂家庭,个人扣 cf_balance):校验 → 扣个人 CF → 发券到家庭 → 落记录。
+     *
+     * <p>F-5:扣减改投统一账本 {@code cf_balance}(兑换人 member 维度,幂等 refId = 券实例 ID),
+     * 不再扣家庭池;日限数值读 {@code service_config}(L2)。</p>
      */
      */
     @Transactional
     @Transactional
     public Long exchangeCouponByCf(Long familyId, Long couponId, Long refUserId) {
     public Long exchangeCouponByCf(Long familyId, Long couponId, Long refUserId) {
@@ -245,34 +252,30 @@ public class FamilyPlatformPointsService {
         if (coupon.getValidUntil() != null && now.after(coupon.getValidUntil())) {
         if (coupon.getValidUntil() != null && now.after(coupon.getValidUntil())) {
             throw new RuntimeException("优惠券已过期");
             throw new RuntimeException("优惠券已过期");
         }
         }
+        // N3:券模板绑定家庭时仅该家庭可兑换(NULL = 全平台模板)
+        if (coupon.getFamilyId() != null && !coupon.getFamilyId().equals(familyId)) {
+            throw new RuntimeException("该优惠券仅限指定家庭使用");
+        }
 
 
-        // 2. 家庭级日限次(cf_exchange_daily_limit,默认5)
-        int dailyLimit = 5;
-        try {
-            String limitStr = sysConfigService.getValue("cf_exchange_daily_limit");
-            if (limitStr != null && !limitStr.isEmpty()) dailyLimit = Integer.parseInt(limitStr);
-        } catch (Exception ignored) {}
+        // 2. 个人级日限次(cf.spend.coupon.exchange_daily_limit,默认5;按兑换人统计)
+        int dailyLimit = configService.getIntConfig(CfConfigKeys.SPEND_COUPON_EXCHANGE_DAILY_LIMIT, 5);
         Calendar cal = Calendar.getInstance();
         Calendar cal = Calendar.getInstance();
         cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0);
         cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0);
         cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0);
         cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0);
         long todayCount = exchangeRecordMapper.selectCount(
         long todayCount = exchangeRecordMapper.selectCount(
                 new LambdaQueryWrapper<FamilyPlatformExchangeRecord>()
                 new LambdaQueryWrapper<FamilyPlatformExchangeRecord>()
-                        .eq(FamilyPlatformExchangeRecord::getFamilyId, familyId)
+                        .eq(FamilyPlatformExchangeRecord::getUserId, refUserId)
                         .eq(FamilyPlatformExchangeRecord::getStatus, "completed")
                         .eq(FamilyPlatformExchangeRecord::getStatus, "completed")
                         .ge(FamilyPlatformExchangeRecord::getCreatedAt, cal.getTime()));
                         .ge(FamilyPlatformExchangeRecord::getCreatedAt, cal.getTime()));
         if (todayCount >= dailyLimit) {
         if (todayCount >= dailyLimit) {
-            throw new RuntimeException("家庭今日CF值兑换次数已达上限(" + dailyLimit + "次)");
+            throw new RuntimeException("今日CF值兑换次数已达上限(" + dailyLimit + "次)");
         }
         }
 
 
-        // 3. 单券模板日限次(cf_coupon_exchange_daily_limit,默认1)
-        int couponDailyLimit = 1;
-        try {
-            String cdlStr = sysConfigService.getValue("cf_coupon_exchange_daily_limit");
-            if (cdlStr != null && !cdlStr.isEmpty()) couponDailyLimit = Integer.parseInt(cdlStr);
-        } catch (Exception ignored) {}
+        // 3. 单券模板日限次(cf.spend.coupon.coupon_daily_limit,默认1;按兑换人统计)
+        int couponDailyLimit = configService.getIntConfig(CfConfigKeys.SPEND_COUPON_COUPON_DAILY_LIMIT, 1);
         long couponTodayCount = exchangeRecordMapper.selectCount(
         long couponTodayCount = exchangeRecordMapper.selectCount(
                 new LambdaQueryWrapper<FamilyPlatformExchangeRecord>()
                 new LambdaQueryWrapper<FamilyPlatformExchangeRecord>()
-                        .eq(FamilyPlatformExchangeRecord::getFamilyId, familyId)
+                        .eq(FamilyPlatformExchangeRecord::getUserId, refUserId)
                         .eq(FamilyPlatformExchangeRecord::getCouponId, couponId)
                         .eq(FamilyPlatformExchangeRecord::getCouponId, couponId)
                         .eq(FamilyPlatformExchangeRecord::getStatus, "completed")
                         .eq(FamilyPlatformExchangeRecord::getStatus, "completed")
                         .ge(FamilyPlatformExchangeRecord::getCreatedAt, cal.getTime()));
                         .ge(FamilyPlatformExchangeRecord::getCreatedAt, cal.getTime()));
@@ -280,17 +283,15 @@ public class FamilyPlatformPointsService {
             throw new RuntimeException("该优惠券今日兑换次数已达上限");
             throw new RuntimeException("该优惠券今日兑换次数已达上限");
         }
         }
 
 
-        // 4. 扣减家庭池CF值
-        spend(familyId, pointsPrice, "coupon_exchange", couponId,
-              "兑换优惠券: " + (coupon.getName() != null ? coupon.getName() : "优惠券"));
-
-        // 5. 发券给家庭(记录券实例ID,退款时据此退还CF值)
+        // 4. 先发券(券实例ID作为扣减幂等 refId),再扣兑换人个人 cf_balance
         Long familyCouponId = couponService.grantFamilyOne(familyId, couponId, "CF_EXCHANGE", "family:" + familyId);
         Long familyCouponId = couponService.grantFamilyOne(familyId, couponId, "CF_EXCHANGE", "family:" + familyId);
         if (familyCouponId == null) {
         if (familyCouponId == null) {
             throw new RuntimeException("发券失败,兑换未完成");
             throw new RuntimeException("发券失败,兑换未完成");
         }
         }
+        cfSpendDispatchService.spendForUser(refUserId, pointsPrice, "coupon_exchange", familyCouponId,
+                "兑换优惠券: " + (coupon.getName() != null ? coupon.getName() : "优惠券"));
 
 
-        // 6. 落兑换记录
+        // 5. 落兑换记录
         FamilyPlatformExchangeRecord record = new FamilyPlatformExchangeRecord();
         FamilyPlatformExchangeRecord record = new FamilyPlatformExchangeRecord();
         record.setUserId(refUserId);
         record.setUserId(refUserId);
         record.setFamilyId(familyId);
         record.setFamilyId(familyId);
@@ -309,28 +310,20 @@ public class FamilyPlatformPointsService {
     }
     }
 
 
     /**
     /**
-     * 优惠券退回时退还 CF 值:
+     * 优惠券退回时退还 CF 值(F-5:原路返还到兑换人个人 cf_balance,不再退家庭池):
      * 券实例在 {@link #exchangeCouponByCf} 中有 completed 兑换记录 → 属「用户 CF 兑换而来」,
      * 券实例在 {@link #exchangeCouponByCf} 中有 completed 兑换记录 → 属「用户 CF 兑换而来」,
-     * 按原消耗 cfCost 退还到家庭池;无兑换记录(平台赠送:JOIN/POPULATION/MANUAL 等)→ 不退。
+     * 按原消耗 cfCost 退还到兑换人本人;无兑换记录(平台赠送:JOIN/POPULATION/MANUAL 等)→ 不退。
      *
      *
-     * 幂等:以 (refType=coupon_refund, refId=券实例ID) 去重,同一张券重复退只生效一次。
+     * 幂等:以 (member_id, type=earn, ref_type=coupon_refund, ref_id=券实例ID) 去重,同一张券重复退只生效一次。
      *
      *
      * @param familyCouponId 券实例ID(family_coupon.id)
      * @param familyCouponId 券实例ID(family_coupon.id)
-     * @return 实际退还的 CF 值;0 = 平台赠送券 / 记录缺失 / 已退还过
+     * @return 实际退还的 CF 值;0 = 平台赠送券 / 记录缺失 / 兑换人已退出家庭 / 已退还过
      */
      */
     @Transactional
     @Transactional
     public int refundCouponCf(Long familyCouponId) {
     public int refundCouponCf(Long familyCouponId) {
         if (familyCouponId == null) {
         if (familyCouponId == null) {
             return 0;
             return 0;
         }
         }
-        // 幂等检查:同一券实例只退一次
-        Long dup = logMapper.selectCount(
-                new LambdaQueryWrapper<FamilyPlatformBalanceLog>()
-                        .eq(FamilyPlatformBalanceLog::getRefType, "coupon_refund")
-                        .eq(FamilyPlatformBalanceLog::getRefId, familyCouponId));
-        if (dup != null && dup > 0) {
-            return 0;
-        }
         FamilyPlatformExchangeRecord rec = exchangeRecordMapper.selectOne(
         FamilyPlatformExchangeRecord rec = exchangeRecordMapper.selectOne(
                 new LambdaQueryWrapper<FamilyPlatformExchangeRecord>()
                 new LambdaQueryWrapper<FamilyPlatformExchangeRecord>()
                         .eq(FamilyPlatformExchangeRecord::getFamilyCouponId, familyCouponId)
                         .eq(FamilyPlatformExchangeRecord::getFamilyCouponId, familyCouponId)
@@ -349,10 +342,21 @@ public class FamilyPlatformPointsService {
                     familyCouponId, rec.getCouponId());
                     familyCouponId, rec.getCouponId());
             return 0;
             return 0;
         }
         }
-        earn(rec.getFamilyId(), rec.getCfCost(), "coupon_refund", familyCouponId,
-                "优惠券退回返还CF值: " + (rec.getCouponName() != null ? rec.getCouponName() : ""));
-        logger.info("优惠券退回退还CF值: familyId={}, familyCouponId={}, cfCost={}",
-                rec.getFamilyId(), familyCouponId, rec.getCfCost());
+        // F-5:退还到兑换人本人(原路返还),幂等由账本 (member_id, type, ref_type, ref_id) 唯一键兜底
+        String remark = "优惠券退回返还CF值: " + (rec.getCouponName() != null ? rec.getCouponName() : "");
+        try {
+            CfBalanceResult result = cfSpendDispatchService.refundForUser(
+                    rec.getUserId(), rec.getCfCost(), "coupon_refund", familyCouponId, remark);
+            if (result != null && Boolean.TRUE.equals(result.getDuplicated())) {
+                return 0;
+            }
+        } catch (Exception e) {
+            logger.warn("CF退还跳过:兑换人成员解析失败 familyCouponId={}, userId={}, err={}",
+                    familyCouponId, rec.getUserId(), e.getMessage());
+            return 0;
+        }
+        logger.info("优惠券退回退还CF值: userId={}, familyCouponId={}, cfCost={}",
+                rec.getUserId(), familyCouponId, rec.getCfCost());
         return rec.getCfCost();
         return rec.getCfCost();
     }
     }
 
 

+ 9 - 12
cfc-backend/src/main/java/com/etotem/cfc/service/PointsExchangeService.java

@@ -2,6 +2,8 @@ package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.etotem.cfc.config.ConfigService;
+import com.etotem.cfc.constant.CfConfigKeys;
 import com.etotem.cfc.entity.Coupon;
 import com.etotem.cfc.entity.Coupon;
 import com.etotem.cfc.entity.FamilyCouponGrantLog;
 import com.etotem.cfc.entity.FamilyCouponGrantLog;
 import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.FamilyMember;
@@ -31,14 +33,16 @@ public class PointsExchangeService {
     @Resource
     @Resource
     private PointsExchangeRecordMapper pointsExchangeRecordMapper;
     private PointsExchangeRecordMapper pointsExchangeRecordMapper;
 
 
+    /** F-5:积分兑换(商品 / 优惠券)扣减改投统一账本 cf_balance(目标成员维度,幂等 refId = 兑换记录 ID) */
     @Resource
     @Resource
-    private PointsService pointsService;
+    private CfSpendDispatchService cfSpendDispatchService;
 
 
+    /** F-5:日限 / 最低兑换数值读 L2 service_config(不写死业务常量) */
     @Resource
     @Resource
-    private FamilyMemberMapper familyMemberMapper;
+    private ConfigService configService;
 
 
     @Resource
     @Resource
-    private SysConfigService sysConfigService;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private CouponMapper couponMapper;
     private CouponMapper couponMapper;
@@ -95,15 +99,8 @@ public class PointsExchangeService {
             throw new RuntimeException("未找到关联的孩子信息");
             throw new RuntimeException("未找到关联的孩子信息");
         }
         }
 
 
-        int dailyLimit = 5;
-        try {
-            String limitStr = sysConfigService.getValue("exchange_daily_limit");
-            if (limitStr != null && !limitStr.isEmpty()) {
-                dailyLimit = Integer.parseInt(limitStr);
-            }
-        } catch (Exception e) {
-            dailyLimit = 5;
-        }
+        // 每日兑换次数上限(L2:cf.spend.exchange.daily_limit,默认5)
+        int dailyLimit = configService.getIntConfig(CfConfigKeys.SPEND_EXCHANGE_DAILY_LIMIT, 5);
 
 
         Calendar cal = Calendar.getInstance();
         Calendar cal = Calendar.getInstance();
         cal.set(Calendar.HOUR_OF_DAY, 0);
         cal.set(Calendar.HOUR_OF_DAY, 0);

+ 41 - 38
cfc-backend/src/main/java/com/etotem/cfc/service/WithdrawalService.java

@@ -3,6 +3,8 @@ package com.etotem.cfc.service;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.common.Result;
+import com.etotem.cfc.config.ConfigService;
+import com.etotem.cfc.constant.CfConfigKeys;
 import com.etotem.cfc.entity.WithdrawalRequest;
 import com.etotem.cfc.entity.WithdrawalRequest;
 import com.etotem.cfc.mapper.WithdrawalRequestMapper;
 import com.etotem.cfc.mapper.WithdrawalRequestMapper;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -11,53 +13,61 @@ import javax.annotation.Resource;
 import java.util.Date;
 import java.util.Date;
 import com.etotem.cfc.util.SortUtil;
 import com.etotem.cfc.util.SortUtil;
 
 
+/**
+ * 提现(F-5:个人 CF 值统一账本)。
+ *
+ * <p>消费侧改投 {@link CfSpendDispatchService}:申请冻结 / 驳回解冻 / 通过提现全部落在
+ * 申请人自己的成员账本(cf_balance),不再写家庭池;提现开关与最低金额读
+ * {@code service_config}(L2,键见 {@link CfConfigKeys})。</p>
+ */
 @Service
 @Service
 public class WithdrawalService {
 public class WithdrawalService {
 
 
-    private static final Integer MIN_WITHDRAWAL = 10000; // 100 元 = 10000 分
-
     @Resource
     @Resource
     private WithdrawalRequestMapper withdrawalRequestMapper;
     private WithdrawalRequestMapper withdrawalRequestMapper;
 
 
+    /** F-5:提现扣减/冻结/提现改投统一账本 cf_balance(申请人 member 维度) */
     @Resource
     @Resource
-    private PlatformPointsService platformPointsService;
-
-    @Resource
-    private FamilyMemberService familyMemberService;
+    private CfSpendDispatchService cfSpendDispatchService;
 
 
+    /** F-5:提现开关 / 最低金额读 L2 service_config(不写死业务常量) */
     @Resource
     @Resource
-    private FamilyPlatformPointsService familyPlatformPointsService;
+    private ConfigService configService;
 
 
     public Result<String> apply(Long userId, Integer amount, String accountInfo) {
     public Result<String> apply(Long userId, Integer amount, String accountInfo) {
         if (amount == null || amount <= 0) {
         if (amount == null || amount <= 0) {
             return Result.error("提现金额必须大于0");
             return Result.error("提现金额必须大于0");
         }
         }
-        if (amount < MIN_WITHDRAWAL) {
-            return Result.error("最低提现金额为 ¥" + String.format("%.2f", MIN_WITHDRAWAL / 100.0));
+        // 提现开关(L2:cf.withdraw_enabled,默认关闭,Q4 合规审查待定)
+        if (!configService.getBooleanConfig(CfConfigKeys.WITHDRAW_ENABLED, false)) {
+            return Result.error("提现功能未开启");
+        }
+        int minAmount = configService.getIntConfig(CfConfigKeys.SPEND_WITHDRAW_MIN_AMOUNT_CENT, 10000);
+        if (amount < minAmount) {
+            return Result.error("最低提现金额为 ¥" + String.format("%.2f", minAmount / 100.0));
         }
         }
         if (accountInfo == null || accountInfo.trim().isEmpty()) {
         if (accountInfo == null || accountInfo.trim().isEmpty()) {
             return Result.error("请填写账户信息");
             return Result.error("请填写账户信息");
         }
         }
-        // 从用户获取归属家庭ID
-        Long familyId = resolveFamilyId(userId);
-        if (familyId == null) {
-            return Result.error("未找到归属家庭,请先加入家庭后再提现");
+        Long memberId = cfSpendDispatchService.resolveMemberId(userId);
+        if (memberId == null) {
+            return Result.error("未找到你的家庭成员信息,无法提现");
         }
         }
-        Integer available = familyPlatformPointsService.getWithdrawable(familyId);
+        int available = cfSpendDispatchService.getAvailableForMember(memberId);
         if (available < amount) {
         if (available < amount) {
-            return Result.error("家庭积分不足,当前可提现 ¥" + String.format("%.2f", available / 100.0));
+            return Result.error("CF值不足,当前可提现 ¥" + String.format("%.2f", available / 100.0));
         }
         }
         WithdrawalRequest request = new WithdrawalRequest();
         WithdrawalRequest request = new WithdrawalRequest();
         request.setUserId(userId);
         request.setUserId(userId);
         request.setAmount(amount);
         request.setAmount(amount);
         request.setAccountInfo(accountInfo);
         request.setAccountInfo(accountInfo);
         request.setStatus("pending");
         request.setStatus("pending");
-        request.setType("platform_points");
+        request.setType("cf_balance");
         request.setCreatedAt(new Date());
         request.setCreatedAt(new Date());
         withdrawalRequestMapper.insert(request);
         withdrawalRequestMapper.insert(request);
-        // 冻结家庭池 CF 值(以提现申请 ID 作为流水关联)
+        // 冻结个人统一账本 CF 值(以提现申请 ID 作为幂等 refId)
         try {
         try {
-            familyPlatformPointsService.freeze(familyId, amount, request.getId());
+            cfSpendDispatchService.freezeForMember(memberId, amount, "withdrawal", request.getId(), "提现冻结");
         } catch (Exception e) {
         } catch (Exception e) {
             request.setStatus("rejected");
             request.setStatus("rejected");
             request.setRemark("CF值冻结失败");
             request.setRemark("CF值冻结失败");
@@ -67,15 +77,6 @@ public class WithdrawalService {
         return Result.success("提现申请已提交,等待审核");
         return Result.success("提现申请已提交,等待审核");
     }
     }
 
 
-    /** 从用户查找归属家庭ID */
-    @Resource
-    private com.etotem.cfc.mapper.UserMapper userMapper;
-
-    private Long resolveFamilyId(Long userId) {
-        com.etotem.cfc.entity.User user = userMapper.selectById(userId);
-        return user != null ? user.getFamilyId() : null;
-    }
-
     public void audit(Long requestId, Long adminId, String status, String reason) {
     public void audit(Long requestId, Long adminId, String status, String reason) {
         WithdrawalRequest request = withdrawalRequestMapper.selectById(requestId);
         WithdrawalRequest request = withdrawalRequestMapper.selectById(requestId);
         if (request == null) {
         if (request == null) {
@@ -85,17 +86,19 @@ public class WithdrawalService {
             throw new RuntimeException("该申请已处理");
             throw new RuntimeException("该申请已处理");
         }
         }
 
 
-        Long familyId = resolveFamilyId(request.getUserId());
-        if (familyId != null) {
-            if ("approved".equals(status)) {
-                familyPlatformPointsService.confirmWithdraw(familyId, request.getAmount(), requestId);
-                request.setStatus("processed");
-                request.setRemark("提现成功");
-            } else if ("rejected".equals(status)) {
-                familyPlatformPointsService.unfreeze(familyId, request.getAmount(), requestId);
-                request.setStatus("rejected");
-                request.setRejectReason(reason != null ? reason.trim() : null);
-            }
+        Long memberId = cfSpendDispatchService.resolveMemberId(request.getUserId());
+        if (memberId == null) {
+            throw new RuntimeException("未找到提现申请人的家庭成员,无法处理");
+        }
+        if ("approved".equals(status)) {
+            // 通过:冻结 → 已提现(受 cf.withdraw_enabled 开关控制;关掉后此处同样被账本拦截)
+            cfSpendDispatchService.withdrawForMember(memberId, request.getAmount(), "withdrawal", requestId, "提现成功");
+            request.setStatus("processed");
+            request.setRemark("提现成功");
+        } else if ("rejected".equals(status)) {
+            cfSpendDispatchService.unfreezeForMember(memberId, request.getAmount(), "withdrawal", requestId, "提现审核拒绝解冻");
+            request.setStatus("rejected");
+            request.setRejectReason(reason != null ? reason.trim() : null);
         }
         }
 
 
         request.setAuditBy(adminId);
         request.setAuditBy(adminId);

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-1150e39dd3a650151bad253eecef2252c322591d
+29887b33b393c83067a14688607139e2fe3efeb9

+ 1 - 1
cfc-web/package.json

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

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

@@ -4,6 +4,12 @@
 
 
 ---
 ---
 
 
+## v1.0.1789 (2026-10-25)
+
+### 新功能
+- F-3c 发放接入 F20-F24 统一账本
+
+
 ## v1.0.1788 (2026-10-25)
 ## v1.0.1788 (2026-10-25)
 
 
 ### 新功能
 ### 新功能

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

@@ -1,6 +1,6 @@
 # 更新日志
 # 更新日志
 
 
-> 当前版本: v1.0.1788
+> 当前版本: v1.0.1789
 
 
 ## 历史版本
 ## 历史版本
 
 
@@ -8,6 +8,12 @@
 
 
 ---
 ---
 
 
+## v1.0.1789 (2026-10-25)
+
+### 新功能
+- F-3c 发放接入 F20-F24 统一账本
+
+
 ## v1.0.1788 (2026-10-25)
 ## v1.0.1788 (2026-10-25)
 
 
 ### 新功能
 ### 新功能