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 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 pageRel(GltTicketOrderParam param) { PageParam page = new PageParam<>(param); page.setDefaultOrder("sort_number asc, create_time desc"); List list = baseMapper.selectPageRel(page, param); return new PageResult<>(list, page.getTotal()); } @Override public List listRel(GltTicketOrderParam param) { List list = baseMapper.selectListRel(param); // 排序 PageParam 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() .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 uw = new LambdaUpdateWrapper() .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 qw = new LambdaQueryWrapper() .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 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() .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 uw = new LambdaUpdateWrapper() .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 qw = new LambdaQueryWrapper() .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() .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() .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() .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() .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); }); } }