- 将经销商佣金解冻任务执行频率从每分钟调整为每30秒一次 - 将经销商订单结算任务执行频率从每20秒调整为每10秒一次 - 将GLT套票发放任务执行频率从每分钟调整为每15秒一次 - 将GLT票券订单自动确认任务执行频率从每分钟调整为每33秒一次 - 将GLT用户票券自动释放任务执行频率从每分钟调整为每10分钟一次 - 在票券订单完成时同步更新商城订单状态为已完成
772 lines
33 KiB
Java
772 lines
33 KiB
Java
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_order(storeId/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);
|
||
});
|
||
}
|
||
|
||
}
|