Merge remote-tracking branch 'origin/master'

This commit is contained in:
2026-02-24 16:11:26 +08:00
3 changed files with 213 additions and 1 deletions

View File

@@ -12,10 +12,14 @@ import com.gxwebsoft.glt.service.GltUserTicketService;
import com.gxwebsoft.shop.entity.ShopDealerCapital; import com.gxwebsoft.shop.entity.ShopDealerCapital;
import com.gxwebsoft.shop.entity.ShopDealerOrder; import com.gxwebsoft.shop.entity.ShopDealerOrder;
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.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.ShopDealerUserService; 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 com.gxwebsoft.shop.service.ShopOrderService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -25,6 +29,7 @@ import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@@ -32,6 +37,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean; 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_ELIGIBLE_TICKET_ORDERS_PER_RUN = 200;
private static final int MAX_CAPITALS_PER_RUN = 500; private static final int MAX_CAPITALS_PER_RUN = 500;
private static final int FLOW_TYPE_DELIVERY_REWARD = 60; // 配送奖励(直接入可提现金额)
@Resource @Resource
private TransactionTemplate transactionTemplate; private TransactionTemplate transactionTemplate;
@Resource @Resource
private ShopOrderService shopOrderService; private ShopOrderService shopOrderService;
@Resource
private ShopOrderGoodsService shopOrderGoodsService;
@Resource
private ShopGoodsService shopGoodsService;
@Resource @Resource
private ShopDealerCapitalService shopDealerCapitalService; private ShopDealerCapitalService shopDealerCapitalService;
@@ -110,12 +124,36 @@ public class DealerCommissionUnfreeze10584Task {
return; 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); List<ShopDealerCapital> capitals = findCapitalsByEligibleOrderNos(eligibleOrderNos);
if (capitals.isEmpty()) { if (capitals.isEmpty()) {
// 若本轮没有取到佣金明细,回退再按“最早确认收货”的订单扫一轮,尽量覆盖历史遗留未解冻。 // 若本轮没有取到佣金明细,回退再按“最早确认收货”的订单扫一轮,尽量覆盖历史遗留未解冻。
eligibleOrderNos.clear(); eligibleOrderNos.clear();
eligibleOrderNos.addAll(findEligibleNonWaterOrderNos(waterFormIds, false)); eligibleOrderNos.addAll(findEligibleNonWaterOrderNos(waterFormIds, false));
eligibleOrderNos.addAll(findEligibleWaterOrderNosByFirstFinishedTicketOrder(waterFormIds)); 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); capitals = findCapitalsByEligibleOrderNos(eligibleOrderNos);
} }
@@ -140,11 +178,182 @@ public class DealerCommissionUnfreeze10584Task {
log.info("分销佣金解冻完成 - tenantId={}, eligibleOrderNos={}, scannedCapitals={}, unfrozen={}", log.info("分销佣金解冻完成 - tenantId={}, eligibleOrderNos={}, scannedCapitals={}, unfrozen={}",
TENANT_ID, eligibleOrderNos.size(), capitals.size(), unfrozen); TENANT_ID, eligibleOrderNos.size(), capitals.size(), unfrozen);
} }
if (rewarded > 0) {
log.info("配送奖励发放完成 - tenantId={}, eligibleOrderNos={}, rewarded={}", TENANT_ID, eligibleOrderNos.size(), rewarded);
}
} finally { } finally {
running.set(false); 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() { private Set<Integer> loadWaterFormIds() {
return gltTicketTemplateService.list( return gltTicketTemplateService.list(
new LambdaQueryWrapper<GltTicketTemplate>() new LambdaQueryWrapper<GltTicketTemplate>()

View File

@@ -37,7 +37,7 @@ public class ShopDealerCapital implements Serializable {
@Schema(description = "订单编号") @Schema(description = "订单编号")
private String orderNo; private String orderNo;
@Schema(description = "资金流动类型 (10佣金收入 20提现支出 30转账支出 40转账收入 50佣金解冻)") @Schema(description = "资金流动类型 (10佣金收入 20提现支出 30转账支出 40转账收入 50佣金解冻 60配送奖励)")
private Integer flowType; private Integer flowType;
@Schema(description = "金额") @Schema(description = "金额")

View File

@@ -101,6 +101,9 @@ public class ShopGoods implements Serializable {
@Schema(description = "分红(二级)") @Schema(description = "分红(二级)")
private BigDecimal secondDividend; private BigDecimal secondDividend;
@Schema(description = "配送奖金")
private BigDecimal deliveryMoney;
@Schema(description = "库存计算方式(10下单减库存 20付款减库存)") @Schema(description = "库存计算方式(10下单减库存 20付款减库存)")
@JsonAlias({"cdeductStockType"}) @JsonAlias({"cdeductStockType"})
private Integer deductStockType; private Integer deductStockType;