feat(task): 完善经销商订单结算功能支持三级分销和商品级别配置

- 引入商品实体和订单商品实体依赖,新增相关服务注入
- 实现商品分销开关检查,未开启分销的商品跳过结算流程
- 添加三级分销佣金计算逻辑,支持第三级经销商佣金结算
- 实现商品级别的分润比例配置,支持按商品设置不同的佣金率
- 新增商品分销配置解析功能,兼容旧版固定比例逻辑
- 扩展分佣记录实体,增加第三级用户和金额字段
- 更新日志输出格式,显示详细的分润比例和金额信息
- 优化门店分红计算,支持单门店汇总分润和多门店分级分润
This commit is contained in:
2026-01-28 14:49:49 +08:00
parent cbc9a1c861
commit fa53fd399f
2 changed files with 182 additions and 26 deletions

View File

@@ -5,10 +5,8 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.baomidou.mybatisplus.annotation.TableLogic;
import java.io.Serializable; import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@@ -76,6 +74,27 @@ public class ShopGoods implements Serializable {
@Schema(description = "佣金") @Schema(description = "佣金")
private BigDecimal commission; 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付款减库存)") @Schema(description = "库存计算方式(10下单减库存 20付款减库存)")
private Integer deductStockType; private Integer deductStockType;

View File

