rk
18 小时以前 095210f9149c73e6e00d997b39fd6c44a65e4d38
server/services/src/main/java/com/doumee/service/business/impl/OrdersServiceImpl.java
@@ -17,6 +17,7 @@
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.core.utils.DateUtil;
import com.doumee.core.utils.ID;
import com.doumee.core.utils.geocode.MapUtil;
import com.doumee.core.utils.Utils;
import com.doumee.core.utils.aliyun.AliSmsService;
@@ -221,14 +222,18 @@
        queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getDepositShopName()), "s1.name", pageWrap.getModel().getDepositShopName());
        queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getTakeShopName()),  "s2.name",  pageWrap.getModel().getTakeShopName());
        queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getGoodsInfo()), Orders::getGoodsInfo, pageWrap.getModel().getGoodsInfo());
        queryWrapper.ge(pageWrap.getModel().getCreateStartTime() != null, Orders::getCreateTime, Utils.Date.getStart(pageWrap.getModel().getCreateStartTime()));
        queryWrapper.le(pageWrap.getModel().getCreateEndTime() != null, Orders::getCreateTime, Utils.Date.getEnd(pageWrap.getModel().getCreateEndTime()));
        queryWrapper.ge(pageWrap.getModel().getCreateStartTime() != null, Orders::getCreateTime, pageWrap.getModel().getCreateStartTime());
        queryWrapper.le(pageWrap.getModel().getCreateEndTime() != null, Orders::getCreateTime, pageWrap.getModel().getCreateEndTime());
        queryWrapper.eq(pageWrap.getModel().getDepositShopId() != null, Orders::getDepositShopId, pageWrap.getModel().getDepositShopId());
        queryWrapper.eq(pageWrap.getModel().getType() != null, Orders::getType, pageWrap.getModel().getType());
        queryWrapper.eq(pageWrap.getModel().getStatus() != null, Orders::getStatus, pageWrap.getModel().getStatus());
        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.eq(pageWrap.getModel().getAcceptDriver() != null, Orders::getAcceptDriver, pageWrap.getModel().getAcceptDriver());
        queryWrapper.and(pageWrap.getModel().getShopId() != null, i -> i.eq(Orders::getDepositShopId, pageWrap.getModel().getShopId())
                .or().eq(Orders::getTakeShopId, pageWrap.getModel().getShopId()));
        for (PageWrap.SortData sortData : pageWrap.getSorts()) {
            if (sortData.getDirection().equalsIgnoreCase(PageWrap.DESC)) {
                queryWrapper.orderByDesc(sortData.getProperty());
@@ -236,7 +241,14 @@
                queryWrapper.orderByAsc(sortData.getProperty());
            }
        }
        return PageData.from(ordersMapper.selectJoinPage(page, Orders.class, queryWrapper));
        PageData<Orders> pageData = PageData.from(ordersMapper.selectJoinPage(page, Orders.class, queryWrapper));
        for (Orders o : pageData.getRecords()) {
            if (o.getStatus() != null) {
                Constants.OrderStatus os = Constants.OrderStatus.getByKey(o.getStatus());
                o.setStatusDesc(os != null ? os.getValue() : "");
            }
        }
        return pageData;
    }
    @Override
