rk
2 天以前 467fe3b3ec6aa9d449b094bdd9df4611323d88d1
server/services/src/main/java/com/doumee/service/business/impl/OrdersServiceImpl.java
@@ -8,15 +8,18 @@
import com.doumee.biz.system.OperationConfigBiz;
import com.doumee.biz.system.SystemDictDataBiz;
import com.doumee.config.wx.WxMiniConfig;
import com.doumee.config.wx.WxMiniUtilService;
import com.doumee.config.wx.WxPayProperties;
import com.doumee.config.wx.WxPayV3Service;
import com.wechat.pay.java.service.refund.model.Refund;
import com.doumee.core.constants.Constants;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.core.utils.DateUtil;
import com.doumee.core.utils.Tencent.MapUtil;
import com.doumee.core.utils.geocode.MapUtil;
import com.doumee.core.utils.Utils;
import com.doumee.core.utils.aliyun.AliSmsService;
import com.doumee.dao.business.*;
import com.doumee.dao.business.model.*;
import com.doumee.dao.system.SystemUserMapper;
@@ -30,38 +33,35 @@
import com.doumee.dao.dto.MyOrderDTO;
import com.doumee.dao.dto.OrderItemDTO;
import com.doumee.dao.vo.*;
import com.doumee.service.business.NoticeService;
import com.doumee.service.business.OrderLogService;
import com.doumee.service.business.OrdersService;
import com.doumee.dao.business.model.Notice;
import com.doumee.service.business.AreasService;
import com.doumee.service.business.PricingRuleService;
import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.xiaoymin.knife4j.core.util.CollectionUtils;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
 * 寄存订单信息Service实现
 * @author rk
 * @date 2026/04/10
 */
@Slf4j
@Service
public class OrdersServiceImpl implements OrdersService {
@@ -104,8 +104,7 @@
    @Autowired
    private RevenueMapper revenueMapper;
    @Autowired
    private WxMiniUtilService wxMiniUtilService;
    @Autowired
    private SystemUserMapper systemUserMapper;
@@ -127,6 +126,18 @@
    @Autowired
    private AreasService areasService;
    @Autowired
    private NoticeService noticeService;
    @Autowired
    private SmsrecordMapper smsrecordMapper;
    @Autowired
    private WxPayV3Service wxPayV3Service;
    @Autowired
    private WxPayProperties wxPayProperties;
    @Override
    public Integer create(Orders orders) {
@@ -218,6 +229,7 @@
        queryWrapper.eq(pageWrap.getModel().getTakeShopId() != null, Orders::getTakeShopId, pageWrap.getModel().getTakeShopId());
        queryWrapper.and(pageWrap.getModel().getDriverKeyword() != null, i->i.like(DriverInfo::getName, pageWrap.getModel().getDriverKeyword())
                .or().like(DriverInfo::getTelephone, pageWrap.getModel().getDriverKeyword()));
        queryWrapper.eq(pageWrap.getModel().getSettlementStatus() != null, Orders::getSettlementStatus, pageWrap.getModel().getSettlementStatus());
        for (PageWrap.SortData sortData : pageWrap.getSorts()) {
            if (sortData.getDirection().equalsIgnoreCase(PageWrap.DESC)) {
                queryWrapper.orderByDesc(sortData.getProperty());
@@ -251,6 +263,7 @@
        queryWrapper.eq(pageWrap.getModel().getTakeShopId() != null, Orders::getTakeShopId, pageWrap.getModel().getTakeShopId());
        queryWrapper.and(pageWrap.getModel().getDriverKeyword() != null, i->i.like(DriverInfo::getName, pageWrap.getModel().getDriverKeyword())
                .or().like(DriverInfo::getTelephone, pageWrap.getModel().getDriverKeyword()));
        queryWrapper.eq(pageWrap.getModel().getSettlementStatus() != null, Orders::getSettlementStatus, pageWrap.getModel().getSettlementStatus());
        queryWrapper.select(
                "IFNULL(SUM(t.total_amount), 0) as total_amount_sum",
@@ -289,7 +302,7 @@
            return BigDecimal.ZERO;
        }
        String rateStr = systemDictDataBiz.queryByCode(Constants.OPERATION_CONFIG, Constants.OP_INSURANCE_RATE).getCode();
        BigDecimal rate = new BigDecimal(rateStr);
        BigDecimal rate = new BigDecimal(rateStr).divide(new BigDecimal("100"), 4, BigDecimal.ROUND_HALF_UP);
        return declaredValue.multiply(rate).setScale(2, BigDecimal.ROUND_HALF_UP);
    }
@@ -337,9 +350,11 @@
        List<Category> categories = categoryMapper.selectBatchIds(categoryIds);
        Map<Integer, String> categoryNameMap = new HashMap<>();
        Map<Integer, String> categoryDetailMap = new HashMap<>();
        Map<Integer, String> categoryOtherFieldMap = new HashMap<>();
        for (Category c : categories) {
            categoryNameMap.put(c.getId(), c.getName());
            categoryDetailMap.put(c.getId(), c.getDetail());
            categoryOtherFieldMap.put(c.getId(),c.getOtherField());
        }
        // 计算每项物品费用:小计 = 单价 × 数量 × 天数
@@ -359,7 +374,7 @@
            ItemPriceVO vo = new ItemPriceVO();
            vo.setCategoryId(item.getCategoryId());
            vo.setCategoryName(categoryNameMap.getOrDefault(item.getCategoryId(), ""));
            vo.setDetail(categoryDetailMap.get(item.getCategoryId()));
            vo.setDetail(categoryOtherFieldMap.get(item.getCategoryId()));
            vo.setQuantity(item.getQuantity());
            vo.setUnitPrice(unitPrice);
            vo.setLocallyPrice(unitPrice);
@@ -413,10 +428,13 @@
        // 1. 调用腾讯地图距离矩阵API计算驾车距离
        String from = dto.getFromLat() + "," + dto.getFromLgt();
        String to = dto.getToLat() + "," + dto.getToLgt();
        JSONObject distanceResult = MapUtil.distanceSingle("driving", from, to);
        JSONObject distanceResult = MapUtil.direction("driving", from, to);
        BigDecimal distance = distanceResult.getBigDecimal("distance");
        // distance 单位为米,转为公里
        // distance 单位为米,转为公里(不足1公里按1公里计算)
        BigDecimal distanceKm = distance.divide(new BigDecimal(1000), 2, RoundingMode.HALF_UP);
        if (distanceKm.compareTo(BigDecimal.ONE) < 0) {
            distanceKm = BigDecimal.ONE;
        }
        // 收集所有物品类型ID
        List<Integer> categoryIds = new ArrayList<>();
@@ -454,9 +472,11 @@
        List<Category> categories = categoryMapper.selectBatchIds(categoryIds);
        Map<Integer, String> categoryNameMap = new HashMap<>();
        Map<Integer, String> categoryDetailMap = new HashMap<>();
        Map<Integer, String> categoryOtherFieldMap = new HashMap<>();
        for (Category c : categories) {
            categoryNameMap.put(c.getId(), c.getName());
            categoryDetailMap.put(c.getId(), c.getDetail());
            categoryOtherFieldMap.put(c.getId(),c.getOtherField());
        }
        // 3. 逐项计算运费:起步价 + 超出部分阶梯价
@@ -495,7 +515,7 @@
            ItemPriceVO vo = new ItemPriceVO();
            vo.setCategoryId(item.getCategoryId());
            vo.setCategoryName(categoryNameMap.getOrDefault(item.getCategoryId(), ""));
            vo.setDetail(categoryDetailMap.get(item.getCategoryId()));
            vo.setDetail(categoryOtherFieldMap.get(item.getCategoryId()));
            vo.setQuantity(item.getQuantity());
            vo.setUnitPrice(unitPrice);
            vo.setLocallyPrice(locallyPrice);
@@ -518,16 +538,17 @@
        // 5. 加急费用:物品价格 × 加急系数(字典 URGENT_COEFFICIENT)
        long urgentFeeFen = 0L;
        if (Boolean.TRUE.equals(dto.getUrgent())) {
            String urgentRateStr = systemDictDataBiz.queryByCode(
                    Constants.OPERATION_CONFIG, Constants.OP_URGENT_COEFFICIENT).getCode();
            BigDecimal urgentRate = new BigDecimal(urgentRateStr);
            urgentFeeFen = new BigDecimal(itemPriceTotal).multiply(urgentRate)
                    .setScale(0, RoundingMode.HALF_UP).longValue();
        }
        String urgentRateStr = systemDictDataBiz.queryByCode(
                Constants.OPERATION_CONFIG, Constants.OP_URGENT_COEFFICIENT).getCode();
        BigDecimal urgentRate = new BigDecimal(urgentRateStr);
        urgentFeeFen = new BigDecimal(itemPriceTotal).multiply(urgentRate)
                .setScale(0, RoundingMode.HALF_UP).longValue();
        // 6. 总价格 = 物品价格 + 保价费用 + 加急费用
        long totalPrice = itemPriceTotal + insuranceFeeFen + urgentFeeFen;
        // 6. 总价格 = 物品价格 + 保价费用 + 加急费用(加急时才包含加急费)
        long totalPrice = itemPriceTotal + insuranceFeeFen;
        if (Boolean.TRUE.equals(dto.getUrgent())) {
            totalPrice += urgentFeeFen;
        }
        PriceCalculateVO result = new PriceCalculateVO();
        result.setItemList(itemList);
@@ -639,8 +660,11 @@
        // ========== 3. 查询寄件店铺信息 ==========
        ShopInfo depositShop = shopInfoMapper.selectById(dto.getDepositShopId());
        if (depositShop == null) {
        if (depositShop == null || Constants.equalsInteger(depositShop.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "寄件店铺不存在");
        }
        if (depositShop.getStatus() == null || !Constants.equalsInteger(depositShop.getStatus(), Constants.ZERO)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "寄件店铺已停业,请选择其他门店");
        }
        // ========== 4. 计算费用 ==========
@@ -657,18 +681,24 @@
            }
            // 取件点:店铺 or 自选点,至少提供一组
            if (dto.getTakeShopId() != null) {
                if (dto.getTakeShopId().equals(dto.getDepositShopId())) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "异地寄存订单存件门店和取件门店不能相同");
                }
                takeShop = shopInfoMapper.selectById(dto.getTakeShopId());
                if (takeShop == null) {
                if (takeShop == null || Constants.equalsInteger(takeShop.getDeleted(), Constants.ONE)) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "取件店铺不存在");
                }
                if (takeShop.getStatus() == null || Constants.equalsInteger(takeShop.getStatus(), Constants.ONE)) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "取件店铺已停业,请选择其他门店");
                }
                takeLat = BigDecimal.valueOf(takeShop.getLatitude());
                takeLgt = BigDecimal.valueOf(takeShop.getLongitude());
                takeLocationValue = takeShop.getAddress();
            } else if (dto.getTakeLat() != null && dto.getTakeLgt() != null && StringUtils.isNotBlank(dto.getTakeLocation())) {
                // 无取件门店,校验存件点与自选取件点是否在同一城市
                if (MapUtil.isSameCity(depositShop.getLatitude(), depositShop.getLongitude(),
                if (!MapUtil.isSameCity(depositShop.getLatitude(), depositShop.getLongitude(),
                        dto.getTakeLat().doubleValue(), dto.getTakeLgt().doubleValue())) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "异地寄存订单存取点不能在同一城市,如需同城寄存请选择就近门店");
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "异地寄存订单存取点不在同一城市,如需请选择同城门店");
                }
                takeLat = dto.getTakeLat();
                takeLgt = dto.getTakeLgt();
