rk
22 小时以前 33caf2bb79bb3c561916c91ae386ec772411e2e8
server/services/src/main/java/com/doumee/service/business/impl/OrdersServiceImpl.java
@@ -1,15 +1,19 @@
package com.doumee.service.business.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.doumee.biz.system.AreasBiz;
import com.doumee.config.xyy.XyyConfig;
import com.doumee.config.xyy.dto.PrintRequest;
import com.doumee.biz.system.OperationConfigBiz;
import com.doumee.biz.system.SystemDictDataBiz;
import com.doumee.config.wx.WxMiniConfig;
import com.doumee.config.wx.WxPayProperties;
import com.doumee.config.wx.WxPayV3Service;
import com.doumee.core.utils.jpush.JPushUtil;
import com.wechat.pay.java.service.refund.model.Refund;
import com.doumee.core.constants.Constants;
import com.doumee.core.constants.ResponseStatus;
@@ -17,6 +21,7 @@
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.core.utils.DateUtil;
import com.doumee.core.utils.GeoUtils;
import com.doumee.core.utils.ID;
import com.doumee.core.utils.geocode.MapUtil;
import com.doumee.core.utils.Utils;
@@ -45,6 +50,7 @@
import com.github.xiaoymin.knife4j.core.util.CollectionUtils;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -55,6 +61,7 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.text.SimpleDateFormat;
import java.util.concurrent.TimeUnit;
/**
@@ -129,6 +136,9 @@
    private AreasService areasService;
    @Autowired
    private AreasMapper aareasMapper;
    @Autowired
    private NoticeService noticeService;
    @Autowired
@@ -139,6 +149,12 @@
    @Autowired
    private WxPayProperties wxPayProperties;
    @Autowired
    private XyyConfig xyyConfig;
    @Autowired
    private PrintService printService;
    @Override
    public Integer create(Orders orders) {
@@ -216,14 +232,13 @@
                .leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID");
                ;
        Utils.MP.blankToNull(pageWrap.getModel());
        pageWrap.getModel().setDeleted(Constants.ZERO);
        queryWrapper.eq(pageWrap.getModel().getDeleted() != null, Orders::getDeleted, pageWrap.getModel().getDeleted());
        queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getCode()), Orders::getCode, pageWrap.getModel().getCode());
        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, pageWrap.getModel().getCreateStartTime());
        queryWrapper.le(pageWrap.getModel().getCreateEndTime() != null, Orders::getCreateTime, pageWrap.getModel().getCreateEndTime());
        queryWrapper.le(pageWrap.getModel().getCreateEndTime() != null, Orders::getCreateTime, Utils.Date.getEnd(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());
@@ -234,18 +249,15 @@
        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());
            } else {
                queryWrapper.orderByAsc(sortData.getProperty());
            }
        }
        queryWrapper.orderByDesc(Orders::getId);
        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() : "");
            }
            if(Constants.equalsInteger(o.getIsUrgent(),Constants.ZERO)){
                o.setUrgentAmount(Constants.ZERO.longValue());
            }
        }
        return pageData;
@@ -368,7 +380,7 @@
        for (Category c : categories) {
            categoryNameMap.put(c.getId(), c.getName());
            categoryDetailMap.put(c.getId(), c.getDetail());
            categoryOtherFieldMap.put(c.getId(),c.getOtherField());
            categoryOtherFieldMap.put(c.getId(),c.getRemark());
        }
        // 计算每项物品费用:小计 = 单价 × 数量 × 天数
@@ -490,12 +502,13 @@
        for (Category c : categories) {
            categoryNameMap.put(c.getId(), c.getName());
            categoryDetailMap.put(c.getId(), c.getDetail());
            categoryOtherFieldMap.put(c.getId(),c.getOtherField());
            categoryOtherFieldMap.put(c.getId(),c.getRemark());
        }
        // 3. 逐项计算运费:起步价 + 超出部分阶梯价
        List<ItemPriceVO> itemList = new ArrayList<>();
        long itemPriceTotal = 0L;
        long maxExtraFeeTotal = 0L; // 最大超出距离费用(单价×数量)
        for (OrderItemDTO item : dto.getItems()) {
            PricingRule rule = ruleMap.get(String.valueOf(item.getCategoryId()));
@@ -511,13 +524,15 @@
            long extraPricePerUnit = Long.parseLong(rule.getFieldE());
            // 阶梯计价:距离 ≤ 起步距离取起步价,超出按 ceil(超出距离/单位) × 单价累加
            long extraFee = 0L;
            long unitPrice;
            if (distanceKm.compareTo(startDistance) <= 0) {
                unitPrice = startPrice;
            } else {
                BigDecimal extraKm = distanceKm.subtract(startDistance);
                BigDecimal extraCount = extraKm.divide(extraDistanceUnit, 0, RoundingMode.CEILING);
                unitPrice = startPrice + extraCount.longValue() * extraPricePerUnit;
                extraFee = extraCount.longValue() * extraPricePerUnit;
                unitPrice = startPrice + extraFee;
            }
            long subtotal = unitPrice * item.getQuantity();
@@ -540,8 +555,17 @@
            vo.setExtraPrice(extraPricePerUnit);
            itemList.add(vo);
            itemPriceTotal += subtotal;
            // 所有物品的起步价×数量 累加
            itemPriceTotal += startPrice * item.getQuantity();
            // 记录最大的超出距离费用
            long extraFeeTotal = extraFee * item.getQuantity();
            if (extraFeeTotal > maxExtraFeeTotal) {
                maxExtraFeeTotal = extraFeeTotal;
            }
        }
        // 多物品时只加最大的超出距离费用
        itemPriceTotal += maxExtraFeeTotal;
        // 4. 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分(保价金额>0时计费)
        long insuranceFeeFen = 0L;
@@ -1384,6 +1408,25 @@
        }
        return urls;
    }
    private  List<Multifile> getFileUrlsByidList(List<Integer> orderId, int objType, String prefix) {
        if(orderId ==null || orderId.size()==0){
            return  null;
        }
        List<Multifile> files = multifileMapper.selectList(
                new QueryWrapper<Multifile>().lambda()
                        .in(Multifile::getObjId, orderId)
                        .eq(Multifile::getObjType, objType)
                        .eq(Multifile::getIsdeleted, Constants.ZERO)
                        .orderByAsc(Multifile::getSortnum));
        if (files != null) {
            for (Multifile f : files) {
                if (StringUtils.isNotBlank(f.getFileurl())) {
                    f.setFileurl(prefix + f.getFileurl());
                }
            }
        }
        return files;
    }
    /**
     * 计算并设置订单薪酬分配(司机、存件门店、取件门店)
@@ -1405,20 +1448,31 @@
            return;
        }
        Integer cityId = Integer.valueOf(orders.getCityId());
        boolean isRemote = Constants.equalsInteger(orders.getType(), Constants.ONE);
        boolean isCompany = Constants.equalsInteger(depositShop.getCompanyType(), Constants.ONE);
        // 司机占比:fieldA=4(配送员)
        // 司机占比:fieldA=4(配送员),始终从城市配置取
        BigDecimal driverRata = getRevenueShareRata(cityId, Constants.FOUR);
        // 寄件门店占比:fieldA=0(企业寄)/1(个人寄)
        int depositFieldA = Constants.equalsInteger(depositShop.getCompanyType(), Constants.ONE) ? Constants.ZERO : Constants.ONE;
        BigDecimal depositShopRata = getRevenueShareRata(cityId, depositFieldA);
        // 取件门店占比:无取件门店时比例为0
        BigDecimal takeShopRata = BigDecimal.ZERO;
        if (takeShop != null) {
            int takeFieldA = Constants.equalsInteger(takeShop.getCompanyType(), Constants.ONE) ? Constants.TWO : Constants.THREE;
            takeShopRata = getRevenueShareRata(cityId, takeFieldA);
        // 存件门店占比
        BigDecimal depositShopRata;
        if (isRemote) {
            int fallbackFieldA = isCompany ? Constants.ZERO : Constants.ONE;
            depositShopRata = getShopRevenueShare(depositShop, "remoteDeposit", cityId, fallbackFieldA);
        } else {
            int fallbackFieldA = isCompany ? Constants.FIVE : Constants.SIX;
            depositShopRata = getShopRevenueShare(depositShop, "localDeposit", cityId, fallbackFieldA);
        }
        // 计算薪酬(分):totalAmount 为分,rata 为比例值(如 0.15 表示 15%)
        // 取件门店占比
        BigDecimal takeShopRata = BigDecimal.ZERO;
        if (isRemote && takeShop != null) {
            boolean takeIsCompany = Constants.equalsInteger(takeShop.getCompanyType(), Constants.ONE);
            int fallbackFieldA = takeIsCompany ? Constants.TWO : Constants.THREE;
            takeShopRata = getShopRevenueShare(takeShop, "remoteTake", cityId, fallbackFieldA);
        }
        // 计算薪酬(分)
        long driverFee = new BigDecimal(totalAmount).multiply(driverRata).longValue();
        long depositShopFee = new BigDecimal(totalAmount).multiply(depositShopRata).longValue();
        long takeShopFee = new BigDecimal(totalAmount).multiply(takeShopRata).longValue();
@@ -1435,7 +1489,7 @@
     * 从 pricing_rule 表获取分成比例(type=4)
     *
     * @param cityId   城市主键
     * @param fieldA   类型:0=企业寄, 1=个人寄, 2=企业取, 3=个人取, 4=配送员
     * @param fieldA   类型:0=异地企业寄, 1=异地个人寄, 2=异地企业取, 3=异地个人取, 4=配送员, 5=就地企业存, 6=就地个人存
     * @return 分成比例(如 0.15 表示 15%)
     */
    private BigDecimal getRevenueShareRata(Integer cityId, int fieldA) {
@@ -1446,10 +1500,35 @@
                .eq(PricingRule::getFieldA, String.valueOf(fieldA))
                .last("limit 1"));
        if (rule != null && StringUtils.isNotBlank(rule.getFieldB())) {
            // fieldB 存储的是百分比整数(如15表示15%),转换为小数比例(0.15)
            // fieldB 存储的是百分比数字(如15表示15%),转换为小数比例(0.15)
            return new BigDecimal(rule.getFieldB()).divide(new BigDecimal("100"), 4, BigDecimal.ROUND_HALF_UP);
        }
        return BigDecimal.ZERO;
    }
    /**
     * 获取门店收益占比:优先从门店 revenueShareConfig 取,无则兜底城市 pricing_rule
     *
     * @param shop           门店信息
     * @param jsonKey        revenueShareConfig 中的键名
     * @param cityId         城市主键(兜底用)
     * @param fallbackFieldA 兜底 pricing_rule fieldA 值
     * @return 分成比例
     */
    private BigDecimal getShopRevenueShare(ShopInfo shop, String jsonKey, Integer cityId, int fallbackFieldA) {
        if (shop != null && StringUtils.isNotBlank(shop.getRevenueShareConfig())) {
            try {
                JSONObject config = JSONObject.parseObject(shop.getRevenueShareConfig());
                if (config != null && config.containsKey(jsonKey)) {
                    Double value = config.getDouble(jsonKey);
                    if (value != null) {
                        return new BigDecimal(value).divide(new BigDecimal("100"), 4, BigDecimal.ROUND_HALF_UP);
                    }
                }
            } catch (Exception ignored) {
            }
        }
        return getRevenueShareRata(cityId, fallbackFieldA);
    }
    @Override