@@ -254,14 +266,18 @@
        queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getDepositShopName()), "s1.name", pageWrap.getModel().getDepositShopName());
        queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getTakeShopName()),  "s2.name",  pageWrap.getModel().getTakeShopName());
        queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getGoodsInfo()), Orders::getGoodsInfo, pageWrap.getModel().getGoodsInfo());
        queryWrapper.ge(pageWrap.getModel().getCreateStartTime() != null, Orders::getCreateTime, Utils.Date.getStart(pageWrap.getModel().getCreateStartTime()));
        queryWrapper.le(pageWrap.getModel().getCreateEndTime() != null, Orders::getCreateTime, Utils.Date.getEnd(pageWrap.getModel().getCreateEndTime()));
        queryWrapper.ge(pageWrap.getModel().getCreateStartTime() != null, Orders::getCreateTime, pageWrap.getModel().getCreateStartTime());
        queryWrapper.le(pageWrap.getModel().getCreateEndTime() != null, Orders::getCreateTime, pageWrap.getModel().getCreateEndTime());
        queryWrapper.eq(pageWrap.getModel().getDepositShopId() != null, Orders::getDepositShopId, pageWrap.getModel().getDepositShopId());
        queryWrapper.eq(pageWrap.getModel().getType() != null, Orders::getType, pageWrap.getModel().getType());
        queryWrapper.eq(pageWrap.getModel().getStatus() != null, Orders::getStatus, pageWrap.getModel().getStatus());
        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.eq(pageWrap.getModel().getAcceptDriver() != null, Orders::getAcceptDriver, pageWrap.getModel().getAcceptDriver());
        queryWrapper.and(pageWrap.getModel().getShopId() != null, i -> i.eq(Orders::getDepositShopId, pageWrap.getModel().getShopId())
                .or().eq(Orders::getTakeShopId, pageWrap.getModel().getShopId()));
        queryWrapper.select(
                "IFNULL(SUM(t.total_amount), 0) as total_amount_sum",
@@ -534,18 +550,26 @@
            insuranceFeeFen = insuranceFeeYuan.multiply(new BigDecimal(100)).longValue();
        }
        // 5. 加急费用:物品价格 × 加急系数(字典 URGENT_COEFFICIENT)
        // 5. 加急费用:物品价格 × (加急系数-1),如150%表示增加50%
        long urgentFeeFen = 0L;
        String urgentRateStr = systemDictDataBiz.queryByCode(
                Constants.OPERATION_CONFIG, Constants.OP_URGENT_COEFFICIENT).getCode();
        BigDecimal urgentRate = new BigDecimal(urgentRateStr);
        urgentFeeFen = new BigDecimal(itemPriceTotal).multiply(urgentRate)
        BigDecimal urgentIncreaseRate = urgentRate.subtract(BigDecimal.ONE);
        urgentFeeFen = new BigDecimal(itemPriceTotal).multiply(urgentIncreaseRate)
                .setScale(0, RoundingMode.HALF_UP).longValue();
        // 6. 总价格 = 物品价格 + 保价费用 + 加急费用(加急时才包含加急费)
        long totalPrice = itemPriceTotal + insuranceFeeFen;
        if (Boolean.TRUE.equals(dto.getUrgent())) {
            totalPrice += urgentFeeFen;
            // 加急时更新物品明细单价和小计
            for (ItemPriceVO vo : itemList) {
                long adjustedUnitPrice = new BigDecimal(vo.getUnitPrice()).multiply(urgentRate)
                        .setScale(0, RoundingMode.HALF_UP).longValue();
                vo.setUnitPrice(adjustedUnitPrice);
                vo.setSubtotal(adjustedUnitPrice * vo.getQuantity());
            }
        }
        PriceCalculateVO result = new PriceCalculateVO();
@@ -696,7 +720,7 @@
                // 无取件门店,校验存件点与自选取件点是否在同一城市
                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();
@@ -783,6 +807,7 @@
            // 异地:取件点信息
            orders.setTakeShopId(dto.getTakeShopId());
            orders.setTakeLocation(takeLocationValue);
            orders.setTakeLocationRemark(takeLocationValue);
            orders.setTakeLat(takeLat);
            orders.setTakeLgt(takeLgt);
            orders.setIsUrgent(dto.getIsUrgent());
@@ -791,6 +816,7 @@
            // 就地:取件点同寄件店铺
            orders.setTakeShopId(dto.getDepositShopId());
            orders.setTakeLocation(depositShop.getAddress());
            orders.setTakeLocationRemark(depositShop.getAddress());
            orders.setTakeLat(BigDecimal.valueOf(depositShop.getLatitude()));
            orders.setTakeLgt(BigDecimal.valueOf(depositShop.getLongitude()));
            orders.setIsUrgent(Constants.ZERO);