@@ -7,14 +7,18 @@ import com.gxwebsoft.shop.entity.ShopDealerCapital;
import com.gxwebsoft.shop.entity.ShopDealerOrder; import com.gxwebsoft.shop.entity.ShopDealerOrder;
import com.gxwebsoft.shop.entity.ShopDealerReferee; import com.gxwebsoft.shop.entity.ShopDealerReferee;
import com.gxwebsoft.shop.entity.ShopDealerUser; import com.gxwebsoft.shop.entity.ShopDealerUser;
import com.gxwebsoft.shop.entity.ShopGoods;
import com.gxwebsoft.shop.entity.ShopOrder; import com.gxwebsoft.shop.entity.ShopOrder;
import com.gxwebsoft.shop.entity.ShopOrderGoods;
import com.gxwebsoft.common.system.entity.User; import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.common.system.mapper.UserMapper; import com.gxwebsoft.common.system.mapper.UserMapper;
import com.gxwebsoft.shop.service.ShopDealerCapitalService; import com.gxwebsoft.shop.service.ShopDealerCapitalService;
import com.gxwebsoft.shop.service.ShopDealerOrderService; import com.gxwebsoft.shop.service.ShopDealerOrderService;
import com.gxwebsoft.shop.service.ShopDealerRefereeService; import com.gxwebsoft.shop.service.ShopDealerRefereeService;
import com.gxwebsoft.shop.service.ShopDealerUserService; import com.gxwebsoft.shop.service.ShopDealerUserService;
import com.gxwebsoft.shop.service.ShopGoodsService;
import com.gxwebsoft.shop.service.ShopOrderService; import com.gxwebsoft.shop.service.ShopOrderService;
import com.gxwebsoft.shop.service.ShopOrderGoodsService;
import com.gxwebsoft.shop.util.UpstreamUserFinder; import com.gxwebsoft.shop.util.UpstreamUserFinder;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
@@ -67,6 +71,12 @@ public class DealerOrderSettlement10584Task {
@Resource @Resource
private ShopDealerOrderService shopDealerOrderService; private ShopDealerOrderService shopDealerOrderService;
@Resource
private ShopGoodsService shopGoodsService;
@Resource
private ShopOrderGoodsService shopOrderGoodsService;
@Resource @Resource
private UserMapper userMapper; private UserMapper userMapper;
@@ -141,14 +151,32 @@ public class DealerOrderSettlement10584Task {
throw new IllegalStateException("订单金额为空或<=0无法结算 - orderId=" + order.getOrderId() + ", orderNo=" + order.getOrderNo()); throw new IllegalStateException("订单金额为空或<=0无法结算 - orderId=" + order.getOrderId() + ", orderNo=" + order.getOrderNo());
} }
log.info("开始结算订单 - orderId={}, orderNo={}, buyerUserId={}, payPrice={}", ShopGoods goods = findOrderSingleGoods(order);
order.getOrderId(), order.getOrderNo(), order.getUserId(), baseAmount); 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 // 1) 直推/间推shop_dealer_referee
DealerRefereeCommission dealerRefereeCommission = settleDealerRefereeCommission(order, baseAmount); DealerRefereeCommission dealerRefereeCommission = settleDealerRefereeCommission(order, baseAmount, rateConfig);
// 2) 门店分红上级:从下单用户开始逐级向上找,命中 ShopDealerUser.type=1 的最近两级(直推门店/间推门店)。 // 2) 门店分红上级:从下单用户开始逐级向上找,命中 ShopDealerUser.type=1 的最近两级(直推门店/间推门店)。
ShopRoleCommission shopRoleCommission = settleShopRoleRefereeCommission(order, baseAmount, level1ParentCache, shopRoleCache); ShopRoleCommission shopRoleCommission = settleShopRoleRefereeCommission(order, baseAmount, rateConfig, level1ParentCache, shopRoleCache);
// 3) 写入分销订单记录(用于排查/统计;详细分佣以 ShopDealerCapital 为准) // 3) 写入分销订单记录(用于排查/统计;详细分佣以 ShopDealerCapital 为准)
createDealerOrderRecord(order, baseAmount, dealerRefereeCommission, shopRoleCommission); createDealerOrderRecord(order, baseAmount, dealerRefereeCommission, shopRoleCommission);
@@ -156,7 +184,7 @@ public class DealerOrderSettlement10584Task {
log.info("订单结算完成 - orderId={}, orderNo={}, baseAmount={}", order.getOrderId(), order.getOrderNo(), baseAmount); 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 取); // 1) 同一 userId 下有 level=1/2 的多级关系(直接按 level 取);
// 2) 仅维护 level=1用“查两次”回退获取上级 // 2) 仅维护 level=1用“查两次”回退获取上级
@@ -165,19 +193,25 @@ public class DealerOrderSettlement10584Task {
if (simpleDealerId == null && directDealerId != null) { if (simpleDealerId == null && directDealerId != null) {
simpleDealerId = getDealerRefereeId(directDealerId, 1); 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 也照常发放两笔) // 允许同一条线内同一个人同时拿到“直推 + 间推”(即使 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={}", log.info("分销直推/间推/第3级查询结果 - orderNo={}, buyerUserId={}, directDealerId={}, directMoney={}, simpleDealerId={}, simpleMoney={}, thirdDealerId={}, thirdMoney={}",
order.getOrderNo(), order.getUserId(), directDealerId, directMoney, simpleDealerId, simpleMoney); order.getOrderNo(), order.getUserId(), directDealerId, directMoney, simpleDealerId, simpleMoney, thirdDealerId, thirdMoney);
// 直推:对方=买家;推荐奖(5%):对方=直推分销商(便于在资金明细中看出“来自哪个下级分销商/团队订单”) // 直推:对方=买家;推荐奖(5%):对方=直推分销商(便于在资金明细中看出“来自哪个下级分销商/团队订单”)
creditDealerCommission(directDealerId, directMoney, order, order.getUserId(), "直推佣金(10%)"); creditDealerCommission(directDealerId, directMoney, order, order.getUserId(), "直推佣金(rate=" + rateConfig.dealerDirectRate + ")");
creditDealerCommission(simpleDealerId, simpleMoney, order, directDealerId, "推荐奖(5%)"); 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) { private Integer getDealerRefereeId(Integer userId) {
@@ -204,6 +238,7 @@ public class DealerOrderSettlement10584Task {
private ShopRoleCommission settleShopRoleRefereeCommission( private ShopRoleCommission settleShopRoleRefereeCommission(
ShopOrder order, ShopOrder order,
BigDecimal baseAmount, BigDecimal baseAmount,
CommissionRateConfig rateConfig,
Map<Integer, Integer> level1ParentCache, Map<Integer, Integer> level1ParentCache,
Map<Integer, Boolean> shopRoleCache Map<Integer, Boolean> shopRoleCache
) { ) {
@@ -215,20 +250,22 @@ public class DealerOrderSettlement10584Task {
} }
if (shopRoleReferees.size() == 1) { if (shopRoleReferees.size() == 1) {
// 仅找到一个门店:3%都给他 // 仅找到一个门店:按(直推+间推)汇总发放
BigDecimal money = calcDividendMoney(baseAmount, RATE_0_03); BigDecimal singleStoreRate = safeRate(rateConfig.storeDirectRate).add(safeRate(rateConfig.storeSimpleRate));
log.info("分红发放(仅1门店3%) - orderNo={}, firstDividendUserId={}, money={}", order.getOrderNo(), shopRoleReferees.get(0), money); BigDecimal money = calcDividendMoney(baseAmount, singleStoreRate);
creditDealerCommission(shopRoleReferees.get(0), money, order, order.getUserId(), "门店直推佣金(仅1门店3%)"); 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); return new ShopRoleCommission(shopRoleReferees.get(0), money, null, BigDecimal.ZERO);
} }
// 两个或以上:第一个0.02第二个0.01 // 两个或以上:按配置分别发放
BigDecimal storeDirectMoney = calcDividendMoney(baseAmount, RATE_0_02); BigDecimal storeDirectMoney = calcDividendMoney(baseAmount, rateConfig.storeDirectRate);
BigDecimal storeSimpleMoney = calcDividendMoney(baseAmount, RATE_0_01); BigDecimal storeSimpleMoney = calcDividendMoney(baseAmount, rateConfig.storeSimpleRate);
log.info("分红发放(2人) - orderNo={}, firstDividendUserId={}, firstDividendMoney={}, secondDividendUserId={}, secondDividendMoney={}", log.info("分红发放(2人) - orderNo={}, firstDividendUserId={}, firstDividendRate={}, firstDividendMoney={}, secondDividendUserId={}, secondDividendRate={}, secondDividendMoney={}",
order.getOrderNo(), shopRoleReferees.get(0), storeDirectMoney, shopRoleReferees.get(1), storeSimpleMoney); order.getOrderNo(), shopRoleReferees.get(0), rateConfig.storeDirectRate, storeDirectMoney, shopRoleReferees.get(1), rateConfig.storeSimpleRate, storeSimpleMoney);
creditDealerCommission(shopRoleReferees.get(0), storeDirectMoney, order, order.getUserId(), "门店直推佣金(第1级2%)"); creditDealerCommission(shopRoleReferees.get(0), storeDirectMoney, order, order.getUserId(), "门店直推佣金(rate=" + rateConfig.storeDirectRate + ")");
creditDealerCommission(shopRoleReferees.get(1), storeSimpleMoney, order, order.getUserId(), "门店间推佣金(第2级1%)"); creditDealerCommission(shopRoleReferees.get(1), storeSimpleMoney, order, order.getUserId(), "门店间推佣金(rate=" + rateConfig.storeSimpleRate + ")");
return new ShopRoleCommission(shopRoleReferees.get(0), storeDirectMoney, shopRoleReferees.get(1), storeSimpleMoney); return new ShopRoleCommission(shopRoleReferees.get(0), storeDirectMoney, shopRoleReferees.get(1), storeSimpleMoney);
} }
@@ -474,6 +511,8 @@ public class DealerOrderSettlement10584Task {
dealerOrder.setFirstMoney(dealerRefereeCommission.directMoney); dealerOrder.setFirstMoney(dealerRefereeCommission.directMoney);
dealerOrder.setSecondUserId(dealerRefereeCommission.simpleDealerId); dealerOrder.setSecondUserId(dealerRefereeCommission.simpleDealerId);
dealerOrder.setSecondMoney(dealerRefereeCommission.simpleMoney); dealerOrder.setSecondMoney(dealerRefereeCommission.simpleMoney);
dealerOrder.setThirdUserId(dealerRefereeCommission.thirdDealerId);
dealerOrder.setThirdMoney(dealerRefereeCommission.thirdMoney);
// 门店(角色shop)两级分红单独落字段(详细以 ShopDealerCapital 为准) // 门店(角色shop)两级分红单独落字段(详细以 ShopDealerCapital 为准)
dealerOrder.setFirstDividendUser(shopRoleCommission.storeDirectUserId); dealerOrder.setFirstDividendUser(shopRoleCommission.storeDirectUserId);
@@ -498,6 +537,7 @@ public class DealerOrderSettlement10584Task {
// 轻量“过程”留痕,方便排查;详细分佣以 ShopDealerCapital 为准。 // 轻量“过程”留痕,方便排查;详细分佣以 ShopDealerCapital 为准。
return "direct=" + dealerRefereeCommission.directDealerId + ":" + dealerRefereeCommission.directMoney return "direct=" + dealerRefereeCommission.directDealerId + ":" + dealerRefereeCommission.directMoney
+ ",simple=" + dealerRefereeCommission.simpleDealerId + ":" + dealerRefereeCommission.simpleMoney + ",simple=" + dealerRefereeCommission.simpleDealerId + ":" + dealerRefereeCommission.simpleMoney
+ ",third=" + dealerRefereeCommission.thirdDealerId + ":" + dealerRefereeCommission.thirdMoney
+ ",dividend1=" + shopRoleCommission.storeDirectUserId + ":" + shopRoleCommission.storeDirectMoney + ",dividend1=" + shopRoleCommission.storeDirectUserId + ":" + shopRoleCommission.storeDirectMoney
+ ",dividend2=" + shopRoleCommission.storeSimpleUserId + ":" + shopRoleCommission.storeSimpleMoney; + ",dividend2=" + shopRoleCommission.storeSimpleUserId + ":" + shopRoleCommission.storeSimpleMoney;
} }
@@ -523,17 +563,114 @@ public class DealerOrderSettlement10584Task {
return base.multiply(rate).setScale(DIVIDEND_SCALE, RoundingMode.HALF_UP); 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<ShopOrderGoods>()
.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<ShopGoods>()
.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 static class DealerRefereeCommission {
private final Integer directDealerId; private final Integer directDealerId;
private final BigDecimal directMoney; private final BigDecimal directMoney;
private final Integer simpleDealerId; private final Integer simpleDealerId;
private final BigDecimal simpleMoney; 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.directDealerId = directDealerId;
this.directMoney = directMoney != null ? directMoney : BigDecimal.ZERO; this.directMoney = directMoney != null ? directMoney : BigDecimal.ZERO;
this.simpleDealerId = simpleDealerId; this.simpleDealerId = simpleDealerId;
this.simpleMoney = simpleMoney != null ? simpleMoney : BigDecimal.ZERO; this.simpleMoney = simpleMoney != null ? simpleMoney : BigDecimal.ZERO;
this.thirdDealerId = thirdDealerId;
this.thirdMoney = thirdMoney != null ? thirdMoney : BigDecimal.ZERO;
} }
} }