@@ -3,13 +3,12 @@ package com.gxwebsoft.shop.task;
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.common.system.entity.Role ;
import com.gxwebsoft.common.system.service.UserRoleService ;
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.ShopOrder ;
import com.gxwebsoft.shop.mapper.ShopDealerRefereeMapper ;
import com.gxwebsoft.shop.service.ShopDealerCapitalService ;
import com.gxwebsoft.shop.service.ShopDealerOrderService ;
import com.gxwebsoft.shop.service.ShopDealerRefereeService ;
@@ -65,12 +64,12 @@ public class DealerOrderSettlement10584Task {
private ShopDealerOrderService shopDealerOrderService ;
@Resource
private UserRoleService userRoleService ;
private ShopDealerRefereeMapper shopDealerRefereeMapper ;
/**
* 每2 0秒执行一次。
* 每3 0秒执行一次。
*/
@Scheduled ( cron = " 0/2 0 * * * * ? " )
@Scheduled ( cron = " 0/3 0 * * * * ? " )
@IgnoreTenant ( " 该定时任务仅处理租户10584, 但需要显式按tenantId过滤, 避免定时任务线程无租户上下文导致查询异常 " )
public void settleTenant10584Orders ( ) {
try {
@@ -84,9 +83,6 @@ public class DealerOrderSettlement10584Task {
orders . size ( ) ,
orders . stream ( ) . limit ( 10 ) . map ( ShopOrder : : getOrderNo ) . toList ( ) ) ;
// 缓存:减少同一批次内重复查角色
Map < Integer , Boolean > shopRoleCache = new HashMap < > ( ) ;
for ( ShopOrder order : orders ) {
try {
transactionTemplate . executeWithoutResult ( status - > {
@@ -94,7 +90,7 @@ public class DealerOrderSettlement10584Task {
if ( ! claimOrderToSettle ( order . getOrderId ( ) ) ) {
return ;
}
settleOneOrder ( order , shopRoleCache );
settleOneOrder ( order ) ;
} ) ;
} catch ( Exception e ) {
log . error ( " 订单结算失败,将回滚本订单并在下次任务重试 - orderId={}, orderNo={} " , order . getOrderId ( ) , order . getOrderNo ( ) , e ) ;
@@ -127,7 +123,7 @@ public class DealerOrderSettlement10584Task {
) ;
}
private void settleOneOrder ( ShopOrder order , Map < Integer , Boolean > shopRoleCache ) {
private void settleOneOrder ( ShopOrder order ) {
if ( order . getUserId ( ) = = null | | order . getOrderNo ( ) = = null ) {
throw new IllegalStateException ( " 订单关键信息缺失,无法结算 - orderId= " + order . getOrderId ( ) ) ;
}
@@ -144,7 +140,7 @@ public class DealerOrderSettlement10584Task {
DealerRefereeCommission dealerRefereeCommission = settleDealerRefereeCommission ( order , baseAmount ) ;
// 2) 角色为shop的推荐人( shop_dealer_referee 链路向上查找 role=shop 的上级)
ShopRoleCommission shopRoleCommission = settleShopRoleRefereeCommission ( order , baseAmount , shopRoleCache );
ShopRoleCommission shopRoleCommission = settleShopRoleRefereeCommission ( order , baseAmount ) ;
// 3) 写入分销订单记录(用于排查/统计;详细分佣以 ShopDealerCapital 为准)
createDealerOrderRecord ( order , baseAmount , dealerRefereeCommission , shopRoleCommission ) ;
@@ -153,9 +149,14 @@ public class DealerOrderSettlement10584Task {
}
private DealerRefereeCommission settleDealerRefereeCommission ( ShopOrder order , BigDecimal baseAmount ) {
// level=2 可能不可靠:按“查两次”方式获取简推(直推的上级)。
Integer directDealerId = getDealerRefereeId ( order . getUserId ( ) ) ;
Integer simpleDealerId = directDealerId ! = null ? getDealerRefereeId ( directDealerId ) : null ;
// 兼容两种数据形态:
// 1) 同一 userId 下有 level=1/2 的多级关系(直接按 level 取);
// 2) 仅维护 level=1( 用“查两次”回退获取上级) 。
Integer directDealerId = getDealerRefereeId ( order . getUserId ( ) , 1 ) ;
Integer simpleDealerId = getDealerRefereeId ( order . getUserId ( ) , 2 ) ;
if ( simpleDealerId = = null & & directDealerId ! = null ) {
simpleDealerId = getDealerRefereeId ( directDealerId , 1 ) ;
}
BigDecimal directMoney = directDealerId ! = null ? calcMoney ( baseAmount , RATE_0_10 ) : BigDecimal . ZERO ;
// 允许同一条线内同一个人同时拿到“直推 + 简推”(即使 directDealerId == simpleDealerId 也照常发放两笔)
@@ -164,13 +165,18 @@ public class DealerOrderSettlement10584Task {
log . info ( " 分销直推/简推查询结果 - orderNo={}, buyerUserId={}, directDealerId={}, directMoney={}, simpleDealerId={}, simpleMoney={} " ,
order . getOrderNo ( ) , order . getUserId ( ) , directDealerId , directMoney , simpleDealerId , simpleMoney ) ;
creditDealerCommission ( directDealerId , directMoney , order , " 直推佣金(10%) " ) ;
creditDealerCommission ( simple DealerId, simple Money, order , " 简 推佣金(5 %)" ) ;
// 直推:对方=买家;推荐奖(5%):对方=直推分销商(便于在资金明细中看出“来自哪个下级分销商/团队订单”)
creditDealerCommission ( direct DealerId, direct Money, order , order . getUserId ( ) , " 直 推佣金(10 %)" ) ;
creditDealerCommission ( simpleDealerId , simpleMoney , order , directDealerId , " 推荐奖(5%) " ) ;
return new DealerRefereeCommission ( directDealerId , directMoney , simpleDealerId , simpleMoney ) ;
}
private Integer getDealerRefereeId ( Integer userId ) {
return getDealerRefereeId ( userId , 1 ) ;
}
private Integer getDealerRefereeId ( Integer userId , int level ) {
if ( userId = = null ) {
return null ;
}
@@ -178,17 +184,17 @@ public class DealerOrderSettlement10584Task {
new LambdaQueryWrapper < ShopDealerReferee > ( )
. eq ( ShopDealerReferee : : getTenantId , TENANT_ID )
. eq ( ShopDealerReferee : : getUserId , userId )
. eq ( ShopDealerReferee : : getLevel , 1 )
. eq ( ShopDealerReferee : : getLevel , level )
. orderByDesc ( ShopDealerReferee : : getId )
. last ( " limit 1 " )
) ;
log . debug ( " shop_dealer_referee(level=1 ) 查询 - tenantId={}, userId={}, dealerId={} " ,
TENANT_ID , userId , rel ! = null ? rel . getDealerId ( ) : null ) ;
log . debug ( " shop_dealer_referee(level={} ) 查询 - tenantId={}, userId={}, dealerId={} " ,
level , TENANT_ID, userId , rel ! = null ? rel . getDealerId ( ) : null ) ;
return rel ! = null ? rel . getDealerId ( ) : null ;
}
private ShopRoleCommission settleShopRoleRefereeCommission ( ShopOrder order , BigDecimal baseAmount , Map < Integer , Boolean > shopRoleCache ) {
List < Integer > shopRoleReferees = findFirstTwoShopRoleRefereesByDealerReferee ( order . getUserId ( ) , shopRoleCache );
private ShopRoleCommission settleShopRoleRefereeCommission ( ShopOrder order , BigDecimal baseAmount ) {
List < Integer > shopRoleReferees = findFirstTwoShopRoleRefereesByDealerReferee ( order . getUserId ( ) ) ;
log . info ( " 门店(角色shop)上级链路结果 - orderNo={}, buyerUserId={}, shopRoleReferees={} " ,
order . getOrderNo ( ) , order . getUserId ( ) , shopRoleReferees ) ;
if ( shopRoleReferees . isEmpty ( ) ) {
@@ -196,23 +202,24 @@ public class DealerOrderSettlement10584Task {
}
if ( shopRoleReferees . size ( ) = = 1 ) {
BigDecimal money = calcMoney ( baseAmount , RATE_0_10 ) ;
// 门店直推: 2%
BigDecimal money = calcMoney ( baseAmount , RATE_0_02 ) ;
log . info ( " 门店直推/简推发放(仅1人) - orderNo={}, storeDirectUserId={}, money={} " , order . getOrderNo ( ) , shopRoleReferees . get ( 0 ) , money ) ;
creditDealerCommission ( shopRoleReferees . get ( 0 ) , money , order , " 门店直推佣金(角色shop, 仅1人, 10 %) " ) ;
creditDealerCommission ( shopRoleReferees . get ( 0 ) , money , order , order . getUserId ( ) , " 门店直推佣金(角色shop, 仅1人, 2 %) " ) ;
return new ShopRoleCommission ( shopRoleReferees . get ( 0 ) , money , null , BigDecimal . ZERO ) ;
}
// 两个或以上: 第一个0.02, 第二个0.08
// 两个或以上: 第一个0.02, 第二个0.01
BigDecimal storeDirectMoney = calcMoney ( baseAmount , RATE_0_02 ) ;
BigDecimal storeSimpleMoney = calcMoney ( baseAmount , RATE_0_01 ) ;
log . info ( " 门店直推/门店简推发放 - orderNo={}, storeDirectUserId={}, storeDirectMoney={}, storeSimpleUserId={}, storeSimpleMoney={} " ,
order . getOrderNo ( ) , shopRoleReferees . get ( 0 ) , storeDirectMoney , shopRoleReferees . get ( 1 ) , storeSimpleMoney ) ;
creditDealerCommission ( shopRoleReferees . get ( 0 ) , storeDirectMoney , order , " 门店直推佣金(角色shop, 第1个, 2%) " ) ;
creditDealerCommission ( shopRoleReferees . get ( 1 ) , storeSimpleMoney , order , " 门店简推佣金(角色shop, 第2个, 1%) " ) ;
creditDealerCommission ( shopRoleReferees . get ( 0 ) , storeDirectMoney , order , order . getUserId ( ) , " 门店直推佣金(角色shop, 第1个, 2%) " ) ;
creditDealerCommission ( shopRoleReferees . get ( 1 ) , storeSimpleMoney , order , order . getUserId ( ) , " 门店简推佣金(角色shop, 第2个, 1%) " ) ;
return new ShopRoleCommission ( shopRoleReferees . get ( 0 ) , storeDirectMoney , shopRoleReferees . get ( 1 ) , storeSimpleMoney ) ;
}
private List < Integer > findFirstTwoShopRoleRefereesByDealerReferee ( Integer buyerUserId , Map < Integer , Boolean > shopRoleCache ) {
private List < Integer > findFirstTwoShopRoleRefereesByDealerReferee ( Integer buyerUserId ) {
if ( buyerUserId = = null ) {
return Collections . emptyList ( ) ;
}
@@ -220,9 +227,31 @@ public class DealerOrderSettlement10584Task {
List < Integer > result = new ArrayList < > ( 2 ) ;
Set < Integer > visited = new HashSet < > ( ) ;
// 优先:直接从该买家(userId)的多级关系(level=1/2/3/...)里,按 level 升序找到最近的两级门店(角色shop)。
// 背景:部分数据只维护“买家 -> 上级们”的多级(level)记录,但不一定维护“上级 -> 更上级”的记录;
// 若仅做链路向上查找,会导致门店直推/简推无法命中,收益与资金明细都不会写入。
// 注意: isShopRole 为扩展字段(非表字段), 需用自定义SQL带出。
List < ShopDealerReferee > relList = shopDealerRefereeMapper . selectRefereeChainWithShopRole (
TENANT_ID , buyerUserId , MAX_REFEREE_CHAIN_DEPTH
) ;
for ( ShopDealerReferee rel : relList ) {
Integer dealerId = rel ! = null ? rel . getDealerId ( ) : null ;
if ( dealerId = = null | | ! visited . add ( dealerId ) ) {
continue ;
}
if ( Boolean . TRUE . equals ( rel . getIsShopRole ( ) ) ) {
result . add ( dealerId ) ;
if ( result . size ( ) > = 2 ) {
return result ;
}
}
}
// 兜底:若只维护了 level=1 的推荐关系,则尝试按“查两次/多次”的方式一路向上找门店角色。
Integer current = buyerUserId ;
for ( int i = 0 ; i < MAX_REFEREE_CHAIN_DEPTH & & current ! = null ; i + + ) {
Integer parentId = get DealerRefereeId ( current ) ;
ShopDealerReferee parentRel = shop DealerRefereeMapper . selectFirstLevelRefereeWithShopRole ( TENANT_ID , current ) ;
Integer parentId = parentRel ! = null ? parentRel . getDealerId ( ) : null ;
if ( parentId = = null ) {
break ;
}
@@ -233,7 +262,7 @@ public class DealerOrderSettlement10584Task {
break ;
}
if ( hasShopRole ( parentId , s hopRoleCache ) ) {
if ( parentRel ! = null & & Boolean . TRUE . equals ( parentRel . getIsS hopRole( ) ) ) {
log . debug ( " 门店(角色shop)命中 - parentId={} " , parentId ) ;
result . add ( parentId ) ;
if ( result . size ( ) > = 2 ) {
@@ -247,23 +276,7 @@ public class DealerOrderSettlement10584Task {
return result ;
}
private boolean hasShopRole ( Integer u serId, Map < Integer , Boolean > shopRoleCache ) {
Boolean cached = shopRoleCache . get ( userId ) ;
if ( cached ! = null ) {
return cached ;
}
List < Role > roles = userRoleService . listByUserId ( userId ) ;
boolean isShop = roles ! = null & & roles . stream ( ) . anyMatch ( r - > " shop " . equalsIgnoreCase ( r . getRoleCode ( ) ) ) ;
log . debug ( " 角色判定 - userId={}, roles={}, isShop={} " ,
userId ,
roles = = null ? null : roles . stream ( ) . map ( Role : : getRoleCode ) . toList ( ) ,
isShop ) ;
shopRoleCache . put ( userId , isShop ) ;
return isShop ;
}
private void creditDealerCommission ( Integer dealerUserId , BigDecimal money , ShopOrder order , String comments ) {
private void creditDealerCommission ( Integer dealerU serId, BigDecimal money , ShopOrder order , Integer toUserId , String comments ) {
if ( dealerUserId = = null | | money = = null | | money . signum ( ) < = 0 ) {
return ;
}
@@ -280,8 +293,47 @@ public class DealerOrderSettlement10584Task {
) ;
if ( ! updated ) {
log . warn ( " 发放佣金失败:未找到 分销商 账户 - tenantId={}, dealerUserId={}, orderNo={} " , TENANT_ID , dealerUserId , order . getOrderNo ( ) ) ;
return ;
// 门店角色用户可能未开通 分销账户:此时门店直推/简推会“找到了人但入不了账”,收益与明细都不会写入。
// 这里补偿创建账户后再尝试入账一次。
ShopDealerUser existed = shopDealerUserService . getOne (
new LambdaQueryWrapper < ShopDealerUser > ( )
. eq ( ShopDealerUser : : getTenantId , TENANT_ID )
. eq ( ShopDealerUser : : getUserId , dealerUserId )
. last ( " limit 1 " )
) ;
if ( existed = = null ) {
ShopDealerUser newDealerUser = new ShopDealerUser ( ) ;
newDealerUser . setTenantId ( TENANT_ID ) ;
newDealerUser . setUserId ( dealerUserId ) ;
newDealerUser . setType ( 0 ) ;
newDealerUser . setIsDelete ( 0 ) ;
newDealerUser . setSortNumber ( 0 ) ;
newDealerUser . setFirstNum ( 0 ) ;
newDealerUser . setSecondNum ( 0 ) ;
newDealerUser . setThirdNum ( 0 ) ;
newDealerUser . setMoney ( BigDecimal . ZERO ) ;
newDealerUser . setFreezeMoney ( BigDecimal . ZERO ) ;
newDealerUser . setTotalMoney ( BigDecimal . ZERO ) ;
newDealerUser . setCreateTime ( java . time . LocalDateTime . now ( ) ) ;
newDealerUser . setUpdateTime ( java . time . LocalDateTime . now ( ) ) ;
try {
shopDealerUserService . save ( newDealerUser ) ;
} catch ( Exception ignore ) {
// 并发下可能已被其他线程/实例创建,忽略后继续重试入账。
}
}
updated = shopDealerUserService . update (
new LambdaUpdateWrapper < ShopDealerUser > ( )
. eq ( ShopDealerUser : : getTenantId , TENANT_ID )
. eq ( ShopDealerUser : : getUserId , dealerUserId )
. setSql ( " money = IFNULL(money,0) + " + money . toPlainString ( ) )
. setSql ( " total_money = IFNULL(total_money,0) + " + money . toPlainString ( ) )
) ;
if ( ! updated ) {
log . warn ( " 发放佣金失败:未找到/创建分销商账户 - tenantId={}, dealerUserId={}, orderNo={} " , TENANT_ID , dealerUserId , order . getOrderNo ( ) ) ;
return ;
}
}
ShopDealerCapital cap = new ShopDealerCapital ( ) ;
@@ -290,7 +342,7 @@ public class DealerOrderSettlement10584Task {
cap . setFlowType ( 10 ) ;
cap . setMoney ( money ) ;
cap . setComments ( comments ) ;
cap . setToUserId ( order . getUserId ( ) ) ;
cap . setToUserId ( toUserId ) ;
cap . setMonth ( LocalDate . now ( ) . format ( DateTimeFormatter . ofPattern ( " yyyy-MM " ) ) ) ;
cap . setTenantId ( TENANT_ID ) ;
shopDealerCapitalService . save ( cap ) ;
@@ -332,6 +384,8 @@ public class DealerOrderSettlement10584Task {
dealerOrder . setIsSettled ( 1 ) ;
dealerOrder . setSettleTime ( java . time . LocalDateTime . now ( ) ) ;
// ShopDealerCapital 关联查询会拿 shop_dealer_order.month( 覆盖 a.month) , 这里需要同步填充。
dealerOrder . setMonth ( LocalDate . now ( ) . format ( DateTimeFormatter . ofPattern ( " yyyy-MM " ) ) ) ;
dealerOrder . setTenantId ( TENANT_ID ) ;
dealerOrder . setComments ( buildCommissionTraceComment ( dealerRefereeCommission , shopRoleCommission ) ) ;