@@ -758,6 +788,7 @@
            orders.setTakeLat(takeLat);
            orders.setTakeLgt(takeLgt);
            orders.setIsUrgent(dto.getIsUrgent());
            orders.setDistance(priceResult.getDistance());
        } else {
            // 就地:取件点同寄件店铺
            orders.setTakeShopId(dto.getDepositShopId());
@@ -769,7 +800,12 @@
        // 物品信息
        orders.setGoodType(dto.getGoodType());
        orders.setGoodLevel(goodTypeCategory.getRelationId());
        // 查询物品级别 type = 3
        Category levelCategory = categoryMapper.selectById(goodTypeCategory.getRelationId());
        if(Objects.isNull(levelCategory)){
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(),"未查询到物品等级信息:{}"+goodTypeCategory.getName());
        }
        orders.setGoodLevel(Integer.valueOf(levelCategory.getDetail()));
        // 拼接物品信息:物品类型名称、尺寸名称*数量(数组字符串)
        List<String> goodsParts = new ArrayList<>();
        for (ItemPriceVO itemVO : priceResult.getItemList()) {
@@ -791,9 +827,12 @@
        }
        orders.setDeclaredFee(priceResult.getInsuranceFee());
        orders.setPrice(priceResult.getItemPrice());
        // 薪酬计算与占比存储
        calculateAndSetFeeAllocation(orders, depositShop, takeShop);
        // 无人接单通知相关初始化
        orders.setPlatformSmsNotified(Constants.ZERO);
        orders.setPlatformSmsNotifiedTime(now);
        ordersMapper.insert(orders);
        Integer orderId = orders.getId();
@@ -836,7 +875,8 @@
        if (member == null || StringUtils.isBlank(member.getOpenid())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付");
        }
        PayResponse payResponse = wxPay(orders, member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER);
        PayResponse payResponse = wxPayV3(orders.getOutTradeNo(), orders.getTotalAmount(), orders.getId(),
                member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER);
        payResponse.setLockKey(lockKey);
        return payResponse;
    }
@@ -864,6 +904,7 @@
        // 5. 重新生成第三方订单编号(避免重复)
        String orderTradeNo = generateOrderTradeNo();
        orders.setOutTradeNo(orderTradeNo);
        orders.setPlatformSmsNotifiedTime(new Date());
        orders.setUpdateTime(new Date());
        ordersMapper.updateById(orders);
        // 6. 唤起微信支付
@@ -871,7 +912,8 @@
        if (member == null || StringUtils.isBlank(member.getOpenid())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付");
        }
        return wxPay(orders, member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER);
        return wxPayV3(orders.getOutTradeNo(), orders.getTotalAmount(), orders.getId(),
                member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER);
    }
    /**
@@ -906,7 +948,32 @@
        }
    }
    /**
     * 唤起微信支付V3
     *
     * @param outTradeNo   商户订单号
     * @param totalCents   支付金额(分)
     * @param orderId      订单主键
     * @param openid       用户微信openid
     * @param ordersAttach 订单支付类型
     * @return PayResponse 包含微信调起参数和订单主键
     */
    private PayResponse wxPayV3(String outTradeNo, Long totalCents, Integer orderId,
                                String openid, Constants.OrdersAttach ordersAttach) {
        Map<String, String> payParams = wxPayV3Service.createOrder(
                outTradeNo,
                ordersAttach.getName(),
                totalCents != null ? totalCents : 0L,
                openid,
                wxPayProperties.getV3NotifyUrl(),
                ordersAttach.getKey()
        );
        PayResponse payResponse = new PayResponse();
        payResponse.setResponse(payParams);
        payResponse.setOrderId(orderId);
        return payResponse;
    }
@@ -980,9 +1047,6 @@
        // 取消/退款状态时查询退款记录
        Integer status = order.getStatus();
        if (status != null && (status == Constants.OrderStatus.overdue.getStatus()
                || status == Constants.OrderStatus.closed.getStatus()
                || status == Constants.OrderStatus.cancelOverdue.getStatus()
                || status == Constants.OrderStatus.cancelling.getStatus()
                || status == Constants.OrderStatus.cancelled.getStatus())) {
            OrdersRefund ordersRefund = ordersRefundMapper.selectOne(
                    new QueryWrapper<OrdersRefund>().lambda()
@@ -1047,13 +1111,12 @@
        String optUserName = getCurrentUserName();
        // 加急费日志(每次单独记录本次加急费)
        Constants.OrderLogType urgentLogType = Constants.OrderLogType.urgent;
        // 加急费日志
        OrderLog feeLog = new OrderLog();
        feeLog.setOrderId(order.getId());
        feeLog.setTitle(urgentLogType.getTitle());
        feeLog.setLogInfo(urgentLogType.getStatusInfo().replace("{param}", dto.getUrgentFee().toPlainString()));
        feeLog.setObjType(urgentLogType.getStatus());
        feeLog.setTitle(Constants.OrderLogType.urgent.getTitle());
        feeLog.setLogInfo(Constants.OrderLogType.urgent.format(dto.getUrgentFee().toPlainString()));
        feeLog.setObjType(Constants.OrderLogType.urgent.getStatus());
        feeLog.setOrderStatus(order.getStatus());
        feeLog.setOptUserType(3);
        feeLog.setOptUserName(optUserName);
@@ -1070,6 +1133,8 @@
                .eq(Orders::getId, order.getId())
                .set(Orders::getIsUrgent, Constants.ONE)
                .set(Orders::getPlatformRewardAmount, urgentFeeFen)
                .set(Orders::getPlatformSmsNotified, Constants.ZERO) // 重置通知状态为未通知
                .set(Orders::getPlatformSmsNotifiedTime, new Date()) // 重置通知基准时间为当前
                .set(Orders::getUpdateTime, new Date());
        // 异地寄存且有取件门店时,生成司机核销码
@@ -1090,18 +1155,37 @@
            Member driver = memberMapper.selectById(dto.getDriverId());
            String driverName = driver != null ? driver.getName() : String.valueOf(dto.getDriverId());
            Constants.OrderLogType dispatchLogType = Constants.OrderLogType.dispatch;
            OrderLog driverLog = new OrderLog();
            driverLog.setOrderId(order.getId());
            driverLog.setTitle(dispatchLogType.getTitle());
            driverLog.setLogInfo(dispatchLogType.getStatusInfo().replace("{param}", driverName));
            driverLog.setObjType(dispatchLogType.getStatus());
            driverLog.setTitle(Constants.OrderLogType.assignDriver.getTitle());
            driverLog.setLogInfo(Constants.OrderLogType.assignDriver.format(driverName, dto.getUrgentFee().toPlainString()));
            driverLog.setObjType(Constants.OrderLogType.assignDriver.getStatus());
            driverLog.setOrderStatus(order.getStatus());
            driverLog.setOptUserType(3);
            driverLog.setOptUserName(optUserName);
            driverLog.setCreateTime(new Date());
            driverLog.setDeleted(Constants.ZERO);
            orderLogService.create(driverLog);
            // 短信通知指派司机(加急派单)
            DriverInfo driverInfo = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                    .eq(DriverInfo::getMemberId, dto.getDriverId())
                    .eq(DriverInfo::getDeleted, Constants.ZERO)
                    .last("limit 1"));
            if (driverInfo != null) {
                String address1 = order.getDepositShopAddress() != null ? order.getDepositShopAddress() : order.getDepositShopName();
                String address2 = order.getTakeShopAddress() != null ? order.getTakeShopAddress() :
                        (order.getTakeLocation() != null ? order.getTakeLocation() : "");
                // 配送费 = 司机配送费 + 加急费
                long totalDriverFee = (order.getDriverFee() != null ? order.getDriverFee() : 0L) + urgentFeeFen;
                sendSmsNotify(driverInfo.getTelephone(),
                        Constants.SmsNotify.DRIVER_URGENT_DISPATCH,
                        "orderNo", order.getCode(),
                        "address1", address1,
                        "address2", address2,
                        "money1", String.valueOf(totalDriverFee / 100.0),
                        "money2", String.valueOf(urgentFeeFen / 100.0));
            }
        }
        ordersMapper.update(updateWrapper);
@@ -1139,9 +1223,9 @@
                item.setLuggageName(d.getLuggageName());
                item.setLuggageDetail(d.getLuggageDetail());
                item.setNum(d.getNum());
                double unitPriceYuan = d.getUnitPrice() != null ? Constants.getFormatMoney(d.getUnitPrice()) : 0;
                item.setUnitPriceYuan(unitPriceYuan);
                item.setSubtotal(unitPriceYuan * (d.getNum() != null ? d.getNum() : 0));
                long unitPriceFen = d.getUnitPrice() != null ? d.getUnitPrice() : 0L;
                item.setUnitPrice(unitPriceFen);
                item.setSubtotal(unitPriceFen * (d.getNum() != null ? d.getNum() : 0));
                items.add(item);
            }
        }
@@ -1240,14 +1324,17 @@
        // 寄件门店占比:fieldA=0(企业寄)/1(个人寄)
        int depositFieldA = Constants.equalsInteger(depositShop.getCompanyType(), Constants.ONE) ? Constants.ZERO : Constants.ONE;
        BigDecimal depositShopRata = getRevenueShareRata(cityId, depositFieldA);
        // 取件门店占比:fieldA=2(企业取)/3(个人取)
        int takeFieldA = Constants.equalsInteger(takeShop.getCompanyType(), Constants.ONE) ? Constants.TWO : Constants.THREE;
        BigDecimal takeShopRata = getRevenueShareRata(cityId, takeFieldA);
        // 取件门店占比:无取件门店时比例为0
        BigDecimal takeShopRata = BigDecimal.ZERO;
        if (takeShop != null) {
            int takeFieldA = Constants.equalsInteger(takeShop.getCompanyType(), Constants.ONE) ? Constants.TWO : Constants.THREE;
            takeShopRata = getRevenueShareRata(cityId, takeFieldA);
        }
        // 计算薪酬(分):totalAmount 为分,rata 为比例值(如 0.15 表示 15%)
        long driverFee = new BigDecimal(totalAmount).multiply(driverRata).longValue();
        long depositShopFee = new BigDecimal(totalAmount).multiply(depositShopRata).longValue();
        long takeShopFee = totalAmount - driverFee - depositShopFee;
        long takeShopFee = new BigDecimal(totalAmount).multiply(takeShopRata).longValue();
        orders.setDriverFee(driverFee);
        orders.setDepositShopFee(depositShopFee);
@@ -1271,8 +1358,9 @@
                .eq(PricingRule::getCityId, cityId)
                .eq(PricingRule::getFieldA, String.valueOf(fieldA))
                .last("limit 1"));
        if (rule != null && StringUtils.isNotBlank(rule.getFieldC())) {
            return new BigDecimal(rule.getFieldC());
        if (rule != null && StringUtils.isNotBlank(rule.getFieldB())) {
            // fieldB 存储的是百分比整数(如15表示15%),转换为小数比例(0.15)
            return new BigDecimal(rule.getFieldB()).divide(new BigDecimal("100"), 4, BigDecimal.ROUND_HALF_UP);
        }
        return BigDecimal.ZERO;
    }