@@ -1489,11 +1568,23 @@
                .eq(status != null, Orders::getStatus, status)
                .in(statusList != null, Orders::getStatus, statusList)
                .orderByDesc(Orders::getCreateTime);
        // 关键词搜索:收件人/收件人电话模糊、订单号精准
        if (model != null && StringUtils.isNotBlank(model.getKeyword())) {
            String kw = model.getKeyword().trim();
            wrapper.and(w -> w.like(Orders::getTakeUser, kw)
                    .or().like(Orders::getTakePhone, kw)
                    .or().eq(Orders::getCode, kw));
        }
        IPage<Orders> orderPage = ordersMapper.selectJoinPage(p, Orders.class, wrapper);
        List<MyOrderVO> voList = new ArrayList<>();
        if (orderPage != null && orderPage.getRecords() != null) {
        if (orderPage != null && orderPage.getRecords() != null  && orderPage.getRecords().size()>0) {
            String imgPrefix = getOrdersPrefix();
            List<Integer> idList =new ArrayList<>();
            for (Orders o : orderPage.getRecords()) {
                idList.add(o.getId());
            }
            List<Multifile> files = getFileUrlsByidList(idList,Constants.FileType.ORDER_FILE.getKey(), imgPrefix);
            for (Orders o : orderPage.getRecords()) {
                MyOrderVO vo = new MyOrderVO();
                vo.setId(o.getId());
@@ -1538,16 +1629,20 @@
                // 评价状态
                vo.setCommentStatus(o.getCommentStatus());
                //序号
                vo.setSortnum(Constants.formatIntegerNum(o.getDepositShopId())+"-"+o.getId());
                if(o.getTakeShopId()!=null){
                    vo.setSortnumTake(Constants.formatIntegerNum(o.getTakeShopId())+"-"+o.getId());
                }
                // 查询物品明细(一次查询,同时用于物品列表和逾期计算)
                List<OrdersDetail> details = ordersDetailMapper.selectList(
                        new QueryWrapper<OrdersDetail>().lambda()
                                .eq(OrdersDetail::getOrderId, o.getId())
                                .eq(OrdersDetail::getDeleted, Constants.ZERO));
                // 物品明细
                vo.setDetailList(buildDetailList(details));
                vo.setOrderImages(getFileUrlsFromList(o.getId(),files));
                // 逾期状态
                fillOverdueStatus(vo, o, details);
                voList.add(vo);
@@ -1561,6 +1656,22 @@
        pageData.setPage(orderPage.getCurrent());
        pageData.setCapacity(orderPage.getSize());
        return pageData;
    }
    private List<String> getFileUrlsFromList(Integer id, List<Multifile> files) {
        List<String> urls = new ArrayList<>();
        try {
            if(files!=null){
                for(Multifile f : files){
                    if(Constants.equalsInteger(f.getObjId(),id)){
                        urls.add(f.getFileurl());
                    }
                }
            }
        }catch (Exception e){
        }
        return urls;
    }
    @Override
@@ -1600,20 +1711,33 @@
        // 门店待处理订单:按业务环节区分门店角色
        if (combinedStatus != null && Constants.equalsInteger(combinedStatus, Constants.SEVEN)) {
            wrapper.and(w -> w
                    .and(w1 -> w1.eq(Orders::getDepositShopId, shopId)
                    .and(w1 -> w1.eq(Orders::getType, Constants.ZERO).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()))
                                    Constants.OrderStatus.arrived.getStatus())
                    .or(w3-> w3.eq(Orders::getType, Constants.ONE).eq(Orders::getDepositShopId, shopId)
                            .eq(Orders::getStatus, Constants.OrderStatus.waitDeposit.getStatus()))
                    .or(w2 -> w2.eq(Orders::getType, Constants.ONE).eq(Orders::getTakeShopId, shopId)
                            .eq(Orders::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);
                .in(statusList != null && !Constants.equalsInteger(combinedStatus, Constants.SEVEN), Orders::getStatus, statusList);
        if(Objects.nonNull(model.getCombinedStatus())&&Constants.equalsInteger(model.getCombinedStatus(),Constants.OrderCombinedStatus.finished.getKey())){
            wrapper.orderByDesc(Orders::getFinishTime);
        }else{
            wrapper.orderByDesc(Orders::getId);
        }
        // 关键词搜索:收件人/收件人电话模糊、订单号精准
        MyOrderDTO shopModel = pageWrap.getModel();
        if (shopModel != null && StringUtils.isNotBlank(shopModel.getKeyword())) {
            String kw = shopModel.getKeyword().trim();
            wrapper.and(w -> w.like(Orders::getTakeUser, kw)
                    .or().like(Orders::getTakePhone, kw)
                    .or().eq(Orders::getCode, kw));
        }
        IPage<Orders> orderPage = ordersMapper.selectJoinPage(p, Orders.class, wrapper);
        List<MyOrderVO> voList = new ArrayList<>();
@@ -1666,8 +1790,6 @@
                                .eq(OrdersDetail::getDeleted, Constants.ZERO));
                vo.setDetailList(buildDetailList(details));
                // 逾期状态
                fillOverdueStatus(vo, o, details);
@@ -1767,7 +1889,11 @@
        if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.waitPay.getStatus())) {
            vo.setPayCountdownMs(calcPayCountdownMs(order));
        }
        //序号
        vo.setSortnum(Constants.formatIntegerNum(order.getDepositShopId())+"-"+order.getId());
        if(order.getTakeShopId()!=null){
            vo.setSortnumTake(Constants.formatIntegerNum(order.getTakeShopId())+"-"+order.getId());
        }
        // 存件门店
        if (order.getDepositShopId() != null) {
            ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId());
@@ -2155,6 +2281,14 @@
        order.setWxExternalNo(wxTradeNo);
        order.setPayAmount(order.getTotalAmount());
        order.setUpdateTime(now);
        // 计算店铺订单序号:当前存件门店当天已支付订单数 + 1
        Date todayStart = DateUtil.getStartOfDay(now);
        Long currentCount = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getDepositShopId, order.getDepositShopId())
                .eq(Orders::getPayStatus, Constants.ONE)
                .ge(Orders::getPayTime, todayStart)
                .eq(Orders::getDeleted, Constants.ZERO));
        order.setAutoNum(currentCount + 1);
        // 生成会员核销码
        order.setMemberVerifyCode(generateVerifyCode());
        // 异地寄存:计算预计送达时间
