Files
mp-java/src/main/java/com/gxwebsoft/glt/service/impl/GltTicketOrderServiceImpl.java
赵忠林 1177730464 perf(task): 调整定时任务执行频率以优化性能
- 将经销商佣金解冻任务执行频率从每分钟调整为每30秒一次
- 将经销商订单结算任务执行频率从每20秒调整为每10秒一次
- 将GLT套票发放任务执行频率从每分钟调整为每15秒一次
- 将GLT票券订单自动确认任务执行频率从每分钟调整为每33秒一次
- 将GLT用户票券自动释放任务执行频率从每分钟调整为每10分钟一次
- 在票券订单完成时同步更新商城订单状态为已完成
2026-02-10 13:52:13 +08:00

772 lines
33 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.gxwebsoft.glt.service.impl;
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.exception.BusinessException;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.common.system.mapper.UserMapper;
import com.gxwebsoft.glt.entity.GltTicketOrder;
import com.gxwebsoft.glt.entity.GltUserTicket;
import com.gxwebsoft.glt.entity.GltUserTicketLog;
import com.gxwebsoft.glt.mapper.GltTicketOrderMapper;
import com.gxwebsoft.glt.mapper.GltUserTicketMapper;
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.entity.ShopDealerCapital;
import com.gxwebsoft.shop.entity.ShopDealerUser;
import com.gxwebsoft.shop.entity.ShopOrder;
import com.gxwebsoft.shop.service.ShopDealerCapitalService;
import com.gxwebsoft.shop.service.ShopDealerUserService;
import com.gxwebsoft.shop.service.ShopOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.stereotype.Service;
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.format.DateTimeFormatter;
import java.time.LocalDateTime;
import java.util.List;
/**
* 送水订单Service实现
*
* @author 科技小王子
* @since 2026-02-05 18:50:20
*/
@Slf4j
@Service
public class GltTicketOrderServiceImpl extends ServiceImpl<GltTicketOrderMapper, GltTicketOrder> implements GltTicketOrderService {
public static final int CHANGE_TYPE_WRITE_OFF = 20;
private static final BigDecimal RIDER_UNIT_COMMISSION = new BigDecimal("0.10");
private static final int RIDER_COMMISSION_SCALE = 2;
private static final int TENANT_ID_10584 = 10584;
@Resource
private GltUserTicketMapper gltUserTicketMapper;
@Resource
private GltUserTicketService gltUserTicketService;
@Resource
private GltUserTicketLogService gltUserTicketLogService;
@Resource
private TransactionTemplate transactionTemplate;
@Resource
private ShopDealerUserService shopDealerUserService;
@Resource
private ShopDealerCapitalService shopDealerCapitalService;
@Resource
private UserMapper userMapper;
@Resource
private ShopOrderService shopOrderService;
@Override
public PageResult<GltTicketOrder> pageRel(GltTicketOrderParam param) {
PageParam<GltTicketOrder, GltTicketOrderParam> page = new PageParam<>(param);
page.setDefaultOrder("sort_number asc, create_time desc");
List<GltTicketOrder> list = baseMapper.selectPageRel(page, param);
return new PageResult<>(list, page.getTotal());
}
@Override
public List<GltTicketOrder> listRel(GltTicketOrderParam param) {
List<GltTicketOrder> list = baseMapper.selectListRel(param);
// 排序
PageParam<GltTicketOrder, GltTicketOrderParam> page = new PageParam<>();
page.setDefaultOrder("sort_number asc, create_time desc");
return page.sortRecords(list);
}
@Override
public GltTicketOrder getByIdRel(Integer id) {
GltTicketOrderParam param = new GltTicketOrderParam();
param.setId(id);
return param.getOne(baseMapper.selectListRel(param));
}
@Override
@Transactional(rollbackFor = Exception.class)
public GltTicketOrder createWithWriteOff(GltTicketOrder gltTicketOrder, Integer userId, Integer tenantId) {
if (gltTicketOrder == null) {
throw new BusinessException("订单参数不能为空");
}
if (userId == null) {
throw new BusinessException("请先登录");
}
Integer userTicketId = gltTicketOrder.getUserTicketId();
if (userTicketId == null) {
throw new BusinessException("userTicketId不能为空");
}
int totalNum = gltTicketOrder.getTotalNum() == null ? 0 : gltTicketOrder.getTotalNum();
if (totalNum <= 0) {
throw new BusinessException("totalNum必须大于0");
}
if (tenantId == null) {
throw new BusinessException("租户信息缺失");
}
// 1) 校验水票归属当前用户 + 正常状态,并锁定记录,避免并发扣减导致日志不准确
GltUserTicket userTicket = gltUserTicketMapper.selectByIdForUpdate(userTicketId, userId, tenantId);
if (userTicket == null) {
throw new BusinessException("水票不存在、已失效或无权限");
}
int availableQty = userTicket.getAvailableQty() == null ? 0 : userTicket.getAvailableQty();
int usedQty = userTicket.getUsedQty() == null ? 0 : userTicket.getUsedQty();
if (availableQty < totalNum) {
throw new BusinessException("水票数量不足");
}
// 2) 更新 glt_user_ticket: availableQty -= totalNum, usedQty += totalNum
LocalDateTime now = LocalDateTime.now();
int availableAfter = availableQty - totalNum;
int usedAfter = usedQty + totalNum;
userTicket.setAvailableQty(availableAfter);
userTicket.setUsedQty(usedAfter);
userTicket.setUpdateTime(now);
if (!gltUserTicketService.updateById(userTicket)) {
throw new BusinessException("扣减水票失败");
}
// 4) 插入 glt_ticket_orderstoreId/addressId/totalNum/buyerRemarks…
gltTicketOrder.setUserId(userId);
// 订单基础字段由后端兜底,避免前端误传/恶意传参
gltTicketOrder.setStatus(0);
gltTicketOrder.setDeleted(0);
gltTicketOrder.setTenantId(tenantId);
if (gltTicketOrder.getDeliveryStatus() == null) {
gltTicketOrder.setDeliveryStatus(DELIVERY_STATUS_WAITING);
}
if (gltTicketOrder.getSortNumber() == null) {
gltTicketOrder.setSortNumber(0);
}
if (gltTicketOrder.getCreateTime() == null) {
gltTicketOrder.setCreateTime(now);
}
gltTicketOrder.setUpdateTime(now);
if (!this.save(gltTicketOrder)) {
throw new BusinessException("创建订单失败");
}
// 3) 插入 glt_user_ticket_log核销记录
GltUserTicketLog log = new GltUserTicketLog();
log.setUserTicketId(userTicketId);
log.setChangeType(CHANGE_TYPE_WRITE_OFF);
log.setChangeAvailable(-totalNum);
log.setChangeFrozen(0);
log.setChangeUsed(totalNum);
log.setAvailableAfter(availableAfter);
log.setFrozenAfter(userTicket.getFrozenQty() == null ? 0 : userTicket.getFrozenQty());
log.setUsedAfter(usedAfter);
log.setOrderId(gltTicketOrder.getId());
log.setOrderNo(gltTicketOrder.getId() == null ? null : String.valueOf(gltTicketOrder.getId()));
log.setUserId(userId);
log.setSortNumber(0);
String comments = gltTicketOrder.getComments();
if (StrUtil.isBlank(comments)) {
comments = gltTicketOrder.getBuyerRemarks();
}
if (StrUtil.isBlank(comments)) {
comments = "水票下单核销";
}
log.setComments(comments);
log.setStatus(0);
log.setDeleted(0);
log.setTenantId(tenantId);
log.setCreateTime(now);
log.setUpdateTime(now);
if (!gltUserTicketLogService.save(log)) {
throw new BusinessException("写入核销记录失败");
}
return gltTicketOrder;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void accept(Integer id, Integer riderId, Integer tenantId) {
if (id == null) {
throw new BusinessException("订单id不能为空");
}
if (riderId == null) {
throw new BusinessException("配送员信息缺失");
}
if (tenantId == null) {
throw new BusinessException("租户信息缺失");
}
// 原子接单:避免并发抢单
LocalDateTime now = LocalDateTime.now();
boolean ok = this.lambdaUpdate()
.set(GltTicketOrder::getRiderId, riderId)
.set(GltTicketOrder::getUpdateTime, now)
.eq(GltTicketOrder::getId, id)
.eq(GltTicketOrder::getTenantId, tenantId)
.eq(GltTicketOrder::getDeleted, 0)
.and(w -> w.eq(GltTicketOrder::getDeliveryStatus, DELIVERY_STATUS_WAITING)
.or().isNull(GltTicketOrder::getDeliveryStatus))
.and(w -> w.isNull(GltTicketOrder::getRiderId).or().eq(GltTicketOrder::getRiderId, 0))
.update();
if (ok) {
// 接单成功后同步商城订单发货状态10未发货 -> 20已发货
updateShopOrderDeliveryStatusAfterAccept(id, tenantId, riderId, now);
return;
}
// 回查给出更明确的错误
GltTicketOrder order = this.lambdaQuery()
.eq(GltTicketOrder::getId, id)
.eq(GltTicketOrder::getTenantId, tenantId)
.eq(GltTicketOrder::getDeleted, 0)
.one();
if (order == null) {
throw new BusinessException("订单不存在");
}
if (order.getRiderId() != null && order.getRiderId() > 0) {
throw new BusinessException("订单已被其他配送员接单");
}
throw new BusinessException("订单状态不允许接单");
}
@Override
public void markShopOrderShippedAfterRiderAssigned(Integer ticketOrderId, Integer tenantId, Integer riderId) {
updateShopOrderDeliveryStatusAfterAccept(ticketOrderId, tenantId, riderId, LocalDateTime.now());
}
private void updateShopOrderDeliveryStatusAfterAccept(Integer ticketOrderId, Integer tenantId, Integer riderId, LocalDateTime now) {
if (ticketOrderId == null || tenantId == null) {
return;
}
// 找到关联水票的商城订单glt_user_ticket.orderId / orderNo
GltTicketOrder ticketOrder = this.lambdaQuery()
.select(GltTicketOrder::getId, GltTicketOrder::getUserTicketId, GltTicketOrder::getRiderId)
.eq(GltTicketOrder::getId, ticketOrderId)
.eq(GltTicketOrder::getTenantId, tenantId)
.eq(GltTicketOrder::getDeleted, 0)
.last("limit 1")
.one();
if (ticketOrder == null || ticketOrder.getUserTicketId() == null) {
return;
}
Integer actualRiderId = (riderId != null && riderId > 0) ? riderId : ticketOrder.getRiderId();
GltUserTicket userTicket = gltUserTicketService.getOne(
new LambdaQueryWrapper<GltUserTicket>()
.eq(GltUserTicket::getTenantId, tenantId)
.eq(GltUserTicket::getDeleted, 0)
.eq(GltUserTicket::getId, ticketOrder.getUserTicketId())
.last("limit 1")
);
if (userTicket == null) {
return;
}
Integer shopOrderId = userTicket.getOrderId();
String shopOrderNo = userTicket.getOrderNo();
if (shopOrderId == null && !StringUtils.hasText(shopOrderNo)) {
return;
}
LambdaUpdateWrapper<ShopOrder> uw = new LambdaUpdateWrapper<ShopOrder>()
.eq(ShopOrder::getTenantId, tenantId)
.eq(ShopOrder::getDeleted, 0)
// deliveryStatus 已经是 20 时也可能需要补写/更新 riderId因此条件包含 riderId 不一致的场景
.and(w -> {
w.ne(ShopOrder::getDeliveryStatus, 20).or().isNull(ShopOrder::getDeliveryStatus);
if (actualRiderId != null && actualRiderId > 0) {
w.or().ne(ShopOrder::getRiderId, actualRiderId).or().isNull(ShopOrder::getRiderId);
}
})
.set(ShopOrder::getDeliveryStatus, 20)
.set(ShopOrder::getUpdateTime, now);
if (actualRiderId != null && actualRiderId > 0) {
uw.set(ShopOrder::getRiderId, actualRiderId);
}
if (shopOrderId != null) {
uw.eq(ShopOrder::getOrderId, shopOrderId);
} else {
uw.eq(ShopOrder::getOrderNo, shopOrderNo);
}
boolean updated = shopOrderService.update(uw);
if (updated) {
return;
}
// 幂等:若已是 20则视为成功否则记录日志便于排查关联关系/数据缺失
LambdaQueryWrapper<ShopOrder> qw = new LambdaQueryWrapper<ShopOrder>()
.eq(ShopOrder::getTenantId, tenantId)
.eq(ShopOrder::getDeleted, 0)
.eq(ShopOrder::getDeliveryStatus, 20);
if (actualRiderId != null && actualRiderId > 0) {
qw.eq(ShopOrder::getRiderId, actualRiderId);
}
if (shopOrderId != null) {
qw.eq(ShopOrder::getOrderId, shopOrderId);
} else {
qw.eq(ShopOrder::getOrderNo, shopOrderNo);
}
if (shopOrderService.count(qw) <= 0) {
log.warn("接单/指派成功但同步商城订单发货状态/配送员失败 - tenantId={}, ticketOrderId={}, riderId={}, shopOrderId={}, shopOrderNo={}",
tenantId, ticketOrderId, actualRiderId, shopOrderId, shopOrderNo);
}
}
@Override
public void start(Integer id, Integer riderId, Integer tenantId) {
if (id == null) {
throw new BusinessException("订单id不能为空");
}
if (riderId == null) {
throw new BusinessException("配送员信息缺失");
}
if (tenantId == null) {
throw new BusinessException("租户信息缺失");
}
LocalDateTime now = LocalDateTime.now();
boolean ok = this.lambdaUpdate()
.set(GltTicketOrder::getDeliveryStatus, DELIVERY_STATUS_DELIVERING)
.set(GltTicketOrder::getSendStartTime, now)
.set(GltTicketOrder::getUpdateTime, now)
.eq(GltTicketOrder::getId, id)
.eq(GltTicketOrder::getTenantId, tenantId)
.eq(GltTicketOrder::getDeleted, 0)
.eq(GltTicketOrder::getRiderId, riderId)
.and(w -> w.eq(GltTicketOrder::getDeliveryStatus, DELIVERY_STATUS_WAITING)
.or().isNull(GltTicketOrder::getDeliveryStatus))
.update();
if (ok) {
return;
}
GltTicketOrder order = this.lambdaQuery()
.eq(GltTicketOrder::getId, id)
.eq(GltTicketOrder::getTenantId, tenantId)
.eq(GltTicketOrder::getDeleted, 0)
.one();
if (order == null) {
throw new BusinessException("订单不存在");
}
if (!riderId.equals(order.getRiderId())) {
throw new BusinessException("无权限操作该订单");
}
if (order.getDeliveryStatus() != null && order.getDeliveryStatus() == DELIVERY_STATUS_DELIVERING) {
// 幂等:重复开始配送视为成功
return;
}
throw new BusinessException("订单状态不允许开始配送");
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delivered(Integer id, Integer riderId, Integer tenantId, String sendEndImg) {
if (id == null) {
throw new BusinessException("订单id不能为空");
}
if (riderId == null) {
throw new BusinessException("配送员信息缺失");
}
if (tenantId == null) {
throw new BusinessException("租户信息缺失");
}
LocalDateTime now = LocalDateTime.now();
var update = this.lambdaUpdate()
.set(GltTicketOrder::getDeliveryStatus, DELIVERY_STATUS_WAIT_CONFIRM)
.set(GltTicketOrder::getSendEndTime, now)
.set(GltTicketOrder::getUpdateTime, now);
if (StringUtils.hasText(sendEndImg)) {
update.set(GltTicketOrder::getSendEndImg, sendEndImg);
}
boolean ok = update
.eq(GltTicketOrder::getId, id)
.eq(GltTicketOrder::getTenantId, tenantId)
.eq(GltTicketOrder::getDeleted, 0)
.eq(GltTicketOrder::getRiderId, riderId)
.eq(GltTicketOrder::getDeliveryStatus, DELIVERY_STATUS_DELIVERING)
.update();
if (ok) {
// 配送员拍照上传送达后可触发提成结算(幂等,重复调用不会重复入账)
settleRiderCommissionIfEligible(id, tenantId, true);
// 配送员确认送达后,同步商城订单为“已完成”(orderStatus=1)
updateShopOrderOrderStatusAfterTicketFinished(id, tenantId, now);
return;
}
GltTicketOrder order = this.lambdaQuery()
.eq(GltTicketOrder::getId, id)
.eq(GltTicketOrder::getTenantId, tenantId)
.eq(GltTicketOrder::getDeleted, 0)
.one();
if (order == null) {
throw new BusinessException("订单不存在");
}
if (!riderId.equals(order.getRiderId())) {
throw new BusinessException("无权限操作该订单");
}
if (order.getDeliveryStatus() != null
&& (order.getDeliveryStatus() == DELIVERY_STATUS_WAIT_CONFIRM
|| order.getDeliveryStatus() == DELIVERY_STATUS_FINISHED)) {
// 幂等:重复送达视为成功
settleRiderCommissionIfEligible(id, tenantId, true);
updateShopOrderOrderStatusAfterTicketFinished(id, tenantId, LocalDateTime.now());
return;
}
throw new BusinessException("订单状态不允许确认送达");
}
@Override
@Transactional(rollbackFor = Exception.class)
public void confirmReceive(Integer id, Integer userId, Integer tenantId) {
if (id == null) {
throw new BusinessException("订单id不能为空");
}
if (userId == null) {
throw new BusinessException("请先登录");
}
if (tenantId == null) {
throw new BusinessException("租户信息缺失");
}
LocalDateTime now = LocalDateTime.now();
boolean ok = this.lambdaUpdate()
.set(GltTicketOrder::getDeliveryStatus, DELIVERY_STATUS_FINISHED)
.set(GltTicketOrder::getReceiveConfirmTime, now)
.set(GltTicketOrder::getReceiveConfirmType, RECEIVE_CONFIRM_TYPE_MANUAL)
.set(GltTicketOrder::getUpdateTime, now)
.eq(GltTicketOrder::getId, id)
.eq(GltTicketOrder::getTenantId, tenantId)
.eq(GltTicketOrder::getDeleted, 0)
.eq(GltTicketOrder::getUserId, userId)
.eq(GltTicketOrder::getDeliveryStatus, DELIVERY_STATUS_WAIT_CONFIRM)
.update();
if (ok) {
// 用户确认收货完成后触发配送员提成结算(幂等,重复调用不会重复入账)
settleRiderCommissionIfEligible(id, tenantId, false);
// 送水订单完成后,同步商城订单为“已完成”(orderStatus=1)
updateShopOrderOrderStatusAfterTicketFinished(id, tenantId, now);
return;
}
GltTicketOrder order = this.lambdaQuery()
.eq(GltTicketOrder::getId, id)
.eq(GltTicketOrder::getTenantId, tenantId)
.eq(GltTicketOrder::getDeleted, 0)
.one();
if (order == null) {
throw new BusinessException("订单不存在");
}
if (!userId.equals(order.getUserId())) {
throw new BusinessException("无权限操作该订单");
}
if (order.getDeliveryStatus() != null && order.getDeliveryStatus() == DELIVERY_STATUS_FINISHED) {
// 幂等:重复确认收货视为成功
settleRiderCommissionIfEligible(id, tenantId, false);
updateShopOrderOrderStatusAfterTicketFinished(id, tenantId, LocalDateTime.now());
return;
}
throw new BusinessException("订单状态不允许确认收货");
}
@Override
public int autoConfirmTimeout(Integer tenantId, LocalDateTime now, int timeoutHours, int batchSize) {
if (tenantId == null) {
return 0;
}
if (now == null) {
now = LocalDateTime.now();
}
int hours = Math.max(timeoutHours, 1);
int limit = Math.max(batchSize, 1);
LocalDateTime deadline = now.minusHours(hours);
List<GltTicketOrder> candidates = this.lambdaQuery()
.select(GltTicketOrder::getId)
.eq(GltTicketOrder::getTenantId, tenantId)
.eq(GltTicketOrder::getDeleted, 0)
.eq(GltTicketOrder::getDeliveryStatus, DELIVERY_STATUS_WAIT_CONFIRM)
.isNotNull(GltTicketOrder::getSendEndTime)
.le(GltTicketOrder::getSendEndTime, deadline)
.orderByAsc(GltTicketOrder::getSendEndTime)
.orderByAsc(GltTicketOrder::getId)
.last("limit " + limit)
.list();
if (candidates == null || candidates.isEmpty()) {
return 0;
}
int confirmed = 0;
for (GltTicketOrder item : candidates) {
Integer id = item != null ? item.getId() : null;
if (id == null) {
continue;
}
try {
final LocalDateTime nowFinal = now;
final LocalDateTime deadlineFinal = deadline;
Boolean ok = transactionTemplate.execute(status -> {
boolean updated = this.lambdaUpdate()
.set(GltTicketOrder::getDeliveryStatus, DELIVERY_STATUS_FINISHED)
.set(GltTicketOrder::getReceiveConfirmTime, nowFinal)
.set(GltTicketOrder::getReceiveConfirmType, RECEIVE_CONFIRM_TYPE_TIMEOUT)
.set(GltTicketOrder::getUpdateTime, nowFinal)
.eq(GltTicketOrder::getId, id)
.eq(GltTicketOrder::getTenantId, tenantId)
.eq(GltTicketOrder::getDeleted, 0)
.eq(GltTicketOrder::getDeliveryStatus, DELIVERY_STATUS_WAIT_CONFIRM)
.le(GltTicketOrder::getSendEndTime, deadlineFinal)
.update();
if (!updated) {
return false;
}
// 超时自动确认收货后,也按“完成”逻辑触发配送员提成结算(幂等)。
settleRiderCommissionIfEligible(id, tenantId, false);
updateShopOrderOrderStatusAfterTicketFinished(id, tenantId, nowFinal);
return true;
});
if (Boolean.TRUE.equals(ok)) {
confirmed++;
}
} catch (Exception e) {
log.warn("送水订单超时自动确认收货失败 - tenantId={}, ticketOrderId={}", tenantId, id, e);
}
}
return confirmed;
}
private void updateShopOrderOrderStatusAfterTicketFinished(Integer ticketOrderId, Integer tenantId, LocalDateTime now) {
if (ticketOrderId == null || tenantId == null) {
return;
}
if (now == null) {
now = LocalDateTime.now();
}
// 找到关联水票的商城订单glt_user_ticket.orderId / orderNo
GltTicketOrder ticketOrder = this.lambdaQuery()
.select(GltTicketOrder::getId, GltTicketOrder::getUserTicketId)
.eq(GltTicketOrder::getId, ticketOrderId)
.eq(GltTicketOrder::getTenantId, tenantId)
.eq(GltTicketOrder::getDeleted, 0)
.last("limit 1")
.one();
if (ticketOrder == null || ticketOrder.getUserTicketId() == null) {
return;
}
GltUserTicket userTicket = gltUserTicketService.getOne(
new LambdaQueryWrapper<GltUserTicket>()
.eq(GltUserTicket::getTenantId, tenantId)
.eq(GltUserTicket::getDeleted, 0)
.eq(GltUserTicket::getId, ticketOrder.getUserTicketId())
.last("limit 1")
);
if (userTicket == null) {
return;
}
Integer shopOrderId = userTicket.getOrderId();
String shopOrderNo = userTicket.getOrderNo();
if (shopOrderId == null && !StringUtils.hasText(shopOrderNo)) {
return;
}
LambdaUpdateWrapper<ShopOrder> uw = new LambdaUpdateWrapper<ShopOrder>()
.eq(ShopOrder::getTenantId, tenantId)
.eq(ShopOrder::getDeleted, 0)
.and(w -> w.ne(ShopOrder::getOrderStatus, 1).or().isNull(ShopOrder::getOrderStatus))
.set(ShopOrder::getOrderStatus, 1)
.set(ShopOrder::getUpdateTime, now);
if (shopOrderId != null) {
uw.eq(ShopOrder::getOrderId, shopOrderId);
} else {
uw.eq(ShopOrder::getOrderNo, shopOrderNo);
}
boolean updated = shopOrderService.update(uw);
if (updated) {
return;
}
// 幂等:若已是 1则视为成功否则记录日志便于排查关联关系/数据缺失
LambdaQueryWrapper<ShopOrder> qw = new LambdaQueryWrapper<ShopOrder>()
.eq(ShopOrder::getTenantId, tenantId)
.eq(ShopOrder::getDeleted, 0)
.eq(ShopOrder::getOrderStatus, 1);
if (shopOrderId != null) {
qw.eq(ShopOrder::getOrderId, shopOrderId);
} else {
qw.eq(ShopOrder::getOrderNo, shopOrderNo);
}
if (shopOrderService.count(qw) <= 0) {
log.warn("送水订单完成但同步商城订单完成状态失败 - tenantId={}, ticketOrderId={}, shopOrderId={}, shopOrderNo={}",
tenantId, ticketOrderId, shopOrderId, shopOrderNo);
}
}
private void settleRiderCommissionIfEligible(Integer ticketOrderId, Integer tenantId, boolean requirePhoto) {
if (ticketOrderId == null || tenantId == null) {
return;
}
// 目前仅租户10584启用该提成规则避免影响其他租户历史逻辑。
if (tenantId != TENANT_ID_10584) {
return;
}
transactionTemplate.executeWithoutResult(status -> {
// 锁定送水订单行:避免并发下重复结算(如:配送员送达&用户确认收货同时触发)
GltTicketOrder order = this.lambdaQuery()
.eq(GltTicketOrder::getId, ticketOrderId)
.eq(GltTicketOrder::getTenantId, tenantId)
.eq(GltTicketOrder::getDeleted, 0)
.last("limit 1 for update")
.one();
if (order == null) {
return;
}
Integer riderId = order.getRiderId();
if (riderId == null || riderId <= 0) {
return;
}
Integer deliveryStatus = order.getDeliveryStatus();
if (requirePhoto) {
// 配送员拍照上传触发:至少需要到“待客户确认”或“已完成”状态,且存在送达照片。
if (deliveryStatus == null || (deliveryStatus != DELIVERY_STATUS_WAIT_CONFIRM && deliveryStatus != DELIVERY_STATUS_FINISHED)) {
return;
}
if (!StringUtils.hasText(order.getSendEndImg())) {
return;
}
} else {
// 用户确认收货触发:必须为“已完成”状态。
if (deliveryStatus == null || deliveryStatus != DELIVERY_STATUS_FINISHED) {
return;
}
}
int qty = order.getTotalNum() == null ? 0 : order.getTotalNum();
if (qty <= 0) {
return;
}
BigDecimal money = RIDER_UNIT_COMMISSION
.multiply(BigDecimal.valueOf(qty))
.setScale(RIDER_COMMISSION_SCALE, RoundingMode.HALF_UP);
if (money.signum() <= 0) {
return;
}
String orderNo = "gltTicketOrder:" + order.getId();
String comments = "配送员提成(ticketOrderId=" + order.getId() + ",unit=" + RIDER_UNIT_COMMISSION + ",qty=" + qty + ")";
// 幂等:同一送水订单同一配送员只结算一次
boolean already = shopDealerCapitalService.count(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ShopDealerCapital>()
.eq(ShopDealerCapital::getTenantId, tenantId)
.eq(ShopDealerCapital::getFlowType, 10)
.eq(ShopDealerCapital::getUserId, riderId)
.eq(ShopDealerCapital::getOrderNo, orderNo)
.likeRight(ShopDealerCapital::getComments, "配送员提成(ticketOrderId=" + order.getId() + ",")
) > 0;
if (already) {
return;
}
// 送水订单提成:先入冻结金额 freeze_money与分销订单佣金一致
LocalDateTime now = LocalDateTime.now();
boolean updated = shopDealerUserService.update(
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<ShopDealerUser>()
.eq(ShopDealerUser::getTenantId, tenantId)
.eq(ShopDealerUser::getUserId, riderId)
.setSql("freeze_money = IFNULL(freeze_money,0) + " + money.toPlainString())
.setSql("total_money = IFNULL(total_money,0) + " + money.toPlainString())
.set(ShopDealerUser::getUpdateTime, now)
);
if (!updated) {
// 配送员可能未开通分销账户:创建后再尝试入账一次(与分销结算逻辑保持一致)
ShopDealerUser existed = shopDealerUserService.getOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ShopDealerUser>()
.eq(ShopDealerUser::getTenantId, tenantId)
.eq(ShopDealerUser::getUserId, riderId)
.last("limit 1")
);
if (existed == null) {
ShopDealerUser newDealerUser = new ShopDealerUser();
newDealerUser.setTenantId(tenantId);
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);
try {
User sysUser = userMapper.selectByIdIgnoreTenant(riderId);
if (sysUser != null) {
newDealerUser.setRealName(sysUser.getRealName() != null ? sysUser.getRealName() : sysUser.getNickname());
newDealerUser.setMobile(sysUser.getPhone());
}
} catch (Exception ignore) {
// 基础信息补齐失败不影响入账
}
newDealerUser.setCreateTime(now);
newDealerUser.setUpdateTime(now);
shopDealerUserService.save(newDealerUser);
}
updated = shopDealerUserService.update(
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<ShopDealerUser>()
.eq(ShopDealerUser::getTenantId, tenantId)
.eq(ShopDealerUser::getUserId, riderId)
.setSql("freeze_money = IFNULL(freeze_money,0) + " + money.toPlainString())
.setSql("total_money = IFNULL(total_money,0) + " + money.toPlainString())
.set(ShopDealerUser::getUpdateTime, now)
);
if (!updated) {
log.warn("配送员提成入账失败:未找到/创建分销账户 - tenantId={}, ticketOrderId={}, riderId={}", tenantId, order.getId(), riderId);
return;
}
}
ShopDealerCapital cap = new ShopDealerCapital();
cap.setUserId(riderId);
cap.setOrderNo(orderNo);
cap.setFlowType(10);
cap.setMoney(money);
cap.setComments(comments);
cap.setToUserId(order.getUserId());
cap.setMonth(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM")));
cap.setTenantId(tenantId);
cap.setCreateTime(now);
cap.setUpdateTime(now);
shopDealerCapitalService.save(cap);
});
}
}