From fa53fd399fe8293a1ffbfc209ebb3e014eda6d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Wed, 28 Jan 2026 14:49:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(task):=20=E5=AE=8C=E5=96=84=E7=BB=8F?= =?UTF-8?q?=E9=94=80=E5=95=86=E8=AE=A2=E5=8D=95=E7=BB=93=E7=AE=97=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=94=AF=E6=8C=81=E4=B8=89=E7=BA=A7=E5=88=86=E9=94=80?= =?UTF-8?q?=E5=92=8C=E5=95=86=E5=93=81=E7=BA=A7=E5=88=AB=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入商品实体和订单商品实体依赖,新增相关服务注入 - 实现商品分销开关检查,未开启分销的商品跳过结算流程 - 添加三级分销佣金计算逻辑,支持第三级经销商佣金结算 - 实现商品级别的分润比例配置,支持按商品设置不同的佣金率 - 新增商品分销配置解析功能,兼容旧版固定比例逻辑 - 扩展分佣记录实体,增加第三级用户和金额字段 - 更新日志输出格式,显示详细的分润比例和金额信息 - 优化门店分红计算,支持单门店汇总分润和多门店分级分润 --- .../com/gxwebsoft/shop/entity/ShopGoods.java | 23 ++- .../task/DealerOrderSettlement10584Task.java | 185 +++++++++++++++--- 2 files changed, 182 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopGoods.java b/src/main/java/com/gxwebsoft/shop/entity/ShopGoods.java index 3d03203..953b867 100644 --- a/src/main/java/com/gxwebsoft/shop/entity/ShopGoods.java +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopGoods.java @@ -5,10 +5,8 @@ import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import java.time.LocalDateTime; import com.fasterxml.jackson.annotation.JsonFormat; -import com.baomidou.mybatisplus.annotation.TableLogic; import java.io.Serializable; import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -76,6 +74,27 @@ public class ShopGoods implements Serializable { @Schema(description = "佣金") private BigDecimal commission; + @Schema(description = "是否开启分销佣金(0否 1是)") + private Integer isOpenCommission; + + @Schema(description = "佣金类型(10固定金额 20百分比)") + private Integer commissionType; + + @Schema(description = "分销佣金(一级)") + private BigDecimal firstMoney; + + @Schema(description = "分销佣金(二级)") + private BigDecimal secondMoney; + + @Schema(description = "分销佣金(三级)") + private BigDecimal thirdMoney; + + @Schema(description = "分红(一级)") + private BigDecimal firstDividend; + + @Schema(description = "分红(二级)") + private BigDecimal secondDividend; + @Schema(description = "库存计算方式(10下单减库存 20付款减库存)") private Integer deductStockType; diff --git a/src/main/java/com/gxwebsoft/shop/task/DealerOrderSettlement10584Task.java b/src/main/java/com/gxwebsoft/shop/task/DealerOrderSettlement10584Task.java index 2febcd6..ee46762 100644 --- a/src/main/java/com/gxwebsoft/shop/task/DealerOrderSettlement10584Task.java +++ b/src/main/java/com/gxwebsoft/shop/task/DealerOrderSettlement10584Task.java @@ -7,14 +7,18 @@ import com.gxwebsoft.shop.entity.ShopDealerCapital; import com.gxwebsoft.shop.entity.ShopDealerOrder; import com.gxwebsoft.shop.entity.ShopDealerReferee; import com.gxwebsoft.shop.entity.ShopDealerUser; +import com.gxwebsoft.shop.entity.ShopGoods; import com.gxwebsoft.shop.entity.ShopOrder; +import com.gxwebsoft.shop.entity.ShopOrderGoods; import com.gxwebsoft.common.system.entity.User; import com.gxwebsoft.common.system.mapper.UserMapper; import com.gxwebsoft.shop.service.ShopDealerCapitalService; import com.gxwebsoft.shop.service.ShopDealerOrderService; import com.gxwebsoft.shop.service.ShopDealerRefereeService; import com.gxwebsoft.shop.service.ShopDealerUserService; +import com.gxwebsoft.shop.service.ShopGoodsService; import com.gxwebsoft.shop.service.ShopOrderService; +import com.gxwebsoft.shop.service.ShopOrderGoodsService; import com.gxwebsoft.shop.util.UpstreamUserFinder; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; @@ -67,6 +71,12 @@ public class DealerOrderSettlement10584Task { @Resource private ShopDealerOrderService shopDealerOrderService; + @Resource + private ShopGoodsService shopGoodsService; + + @Resource + private ShopOrderGoodsService shopOrderGoodsService; + @Resource private UserMapper userMapper; @@ -141,14 +151,32 @@ public class DealerOrderSettlement10584Task { throw new IllegalStateException("订单金额为空或<=0,无法结算 - orderId=" + order.getOrderId() + ", orderNo=" + order.getOrderNo()); } - log.info("开始结算订单 - orderId={}, orderNo={}, buyerUserId={}, payPrice={}", - order.getOrderId(), order.getOrderNo(), order.getUserId(), baseAmount); + ShopGoods goods = findOrderSingleGoods(order); + if (goods != null && goods.getIsOpenCommission() != null && goods.getIsOpenCommission() != 1) { + log.info("商品未开启分销,跳过订单结算 - orderId={}, orderNo={}, buyerUserId={}, goodsId={}, isOpenCommission={}", + order.getOrderId(), order.getOrderNo(), order.getUserId(), goods.getGoodsId(), goods.getIsOpenCommission()); + return; + } + + CommissionRateConfig rateConfig = resolveCommissionRateConfig(goods); + + log.info("开始结算订单 - orderId={}, orderNo={}, buyerUserId={}, payPrice={}, goodsId={}, rates[dealer1={}, dealer2={}, dealer3={}, div1={}, div2={}]", + order.getOrderId(), + order.getOrderNo(), + order.getUserId(), + baseAmount, + goods != null ? goods.getGoodsId() : null, + rateConfig.dealerDirectRate, + rateConfig.dealerSimpleRate, + rateConfig.dealerThirdRate, + rateConfig.storeDirectRate, + rateConfig.storeSimpleRate); // 1) 直推/间推(shop_dealer_referee) - DealerRefereeCommission dealerRefereeCommission = settleDealerRefereeCommission(order, baseAmount); + DealerRefereeCommission dealerRefereeCommission = settleDealerRefereeCommission(order, baseAmount, rateConfig); // 2) 门店分红上级:从下单用户开始逐级向上找,命中 ShopDealerUser.type=1 的最近两级(直推门店/间推门店)。 - ShopRoleCommission shopRoleCommission = settleShopRoleRefereeCommission(order, baseAmount, level1ParentCache, shopRoleCache); + ShopRoleCommission shopRoleCommission = settleShopRoleRefereeCommission(order, baseAmount, rateConfig, level1ParentCache, shopRoleCache); // 3) 写入分销订单记录(用于排查/统计;详细分佣以 ShopDealerCapital 为准) createDealerOrderRecord(order, baseAmount, dealerRefereeCommission, shopRoleCommission); @@ -156,7 +184,7 @@ public class DealerOrderSettlement10584Task { log.info("订单结算完成 - orderId={}, orderNo={}, baseAmount={}", order.getOrderId(), order.getOrderNo(), baseAmount); } - private DealerRefereeCommission settleDealerRefereeCommission(ShopOrder order, BigDecimal baseAmount) { + private DealerRefereeCommission settleDealerRefereeCommission(ShopOrder order, BigDecimal baseAmount, CommissionRateConfig rateConfig) { // 兼容两种数据形态: // 1) 同一 userId 下有 level=1/2 的多级关系(直接按 level 取); // 2) 仅维护 level=1(用“查两次”回退获取上级)。 @@ -165,19 +193,25 @@ public class DealerOrderSettlement10584Task { if (simpleDealerId == null && directDealerId != null) { simpleDealerId = getDealerRefereeId(directDealerId, 1); } + Integer thirdDealerId = getDealerRefereeId(order.getUserId(), 3); + if (thirdDealerId == null && simpleDealerId != null) { + thirdDealerId = getDealerRefereeId(simpleDealerId, 1); + } - BigDecimal directMoney = directDealerId != null ? calcMoney(baseAmount, RATE_0_10) : BigDecimal.ZERO; + BigDecimal directMoney = directDealerId != null ? calcMoney(baseAmount, rateConfig.dealerDirectRate) : BigDecimal.ZERO; // 允许同一条线内同一个人同时拿到“直推 + 间推”(即使 directDealerId == simpleDealerId 也照常发放两笔) - BigDecimal simpleMoney = simpleDealerId != null ? calcMoney(baseAmount, RATE_0_05) : BigDecimal.ZERO; + BigDecimal simpleMoney = simpleDealerId != null ? calcMoney(baseAmount, rateConfig.dealerSimpleRate) : BigDecimal.ZERO; + BigDecimal thirdMoney = thirdDealerId != null ? calcMoney(baseAmount, rateConfig.dealerThirdRate) : BigDecimal.ZERO; - log.info("分销直推/间推查询结果 - orderNo={}, buyerUserId={}, directDealerId={}, directMoney={}, simpleDealerId={}, simpleMoney={}", - order.getOrderNo(), order.getUserId(), directDealerId, directMoney, simpleDealerId, simpleMoney); + log.info("分销直推/间推/第3级查询结果 - orderNo={}, buyerUserId={}, directDealerId={}, directMoney={}, simpleDealerId={}, simpleMoney={}, thirdDealerId={}, thirdMoney={}", + order.getOrderNo(), order.getUserId(), directDealerId, directMoney, simpleDealerId, simpleMoney, thirdDealerId, thirdMoney); // 直推:对方=买家;推荐奖(5%):对方=直推分销商(便于在资金明细中看出“来自哪个下级分销商/团队订单”) - creditDealerCommission(directDealerId, directMoney, order, order.getUserId(), "直推佣金(10%)"); - creditDealerCommission(simpleDealerId, simpleMoney, order, directDealerId, "推荐奖(5%)"); + creditDealerCommission(directDealerId, directMoney, order, order.getUserId(), "直推佣金(rate=" + rateConfig.dealerDirectRate + ")"); + creditDealerCommission(simpleDealerId, simpleMoney, order, directDealerId, "推荐奖(rate=" + rateConfig.dealerSimpleRate + ")"); + creditDealerCommission(thirdDealerId, thirdMoney, order, simpleDealerId, "第3级佣金(rate=" + rateConfig.dealerThirdRate + ")"); - return new DealerRefereeCommission(directDealerId, directMoney, simpleDealerId, simpleMoney); + return new DealerRefereeCommission(directDealerId, directMoney, simpleDealerId, simpleMoney, thirdDealerId, thirdMoney); } private Integer getDealerRefereeId(Integer userId) { @@ -204,6 +238,7 @@ public class DealerOrderSettlement10584Task { private ShopRoleCommission settleShopRoleRefereeCommission( ShopOrder order, BigDecimal baseAmount, + CommissionRateConfig rateConfig, Map level1ParentCache, Map shopRoleCache ) { @@ -215,20 +250,22 @@ public class DealerOrderSettlement10584Task { } if (shopRoleReferees.size() == 1) { - // 仅找到一个门店:3%都给他 - BigDecimal money = calcDividendMoney(baseAmount, RATE_0_03); - log.info("分红发放(仅1门店,3%) - orderNo={}, firstDividendUserId={}, money={}", order.getOrderNo(), shopRoleReferees.get(0), money); - creditDealerCommission(shopRoleReferees.get(0), money, order, order.getUserId(), "门店直推佣金(仅1门店,3%)"); + // 仅找到一个门店:按(直推+间推)汇总发放 + BigDecimal singleStoreRate = safeRate(rateConfig.storeDirectRate).add(safeRate(rateConfig.storeSimpleRate)); + BigDecimal money = calcDividendMoney(baseAmount, singleStoreRate); + log.info("分红发放(仅1门店) - orderNo={}, firstDividendUserId={}, rate={}, money={}", + order.getOrderNo(), shopRoleReferees.get(0), singleStoreRate, money); + creditDealerCommission(shopRoleReferees.get(0), money, order, order.getUserId(), "门店直推佣金(仅1门店,rate=" + singleStoreRate + ")"); return new ShopRoleCommission(shopRoleReferees.get(0), money, null, BigDecimal.ZERO); } - // 两个或以上:第一个0.02,第二个0.01 - BigDecimal storeDirectMoney = calcDividendMoney(baseAmount, RATE_0_02); - BigDecimal storeSimpleMoney = calcDividendMoney(baseAmount, RATE_0_01); - log.info("分红发放(2人) - orderNo={}, firstDividendUserId={}, firstDividendMoney={}, secondDividendUserId={}, secondDividendMoney={}", - order.getOrderNo(), shopRoleReferees.get(0), storeDirectMoney, shopRoleReferees.get(1), storeSimpleMoney); - creditDealerCommission(shopRoleReferees.get(0), storeDirectMoney, order, order.getUserId(), "门店直推佣金(第1级,2%)"); - creditDealerCommission(shopRoleReferees.get(1), storeSimpleMoney, order, order.getUserId(), "门店间推佣金(第2级,1%)"); + // 两个或以上:按配置分别发放 + BigDecimal storeDirectMoney = calcDividendMoney(baseAmount, rateConfig.storeDirectRate); + BigDecimal storeSimpleMoney = calcDividendMoney(baseAmount, rateConfig.storeSimpleRate); + log.info("分红发放(2人) - orderNo={}, firstDividendUserId={}, firstDividendRate={}, firstDividendMoney={}, secondDividendUserId={}, secondDividendRate={}, secondDividendMoney={}", + order.getOrderNo(), shopRoleReferees.get(0), rateConfig.storeDirectRate, storeDirectMoney, shopRoleReferees.get(1), rateConfig.storeSimpleRate, storeSimpleMoney); + creditDealerCommission(shopRoleReferees.get(0), storeDirectMoney, order, order.getUserId(), "门店直推佣金(rate=" + rateConfig.storeDirectRate + ")"); + creditDealerCommission(shopRoleReferees.get(1), storeSimpleMoney, order, order.getUserId(), "门店间推佣金(rate=" + rateConfig.storeSimpleRate + ")"); return new ShopRoleCommission(shopRoleReferees.get(0), storeDirectMoney, shopRoleReferees.get(1), storeSimpleMoney); } @@ -474,6 +511,8 @@ public class DealerOrderSettlement10584Task { dealerOrder.setFirstMoney(dealerRefereeCommission.directMoney); dealerOrder.setSecondUserId(dealerRefereeCommission.simpleDealerId); dealerOrder.setSecondMoney(dealerRefereeCommission.simpleMoney); + dealerOrder.setThirdUserId(dealerRefereeCommission.thirdDealerId); + dealerOrder.setThirdMoney(dealerRefereeCommission.thirdMoney); // 门店(角色shop)两级分红单独落字段(详细以 ShopDealerCapital 为准) dealerOrder.setFirstDividendUser(shopRoleCommission.storeDirectUserId); @@ -498,6 +537,7 @@ public class DealerOrderSettlement10584Task { // 轻量“过程”留痕,方便排查;详细分佣以 ShopDealerCapital 为准。 return "direct=" + dealerRefereeCommission.directDealerId + ":" + dealerRefereeCommission.directMoney + ",simple=" + dealerRefereeCommission.simpleDealerId + ":" + dealerRefereeCommission.simpleMoney + + ",third=" + dealerRefereeCommission.thirdDealerId + ":" + dealerRefereeCommission.thirdMoney + ",dividend1=" + shopRoleCommission.storeDirectUserId + ":" + shopRoleCommission.storeDirectMoney + ",dividend2=" + shopRoleCommission.storeSimpleUserId + ":" + shopRoleCommission.storeSimpleMoney; } @@ -523,17 +563,114 @@ public class DealerOrderSettlement10584Task { return base.multiply(rate).setScale(DIVIDEND_SCALE, RoundingMode.HALF_UP); } + private ShopGoods findOrderSingleGoods(ShopOrder order) { + if (order == null) { + return null; + } + + Integer goodsId = order.getFormId(); + if (goodsId == null) { + ShopOrderGoods orderGoods = shopOrderGoodsService.getOne( + new LambdaQueryWrapper() + .eq(ShopOrderGoods::getTenantId, TENANT_ID) + .eq(ShopOrderGoods::getOrderId, order.getOrderId()) + .orderByDesc(ShopOrderGoods::getId) + .last("limit 1") + ); + goodsId = orderGoods != null ? orderGoods.getGoodsId() : null; + } + + if (goodsId == null) { + return null; + } + + return shopGoodsService.getOne( + new LambdaQueryWrapper() + .eq(ShopGoods::getTenantId, TENANT_ID) + .eq(ShopGoods::getGoodsId, goodsId) + .last("limit 1") + ); + } + + private CommissionRateConfig resolveCommissionRateConfig(ShopGoods goods) { + if (goods == null) { + // 无商品信息:回退旧逻辑 + return new CommissionRateConfig(RATE_0_10, RATE_0_05, BigDecimal.ZERO, RATE_0_02, RATE_0_01); + } + + boolean anyConfigured = + (goods.getFirstMoney() != null && goods.getFirstMoney().signum() > 0) + || (goods.getSecondMoney() != null && goods.getSecondMoney().signum() > 0) + || (goods.getThirdMoney() != null && goods.getThirdMoney().signum() > 0) + || (goods.getFirstDividend() != null && goods.getFirstDividend().signum() > 0) + || (goods.getSecondDividend() != null && goods.getSecondDividend().signum() > 0); + + if (!anyConfigured) { + // 商品未配置新分润字段:回退旧逻辑(避免历史数据字段默认0导致“全不结算”) + return new CommissionRateConfig(RATE_0_10, RATE_0_05, BigDecimal.ZERO, RATE_0_02, RATE_0_01); + } + + // 商品配置了新分润字段:只按“配置值>0”的比例结算,未配置则视为0(由运营侧完全控制) + BigDecimal dealerDirectRate = safeRatePositive(goods.getFirstMoney()); + BigDecimal dealerSimpleRate = safeRatePositive(goods.getSecondMoney()); + BigDecimal dealerThirdRate = safeRatePositive(goods.getThirdMoney()); + BigDecimal storeDirectRate = safeRatePositive(goods.getFirstDividend()); + BigDecimal storeSimpleRate = safeRatePositive(goods.getSecondDividend()); + return new CommissionRateConfig(dealerDirectRate, dealerSimpleRate, dealerThirdRate, storeDirectRate, storeSimpleRate); + } + + private BigDecimal safeRate(BigDecimal rate) { + return rate != null ? rate : BigDecimal.ZERO; + } + + private BigDecimal safeRatePositive(BigDecimal rate) { + return rate != null && rate.signum() > 0 ? rate : BigDecimal.ZERO; + } + + private static class CommissionRateConfig { + private final BigDecimal dealerDirectRate; + private final BigDecimal dealerSimpleRate; + private final BigDecimal dealerThirdRate; + private final BigDecimal storeDirectRate; + private final BigDecimal storeSimpleRate; + + private CommissionRateConfig( + BigDecimal dealerDirectRate, + BigDecimal dealerSimpleRate, + BigDecimal dealerThirdRate, + BigDecimal storeDirectRate, + BigDecimal storeSimpleRate + ) { + this.dealerDirectRate = dealerDirectRate != null ? dealerDirectRate : BigDecimal.ZERO; + this.dealerSimpleRate = dealerSimpleRate != null ? dealerSimpleRate : BigDecimal.ZERO; + this.dealerThirdRate = dealerThirdRate != null ? dealerThirdRate : BigDecimal.ZERO; + this.storeDirectRate = storeDirectRate != null ? storeDirectRate : BigDecimal.ZERO; + this.storeSimpleRate = storeSimpleRate != null ? storeSimpleRate : BigDecimal.ZERO; + } + } + private static class DealerRefereeCommission { private final Integer directDealerId; private final BigDecimal directMoney; private final Integer simpleDealerId; private final BigDecimal simpleMoney; + private final Integer thirdDealerId; + private final BigDecimal thirdMoney; - private DealerRefereeCommission(Integer directDealerId, BigDecimal directMoney, Integer simpleDealerId, BigDecimal simpleMoney) { + private DealerRefereeCommission( + Integer directDealerId, + BigDecimal directMoney, + Integer simpleDealerId, + BigDecimal simpleMoney, + Integer thirdDealerId, + BigDecimal thirdMoney + ) { this.directDealerId = directDealerId; this.directMoney = directMoney != null ? directMoney : BigDecimal.ZERO; this.simpleDealerId = simpleDealerId; this.simpleMoney = simpleMoney != null ? simpleMoney : BigDecimal.ZERO; + this.thirdDealerId = thirdDealerId; + this.thirdMoney = thirdMoney != null ? thirdMoney : BigDecimal.ZERO; } }