@@ -2383,26 +2517,37 @@
        otherOrders.setUpdateTime(now);
        otherOrdersMapper.updateById(otherOrders);
        // 4. 查询门店信息(通过注册会员主键关联)
        // 4. 查询变更版本门店信息(通过注册会员主键关联)
        ShopInfo shopInfo = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda()
                .eq(ShopInfo::getRegionMemberId, otherOrders.getMemberId())
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .eq(ShopInfo::getVersionType, Constants.ONE)
                .last("limit 1"));
        if (shopInfo == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "门店不存在");
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "门店变更记录不存在");
        }
        // 5. 更新门店状态:已支付押金
        shopInfo.setAuditStatus(Constants.THREE); // 3=已支付押金
        // 5. 更新变更版本支付状态
        Member member = memberMapper.selectById(otherOrders.getMemberId());
        shopInfo.setAuditStatus(Constants.THREE);
        shopInfo.setPayStatus(Constants.ONE);
        shopInfo.setPayTime(now);
        shopInfo.setWxExternalNo(wxTradeNo);
        shopInfo.setCode(otherOrders.getCode());
        Member member = memberMapper.selectById(otherOrders.getMemberId());
        if (member != null) {
            shopInfo.setPayMemberOpenId(member.getOpenid());
        }
        shopInfo.setUpdateTime(now);
        shopInfoMapper.updateById(shopInfo);
        // 6. 同步更新正式版本状态为已支付押金
        if (shopInfo.getRelationShopId() != null) {
            ShopInfo officialShop = shopInfoMapper.selectById(shopInfo.getRelationShopId());
            if (officialShop != null) {
                officialShop.setAuditStatus(Constants.THREE);
                officialShop.setUpdateTime(now);
                shopInfoMapper.updateById(officialShop);
            }
        }
        // 短信通知门店:成功入驻
        String rawPassword = shopInfo.getTelephone() != null && shopInfo.getTelephone().length() >= 6