@@ -1303,8 +1391,12 @@
                .select("s1.link_phone", Orders::getDepositShopLinkPhone)
                .select("s2.name", Orders::getTakeShopName)
                .select("s2.address", Orders::getTakeShopAddress)
                .select("s2.link_phone", Orders::getTakeShopLinkPhone)
                .select("d.name", Orders::getDriverName)
                .select("d.telephone", Orders::getDriverPhone)
                .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID")
                .leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID")
                .leftJoin("driver_info d on d.id = t.ACCEPT_DRIVER")
                .eq(Orders::getDeleted, Constants.ZERO)
                .eq(Orders::getMemberId, memberId)
                .eq(status != null, Orders::getStatus, status)
@@ -1321,22 +1413,33 @@
                vo.setCode(o.getCode());
                vo.setType(o.getType());
                vo.setStatus(o.getStatus());
                vo.setStatusName(Constants.OrderStatus.getDescByKey(o.getStatus(),
                        Constants.equalsInteger(o.getType(), Constants.ZERO)?o.getType():Objects.nonNull(o.getTakeShopId())?Constants.ONE:Constants.TWO)
                );
                vo.setCreateTime(o.getCreateTime());
                vo.setExpectedTakeTime(o.getExpectedTakeTime());
                vo.setMemberVerifyCode(o.getMemberVerifyCode());
                // 存件门店(关联查询直接取值)
                vo.setDepositShopId(o.getDepositShopId());
                vo.setDepositShopName(o.getDepositShopName());
                vo.setDepositShopLinkName(o.getDepositShopLinkName());
                vo.setDepositShopPhone(o.getDepositShopLinkPhone());
                // 取件信息:有取件门店取门店,无则取用户自选取件点
                if (o.getTakeShopId() != null) {
                    vo.setTakeShopId(o.getTakeShopId());
                    vo.setTakeShopName(o.getTakeShopName());
                    vo.setTakeShopAddress(o.getTakeShopAddress());
                    vo.setTakeShopPhone(o.getTakeShopLinkPhone());
                } else {
                    vo.setTakeLocation(o.getTakeLocation());
                    vo.setTakeLocationRemark(o.getTakeLocationRemark());
                }
                // 司机信息
                vo.setDriverName(o.getDriverName());
                vo.setDriverPhone(o.getDriverPhone());
                // 取件联系人
                vo.setTakeUser(o.getTakeUser());
@@ -1345,6 +1448,9 @@
                // 费用(分)
                vo.setDeclaredFee(o.getDeclaredFee());
                vo.setEstimatedAmount(o.getEstimatedAmount());
                // 评价状态
                vo.setCommentStatus(o.getCommentStatus());
                // 查询物品明细(一次查询,同时用于物品列表和逾期计算)
                List<OrdersDetail> details = ordersDetailMapper.selectList(
@@ -1355,13 +1461,8 @@
                // 物品明细
                vo.setDetailList(buildDetailList(details));
                // 逾期信息(仅待取件状态计算)
                if (Integer.valueOf(Constants.OrderStatus.arrived.getStatus()).equals(o.getStatus())) {
                    OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(o, details);
                    vo.setOverdue(overdueInfo.getOverdue());
                    vo.setOverdueDays(overdueInfo.getOverdueDays());
                    vo.setOverdueFee(overdueInfo.getOverdueFee());
                }
                // 逾期状态
                fillOverdueStatus(vo, o, details);
                voList.add(vo);
            }
        }
@@ -1401,12 +1502,30 @@
                .select("s1.link_phone", Orders::getDepositShopLinkPhone)
                .select("s2.name", Orders::getTakeShopName)
                .select("s2.address", Orders::getTakeShopAddress)
                .select("s2.link_phone", Orders::getTakeShopLinkPhone)
                .select("d.name", Orders::getDriverName)
                .select("d.telephone", Orders::getDriverPhone)
                .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID")
                .leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID")
                .eq(Orders::getPayStatus, Constants.ONE)
                .and(w -> w.eq(Orders::getDepositShopId, shopId).or().eq(Orders::getTakeShopId, shopId))
                .eq(status != null, Orders::getStatus, status)
                .in(statusList != null, Orders::getStatus, statusList)
                .leftJoin("driver_info d on d.id = t.ACCEPT_DRIVER")
                .eq(Orders::getPayStatus, Constants.ONE);
        // 门店待处理订单:按业务环节区分门店角色
        if (combinedStatus != null && Constants.equalsInteger(combinedStatus, Constants.SEVEN)) {
            wrapper.and(w -> w
                    .and(w1 -> w1.eq(Orders::getDepositShopId, shopId)
                            .in(Orders::getStatus, Constants.OrderStatus.waitDeposit.getStatus(),
                                    Constants.OrderStatus.deposited.getStatus()))
                    .or(w2 -> w2.eq(Orders::getTakeShopId, shopId)
                            .in(Orders::getStatus, Constants.OrderStatus.delivering.getStatus(),
                                    Constants.OrderStatus.arrived.getStatus()))
            );
        } else {
            wrapper.and(w -> w.eq(Orders::getDepositShopId, shopId).or().eq(Orders::getTakeShopId, shopId));
        }
        wrapper.eq(status != null, Orders::getStatus, status)
                .in(statusList != null && !Constants.equalsInteger(combinedStatus, Constants.SEVEN), Orders::getStatus, statusList)
                .orderByDesc(Orders::getCreateTime);
        IPage<Orders> orderPage = ordersMapper.selectJoinPage(p, Orders.class, wrapper);
@@ -1418,7 +1537,11 @@
                vo.setCode(o.getCode());
                vo.setType(o.getType());
                vo.setStatus(o.getStatus());
                vo.setStatusName(Constants.OrderStatus.getDescByKey(o.getStatus(),
                        Constants.equalsInteger(o.getType(), Constants.ZERO)?o.getType():Objects.nonNull(o.getTakeShopId())?Constants.ONE:Constants.TWO)
                );
                vo.setCreateTime(o.getCreateTime());
                vo.setRemark(o.getRemark());
                vo.setExpectedTakeTime(o.getExpectedTakeTime());
                vo.setDepositShopName(o.getDepositShopName());
@@ -1435,10 +1558,15 @@
                if (o.getTakeShopId() != null) {
                    vo.setTakeShopName(o.getTakeShopName());
                    vo.setTakeShopAddress(o.getTakeShopAddress());
                    vo.setTakeShopPhone(o.getTakeShopLinkPhone());
                } else {
                    vo.setTakeLocation(o.getTakeLocation());
                    vo.setTakeLocationRemark(o.getTakeLocationRemark());
                }
                // 司机信息
                vo.setDriverName(o.getDriverName());
                vo.setDriverPhone(o.getDriverPhone());
                vo.setTakeUser(o.getTakeUser());
                vo.setTakePhone(o.getTakePhone());
@@ -1452,12 +1580,16 @@
                vo.setDetailList(buildDetailList(details));
                if (Integer.valueOf(Constants.OrderStatus.arrived.getStatus()).equals(o.getStatus())) {
                    OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(o, details);
                    vo.setOverdue(overdueInfo.getOverdue());
                    vo.setOverdueDays(overdueInfo.getOverdueDays());
                    vo.setOverdueFee(overdueInfo.getOverdueFee());
                }
                // 逾期状态
                fillOverdueStatus(vo, o, details);
//                if (Integer.valueOf(Constants.OrderStatus.arrived.getStatus()).equals(o.getStatus())) {
//                    OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(o, details);
//                    vo.setOverdue(overdueInfo.getOverdue());
//                    vo.setOverdueDays(overdueInfo.getOverdueDays());
//                    vo.setOverdueFee(overdueInfo.getOverdueFee());
//                }
                voList.add(vo);
            }
        }
@@ -1503,7 +1635,7 @@
            }
        }
        if (order == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在");
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "核销码无效");
        }
        return buildOrderDetailVO(order, false);
    }
@@ -1527,13 +1659,15 @@
        vo.setExpectedDepositTime(order.getExpectedDepositTime());
        vo.setExpectedTakeTime(order.getExpectedTakeTime());
        vo.setArriveTime(order.getArriveTime());
        vo.setStatusName(Constants.OrderStatus.getDescByKey(order.getStatus(),order.getType()));
        // 费用(分)
        vo.setBasicAmount(order.getBasicAmount());
        vo.setDeclaredAmount(order.getDeclaredAmount());
        vo.setDeclaredFee(order.getDeclaredFee());
        vo.setUrgentAmount(order.getUrgentAmount());
        vo.setActualPayAmount(order.getPayAmount());
        vo.setIsUrgent(order.getIsUrgent());
        vo.setActualPayAmount(Constants.equalsInteger(order.getPayStatus(), Constants.ONE)?order.getPayAmount():order.getEstimatedAmount());
        // 标记
        vo.setExceptionStatus(order.getExceptionStatus());