@@ -818,6 +844,12 @@
        orders.setEstimatedAmount(priceResult.getTotalPrice());
        orders.setTotalAmount(priceResult.getTotalPrice());
        orders.setUrgentAmount(priceResult.getUrgentFee());
        // 存储加急系数
        if (Constants.ONE.equals(dto.getType()) && Constants.ONE.equals(dto.getIsUrgent())) {
            String urgentRateStr = systemDictDataBiz.queryByCode(
                    Constants.OPERATION_CONFIG, Constants.OP_URGENT_COEFFICIENT).getCode();
            orders.setUrgentRata(new BigDecimal(urgentRateStr));
        }
        if (dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0) {
            orders.setDeclaredAmount(dto.getDeclaredAmount().multiply(new BigDecimal(100)).longValue());
        } else {
@@ -835,7 +867,27 @@
        ordersMapper.insert(orders);
        Integer orderId = orders.getId();
        // 创建订单日志
        OrderLog createLog = new OrderLog();
        createLog.setOrderId(orderId);
        createLog.setTitle(Constants.OrderLogType.createOrder.getTitle());
        createLog.setLogInfo(Constants.OrderLogType.createOrder.format(orderCode));
        createLog.setObjType(Constants.OrderLogType.createOrder.getStatus());
        createLog.setOrderStatus(orders.getStatus());
        createLog.setOptUserId(memberId);
        createLog.setOptUserType(0);
        createLog.setCreateTime(now);
        createLog.setDeleted(Constants.ZERO);
        orderLogService.create(createLog);
        // ========== 7. 创建订单明细 ==========
        // 异地寄存极速达时,按加急比例计算明细行单价
        BigDecimal urgentIncreaseRate = null;
        if (Constants.ONE.equals(dto.getType()) && Constants.ONE.equals(dto.getIsUrgent())) {
            String urgentRateStr = systemDictDataBiz.queryByCode(
                    Constants.OPERATION_CONFIG, Constants.OP_URGENT_COEFFICIENT).getCode();
            urgentIncreaseRate = new BigDecimal(urgentRateStr).subtract(BigDecimal.ONE);
        }
        for (ItemPriceVO itemVO : priceResult.getItemList()) {
            OrdersDetail detail = new OrdersDetail();
            detail.setOrderId(orderId);
@@ -843,11 +895,29 @@
            detail.setLuggageName(itemVO.getCategoryName());
            detail.setLuggageDetail(itemVO.getDetail());
            detail.setNum(itemVO.getQuantity());
            detail.setUnitPrice(itemVO.getUnitPrice());
            long detailUnitPrice = itemVO.getUnitPrice();
            if (urgentIncreaseRate != null) {
                detailUnitPrice = new BigDecimal(detailUnitPrice)
                        .multiply(BigDecimal.ONE.add(urgentIncreaseRate))
                        .setScale(0, RoundingMode.HALF_UP).longValue();
            }
            detail.setUnitPrice(detailUnitPrice);
            detail.setStartDistance(itemVO.getStartDistance());
            detail.setStartPrice(itemVO.getStartPrice());
            if (urgentIncreaseRate != null && itemVO.getStartPrice() != null) {
                detail.setStartPrice(new BigDecimal(itemVO.getStartPrice())
                        .multiply(BigDecimal.ONE.add(urgentIncreaseRate))
                        .setScale(0, RoundingMode.HALF_UP).longValue());
            } else {
                detail.setStartPrice(itemVO.getStartPrice());
            }
            detail.setExtraDistance(itemVO.getExtraDistance());
            detail.setExtraPrice(itemVO.getExtraPrice());
            if (urgentIncreaseRate != null && itemVO.getExtraPrice() != null) {
                detail.setExtraPrice(new BigDecimal(itemVO.getExtraPrice())
                        .multiply(BigDecimal.ONE.add(urgentIncreaseRate))
                        .setScale(0, RoundingMode.HALF_UP).longValue());
            } else {
                detail.setExtraPrice(itemVO.getExtraPrice());
            }
            detail.setLocallyPrice(itemVO.getLocallyPrice());
            detail.setDeleted(Constants.ZERO);
            detail.setCreateTime(now);
@@ -985,6 +1055,12 @@
        OrderDetailVO vo = new OrderDetailVO();
        vo.setOrder(order);
        // 订单状态描述
        if (order.getStatus() != null) {
            Constants.OrderStatus os = Constants.OrderStatus.getByKey(order.getStatus());
            vo.setStatusDesc(os != null ? os.getValue() : "");
        }
        // 图片路径前缀
        String imgPrefix = getOrdersPrefix();
@@ -1109,20 +1185,6 @@
        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.setOrderStatus(order.getStatus());
        feeLog.setOptUserType(3);
        feeLog.setOptUserName(optUserName);
        feeLog.setCreateTime(new Date());
        feeLog.setDeleted(Constants.ZERO);
        orderLogService.create(feeLog);
        // 加急费用 元→分
        long urgentFeeFen = dto.getUrgentFee().multiply(new BigDecimal(100)).longValue();
@@ -1147,25 +1209,70 @@
            updateWrapper.set(Orders::getRemark, dto.getRemark());
        }
        // 指派司机(非必填)
        // 日志:只存一条,有司机用 assignDriver,无司机用 urgent
        if (dto.getDriverId() != null) {
            // 校验司机信息
            DriverInfo driverInfo = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                    .eq(DriverInfo::getMemberId, dto.getDriverId())
                    .eq(DriverInfo::getDeleted, Constants.ZERO)
                    .last("limit 1"));
            if (driverInfo == null) {
                throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "司机信息不存在");
            }
            if (!Integer.valueOf(3).equals(driverInfo.getAuditStatus())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "司机未支付押金,无法派单");
            }
            if (!Constants.ZERO.equals(driverInfo.getStatus())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "司机已被禁用,无法派单");
            }
            // 更新接单司机信息及订单状态
            Date now = new Date();
            updateWrapper.set(Orders::getAssignDriverId, dto.getDriverId());
            updateWrapper.set(Orders::getAcceptDriver, dto.getDriverId());
            updateWrapper.set(Orders::getAcceptTime, now);
            updateWrapper.set(Orders::getAcceptType, 1); // 1=系统派单
            updateWrapper.set(Orders::getStatus, Constants.OrderStatus.accepted.getStatus());
            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.setOrderStatus(order.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(Constants.OrderStatus.accepted.getStatus());
            driverLog.setOptUserType(3);
            driverLog.setOptUserName(optUserName);
            driverLog.setCreateTime(new Date());
            driverLog.setCreateTime(now);
            driverLog.setDeleted(Constants.ZERO);
            orderLogService.create(driverLog);
            // 短信通知指派司机(加急派单)
            String address1 = order.getDepositLocationRemark();
            String address2 = order.getTakeLocationRemark();
            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));
        } else {
            // 未指派司机,只记录加急日志
            OrderLog feeLog = new OrderLog();
            feeLog.setOrderId(order.getId());
            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);
            feeLog.setCreateTime(new Date());
            feeLog.setDeleted(Constants.ZERO);
            orderLogService.create(feeLog);
        }
        ordersMapper.update(updateWrapper);
