Преглед изворни кода

feat(distribution): 销售体系佣金按唯一负责人整体结算(T37/C2)

- DistributionService.calculateCommission: 移除70/30 L1/L2三方拆分
  改为返回单一结算对象 settleToUserId=DistributionSystem.ownerId(唯一负责人),
  settleAmount=totalCommission整体结算, 平台不再参与团队内部分配
- ownerId为空时降级取体系树root节点(parentId=null)用户为负责人;
  无root则走无团队结算分支(settleToUserId=null, settleAmount=0)
- 旧字段 level1Amount/level2Amount/level1User/level2User/platformAmount 移除
- DistributionSystem.ownerId 补充唯一负责人结算语义注释
E2E Test Bot пре 1 недеља
родитељ
комит
a090c16c54

+ 1 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/DistributionSystem.java

@@ -14,6 +14,7 @@ public class DistributionSystem implements Serializable {
     private Long id;
     private String name;
     private String description;
+    /** 唯一负责人(结算对象):平台只向此用户整体结算,团队内部分配平台不负责(T37/C2 口径,2026-11-01 澄清) */
     private Long ownerId;
     private Integer profitRate;
     private Boolean enabled;

+ 35 - 22
cfc-backend/src/main/java/com/etotem/cfc/service/DistributionService.java

@@ -138,6 +138,27 @@ public class DistributionService {
         return Result.success(chain);
     }
 
+    /**
+     * 计算销售体系佣金 —— 负责人结算口径(2026-11-01 末次澄清,T37/C2)。
+     * <p>
+     * 平台从客户处收到费用后只向体系唯一负责人整体结算,团队内部如何分配平台不负责:
+     * <ul>
+     *   <li>{@code totalCommission = orderAmount × profitRate / 1000}:团队整体所得(平台抽成已含在 profitRate 内);</li>
+     *   <li>{@code settleToUserId} = 体系唯一负责人 {@link DistributionSystem#getOwnerId()};ownerId 为空时降级取体系树
+     *       root 节点(parentId=null)的用户作为负责人;两者皆无则走「无团队结算」分支(settleToUserId=null、settleAmount=0);</li>
+     *   <li>{@code settleAmount} = totalCommission:整体结算给负责人,不再做 70/30 L1/L2 三方拆分。</li>
+     * </ul>
+     * 「每个成员都有自己的团队」:以任一成员为 root 的子树 = 该成员的团队,整体结算给该成员(作为其子团队负责人),
+     * 成员间分配不再由平台计算。
+     * <p>
+     * 旧字段 {@code level1Amount/level2Amount/level1User/level2User/platformAmount}(70/30 沿 parent 链拆分)已移除
+     * (一期过渡,无任何消费者),不再输出三方拆分。
+     *
+     * @param systemId    销售体系ID
+     * @param buyerId     购买者用户ID(保留兼容签名,负责人结算口径下不再沿其上级链拆分)
+     * @param orderAmount 订单金额(分)
+     * @return 结算结果:{@code totalCommission, settleToUserId, settleAmount}
+     */
     public Result<Map<String, Object>> calculateCommission(Long systemId, Long buyerId, Integer orderAmount) {
         DistributionSystem system = distributionSystemMapper.selectById(systemId);
         if (system == null || !Boolean.TRUE.equals(system.getEnabled())) {
@@ -146,36 +167,28 @@ public class DistributionService {
 
         int totalCommission = orderAmount * system.getProfitRate() / 1000;
 
-        List<DistributionRelation> chain = new ArrayList<>();
-        Long currentUserId = buyerId;
-        for (int i = 0; i < 2; i++) {
-            DistributionRelation relation = distributionRelationMapper.selectOne(
+        // 唯一负责人:ownerId 优先;为空时降级取体系树 root 节点(parentId=null)用户
+        Long settleToUserId = system.getOwnerId();
+        if (settleToUserId == null) {
+            DistributionRelation root = distributionRelationMapper.selectOne(
                 new LambdaQueryWrapper<DistributionRelation>()
                     .eq(DistributionRelation::getSystemId, systemId)
-                    .eq(DistributionRelation::getUserId, currentUserId)
+                    .isNull(DistributionRelation::getParentId)
+                    .orderByAsc(DistributionRelation::getCreatedAt)
+                    .last("LIMIT 1")
             );
-            if (relation == null || relation.getParentId() == null) break;
-            DistributionRelation parent = distributionRelationMapper.selectOne(
-                new LambdaQueryWrapper<DistributionRelation>()
-                    .eq(DistributionRelation::getSystemId, systemId)
-                    .eq(DistributionRelation::getUserId, relation.getParentId())
-            );
-            if (parent == null) break;
-            chain.add(parent);
-            currentUserId = relation.getParentId();
+            if (root != null) {
+                settleToUserId = root.getUserId();
+            }
         }
 
-        int level1Amount = totalCommission * 70 / 100;
-        int level2Amount = totalCommission * 30 / 100;
+        // 无团队(ownerId 与 root 均不存在)时按 profitRate 走无团队结算分支:平台保留全部,无负责人可结算
+        int settleAmount = settleToUserId != null ? totalCommission : 0;
 
         Map<String, Object> result = new LinkedHashMap<>();
         result.put("totalCommission", totalCommission);
-        result.put("level1Amount", chain.size() >= 1 ? level1Amount : 0);
-        result.put("level1User", chain.size() >= 1 ? chain.get(0).getUserId() : null);
-        result.put("level2Amount", chain.size() >= 2 ? level2Amount : 0);
-        result.put("level2User", chain.size() >= 2 ? chain.get(1).getUserId() : null);
-        result.put("platformAmount", totalCommission - (chain.size() >= 1 ? level1Amount : 0)
-            - (chain.size() >= 2 ? level2Amount : 0));
+        result.put("settleToUserId", settleToUserId);
+        result.put("settleAmount", settleAmount);
         return Result.success(result);
     }