feat(task): 新增配送奖励功能
- 在 DealerCommissionUnfreeze10584Task 中新增配送奖励结算逻辑 - 引入 ShopGoods 和 ShopOrderGoods 实体及相关服务依赖 - 添加 FLOW_TYPE_DELIVERY_REWARD 常量用于标识配送奖励流水类型 - 实现 settleDeliveryRewardIfNeeded 方法处理配送奖励计算和发放 - 增加配送奖励幂等性检查避免重复发放 - 更新 ShopDealerCapital 实体的资金流动类型描述,添加配送奖励类型 - 在 ShopGoods 实体中增加 deliveryMoney 字段用于存储配送奖金配置 - 实现配送奖励的事务性处理和并发安全控制
This commit is contained in:
@@ -12,10 +12,14 @@ import com.gxwebsoft.glt.service.GltUserTicketService;
|
||||
import com.gxwebsoft.shop.entity.ShopDealerCapital;
|
||||
import com.gxwebsoft.shop.entity.ShopDealerOrder;
|
||||
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.shop.service.ShopDealerCapitalService;
|
||||
import com.gxwebsoft.shop.service.ShopDealerOrderService;
|
||||
import com.gxwebsoft.shop.service.ShopDealerUserService;
|
||||
import com.gxwebsoft.shop.service.ShopGoodsService;
|
||||
import com.gxwebsoft.shop.service.ShopOrderGoodsService;
|
||||
import com.gxwebsoft.shop.service.ShopOrderService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -25,6 +29,7 @@ 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;
|
||||
@@ -32,6 +37,7 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -59,12 +65,20 @@ public class DealerCommissionUnfreeze10584Task {
|
||||
private static final int MAX_ELIGIBLE_TICKET_ORDERS_PER_RUN = 200;
|
||||
private static final int MAX_CAPITALS_PER_RUN = 500;
|
||||
|
||||
private static final int FLOW_TYPE_DELIVERY_REWARD = 60; // 配送奖励(直接入可提现金额)
|
||||
|
||||
@Resource
|
||||
private TransactionTemplate transactionTemplate;
|
||||
|
||||
@Resource
|
||||
private ShopOrderService shopOrderService;
|
||||
|
||||
@Resource
|
||||
private ShopOrderGoodsService shopOrderGoodsService;
|
||||
|
||||
@Resource
|
||||
private ShopGoodsService shopGoodsService;
|
||||
|
||||
@Resource
|
||||
private ShopDealerCapitalService shopDealerCapitalService;
|
||||
|
||||
@@ -110,12 +124,36 @@ public class DealerCommissionUnfreeze10584Task {
|
||||
return;
|
||||
}
|
||||
|
||||
// 配送奖励(与佣金解冻独立):按订单发放,幂等保证不会重复入账
|
||||
int rewarded = 0;
|
||||
for (String orderNo : eligibleOrderNos) {
|
||||
try {
|
||||
if (settleDeliveryRewardIfNeeded(orderNo)) {
|
||||
rewarded++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("发放配送奖励失败,将在下次任务重试 - tenantId={}, orderNo={}", TENANT_ID, orderNo, e);
|
||||
}
|
||||
}
|
||||
|
||||
List<ShopDealerCapital> capitals = findCapitalsByEligibleOrderNos(eligibleOrderNos);
|
||||
if (capitals.isEmpty()) {
|
||||
// 若本轮没有取到佣金明细,回退再按“最早确认收货”的订单扫一轮,尽量覆盖历史遗留未解冻。
|
||||
eligibleOrderNos.clear();
|
||||
eligibleOrderNos.addAll(findEligibleNonWaterOrderNos(waterFormIds, false));
|
||||
eligibleOrderNos.addAll(findEligibleWaterOrderNosByFirstFinishedTicketOrder(waterFormIds));
|
||||
|
||||
// 兜底扫描出来的订单也补发配送奖励(幂等)
|
||||
for (String orderNo : eligibleOrderNos) {
|
||||
try {
|
||||
if (settleDeliveryRewardIfNeeded(orderNo)) {
|
||||
rewarded++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("发放配送奖励失败,将在下次任务重试 - tenantId={}, orderNo={}", TENANT_ID, orderNo, e);
|
||||
}
|
||||
}
|
||||
|
||||
capitals = findCapitalsByEligibleOrderNos(eligibleOrderNos);
|
||||
}
|
||||
|
||||
@@ -140,11 +178,182 @@ public class DealerCommissionUnfreeze10584Task {
|
||||
log.info("分销佣金解冻完成 - tenantId={}, eligibleOrderNos={}, scannedCapitals={}, unfrozen={}",
|
||||
TENANT_ID, eligibleOrderNos.size(), capitals.size(), unfrozen);
|
||||
}
|
||||
if (rewarded > 0) {
|
||||
log.info("配送奖励发放完成 - tenantId={}, eligibleOrderNos={}, rewarded={}", TENANT_ID, eligibleOrderNos.size(), rewarded);
|
||||
}
|
||||
} finally {
|
||||
running.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean settleDeliveryRewardIfNeeded(String orderNo) {
|
||||
if (orderNo == null || orderNo.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ShopOrder order = shopOrderService.getOne(
|
||||
new LambdaQueryWrapper<ShopOrder>()
|
||||
.eq(ShopOrder::getTenantId, TENANT_ID)
|
||||
.eq(ShopOrder::getDeleted, 0)
|
||||
.eq(ShopOrder::getOrderNo, orderNo)
|
||||
.last("limit 1")
|
||||
);
|
||||
if (order == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Integer riderId = order.getRiderId();
|
||||
if (riderId == null || riderId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 快速幂等检查:已发放则跳过(事务内仍会二次校验避免并发重复)
|
||||
boolean already = shopDealerCapitalService.count(
|
||||
new LambdaQueryWrapper<ShopDealerCapital>()
|
||||
.eq(ShopDealerCapital::getTenantId, TENANT_ID)
|
||||
.eq(ShopDealerCapital::getFlowType, FLOW_TYPE_DELIVERY_REWARD)
|
||||
.eq(ShopDealerCapital::getUserId, riderId)
|
||||
.eq(ShopDealerCapital::getOrderNo, orderNo)
|
||||
) > 0;
|
||||
if (already) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean.TRUE.equals(transactionTemplate.execute(status -> {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// 锁定配送员资金明细 marker,确保并发幂等
|
||||
ShopDealerCapital existedMarker = shopDealerCapitalService.getOne(
|
||||
new LambdaQueryWrapper<ShopDealerCapital>()
|
||||
.eq(ShopDealerCapital::getTenantId, TENANT_ID)
|
||||
.eq(ShopDealerCapital::getFlowType, FLOW_TYPE_DELIVERY_REWARD)
|
||||
.eq(ShopDealerCapital::getUserId, riderId)
|
||||
.eq(ShopDealerCapital::getOrderNo, orderNo)
|
||||
.last("limit 1 for update")
|
||||
);
|
||||
if (existedMarker != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Integer orderId = order.getOrderId();
|
||||
if (orderId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<ShopOrderGoods> orderGoodsList = shopOrderGoodsService.list(
|
||||
new LambdaQueryWrapper<ShopOrderGoods>()
|
||||
.eq(ShopOrderGoods::getTenantId, TENANT_ID)
|
||||
.eq(ShopOrderGoods::getOrderId, orderId)
|
||||
);
|
||||
if (orderGoodsList == null || orderGoodsList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Integer> goodsIds = orderGoodsList.stream()
|
||||
.map(ShopOrderGoods::getGoodsId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (goodsIds.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Map<Integer, BigDecimal> goodsDeliveryMoneyMap = shopGoodsService.list(
|
||||
new LambdaQueryWrapper<ShopGoods>()
|
||||
.eq(ShopGoods::getTenantId, TENANT_ID)
|
||||
.in(ShopGoods::getGoodsId, goodsIds)
|
||||
).stream().collect(java.util.stream.Collectors.toMap(
|
||||
ShopGoods::getGoodsId,
|
||||
g -> g.getDeliveryMoney() != null ? g.getDeliveryMoney() : BigDecimal.ZERO,
|
||||
(a, b) -> a
|
||||
));
|
||||
|
||||
BigDecimal reward = BigDecimal.ZERO;
|
||||
for (ShopOrderGoods og : orderGoodsList) {
|
||||
Integer goodsId = og.getGoodsId();
|
||||
if (goodsId == null) {
|
||||
continue;
|
||||
}
|
||||
int qty = og.getTotalNum() == null ? 0 : og.getTotalNum();
|
||||
if (qty <= 0) {
|
||||
continue;
|
||||
}
|
||||
BigDecimal unit = goodsDeliveryMoneyMap.getOrDefault(goodsId, BigDecimal.ZERO);
|
||||
if (unit.signum() <= 0) {
|
||||
continue;
|
||||
}
|
||||
reward = reward.add(unit.multiply(BigDecimal.valueOf(qty)));
|
||||
}
|
||||
|
||||
reward = reward.setScale(2, RoundingMode.HALF_UP);
|
||||
if (reward.signum() <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 锁定/创建配送员分销账户
|
||||
ShopDealerUser dealerUser = shopDealerUserService.getOne(
|
||||
new LambdaQueryWrapper<ShopDealerUser>()
|
||||
.eq(ShopDealerUser::getTenantId, TENANT_ID)
|
||||
.eq(ShopDealerUser::getUserId, riderId)
|
||||
.last("limit 1 for update")
|
||||
);
|
||||
if (dealerUser == null) {
|
||||
ShopDealerUser newDealerUser = new ShopDealerUser();
|
||||
newDealerUser.setTenantId(TENANT_ID);
|
||||
newDealerUser.setUserId(riderId);
|
||||
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(now);
|
||||
newDealerUser.setUpdateTime(now);
|
||||
shopDealerUserService.save(newDealerUser);
|
||||
|
||||
dealerUser = shopDealerUserService.getOne(
|
||||
new LambdaQueryWrapper<ShopDealerUser>()
|
||||
.eq(ShopDealerUser::getTenantId, TENANT_ID)
|
||||
.eq(ShopDealerUser::getUserId, riderId)
|
||||
.last("limit 1 for update")
|
||||
);
|
||||
if (dealerUser == null) {
|
||||
log.warn("配送奖励入账失败:未找到/创建分销账户 - tenantId={}, orderNo={}, riderId={}", TENANT_ID, orderNo, riderId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal moneyVal = dealerUser.getMoney() != null ? dealerUser.getMoney() : BigDecimal.ZERO;
|
||||
BigDecimal totalMoneyVal = dealerUser.getTotalMoney() != null ? dealerUser.getTotalMoney() : BigDecimal.ZERO;
|
||||
dealerUser.setMoney(moneyVal.add(reward));
|
||||
dealerUser.setTotalMoney(totalMoneyVal.add(reward));
|
||||
dealerUser.setUpdateTime(now);
|
||||
if (!shopDealerUserService.updateById(dealerUser)) {
|
||||
log.warn("配送奖励入账失败:更新分销账户失败 - tenantId={}, orderNo={}, riderId={}, reward={}", TENANT_ID, orderNo, riderId, reward);
|
||||
return false;
|
||||
}
|
||||
|
||||
ShopDealerCapital cap = new ShopDealerCapital();
|
||||
cap.setUserId(riderId);
|
||||
cap.setOrderNo(orderNo);
|
||||
cap.setFlowType(FLOW_TYPE_DELIVERY_REWARD);
|
||||
cap.setMoney(reward);
|
||||
cap.setComments("配送奖励");
|
||||
cap.setToUserId(order.getUserId());
|
||||
cap.setMonth(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM")));
|
||||
cap.setTenantId(TENANT_ID);
|
||||
cap.setCreateTime(now);
|
||||
cap.setUpdateTime(now);
|
||||
shopDealerCapitalService.save(cap);
|
||||
|
||||
log.info("配送奖励发放成功 - tenantId={}, orderNo={}, riderId={}, reward={}", TENANT_ID, orderNo, riderId, reward);
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
|
||||
private Set<Integer> loadWaterFormIds() {
|
||||
return gltTicketTemplateService.list(
|
||||
new LambdaQueryWrapper<GltTicketTemplate>()
|
||||
|
||||
@@ -37,7 +37,7 @@ public class ShopDealerCapital implements Serializable {
|
||||
@Schema(description = "订单编号")
|
||||
private String orderNo;
|
||||
|
||||
@Schema(description = "资金流动类型 (10佣金收入 20提现支出 30转账支出 40转账收入 50佣金解冻)")
|
||||
@Schema(description = "资金流动类型 (10佣金收入 20提现支出 30转账支出 40转账收入 50佣金解冻 60配送奖励)")
|
||||
private Integer flowType;
|
||||
|
||||
@Schema(description = "金额")
|
||||
|
||||
@@ -101,6 +101,9 @@ public class ShopGoods implements Serializable {
|
||||
@Schema(description = "分红(二级)")
|
||||
private BigDecimal secondDividend;
|
||||
|
||||
@Schema(description = "配送奖金")
|
||||
private BigDecimal deliveryMoney;
|
||||
|
||||
@Schema(description = "库存计算方式(10下单减库存 20付款减库存)")
|
||||
@JsonAlias({"cdeductStockType"})
|
||||
private Integer deductStockType;
|
||||
|
||||
Reference in New Issue
Block a user