@@ -1371,8 +1478,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)
@@ -1407,10 +1518,15 @@
                    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());
@@ -1432,13 +1548,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);
            }
        }
@@ -1478,13 +1589,31 @@
                .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)
                .orderByDesc(Orders::getCreateTime);
                .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::getId);
        IPage<Orders> orderPage = ordersMapper.selectJoinPage(p, Orders.class, wrapper);
        List<MyOrderVO> voList = new ArrayList<>();
@@ -1499,6 +1628,7 @@
                        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());
@@ -1515,10 +1645,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());
@@ -1532,12 +1667,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);
            }
        }
@@ -1583,7 +1722,7 @@
            }
        }
        if (order == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在");
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "核销码无效");
        }
        return buildOrderDetailVO(order, false);
    }
@@ -1614,6 +1753,7 @@
        vo.setDeclaredAmount(order.getDeclaredAmount());
        vo.setDeclaredFee(order.getDeclaredFee());
        vo.setUrgentAmount(order.getUrgentAmount());
        vo.setIsUrgent(order.getIsUrgent());
        vo.setActualPayAmount(Constants.equalsInteger(order.getPayStatus(), Constants.ONE)?order.getPayAmount():order.getEstimatedAmount());
        // 标记
@@ -1685,66 +1825,20 @@
                        .eq(OrdersDetail::getDeleted, Constants.ZERO));
        vo.setDetailList(buildDetailList(details));
        Integer orderStatus = order.getStatus();
        if(Constants.equalsInteger(orderStatus, Constants.FIVE)){
            // 逾期信息
            OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(order, details);
            if (Constants.ONE.equals(order.getType())
                    && order.getTakeShopId() != null) {
                // 异地寄存 + 有取件门店:
                // 根据行李转移到店时间(arriveTime)当天晚上12点判断是否逾期
                if (order.getArriveTime() != null) {
                    Calendar arriveCal = Calendar.getInstance();
                    arriveCal.setTime(order.getArriveTime());
                    arriveCal.set(Calendar.HOUR_OF_DAY, 23);
                    arriveCal.set(Calendar.MINUTE, 59);
                    arriveCal.set(Calendar.SECOND, 59);
                    Date arriveEndOfDay = arriveCal.getTime();
                    boolean isOverdue = new Date().after(arriveEndOfDay);
                    vo.setOverdue(isOverdue);
                    if (isOverdue) {
                        vo.setOverdueDays(overdueInfo.getOverdueDays() > 0 ? overdueInfo.getOverdueDays() : 1);
                        vo.setOverdueFee(overdueInfo.getOverdueFee());
                    } else {
                        vo.setOverdueDays(0);
                        vo.setOverdueFee(0L);
                    }
                } else {
                    vo.setOverdue(false);
                    vo.setOverdueDays(0);
                    vo.setOverdueFee(0L);
                }
            } else if (Constants.ZERO.equals(order.getType())) {
                // 就地寄存:保持原逻辑
                vo.setOverdue(overdueInfo.getOverdue());
                vo.setOverdueDays(overdueInfo.getOverdueDays());
                vo.setOverdueFee(overdueInfo.getOverdueFee());
            }
        } else {
            vo.setOverdue(false);
            vo.setOverdueDays(0);
            vo.setOverdueFee(0L);
        }
        // 逾期状态:0=未到店未逾期 1=未到店存在逾期 2=已到店未逾期 3=已到店待支付逾期 4=逾期已支付
        fillOverdueStatus(vo, order, details);
        // 退款信息(status=96关闭/99取消时返回)
        if (orderStatus != null &&
                (Constants.equalsInteger(orderStatus, Constants.OrderStatus.cancelled.getStatus()))) {
        // 退款信息(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"));
            if (ordersRefund != null) {
                vo.setRefundAmount(ordersRefund.getRefundAmount() != null
                        ? ordersRefund.getRefundAmount() : order.getRefundAmount());
                vo.setRefundRemark(ordersRefund.getRemark());
                vo.setRefundTime(ordersRefund.getRefundTime());
            } else {
                vo.setRefundAmount(order.getRefundAmount());
            }
            vo.setRefundInfo(ordersRefund);
        }
        // 核销码
