1.调整订单分销、分润、分红结算算法功能

2.商品订单支付成功增加执行分销员分销、统计门店/服务商分销业务、执行总分红业务功能
3.配送员完成配送增加解冻商品订单业务功能
4.订单分销记录增加关联结算单号、结算状态字段,方便门店/服务商做计算做准备
This commit is contained in:
2026-05-11 17:55:48 +08:00
parent bc88f54c24
commit 991b6fe529
10 changed files with 1457 additions and 894 deletions

View File

@@ -2,11 +2,11 @@ package com.gxwebsoft.glt.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.gxwebsoft.common.core.annotation.IgnoreTenant;
import com.gxwebsoft.glt.entity.GltTicketTemplate;
import com.gxwebsoft.glt.entity.GltUserTicket;
import com.gxwebsoft.glt.entity.GltUserTicketLog;
import com.gxwebsoft.glt.entity.GltUserTicketRelease;
import com.gxwebsoft.glt.task.DealerOrderSettlement10584Task;
import com.gxwebsoft.shop.entity.ShopOrder;
import com.gxwebsoft.shop.entity.ShopOrderGoods;
import com.gxwebsoft.shop.service.ShopOrderGoodsService;
@@ -15,7 +15,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
@@ -56,6 +55,7 @@ public class GltTicketIssueService {
private final GltUserTicketReleaseService gltUserTicketReleaseService;
private final GltUserTicketLogService gltUserTicketLogService;
private final TransactionTemplate transactionTemplate;
private final DealerOrderSettlement10584Task dealerOrderSettlement;
/**
* 扫描“今日订单”,执行套票发放。
@@ -140,7 +140,10 @@ public class GltTicketIssueService {
//1.发送水票
suerTicketRelease(orderNo, tenantId);
//2.优惠券扣减、积分发送、消息通知
//2.执行分销员分销、统计门店/服务商分销业务
dealerOrderSettlement.orderSettlement(orderNo);
//3.执行平台分红业务 TODO 待开发
}
@@ -149,6 +152,7 @@ public class GltTicketIssueService {
* @param orderNo 订单号
* @param tenantId 租户ID
*/
@Transactional
public void suerTicketRelease(String orderNo, Integer tenantId){
//1.订单为空跳过执行
ShopOrder shopOrder = shopOrderService.getByOrderNo(orderNo, tenantId);

View File

@@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.common.core.enums.ShopDealerCapitalUpdateEnum;
import com.gxwebsoft.common.core.enums.ShopDealerTypeEnum;
import com.gxwebsoft.common.core.exception.BusinessException;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
@@ -19,6 +21,7 @@ import com.gxwebsoft.glt.param.GltTicketOrderParam;
import com.gxwebsoft.glt.service.GltTicketOrderService;
import com.gxwebsoft.glt.service.GltUserTicketLogService;
import com.gxwebsoft.glt.service.GltUserTicketService;
import com.gxwebsoft.shop.dto.ShopDealerUserReduceDto;
import com.gxwebsoft.shop.entity.*;
import com.gxwebsoft.shop.mapper.ShopUserAddressMapper;
import com.gxwebsoft.shop.service.ShopDealerCapitalService;
@@ -765,6 +768,25 @@ public class GltTicketOrderServiceImpl extends ServiceImpl<GltTicketOrderMapper,
}
}
//查询未完成订单,完成资金解冻
LambdaQueryWrapper<ShopOrder> orderLambdaQueryWrapper;
if(shopOrderId != null){
orderLambdaQueryWrapper = new LambdaQueryWrapper<ShopOrder>().eq(ShopOrder::getOrderId, shopOrderId).eq(ShopOrder::getOrderStatus, 0);
}else{
orderLambdaQueryWrapper = new LambdaQueryWrapper<ShopOrder>().eq(ShopOrder::getOrderId, shopOrderNo).eq(ShopOrder::getOrderStatus, 0);
}
ShopOrder order = shopOrderService.getOne(orderLambdaQueryWrapper);
if(order != null){
ShopDealerUserReduceDto reduceDto = new ShopDealerUserReduceDto();
reduceDto.setTypeEnum(ShopDealerTypeEnum.DEFROST);
reduceDto.setOrderUserId(order.getUserId());
reduceDto.setOrderNo(order.getOrderNo());
reduceDto.setUpdateEnum(ShopDealerCapitalUpdateEnum.FREEZE_MONEY_THAW);
//按订单号资金解冻
shopDealerUserService.reduceBalance(reduceDto);
}
LambdaUpdateWrapper<ShopOrder> uw = new LambdaUpdateWrapper<ShopOrder>()
.eq(ShopOrder::getTenantId, tenantId)
.eq(ShopOrder::getDeleted, 0)
@@ -776,7 +798,6 @@ public class GltTicketOrderServiceImpl extends ServiceImpl<GltTicketOrderMapper,
} else {
uw.eq(ShopOrder::getOrderNo, shopOrderNo);
}
boolean updated = shopOrderService.update(uw);
if (updated) {
return;

View File

@@ -1,41 +1,37 @@
package com.gxwebsoft.glt.task;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.gxwebsoft.common.core.annotation.IgnoreTenant;
import com.gxwebsoft.glt.entity.GltTicketTemplate;
import com.gxwebsoft.glt.service.GltTicketTemplateService;
import com.gxwebsoft.shop.entity.ShopDealerCapital;
import com.gxwebsoft.shop.entity.ShopDealerOrder;
import com.gxwebsoft.shop.entity.ShopDealerReferee;
import com.gxwebsoft.shop.entity.ShopDealerSetting;
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.core.enums.ShopDealerCapitalUpdateEnum;
import com.gxwebsoft.common.core.enums.ShopDealerTypeEnum;
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.ShopDealerSettingService;
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.glt.entity.GltTicketTemplate;
import com.gxwebsoft.glt.service.GltTicketTemplateService;
import com.gxwebsoft.shop.dto.ShopDealerUserReduceDto;
import com.gxwebsoft.shop.entity.*;
import com.gxwebsoft.shop.mapper.ShopDealerRefereeMapper;
import com.gxwebsoft.shop.mapper.ShopOrderMapper;
import com.gxwebsoft.shop.service.*;
import com.gxwebsoft.shop.util.UpstreamUserFinder;
import com.alibaba.fastjson.JSONObject;
import com.gxwebsoft.shop.vo.ShopOrderGoodsVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
/**
* 租户10584分销订单结算任务
@@ -50,7 +46,6 @@ public class DealerOrderSettlement10584Task {
private static final BigDecimal RATE_0_10 = new BigDecimal("0.10");
private static final BigDecimal RATE_0_05 = new BigDecimal("0.05");
private static final BigDecimal RATE_0_03 = new BigDecimal("0.03");
private static final BigDecimal RATE_0_02 = new BigDecimal("0.02");
private static final BigDecimal RATE_0_01 = new BigDecimal("0.01");
private static final BigDecimal TOTAL_DEALER_DIVIDEND_RATE = RATE_0_01;
@@ -92,14 +87,23 @@ public class DealerOrderSettlement10584Task {
@Resource
private GltTicketTemplateService gltTicketTemplateService;
@Resource
private ShopOrderMapper shopOrderMapper;
@Resource
private ShopDealerRefereeMapper shopDealerRefereeMapper;
/**
* 每10秒执行一次。
*/
@Scheduled(cron = "0/10 * * * * ?")
// @Scheduled(cron = "0/10 * * * * ?")
@IgnoreTenant("该定时任务仅处理租户10584但需要显式按tenantId过滤避免定时任务线程无租户上下文导致查询异常")
public void settleTenant10584Orders() {
try {
//获取水票模板对应的商品信息列表
Set<Integer> waterFormIds = loadWaterFormIds();
//查询商品列表存在已支付未核销订单数据
List<ShopOrder> orders = findUnsettledPaidOrders(waterFormIds);
if (orders.isEmpty()) {
return;
@@ -108,7 +112,11 @@ public class DealerOrderSettlement10584Task {
// Per-run caches to reduce DB chatter across orders.
Map<Integer, Integer> level1ParentCache = new HashMap<>();
Map<Integer, Boolean> shopRoleCache = new HashMap<>();
//获取系统设置分销等级
DealerBasicSetting dealerBasicSetting = findDealerBasicSetting();
//获取分销员type=2第一个分销人作为平台总分红人
ShopDealerUser totalDealerUser = findTotalDealerUser();
if (totalDealerUser == null || totalDealerUser.getUserId() == null) {
log.warn("未找到分红账号,订单仍可结算但不会发放分红 - tenantId={}", TENANT_ID);
@@ -124,6 +132,7 @@ public class DealerOrderSettlement10584Task {
try {
transactionTemplate.executeWithoutResult(status -> {
// 先“认领”订单:并发/多实例下避免重复结算update=0 表示被其他线程/实例处理)
//更新商品订单为已结算状态
if (!claimOrderToSettle(order.getOrderId(), waterFormIds)) {
return;
}
@@ -138,6 +147,88 @@ public class DealerOrderSettlement10584Task {
}
}
/**
* 每10分钟执行一次。
*/
@Scheduled(cron = "0 0/10 * * * ?")
@IgnoreTenant("该定时任务仅处理租户10584但需要显式按tenantId过滤避免定时任务线程无租户上下文导致查询异常")
public void settleTenant10584OrdersV2() {
try {
//获取水票模板对应的商品信息列表
Set<Integer> waterFormIds = loadWaterFormIds();
//查询商品列表存在已支付未核销订单数据【isSettled = 0 payStatus= 1 orderStatus 不在列表2, 3, 4, 5, 6, 7
List<ShopOrder> orders = findUnsettledPaidOrders(waterFormIds);
if (orders.isEmpty()) {
return;
}
// Per-run caches to reduce DB chatter across orders.
Map<Integer, Integer> level1ParentCache = new HashMap<>();
Map<Integer, Boolean> shopRoleCache = new HashMap<>();
//获取系统设置分销等级
DealerBasicSetting dealerBasicSetting = findDealerBasicSetting();
//获取分销员type=2第一个分销人作为平台总分红人
ShopDealerUser totalDealerUser = findTotalDealerUser();
if (totalDealerUser == null || totalDealerUser.getUserId() == null) {
log.warn("未找到分红账号,订单仍可结算但不会发放分红 - tenantId={}", TENANT_ID);
}
log.debug("租户{}分销设置 - level={}", TENANT_ID, dealerBasicSetting.level);
log.info("租户{}待结算订单数: {}, orderNos(sample)={}",
TENANT_ID,
orders.size(),
orders.stream().limit(10).map(ShopOrder::getOrderNo).toList());
for (ShopOrder order : orders) {
try {
transactionTemplate.executeWithoutResult(status -> {
// 先“认领”订单:并发/多实例下避免重复结算update=0 表示被其他线程/实例处理)
//更新商品订单为已结算状态
if (!claimOrderToSettle(order.getOrderId(), waterFormIds)) {
return;
}
settleOneOrderV2(order, level1ParentCache, shopRoleCache, totalDealerUser, dealerBasicSetting.level);
});
} catch (Exception e) {
log.error("订单结算失败,将回滚本订单并在下次任务重试 - orderId={}, orderNo={}", order.getOrderId(), order.getOrderNo(), e);
}
}
} catch (Exception e) {
log.error("租户{}分销订单结算任务执行失败", TENANT_ID, e);
}
}
/**
* 订单分销金、分润金结算
*/
@Transactional
public void orderSettlement(String orderNo){
LambdaQueryWrapper<ShopOrder> orderLambdaQueryWrapper = new LambdaQueryWrapper<ShopOrder>().eq(ShopOrder::getOrderNo, orderNo).eq(ShopOrder::getIsSettled, 0).eq(ShopOrder::getPayStatus, 1);
ShopOrder order = shopOrderService.getOne(orderLambdaQueryWrapper);
if(order != null){
//获取系统设置分销等级
DealerBasicSetting dealerBasicSetting = findDealerBasicSetting();
//获取分销员type=2第一个分销人作为平台总分红人
ShopDealerUser totalDealerUser = findTotalDealerUser();
Map<Integer, Integer> level1ParentCache = new HashMap<>();
Map<Integer, Boolean> shopRoleCache = new HashMap<>();
transactionTemplate.executeWithoutResult(status -> {
// 先“认领”订单:并发/多实例下避免重复结算update=0 表示被其他线程/实例处理)
//更新商品订单为已结算状态
if (!claimOrderToSettleV2(order.getOrderId())) {
return;
}
settleOneOrderV2(order, level1ParentCache, shopRoleCache, totalDealerUser, dealerBasicSetting.level);
});
}
}
private List<ShopOrder> findUnsettledPaidOrders(Set<Integer> waterFormIds) {
// 租户10584约定
// - 普通订单以发货为准deliveryStatus=20才结算
@@ -148,7 +239,7 @@ public class DealerOrderSettlement10584Task {
.eq(ShopOrder::getPayStatus, true)
.eq(ShopOrder::getIsSettled, 0)
// 退款/取消订单不结算,避免“退款后仍发放分红/分润/佣金”
.and(w -> w.notIn(ShopOrder::getOrderStatus, 2, 4, 5, 6, 7).or().isNull(ShopOrder::getOrderStatus));
.and(w -> w.notIn(ShopOrder::getOrderStatus, 2, 3, 4, 5, 6, 7).or().isNull(ShopOrder::getOrderStatus));
if (waterFormIds != null && !waterFormIds.isEmpty()) {
qw.and(w -> w.eq(ShopOrder::getDeliveryStatus, 20).or().in(ShopOrder::getFormId, waterFormIds));
@@ -178,6 +269,17 @@ public class DealerOrderSettlement10584Task {
return shopOrderService.update(uw);
}
private boolean claimOrderToSettleV2(Integer orderId) {
LambdaUpdateWrapper<ShopOrder> uw = new LambdaUpdateWrapper<ShopOrder>()
.eq(ShopOrder::getOrderId, orderId)
.eq(ShopOrder::getTenantId, TENANT_ID)
.eq(ShopOrder::getIsSettled, 0)
// 二次防御:退款/取消订单不允许被“认领结算”
.and(w -> w.notIn(ShopOrder::getOrderStatus, 2, 3, 4, 5, 6, 7).or().isNull(ShopOrder::getOrderStatus));
uw.set(ShopOrder::getIsSettled, 1);
return shopOrderService.update(uw);
}
private Set<Integer> loadWaterFormIds() {
try {
return gltTicketTemplateService.list(
@@ -252,6 +354,45 @@ public class DealerOrderSettlement10584Task {
log.info("订单结算完成 - orderId={}, orderNo={}, baseAmount={}", order.getOrderId(), order.getOrderNo(), baseAmount);
}
private void settleOneOrderV2(ShopOrder order, Map<Integer, Integer> level1ParentCache, Map<Integer, Boolean> shopRoleCache,
ShopDealerUser totalDealerUser, int dealerLevel) {
if (order.getUserId() == null || order.getOrderNo() == null) {
throw new IllegalStateException("订单关键信息缺失,无法结算 - orderId=" + order.getOrderId());
}
BigDecimal totalPrice = order.getTotalPrice();
BigDecimal payPrice = order.getPayPrice();
BigDecimal rate = payPrice.divide(totalPrice, 2, RoundingMode.HALF_UP);
if(payPrice.compareTo(BigDecimal.ZERO) <= 0){
log.info("订单号:{}实付金额为0无需执行分销逻辑" + order.getOrderNo());
return;
}
//查询订单号订单所有已开启分销的商品分润信息
List<ShopOrderGoodsVO> orderGoodsVOList = shopOrderMapper.getOrderGoodsInfo(order.getOrderNo());
if(CollectionUtils.isNotEmpty(orderGoodsVOList)){
// 1) 直推/间推(直接增加冻结账户余额)
DealerRefereeCommissionV2 dealerRefereeCommission = settleDealerRefereeCommissionV2(order, rate, orderGoodsVOList, dealerLevel);
// 2) 门店分润上级:从下单用户开始逐级向上找,命中 ShopDealerUser.type=1 的最近两级(直推门店/间推门店)【只统计数据,不对分销账户进行处理,
// 已日结形式统计分销记录表shop_dealer_order 做对应一级二级管理津贴结算】
ShopRoleCommission shopRoleCommission = settleShopRoleRefereeCommissionV2(order, rate, orderGoodsVOList, level1ParentCache, shopRoleCache);
// 3) 分红:固定比率,每个订单都分 TODO 总分红未开发,还按原逻辑走
int goodsQty = orderGoodsVOList.stream().mapToInt(ShopOrderGoodsVO::getTotalNum).sum();
TotalDealerCommission totalDealerCommission = settleTotalDealerCommissionV2(order, goodsQty, totalDealerUser);
// 4) 写入分销订单记录(用于排查/统计;详细分佣以 ShopDealerCapital 为准)
createDealerOrderRecordV2(order, dealerRefereeCommission, shopRoleCommission, totalDealerCommission);
log.info("订单结算完成 - orderId={}, orderNo={}, baseAmount={}", order.getOrderId(), order.getOrderNo(), payPrice);
}else {
log.error("订单号:{},未找到下单分销商品数据!", order.getOrderNo());
return;
}
}
private DealerRefereeCommission settleDealerRefereeCommission(
ShopOrder order,
BigDecimal baseAmount,
@@ -329,6 +470,85 @@ public class DealerOrderSettlement10584Task {
return new DealerRefereeCommission(directDealerId, directMoney, simpleDealerId, simpleMoney, thirdDealerId, thirdMoney);
}
/**
* 获取分销员分销霍金数据
* @param order
* @param orderGoodsVOList
* @param dealerLevel
* @return
*/
private DealerRefereeCommissionV2 settleDealerRefereeCommissionV2(ShopOrder order, BigDecimal rate, List<ShopOrderGoodsVO> orderGoodsVOList, int dealerLevel) {
Integer directDealerId = null;
Integer simpleDealerId = null;
AtomicReference<BigDecimal> directMoney = new AtomicReference<>(BigDecimal.ZERO);
AtomicReference<BigDecimal> simpleMoney = new AtomicReference<>(BigDecimal.ZERO);
if (dealerLevel >= 1) {
Integer dealerId = shopDealerRefereeMapper.getDealerIdByUserId(order.getUserId());
if(dealerId != null){
directDealerId = dealerId;
}
}
if (dealerLevel >= 2 && directDealerId != null) {
Integer dealerId = shopDealerRefereeMapper.getDealerIdByUserId(directDealerId);
if(dealerId != null){
simpleDealerId = dealerId;
}
}
if(directDealerId != null || simpleDealerId != null){
Integer finalDirectDealerId = directDealerId;
Integer finalSimpleDealerId = simpleDealerId;
orderGoodsVOList.forEach(orderGoodsVO -> {
//获取商品分润比例/金额
BigDecimal firstMoney = orderGoodsVO.getFirstMoney();
BigDecimal secondMoney = orderGoodsVO.getSecondMoney();
//按实付比例计算单项应参与分润金额
BigDecimal itemRatePrice = orderGoodsVO.getPrice().multiply(BigDecimal.valueOf(orderGoodsVO.getTotalNum())).multiply(rate);
//一级分销员存在(type = 0)且单项实付金额大于0及商品设置了一级分销比例/金额
if(finalDirectDealerId != null && itemRatePrice.compareTo(BigDecimal.ZERO) > 0 && firstMoney.compareTo(BigDecimal.ZERO) > 0){
BigDecimal one = calcMoneyByCommissionType(itemRatePrice, firstMoney, orderGoodsVO.getTotalNum(), 2, orderGoodsVO.getCommissionType());
directMoney.accumulateAndGet(one, BigDecimal::add);
}
//一级分销员存在(type = 0)且单项实付金额大于0及商品设置了一级分销比例/金额
if(finalSimpleDealerId != null && itemRatePrice.compareTo(BigDecimal.ZERO) > 0 && secondMoney.compareTo(BigDecimal.ZERO) > 0 ){
BigDecimal two = calcMoneyByCommissionType(itemRatePrice, secondMoney, orderGoodsVO.getTotalNum(), 2, orderGoodsVO.getCommissionType());
simpleMoney.accumulateAndGet(two, BigDecimal::add);
}
});
//一级分销员账户增加冻结金额
if (directDealerId != null && directMoney.get().compareTo(BigDecimal.ZERO) > 0) {
ShopDealerUserReduceDto reduceDto = new ShopDealerUserReduceDto();
reduceDto.setTypeEnum(ShopDealerTypeEnum.FREEZE_ACCOUNT);
reduceDto.setUserId(directDealerId);
reduceDto.setOrderUserId(order.getUserId());
reduceDto.setOrderNo(order.getOrderNo());
reduceDto.setPrice(directMoney.get());
reduceDto.setUpdateEnum(ShopDealerCapitalUpdateEnum.DISTRIBUTION_INCOME);
shopDealerUserService.reduceBalance(reduceDto);
}
//二级分销员账户增加冻结金额
if (simpleDealerId != null && simpleMoney.get().compareTo(BigDecimal.ZERO) > 0) {
ShopDealerUserReduceDto reduceDto = new ShopDealerUserReduceDto();
reduceDto.setTypeEnum(ShopDealerTypeEnum.FREEZE_ACCOUNT);
reduceDto.setUserId(simpleDealerId);
reduceDto.setOrderUserId(order.getUserId());
reduceDto.setOrderNo(order.getOrderNo());
reduceDto.setPrice(simpleMoney.get());
reduceDto.setUpdateEnum(ShopDealerCapitalUpdateEnum.DISTRIBUTION_INCOME);
shopDealerUserService.reduceBalance(reduceDto);
}
return new DealerRefereeCommissionV2(directDealerId, directMoney.get(), simpleDealerId, simpleMoney.get());
}
return null;
}
private Integer getDealerRefereeId(Integer userId) {
return getDealerRefereeId(userId, 1);
}
@@ -412,6 +632,54 @@ public class DealerOrderSettlement10584Task {
return new ShopRoleCommission(shopRoleReferees.get(0), storeDirectMoney, shopRoleReferees.get(1), storeSimpleMoney);
}
private ShopRoleCommission settleShopRoleRefereeCommissionV2(ShopOrder order, BigDecimal rate, List<ShopOrderGoodsVO> orderGoodsVOList, Map<Integer, Integer> level1ParentCache, Map<Integer, Boolean> shopRoleCache) {
List<Integer> shopRoleReferees = findFirstTwoShopRoleReferees(order.getUserId(), level1ParentCache, shopRoleCache);
log.info("门店分润命中结果(type=1门店角色取前两级) - orderNo={}, buyerUserId={}, shopRoleReferees={}",
order.getOrderNo(), order.getUserId(), shopRoleReferees);
if (shopRoleReferees.isEmpty()) {
return ShopRoleCommission.empty();
}
if(CollectionUtils.isNotEmpty(shopRoleReferees)){
Integer storeDirectUserId;
Integer storeSimpleUserId = null;
AtomicReference<BigDecimal> storeDirectMoney = new AtomicReference<>(BigDecimal.ZERO);
AtomicReference<BigDecimal> storeSimpleMoney = new AtomicReference<>(BigDecimal.ZERO);
if(shopRoleReferees.size() == 1){
storeDirectUserId = shopRoleReferees.get(0);
}else {
storeDirectUserId = shopRoleReferees.get(0);
storeSimpleUserId = shopRoleReferees.get(1);
}
Integer finalStoreDirectUserId = storeDirectUserId;
Integer finalStoreSimpleUserId = storeSimpleUserId;
orderGoodsVOList.forEach(orderGoodsVO ->{
//获取商品对应服务商管理费分润比例/金额
BigDecimal firstMoney = orderGoodsVO.getFirstDividend();
BigDecimal secondMoney = orderGoodsVO.getSecondDividend();
//按实付比例计算单项应参与分润金额
BigDecimal itemRatePrice = orderGoodsVO.getPrice().multiply(BigDecimal.valueOf(orderGoodsVO.getTotalNum())).multiply(rate);
if(finalStoreDirectUserId != null && itemRatePrice.compareTo(BigDecimal.ZERO) > 0){
BigDecimal one = calcMoneyByCommissionType(itemRatePrice, firstMoney, orderGoodsVO.getTotalNum(), 2, orderGoodsVO.getCommissionType());
storeDirectMoney.accumulateAndGet(one, BigDecimal::add);
}
if(finalStoreSimpleUserId != null && itemRatePrice.compareTo(BigDecimal.ZERO) > 0){
BigDecimal two = calcMoneyByCommissionType(itemRatePrice, secondMoney, orderGoodsVO.getTotalNum(), 2, orderGoodsVO.getCommissionType());
storeSimpleMoney.accumulateAndGet(two, BigDecimal::add);
}
});
return new ShopRoleCommission(storeDirectUserId, storeDirectMoney.get(), storeSimpleUserId, storeSimpleMoney.get());
}else {
return null;
}
}
private TotalDealerCommission settleTotalDealerCommission(
ShopOrder order,
BigDecimal baseAmount,
@@ -438,6 +706,32 @@ public class DealerOrderSettlement10584Task {
return new TotalDealerCommission(totalDealerUser.getUserId(), money);
}
private TotalDealerCommission settleTotalDealerCommissionV2(ShopOrder order, int goodsQty, ShopDealerUser totalDealerUser) {
if (totalDealerUser == null || totalDealerUser.getUserId() == null) {
return TotalDealerCommission.empty();
}
BigDecimal rate = safePositive(totalDealerUser.getRate());
if (rate.signum() <= 0) {
rate = TOTAL_DEALER_DIVIDEND_RATE;
}
BigDecimal money = calcMoneyByCommissionType(order.getPayPrice(), rate, goodsQty, DIVIDEND_SCALE, 20);
//一级分销员账户增加冻结金额
if (money.compareTo(BigDecimal.ZERO) > 0) {
ShopDealerUserReduceDto reduceDto = new ShopDealerUserReduceDto();
reduceDto.setTypeEnum(ShopDealerTypeEnum.FREEZE_ACCOUNT);
reduceDto.setUserId(totalDealerUser.getUserId());
reduceDto.setOrderUserId(order.getUserId());
reduceDto.setOrderNo(order.getOrderNo());
reduceDto.setPrice(money);
reduceDto.setUpdateEnum(ShopDealerCapitalUpdateEnum.DIVIDEND_INCOME);
shopDealerUserService.reduceBalance(reduceDto);
return new TotalDealerCommission(totalDealerUser.getUserId(), money);
}
return TotalDealerCommission.empty();
}
private ShopDealerUser findTotalDealerUser() {
return shopDealerUserService.getOne(
new LambdaQueryWrapper<ShopDealerUser>()
@@ -756,6 +1050,92 @@ public class DealerOrderSettlement10584Task {
order.getOrderNo(), dealerOrder.getFirstUserId(), dealerOrder.getSecondUserId(), dealerOrder.getFirstDividendUser(), dealerOrder.getSecondDividendUser());
}
/**
* 记录订单分销业务
* @param order 商品订单
* @param dealerRefereeCommission 一级、二级分销员分销数据
* @param shopRoleCommission 门店/服务商一级、二级管理津贴数据
*/
private void createDealerOrderRecordV2(ShopOrder order, DealerRefereeCommissionV2 dealerRefereeCommission, ShopRoleCommission shopRoleCommission, TotalDealerCommission totalDealerCommission) {
// 幂等:同一订单只写一条(依赖 order_no + tenant_id 作为业务唯一)
ShopDealerOrder existed = shopDealerOrderService.getOne(
new LambdaQueryWrapper<ShopDealerOrder>()
.eq(ShopDealerOrder::getTenantId, TENANT_ID)
.eq(ShopDealerOrder::getOrderNo, order.getOrderNo())
.last("limit 1")
);
if (existed != null) {
// 允许“补发”门店分润时回填分润字段,避免订单已结算但分润字段一直为空,影响排查/对账。
LambdaUpdateWrapper<ShopDealerOrder> uw = new LambdaUpdateWrapper<ShopDealerOrder>()
.eq(ShopDealerOrder::getTenantId, TENANT_ID)
.eq(ShopDealerOrder::getOrderNo, order.getOrderNo());
boolean needUpdate = false;
if (shopRoleCommission != null && shopRoleCommission.storeDirectUserId != null) {
Integer existedUser = existed.getFirstDividendUser();
boolean needSetUser = existedUser == null;
boolean needSetMoney = existed.getFirstDividend() == null || existed.getFirstDividend().signum() == 0;
if (needSetUser) {
uw.set(ShopDealerOrder::getFirstDividendUser, shopRoleCommission.storeDirectUserId);
needUpdate = true;
}
boolean sameUser = existedUser == null || Objects.equals(existedUser, shopRoleCommission.storeDirectUserId);
if (sameUser && needSetMoney && shopRoleCommission.storeDirectMoney != null && shopRoleCommission.storeDirectMoney.signum() > 0) {
uw.set(ShopDealerOrder::getFirstDividend, shopRoleCommission.storeDirectMoney);
needUpdate = true;
}
}
if (shopRoleCommission != null && shopRoleCommission.storeSimpleUserId != null) {
Integer existedUser = existed.getSecondDividendUser();
boolean needSetUser = existedUser == null;
boolean needSetMoney = existed.getSecondDividend() == null || existed.getSecondDividend().signum() == 0;
if (needSetUser) {
uw.set(ShopDealerOrder::getSecondDividendUser, shopRoleCommission.storeSimpleUserId);
needUpdate = true;
}
boolean sameUser = existedUser == null || Objects.equals(existedUser, shopRoleCommission.storeSimpleUserId);
if (sameUser && needSetMoney && shopRoleCommission.storeSimpleMoney != null && shopRoleCommission.storeSimpleMoney.signum() > 0) {
uw.set(ShopDealerOrder::getSecondDividend, shopRoleCommission.storeSimpleMoney);
needUpdate = true;
}
}
if (needUpdate) {
shopDealerOrderService.update(uw);
log.info("ShopDealerOrder已存在回填门店分润字段 - orderNo={}, firstDividendUser={}, secondDividendUser={}",
order.getOrderNo(), shopRoleCommission.storeDirectUserId, shopRoleCommission.storeSimpleUserId);
} else {
log.info("ShopDealerOrder已存在跳过写入 - orderNo={}", order.getOrderNo());
}
return;
}else {
ShopDealerOrder dealerOrder = new ShopDealerOrder();
dealerOrder.setUserId(order.getUserId()); // 买家用户ID
dealerOrder.setOrderNo(order.getOrderNo());
dealerOrder.setOrderPrice(order.getTotalPrice());
dealerOrder.setPayPrice(order.getPayPrice());
//一级、二级分销员分销佣金统计
dealerOrder.setFirstUserId(dealerRefereeCommission != null ? dealerRefereeCommission.directDealerId : null);
dealerOrder.setFirstMoney(dealerRefereeCommission != null ? dealerRefereeCommission.directMoney : BigDecimal.ZERO);
dealerOrder.setSecondUserId(dealerRefereeCommission != null ? dealerRefereeCommission.simpleDealerId : null);
dealerOrder.setSecondMoney(dealerRefereeCommission != null ? dealerRefereeCommission.simpleMoney : BigDecimal.ZERO);
//门店(角色shop)两级分润单独落字段(详细以 ShopDealerCapital 为准)
dealerOrder.setFirstDividendUser(shopRoleCommission != null ? shopRoleCommission.storeDirectUserId : null);
dealerOrder.setFirstDividend(shopRoleCommission != null ? shopRoleCommission.storeDirectMoney : BigDecimal.ZERO);
dealerOrder.setSecondDividendUser(shopRoleCommission != null ? shopRoleCommission.storeSimpleUserId : null);
dealerOrder.setSecondDividend(shopRoleCommission != null ? shopRoleCommission.storeSimpleMoney : BigDecimal.ZERO);
dealerOrder.setIsSettled(1);
dealerOrder.setSettleTime(LocalDateTime.now());
dealerOrder.setMonth(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM")));
dealerOrder.setTenantId(TENANT_ID);
dealerOrder.setComments(buildCommissionTraceCommentV2(dealerRefereeCommission, shopRoleCommission, totalDealerCommission));
shopDealerOrderService.save(dealerOrder);
}
}
private String buildCommissionTraceComment(
DealerRefereeCommission dealerRefereeCommission,
ShopRoleCommission shopRoleCommission,
@@ -770,6 +1150,30 @@ public class DealerOrderSettlement10584Task {
+ ",totalDealer=" + totalDealerCommission.userId + ":" + totalDealerCommission.money;
}
private String buildCommissionTraceCommentV2(
DealerRefereeCommissionV2 dealerRefereeCommission,
ShopRoleCommission shopRoleCommission,
TotalDealerCommission totalDealerCommission
) {
// 轻量“过程”留痕,方便排查;详细分佣以 ShopDealerCapital 为准。
Integer direct = dealerRefereeCommission != null ? dealerRefereeCommission.directDealerId : null;
BigDecimal directMoney = dealerRefereeCommission != null ? dealerRefereeCommission.directMoney : BigDecimal.ZERO;
Integer simpleDealerId = dealerRefereeCommission != null ? dealerRefereeCommission.simpleDealerId : null;
BigDecimal simpleMoney = dealerRefereeCommission != null ? dealerRefereeCommission.simpleMoney : BigDecimal.ZERO;
Integer storeDirectUserId = shopRoleCommission != null ? shopRoleCommission.storeDirectUserId : null;
BigDecimal storeDirectMoney = shopRoleCommission != null ? shopRoleCommission.storeDirectMoney : BigDecimal.ZERO;
Integer storeSimpleUserId = shopRoleCommission != null ? shopRoleCommission.storeSimpleUserId : null;
BigDecimal storeSimpleMoney = shopRoleCommission != null ? shopRoleCommission.storeSimpleMoney : BigDecimal.ZERO;
Integer userId = totalDealerCommission != null ? totalDealerCommission.userId : null;
BigDecimal money = totalDealerCommission != null ? totalDealerCommission.money : BigDecimal.ZERO;
return "direct=" + direct + ":" + directMoney
+ ",simple=" + simpleDealerId + ":" + simpleMoney
+ ",dividend1=" + storeDirectUserId + ":" + storeDirectMoney
+ ",dividend2=" + storeSimpleUserId + ":" + storeSimpleMoney
+ ",totalDealer=" + userId + ":" + money;
}
private BigDecimal getOrderBaseAmount(ShopOrder order) {
if (order == null) {
return null;
@@ -962,6 +1366,25 @@ public class DealerOrderSettlement10584Task {
}
}
private static class DealerRefereeCommissionV2 {
private final Integer directDealerId;
private final BigDecimal directMoney;
private final Integer simpleDealerId;
private final BigDecimal simpleMoney;
private DealerRefereeCommissionV2(
Integer directDealerId,
BigDecimal directMoney,
Integer simpleDealerId,
BigDecimal simpleMoney
) {
this.directDealerId = directDealerId;
this.directMoney = directMoney != null ? directMoney : BigDecimal.ZERO;
this.simpleDealerId = simpleDealerId;
this.simpleMoney = simpleMoney != null ? simpleMoney : BigDecimal.ZERO;
}
}
private static class ShopRoleCommission {
private final Integer storeDirectUserId;
private final BigDecimal storeDirectMoney;

View File

@@ -7,6 +7,8 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serializable;
@@ -23,6 +25,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
@Schema(name = "ShopDealerOrder对象", description = "分销商订单记录表")
@TableName("shop_dealer_order")
public class ShopDealerOrder implements Serializable {
private static final long serialVersionUID = 1L;
@@ -66,6 +69,9 @@ public class ShopDealerOrder implements Serializable {
@TableField(exist = false)
private String firstNickname;
@Schema(description = "分销佣金(一级)")
private BigDecimal firstMoney;
@Schema(description = "分销商用户id(二级)")
private Integer secondUserId;
@@ -73,42 +79,57 @@ public class ShopDealerOrder implements Serializable {
@TableField(exist = false)
private String secondNickname;
@Schema(description = "分销商用户id(三级)")
private Integer thirdUserId;
@Schema(description = "分销商用户昵称(三级)")
@TableField(exist = false)
private String thirdNickname;
@Schema(description = "分销佣金(一级)")
private BigDecimal firstMoney;
@Schema(description = "分销佣金(二级)")
private BigDecimal secondMoney;
@Schema(description = "分销佣金(三级)")
@Schema(description = "分销商用户id(弃用)")
private Integer thirdUserId;
@Schema(description = "分销商用户昵称(弃用)")
@TableField(exist = false)
private String thirdNickname;
@Schema(description = "分销佣金(弃用)")
private BigDecimal thirdMoney;
@Schema(description = "门店(一级)")
@Schema(description = "一级服务商/门店")
private Integer firstDividendUser;
@Schema(description = "门店名称(一级)")
@Schema(description = "一级服务商/门店名称")
@TableField(exist = false)
private String firstDividendUserName;
@Schema(description = "分红(一级)")
@Schema(description = "一级服务商/门店管理津贴")
private BigDecimal firstDividend;
@Schema(description = "门店(二级)")
@Schema(description = "一级服务商/门店结算标识 0-否 1-是")
private Integer firstDividendFlag;
@Schema(description = "一级服务商/门店结算单号")
private String firstDividendNo;
@Schema(description = "一级服务商/门店结算时间")
private LocalDateTime firstDividendTime;
@Schema(description = "二级服务商/门店")
private Integer secondDividendUser;
@Schema(description = "门店名称(二级)")
@Schema(description = "二级服务商/门店名称")
@TableField(exist = false)
private String secondDividendUserName;
@Schema(description = "分红(二级)")
@Schema(description = "二级服务商/门店管理津贴")
private BigDecimal secondDividend;
@Schema(description = "二级服务商/门店结算标识 0-否 1-是")
private Integer secondDividendFlag;
@Schema(description = "二级服务商/门店结算单号")
private String secondDividendNo;
@Schema(description = "二级服务商/门店结算时间")
private LocalDateTime secondDividendTime;
@Schema(description = "佣金比例")
private BigDecimal rate;

View File

@@ -34,4 +34,11 @@ public interface ShopDealerRefereeMapper extends BaseMapper<ShopDealerReferee> {
*/
List<ShopDealerReferee> selectListRel(@Param("param") ShopDealerRefereeParam param);
/**
* 通过用户ID查询上一级分销人信息【type = 0】
* @param userId 用户ID
* @return
*/
Integer getDealerIdByUserId(@Param("userId") Integer userId);
}

View File

@@ -5,9 +5,9 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.shop.entity.ShopOrder;
import com.gxwebsoft.shop.param.ShopOrderParam;
import com.gxwebsoft.shop.vo.ShopOrderGoodsVO;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.List;
@@ -60,4 +60,11 @@ public interface ShopOrderMapper extends BaseMapper<ShopOrder> {
Map<String, Object> selectUserOrderStats(@Param("userId") Integer userId,
@Param("tenantId") Integer tenantId,
@Param("type") Integer type);
/**
* 通过订单号查询订单所有商品分润信息
* @param orderNo 订单号
* @return
*/
List<ShopOrderGoodsVO> getOrderGoodsInfo(@Param("orderNo") String orderNo);
}

View File

@@ -57,5 +57,16 @@
<select id="selectListRel" resultType="com.gxwebsoft.shop.entity.ShopDealerReferee">
<include refid="selectSql"></include>
</select>
<select id="getDealerIdByUserId" resultType="java.lang.Integer">
SELECT
a.dealer_id
FROM
shop_dealer_referee a
LEFT JOIN shop_dealer_user b ON a.dealer_id = b.user_id
WHERE
b.is_delete = 0
AND b.type = 0
AND a.user_id = #{userId}
</select>
</mapper>

View File

@@ -320,6 +320,28 @@
<select id="getByOutTradeNo" resultType="com.gxwebsoft.shop.entity.ShopOrder">
SELECT * FROM shop_order WHERE order_no = #{outTradeNo}
</select>
<select id="getOrderGoodsInfo" resultType="com.gxwebsoft.shop.vo.ShopOrderGoodsVO">
SELECT
a.order_no,
b.goods_id,
c.`name`,
b.total_num,
b.price,
c.first_money,
c.second_money,
c.first_dividend,
c.second_dividend,
c.commission_type
FROM
shop_order a
LEFT JOIN shop_order_goods b ON a.order_id = b.order_id
LEFT JOIN shop_goods c ON b.goods_id = c.goods_id
WHERE
a.deleted = 0
AND c.deleted = 0
AND c.is_open_commission = 1
AND a.order_no = #{orderNo}
</select>
<!-- 根据订单号修改订单 -->
<update id="updateByOutTradeNo" parameterType="com.gxwebsoft.cms.entity.CmsWebsite">

View File

@@ -0,0 +1,47 @@
package com.gxwebsoft.shop.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
/**
* 订单商品信息
* @author xm
* @since 2025-01-12
*/
@Data
@Schema(description = "订单商品信息")
public class ShopOrderGoodsVO {
@Schema(description = "订单号")
private String orderNo;
@Schema(description = "商品ID")
private Integer goodsId;
@Schema(description = "品名")
private String name;
@Schema(description = "购买数量")
private Integer totalNum;
@Schema(description = "单价")
private BigDecimal price;
@Schema(description = "结算方式 10按金额 20按比率")
private Integer commissionType;
@Schema(description = "一级分销员佣金比例/金额")
private BigDecimal firstMoney;
@Schema(description = "二级分销员佣金比例/金额")
private BigDecimal secondMoney;
@Schema(description = "一级服务商管理津贴比例/金额")
private BigDecimal firstDividend;
@Schema(description = "二级服务商管理津贴比例/金额")
private BigDecimal secondDividend;
}