@@ -2414,13 +2559,13 @@
        // 6. 押金支付完成后,若城市未开通则自动开通
        if (shopInfo.getAreaId() != null) {
            Areas shopArea = areasService.getById(shopInfo.getAreaId());
            Areas shopArea = aareasMapper.selectById(shopInfo.getAreaId());
            if (shopArea != null && shopArea.getParentId() != null) {
                Areas cityArea = areasService.getById(shopArea.getParentId());
                Areas cityArea = aareasMapper.selectById(shopArea.getParentId());
                if (cityArea != null && !Constants.equalsInteger(cityArea.getStatus(), Constants.ONE)) {
                    cityArea.setStatus(Constants.ONE);
                    cityArea.setEditDate(now);
                    areasService.updateById(cityArea);
                    aareasMapper.updateById(cityArea);
                    areasService.cacheData();
                }
            }
@@ -2749,6 +2894,10 @@
            }
            order.setMemberVerifyCode(generateVerifyCode());
            ordersMapper.updateById(order);
            // 寄存时图片必填
            if (images == null || images.isEmpty()) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "请上传寄存图片");
            }
            // 保存寄存图片(obj_type=2 订单寄存图片,最多3张)
            saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_DEPOSIT.getKey(), shopId);
            // 记录订单日志
@@ -2758,6 +2907,8 @@
                // 异地寄存 → 待抢单
                sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_GRAB, order.getId(),
                        "orderNo", order.getCode());
                // 推送通知司机
                pushDriverNewOrder(order);
            } else {
                // 就地寄存 → 待取件提醒
                sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_PICKUP_REMIND, order.getId(),
@@ -2784,6 +2935,12 @@
            order.setStatus(Constants.OrderStatus.finished.getStatus());
            order.setConfirmArriveTime(now);
            ordersMapper.updateById(order);
            // 就地寄存(type=0)取件时图片不必填,其他类型取件必填
            if (!Constants.equalsInteger(order.getType(), Constants.ZERO)) {
                if (images == null || images.isEmpty()) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "请上传取件图片");
                }
            }
            // 订单完成,释放核销码
            String verifyCode = order.getMemberVerifyCode();
            if (StringUtils.isNotBlank(verifyCode)) {
@@ -2917,6 +3074,8 @@
                // 异地寄存 → 待抢单
                sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_GRAB, order.getId(),
                        "orderNo", order.getCode());
                // 推送通知司机
                pushDriverNewOrder(order);
            } else {
                // 就地寄存 → 待取件提醒
                sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_PICKUP_REMIND, order.getId(),
@@ -3475,7 +3634,12 @@
            return "司机已取件,正运往目的地";
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.arrived.getStatus())) {
            return "行李已送达服务点,请及时前往取件";
            if(Constants.equalsInteger(order.getType(),Constants.ZERO)){
                return "行李已寄存,请凭取件码前往指定门店取件~";
            }else{
                return "行李已送达服务点,请及时前往取件";
            }
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.finished.getStatus())) {
            if (Constants.equalsInteger(order.getCommentStatus(), Constants.ONE)) {
@@ -3580,7 +3744,10 @@
     * 供分页等已查询明细的业务场景复用,避免重复查询
     */
    private OverdueFeeVO calculateOverdueFeeInternal(Orders order, List<OrdersDetail> details) {
        if (CollectionUtils.isEmpty(details)) {
        if (CollectionUtils.isEmpty(details)||
                Constants.equalsInteger(order.getStatus(),Constants.ZERO)
        || Constants.equalsInteger(order.getStatus(),Constants.ONE)
        ) {
            OverdueFeeVO vo = new OverdueFeeVO();
            vo.setOverdue(false);
            vo.setOverdueDays(0);
@@ -3959,7 +4126,6 @@
                            "time", String.valueOf(noGrabMinutes));
                }
                // 标记已通知 + 记录通知时间
                order.setPlatformSmsNotified(Constants.ONE);
                order.setPlatformSmsNotifiedTime(now);
                order.setUpdateTime(now);
                ordersMapper.updateById(order);
@@ -4072,6 +4238,9 @@
        if (StringUtils.isBlank(phone)) {
            return;
        }
        if (!smsNotify.isEnabled()) {
            return;
        }
        String content = smsNotify.format(paramPairs);
        try {
            JSONObject templateParam = new JSONObject();
@@ -4107,4 +4276,236 @@
        }
    }
    @Override
    public void printOrderLabel(Integer orderId, Integer shopId) {
        Orders orders = ordersMapper.selectById(orderId);
        if (orders == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在");
        }
        if (!Constants.equalsInteger(orders.getDepositShopId(), shopId)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前门店不是该订单的存件门店");
        }
        if (orders.getStatus() < 1 || orders.getStatus() > 7) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许打印");
        }
        ShopInfo shop = shopInfoMapper.selectById(shopId);
        if (shop == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "存件门店不存在");
        }
        if (StringUtils.isBlank(shop.getPrinterSn())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "存件门店未绑定打印机");
        }
        List<OrdersDetail> detailList = ordersDetailMapper.selectList(
                new QueryWrapper<OrdersDetail>().lambda().eq(OrdersDetail::getOrderId, orderId));
        // 拼接客户信息
        String userInfo = "";
        if (StringUtils.isNotBlank(orders.getTakeUser())) {
            String phone = orders.getTakePhone();
            userInfo = orders.getTakeUser();
            if (StringUtils.isNotBlank(phone) && phone.length() >= 4) {
                userInfo += "(" + phone.substring(phone.length() - 4) + ")";
            }
        }
        //序号:商铺ID-日期-自增长序号(如 32-06-001)
        String dateStr = new SimpleDateFormat("dd").format(orders.getPayTime() != null ? orders.getPayTime() : new Date());
        String autoNumStr = String.format("%03d", orders.getAutoNum() != null ? orders.getAutoNum() : 0);
        String sort = shopId + "-" + dateStr + "-" + autoNumStr;