@@ -1560,6 +1694,7 @@
        // 取件信息
        if (order.getTakeShopId() != null) {
            vo.setTakeShopId(order.getTakeShopId());
            ShopInfo takeShop = shopInfoMapper.selectById(order.getTakeShopId());
            if (takeShop != null) {
                vo.setTakeShopName(takeShop.getName());
@@ -1573,6 +1708,16 @@
        // 取件联系人
        vo.setTakeUser(order.getTakeUser());
        vo.setTakePhone(order.getTakePhone());
        // 司机信息
        if (order.getAcceptDriver() != null) {
            DriverInfo driver = driverInfoMapper.selectById(order.getAcceptDriver());
            if (driver != null) {
                vo.setDriverId(driver.getId());
                vo.setDriverName(driver.getName());
                vo.setDriverPhone(driver.getTelephone());
            }
        }
        // 物品类型名称
        if (order.getGoodType() != null) {
@@ -1593,11 +1738,21 @@
                        .eq(OrdersDetail::getDeleted, Constants.ZERO));
        vo.setDetailList(buildDetailList(details));
        // 逾期信息
        OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(order, details);
        vo.setOverdue(overdueInfo.getOverdue());
        vo.setOverdueDays(overdueInfo.getOverdueDays());
        vo.setOverdueFee(overdueInfo.getOverdueFee());
        // 逾期状态:0=未到店未逾期 1=未到店存在逾期 2=已到店未逾期 3=已到店待支付逾期 4=逾期已支付
        fillOverdueStatus(vo, order, details);
        // 退款信息(status=99取消时返回)
        if (order.getStatus() != null &&
                Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.cancelled.getStatus())) {
            vo.setRefundApplyTime(order.getCancelTime());
            OrdersRefund ordersRefund = ordersRefundMapper.selectOne(
                    new QueryWrapper<OrdersRefund>().lambda()
                            .eq(OrdersRefund::getOrderId, order.getId())
                            .eq(OrdersRefund::getDeleted, Constants.ZERO)
                            .orderByDesc(OrdersRefund::getCreateTime)
                            .last("limit 1"));
            vo.setRefundInfo(ordersRefund);
        }
        // 核销码
        Integer status = order.getStatus();
@@ -1619,6 +1774,68 @@
            vo.setMemberVerifyCode(order.getMemberVerifyCode());
        }
        // 异地寄存经纬度(就地寄存不返回)
        if (Constants.ONE.equals(order.getType())) {
            // status=3(已接单):返回存件门店经纬度 + 司机经纬度
            if (Constants.equalsInteger(status, Constants.OrderStatus.accepted.getStatus())) {
                ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId());
                if (depositShop != null) {
                    vo.setDepositShopLng(depositShop.getLongitude());
                    vo.setDepositShopLat(depositShop.getLatitude());
                }
                if (order.getAcceptDriver() != null) {
                    DriverInfo driver = driverInfoMapper.selectById(order.getAcceptDriver());
                    if (driver != null) {
                        vo.setDriverLng(driver.getLongitude());
                        vo.setDriverLat(driver.getLatitude());
                    }
                }
            }
            // status=4(配送中):返回取件点经纬度 + 司机经纬度
            if (Constants.equalsInteger(status, Constants.OrderStatus.delivering.getStatus())) {
                // 取件点经纬度(优先取件门店,否则订单上的取件坐标)
                if (order.getTakeShopId() != null) {
                    ShopInfo takeShop = shopInfoMapper.selectById(order.getTakeShopId());
                    if (takeShop != null) {
                        vo.setTakeLng(takeShop.getLongitude());
                        vo.setTakeLat(takeShop.getLatitude());
                    }
                } else if (order.getTakeLgt() != null && order.getTakeLat() != null) {
                    vo.setTakeLng(order.getTakeLgt().doubleValue());
                    vo.setTakeLat(order.getTakeLat().doubleValue());
                }
                if (order.getAcceptDriver() != null) {
                    DriverInfo driver = driverInfoMapper.selectById(order.getAcceptDriver());
                    if (driver != null) {
                        vo.setDriverLng(driver.getLongitude());
                        vo.setDriverLat(driver.getLatitude());
                    }
                }
            }
        }
        // 评价信息
        vo.setCommentStatus(order.getCommentStatus());
        if (Constants.equalsInteger(order.getCommentStatus(), Constants.ONE)) {
            vo.setCommentTime(order.getCommentTime());
            // 查询评价记录,获取各对象评分
            List<OrderComment> comments = orderCommentMapper.selectList(new QueryWrapper<OrderComment>().lambda()
                    .eq(OrderComment::getOrderId, order.getId())
                    .eq(OrderComment::getDeleted, Constants.ZERO));
            for (OrderComment c : comments) {
                if (Constants.equalsInteger(c.getTargetType(), Constants.ONE)) {
                    vo.setDepositScore(c.getScore());
                    vo.setCommentContent(c.getContent());
                } else if (Constants.equalsInteger(c.getTargetType(), Constants.TWO)) {
                    vo.setTakeScore(c.getScore());
                } else if (Constants.equalsInteger(c.getTargetType(), Constants.THREE)) {
                    vo.setDriverScore(c.getScore());
                }
            }
            // 评价附件图片
            vo.setCommentImages(getFileUrls(order.getId(), Constants.FileType.COMMENT_ATTACH.getKey(), imgPrefix));
        }
        return vo;
    }
@@ -1633,11 +1850,6 @@
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        // 仅异地寄存可取消
        if (!Constants.equalsInteger(order.getType(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅异地寄存订单可取消");
        }
        Integer status = order.getStatus();
        if (status == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单状态异常");
@@ -1645,17 +1857,48 @@
        Date now = new Date();
        // 待支付:直接取消
        // 待支付:直接取消(不限订单类型)
        if (Constants.equalsInteger(status, Constants.OrderStatus.waitPay.getStatus())) {
            order.setStatus(Constants.OrderStatus.cancelled.getStatus());
            order.setCancelTime(now);
            ordersMapper.updateById(order);
            saveCancelLog(order, "会员取消订单(待支付)", reason, memberId);
            saveCancelLog(order, Constants.OrderLogType.memberCancel, "会员取消订单(待支付)", memberId);
            // 短信通知会员:订单已取消
            Member cancelMember1 = memberMapper.selectById(memberId);
            sendSmsNotify(cancelMember1 != null ? cancelMember1.getTelephone() : null,
                    Constants.SmsNotify.MEMBER_CANCELLED, "orderNo", order.getCode());
            return;
        }
        // 待寄存:直接取消,全额退款
        // 待寄存:直接取消,全额退款(不限订单类型)
        if (Constants.equalsInteger(status, Constants.OrderStatus.waitDeposit.getStatus())) {
            // 先标记订单已取消
            order.setStatus(Constants.OrderStatus.cancelled.getStatus());
            order.setCancelTime(now);
            order.setRefundAmount(order.getPayAmount());
            ordersMapper.updateById(order);
            saveCancelLog(order, Constants.OrderLogType.memberCancel, "会员取消订单(待寄存,全额退款)", memberId);
            // 通知会员:已取消
            sendOrderNotice(memberId, Constants.MemberOrderNotify.CANCELLED, orderId,
                    "orderNo", order.getCode());
            // 短信通知会员:订单已取消
            Member cancelMember2 = memberMapper.selectById(memberId);
            sendSmsNotify(cancelMember2 != null ? cancelMember2.getTelephone() : null,
                    Constants.SmsNotify.MEMBER_CANCELLED, "orderNo", order.getCode());
            // 调用微信退款V3,全额退款
            com.wechat.pay.java.service.refund.model.Refund refundResult;
            try {
                refundResult = wxPayV3Service.refund(order.getOutTradeNo(), order.getPayAmount(), order.getPayAmount(),
                        "订单退款", wxPayProperties.getV3RefundNotifyUrl());
            } catch (Exception e) {
                log.error("待寄存订单退款调用异常, orderId={}", orderId, e);
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "退款失败,请稍后重试");
            }
            com.wechat.pay.java.service.refund.model.Status refundStatus = refundResult.getStatus();
            // 记录退款信息
            OrdersRefund refund = new OrdersRefund();
            refund.setOrderId(orderId);
@@ -1663,29 +1906,66 @@
            refund.setCancelInfo(reason);
            refund.setCreateTime(now);
            refund.setDeleted(Constants.ZERO);
            refund.setBeforeStatus(Constants.OrderStatus.waitDeposit.getStatus());
            refund.setRefundAmount(order.getPayAmount());
            refund.setRefundCode(refundResult.getOutRefundNo());
            // 调用微信退款,全额退款
            String refundCode = wxMiniUtilService.wxRefund(order.getOutTradeNo(), order.getPayAmount(), order.getPayAmount());
            refund.setRefundCode(refundCode);
            refund.setRefundTime(new Date());
            if (com.wechat.pay.java.service.refund.model.Status.SUCCESS.equals(refundStatus)) {
                // 退款成功
                refund.setStatus(Constants.ONE);
            } else if (com.wechat.pay.java.service.refund.model.Status.PROCESSING.equals(refundStatus)) {
                // 退款中,等回调处理
                refund.setStatus(Constants.ZERO);
            } else {
                // 退款失败/异常(CLOSED / ABNORMAL / 其他)
                log.error("待寄存订单退款失败, orderId={}, refundStatus={}", orderId, refundStatus);
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "退款失败,请联系客服处理");
            }
            ordersRefundMapper.insert(refund);
            order.setStatus(Constants.OrderStatus.cancelled.getStatus());
            order.setCancelTime(now);
            order.setRefundAmount(order.getPayAmount());
            ordersMapper.updateById(order);
            saveCancelLog(order, "会员取消订单(待寄存,全额退款)", reason, memberId);
            return;
        }
        // 已寄存/已接单:进入取消中状态
        // 已寄存/已接单:仅异地寄存可取消
        if (!Constants.equalsInteger(order.getType(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅异地寄存订单可取消");
        }
        // 已寄存/已接单:直接将订单类型改为就地寄存
        if (Constants.equalsInteger(status, Constants.OrderStatus.deposited.getStatus())
                || Constants.equalsInteger(status, Constants.OrderStatus.accepted.getStatus())) {
            order.setStatus(Constants.OrderStatus.cancelling.getStatus());
            order.setCancelTime(now);
            ordersMapper.updateById(order);
            saveCancelLog(order, "会员申请取消订单(已寄存/已接单)", reason, memberId);
            // 先保存原司机信息,用于后续通知
            Integer originalDriverId = order.getAcceptDriver();
            DriverInfo originalDriver = originalDriverId != null ? driverInfoMapper.selectById(originalDriverId) : null;
            ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId());
            // 取件点信息更新为存件门店,使用 UpdateWrapper 确保 null 字段也能生效
            UpdateWrapper<Orders> updateWrapper = new UpdateWrapper<>();
            updateWrapper.lambda()
                    .eq(Orders::getId, order.getId())
                    .set(Orders::getType, Constants.ZERO)
                    .set(Orders::getTakeShopId, order.getDepositShopId())
                    .set(Orders::getTakeShopName, order.getDepositShopName())
                    .set(Orders::getTakeShopAddress, order.getDepositShopAddress())
                    .set(Orders::getTakeShopLinkPhone, order.getDepositShopLinkPhone())
                    .set(Orders::getTakeLocation, order.getDepositLocation())
                    .set(Orders::getTakeLocationRemark, order.getDepositLocationRemark())
                    .set(Orders::getTakeLat, order.getDepositLat())
                    .set(Orders::getTakeLgt, order.getDepositLgt());
            if (Constants.equalsInteger(status, Constants.OrderStatus.accepted.getStatus())) {
                updateWrapper.lambda()
                        .set(Orders::getAcceptDriver, null)
                        .set(Orders::getAcceptType, null)
                        .set(Orders::getAcceptTime, null);
            }
            ordersMapper.update(null, updateWrapper);
            saveCancelLog(order, Constants.OrderLogType.memberCancelToConvert, "会员取消异地寄存订单,转为就地寄存", memberId);
            // 通知司机:订单已取消(已接单情况下司机需停止服务)
            if (originalDriverId != null && Constants.equalsInteger(status, Constants.OrderStatus.accepted.getStatus())) {
                sendDriverNotice(originalDriverId, Constants.DriverOrderNotify.REFUNDING, orderId,
                        "orderNo", order.getCode());
                sendSmsNotify(originalDriver != null ? originalDriver.getTelephone() : null,
                        Constants.SmsNotify.DRIVER_REFUNDING, "orderNo", order.getCode());
            }
            return;
        }
@@ -1695,12 +1975,12 @@
    /**
     * 保存取消订单操作日志
     */
    private void saveCancelLog(Orders order, String title, String reason, Integer memberId) {
    private void saveCancelLog(Orders order, Constants.OrderLogType logType, String reason, Integer memberId) {
        OrderLog log = new OrderLog();
        log.setOrderId(order.getId());
        log.setTitle(title);
        log.setLogInfo(reason);
        log.setObjType(Constants.ORDER_LOG_CANCEL);
        log.setTitle(logType.getTitle());
        log.setLogInfo(logType.format(reason));
        log.setObjType(logType.getStatus());
        log.setOrderStatus(order.getStatus());
        log.setOptUserId(memberId);
        log.setOptUserType(0); // 0=用户
@@ -1712,18 +1992,86 @@
    /**
     * 保存门店核销日志
     */
    private void saveShopVerifyLog(Orders order, String title, String logInfo, String remark, Integer shopId) {
    private void saveShopVerifyLog(Orders order, Constants.OrderLogType logType, String logInfo, String remark, Integer shopId) {
        OrderLog log = new OrderLog();
        log.setOrderId(order.getId());
        log.setTitle(title);
        log.setLogInfo(logInfo);
        log.setTitle(logType.getTitle());
        log.setLogInfo(logInfo != null ? logInfo : logType.getStatusInfo());
        log.setRemark(remark);
        log.setObjType(logType.getStatus());
        log.setOrderStatus(order.getStatus());
        log.setOptUserId(shopId);
        log.setOptUserType(2); // 2=门店
        log.setCreateTime(new Date());
        log.setDeleted(Constants.ZERO);
        orderLogService.create(log);
    }
    /**
     * 发送订单站内信通知
     */
    private void sendOrderNotice(Integer memberId, Constants.MemberOrderNotify notify, Integer orderId, String... params) {
        Notice notice = new Notice();
        notice.setUserType(0); // 0=会员
        notice.setUserId(memberId);
        notice.setTitle(notify.getTitle());
        notice.setContent(notify.format(params));
        notice.setObjId(orderId);
        notice.setObjType(0); // 0=订单
        notice.setStatus(0);  // 0=未读
        notice.setIsdeleted(Constants.ZERO);
        notice.setCreateDate(new Date());
        noticeService.create(notice);
    }
    /**
     * 发送门店站内信通知
     */
    private void sendShopNotice(Integer shopId, Constants.ShopOrderNotify notify, Integer orderId, String... params) {
        Notice notice = new Notice();
        notice.setUserType(2); // 2=门店
        notice.setUserId(shopId);
        notice.setTitle(notify.getTitle());
        notice.setContent(notify.format(params));
        notice.setObjId(orderId);
        notice.setObjType(0); // 0=订单
        notice.setStatus(0);  // 0=未读
        notice.setIsdeleted(Constants.ZERO);
        notice.setCreateDate(new Date());
        noticeService.create(notice);
    }
    /**
     * 通知存件门店和取件门店(订单完成/评价等)
     */
    private void notifyBothShops(Orders order, Constants.ShopOrderNotify notify, String... params) {
        if (order.getDepositShopId() != null) {
            sendShopNotice(order.getDepositShopId(), notify, order.getId(), params);
        }
        if (order.getTakeShopId() != null) {
            sendShopNotice(order.getTakeShopId(), notify, order.getId(), params);
        }
    }
    /**
     * 发送司机站内信通知
     */
    private void sendDriverNotice(Integer driverId, Constants.DriverOrderNotify notify, Integer orderId, String... params) {
        DriverInfo driver = driverInfoMapper.selectById(driverId);
        if (driver == null || driver.getMemberId() == null) {
            return;
        }
        Notice notice = new Notice();
        notice.setUserType(1); // 1=司机
        notice.setUserId(driver.getMemberId());
        notice.setTitle(notify.getTitle());
        notice.setContent(notify.format(params));
        notice.setObjId(orderId);
        notice.setObjType(0); // 0=订单
        notice.setStatus(0);  // 0=未读
        notice.setIsdeleted(Constants.ZERO);
        notice.setCreateDate(new Date());
        noticeService.create(notice);
    }
    @Override
@@ -1745,6 +2093,7 @@
        order.setPayStatus(Constants.ONE); // 已支付
        order.setPayTime(now);
        order.setWxExternalNo(wxTradeNo);
        order.setPayAmount(order.getTotalAmount());
        order.setUpdateTime(now);
        // 生成会员核销码
        order.setMemberVerifyCode(generateVerifyCode());
@@ -1767,6 +2116,26 @@
            }
        }
        ordersMapper.updateById(order);
        // 通知会员:订单待核验
        sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_VERIFY, order.getId(),
                "orderNo", order.getCode(),
                "storeCode", order.getMemberVerifyCode());
        // 就地寄存订单:通知存件门店待核验
        if (Constants.ZERO.equals(order.getType()) && order.getDepositShopId() != null) {
            sendShopNotice(order.getDepositShopId(), Constants.ShopOrderNotify.WAIT_VERIFY, order.getId(),
                    "orderNo", order.getCode());
        }
        // 短信通知存件门店:有新订单待核验
        if (order.getDepositShopId() != null) {
            ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId());
            if (depositShop != null) {
                sendSmsNotify(depositShop.getLinkPhone(), Constants.SmsNotify.SHOP_WAIT_VERIFY,
                        "orderNo", order.getCode());
            }
        }
    }
    @Override
