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

feat(team.profit.pipeline): S-cfc-64 GREEN 实现 - 分润管道化(T9 收入管道化,红→绿)

- ProfitPoolService:§2.1 利润漏斗核算(S/C/P/O/F/D/R/T + 8份池 + 团队奖);触发源扩展 membership/subscription/assessment_retest,续费/复购持续分润;uk_order 幂等;未知触发源拒绝;D≤0 整单不分润仅记录
- 实体与 Mapper:profit_pool_order / team_share_record / team_award_record(规格 §7.1/7.3/7.4)
- 迁移458-460 建三张表 + schema.sql 同步
- TeamProfitPipelineContractTest 8/8 通过(RED→GREEN);ProfitPoolOrderContractRedTest 4/4 附带通过
E2E Test Bot 5 дней назад
Родитель
Сommit
3d28ba94d3

+ 98 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -3474,6 +3474,104 @@ insertSysConfigSeed("gift_membership_pay_price", "198000", "送会员商品达
 
         // 迁移457: survey_records 添加 source 列(区分定期调研/健康阅卷,HL-01 健康阅卷入口)
         ensureColumn("survey_records", "source", "VARCHAR(32) DEFAULT 'periodic' COMMENT '记录来源: periodic=定期调研 / health_review=健康阅卷'");
+
+        // 迁移458: 单笔订单利润池计算明细表(团队分润 §7.1,uk_order 幂等)
+        createProfitPoolOrderTable();
+        // 迁移459: 团队8份分佣流水表(§7.3,uk_order_rec 幂等)
+        createTeamShareRecordTable();
+        // 迁移460: 团队奖流水表(§7.4,uk_order_team 幂等)
+        createTeamAwardRecordTable();
+    }
+
+    /**
+     * 迁移458: 创建单笔订单利润池计算明细表(§7.1)。
+     *
+     * <p>承载 §2.1 总账模型逐项核算;{@code uk_order(order_type, order_id)} 唯一键
+     * 保证重复回调幂等(§8.3)。触发源(order_type)在 §4.7 杠杆 1 扩展
+     * membership/subscription/assessment_retest(T9 收入管道化)。</p>
+     */
+    private void createProfitPoolOrderTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS profit_pool_order ("
+                    + "id BIGINT AUTO_INCREMENT PRIMARY KEY,"
+                    + "order_id BIGINT NOT NULL COMMENT '来源订单ID',"
+                    + "order_type VARCHAR(32) NOT NULL COMMENT '触发源: product/service/assessment/package/membership/subscription/assessment_retest',"
+                    + "product_id BIGINT NULL COMMENT '商品/服务ID',"
+                    + "amount_cent INT NOT NULL COMMENT '售价S(分)',"
+                    + "cost_cent INT NOT NULL COMMENT '平台成本价C(分)',"
+                    + "platform_retain_cent INT NOT NULL COMMENT '平台留存P=S-C(分)',"
+                    + "ops_cent INT NOT NULL COMMENT '平台运营O(分)',"
+                    + "cf_cost_cent INT NOT NULL COMMENT 'CF值成本F(分)',"
+                    + "pool_cent INT NOT NULL COMMENT '可分配池D(分)',"
+                    + "referral_share_cent INT NOT NULL COMMENT '推荐人体系R(分,CF值预算参照)',"
+                    + "team_share_cent INT NOT NULL COMMENT '团队体系T(分,货币)',"
+                    + "team_eight_share_cent INT NOT NULL COMMENT '8份分佣池(分,默认T×80%)',"
+                    + "team_award_cent INT NOT NULL COMMENT '团队奖池(分,默认T×20%)',"
+                    + "status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/settled/cancelled',"
+                    + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',"
+                    + "settled_at DATETIME NULL COMMENT '结算时间',"
+                    + "UNIQUE KEY uk_order (order_type, order_id)"
+                    + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='单笔订单利润池计算明细'");
+            log.info("迁移458: profit_pool_order 表已就绪");
+        } catch (Exception e) {
+            log.warn("迁移458: profit_pool_order 创建失败: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 迁移459: 创建团队 8 份分佣流水表(§7.3)。
+     *
+     * <p>{@code uk_order_rec(order_id, recipient_user_id, layer)} 唯一键保证重复回调幂等(§8.3)。</p>
+     */
+    private void createTeamShareRecordTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS team_share_record ("
+                    + "id BIGINT AUTO_INCREMENT PRIMARY KEY,"
+                    + "order_id BIGINT NOT NULL COMMENT '来源订单ID',"
+                    + "order_type VARCHAR(32) NOT NULL COMMENT '触发源',"
+                    + "pool_id BIGINT NOT NULL COMMENT '归属利润池明细profit_pool_order.id',"
+                    + "provider_user_id BIGINT NOT NULL COMMENT '服务提供者(规划师/测评师)',"
+                    + "recipient_user_id BIGINT NOT NULL COMMENT '获得分润的团队成员',"
+                    + "layer INT NOT NULL COMMENT '1=服务者本人 2=引荐人 3=引荐人的引荐人',"
+                    + "star_level INT NULL COMMENT '该成员当期星级快照(1-6)',"
+                    + "shares INT NOT NULL COMMENT '分得份数(0-8)',"
+                    + "share_cent INT NOT NULL COMMENT '分润金额(分)',"
+                    + "status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/settled/cancelled',"
+                    + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',"
+                    + "UNIQUE KEY uk_order_rec (order_id, recipient_user_id, layer),"
+                    + "KEY idx_recipient (recipient_user_id)"
+                    + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='团队8份分佣流水(货币)'");
+            log.info("迁移459: team_share_record 表已就绪");
+        } catch (Exception e) {
+            log.warn("迁移459: team_share_record 创建失败: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 迁移460: 创建团队奖流水表(§7.4)。
+     *
+     * <p>{@code uk_order_team(order_id, team_id)} 唯一键保证重复回调幂等(§8.3)。</p>
+     */
+    private void createTeamAwardRecordTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS team_award_record ("
+                    + "id BIGINT AUTO_INCREMENT PRIMARY KEY,"
+                    + "order_id BIGINT NOT NULL COMMENT '来源订单ID',"
+                    + "order_type VARCHAR(32) NOT NULL COMMENT '触发源',"
+                    + "pool_id BIGINT NOT NULL COMMENT '归属利润池明细profit_pool_order.id',"
+                    + "team_id BIGINT NOT NULL COMMENT 'distribution_systems.id',"
+                    + "owner_user_id BIGINT NOT NULL COMMENT '团队负责人(owner_id/根)',"
+                    + "tier VARCHAR(8) NOT NULL COMMENT 'TA0/TA1/TA2/TA3',"
+                    + "award_cent INT NOT NULL COMMENT '团队奖金额(分)',"
+                    + "status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/settled/cancelled',"
+                    + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',"
+                    + "UNIQUE KEY uk_order_team (order_id, team_id),"
+                    + "KEY idx_owner (owner_user_id)"
+                    + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='团队奖流水(货币)'");
+            log.info("迁移460: team_award_record 表已就绪");
+        } catch (Exception e) {
+            log.warn("迁移460: team_award_record 创建失败: {}", e.getMessage());
+        }
     }
 
     /**

+ 85 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/ProfitPoolOrder.java

@@ -0,0 +1,85 @@
+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;
+
+/**
+ * 单笔订单利润池计算明细(规格 §7.1)。
+ *
+ * <p>承载 §2.1 总账模型的逐项核算:平台留存 P、平台运营 O、CF 值成本 F、可分配池 D、
+ * 推荐人体系 R(CF 值预算参照)、团队体系 T(货币直接分佣,本次新增)、
+ * 8 份分佣池(T×80%)、团队奖池(T×20%)。</p>
+ *
+ * <p>触发源(order_type)在 §4.7 杠杆 1(T9 收入管道化)扩展
+ * {@code membership / subscription / assessment_retest},续费/复购在推荐关系存续期间持续分润。</p>
+ *
+ * <p>幂等:{@code uk_order(order_type, order_id)} 唯一键保证重复回调不重复发放(§8.3)。</p>
+ *
+ * <p>规格:docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7
+ * §7.1 + §2.1 + §4.7 杠杆 1 + §8.3。</p>
+ */
+@Data
+@TableName("profit_pool_order")
+public class ProfitPoolOrder implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 来源订单ID */
+    private Long orderId;
+
+    /**
+     * 触发源:product/service/assessment/package/membership/subscription/assessment_retest
+     * (§4.7 杠杆 1 扩展 membership/subscription/assessment_retest)
+     */
+    private String orderType;
+
+    /** 商品/服务ID */
+    private Long productId;
+
+    /** 售价 S(分) */
+    private Integer amountCent;
+
+    /** 平台成本价 C(分) */
+    private Integer costCent;
+
+    /** 平台留存 P = S - C(分) */
+    private Integer platformRetainCent;
+
+    /** 平台运营 O = round(P × ops_rate)(分) */
+    private Integer opsCent;
+
+    /** CF 值成本 F = round(S × cf_cost_rate)(分) */
+    private Integer cfCostCent;
+
+    /** 可分配池 D = P - O - F(分);D ≤ 0 不触发分润(§2.2) */
+    private Integer poolCent;
+
+    /** 推荐人体系 R = round(D × referral_share)(分,CF 值预算参照) */
+    private Integer referralShareCent;
+
+    /** 团队体系 T = round(D × team_share)(分,货币直接分佣) */
+    private Integer teamShareCent;
+
+    /** 8 份分佣池(分,默认 T×80%) */
+    private Integer teamEightShareCent;
+
+    /** 团队奖池(分,默认 T×20%) */
+    private Integer teamAwardCent;
+
+    /** pending/settled/cancelled(§8.1) */
+    private String status;
+
+    /** 创建时间 */
+    private Date createdAt;
+
+    /** 结算时间(冷静期满置 settled) */
+    private Date settledAt;
+}

+ 55 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/TeamAwardRecord.java

@@ -0,0 +1,55 @@
+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;
+
+/**
+ * 团队奖流水(货币)(规格 §7.4)。
+ *
+ * <p>团队奖池 = T×20%(§4.4),按团队年度滚动总服务业绩分档(TA0-TA3)发放,
+ * 归属平台自有团队、平台统一规则分配(#3 拍板 platform_rule),
+ * 幂等唯一键 {@code uk_order_team(order_id, team_id)}(§8.3)。</p>
+ *
+ * <p>规格:docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7 §7.4 + §4.4 + §8.3。</p>
+ */
+@Data
+@TableName("team_award_record")
+public class TeamAwardRecord implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 来源订单ID */
+    private Long orderId;
+
+    /** 触发源(与 profit_pool_order.order_type 一致) */
+    private String orderType;
+
+    /** 归属利润池明细 profit_pool_order.id */
+    private Long poolId;
+
+    /** 归属服务团队 distribution_systems.id */
+    private Long teamId;
+
+    /** 团队负责人(owner_id/根) */
+    private Long ownerUserId;
+
+    /** TA0/TA1/TA2/TA3(§4.4 团队奖分档) */
+    private String tier;
+
+    /** 团队奖金额(分) */
+    private Integer awardCent;
+
+    /** pending/settled/cancelled(§8.1) */
+    private String status;
+
+    /** 创建时间 */
+    private Date createdAt;
+}

+ 60 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/TeamShareRecord.java

@@ -0,0 +1,60 @@
+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;
+
+/**
+ * 团队 8 份分佣流水(货币)(规格 §7.3)。
+ *
+ * <p>面向平台服务团队(规划师/测评师),按 §4.2 星级份数分配(每层最多 6 份、最多 3 层),
+ * 幂等唯一键 {@code uk_order_rec(order_id, recipient_user_id, layer)}(§8.3)。</p>
+ *
+ * <p>规格:docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7 §7.3 + §4.2 + §8.3。</p>
+ */
+@Data
+@TableName("team_share_record")
+public class TeamShareRecord implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 来源订单ID */
+    private Long orderId;
+
+    /** 触发源(与 profit_pool_order.order_type 一致) */
+    private String orderType;
+
+    /** 归属利润池明细 profit_pool_order.id */
+    private Long poolId;
+
+    /** 服务提供者(规划师/测评师) */
+    private Long providerUserId;
+
+    /** 获得分润的团队成员 */
+    private Long recipientUserId;
+
+    /** 1=服务者本人 2=引荐人 3=引荐人的引荐人 */
+    private Integer layer;
+
+    /** 该成员当期星级快照(1-6) */
+    private Integer starLevel;
+
+    /** 分得份数(0-8) */
+    private Integer shares;
+
+    /** 分润金额(分) */
+    private Integer shareCent;
+
+    /** pending/settled/cancelled(§8.1) */
+    private String status;
+
+    /** 创建时间 */
+    private Date createdAt;
+}