//        String content = printService.getPrintContent(shop.getName(), detailList, userInfo, orders.getCode(), orders.getRemark(),
        String content = printService.getPrintContent(shop.getName(), detailList, userInfo, orders.getCode(), sort,
                orders.getTakeLocationRemark(),
                orders.getPayTime() != null ? new SimpleDateFormat("yyyy-MM-dd HH:mm").format(orders.getPayTime()) : "",
                orders.getExpectedTakeTime() != null ? new SimpleDateFormat("yyyy-MM-dd HH:mm").format(orders.getExpectedTakeTime()) : "");
        if (StringUtils.isBlank(content)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "打印内容为空");
        }
        PrintRequest request = new PrintRequest();
        xyyConfig.createRequestHeader(request);
        request.setSn(shop.getPrinterSn());
        request.setContent(content);
        request.setCopies(1);
        request.setVoice(2);
        request.setMode(0);
        com.doumee.config.xyy.vo.ObjectRestResponse<String> resp = printService.printLabel(request);
        if (resp.getCode() != 0) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "打印失败:" + resp.getMsg());
        }
        log.info("订单标签打印成功: orderId={}, orderIndex={}", orderId, resp.getData());
    }
    /**
     * 异地订单寄存成功后推送通知给司机
     * 1、未加急:推送同城且门店配送范围内接单中的司机
     * 2、加急:推送同城所有接单中的司机
     * 城市匹配:司机AREA_ID关联areas表,逐级parent_id找到城市级,与订单cityId匹配
     */
    private void pushDriverNewOrder(Orders order) {
        ShopInfo shop = shopInfoMapper.selectById(order.getDepositShopId());
        if (shop == null || shop.getLongitude() == null || shop.getLatitude() == null) {
            return;
        }
        String orderCityId = order.getCityId();
        if (StringUtils.isBlank(orderCityId)) {
            return;
        }
        String distSql = "(ST_Distance_Sphere(POINT(t.LONGITUDE, t.LATITUDE), POINT(" + shop.getLongitude() + ", " + shop.getLatitude() + ")) / 1000)";
        // MPJ关联查询:司机 → areas(区县) → areas_city(城市),匹配订单cityId
        MPJLambdaWrapper<DriverInfo> wrapper = new MPJLambdaWrapper<>();
        wrapper.selectAll(DriverInfo.class)
                .leftJoin(Areas.class,Areas::getId,DriverInfo::getAreaId)
                .eq(DriverInfo::getAcceptingStatus, Constants.ONE)
                .eq(DriverInfo::getStatus, Constants.ZERO)
                .eq(DriverInfo::getDeleted, Constants.ZERO)
                .eq(DriverInfo::getAuditStatus, Constants.THREE)
                .eq(Areas::getParentId, orderCityId);
        if (!Constants.equalsInteger(order.getIsUrgent(), Constants.ONE)) {
            // 未加急:门店配送范围内
            double rangeKm = shop.getDeliveryArea() != null ? shop.getDeliveryArea().doubleValue() : 5.0;
            wrapper.apply(distSql + " <= {0}", rangeKm);
        }
        List<DriverInfo> drivers = driverInfoMapper.selectJoinList(DriverInfo.class, wrapper);
        if (drivers.isEmpty()) {
            return;
        }
        List<String> aliases = new ArrayList<>();
        for (DriverInfo d : drivers) {
            aliases.add(org.springframework.util.DigestUtils.md5DigestAsHex(d.getTelephone().getBytes()));
        }
        String title = Constants.equalsInteger(order.getIsUrgent(), Constants.ONE) ? "新加急订单" : "新订单";
        String content = "订单号:" + order.getCode();
        java.util.Map<String, String> extras = new java.util.HashMap<>();
        extras.put("orderId", String.valueOf(order.getId()));
        extras.put("orderCode", order.getCode());
        extras.put("type", "new_order");
        JPushUtil.sendByAliases(aliases, title, content, extras);
    }
    @Override
    public int notifyArrivalPickUp() {
        String timeStr = operationConfigBiz.getConfig().getArrivalPickUpTime();
        if (StringUtils.isBlank(timeStr)) {
            return 0;
        }
        int minutes;
        try {
            minutes = Integer.parseInt(timeStr);
        } catch (NumberFormatException e) {
            log.warn("即将到达取件时间配置异常: {}", timeStr);
            return 0;
        }
        if (minutes <= 0) {
            return 0;
        }
        // 计算时间窗口:当前时间 + minutes 就是"即将到达"的临界点
        Date now = new Date();
        Date threshold = new Date(now.getTime() + (long) minutes * 60 * 1000);
        // 查询:status=5、未通知、就地寄存 or 异地(有取件门店)、预计取件时间在 now~threshold 之间
        List<Orders> orders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getStatus, Constants.OrderStatus.arrived.getStatus())
                .eq(Orders::getPayStatus, Constants.ONE)
                .eq(Orders::getDeleted, Constants.ZERO)
                .and(w -> w
                        .eq(Orders::getType, Constants.ZERO)
                        .or(w2 -> w2.eq(Orders::getType, Constants.ONE).isNotNull(Orders::getTakeShopId))
                )
                .ne(Orders::getPickUpNotifyStatus, Constants.ONE)
                .isNotNull(Orders::getExpectedTakeTime)
                .ge(Orders::getExpectedTakeTime, now)
                .le(Orders::getExpectedTakeTime, threshold));
        if (orders == null || orders.isEmpty()) {
            return 0;
        }
        int count = 0;
        for (Orders order : orders) {
            Member member = memberMapper.selectById(order.getMemberId());
            if (member != null && StringUtils.isNotBlank(member.getTelephone())) {
                sendSmsNotify(member.getTelephone(), Constants.SmsNotify.MEMBER_TIME_OUT,
                        "orderNo", order.getCode());
            }
            order.setPickUpNotifyStatus(Constants.ONE);
            ordersMapper.updateById(order);
            count++;
        }
        return count;
    }
    @Override
    public Boolean checkOperationRadius(Integer orderId, Integer userId, Integer userType, Double lng, Double lat) {
        String radiusStr = operationConfigBiz.getConfig().getOperationRadius();
        if (StringUtils.isBlank(radiusStr)) {
            return true;
        }
        double radiusM;
        try {
            radiusM = Double.parseDouble(radiusStr);
        } catch (NumberFormatException e) {
            return true;
        }
        if (radiusM <= 0) {
            return true;
        }
        Orders order = ordersMapper.selectById(orderId);
        if (order == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单不存在");
        }
        BigDecimal targetLat;
        BigDecimal targetLgt;
        if (Constants.equalsInteger(userType, Constants.ZERO)) {
            // 门店操作
            if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.waitDeposit.getStatus())) {
                // status=1 门店寄存核验
                if (!Constants.equalsInteger(order.getDepositShopId(), userId)) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权操作该订单");
                }
                targetLat = order.getDepositLat();
                targetLgt = order.getDepositLgt();
            } else if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.arrived.getStatus())) {
                // status=5 门店完成核销
                if (Constants.equalsInteger(order.getType(), Constants.ZERO)) {
                    // 就地存取 → 对比存件门店
                    if (!Constants.equalsInteger(order.getDepositShopId(), userId)) {
                        throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权操作该订单");
                    }
                    targetLat = order.getDepositLat();
                    targetLgt = order.getDepositLgt();
                } else {
                    // 异地存取 → 对比取件门店
                    if (!Constants.equalsInteger(order.getTakeShopId(), userId)) {
                        throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权操作该订单");
                    }
                    targetLat = order.getTakeLat();
                    targetLgt = order.getTakeLgt();
                }
            } else {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单状态不允许此操作");
            }
        } else if (Constants.equalsInteger(userType, Constants.ONE)) {
            // 司机操作
            if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.deposited.getStatus())) {
                // status=2 司机取件
                if (!Constants.equalsInteger(order.getAcceptDriver(), userId)) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权操作该订单");
                }
                targetLat = order.getDepositLat();
                targetLgt = order.getDepositLgt();
            } else if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.delivering.getStatus())) {
                // status=4 司机送达
                if (!Constants.equalsInteger(order.getAcceptDriver(), userId)) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权操作该订单");
                }
                targetLat = order.getTakeLat();
                targetLgt = order.getTakeLgt();
            } else {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单状态不允许此操作");
            }
        } else {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户类型不合法");
        }
        if (targetLat == null || targetLgt == null) {
            return true;
        }
        double distanceKm = GeoUtils.haversineDistance(lat, lng, targetLat.doubleValue(), targetLgt.doubleValue());
        return distanceKm * 1000 <= radiusM;
    }
}