@@ -1810,8 +2179,9 @@
        otherOrders.setCreateTime(now);
        otherOrdersMapper.insert(otherOrders);
        // 5. 唤起微信支付
        return wxPayForOtherOrder(otherOrders, member.getOpenid(), Constants.OrdersAttach.OVERDUE_FEE);
        // 5. 唤起微信支付V3
        return wxPayV3(otherOrders.getOutTradeNo(), otherOrders.getPayAccount(), otherOrders.getId(),
                member.getOpenid(), Constants.OrdersAttach.OVERDUE_FEE);
    }
    @Override
@@ -1863,11 +2233,10 @@
        if (!Constants.equalsInteger(order.getMemberId(), memberId)) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "无权操作此订单");
        }
        // 仅已完成(7)、已取消(99)、已退款(96)可删除
        // 仅已完成(7)、已取消(99)
        int status = Constants.formatIntegerNum(order.getStatus());
        if (status != Constants.OrderStatus.finished.getStatus()
                && status != Constants.OrderStatus.cancelled.getStatus()
                && status != Constants.OrderStatus.closed.getStatus()) {
                && status != Constants.OrderStatus.cancelled.getStatus()) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "当前订单状态不可删除");
        }
        ordersMapper.update(new UpdateWrapper<Orders>().lambda()
@@ -1878,9 +2247,10 @@
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public PayResponse payShopDeposit(Integer shopId) {
    public PayResponse payShopDeposit(Integer memberId) {
        // 1. 查询门店信息
        ShopInfo shopInfo = shopInfoMapper.selectById(shopId);
        ShopInfo shopInfo = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda()
                .eq(ShopInfo::getRegionMemberId,memberId));
        if (shopInfo == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "门店不存在");
        }
@@ -1904,14 +2274,15 @@
        otherOrders.setMemberId(shopInfo.getRegionMemberId());
        otherOrders.setPayAccount(shopInfo.getDepositAmount());
        otherOrders.setPayStatus(Constants.ZERO);
        otherOrders.setCode("SD" + new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(now) + shopId);
        otherOrders.setCode("SD" + new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(now) + shopInfo.getId());
        otherOrders.setOutTradeNo(outTradeNo);
        otherOrders.setDeleted(Constants.ZERO);
        otherOrders.setCreateTime(now);
        otherOrdersMapper.insert(otherOrders);
        // 5. 唤起微信支付
        return wxPayForOtherOrder(otherOrders, member.getOpenid(), Constants.OrdersAttach.SHOP_DEPOSIT);
        // 5. 唤起微信支付V3
        return wxPayV3(otherOrders.getOutTradeNo(), otherOrders.getPayAccount(), otherOrders.getId(),
                member.getOpenid(), Constants.OrdersAttach.SHOP_DEPOSIT);
    }
    @Override
@@ -1957,6 +2328,15 @@
        }
        shopInfo.setUpdateTime(now);
        shopInfoMapper.updateById(shopInfo);
        // 短信通知门店:成功入驻
        String rawPassword = shopInfo.getTelephone() != null && shopInfo.getTelephone().length() >= 6
                ? shopInfo.getTelephone().substring(shopInfo.getTelephone().length() - 6) + "@123456" : "";
        sendSmsNotify(shopInfo.getTelephone(),
                Constants.SmsNotify.SHOP_AUTH_SUCCESS,
                "storeName", shopInfo.getName(),
                "phone", shopInfo.getTelephone() != null ? shopInfo.getTelephone() : "",
                "password", rawPassword);
        // 6. 押金支付完成后,若城市未开通则自动开通
        if (shopInfo.getAreaId() != null) {
@@ -2048,6 +2428,20 @@
                    }
                }
            }
            // 通知相关门店:订单已结算
            notifyBothShops(order, Constants.ShopOrderNotify.SETTLED,
                    "orderNo", order.getCode(),
                    "amount", String.valueOf(Constants.getFormatMoney(
                            order.getTotalAmount() != null ? order.getTotalAmount() : 0L)));
            // 通知司机:订单已结算
            if (order.getAcceptDriver() != null) {
                sendDriverNotice(order.getAcceptDriver(), Constants.DriverOrderNotify.SETTLED, order.getId(),
                        "orderNo", order.getCode(),
                        "amount", String.valueOf(Constants.getFormatMoney(
                                order.getDriverFee() != null ? order.getDriverFee() : 0L)));
            }
        }
    }
@@ -2136,6 +2530,9 @@
            orderCommentMapper.insert(driverComment);
        }
        // 4.4 保存评价附件图片(obj_type=15,最多3张)
        saveVerifyImages(order.getId(), dto.getImages(), Constants.FileType.COMMENT_ATTACH.getKey(), null);
        // 5. 更新门店/司机平均评分
        updateTargetScore(Constants.ONE, order.getDepositShopId());
        if (isRemote && order.getTakeShopId() != null) {
@@ -2143,6 +2540,20 @@
        }
        if (isRemote && order.getAcceptDriver() != null) {
            updateTargetScore(Constants.THREE, order.getAcceptDriver());
        }
        // 通知会员:订单已评价
        sendOrderNotice(memberId, Constants.MemberOrderNotify.EVALUATED, order.getId(),
                "orderNo", order.getCode());
        // 通知存件门店和取件门店:订单已评价
        notifyBothShops(order, Constants.ShopOrderNotify.EVALUATED,
                "orderNo", order.getCode());
        // 通知司机:订单已评价
        if (order.getAcceptDriver() != null) {
            sendDriverNotice(order.getAcceptDriver(), Constants.DriverOrderNotify.EVALUATED, order.getId(),
                    "orderNo", order.getCode());
        }
    }