+ 12 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/ProfitPoolOrderMapper.java

@@ -0,0 +1,12 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.ProfitPoolOrder;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 单笔订单利润池计算明细 Mapper(规格 §7.1,MyBatis-Plus)。
+ */
+@Mapper
+public interface ProfitPoolOrderMapper extends BaseMapper<ProfitPoolOrder> {
+}

+ 12 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/TeamAwardRecordMapper.java

@@ -0,0 +1,12 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.TeamAwardRecord;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 团队奖流水 Mapper(规格 §7.4,MyBatis-Plus)。
+ */
+@Mapper
+public interface TeamAwardRecordMapper extends BaseMapper<TeamAwardRecord> {
+}

+ 12 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/TeamShareRecordMapper.java

@@ -0,0 +1,12 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.TeamShareRecord;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 团队 8 份分佣流水 Mapper(规格 §7.3,MyBatis-Plus)。
+ */
+@Mapper
+public interface TeamShareRecordMapper extends BaseMapper<TeamShareRecord> {
+}

+ 283 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/ProfitPoolService.java

@@ -0,0 +1,283 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.config.ConfigService;
+import com.etotem.cfc.entity.ProfitPoolOrder;
+import com.etotem.cfc.entity.ServiceConfigItem;
+import com.etotem.cfc.entity.TeamShareRecord;
+import com.etotem.cfc.mapper.ProfitPoolOrderMapper;
+import com.etotem.cfc.mapper.ServiceConfigItemMapper;
+import com.etotem.cfc.mapper.TeamAwardRecordMapper;
+import com.etotem.cfc.mapper.TeamShareRecordMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * 单笔订单利润池计算服务(规格 §2.1 总账模型 + §4.7 杠杆 1 收入管道化 + §8.3 幂等)。
+ *
+ * <p><b>核心职责(T9 收入管道化,能力键 {@code team.profit.pipeline})</b>:</p>
+ * <ul>
+ *   <li>分润触发源扩展 {@code membership / subscription / assessment_retest}(§4.7 杠杆 1),
+ *       续费/复购在推荐关系存续期间持续产生分润;</li>
+ *   <li>按 §2.1 对每笔订单完成利润漏斗核算(S/C/P/O/F/D/R/T + 8 份池 + 团队奖),
+ *       只扩展触发源、不提高分配比率(§10#5);</li>
+ *   <li>{@code D > 0} 才进入分润流程并生成 {@code team_share_record}(pending),
+ *       {@code D ≤ 0}(低于保本线)整单不分润、仅落 {@code profit_pool_order} 供统计(§9#3);</li>
+ *   <li>重复回调按唯一键 {@code uk_order(order_type, order_id)} 幂等(§8.3),不重复发放;</li>
+ *   <li>未知触发源被拒绝(防误分配)。</li>
+ * </ul>
+ *
+ * <p>配置读取:优先 {@link ConfigService}(缓存 + 兜底),未注入时回退
+ * {@link ServiceConfigItemMapper} 直读,均缺失则用 §7.8 默认比率
+ * (ops 10% / cf 1% / referral 70% / team 30% / eight 80% / award 20%)。</p>
+ *
+ * <p>规格:docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7
+ * §2.1 + §2.2 + §4.7 杠杆 1 + §7.1 + §7.8 + §8.3 + §9#3 + §10#5 + §12-T9。</p>
+ */
+@Service
+public class ProfitPoolService {
+
+    private static final Logger log = LoggerFactory.getLogger(ProfitPoolService.class);
+
+    /** §7.1 order_type 允许值(§4.7 杠杆 1 扩展 membership/subscription/assessment_retest) */
+    private static final Set<String> ALLOWED_ORDER_TYPES = new HashSet<>(Arrays.asList(
+            "product", "service", "assessment", "package",
+            "membership", "subscription", "assessment_retest"));
+
+    // §7.8 默认比率(配置未写入时使用)
+    private static final double DEFAULT_OPS_RATE = 0.10;
+    private static final double DEFAULT_CF_COST_RATE = 0.01;
+    private static final double DEFAULT_REFERRAL_SHARE = 0.70;
+    private static final double DEFAULT_TEAM_SHARE = 0.30;
+    private static final double DEFAULT_EIGHT_SHARE_RATIO = 0.80;
+    private static final double DEFAULT_TEAM_AWARD_RATIO = 0.20;
+    private static final int DEFAULT_SHARE_TOTAL = 8;
+
+    private static final String CFG_OPS_RATE = "referral_profit.ops_rate";
+    private static final String CFG_CF_COST_RATE = "referral_profit.cf_cost_rate";
+    private static final String CFG_REFERRAL_SHARE = "referral_profit.referral_share";
+    private static final String CFG_TEAM_SHARE = "referral_profit.team_share";
+    private static final String CFG_EIGHT_SHARE_RATIO = "team_profit.eight_share_ratio";
+    private static final String CFG_TEAM_AWARD_RATIO = "team_profit.team_award_ratio";
+    private static final String CFG_SHARE_TOTAL = "team_profit.share_total";
+
+    @Resource
+    private ConfigService configService;
+
+    @Resource
+    private ProfitPoolOrderMapper profitPoolOrderMapper;
+
+    @Resource
+    private TeamShareRecordMapper teamShareRecordMapper;
+
+    @Resource
+    private TeamAwardRecordMapper teamAwardRecordMapper;
+
+    @Resource
+    private ServiceConfigItemMapper serviceConfigItemMapper;
+
+    /**
+     * 分润结算入口(§12-T2 契约,能力键 {@code team.profit.settle})。
+     *
+     * <p>签名 {@code (Long orderId, String orderType, Long productId, long amountCent, long costCent)}
+     * 由契约测试 {@code ProfitPoolOrderContractRedTest} 锚定;内部与管道化入口共用核算核心。</p>
+     */
+    public ProfitPoolOrder settle(Long orderId, String orderType, Long productId,
+                                  long amountCent, long costCent) {
+        return doSettle(orderId, orderType, productId, amountCent, costCent, null);
+    }
+
+    /**
+     * 分润结算入口(§4.7 杠杆 1 收入管道化,能力键 {@code team.profit.pipeline})。
+     *
+     * <p>触发源扩展 {@code membership / subscription / assessment_retest};{@code memberId}
+     * 为订单归属成员(服务提供者/推荐链根),用于生成 {@code team_share_record} 流水。</p>
+     */
+    public ProfitPoolOrder settle(long orderId, String orderType, long memberId,
+                                  int amountCent, int costCent) {
+        return doSettle(orderId, orderType, null, amountCent, costCent, memberId);
+    }
+
+    /**
+     * 核算核心:幂等检查 → 触发源校验 → §2.1 利润漏斗 → 落单 → D>0 生成团队分润流水。
+     */
+    private ProfitPoolOrder doSettle(Long orderId, String orderType, Long productId,
+                                     long amountCent, long costCent, Long memberId) {
+        // 幂等(§8.3 uk_order):重复回调返回既有记录,不重复发放
+        ProfitPoolOrder existing = findExisting(orderId, orderType);
+        if (existing != null) {
+            return existing;
+        }
+
+        // 未知触发源拒绝(防误分配)
+        if (orderType == null || !ALLOWED_ORDER_TYPES.contains(orderType)) {
+            throw new IllegalArgumentException("未知分润触发源: " + orderType);
+        }
+
+        // §2.1 一般化公式(金额单位:分)
+        double opsRate = rate(CFG_OPS_RATE, DEFAULT_OPS_RATE);
+        double cfCostRate = rate(CFG_CF_COST_RATE, DEFAULT_CF_COST_RATE);
+        double referralShare = rate(CFG_REFERRAL_SHARE, DEFAULT_REFERRAL_SHARE);
+        double teamShare = rate(CFG_TEAM_SHARE, DEFAULT_TEAM_SHARE);
+        double eightShareRatio = rate(CFG_EIGHT_SHARE_RATIO, DEFAULT_EIGHT_SHARE_RATIO);
+        // 团队奖按预算守恒取 T−8份池(8份池占 T×80%、团队奖占 T×20%,§2.1 恒等)
+
+        long p = amountCent - costCent;
+        long o = roundPct(p, opsRate);
+        long f = roundPct(amountCent, cfCostRate);
+        long d = p - o - f;
+
+        long r;
+        long t;
+        long eight;
+        long award;
+        if (d <= 0) {
+            // §2.2/§9#3:D≤0 整单不产生推荐人/团队分润,仅落 profit_pool_order 供统计
+            r = 0;
+            t = 0;
+            eight = 0;
+            award = 0;
+        } else {
+            r = roundPct(d, referralShare);
+            t = roundPct(d, teamShare);
+            eight = roundPct(t, eightShareRatio);
+            // 预算守恒:8 份池 + 团队奖 = T(§2.1,不超 D 预算)
+            award = t - eight;
+        }
+
+        ProfitPoolOrder pool = new ProfitPoolOrder();
+        pool.setOrderId(orderId);
+        pool.setOrderType(orderType);
+        pool.setProductId(productId);
+        pool.setAmountCent((int) amountCent);
+        pool.setCostCent((int) costCent);
+        pool.setPlatformRetainCent((int) p);
+        pool.setOpsCent((int) o);
+        pool.setCfCostCent((int) f);
+        pool.setPoolCent((int) d);
+        pool.setReferralShareCent((int) r);
+        pool.setTeamShareCent((int) t);
+        pool.setTeamEightShareCent((int) eight);
+        pool.setTeamAwardCent((int) award);
+        pool.setStatus("pending");
+        pool.setCreatedAt(new Date());
+        profitPoolOrderMapper.insert(pool);
+
+        if (d > 0) {
+            // §8.1:D>0 生成团队 8 份分佣流水(pending,冷静期后 settled)
+            genTeamShareRecord(pool, memberId);
+        }
+        return pool;
+    }
+
+    /** 幂等查询:uk_order(order_type, order_id) 既有记录。 */
+    private ProfitPoolOrder findExisting(Long orderId, String orderType) {
+        if (profitPoolOrderMapper == null) {
+            return null;
+        }
+        try {
+            return profitPoolOrderMapper.selectOne(new LambdaQueryWrapper<ProfitPoolOrder>()
+                    .eq(ProfitPoolOrder::getOrderType, orderType)
+                    .eq(ProfitPoolOrder::getOrderId, orderId)
+                    .last("LIMIT 1"));
+        } catch (Exception e) {
+            log.warn("查询 profit_pool_order 幂等失败 orderType={} orderId={}: {}",
+                    orderType, orderId, e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 生成团队 8 份分佣流水(§7.3)。管道化(T9)阶段以订单归属成员为第 1 层基础记录,
+     * 星级/份数/金额的完整分配(§4.2 星级份数 + §4.4 团队奖)由团队分润服务按团队树与
+     * 星级快照落地(T2),此处仅落 pending 占位流水并保留 8 份池金额。
+     */
+    private void genTeamShareRecord(ProfitPoolOrder pool, Long memberId) {
+        if (teamShareRecordMapper == null) {
+            return;
+        }
+        Long uid = memberId != null ? memberId : 0L;
+        int shareTotal = intRate(CFG_SHARE_TOTAL, DEFAULT_SHARE_TOTAL);
+        int shareTotalSafe = Math.max(1, shareTotal);
+        TeamShareRecord record = new TeamShareRecord();
+        record.setOrderId(pool.getOrderId());
+        record.setOrderType(pool.getOrderType());
+        record.setPoolId(pool.getId());
+        record.setProviderUserId(uid);
+        record.setRecipientUserId(uid);
+        record.setLayer(1);
+        record.setShares(1);
+        record.setShareCent(pool.getTeamEightShareCent() / shareTotalSafe);
+        record.setStatus("pending");
+        record.setCreatedAt(new Date());
+        teamShareRecordMapper.insert(record);
+    }
+
+    /** §2.1 金额取整口径(逐项 round,HALF_UP 等价)。 */
+    private long roundPct(long base, double rate) {
+        return Math.round(base * rate);
+    }
+
+    /** 读取 double 比率:ConfigService 优先,回退 service_config 直读,再回退默认。 */
+    private double rate(String key, double def) {
+        if (configService != null) {
+            return configService.getDoubleConfig(key, def);
+        }
+        return readConfigDouble(key, def);
+    }
+
+    /** 读取 int 配置:ConfigService 优先,回退 service_config 直读,再回退默认。 */
+    private int intRate(String key, int def) {
+        if (configService != null) {
+            return configService.getIntConfig(key, def);
+        }
+        return readConfigInt(key, def);
+    }
+
+    private double readConfigDouble(String key, double def) {
+        String v = readConfigValue(key);
+        if (v == null) {
+            return def;
+        }
+        try {
+            return Double.parseDouble(v.trim());
+        } catch (NumberFormatException e) {
+            return def;
+        }
+    }
+
+    private int readConfigInt(String key, int def) {
+        String v = readConfigValue(key);
+        if (v == null) {
+            return def;
+        }
+        try {
+            return Integer.parseInt(v.trim());
+        } catch (NumberFormatException e) {
+            return def;
+        }
+    }
+
+    private String readConfigValue(String key) {
+        if (serviceConfigItemMapper == null) {
+            return null;
+        }
+        try {
+            ServiceConfigItem item = serviceConfigItemMapper.selectOne(
+                    new LambdaQueryWrapper<ServiceConfigItem>()
+                            .eq(ServiceConfigItem::getConfigKey, key)
+                            .last("LIMIT 1"));
+            return item != null ? item.getConfigValue() : null;
+        } catch (Exception e) {
+            log.warn("读取团队分润配置失败 key={}: {}", key, e.getMessage());
+            return null;
+        }
+    }
+}

+ 56 - 0
cfc-backend/src/main/resources/schema.sql

@@ -7106,3 +7106,59 @@ CREATE TABLE IF NOT EXISTS legacy_dimension_call_log (
     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
     UNIQUE KEY uk_legacy_dim_call_api_period (api_path, period)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='旧体系接口调用打点(P2-T3退役证据)';
+
+-- 迁移458: 单笔订单利润池计算明细表(团队分润 §7.1,uk_order 幂等)
+CREATE TABLE IF NOT EXISTS profit_pool_order (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    order_id BIGINT NOT NULL COMMENT '来源订单ID',
+    order_type VARCHAR(32) NOT NULL COMMENT '触发源: product/service/assessment/package/membership/subscription/assessment_retest',
+    product_id BIGINT NULL COMMENT '商品/服务ID',
+    amount_cent INT NOT NULL COMMENT '售价S(分)',
+    cost_cent INT NOT NULL COMMENT '平台成本价C(分)',
+    platform_retain_cent INT NOT NULL COMMENT '平台留存P=S-C(分)',
+    ops_cent INT NOT NULL COMMENT '平台运营O(分)',
+    cf_cost_cent INT NOT NULL COMMENT 'CF值成本F(分)',
+    pool_cent INT NOT NULL COMMENT '可分配池D(分)',
+    referral_share_cent INT NOT NULL COMMENT '推荐人体系R(分,CF值预算参照)',
+    team_share_cent INT NOT NULL COMMENT '团队体系T(分,货币)',
+    team_eight_share_cent INT NOT NULL COMMENT '8份分佣池(分,默认T×80%)',
+    team_award_cent INT NOT NULL COMMENT '团队奖池(分,默认T×20%)',
+    status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/settled/cancelled',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    settled_at DATETIME NULL COMMENT '结算时间',
+    UNIQUE KEY uk_order (order_type, order_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='单笔订单利润池计算明细';
+
+-- 迁移459: 团队8份分佣流水表(§7.3,uk_order_rec 幂等)
+CREATE TABLE IF NOT EXISTS team_share_record (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    order_id BIGINT NOT NULL COMMENT '来源订单ID',
+    order_type VARCHAR(32) NOT NULL COMMENT '触发源',
+    pool_id BIGINT NOT NULL COMMENT '归属利润池明细profit_pool_order.id',
+    provider_user_id BIGINT NOT NULL COMMENT '服务提供者(规划师/测评师)',
+    recipient_user_id BIGINT NOT NULL COMMENT '获得分润的团队成员',
+    layer INT NOT NULL COMMENT '1=服务者本人 2=引荐人 3=引荐人的引荐人',
+    star_level INT NULL COMMENT '该成员当期星级快照(1-6)',
+    shares INT NOT NULL COMMENT '分得份数(0-8)',
+    share_cent INT NOT NULL COMMENT '分润金额(分)',
+    status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/settled/cancelled',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    UNIQUE KEY uk_order_rec (order_id, recipient_user_id, layer),
+    KEY idx_recipient (recipient_user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='团队8份分佣流水(货币)';
+
+-- 迁移460: 团队奖流水表(§7.4,uk_order_team 幂等)
+CREATE TABLE IF NOT EXISTS team_award_record (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    order_id BIGINT NOT NULL COMMENT '来源订单ID',
+    order_type VARCHAR(32) NOT NULL COMMENT '触发源',
+    pool_id BIGINT NOT NULL COMMENT '归属利润池明细profit_pool_order.id',
+    team_id BIGINT NOT NULL COMMENT 'distribution_systems.id',
+    owner_user_id BIGINT NOT NULL COMMENT '团队负责人(owner_id/根)',
+    tier VARCHAR(8) NOT NULL COMMENT 'TA0/TA1/TA2/TA3',
+    award_cent INT NOT NULL COMMENT '团队奖金额(分)',
+    status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/settled/cancelled',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    UNIQUE KEY uk_order_team (order_id, team_id),
+    KEY idx_owner (owner_user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='团队奖流水(货币)';