feat(task): 完善经销商订单结算功能支持三级分销和商品级别配置
- 引入商品实体和订单商品实体依赖,新增相关服务注入 - 实现商品分销开关检查,未开启分销的商品跳过结算流程 - 添加三级分销佣金计算逻辑,支持第三级经销商佣金结算 - 实现商品级别的分润比例配置,支持按商品设置不同的佣金率 - 新增商品分销配置解析功能,兼容旧版固定比例逻辑 - 扩展分佣记录实体,增加第三级用户和金额字段 - 更新日志输出格式,显示详细的分润比例和金额信息 - 优化门店分红计算,支持单门店汇总分润和多门店分级分润
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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<Integer, Integer> level1ParentCache,
|
||||
Map<Integer, Boolean> 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<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 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user