@@ -1767,46 +1861,17 @@
            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.setDepositShopLng(order.getDepositLgt().doubleValue());
        vo.setDepositShopLat(order.getDepositLat().doubleValue());
        // 取件点经纬度
        vo.setTakeLng(order.getTakeLgt().doubleValue());
        vo.setTakeLat(order.getTakeLat().doubleValue());
        //司机经纬度
        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)) {
@@ -1855,7 +1920,7 @@
            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,
@@ -1871,7 +1936,7 @@
            order.setRefundAmount(order.getPayAmount());
            ordersMapper.updateById(order);
            saveCancelLog(order, "会员取消订单(待寄存,全额退款)", reason, memberId);
            saveCancelLog(order, Constants.OrderLogType.memberCancel, "会员取消订单(待寄存,全额退款)", memberId);
            // 通知会员:已取消
            sendOrderNotice(memberId, Constants.MemberOrderNotify.CANCELLED, orderId,
                    "orderNo", order.getCode());
@@ -1881,9 +1946,10 @@
                    Constants.SmsNotify.MEMBER_CANCELLED, "orderNo", order.getCode());
            // 调用微信退款V3,全额退款
            String outRefundNo = ID.nextGUID();
            com.wechat.pay.java.service.refund.model.Refund refundResult;
            try {
                refundResult = wxPayV3Service.refund(order.getOutTradeNo(), order.getPayAmount(), order.getPayAmount(),
                refundResult = wxPayV3Service.refund(outRefundNo, order.getOutTradeNo(), order.getPayAmount(), order.getPayAmount(),
                        "订单退款", wxPayProperties.getV3RefundNotifyUrl());
            } catch (Exception e) {
                log.error("待寄存订单退款调用异常, orderId={}", orderId, e);
@@ -1901,7 +1967,7 @@
            refund.setDeleted(Constants.ZERO);
            refund.setBeforeStatus(Constants.OrderStatus.waitDeposit.getStatus());
            refund.setRefundAmount(order.getPayAmount());
            refund.setRefundCode(refundResult.getOutRefundNo());
            refund.setRefundCode(outRefundNo);
            if (com.wechat.pay.java.service.refund.model.Status.SUCCESS.equals(refundStatus)) {
                // 退款成功
@@ -1926,23 +1992,38 @@
        // 已寄存/已接单:直接将订单类型改为就地寄存
        if (Constants.equalsInteger(status, Constants.OrderStatus.deposited.getStatus())
                || Constants.equalsInteger(status, Constants.OrderStatus.accepted.getStatus())) {
            order.setType(Constants.ZERO); // 就地寄存
            ordersMapper.updateById(order);
            saveCancelLog(order, "会员取消异地寄存订单,转为就地寄存", reason, memberId);
            // 通知存件门店
            if (order.getDepositShopId() != null) {
                sendShopNotice(order.getDepositShopId(), Constants.ShopOrderNotify.REFUNDING, orderId,
                        "orderNo", order.getCode());
                ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId());
                sendSmsNotify(depositShop != null ? depositShop.getLinkPhone() : null,
                        Constants.SmsNotify.SHOP_REFUNDING, "orderNo", order.getCode());
            // 先保存原司机信息,用于后续通知
            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())
                    .set(Orders::getExpectedTakeTime, new Date());
            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 (order.getAcceptDriver() != null && Constants.equalsInteger(status, Constants.OrderStatus.accepted.getStatus())) {
                sendDriverNotice(order.getAcceptDriver(), Constants.DriverOrderNotify.REFUNDING, orderId,
            if (originalDriverId != null && Constants.equalsInteger(status, Constants.OrderStatus.accepted.getStatus())) {
                sendDriverNotice(originalDriverId, Constants.DriverOrderNotify.REFUNDING, orderId,
                        "orderNo", order.getCode());
                DriverInfo driver = driverInfoMapper.selectById(order.getAcceptDriver());
                sendSmsNotify(driver != null ? driver.getTelephone() : null,
                sendSmsNotify(originalDriver != null ? originalDriver.getTelephone() : null,
                        Constants.SmsNotify.DRIVER_REFUNDING, "orderNo", order.getCode());
            }
            return;
@@ -1954,12 +2035,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=用户
@@ -1971,12 +2052,13 @@
    /**
     * 保存门店核销日志
     */
    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=门店
@@ -2094,6 +2176,20 @@
            }
        }
        ordersMapper.updateById(order);
        // 支付成功日志
        OrderLog payLog = new OrderLog();
        payLog.setOrderId(order.getId());
        payLog.setTitle(Constants.OrderLogType.payOrder.getTitle());
        payLog.setLogInfo(Constants.OrderLogType.payOrder.format(
                String.valueOf(order.getTotalAmount() != null ? order.getTotalAmount() / 100.0 : 0)));
        payLog.setObjType(Constants.OrderLogType.payOrder.getStatus());
        payLog.setOrderStatus(order.getStatus());
        payLog.setOptUserId(order.getMemberId());
        payLog.setOptUserType(0);
        payLog.setCreateTime(now);
        payLog.setDeleted(Constants.ZERO);
        orderLogService.create(payLog);
        // 通知会员:订单待核验
        sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_VERIFY, order.getId(),
@@ -2228,6 +2324,7 @@
    public PayResponse payShopDeposit(Integer memberId) {
        // 1. 查询门店信息
        ShopInfo shopInfo = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda()
                .eq(ShopInfo::getDeleted,Constants.ZERO)
                .eq(ShopInfo::getRegionMemberId,memberId));
        if (shopInfo == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "门店不存在");
@@ -2307,11 +2404,19 @@
        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(),
                "password", rawPassword);
        // 6. 押金支付完成后,若城市未开通则自动开通
        if (shopInfo.getAreaId() != null) {
            Areas shopArea = areasBiz.resolveArea(shopInfo.getAreaId());
            Areas shopArea = areasService.getById(shopInfo.getAreaId());
            if (shopArea != null && shopArea.getParentId() != null) {
                Areas cityArea = areasBiz.resolveArea(shopArea.getParentId());
                Areas cityArea = areasService.getById(shopArea.getParentId());
                if (cityArea != null && !Constants.equalsInteger(cityArea.getStatus(), Constants.ONE)) {
                    cityArea.setStatus(Constants.ONE);
                    cityArea.setEditDate(now);
@@ -2341,7 +2446,8 @@
                .eq(Orders::getDeleted, Constants.ZERO)
                .eq(Orders::getStatus, Constants.OrderStatus.finished.getStatus())
                .eq(Orders::getSettlementStatus, Constants.ZERO)
                .le(Orders::getFinishTime, deadline));
                .le(Orders::getFinishTime, deadline)
        );
        if (ordersList == null || ordersList.isEmpty()) {
            return;
        }
@@ -2386,7 +2492,7 @@
                } else if (Constants.equalsInteger(revenue.getMemberType(), Constants.TWO)) {
                    // 门店:通过 memberId 查 ShopInfo(regionMemberId),更新 balance / totalBalance
                    ShopInfo shop = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda()
                            .eq(ShopInfo::getRegionMemberId, revenue.getMemberId())
                            .eq(ShopInfo::getId, revenue.getMemberId())
                            .eq(ShopInfo::getDeleted, Constants.ZERO)
                            .last("limit 1"));
                    if (shop != null) {
@@ -2588,6 +2694,186 @@
    @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.equalsInteger(order.getType(),Constants.ZERO)?Constants.OrderStatus.arrived.getStatus():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 if(Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.delivering.getKey())){
            // 仅异地寄存 + 有取件门店 + 派送中(4) 可核销
            if (!Constants.equalsInteger(order.getType(), Constants.ONE)) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅异地寄存订单支持司机核销");
            }
            if (order.getTakeShopId() == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单无取件门店,无需司机核销");
            }
            if (!Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.delivering.getStatus())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许核销");
            }
            // 校验取件门店与当前登录门店一致
            if (!shopId.equals(order.getTakeShopId())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店,无法核销");
            }
            // 派送中(4) → 已到店(5)
            order.setStatus(Constants.OrderStatus.arrived.getStatus());
            order.setArriveTime(new Date());
            if (StringUtils.isNotBlank(remark)) {
                order.setRemark(remark);
            }
            ordersMapper.updateById(order);
            // 释放司机核销码
            releaseVerifyCode(order.getDriverVerifyCode());
            // 保存附件(obj_type=3 门店入库图片,最多3张)
            saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_TAKE.getKey(), order.getAcceptDriver());
            // 通知会员:订单已送达
            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() : "");
            }
        }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(), "核销码不能为空");