@@ -2208,6 +2619,129 @@
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void verifyOrder(String verifyCode, Integer shopId, List<String> images, String remark) {
        if (StringUtils.isBlank(verifyCode)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "核销码不能为空");
        }
        // 先查会员核销码
        Orders query = new Orders();
        query.setMemberVerifyCode(verifyCode);
        query.setDeleted(Constants.ZERO);
        Orders byMemberCode = findOne(query);
        if (byMemberCode != null) {
            shopVerifyOrder(verifyCode, shopId, images, remark);
        } else {
            driverVerifyOrder(verifyCode, images, remark, shopId);
        }
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void verifyOrderByShopId(Integer orderId, Integer shopId, List<String> images, String remark) {
        if (orderId == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单主键不能为空");
        }
        // 根据订单主键查找订单
        Orders order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getId, orderId)
                .eq(Orders::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (order == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在");
        }
        // 查询门店名称用于日志
        String shopName = "";
        ShopInfo shopInfo = shopInfoMapper.selectById(shopId);
        if (shopInfo != null) {
            shopName = shopInfo.getName() != null ? shopInfo.getName() : "";
        }
        Integer status = order.getStatus();
        Date now = new Date();
        if (Constants.equalsInteger(status, Constants.OrderStatus.waitDeposit.getStatus())) {
            // 待寄存(1) → 已寄存(2),两种类型通用
            // 校验当前门店是否为订单的存件门店
            if (!shopId.equals(order.getDepositShopId())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店,无法核销");
            }
            order.setStatus(Constants.OrderStatus.deposited.getStatus());
            order.setDepositTime(now);
            // 释放当前核销码,生成新的核销码供取件时使用
            String verifyCode = order.getMemberVerifyCode();
            if (StringUtils.isNotBlank(verifyCode)) {
                releaseVerifyCode(verifyCode);
            }
            order.setMemberVerifyCode(generateVerifyCode());
            ordersMapper.updateById(order);
            // 保存寄存图片(obj_type=2 订单寄存图片,最多3张)
            saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_DEPOSIT.getKey(), shopId);
            // 记录订单日志
            saveShopVerifyLog(order, Constants.OrderLogType.shopDeposit, Constants.OrderLogType.shopDeposit.format(shopName), remark, shopId);
            // 通知会员:门店核销成功
            if (Constants.equalsInteger(order.getType(), Constants.ONE)) {
                // 异地寄存 → 待抢单
                sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_GRAB, order.getId(),
                        "orderNo", order.getCode());
            } else {
                // 就地寄存 → 待取件提醒
                sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_PICKUP_REMIND, order.getId(),
                        "orderNo", order.getCode(), "shopName", shopName);
            }
        } else if (Constants.equalsInteger(status, Constants.OrderStatus.arrived.getStatus())) {
            // 异地寄存 + 无取件门店 → 无法核销(客户自取,无门店操作)
            if (Constants.equalsInteger(order.getType(), Constants.ONE) && order.getTakeShopId() == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单无取件门店,无法核销");
            }
            // 校验取件门店与当前登录门店一致
            if (!shopId.equals(order.getTakeShopId())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店,无法核销");
            }
            // 校验是否已确认顾客到店
            if (order.getConfirmArriveTime() == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "请先确认顾客到店");
            }
            // 校验是否存在待处理的逾期费用
            if (Constants.equalsInteger(order.getOverdueStatus(), Constants.ONE)) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "存在逾期费用待处理,请先完成逾期费用支付");
            }
            // 待取件(5) → 已完成(7)
            order.setStatus(Constants.OrderStatus.finished.getStatus());
            order.setConfirmArriveTime(now);
            ordersMapper.updateById(order);
            // 订单完成,释放核销码
            String verifyCode = order.getMemberVerifyCode();
            if (StringUtils.isNotBlank(verifyCode)) {
                releaseVerifyCode(verifyCode);
            }
            // 保存出库图片(obj_type=13 门店出库图片,最多3张)
            saveVerifyImages(order.getId(), images, Constants.FileType.STORE_OUT.getKey(), shopId);
            // 生成收益记录
            calculateAndSaveOrderFees(order.getId());
            generateRevenueRecords(order.getId());
            // 记录订单日志
            saveShopVerifyLog(order, Constants.OrderLogType.shopTake, Constants.OrderLogType.shopTake.format(shopName), remark, shopId);
            // 通知会员:订单已完成
            sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.FINISHED, order.getId(),
                    "orderNo", order.getCode());
            // 通知存件门店和取件门店:订单已完成
            String settleDays = operationConfigBiz.getConfig().getSettlementDate();
            notifyBothShops(order, Constants.ShopOrderNotify.FINISHED,
                    "orderNo", order.getCode(),
                    "settleDays", settleDays != null ? settleDays : "7");
            // 通知司机:订单已完成
            if (order.getAcceptDriver() != null) {
                sendDriverNotice(order.getAcceptDriver(), Constants.DriverOrderNotify.FINISHED, order.getId(),
                        "orderNo", order.getCode(),
                        "settleDays", settleDays != null ? settleDays : "7");
            }
        } else {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许核销");
        }
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void shopVerifyOrder(String verifyCode, Integer shopId, List<String> images, String remark) {
        if (StringUtils.isBlank(verifyCode)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "核销码不能为空");
@@ -2245,7 +2779,17 @@
            // 保存寄存图片(obj_type=2 订单寄存图片,最多3张)
            saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_DEPOSIT.getKey(), shopId);
            // 记录订单日志
            saveShopVerifyLog(order, "门店确认寄存", "门店【" + shopName + "】确认寄存", remark, shopId);
            saveShopVerifyLog(order, Constants.OrderLogType.shopDeposit, Constants.OrderLogType.shopDeposit.format(shopName), remark, shopId);
            // 通知会员:门店核销成功
            if (Constants.equalsInteger(order.getType(), Constants.ONE)) {
                // 异地寄存 → 待抢单
                sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_GRAB, order.getId(),
                        "orderNo", order.getCode());
            } else {
                // 就地寄存 → 待取件提醒
                sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_PICKUP_REMIND, order.getId(),
                        "orderNo", order.getCode(), "shopName", shopName);
            }
        } else if (Constants.equalsInteger(status, Constants.OrderStatus.arrived.getStatus())) {
            // 异地寄存 + 无取件门店 → 无法核销(客户自取,无门店操作)
            if (Constants.equalsInteger(order.getType(), Constants.ONE) && order.getTakeShopId() == null) {
@@ -2254,6 +2798,14 @@
            // 校验取件门店与当前登录门店一致
            if (!shopId.equals(order.getTakeShopId())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店,无法核销");
            }
            // 校验是否已确认顾客到店
            if (order.getConfirmArriveTime() == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "请先确认顾客到店");
            }
            // 校验是否存在待处理的逾期费用
            if (Constants.equalsInteger(order.getOverdueStatus(), Constants.ONE)) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "存在逾期费用待处理,请先完成逾期费用支付");
            }
            // 待取件(5) → 已完成(7)
            order.setStatus(Constants.OrderStatus.finished.getStatus());
@@ -2267,7 +2819,21 @@
            calculateAndSaveOrderFees(order.getId());
            generateRevenueRecords(order.getId());
            // 记录订单日志
            saveShopVerifyLog(order, "门店确认取件", "门店【" + shopName + "】确认取件,订单完成", remark, shopId);
            saveShopVerifyLog(order, Constants.OrderLogType.shopTake, Constants.OrderLogType.shopTake.format(shopName), remark, shopId);
            // 通知会员:订单已完成
            sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.FINISHED, order.getId(),
                    "orderNo", order.getCode());
            // 通知存件门店和取件门店:订单已完成
            String settleDays = operationConfigBiz.getConfig().getSettlementDate();
            notifyBothShops(order, Constants.ShopOrderNotify.FINISHED,
                    "orderNo", order.getCode(),
                    "settleDays", settleDays != null ? settleDays : "7");
            // 通知司机:订单已完成
            if (order.getAcceptDriver() != null) {
                sendDriverNotice(order.getAcceptDriver(), Constants.DriverOrderNotify.FINISHED, order.getId(),
                        "orderNo", order.getCode(),
                        "settleDays", settleDays != null ? settleDays : "7");
            }
        } else {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许核销");
        }
@@ -2341,16 +2907,19 @@
            refundRecord.setType(3); // 出库退款
            refundRecord.setCreateTime(now);
            refundRecord.setRefundRemark(remark);
            refundRecord.setRefundAmount(order.getRefundAmount());
            refundRecord.setDeleted(Constants.ZERO);
            refundRecord.setBeforeStatus(order.getStatus());
            ordersRefundMapper.insert(refundRecord);
            // 调用微信退款(放在最后,确保前置操作全部成功)
            String refundCode = wxMiniUtilService.wxRefund(
                    order.getOutTradeNo(), order.getPayAmount(), order.getRefundAmount());
            // 调用微信退款V3(放在最后,确保前置操作全部成功)
            Refund refundResult = wxPayV3Service.refund(
                    order.getOutTradeNo(), order.getPayAmount(), order.getRefundAmount(),
                    "订单退款", wxPayProperties.getV3RefundNotifyUrl());
            // 退款成功后回填退款单号和时间
            refundRecord.setRefundCode(refundCode);
            refundRecord.setRefundTime(new Date());
            // 退款成功后回填退款单号,标记退款中
            refundRecord.setRefundCode(refundResult.getOutRefundNo());
            refundRecord.setStatus(Constants.ZERO); // 退款中
            ordersRefundMapper.updateById(refundRecord);
        }
@@ -2363,7 +2932,21 @@
        if (order.getRefundAmount() != null && order.getRefundAmount() > 0) {
            logInfo += ",退款" + Constants.getFormatMoney(order.getRefundAmount()) + "元";
        }
        saveShopVerifyLog(order, "门店确认出库", logInfo, remark, shopId);
        saveShopVerifyLog(order, Constants.OrderLogType.shopOutStock, logInfo, remark, shopId);
        // 通知会员:订单已完成
        sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.FINISHED, order.getId(),
                "orderNo", order.getCode());
        // 通知存件门店和取件门店:订单已完成
        String settleDays = operationConfigBiz.getConfig().getSettlementDate();
        notifyBothShops(order, Constants.ShopOrderNotify.FINISHED,
                "orderNo", order.getCode(),
                "settleDays", settleDays != null ? settleDays : "7");
        // 通知司机:订单已完成
        if (order.getAcceptDriver() != null) {
            sendDriverNotice(order.getAcceptDriver(), Constants.DriverOrderNotify.FINISHED, order.getId(),
                    "orderNo", order.getCode(),
                    "settleDays", settleDays != null ? settleDays : "7");
        }
    }
    @Override
@@ -2401,6 +2984,21 @@
        // 7. 生成收益记录
        calculateAndSaveOrderFees(orderId);
        generateRevenueRecords(orderId);
        // 通知会员:订单已完成
        sendOrderNotice(memberId, Constants.MemberOrderNotify.FINISHED, orderId,
                "orderNo", order.getCode());
        // 通知存件门店和取件门店:订单已完成
        String settleDays = operationConfigBiz.getConfig().getSettlementDate();
        notifyBothShops(order, Constants.ShopOrderNotify.FINISHED,
                "orderNo", order.getCode(),
                "settleDays", settleDays != null ? settleDays : "7");
        // 通知司机:订单已完成
        if (order.getAcceptDriver() != null) {
            sendDriverNotice(order.getAcceptDriver(), Constants.DriverOrderNotify.FINISHED, order.getId(),
                    "orderNo", order.getCode(),
                    "settleDays", settleDays != null ? settleDays : "7");
        }
    }
    @Override