@@ -2625,7 +2911,7 @@
            // 保存寄存图片(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)) {
                // 异地寄存 → 待抢单
@@ -2634,7 +2920,7 @@
            } else {
                // 就地寄存 → 待取件提醒
                sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_PICKUP_REMIND, order.getId(),
                        "orderNo", order.getCode());
                        "orderNo", order.getCode(), "shopName", shopName);
            }
        } else if (Constants.equalsInteger(status, Constants.OrderStatus.arrived.getStatus())) {
            // 异地寄存 + 无取件门店 → 无法核销(客户自取,无门店操作)
@@ -2644,6 +2930,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());
@@ -2657,7 +2951,7 @@
            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());
@@ -2751,12 +3045,13 @@
            ordersRefundMapper.insert(refundRecord);
            // 调用微信退款V3(放在最后,确保前置操作全部成功)
            String outRefundNo2 = ID.nextGUID();
            Refund refundResult = wxPayV3Service.refund(
                    order.getOutTradeNo(), order.getPayAmount(), order.getRefundAmount(),
                    outRefundNo2, order.getOutTradeNo(), order.getPayAmount(), order.getRefundAmount(),
                    "订单退款", wxPayProperties.getV3RefundNotifyUrl());
            // 退款成功后回填退款单号,标记退款中
            refundRecord.setRefundCode(refundResult.getOutRefundNo());
            refundRecord.setRefundCode(outRefundNo2);
            refundRecord.setStatus(Constants.ZERO); // 退款中
            ordersRefundMapper.updateById(refundRecord);
        }
@@ -2770,7 +3065,7 @@
        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());
@@ -2904,8 +3199,8 @@
        // 存件门店收益
        if (depositShopFee > 0 && order.getDepositShopId() != null) {
            ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId());
            if (depositShop != null && depositShop.getRegionMemberId() != null) {
                revenueMapper.insert(buildRevenue(depositShop.getRegionMemberId(), Constants.TWO,
            if (depositShop != null && depositShop.getId() != null) {
                revenueMapper.insert(buildRevenue(depositShop.getId(), Constants.TWO,
                        depositShopFee, orderId, order.getCode()));
            }
        }
@@ -2913,8 +3208,8 @@
        // 取件门店收益(异地寄存且有取件门店)
        if (takeShopFee > 0 && order.getTakeShopId() != null) {
            ShopInfo takeShop = shopInfoMapper.selectById(order.getTakeShopId());
            if (takeShop != null && takeShop.getRegionMemberId() != null) {
                revenueMapper.insert(buildRevenue(takeShop.getRegionMemberId(), Constants.TWO,
            if (takeShop != null && takeShop.getId() != null) {
                revenueMapper.insert(buildRevenue(takeShop.getId(), Constants.TWO,
                        takeShopFee, orderId, order.getCode()));
            }
        }
@@ -3102,9 +3397,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,订单保持当前状态
@@ -3148,7 +3443,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);
        }
    }
@@ -3225,6 +3520,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};
            }
        }
    }
    /**
     * 逾期费用内部计算(不查库,接受预查询的数据)
     * 供分页等已查询明细的业务场景复用,避免重复查询
     */