@@ -2426,7 +3024,7 @@
        Long takeShopFee = 0L;
        Long driverFee = 0L;
        if (Constants.equalsInteger(order.getType(), Constants.TWO)) {
        if (Constants.equalsInteger(order.getType(), Constants.ONE)) {
            // 异地寄存:存件门店 + 司机
            driverFee = new BigDecimal(totalAmount)
                    .multiply(driverRate)
@@ -2552,6 +3150,35 @@
        // 保存附件(obj_type=3 门店入库图片,最多3张)
        saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_TAKE.getKey(), driverId);
        // 通知会员:订单已送达
        String destination = order.getTakeShopAddress() != null ? order.getTakeShopAddress() : "";
        if (order.getMemberVerifyCode() != null) {
            sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.ARRIVED_HAS_SHOP, order.getId(),
                    "orderNo", order.getCode(),
                    "destination", destination,
                    "pickupCode", order.getMemberVerifyCode());
        } else {
            sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.ARRIVED_NO_SHOP, order.getId(),
                    "orderNo", order.getCode(),
                    "destination", destination);
        }
        // 通知取件门店:订单已送达
        if (order.getTakeShopId() != null) {
            sendShopNotice(order.getTakeShopId(), Constants.ShopOrderNotify.ARRIVED, order.getId(),
                    "orderNo", order.getCode(),
                    "destination", destination);
        }
        // 短信通知会员:行李已送达
        Member arrivedMember = memberMapper.selectById(order.getMemberId());
        if (arrivedMember != null) {
            sendSmsNotify(arrivedMember.getTelephone(), Constants.SmsNotify.MEMBER_ARRIVED,
                    "orderNo", order.getCode(),
                    "address", destination,
                    "code", order.getMemberVerifyCode() != null ? order.getMemberVerifyCode() : "");
        }
    }
    /**
@@ -2637,9 +3264,9 @@
            ordersMapper.updateById(order);
            // 记录订单日志
            saveShopVerifyLog(order, "确认顾客到店(逾期)",
                    "门店【" + shopName + "】确认顾客到店,逾期" + overdueInfo.getOverdueDays()
                            + "天,逾期费用" + Constants.getFormatMoney(overdueInfo.getOverdueFee()) + "元",
            String overdueLogInfo = "门店【" + shopName + "】确认顾客到店,逾期" + overdueInfo.getOverdueDays()
                            + "天,逾期费用" + Constants.getFormatMoney(overdueInfo.getOverdueFee()) + "元";
            saveShopVerifyLog(order, Constants.OrderLogType.shopConfirmArriveOverdue, overdueLogInfo,
                    null, shopId);
        } else {
            // 未逾期:标记逾期状态为0,订单保持当前状态
@@ -2683,7 +3310,7 @@
            if (order.getRefundAmount() != null && order.getRefundAmount() > 0) {
                logInfo += ",需退款" + Constants.getFormatMoney(order.getRefundAmount()) + "元";
            }
            saveShopVerifyLog(order, "确认顾客到店", logInfo, null, shopId);
            saveShopVerifyLog(order, Constants.OrderLogType.shopConfirmArrive, logInfo, null, shopId);
        }
    }
@@ -2726,12 +3353,6 @@
        if (Constants.equalsInteger(status, Constants.OrderStatus.cancelled.getStatus())) {
            return "订单已取消,感谢您的支持,欢迎下次再会!";
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.cancelling.getStatus())) {
            return "退款申请已提交,平台会尽快为您处理退款";
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.closed.getStatus())) {
            return "退款已成功原路返回,请注意查收";
        }
        return "";
    }
@@ -2766,6 +3387,62 @@
    }
    /**
    /**
     * 填充逾期状态到 VO(MyOrderVO)
     * overdueStatus: 0=未到店未逾期 1=未到店存在逾期 2=已到店未逾期 3=已到店待支付逾期 4=逾期已支付
     */
    private void fillOverdueStatus(MyOrderVO vo, Orders order, List<OrdersDetail> details) {
        Integer[] result = calcOverdueStatus(order, details);
        vo.setOverdueStatus(result[0]);
        vo.setOverdue(result[1] == 1);
        vo.setOverdueDays(result[2]);
        vo.setOverdueFee(result[3].longValue());
    }
    /**
     * 填充逾期状态到 VO(MyOrderDetailVO)
     */
    private void fillOverdueStatus(MyOrderDetailVO vo, Orders order, List<OrdersDetail> details) {
        Integer[] result = calcOverdueStatus(order, details);
        vo.setOverdueStatus(result[0]);
        vo.setOverdue(result[1] == 1);
        vo.setOverdueDays(result[2]);
        vo.setOverdueFee(result[3].longValue());
    }
    /**
     * 计算逾期状态
     * @return [overdueStatus, isOverdue(0/1), overdueDays, overdueFee]
     */
    private Integer[] calcOverdueStatus(Orders order, List<OrdersDetail> details) {
        // 4=逾期已支付(订单 overdueStatus=2)
        if (Constants.equalsInteger(order.getOverdueStatus(), Constants.TWO)) {
            return new Integer[]{4, 1, order.getOverdueDays(), order.getOverdueAmount() != null ? order.getOverdueAmount().intValue() : 0};
        }
        // 3=已到店待支付逾期(订单 overdueStatus=1)
        if (Constants.equalsInteger(order.getOverdueStatus(), Constants.ONE)) {
            return new Integer[]{3, 1, order.getOverdueDays(), order.getOverdueAmount() != null ? order.getOverdueAmount().intValue() : 0};
        }
        // 计算实时逾期
        OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(order, details);
        boolean hasOverdue = overdueInfo.getOverdue() != null && overdueInfo.getOverdue()
                && overdueInfo.getOverdueFee() != null && overdueInfo.getOverdueFee() > 0;
        if (order.getConfirmArriveTime() != null) {
            // 已到店
            int days = hasOverdue ? overdueInfo.getOverdueDays() : 0;
            long fee = hasOverdue ? overdueInfo.getOverdueFee() : 0L;
            return new Integer[]{2, hasOverdue ? 1 : 0, days, (int) fee};
        } else {
            // 未到店
            if (hasOverdue) {
                return new Integer[]{1, 1, overdueInfo.getOverdueDays(), overdueInfo.getOverdueFee().intValue()};
            } else {
                return new Integer[]{0, 0, 0, 0};
            }
        }
    }
    /**
     * 逾期费用内部计算(不查库,接受预查询的数据)
     * 供分页等已查询明细的业务场景复用,避免重复查询
     */
@@ -2782,8 +3459,8 @@
        // 物品基础日费用 = Σ(单价 × 数量)
        long dailyBaseFee = 0L;
        for (OrdersDetail d : details) {
            dailyBaseFee += (d.getUnitPrice() != null ? d.getUnitPrice() : 0L)
                    * (d.getNum() != null ? d.getNum() : 0);
            dailyBaseFee += (d.getLocallyPrice() != null ? d.getLocallyPrice() : 0L)
                    * (d.getNum() != null ? d.getNum() : 1);
        }
        Date now = new Date();
@@ -2867,19 +3544,20 @@
    /**
     * 就地寄存逾期天数计算
     * 过了预计取件时间当天的12点后才算一天
     * 过了预计取件时间当天的24:00(次日00:00)后才算第一天
     */
    private int calcLocalOverdueDays(Date now, Date expectedTakeTime) {
        if (expectedTakeTime == null || !now.after(expectedTakeTime)) {
            return 0;
        }
        // 基准时间 = 预计取件日期的12:00
        // 基准时间 = 预计取件日期的次日 00:00(即当天24:00)
        Calendar baseCal = Calendar.getInstance();
        baseCal.setTime(expectedTakeTime);
        baseCal.set(Calendar.HOUR_OF_DAY, 12);
        baseCal.set(Calendar.HOUR_OF_DAY, 0);
        baseCal.set(Calendar.MINUTE, 0);
        baseCal.set(Calendar.SECOND, 0);
        baseCal.set(Calendar.MILLISECOND, 0);
        baseCal.add(Calendar.DAY_OF_MONTH, 1); // 次日00:00 = 当天24:00
        Date baseTime = baseCal.getTime();
        if (!now.after(baseTime)) {
@@ -2947,13 +3625,68 @@
    }
    @Override
    public ActiveOrderTipVO getActiveOrderTip(Integer memberId) {
        // 查询状态为 0~5 的最新一条订单
        QueryWrapper<Orders> wrapper = new QueryWrapper<>();
        wrapper.eq("MEMBER_ID", memberId)
                .in("STATUS", 0, 1, 2, 3, 4, 5)
                .orderByDesc("CREATE_TIME")
                .last("LIMIT 1");
        Orders order = ordersMapper.selectOne(wrapper);
        if (order == null) {
            return null;
        }
        ActiveOrderTipVO vo = new ActiveOrderTipVO();
        vo.setOrderId(order.getId());
        vo.setStatus(order.getStatus());
        // 构建提示文案
        boolean isLocal = Constants.equalsInteger(order.getType(), Constants.ZERO);
        Integer status = order.getStatus();
        String tip = null;
        if (Constants.equalsInteger(status, Constants.OrderStatus.waitPay.getStatus())) {
            // 待支付:提示支付倒计时
            String minutes = "";
            try {
                minutes = operationConfigBiz.getConfig().getAutoCancelTime();
            } catch (Exception ignored) {}
            tip = "请在" + (StringUtils.isNotBlank(minutes) ? minutes : "") + "分钟内完成支付,超时订单将自动取消";
            // 计算支付倒计时
            if (StringUtils.isNotBlank(minutes) && order.getCreateTime() != null) {
                long timeoutMs = Long.parseLong(minutes) * 60 * 1000;
                long deadline = order.getCreateTime().getTime() + timeoutMs;
                long remain = deadline - System.currentTimeMillis();
                vo.setPayCountdown(remain > 0 ? remain : -1L);
            } else {
                vo.setPayCountdown(-1L);
            }
        } else if (Constants.equalsInteger(status, Constants.OrderStatus.waitDeposit.getStatus())) {
            tip = "订单已支付,请前往门店寄存";
        } else if (Constants.equalsInteger(status, Constants.OrderStatus.deposited.getStatus())) {
            tip = isLocal ? "行李已寄存,请凭取件码前往指定门店取件" : "门店已接单,正在为您安排取件司机";
        } else if (Constants.equalsInteger(status, Constants.OrderStatus.accepted.getStatus())) {
            tip = isLocal ? "行李已寄存,请凭取件码前往指定门店取件" : "已有司机抢单,正前往取件地点";
        } else if (Constants.equalsInteger(status, Constants.OrderStatus.delivering.getStatus())) {
            tip = "司机已取件,正运往目的地";
        } else if (Constants.equalsInteger(status, Constants.OrderStatus.arrived.getStatus())) {
            tip = "行李已送达服务点,请及时前往取件";
        }
        vo.setTip(tip);
        return vo;
    }
    @Override
    public EstimatedDeliveryResultVO calculateEstimatedDelivery(Integer cityId,
                                                                Double fromLat, Double fromLng,
                                                                Double toLat, Double toLng) {
        // 腾讯地图距离矩阵API计算实际距离
        String from = fromLat + "," + fromLng;
        String to = toLat + "," + toLng;
        JSONObject distanceResult = MapUtil.distanceSingle("driving", from, to);
        JSONObject distanceResult = MapUtil.direction("driving", from, to);
        // 获取距离(米),转公里
        int distanceMeters = distanceResult.getIntValue("distance");
@@ -2971,4 +3704,293 @@
        return vo;
    }
    @Override
    public int cancelTimeoutUnpaidOrders() {
        // 获取超时配置(分钟)
        String autoCancelTimeStr = operationConfigBiz.getConfig().getAutoCancelTime();
        if (StringUtils.isBlank(autoCancelTimeStr)) {
            log.info("未配置超时取消时间,跳过");
            return 0;
        }
        int autoCancelMinutes;
        try {
            autoCancelMinutes = Integer.parseInt(autoCancelTimeStr);
        } catch (NumberFormatException e) {
            log.warn("超时取消时间配置异常: {}", autoCancelTimeStr);
            return 0;
        }
        if (autoCancelMinutes <= 0) {
            return 0;
        }
        // 查询所有超时未支付订单:status=0 且 创建时间 + 配置分钟数 < 当前时间
        Date deadline = new Date(System.currentTimeMillis() - (long) autoCancelMinutes * 60 * 1000);
        List<Orders> timeoutOrders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getStatus, Constants.OrderStatus.waitPay.getStatus())
                .eq(Orders::getDeleted, Constants.ZERO)
                .lt(Orders::getCreateTime, deadline));
        if (timeoutOrders == null || timeoutOrders.isEmpty()) {
            return 0;
        }
        int count = 0;
        Date now = new Date();
        for (Orders order : timeoutOrders) {
            try {
                order.setStatus(Constants.OrderStatus.cancelled.getStatus());
                order.setCancelTime(now);
                order.setUpdateTime(now);
                ordersMapper.updateById(order);
                // 写入操作日志
                OrderLog orderLog = new OrderLog();
                orderLog.setOrderId(order.getId());
                orderLog.setTitle(Constants.OrderLogType.systemCancel.getTitle());
                orderLog.setLogInfo(Constants.OrderLogType.systemCancel.format("订单超时" + autoCancelMinutes + "分钟未支付,系统自动取消"));
                orderLog.setObjType(Constants.OrderLogType.systemCancel.getStatus());
                orderLog.setOrderStatus(Constants.OrderStatus.cancelled.getStatus());
                orderLog.setOptUserType(3); // 3=系统
                orderLog.setCreateTime(now);
                orderLog.setDeleted(Constants.ZERO);
                orderLogService.create(orderLog);
                // 短信通知会员:订单已取消
                if (order.getMemberId() != null) {
                    Member member = memberMapper.selectById(order.getMemberId());
                    sendSmsNotify(member != null ? member.getTelephone() : null,
                            Constants.SmsNotify.MEMBER_CANCELLED, "orderNo", order.getCode());
                }
                count++;
            } catch (Exception e) {
                log.error("取消超时订单异常, orderId={}, error={}", order.getId(), e.getMessage());
            }
        }
        log.info("超时未支付订单自动取消完成,共取消{}单", count);
        return count;
    }
    @Override
    public int notifyUngrabbedOrders() {
        // 获取无人抢单通知时间配置(分钟)
        String noGrabTimeStr = operationConfigBiz.getConfig().getNoGrabNotifyTime();
        if (StringUtils.isBlank(noGrabTimeStr)) {
            return 0;
        }
        int noGrabMinutes;
        try {
            noGrabMinutes = Integer.parseInt(noGrabTimeStr);
        } catch (NumberFormatException e) {
            log.warn("无人抢单通知时间配置异常: {}", noGrabTimeStr);
            return 0;
        }
        if (noGrabMinutes <= 0) {
            return 0;
        }
        // 获取通知人员主键列表
        String noGrabUsers = operationConfigBiz.getConfig().getNoGrabNotifyUsers();
        if (StringUtils.isBlank(noGrabUsers)) {
            return 0;
        }
        List<String> userIdStrList = Arrays.asList(noGrabUsers.split(","));
        List<Integer> userIds = new ArrayList<>();
        for (String idStr : userIdStrList) {
            if (StringUtils.isNotBlank(idStr.trim())) {
                userIds.add(Integer.parseInt(idStr.trim()));
            }
        }
        if (userIds.isEmpty()) {
            return 0;
        }
        // 查询通知人员手机号
        List<String> notifyPhones = new ArrayList<>();
        for (Integer userId : userIds) {
            SystemUser user = systemUserMapper.selectById(userId);
            if (user != null && StringUtils.isNotBlank(user.getMobile())) {
                notifyPhones.add(user.getMobile());
            }
        }
        if (notifyPhones.isEmpty()) {
            log.warn("无人抢单通知人员均无有效手机号");
            return 0;
        }
        // 查询异地已寄存(status=2)、未通知(platformSmsNotified=0或null)、超时的订单
        Date deadline = new Date(System.currentTimeMillis() - (long) noGrabMinutes * 60 * 1000);
        List<Orders> ungrabbedOrders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getType, Constants.ONE) // 异地寄存
                .eq(Orders::getStatus, Constants.OrderStatus.deposited.getStatus()) // 已寄存
                .eq(Orders::getDeleted, Constants.ZERO)
                .ne(Orders::getPlatformSmsNotified, Constants.ONE) // 未通知
                .lt(Orders::getPlatformSmsNotifiedTime, deadline)); // 通知基准时间超过配置时间
        if (ungrabbedOrders == null || ungrabbedOrders.isEmpty()) {
            return 0;
        }
        int count = 0;
        Date now = new Date();
        for (Orders order : ungrabbedOrders) {
            try {
                // 给所有通知人员发短信
                for (String phone : notifyPhones) {
                    sendSmsNotify(phone, Constants.SmsNotify.PLATFORM_WAIT_GRAB,
                            "orderNo", order.getCode(),
                            "time", String.valueOf(noGrabMinutes));
                }
                // 标记已通知 + 记录通知时间
                order.setPlatformSmsNotified(Constants.ONE);
                order.setPlatformSmsNotifiedTime(now);
                order.setUpdateTime(now);
                ordersMapper.updateById(order);
                count++;
            } catch (Exception e) {
                log.error("无人抢单短信通知异常, orderId={}, error={}", order.getId(), e.getMessage());
            }
        }
        log.info("无人抢单短信通知完成,共通知{}单", count);
        return count;
    }
    @Override
    public int autoCompleteOrders() {
        // 获取自动确认收货天数配置
        String autoConfirmDaysStr = operationConfigBiz.getConfig().getAutoConfirmReceipt();
        if (StringUtils.isBlank(autoConfirmDaysStr)) {
            return 0;
        }
        int autoConfirmDays;
        try {
            autoConfirmDays = Integer.parseInt(autoConfirmDaysStr);
        } catch (NumberFormatException e) {
            log.warn("自动确认收货天数配置异常: {}", autoConfirmDaysStr);
            return 0;
        }
        if (autoConfirmDays <= 0) {
            return 0;
        }
        // 查询已送达(status=5)且送达时间超过配置天数的订单
        Date deadline = new Date(System.currentTimeMillis() - (long) autoConfirmDays * 24 * 60 * 60 * 1000);
        List<Orders> orders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getStatus, Constants.OrderStatus.arrived.getStatus())
                .eq(Orders::getDeleted, Constants.ZERO)
                .isNotNull(Orders::getArriveTime)
                .lt(Orders::getArriveTime, deadline));
        if (orders == null || orders.isEmpty()) {
            return 0;
        }
        int count = 0;
        Date now = new Date();
        for (Orders order : orders) {
            try {
                // 逾期未支付的不自动完成
                if (Constants.equalsInteger(order.getOverdueStatus(), Constants.ONE)) {
                    continue;
                }
                // 更新订单状态为已完成
                order.setStatus(Constants.OrderStatus.finished.getStatus());
                order.setFinishTime(now);
                order.setUpdateTime(now);
                ordersMapper.updateById(order);
                // 释放核销码
                if (StringUtils.isNotBlank(order.getMemberVerifyCode())) {
                    releaseVerifyCode(order.getMemberVerifyCode());
                }
                // 生成收益记录
                calculateAndSaveOrderFees(order.getId());
                generateRevenueRecords(order.getId());
                // 记录操作日志
                OrderLog orderLog = new OrderLog();
                orderLog.setOrderId(order.getId());
                orderLog.setTitle(Constants.OrderLogType.systemComplete.getTitle());
                orderLog.setLogInfo(Constants.OrderLogType.systemComplete.format("订单已送达超过" + autoConfirmDays + "天未确认,系统自动完成"));
                orderLog.setObjType(Constants.OrderLogType.systemComplete.getStatus());
                orderLog.setOrderStatus(Constants.OrderStatus.finished.getStatus());
                orderLog.setOptUserType(3); // 3=系统
                orderLog.setCreateTime(now);
                orderLog.setDeleted(Constants.ZERO);
                orderLogService.create(orderLog);
                // 通知会员:订单已完成
                sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.FINISHED, order.getId(),
                        "orderNo", order.getCode());
                // 通知存件门店和取件门店
                String settleDays = operationConfigBiz.getConfig().getSettlementDate();
                notifyBothShops(order, Constants.ShopOrderNotify.FINISHED,
                        "orderNo", order.getCode(),
                        "settleDays", settleDays != null ? settleDays : "7");
                // 通知司机
                if (order.getAcceptDriver() != null) {
                    sendDriverNotice(order.getAcceptDriver(), Constants.DriverOrderNotify.FINISHED, order.getId(),
                            "orderNo", order.getCode(),
                            "settleDays", settleDays != null ? settleDays : "7");
                }
                count++;
            } catch (Exception e) {
                log.error("自动完成订单异常, orderId={}, error={}", order.getId(), e.getMessage());
            }
        }
        log.info("自动完成超时订单完成,共完成{}单", count);
        return count;
    }
    /**
     * 发送短信通知(失败不影响主业务)
     * @param phone       接收手机号
     * @param smsNotify   短信模板枚举
     * @param paramPairs  模板参数,key-value 交替传入,如 "orderNo", "XL202604220001"
     */
    private void sendSmsNotify(String phone, Constants.SmsNotify smsNotify, String... paramPairs) {
        if (StringUtils.isBlank(phone)) {
            return;
        }
        String content = smsNotify.format(paramPairs);
        try {
            JSONObject templateParam = new JSONObject();
            for (int i = 0; i < paramPairs.length - 1; i += 2) {
                templateParam.put(paramPairs[i], paramPairs[i + 1]);
            }
            boolean result = AliSmsService.sendSms(phone, smsNotify.getTemplateCode(),
                    templateParam.toJSONString());
            if (result) {
                log.info("短信发送成功: phone={}, template={}", phone, smsNotify.name());
            } else {
                log.warn("短信发送失败: phone={}, template={}", phone, smsNotify.name());
            }
            // 存储短信记录
            Smsrecord record = new Smsrecord();
            record.setPhone(phone);
            record.setContent(content);
            record.setType(Constants.ONE); // 1=订单通知
            record.setStatus(result ? Constants.ONE : Constants.ZERO); // 1=已发送, 0=发送失败
            record.setCreateTime(new Date());
            record.setDeleted(Constants.ZERO);
            smsrecordMapper.insert(record);
        } catch (Exception e) {
            log.error("短信发送异常: phone={}, template={}, error={}", phone, smsNotify.name(), e.getMessage());
            // 异常也记录
            try {
                Smsrecord record = new Smsrecord();
                record.setPhone(phone);
                record.setContent(content);
                record.setType(Constants.ONE);
                record.setStatus(Constants.ZERO); // 发送失败
                record.setCreateTime(new Date());
                record.setDeleted(Constants.ZERO);
                smsrecordMapper.insert(record);
            } catch (Exception ignored) {}
        }
    }
}