@@ -3241,8 +3592,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();
@@ -3326,25 +3677,20 @@
    /**
     * 就地寄存逾期天数计算
     * 过了预计取件时间当天的12点后才算一天
     * 过了预计取件日期的次日(只比较年月日)后开始计逾期天数
     */
    private int calcLocalOverdueDays(Date now, Date expectedTakeTime) {
        if (expectedTakeTime == null || !now.after(expectedTakeTime)) {
        if (expectedTakeTime == null) {
            return 0;
        }
        // 基准时间 = 预计取件日期的12: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);
        Date baseTime = baseCal.getTime();
        if (!now.after(baseTime)) {
            return 0;
        }
        // 逾期天数 = 当前日期 - 基准日期(按天取差)
        Calendar nowCal = Calendar.getInstance();
        nowCal.setTime(now);
        nowCal.set(Calendar.HOUR_OF_DAY, 0);
@@ -3352,40 +3698,31 @@
        nowCal.set(Calendar.SECOND, 0);
        nowCal.set(Calendar.MILLISECOND, 0);
        Calendar baseDateCal = Calendar.getInstance();
        baseDateCal.setTime(baseTime);
        baseDateCal.set(Calendar.HOUR_OF_DAY, 0);
        baseDateCal.set(Calendar.MINUTE, 0);
        baseDateCal.set(Calendar.SECOND, 0);
        baseDateCal.set(Calendar.MILLISECOND, 0);
        long diffMs = nowCal.getTimeInMillis() - baseDateCal.getTimeInMillis();
        int days = (int) (diffMs / (1000 * 60 * 60 * 24));
        return Math.max(days, 0);
        // 基准日期 = 预计取件日期的次日,当天及之前不算逾期
        if (nowCal.before(baseCal)) {
            return 0;
        }
        // 逾期天数 = 当前日期 - 基准日期
        long diffMs = nowCal.getTimeInMillis() - baseCal.getTimeInMillis();
        return (int) (diffMs / (1000 * 60 * 60 * 24));
    }
    /**
     * 异地寄存逾期天数计算
     * 过了转移到店时间当天的晚上12点(24:00)后才算第一天
     * 过了转移到店日期的次日(只比较年月日)后开始计逾期天数
     */
    private int calcRemoteOverdueDays(Date now, Date arriveTime) {
        if (arriveTime == null || !now.after(arriveTime)) {
        if (arriveTime == null) {
            return 0;
        }
        // 基准时间 = 转移到店日期的次日 00:00(即当天24:00)
        // 只取年月日
        Calendar baseCal = Calendar.getInstance();
        baseCal.setTime(arriveTime);
        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)) {
            return 0;
        }
        // 逾期天数 = 当前日期 - 基准日期
        Calendar nowCal = Calendar.getInstance();
        nowCal.setTime(now);
        nowCal.set(Calendar.HOUR_OF_DAY, 0);
@@ -3393,16 +3730,14 @@
        nowCal.set(Calendar.SECOND, 0);
        nowCal.set(Calendar.MILLISECOND, 0);
        Calendar baseDateCal = Calendar.getInstance();
        baseDateCal.setTime(baseTime);
        baseDateCal.set(Calendar.HOUR_OF_DAY, 0);
        baseDateCal.set(Calendar.MINUTE, 0);
        baseDateCal.set(Calendar.SECOND, 0);
        baseDateCal.set(Calendar.MILLISECOND, 0);
        long diffMs = nowCal.getTimeInMillis() - baseDateCal.getTimeInMillis();
        int days = (int) (diffMs / (1000 * 60 * 60 * 24));
        return Math.max(days, 0);
        // 基准日期 = 到店日期的次日,当天及之前不算逾期
        baseCal.add(Calendar.DAY_OF_MONTH, 1);
        if (nowCal.before(baseCal)) {
            return 0;
        }
        // 逾期天数 = 当前日期 - 基准日期
        long diffMs = nowCal.getTimeInMillis() - baseCal.getTimeInMillis();
        return (int) (diffMs / (1000 * 60 * 60 * 24));
    }
    @Override
@@ -3411,7 +3746,7 @@
        QueryWrapper<Orders> wrapper = new QueryWrapper<>();
        wrapper.eq("MEMBER_ID", memberId)
                .in("STATUS", 0, 1, 2, 3, 4, 5)
                .orderByDesc("CREATE_TIME")
                .orderByAsc("CREATE_TIME")
                .last("LIMIT 1");
        Orders order = ordersMapper.selectOne(wrapper);
        if (order == null) {
@@ -3421,6 +3756,7 @@
        ActiveOrderTipVO vo = new ActiveOrderTipVO();
        vo.setOrderId(order.getId());
        vo.setStatus(order.getStatus());
        vo.setStatusDesc(Constants.OrderStatus.getDescByKey(order.getStatus(), order.getType()));
        // 构建提示文案
        boolean isLocal = Constants.equalsInteger(order.getType(), Constants.ZERO);
@@ -3527,9 +3863,9 @@
                // 写入操作日志
                OrderLog orderLog = new OrderLog();
                orderLog.setOrderId(order.getId());
                orderLog.setTitle("系统自动取消(超时未支付)");
                orderLog.setLogInfo("订单超时" + autoCancelMinutes + "分钟未支付,系统自动取消");
                orderLog.setObjType(Constants.ORDER_LOG_CANCEL);
                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);
@@ -3693,9 +4029,9 @@
                // 记录操作日志
                OrderLog orderLog = new OrderLog();
                orderLog.setOrderId(order.getId());
                orderLog.setTitle("系统自动完成");
                orderLog.setLogInfo("订单已送达超过" + autoConfirmDays + "天未确认,系统自动完成");
                orderLog.setObjType(Constants.ORDER_LOG_CANCEL);
                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);
@@ -3742,31 +4078,28 @@
            for (int i = 0; i < paramPairs.length - 1; i += 2) {
                templateParam.put(paramPairs[i], paramPairs[i + 1]);
            }
            boolean result = AliSmsService.sendSms(phone, smsNotify.getTemplateCode(),
            String error = 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.setType(Constants.ONE);
            record.setStatus(error == null ? Constants.ONE : Constants.ZERO);
            if (error != null) {
                record.setRemark(error);
            }
            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.setStatus(Constants.ZERO);
                record.setRemark(e.getMessage());
                record.setCreateTime(new Date());
                record.setDeleted(Constants.ZERO);
                smsrecordMapper.insert(record);