MrShi
7 小时以前 e50954f0708ecbbc672352102ae3b24279d40cc1
server/services/src/main/java/com/doumee/service/business/impl/OrdersServiceImpl.java
@@ -4,23 +4,57 @@
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.biz.system.OperationConfigBiz;
import com.doumee.biz.system.SystemDictDataBiz;
import com.doumee.config.wx.WxMiniConfig;
import com.doumee.config.wx.WxMiniUtilService;
import com.doumee.core.constants.Constants;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.core.utils.DateUtil;
import com.doumee.core.utils.Tencent.MapUtil;
import com.doumee.core.utils.Utils;
import com.doumee.dao.business.OrdersMapper;
import com.doumee.dao.business.model.Category;
import com.doumee.dao.business.model.Orders;
import com.doumee.dao.business.*;
import com.doumee.dao.business.model.*;
import com.doumee.dao.system.SystemUserMapper;
import com.doumee.dao.system.model.SystemDictData;
import com.doumee.dao.system.model.SystemUser;
import com.doumee.dao.dto.CalculateLocalPriceDTO;
import com.doumee.dao.dto.CalculateRemotePriceDTO;
import com.doumee.dao.dto.CommentOrderDTO;
import com.doumee.dao.dto.CreateOrderDTO;
import com.doumee.dao.dto.DispatchDTO;
import com.doumee.dao.dto.MyOrderDTO;
import com.doumee.dao.dto.OrderItemDTO;
import com.doumee.dao.vo.*;
import com.doumee.service.business.OrderLogService;
import com.doumee.service.business.OrdersService;
import com.doumee.service.business.AreasService;
import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.xiaoymin.knife4j.core.util.CollectionUtils;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.List;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
 * 寄存订单信息Service实现
@@ -32,6 +66,62 @@
    @Autowired
    private OrdersMapper ordersMapper;
    @Autowired
    private MemberMapper memberMapper;
    @Autowired
    private ShopInfoMapper shopInfoMapper;
    @Autowired
    private DriverInfoMapper driverInfoMapper;
    @Autowired
    private CategoryMapper categoryMapper;
    @Autowired
    private MultifileMapper multifileMapper;
    @Autowired
    private OrdersDetailMapper ordersDetailMapper;
    @Autowired
    private SystemDictDataBiz systemDictDataBiz;
    @Autowired
    private OrderLogService orderLogService;
    @Autowired
    private OrdersRefundMapper ordersRefundMapper;
    @Autowired
    private OtherOrdersMapper otherOrdersMapper;
    @Autowired
    private OrderCommentMapper orderCommentMapper;
    @Autowired
    private RevenueMapper revenueMapper;
    @Autowired
    private WxMiniUtilService wxMiniUtilService;
    @Autowired
    private SystemUserMapper systemUserMapper;
    @Autowired
    private PricingRuleMapper pricingRuleMapper;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private AreasBiz areasBiz;
    @Autowired
    private OperationConfigBiz operationConfigBiz;
    @Autowired
    private AreasService areasService;
    @Override
    public Integer create(Orders orders) {
@@ -102,11 +192,18 @@
        MPJLambdaWrapper<Orders> queryWrapper = new MPJLambdaWrapper<Orders>()
                .selectAll(Orders.class)
                .selectAs(Category::getDetail, Orders::getOrderLevel)
                .leftJoin(Category.class, Category::getId, Orders::getGoodType);
                .select("s1.name", Orders::getDepositShopName)
                .leftJoin(Category.class, Category::getId, Orders::getGoodType)
                .leftJoin(DriverInfo.class, DriverInfo::getId, Orders::getAcceptDriver)
                .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID")
                .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, Utils.Date.getStart(pageWrap.getModel().getCreateStartTime()));
        queryWrapper.le(pageWrap.getModel().getCreateEndTime() != null, Orders::getCreateTime, Utils.Date.getEnd(pageWrap.getModel().getCreateEndTime()));
@@ -114,6 +211,8 @@
        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()));
        for (PageWrap.SortData sortData : pageWrap.getSorts()) {
            if (sortData.getDirection().equalsIgnoreCase(PageWrap.DESC)) {
                queryWrapper.orderByDesc(sortData.getProperty());
@@ -125,9 +224,2637 @@
    }
    @Override
    public OrderSummaryVO findSummary(PageWrap<Orders> pageWrap) {
        // 构建与findPage相同的查询条件
        MPJLambdaWrapper<Orders> queryWrapper = new MPJLambdaWrapper<Orders>()
                .leftJoin(Category.class, Category::getId, Orders::getGoodType)
                .leftJoin(DriverInfo.class, DriverInfo::getId, Orders::getAcceptDriver)
                .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID")
                .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, Utils.Date.getStart(pageWrap.getModel().getCreateStartTime()));
        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());
        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.select(
                "IFNULL(SUM(t.total_amount), 0) as total_amount_sum",
                "IFNULL(SUM(CASE WHEN t.settlement_status = 1 THEN t.total_amount ELSE 0 END), 0) as settled_total_amount_sum",
                "IFNULL(SUM(t.driver_fee), 0) as driver_fee_sum",
                "IFNULL(SUM(CASE WHEN t.settlement_status = 1 THEN t.driver_fee ELSE 0 END), 0) as settled_driver_fee_sum"
        );
        queryWrapper.groupBy("1=1");
        List<Map<String, Object>> result = ordersMapper.selectJoinMaps(queryWrapper);
        OrderSummaryVO vo = new OrderSummaryVO();
        if (result != null && !result.isEmpty()) {
            Map<String, Object> row = result.get(0);
            vo.setTotalAmountSum(toLong(row.get("total_amount_sum")));
            vo.setSettledTotalAmountSum(toLong(row.get("settled_total_amount_sum")));
            vo.setDriverFeeSum(toLong(row.get("driver_fee_sum")));
            vo.setSettledDriverFeeSum(toLong(row.get("settled_driver_fee_sum")));
        } else {
            vo.setTotalAmountSum(0L);
            vo.setSettledTotalAmountSum(0L);
            vo.setDriverFeeSum(0L);
            vo.setSettledDriverFeeSum(0L);
        }
        return vo;
    }
    private Long toLong(Object val) {
        if (val == null) return 0L;
        if (val instanceof Number) return ((Number) val).longValue();
        return Long.parseLong(val.toString());
    }
    @Override
    public BigDecimal calculateInsuranceFee(BigDecimal declaredValue) {
        if (declaredValue == null || declaredValue.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        String rateStr = systemDictDataBiz.queryByCode(Constants.OPERATION_CONFIG, Constants.OP_INSURANCE_RATE).getCode();
        BigDecimal rate = new BigDecimal(rateStr);
        return declaredValue.multiply(rate).setScale(2, BigDecimal.ROUND_HALF_UP);
    }
    /**
     * 计算就地存取预估费用
     *
     * 计算规则:
     * 1. 根据城市+物品类型 查询 pricing_rule(type=0),fieldA=categoryId, fieldB=单价(分/天)
     * 2. 每项小计 = 单价 × 数量 × 天数
     * 3. 物品价格 = 各项小计之和
     * 4. 保价费用 = 报价金额 × 保价费率(字典 INSURANCE_RATE),元转分
     * 5. 总价格 = 物品价格 + 保价费用
     *
     * @param dto 就地存取计价请求参数
     * @return 价格计算结果
     */
    @Override
    public PriceCalculateVO calculateLocalPrice(CalculateLocalPriceDTO dto) {
        // 天数校验,最少1天
        int days = dto.getEstimatedDepositDays() != null && dto.getEstimatedDepositDays() > 0
                ? dto.getEstimatedDepositDays() : 1;
        // 收集所有物品类型ID
        List<Integer> categoryIds = new ArrayList<>();
        for (OrderItemDTO item : dto.getItems()) {
            categoryIds.add(item.getCategoryId());
        }
        // 批量查询计价规则 pricing_rule type=0:fieldA=categoryId, fieldB=单价(分/天)
        List<String> fieldAList = new ArrayList<>();
        for (Integer cid : categoryIds) {
            fieldAList.add(String.valueOf(cid));
        }
        List<PricingRule> rules = pricingRuleMapper.selectList(new QueryWrapper<PricingRule>().lambda()
                .eq(PricingRule::getDeleted, Constants.ZERO)
                .eq(PricingRule::getType, Constants.ZERO)
                .eq(PricingRule::getCityId, dto.getCityId())
                .in(PricingRule::getFieldA, fieldAList));
        Map<String, PricingRule> ruleMap = new HashMap<>();
        for (PricingRule r : rules) {
            ruleMap.put(r.getFieldA(), r);
        }
        // 批量查询物品类型名称
        List<Category> categories = categoryMapper.selectBatchIds(categoryIds);
        Map<Integer, String> categoryNameMap = new HashMap<>();
        Map<Integer, String> categoryDetailMap = new HashMap<>();
        for (Category c : categories) {
            categoryNameMap.put(c.getId(), c.getName());
            categoryDetailMap.put(c.getId(), c.getDetail());
        }
        // 计算每项物品费用:小计 = 单价 × 数量 × 天数
        List<ItemPriceVO> itemList = new ArrayList<>();
        long itemPriceTotal = 0L;
        for (OrderItemDTO item : dto.getItems()) {
            PricingRule rule = ruleMap.get(String.valueOf(item.getCategoryId()));
            if (rule == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
                        "未找到该城市物品类型的计价规则");
            }
            long unitPrice = Long.parseLong(rule.getFieldB());
            long subtotal = unitPrice * item.getQuantity() * days;
            ItemPriceVO vo = new ItemPriceVO();
            vo.setCategoryId(item.getCategoryId());
            vo.setCategoryName(categoryNameMap.getOrDefault(item.getCategoryId(), ""));
            vo.setDetail(categoryDetailMap.get(item.getCategoryId()));
            vo.setQuantity(item.getQuantity());
            vo.setUnitPrice(unitPrice);
            vo.setLocallyPrice(unitPrice);
            vo.setSubtotal(subtotal);
            itemList.add(vo);
            itemPriceTotal += subtotal;
        }
        // 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分
        long insuranceFeeFen = 0L;
        if (Boolean.TRUE.equals(dto.getInsured()) && dto.getDeclaredAmount() != null) {
            BigDecimal insuranceFeeYuan = calculateInsuranceFee(dto.getDeclaredAmount());
            insuranceFeeFen = insuranceFeeYuan.multiply(new BigDecimal(100)).longValue();
        }
        // 总价格 = 物品价格 + 保价费用
        long totalPrice = itemPriceTotal + insuranceFeeFen;
        PriceCalculateVO result = new PriceCalculateVO();
        result.setItemList(itemList);
        result.setItemPrice(itemPriceTotal);
        result.setInsuranceFee(insuranceFeeFen);
        result.setTotalPrice(totalPrice);
        result.setDays(days);
        result.setUrgentFee(0L);
        return result;
    }
    /**
     * 计算异地存取预估费用
     *
     * 计算规则:
     * 1. 调用腾讯地图API计算寄件点与取件点的驾车距离(米→公里)
     * 2. 根据城市+物品类型 查询 pricing_rule(type=1):
     *    fieldB=起步距离(km), fieldC=起步价(分), fieldD=超出距离单位(km), fieldE=超出距离单价(分)
     * 3. 每项运费单价:
     *    - 距离 ≤ 起步距离 → 单价 = 起步价
     *    - 距离 > 起步距离 → 单价 = 起步价 + ceil((距离-起步距离)/超出距离单位) × 超出距离单价
     * 4. 小计 = 运费单价 × 数量
     * 5. 物品价格 = 各项小计之和
     * 6. 保价费用 = 报价金额 × 保价费率(字典 INSURANCE_RATE),元转分
     * 7. 加急费用 = 物品价格 × 加急系数(字典 URGENT_COEFFICIENT)
     * 8. 总价格 = 物品价格 + 保价费用 + 加急费用
     *
     * @param dto 异地存取计价请求参数
     * @return 价格计算结果
     */
    @Override
    public PriceCalculateVO calculateRemotePrice(CalculateRemotePriceDTO dto) {
        // 1. 调用腾讯地图距离矩阵API计算驾车距离
        String from = dto.getFromLat() + "," + dto.getFromLgt();
        String to = dto.getToLat() + "," + dto.getToLgt();
        JSONObject distanceResult = MapUtil.distanceSingle("driving", from, to);
        BigDecimal distance = distanceResult.getBigDecimal("distance");
        // distance 单位为米,转为公里
        BigDecimal distanceKm = distance.divide(new BigDecimal(1000), 2, RoundingMode.HALF_UP);
        // 收集所有物品类型ID
        List<Integer> categoryIds = new ArrayList<>();
        for (OrderItemDTO item : dto.getItems()) {
            categoryIds.add(item.getCategoryId());
        }
        // 2. 批量查询配送计价规则 pricing_rule type=1
        List<String> fieldAList = new ArrayList<>();
        for (Integer cid : categoryIds) {
            fieldAList.add(String.valueOf(cid));
        }
        List<PricingRule> rules = pricingRuleMapper.selectList(new QueryWrapper<PricingRule>().lambda()
                .eq(PricingRule::getDeleted, Constants.ZERO)
                .eq(PricingRule::getType, Constants.ONE)
                .eq(PricingRule::getCityId, dto.getCityId())
                .in(PricingRule::getFieldA, fieldAList));
        Map<String, PricingRule> ruleMap = new HashMap<>();
        for (PricingRule r : rules) {
            ruleMap.put(r.getFieldA(), r);
        }
        // 查询就地存取计价规则 pricing_rule type=0,用于获取 locallyPrice
        List<PricingRule> localRules = pricingRuleMapper.selectList(new QueryWrapper<PricingRule>().lambda()
                .eq(PricingRule::getDeleted, Constants.ZERO)
                .eq(PricingRule::getType, Constants.ZERO)
                .eq(PricingRule::getCityId, dto.getCityId())
                .in(PricingRule::getFieldA, fieldAList));
        Map<String, PricingRule> localRuleMap = new HashMap<>();
        for (PricingRule r : localRules) {
            localRuleMap.put(r.getFieldA(), r);
        }
        // 批量查询物品类型名称
        List<Category> categories = categoryMapper.selectBatchIds(categoryIds);
        Map<Integer, String> categoryNameMap = new HashMap<>();
        Map<Integer, String> categoryDetailMap = new HashMap<>();
        for (Category c : categories) {
            categoryNameMap.put(c.getId(), c.getName());
            categoryDetailMap.put(c.getId(), c.getDetail());
        }
        // 3. 逐项计算运费:起步价 + 超出部分阶梯价
        List<ItemPriceVO> itemList = new ArrayList<>();
        long itemPriceTotal = 0L;
        for (OrderItemDTO item : dto.getItems()) {
            PricingRule rule = ruleMap.get(String.valueOf(item.getCategoryId()));
            if (rule == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
                        "未找到该城市物品类型的配送计价规则");
            }
            // fieldB=起步距离(km), fieldC=起步价(分), fieldD=超出距离单位(km), fieldE=超出距离单价(分)
            BigDecimal startDistance = new BigDecimal(rule.getFieldB());
            long startPrice = Long.parseLong(rule.getFieldC());
            BigDecimal extraDistanceUnit = new BigDecimal(rule.getFieldD());
            long extraPricePerUnit = Long.parseLong(rule.getFieldE());
            // 阶梯计价:距离 ≤ 起步距离取起步价,超出按 ceil(超出距离/单位) × 单价累加
            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;
            }
            long subtotal = unitPrice * item.getQuantity();
            // 就地存取单价
            PricingRule localRule = localRuleMap.get(String.valueOf(item.getCategoryId()));
            Long locallyPrice = localRule != null ? Long.parseLong(localRule.getFieldB()) : null;
            ItemPriceVO vo = new ItemPriceVO();
            vo.setCategoryId(item.getCategoryId());
            vo.setCategoryName(categoryNameMap.getOrDefault(item.getCategoryId(), ""));
            vo.setDetail(categoryDetailMap.get(item.getCategoryId()));
            vo.setQuantity(item.getQuantity());
            vo.setUnitPrice(unitPrice);
            vo.setLocallyPrice(locallyPrice);
            vo.setSubtotal(subtotal);
            vo.setStartDistance(startDistance);
            vo.setStartPrice(startPrice);
            vo.setExtraDistance(extraDistanceUnit);
            vo.setExtraPrice(extraPricePerUnit);
            itemList.add(vo);
            itemPriceTotal += subtotal;
        }
        // 4. 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分
        long insuranceFeeFen = 0L;
        if (Boolean.TRUE.equals(dto.getInsured()) && dto.getDeclaredAmount() != null) {
            BigDecimal insuranceFeeYuan = calculateInsuranceFee(dto.getDeclaredAmount());
            insuranceFeeFen = insuranceFeeYuan.multiply(new BigDecimal(100)).longValue();
        }
        // 5. 加急费用:物品价格 × 加急系数(字典 URGENT_COEFFICIENT)
        long urgentFeeFen = 0L;
        if (Boolean.TRUE.equals(dto.getUrgent())) {
            String urgentRateStr = systemDictDataBiz.queryByCode(
                    Constants.OPERATION_CONFIG, Constants.OP_URGENT_COEFFICIENT).getCode();
            BigDecimal urgentRate = new BigDecimal(urgentRateStr);
            urgentFeeFen = new BigDecimal(itemPriceTotal).multiply(urgentRate)
                    .setScale(0, RoundingMode.HALF_UP).longValue();
        }
        // 6. 总价格 = 物品价格 + 保价费用 + 加急费用
        long totalPrice = itemPriceTotal + insuranceFeeFen + urgentFeeFen;
        PriceCalculateVO result = new PriceCalculateVO();
        result.setItemList(itemList);
        result.setItemPrice(itemPriceTotal);
        result.setInsuranceFee(insuranceFeeFen);
        result.setUrgentFee(urgentFeeFen);
        result.setTotalPrice(totalPrice);
        result.setDistance(distanceKm);
        return result;
    }
    @Override
    public long count(Orders orders) {
        QueryWrapper<Orders> wrapper = new QueryWrapper<>(orders);
        return ordersMapper.selectCount(wrapper);
    }
    /**
     * 创建订单
     *
     * 业务流程:
     * 1. 参数校验:必填字段、时间顺序、物品不重复、异地必填服务时效
     * 2. 查询寄件店铺信息(获取经纬度、地址)
     * 3. 调用计价接口计算费用(calculateLocalPrice / calculateRemotePrice)
     * 4. 生成订单编号:JC + yyyyMMddHHmmss + 4位随机数
     * 5. 创建订单主表 Orders(状态=待支付)
     * 6. 创建订单明细表 OrdersDetail(每项物品尺寸)
     * 7. 创建附件记录 Multifile(物品图片 objType=12)
     *
     * @param dto      创建订单请求参数
     * @param memberId 当前登录会员ID
     * @return 订单ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayResponse createOrder(CreateOrderDTO dto, Integer memberId) {
        String lockKey  = Constants.GOODS_ORDER_CREATE_LOCK + memberId;
        //判断前端是否在同一页面创建了两次订单
        if (redisTemplate.hasKey(lockKey)) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"正在创建订单,请勿重复调用!");
        } else {
            redisTemplate.opsForValue().set(lockKey, "", 5, TimeUnit.SECONDS);
        }
        Date now = new Date();
        // ========== 1. 参数校验 ==========
        // 预计到店存件时间必须小于预计到店取件时间
        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm");
        Date depositTime;
        Date takeTime;
        try {
            depositTime = sdf.parse(dto.getExpectedDepositTime());
            takeTime = sdf.parse(dto.getExpectedTakeTime());
        } catch (java.text.ParseException e) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "时间格式错误,正确格式:yyyy-MM-dd HH:mm");
        }
        if (!depositTime.before(takeTime)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "预计到店存件时间必须小于预计到店取件时间");
        }
        // 物品尺寸不能重复
        List<Integer> categoryIds = new ArrayList<>();
        for (OrderItemDTO item : dto.getItems()) {
            if (categoryIds.contains(item.getCategoryId())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "物品尺寸不能重复");
            }
            categoryIds.add(item.getCategoryId());
        }
        // 物品图片最多3张
        if (dto.getGoodsImages() != null && dto.getGoodsImages().size() > 3) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "物品图片最多3张");
        }
        // ========== 2. 校验物品类型 ==========
        Category goodTypeCategory = categoryMapper.selectById(dto.getGoodType());
        if (goodTypeCategory == null || Constants.equalsInteger(goodTypeCategory.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "物品类型不存在");
        }
        if (!Constants.equalsInteger(goodTypeCategory.getType(), Constants.TWO)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "物品类型参数错误");
        }
        // ========== 3. 查询寄件店铺信息 ==========
        ShopInfo depositShop = shopInfoMapper.selectById(dto.getDepositShopId());
        if (depositShop == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "寄件店铺不存在");
        }
        // ========== 4. 计算费用 ==========
        // 异地寄存:校验取件点
        BigDecimal takeLat = null;
        BigDecimal takeLgt = null;
        String takeLocationValue = null;
        ShopInfo takeShop = null;
        if (Constants.ONE.equals(dto.getType())) {
            // 异地必填服务时效
            if (dto.getIsUrgent() == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "异地寄存服务时效不能为空");
            }
            // 取件点:店铺 or 自选点,至少提供一组
            if (dto.getTakeShopId() != null) {
                takeShop = shopInfoMapper.selectById(dto.getTakeShopId());
                if (takeShop == null) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "取件店铺不存在");
                }
                takeLat = BigDecimal.valueOf(takeShop.getLatitude());
                takeLgt = BigDecimal.valueOf(takeShop.getLongitude());
                takeLocationValue = takeShop.getAddress();
            } else if (dto.getTakeLat() != null && dto.getTakeLgt() != null && StringUtils.isNotBlank(dto.getTakeLocation())) {
                takeLat = dto.getTakeLat();
                takeLgt = dto.getTakeLgt();
                takeLocationValue = dto.getTakeLocation();
            } else {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "请选择取件店铺或输入自选取件地址");
            }
        } else {
            // 就地存取:取件门店同寄件门店
            takeShop = depositShop;
        }
        // ========== 3. 计算费用 ==========
        PriceCalculateVO priceResult;
        if (Constants.ZERO.equals(dto.getType())) {
            // 就地寄存:计算天数
            long diffMs = takeTime.getTime() - depositTime.getTime();
            int days = (int) Math.max(1, (diffMs / (1000 * 60 * 60 * 24)) + 1);
            CalculateLocalPriceDTO priceDTO = new CalculateLocalPriceDTO();
            priceDTO.setCityId(dto.getCityId());
            priceDTO.setEstimatedDepositDays(days);
            priceDTO.setItems(dto.getItems());
            priceDTO.setInsured(dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0);
            priceDTO.setDeclaredAmount(dto.getDeclaredAmount());
            priceResult = calculateLocalPrice(priceDTO);
        } else {
            // 异地寄存
            CalculateRemotePriceDTO priceDTO = new CalculateRemotePriceDTO();
            priceDTO.setCityId(dto.getCityId());
            priceDTO.setFromLat(BigDecimal.valueOf(depositShop.getLatitude()));
            priceDTO.setFromLgt(BigDecimal.valueOf(depositShop.getLongitude()));
            priceDTO.setToLat(takeLat);
            priceDTO.setToLgt(takeLgt);
            priceDTO.setItems(dto.getItems());
            priceDTO.setInsured(dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0);
            priceDTO.setDeclaredAmount(dto.getDeclaredAmount());
            priceDTO.setUrgent(Constants.ONE.equals(dto.getIsUrgent()));
            priceResult = calculateRemotePrice(priceDTO);
        }
        // ========== 5. 生成订单编号 ==========
        String orderCode = "JC" + new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(now)
                + String.format("%04d", new java.util.Random().nextInt(10000));
        // 生成32位唯一第三方订单编号
        String orderTradeNo = generateOrderTradeNo();
        // ========== 6. 创建订单主表 ==========
        Orders orders = new Orders();
        orders.setCode(orderCode);
        orders.setOutTradeNo(orderTradeNo);
        orders.setMemberId(memberId);
        orders.setType(dto.getType());
        orders.setCityId(String.valueOf(dto.getCityId()));
        orders.setStatus(Constants.ZERO); // 待支付
        orders.setPayStatus(Constants.ZERO); // 未支付
        orders.setCommentStatus(Constants.ZERO); // 未评价
        orders.setSettlementStatus(Constants.ZERO); // 未结算
        orders.setDeleted(Constants.ZERO);
        orders.setCreateTime(now);
        orders.setUpdateTime(now);
        // 寄件信息
        orders.setDepositShopId(dto.getDepositShopId());
        // 存件地点:省市区全路径 + 地址描述
        String depositLocationRemark = depositShop.getAddress();
        if (depositShop.getAreaId() != null) {
            Areas depositArea = areasBiz.resolveArea(depositShop.getAreaId());
            if (depositArea != null) {
                depositLocationRemark = depositArea.getProvinceName() + depositArea.getCityName() + depositArea.getName() + depositShop.getAddress();
            }
        }
        orders.setDepositLocation(depositLocationRemark);
        orders.setDepositLocationRemark(depositShop.getAddress());
        orders.setDepositLat(BigDecimal.valueOf(depositShop.getLatitude()));
        orders.setDepositLgt(BigDecimal.valueOf(depositShop.getLongitude()));
        // 取件信息
        orders.setTakeUser(dto.getTakeUser());
        orders.setTakePhone(dto.getTakePhone());
        orders.setExpectedDepositTime(depositTime);
        orders.setExpectedTakeTime(takeTime);
        // 计算预计存放天数
        long dayDiff = (takeTime.getTime() - depositTime.getTime()) / (1000 * 60 * 60 * 24);
        orders.setEstimatedDepositDays((int) Math.max(1, dayDiff + 1));
        if (Constants.ONE.equals(dto.getType())) {
            // 异地:取件点信息
            orders.setTakeShopId(dto.getTakeShopId());
            orders.setTakeLocation(takeLocationValue);
            orders.setTakeLat(takeLat);
            orders.setTakeLgt(takeLgt);
            orders.setIsUrgent(dto.getIsUrgent());
        } else {
            // 就地:取件点同寄件店铺
            orders.setTakeShopId(dto.getDepositShopId());
            orders.setTakeLocation(depositShop.getAddress());
            orders.setTakeLat(BigDecimal.valueOf(depositShop.getLatitude()));
            orders.setTakeLgt(BigDecimal.valueOf(depositShop.getLongitude()));
            orders.setIsUrgent(Constants.ZERO);
        }
        // 物品信息
        orders.setGoodType(dto.getGoodType());
        // 拼接物品信息:物品类型名称、尺寸名称*数量(数组字符串)
        List<String> goodsParts = new ArrayList<>();
        for (ItemPriceVO itemVO : priceResult.getItemList()) {
            goodsParts.add(itemVO.getCategoryName() + "*" + itemVO.getQuantity());
        }
        orders.setGoodsInfo(goodTypeCategory.getName() + "、" + String.join(",", goodsParts));
        orders.setRemark(dto.getRemark());
        orders.setSelfTake(Constants.ZERO);
        // 费用信息(分)
        orders.setBasicAmount(priceResult.getItemPrice());
        orders.setEstimatedAmount(priceResult.getTotalPrice());
        orders.setTotalAmount(priceResult.getTotalPrice());
        orders.setUrgentAmount(priceResult.getUrgentFee());
        if (dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0) {
            orders.setDeclaredAmount(dto.getDeclaredAmount().multiply(new BigDecimal(100)).longValue());
        } else {
            orders.setDeclaredAmount(0L);
        }
        orders.setDeclaredFee(priceResult.getInsuranceFee());
        orders.setPrice(priceResult.getItemPrice());
        // 薪酬计算与占比存储
        calculateAndSetFeeAllocation(orders, depositShop, takeShop);
        ordersMapper.insert(orders);
        Integer orderId = orders.getId();
        // ========== 7. 创建订单明细 ==========
        for (ItemPriceVO itemVO : priceResult.getItemList()) {
            OrdersDetail detail = new OrdersDetail();
            detail.setOrderId(orderId);
            detail.setLuggageId(itemVO.getCategoryId());
            detail.setLuggageName(itemVO.getCategoryName());
            detail.setLuggageDetail(itemVO.getDetail());
            detail.setNum(itemVO.getQuantity());
            detail.setUnitPrice(itemVO.getUnitPrice());
            detail.setStartDistance(itemVO.getStartDistance());
            detail.setStartPrice(itemVO.getStartPrice());
            detail.setExtraDistance(itemVO.getExtraDistance());
            detail.setExtraPrice(itemVO.getExtraPrice());
            detail.setLocallyPrice(itemVO.getLocallyPrice());
            detail.setDeleted(Constants.ZERO);
            detail.setCreateTime(now);
            ordersDetailMapper.insert(detail);
        }
        // ========== 8. 保存物品图片附件 ==========
        if (dto.getGoodsImages() != null && !dto.getGoodsImages().isEmpty()) {
            int sortNum = 1;
            for (String imgUrl : dto.getGoodsImages()) {
                Multifile multifile = new Multifile();
                multifile.setObjId(orderId);
                multifile.setObjType(Constants.FileType.ORDER_FILE.getKey());
                multifile.setType(Constants.ZERO);
                multifile.setFileurl(imgUrl);
                multifile.setIsdeleted(Constants.ZERO);
                multifile.setCreateDate(now);
                multifile.setSortnum(sortNum++);
                multifileMapper.insert(multifile);
            }
        }
        // ========== 9. 唤起微信支付 ==========
        Member member = memberMapper.selectById(memberId);
        if (member == null || StringUtils.isBlank(member.getOpenid())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付");
        }
        PayResponse payResponse = wxPay(orders, member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER);
        payResponse.setLockKey(lockKey);
        return payResponse;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayResponse continuePay(Integer orderId, Integer memberId) {
        // 1. 查询订单
        Orders orders = ordersMapper.selectById(orderId);
        if (Objects.isNull(orders) || Constants.equalsInteger(orders.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        // 2. 校验订单归属
        if (!Constants.equalsInteger(orders.getMemberId(), memberId)) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "无权操作该订单");
        }
        // 3. 校验订单状态:仅待支付可继续支付
        if (!Constants.equalsInteger(orders.getStatus(), Constants.ZERO)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不支持继续支付");
        }
        // 4. 校验支付金额
        if (orders.getTotalAmount() == null || orders.getTotalAmount() <= 0) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单金额异常,无法发起支付");
        }
        // 5. 重新生成第三方订单编号(避免重复)
        String orderTradeNo = generateOrderTradeNo();
        orders.setOutTradeNo(orderTradeNo);
        orders.setUpdateTime(new Date());
        ordersMapper.updateById(orders);
        // 6. 唤起微信支付
        Member member = memberMapper.selectById(memberId);
        if (member == null || StringUtils.isBlank(member.getOpenid())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付");
        }
        return wxPay(orders, member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER);
    }
    /**
     * 唤起微信小程序支付
     *
     * @param orders       订单实体(需要 code、totalAmount)
     * @param openid       用户微信openid
     * @param ordersAttach 订单支付类型
     * @return PayResponse 包含微信调起参数和订单主键
     */
    private PayResponse wxPay(Orders orders, String openid, Constants.OrdersAttach ordersAttach) {
        try {
            WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
            request.setBody(ordersAttach.getName());
            request.setAttach(ordersAttach.getKey());
            request.setOutTradeNo(orders.getOutTradeNo());
            // totalAmount 单位为分,WeChat Pay setTotalFee 也是分,直接转int
            long totalFee = orders.getTotalAmount() != null ? orders.getTotalAmount() : 0L;
            request.setTotalFee((int) totalFee);
            request.setTimeStart(DateUtil.DateToString(new Date(), "yyyyMMddHHmmss"));
            request.setSpbillCreateIp(Constants.getIpAddr());
            request.setOpenid(openid);
            Object response = WxMiniConfig.wxPayService.createOrder(request);
            PayResponse payResponse = new PayResponse();
            payResponse.setResponse(response);
            payResponse.setOrderId(orders.getId());
            return payResponse;
        } catch (WxPayException e) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "支付调起失败:" + e.getMessage());
        }
    }
    @Override
    public OrderDetailVO findDetail(Integer id) {
        Orders order = ordersMapper.selectById(id);
        if (Objects.isNull(order)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        OrderDetailVO vo = new OrderDetailVO();
        vo.setOrder(order);
        // 图片路径前缀
        String imgPrefix = getOrdersPrefix();
        // 下单图片 (type=12)
        vo.setOrderFiles(getFileUrls(id, Constants.FileType.ORDER_FILE.getKey(), imgPrefix));
        // 会员信息
        if (order.getMemberId() != null) {
            Member member = memberMapper.selectById(order.getMemberId());
            if (member != null) {
                vo.setMemberName(member.getName());
                vo.setMemberPhone(member.getTelephone());
            }
        }
        // 寄存门店信息
        if (order.getDepositShopId() != null) {
            ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId());
            if (depositShop != null) {
                vo.setDepositShopName(depositShop.getName());
                vo.setDepositShopPhone(depositShop.getLinkPhone());
            }
        }
        // 取件门店信息
        if (order.getTakeShopId() != null) {
            ShopInfo takeShop = shopInfoMapper.selectById(order.getTakeShopId());
            if (takeShop != null) {
                vo.setTakeShopName(takeShop.getName());
                vo.setTakeShopAddress(takeShop.getAddress());
                vo.setTakeShopPhone(takeShop.getLinkPhone());
            }
        }
        // 接单司机信息
        if (order.getAcceptDriver() != null) {
            DriverInfo driverInfo = driverInfoMapper.selectById(order.getAcceptDriver());
            if (driverInfo != null) {
                vo.setDriverName(driverInfo.getName());
            }
        }
        // 配送附件图片
        vo.setDepositImages(getFileUrls(id, Constants.FileType.ORDER_DEPOSIT.getKey(), imgPrefix));
        vo.setStoreInImages(getFileUrls(id, Constants.FileType.ORDER_TAKE.getKey(), imgPrefix));
        vo.setDriverTakeImages(getFileUrls(id, Constants.FileType.DRIVER_TAKE.getKey(), imgPrefix));
        vo.setDriverDoneImages(getFileUrls(id, Constants.FileType.DRIVER_DONE.getKey(), imgPrefix));
        vo.setStoreOutImages(getFileUrls(id, Constants.FileType.STORE_OUT.getKey(), imgPrefix));
        // 物品明细
        vo.setDetailList(buildDetailList(id));
        Category category = categoryMapper.selectById(order.getGoodType());
        if(CollectionUtils.isNotEmpty(vo.getDetailList())&&Objects.nonNull(category)){
            for (OrderItemVO v:vo.getDetailList()) {
                v.setTypeName(category.getName());
            }
        }
        // 取消/退款状态时查询退款记录
        Integer status = order.getStatus();
        if (status != null && (status == Constants.OrderStatus.overdue.getStatus()
                || status == Constants.OrderStatus.closed.getStatus()
                || status == Constants.OrderStatus.cancelOverdue.getStatus()
                || status == Constants.OrderStatus.cancelling.getStatus()
                || status == Constants.OrderStatus.cancelled.getStatus())) {
            OrdersRefund ordersRefund = ordersRefundMapper.selectOne(
                    new QueryWrapper<OrdersRefund>().lambda()
                            .eq(OrdersRefund::getOrderId, id)
                            .eq(OrdersRefund::getDeleted, Constants.ZERO)
                            .orderByDesc(OrdersRefund::getCreateTime)
                            .last("limit 1"));
            if (ordersRefund != null) {
                vo.setOrdersRefund(ordersRefund);
                // 退款方式:1=平台直接取消 → 返回平台操作人名称
                if (Constants.equalsInteger(ordersRefund.getType(), Constants.ONE) && ordersRefund.getUserId() != null) {
                    // userId 关联 system_user 表,查询操作人名称
                    vo.setPlatformUserName(getPlatformUserName(ordersRefund.getUserId()));
                }
                // 退款方式:2=已存件申请取消 → 返回退款取件图片 (multifile objType=14)
                if (Constants.equalsInteger(ordersRefund.getType(), Constants.TWO)) {
                    vo.setRefundTakeImages(getFileUrls(id, Constants.FileType.REFUND_TAKE.getKey(), imgPrefix));
                }
            }
        }
        return vo;
    }
    @Override
    public OrderDispatchVO findDispatchInfo(Integer id) {
        Orders order = ordersMapper.selectById(id);
        if (Objects.isNull(order)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        OrderDispatchVO vo = new OrderDispatchVO();
        vo.setCode(order.getCode());
        vo.setPayAmountYuan(order.getPayAmount() != null ? Constants.getFormatMoney(order.getPayAmount()) : 0);
        vo.setType(order.getType());
        vo.setTypeDesc(order.getType() != null && order.getType() == Constants.ONE ? "异地存取" : "就地存取");
        vo.setDetailList(buildDetailList(id));
        return vo;
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void dispatch(DispatchDTO dto) {
        // 参数校验
        if (dto == null || dto.getOrderId() == null || dto.getUrgentFee() == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单主键和加急费用不能为空");
        }
        Orders order = ordersMapper.selectById(dto.getOrderId());
        if (Objects.isNull(order)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        // 前置条件校验:异地存取 + 已寄存
        if (!Constants.ONE.equals(order.getType())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅支持异地存取订单派单");
        }
        if (!Integer.valueOf(Constants.OrderStatus.deposited.getStatus()).equals(order.getStatus())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅已寄存状态订单可派单");
        }
        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();
        // 使用 UpdateWrapper 精确更新,不影响其他字段
        com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<Orders> updateWrapper =
                new UpdateWrapper<Orders>().lambda()
                .eq(Orders::getId, order.getId())
                .set(Orders::getIsUrgent, Constants.ONE)
                .set(Orders::getPlatformRewardAmount, urgentFeeFen)
                .set(Orders::getUpdateTime, new Date());
        // 异地寄存且有取件门店时,生成司机核销码
        if (order.getTakeShopId() != null) {
            String driverVerifyCode = generateVerifyCode();
            updateWrapper.set(Orders::getDriverVerifyCode, driverVerifyCode);
        }
        // 备注
        if (StringUtils.isNotBlank(dto.getRemark())) {
            updateWrapper.set(Orders::getRemark, dto.getRemark());
        }
        // 指派司机(非必填)
        if (dto.getDriverId() != null) {
            updateWrapper.set(Orders::getAssignDriverId, dto.getDriverId());
            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.setOptUserType(3);
            driverLog.setOptUserName(optUserName);
            driverLog.setCreateTime(new Date());
            driverLog.setDeleted(Constants.ZERO);
            orderLogService.create(driverLog);
        }
        ordersMapper.update(updateWrapper);
    }
    private String getCurrentUserName() {
        try {
            com.doumee.core.model.LoginUserInfo user =
                    (com.doumee.core.model.LoginUserInfo) org.apache.shiro.SecurityUtils.getSubject().getPrincipal();
            return user != null ? user.getUsername() : "系统";
        } catch (Exception e) {
            return "系统";
        }
    }
    /**
     * 构建订单物品明细列表
     */
    private List<OrderItemVO> buildDetailList(Integer orderId) {
        List<OrdersDetail> details = ordersDetailMapper.selectList(
                new QueryWrapper<OrdersDetail>().lambda()
                        .eq(OrdersDetail::getOrderId, orderId)
                        .eq(OrdersDetail::getDeleted, Constants.ZERO));
        return buildDetailList(details);
    }
    /**
     * 根据已查询的明细构建物品列表(避免重复查询)
     */
    private List<OrderItemVO> buildDetailList(List<OrdersDetail> details) {
        List<OrderItemVO> items = new ArrayList<>();
        if (details != null) {
            for (OrdersDetail d : details) {
                OrderItemVO item = new OrderItemVO();
                item.setLuggageName(d.getLuggageName());
                item.setLuggageDetail(d.getLuggageDetail());
                item.setNum(d.getNum());
                double unitPriceYuan = d.getUnitPrice() != null ? Constants.getFormatMoney(d.getUnitPrice()) : 0;
                item.setUnitPriceYuan(unitPriceYuan);
                item.setSubtotal(unitPriceYuan * (d.getNum() != null ? d.getNum() : 0));
                items.add(item);
            }
        }
        return items;
    }
    /**
     * 生成32位唯一第三方订单编号(时间戳17位 + 随机数15位)
     */
    private String generateOrderTradeNo() {
        return new java.text.SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date())
                + String.format("%015d", Math.abs(new java.util.Random().nextLong() % 1000000000000000L));
    }
    /**
     * 生成6位数字核销码(Redis验重)
     * 使用 SETNX 抢占,保证唯一;使用完毕后调用 releaseVerifyCode 从 Redis 移除
     */
    private String generateVerifyCode() {
        Random random = new Random();
        String redisKey = Constants.REDIS_VERIFY_CODE_KEY;
        for (int i = 0; i < 200; i++) {
            String code = String.format("%06d", random.nextInt(1000000));
            Boolean success = redisTemplate.opsForValue().setIfAbsent(redisKey + code, "1", 24, TimeUnit.HOURS);
            if (success != null && success) {
                return code;
            }
        }
        throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "核销码生成失败,请重试");
    }
    /**
     * 释放核销码占位(核销完成后调用,移除 Redis 中的 key)
     */
    public void releaseVerifyCode(String code) {
        if (StringUtils.isNotBlank(code)) {
            redisTemplate.delete(Constants.REDIS_VERIFY_CODE_KEY + code);
        }
    }
    private String getOrdersPrefix() {
        try {
            return systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
                    + systemDictDataBiz.queryByCode(Constants.OSS, Constants.ORDERS_FILES).getCode();
        } catch (Exception e) {
            return "";
        }
    }
    private String getPlatformUserName(Integer userId) {
        SystemUser user = systemUserMapper.selectById(userId);
        return user != null ? user.getRealname() : null;
    }
    private List<String> getFileUrls(Integer orderId, int objType, String prefix) {
        List<Multifile> files = multifileMapper.selectList(
                new QueryWrapper<Multifile>().lambda()
                        .eq(Multifile::getObjId, orderId)
                        .eq(Multifile::getObjType, objType)
                        .eq(Multifile::getIsdeleted, Constants.ZERO)
                        .orderByAsc(Multifile::getSortnum));
        List<String> urls = new ArrayList<>();
        if (files != null) {
            for (Multifile f : files) {
                if (StringUtils.isNotBlank(f.getFileurl())) {
                    urls.add(prefix + f.getFileurl());
                }
            }
        }
        return urls;
    }
    /**
     * 计算并设置订单薪酬分配(司机、存件门店、取件门店)
     * 从 pricing_rule (type=4) 读取分成比例,根据门店企业/个人类型区分
     *
     * @param orders        订单实体(需要 totalAmount、cityId 已设置)
     * @param depositShop   寄件门店(需要 companyType)
     * @param takeShop      取件门店(需要 companyType,就地存取时与 depositShop 相同)
     */
    private void calculateAndSetFeeAllocation(Orders orders, ShopInfo depositShop, ShopInfo takeShop) {
        Long totalAmount = orders.getTotalAmount() != null ? orders.getTotalAmount() : 0L;
        if (totalAmount <= 0) {
            orders.setDriverFee(0L);
            orders.setDepositShopFee(0L);
            orders.setTakeShopFee(0L);
            orders.setDriverFeeRata(BigDecimal.ZERO);
            orders.setDepositShopFeeRata(BigDecimal.ZERO);
            orders.setTakeShopFeeRata(BigDecimal.ZERO);
            return;
        }
        Integer cityId = Integer.valueOf(orders.getCityId());
        // 司机占比: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);
        // 取件门店占比:fieldA=2(企业取)/3(个人取)
        int takeFieldA = Constants.equalsInteger(takeShop.getCompanyType(), Constants.ONE) ? Constants.TWO : Constants.THREE;
        BigDecimal takeShopRata = getRevenueShareRata(cityId, takeFieldA);
        // 计算薪酬(分):totalAmount 为分,rata 为比例值(如 0.15 表示 15%)
        long driverFee = new BigDecimal(totalAmount).multiply(driverRata).longValue();
        long depositShopFee = new BigDecimal(totalAmount).multiply(depositShopRata).longValue();
        long takeShopFee = totalAmount - driverFee - depositShopFee;
        orders.setDriverFee(driverFee);
        orders.setDepositShopFee(depositShopFee);
        orders.setTakeShopFee(takeShopFee);
        orders.setDriverFeeRata(driverRata);
        orders.setDepositShopFeeRata(depositShopRata);
        orders.setTakeShopFeeRata(takeShopRata);
    }
    /**
     * 从 pricing_rule 表获取分成比例(type=4)
     *
     * @param cityId   城市主键
     * @param fieldA   类型:0=企业寄, 1=个人寄, 2=企业取, 3=个人取, 4=配送员
     * @return 分成比例(如 0.15 表示 15%)
     */
    private BigDecimal getRevenueShareRata(Integer cityId, int fieldA) {
        PricingRule rule = pricingRuleMapper.selectOne(new QueryWrapper<PricingRule>().lambda()
                .eq(PricingRule::getDeleted, Constants.ZERO)
                .eq(PricingRule::getType, Constants.FOUR)
                .eq(PricingRule::getCityId, cityId)
                .eq(PricingRule::getFieldA, String.valueOf(fieldA))
                .last("limit 1"));
        if (rule != null && StringUtils.isNotBlank(rule.getFieldC())) {
            return new BigDecimal(rule.getFieldC());
        }
        return BigDecimal.ZERO;
    }
    @Override
    public PageData<MyOrderVO> findMyOrderPage(PageWrap<MyOrderDTO> pageWrap, Integer memberId) {
        MyOrderDTO model = pageWrap.getModel();
        Integer status = model != null ? model.getStatus() : null;
        Integer combinedStatus = model != null ? model.getCombinedStatus() : null;
        // 解析合并状态为具体状态列表
        List<Integer> statusList = null;
        if (combinedStatus != null) {
            Constants.OrderCombinedStatus combined = Constants.OrderCombinedStatus.getByKey(combinedStatus);
            if (combined != null) {
                statusList = new ArrayList<>();
                for (int s : combined.getStatuses()) {
                    statusList.add(s);
                }
            }
        }
        IPage<Orders> p = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
        MPJLambdaWrapper<Orders> wrapper = new MPJLambdaWrapper<Orders>()
                .selectAll(Orders.class)
                .select("s1.name", Orders::getDepositShopName)
                .select("s1.link_name", Orders::getDepositShopLinkName)
                .select("s1.link_phone", Orders::getDepositShopLinkPhone)
                .select("s2.name", Orders::getTakeShopName)
                .select("s2.address", Orders::getTakeShopAddress)
                .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID")
                .leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID")
                .eq(Orders::getDeleted, Constants.ZERO)
                .eq(Orders::getMemberId, memberId)
                .eq(status != null, Orders::getStatus, status)
                .in(statusList != null, Orders::getStatus, statusList)
                .orderByDesc(Orders::getCreateTime);
        IPage<Orders> orderPage = ordersMapper.selectJoinPage(p, Orders.class, wrapper);
        List<MyOrderVO> voList = new ArrayList<>();
        if (orderPage != null && orderPage.getRecords() != null) {
            for (Orders o : orderPage.getRecords()) {
                MyOrderVO vo = new MyOrderVO();
                vo.setId(o.getId());
                vo.setCode(o.getCode());
                vo.setType(o.getType());
                vo.setStatus(o.getStatus());
                vo.setCreateTime(o.getCreateTime());
                vo.setExpectedTakeTime(o.getExpectedTakeTime());
                // 存件门店(关联查询直接取值)
                vo.setDepositShopName(o.getDepositShopName());
                vo.setDepositShopLinkName(o.getDepositShopLinkName());
                vo.setDepositShopPhone(o.getDepositShopLinkPhone());
                // 取件信息:有取件门店取门店,无则取用户自选取件点
                if (o.getTakeShopId() != null) {
                    vo.setTakeShopName(o.getTakeShopName());
                    vo.setTakeShopAddress(o.getTakeShopAddress());
                } else {
                    vo.setTakeLocation(o.getTakeLocation());
                    vo.setTakeLocationRemark(o.getTakeLocationRemark());
                }
                // 取件联系人
                vo.setTakeUser(o.getTakeUser());
                vo.setTakePhone(o.getTakePhone());
                // 费用(分)
                vo.setDeclaredFee(o.getDeclaredFee());
                vo.setEstimatedAmount(o.getEstimatedAmount());
                // 查询物品明细(一次查询,同时用于物品列表和逾期计算)
                List<OrdersDetail> details = ordersDetailMapper.selectList(
                        new QueryWrapper<OrdersDetail>().lambda()
                                .eq(OrdersDetail::getOrderId, o.getId())
                                .eq(OrdersDetail::getDeleted, Constants.ZERO));
                // 物品明细
                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());
                }
                voList.add(vo);
            }
        }
        IPage<MyOrderVO> vPage = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
        PageData<MyOrderVO> pageData = PageData.from(vPage);
        pageData.setRecords(voList);
        pageData.setTotal(orderPage.getTotal());
        pageData.setPage(orderPage.getCurrent());
        pageData.setCapacity(orderPage.getSize());
        return pageData;
    }
    @Override
    public PageData<MyOrderVO> findShopOrderPage(PageWrap<MyOrderDTO> pageWrap, Integer shopId) {
        MyOrderDTO model = pageWrap.getModel();
        Integer status = model != null ? model.getStatus() : null;
        Integer combinedStatus = model != null ? model.getCombinedStatus() : null;
        // 解析合并状态为具体状态列表
        List<Integer> statusList = null;
        if (combinedStatus != null) {
            Constants.OrderCombinedStatus combined = Constants.OrderCombinedStatus.getByKey(combinedStatus);
            if (combined != null) {
                statusList = new ArrayList<>();
                for (int s : combined.getStatuses()) {
                    statusList.add(s);
                }
            }
        }
        IPage<Orders> p = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
        MPJLambdaWrapper<Orders> wrapper = new MPJLambdaWrapper<Orders>()
                .selectAll(Orders.class)
                .select("s1.name", Orders::getDepositShopName)
                .select("s1.link_name", Orders::getDepositShopLinkName)
                .select("s1.link_phone", Orders::getDepositShopLinkPhone)
                .select("s2.name", Orders::getTakeShopName)
                .select("s2.address", Orders::getTakeShopAddress)
                .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);
        IPage<Orders> orderPage = ordersMapper.selectJoinPage(p, Orders.class, wrapper);
        List<MyOrderVO> voList = new ArrayList<>();
        if (orderPage != null && orderPage.getRecords() != null) {
            for (Orders o : orderPage.getRecords()) {
                MyOrderVO vo = new MyOrderVO();
                vo.setId(o.getId());
                vo.setCode(o.getCode());
                vo.setType(o.getType());
                vo.setStatus(o.getStatus());
                vo.setCreateTime(o.getCreateTime());
                vo.setExpectedTakeTime(o.getExpectedTakeTime());
                vo.setDepositShopName(o.getDepositShopName());
                vo.setDepositShopLinkName(o.getDepositShopLinkName());
                vo.setDepositShopPhone(o.getDepositShopLinkPhone());
                // 门店角色:存件门店=1,取件门店=2
                if (Constants.equalsInteger(o.getDepositShopId(), shopId)) {
                    vo.setShopRole(Constants.ONE);
                } else if (Constants.equalsInteger(o.getTakeShopId(), shopId)) {
                    vo.setShopRole(Constants.TWO);
                }
                if (o.getTakeShopId() != null) {
                    vo.setTakeShopName(o.getTakeShopName());
                    vo.setTakeShopAddress(o.getTakeShopAddress());
                } else {
                    vo.setTakeLocation(o.getTakeLocation());
                    vo.setTakeLocationRemark(o.getTakeLocationRemark());
                }
                vo.setTakeUser(o.getTakeUser());
                vo.setTakePhone(o.getTakePhone());
                vo.setDeclaredFee(o.getDeclaredFee());
                vo.setEstimatedAmount(o.getEstimatedAmount());
                List<OrdersDetail> details = ordersDetailMapper.selectList(
                        new QueryWrapper<OrdersDetail>().lambda()
                                .eq(OrdersDetail::getOrderId, o.getId())
                                .eq(OrdersDetail::getDeleted, Constants.ZERO));
                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());
                }
                voList.add(vo);
            }
        }
        IPage<MyOrderVO> vPage = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
        PageData<MyOrderVO> pageData = PageData.from(vPage);
        pageData.setRecords(voList);
        pageData.setTotal(orderPage.getTotal());
        pageData.setPage(orderPage.getCurrent());
        pageData.setCapacity(orderPage.getSize());
        return pageData;
    }
    @Override
    public MyOrderDetailVO findMyOrderDetail(Integer id, Integer memberId) {
        Orders order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getId, id)
                .eq(Orders::getMemberId, memberId)
                .eq(Orders::getDeleted, Constants.ZERO));
        if (order == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        return buildOrderDetailVO(order, true);
    }
    @Override
    public MyOrderDetailVO findShopOrderDetail(Integer orderId, String verifyCode) {
        Orders order = null;
        if (orderId != null) {
            order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
                    .eq(Orders::getId, orderId)
                    .eq(Orders::getDeleted, Constants.ZERO));
        } else if (StringUtils.isNotBlank(verifyCode)) {
            order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
                    .eq(Orders::getMemberVerifyCode, verifyCode)
                    .eq(Orders::getDeleted, Constants.ZERO)
                    .last("limit 1"));
            if (order == null) {
                order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
                        .eq(Orders::getDriverVerifyCode, verifyCode)
                        .eq(Orders::getDeleted, Constants.ZERO)
                        .last("limit 1"));
            }
        }
        if (order == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在");
        }
        return buildOrderDetailVO(order, false);
    }
    /**
     * 构建订单详情VO(会员端/门店端复用)
     *
     * @param order          订单实体
     * @param memberViewMode true=会员端(按条件返回核销码),false=门店端(始终返回核销码)
     */
    private MyOrderDetailVO buildOrderDetailVO(Orders order, boolean memberViewMode) {
        MyOrderDetailVO vo = new MyOrderDetailVO();
        vo.setId(order.getId());
        vo.setStatus(order.getStatus());
        vo.setType(order.getType());
        vo.setCode(order.getCode());
        vo.setOutTradeNo(order.getOutTradeNo());
        vo.setRemark(order.getRemark());
        vo.setCreateTime(order.getCreateTime());
        vo.setPayTime(order.getPayTime());
        vo.setExpectedDepositTime(order.getExpectedDepositTime());
        vo.setExpectedTakeTime(order.getExpectedTakeTime());
        vo.setArriveTime(order.getArriveTime());
        // 费用(分)
        vo.setBasicAmount(order.getBasicAmount());
        vo.setDeclaredAmount(order.getDeclaredAmount());
        vo.setDeclaredFee(order.getDeclaredFee());
        vo.setUrgentAmount(order.getUrgentAmount());
        vo.setActualPayAmount(order.getPayAmount());
        // 标记
        vo.setExceptionStatus(order.getExceptionStatus());
        // 是否超出取件时间
        vo.setPastTakeTime(order.getExpectedTakeTime() != null && new Date().after(order.getExpectedTakeTime()));
        // 订单状态描述 + 倒计时
        vo.setStatusDesc(buildStatusDesc(order));
        if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.waitPay.getStatus())) {
            vo.setPayCountdownMs(calcPayCountdownMs(order));
        }
        // 存件门店
        if (order.getDepositShopId() != null) {
            ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId());
            if (depositShop != null) {
                vo.setDepositShopName(depositShop.getName());
                vo.setDepositShopLinkName(depositShop.getLinkName());
                vo.setDepositShopPhone(depositShop.getLinkPhone());
                vo.setDepositShopAddress(depositShop.getAddress());
            }
        }
        // 取件信息
        if (order.getTakeShopId() != null) {
            ShopInfo takeShop = shopInfoMapper.selectById(order.getTakeShopId());
            if (takeShop != null) {
                vo.setTakeShopName(takeShop.getName());
                vo.setTakeShopAddress(takeShop.getAddress());
            }
        } else {
            vo.setTakeLocation(order.getTakeLocation());
            vo.setTakeLocationRemark(order.getTakeLocationRemark());
        }
        // 取件联系人
        vo.setTakeUser(order.getTakeUser());
        vo.setTakePhone(order.getTakePhone());
        // 物品类型名称
        if (order.getGoodType() != null) {
            Category category = categoryMapper.selectById(order.getGoodType());
            if (category != null) {
                vo.setGoodTypeName(category.getName());
            }
        }
        // 下单照片
        String imgPrefix = getOrdersPrefix();
        vo.setOrderImages(getFileUrls(order.getId(), Constants.FileType.ORDER_FILE.getKey(), imgPrefix));
        // 物品明细
        List<OrdersDetail> details = ordersDetailMapper.selectList(
                new QueryWrapper<OrdersDetail>().lambda()
                        .eq(OrdersDetail::getOrderId, order.getId())
                        .eq(OrdersDetail::getDeleted, Constants.ZERO));
        vo.setDetailList(buildDetailList(details));
        // 逾期信息
        OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(order, details);
        vo.setOverdue(overdueInfo.getOverdue());
        vo.setOverdueDays(overdueInfo.getOverdueDays());
        vo.setOverdueFee(overdueInfo.getOverdueFee());
        // 核销码
        Integer status = order.getStatus();
        if (memberViewMode) {
            // 会员端:待寄存(1)返回;待取件(5)时,就地寄存无取件门店不返回
            boolean returnCode = false;
            if (Constants.equalsInteger(status, Constants.OrderStatus.waitDeposit.getStatus())) {
                returnCode = true;
            } else if (Constants.equalsInteger(status, Constants.OrderStatus.arrived.getStatus())) {
                if (!(Constants.equalsInteger(order.getType(), Constants.ZERO) && order.getTakeShopId() == null)) {
                    returnCode = true;
                }
            }
            if (returnCode) {
                vo.setMemberVerifyCode(order.getMemberVerifyCode());
            }
        } else {
            // 门店端:始终返回会员核销码
            vo.setMemberVerifyCode(order.getMemberVerifyCode());
        }
        return vo;
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void cancelOrder(Integer orderId, Integer memberId, String reason) {
        Orders order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getId, orderId)
                .eq(Orders::getMemberId, memberId)
                .eq(Orders::getDeleted, Constants.ZERO));
        if (order == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        // 仅异地寄存可取消
        if (!Constants.equalsInteger(order.getType(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅异地寄存订单可取消");
        }
        Integer status = order.getStatus();
        if (status == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单状态异常");
        }
        Date now = new Date();
        // 待支付:直接取消
        if (Constants.equalsInteger(status, Constants.OrderStatus.waitPay.getStatus())) {
            order.setStatus(Constants.OrderStatus.cancelled.getStatus());
            order.setCancelTime(now);
            ordersMapper.updateById(order);
            saveCancelLog(order, "会员取消订单(待支付)", reason, memberId);
            return;
        }
        // 待寄存:直接取消,全额退款
        if (Constants.equalsInteger(status, Constants.OrderStatus.waitDeposit.getStatus())) {
            // 记录退款信息
            OrdersRefund refund = new OrdersRefund();
            refund.setOrderId(orderId);
            refund.setType(0); // 未寄存直接取消
            refund.setCancelInfo(reason);
            refund.setCreateTime(now);
            refund.setDeleted(Constants.ZERO);
            // 调用微信退款,全额退款
            String refundCode = wxMiniUtilService.wxRefund(order.getOutTradeNo(), order.getPayAmount(), order.getPayAmount());
            refund.setRefundCode(refundCode);
            refund.setRefundTime(new Date());
            ordersRefundMapper.insert(refund);
            order.setStatus(Constants.OrderStatus.cancelled.getStatus());
            order.setCancelTime(now);
            order.setRefundAmount(order.getPayAmount());
            ordersMapper.updateById(order);
            saveCancelLog(order, "会员取消订单(待寄存,全额退款)", reason, memberId);
            return;
        }
        // 已寄存/已接单:进入取消中状态
        if (Constants.equalsInteger(status, Constants.OrderStatus.deposited.getStatus())
                || Constants.equalsInteger(status, Constants.OrderStatus.accepted.getStatus())) {
            order.setStatus(Constants.OrderStatus.cancelling.getStatus());
            order.setCancelTime(now);
            ordersMapper.updateById(order);
            saveCancelLog(order, "会员申请取消订单(已寄存/已接单)", reason, memberId);
            return;
        }
        throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许取消");
    }
    /**
     * 保存取消订单操作日志
     */
    private void saveCancelLog(Orders order, String title, 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.setOrderStatus(order.getStatus());
        log.setOptUserId(memberId);
        log.setOptUserType(0); // 0=用户
        log.setCreateTime(new Date());
        log.setDeleted(Constants.ZERO);
        orderLogService.create(log);
    }
    /**
     * 保存门店核销日志
     */
    private void saveShopVerifyLog(Orders order, String title, String logInfo, String remark, Integer shopId) {
        OrderLog log = new OrderLog();
        log.setOrderId(order.getId());
        log.setTitle(title);
        log.setLogInfo(logInfo);
        log.setRemark(remark);
        log.setOrderStatus(order.getStatus());
        log.setOptUserId(shopId);
        log.setOptUserType(2); // 2=门店
        log.setCreateTime(new Date());
        log.setDeleted(Constants.ZERO);
        orderLogService.create(log);
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void handleStorageOrderPayNotify(String outTradeNo, String wxTradeNo) {
        Orders order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getOutTradeNo, outTradeNo)
                .eq(Orders::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (order == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在: " + outTradeNo);
        }
        // 幂等:已支付则跳过
        if (Constants.equalsInteger(order.getPayStatus(), Constants.ONE)) {
            return;
        }
        Date now = new Date();
        order.setStatus(Constants.OrderStatus.waitDeposit.getStatus()); // 待寄存
        order.setPayStatus(Constants.ONE); // 已支付
        order.setPayTime(now);
        order.setWxExternalNo(wxTradeNo);
        order.setUpdateTime(now);
        // 生成会员核销码
        order.setMemberVerifyCode(generateVerifyCode());
        ordersMapper.updateById(order);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayResponse payOverdueFee(Integer orderId, Integer memberId) {
        // 1. 查询寄存订单
        Orders order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getId, orderId)
                .eq(Orders::getMemberId, memberId)
                .eq(Orders::getDeleted, Constants.ZERO));
        if (order == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在");
        }
        // 2. 校验状态:待取件(5) + 逾期(1)
        if (!Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.arrived.getStatus())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不支持逾期支付");
        }
        if (!Constants.equalsInteger(order.getOverdueStatus(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不存在逾期费用");
        }
        if (order.getOverdueAmount() == null || order.getOverdueAmount() <= 0) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "逾期费用异常,无法发起支付");
        }
        // 3. 查询会员
        Member member = memberMapper.selectById(memberId);
        if (member == null || StringUtils.isBlank(member.getOpenid())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付");
        }
        // 4. 创建逾期费用订单
        String outTradeNo = generateOrderTradeNo();
        Date now = new Date();
        OtherOrders otherOrders = new OtherOrders();
        otherOrders.setType(Constants.TWO); // 2=逾期费用订单
        otherOrders.setMemberId(memberId);
        otherOrders.setOrderId(orderId);
        otherOrders.setPayAccount(order.getOverdueAmount());
        otherOrders.setPayStatus(Constants.ZERO);
        otherOrders.setCode("OD" + new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(now) + orderId);
        otherOrders.setOutTradeNo(outTradeNo);
        otherOrders.setDeleted(Constants.ZERO);
        otherOrders.setCreateTime(now);
        otherOrdersMapper.insert(otherOrders);
        // 5. 唤起微信支付
        return wxPayForOtherOrder(otherOrders, member.getOpenid(), Constants.OrdersAttach.OVERDUE_FEE);
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void handleOverdueFeePayNotify(String outTradeNo, String wxTradeNo) {
        // 1. 查找逾期费用订单
        OtherOrders otherOrders = otherOrdersMapper.selectOne(new QueryWrapper<OtherOrders>().lambda()
                .eq(OtherOrders::getOutTradeNo, outTradeNo)
                .eq(OtherOrders::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (otherOrders == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "逾期费用订单不存在: " + outTradeNo);
        }
        // 2. 幂等:已支付则跳过
        if (Constants.equalsInteger(otherOrders.getPayStatus(), Constants.ONE)) {
            return;
        }
        Date now = new Date();
        // 3. 更新逾期费用订单状态
        otherOrders.setPayStatus(Constants.ONE);
        otherOrders.setPayTime(now);
        otherOrders.setWxExternalNo(wxTradeNo);
        otherOrders.setUpdateTime(now);
        otherOrdersMapper.updateById(otherOrders);
        // 4. 更新寄存订单逾期状态为已支付(2),更新总金额,重算三方收益
        if (otherOrders.getOrderId() != null) {
            Orders order = ordersMapper.selectById(otherOrders.getOrderId());
            if (order != null) {
                order.setOverdueStatus(Constants.TWO); // 2=已支付
                // 总金额 = 原金额 + 逾期费用
                Long overdueFee = otherOrders.getPayAccount() != null ? otherOrders.getPayAccount() : 0L;
                long newTotal = (order.getTotalAmount() != null ? order.getTotalAmount() : 0L) + overdueFee;
                order.setTotalAmount(newTotal);
                order.setUpdateTime(now);
                ordersMapper.updateById(order);
                // 重算三方收益
                calculateAndSaveOrderFees(order.getId());
            }
        }
    }
    @Override
    public void deleteMyOrder(Integer orderId, Integer memberId) {
        Orders order = ordersMapper.selectById(orderId);
        if (order == null || !Constants.equalsInteger(order.getDeleted(), Constants.ZERO)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        if (!Constants.equalsInteger(order.getMemberId(), memberId)) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "无权操作此订单");
        }
        // 仅已完成(7)、已取消(99)、已退款(96)可删除
        int status = Constants.formatIntegerNum(order.getStatus());
        if (status != Constants.OrderStatus.finished.getStatus()
                && status != Constants.OrderStatus.cancelled.getStatus()
                && status != Constants.OrderStatus.closed.getStatus()) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "当前订单状态不可删除");
        }
        ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                .set(Orders::getDeleted, Constants.ONE)
                .set(Orders::getUpdateTime, new Date())
                .eq(Orders::getId, orderId));
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public PayResponse payShopDeposit(Integer shopId) {
        // 1. 查询门店信息
        ShopInfo shopInfo = shopInfoMapper.selectById(shopId);
        if (shopInfo == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "门店不存在");
        }
        // 2. 校验状态:审批通过(1)才能支付押金
        if (!Constants.equalsInteger(shopInfo.getAuditStatus(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前门店状态不支持支付押金");
        }
        if (shopInfo.getDepositAmount() == null || shopInfo.getDepositAmount() <= 0) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "押金金额异常,无法发起支付");
        }
        // 3. 查询会员openid
        Member member = memberMapper.selectById(shopInfo.getRegionMemberId());
        if (member == null || StringUtils.isBlank(member.getOpenid())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付");
        }
        // 4. 创建押金订单
        String outTradeNo = generateOrderTradeNo();
        Date now = new Date();
        OtherOrders otherOrders = new OtherOrders();
        otherOrders.setType(Constants.ZERO); // 0=店铺押金订单
        otherOrders.setMemberId(shopInfo.getRegionMemberId());
        otherOrders.setPayAccount(shopInfo.getDepositAmount());
        otherOrders.setPayStatus(Constants.ZERO);
        otherOrders.setCode("SD" + new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(now) + shopId);
        otherOrders.setOutTradeNo(outTradeNo);
        otherOrders.setDeleted(Constants.ZERO);
        otherOrders.setCreateTime(now);
        otherOrdersMapper.insert(otherOrders);
        // 5. 唤起微信支付
        return wxPayForOtherOrder(otherOrders, member.getOpenid(), Constants.OrdersAttach.SHOP_DEPOSIT);
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void handleShopDepositPayNotify(String outTradeNo, String wxTradeNo) {
        // 1. 查找押金订单
        OtherOrders otherOrders = otherOrdersMapper.selectOne(new QueryWrapper<OtherOrders>().lambda()
                .eq(OtherOrders::getOutTradeNo, outTradeNo)
                .eq(OtherOrders::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (otherOrders == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "押金订单不存在: " + outTradeNo);
        }
        // 2. 幂等:已支付则跳过
        if (Constants.equalsInteger(otherOrders.getPayStatus(), Constants.ONE)) {
            return;
        }
        Date now = new Date();
        // 3. 更新押金订单状态
        otherOrders.setPayStatus(Constants.ONE);
        otherOrders.setPayTime(now);
        otherOrders.setWxExternalNo(wxTradeNo);
        otherOrders.setUpdateTime(now);
        otherOrdersMapper.updateById(otherOrders);
        // 4. 查询门店信息(通过注册会员主键关联)
        ShopInfo shopInfo = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda()
                .eq(ShopInfo::getRegionMemberId, otherOrders.getMemberId())
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (shopInfo == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "门店不存在");
        }
        // 5. 更新门店状态:已支付押金
        shopInfo.setAuditStatus(Constants.THREE); // 3=已支付押金
        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.getAreaId() != null) {
            Areas shopArea = areasBiz.resolveArea(shopInfo.getAreaId());
            if (shopArea != null && shopArea.getParentId() != null) {
                Areas cityArea = areasBiz.resolveArea(shopArea.getParentId());
                if (cityArea != null && !Constants.equalsInteger(cityArea.getStatus(), Constants.ONE)) {
                    cityArea.setStatus(Constants.ONE);
                    cityArea.setEditDate(now);
                    areasService.updateById(cityArea);
                    areasService.cacheData();
                }
            }
        }
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void settleOrders() {
        // 1. 读取结算天数配置
        SystemDictData settlementConfig = systemDictDataBiz.queryByCode(Constants.OPERATION_CONFIG, Constants.OP_SETTLEMENT_DATE);
        if (settlementConfig == null || StringUtils.isBlank(settlementConfig.getCode())) {
            return;
        }
        int days = Integer.parseInt(settlementConfig.getCode());
        // 结算截止时间 = 当前时间 - N天
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DAY_OF_MONTH, -days);
        Date deadline = cal.getTime();
        // 2. 查询已完成的待结算订单(完成时间 <= 截止时间)
        List<Orders> ordersList = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getDeleted, Constants.ZERO)
                .eq(Orders::getStatus, Constants.OrderStatus.finished.getStatus())
                .eq(Orders::getSettlementStatus, Constants.ZERO)
                .le(Orders::getFinishTime, deadline));
        if (ordersList == null || ordersList.isEmpty()) {
            return;
        }
        Date now = new Date();
        for (Orders order : ordersList) {
            // 3. 更新订单结算状态
            ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                    .set(Orders::getSettlementStatus, Constants.ONE)
                    .set(Orders::getSettlementTime, now)
                    .set(Orders::getUpdateTime, now)
                    .eq(Orders::getId, order.getId()));
            // 4. 查询关联的待入账 Revenue 记录
            List<Revenue> revenues = revenueMapper.selectList(new QueryWrapper<Revenue>().lambda()
                    .eq(Revenue::getObjId, order.getId())
                    .eq(Revenue::getObjType, Constants.ZERO)
                    .eq(Revenue::getVaildStatus, Constants.ZERO)
                    .eq(Revenue::getDeleted, Constants.ZERO));
            for (Revenue revenue : revenues) {
                Long amount = revenue.getAmount() != null ? revenue.getAmount() : 0L;
                // 更新 Revenue 为已入账
                revenueMapper.update(new UpdateWrapper<Revenue>().lambda()
                        .set(Revenue::getVaildStatus, Constants.ONE)
                        .set(Revenue::getUpdateTime, now)
                        .eq(Revenue::getId, revenue.getId()));
                // 根据 memberType 更新余额
                if (Constants.equalsInteger(revenue.getMemberType(), Constants.ONE)) {
                    // 司机:通过 memberId 查 DriverInfo,更新 balance / totalBalance
                    DriverInfo driver = driverInfoMapper.selectOne(new QueryWrapper<DriverInfo>().lambda()
                            .eq(DriverInfo::getMemberId, revenue.getMemberId())
                            .eq(DriverInfo::getDeleted, Constants.ZERO)
                            .last("limit 1"));
                    if (driver != null) {
                        driverInfoMapper.update(new UpdateWrapper<DriverInfo>().lambda()
                                .setSql(" BALANCE = IFNULL(BALANCE, 0) + " + amount)
                                .setSql(" TOTAL_BALANCE = IFNULL(TOTAL_BALANCE, 0) + " + amount)
                                .eq(DriverInfo::getId, driver.getId()));
                    }
                } 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::getDeleted, Constants.ZERO)
                            .last("limit 1"));
                    if (shop != null) {
                        shopInfoMapper.update(new UpdateWrapper<ShopInfo>().lambda()
                                .setSql(" BALANCE = IFNULL(BALANCE, 0) + " + amount)
                                .setSql(" TOTAL_BALANCE = IFNULL(TOTAL_BALANCE, 0) + " + amount)
                                .eq(ShopInfo::getId, shop.getId()));
                    }
                }
            }
        }
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void commentOrder(CommentOrderDTO dto, Integer memberId) {
        // 1. 校验订单
        Orders order = ordersMapper.selectById(dto.getOrderId());
        if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在");
        }
        if (!Constants.equalsInteger(order.getMemberId(), memberId)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权评价该订单");
        }
        if (!Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.finished.getStatus())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不支持评价");
        }
        if (Constants.equalsInteger(order.getCommentStatus(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单已评价");
        }
        // 2. 异地寄存订单:取件门店和司机评分校验
        boolean isRemote = Constants.equalsInteger(order.getType(), Constants.ONE);
        if (isRemote) {
            if (dto.getDriverScore() == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "异地寄存订单必须评价司机");
            }
            if (order.getTakeShopId() != null && dto.getTakeScore() == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "请评价取件门店");
            }
        }
        Date now = new Date();
        // 3. 更新订单评价状态
        order.setCommentStatus(Constants.ONE);
        order.setCommentInfo(dto.getContent());
        order.setCommentDepositLevel(dto.getDepositScore());
        order.setCommentTakeLevel(dto.getTakeScore());
        order.setCommentDriverLevel(dto.getDriverScore());
        order.setCommentTime(now);
        order.setUpdateTime(now);
        ordersMapper.updateById(order);
        // 4. 创建评价记录
        // 4.1 存件门店
        OrderComment depositComment = new OrderComment();
        depositComment.setOrderId(order.getId());
        depositComment.setOrderCode(order.getCode());
        depositComment.setMemberId(memberId);
        depositComment.setTargetType(Constants.ONE); // 1=存件门店
        depositComment.setTargetId(order.getDepositShopId());
        depositComment.setScore(dto.getDepositScore());
        depositComment.setContent(dto.getContent());
        depositComment.setDeleted(Constants.ZERO);
        depositComment.setCreateTime(now);
        orderCommentMapper.insert(depositComment);
        // 4.2 取件门店(异地寄存且有取件门店)
        if (isRemote && order.getTakeShopId() != null && dto.getTakeScore() != null) {
            OrderComment takeComment = new OrderComment();
            takeComment.setOrderId(order.getId());
            takeComment.setOrderCode(order.getCode());
            takeComment.setMemberId(memberId);
            takeComment.setTargetType(Constants.TWO); // 2=取件门店
            takeComment.setTargetId(order.getTakeShopId());
            takeComment.setScore(dto.getTakeScore());
            takeComment.setContent(dto.getContent());
            takeComment.setDeleted(Constants.ZERO);
            takeComment.setCreateTime(now);
            orderCommentMapper.insert(takeComment);
        }
        // 4.3 司机(异地寄存)
        if (isRemote && order.getAcceptDriver() != null && dto.getDriverScore() != null) {
            OrderComment driverComment = new OrderComment();
            driverComment.setOrderId(order.getId());
            driverComment.setOrderCode(order.getCode());
            driverComment.setMemberId(memberId);
            driverComment.setTargetType(Constants.THREE); // 3=司机
            driverComment.setTargetId(order.getAcceptDriver());
            driverComment.setScore(dto.getDriverScore());
            driverComment.setContent(dto.getContent());
            driverComment.setDeleted(Constants.ZERO);
            driverComment.setCreateTime(now);
            orderCommentMapper.insert(driverComment);
        }
        // 5. 更新门店/司机平均评分
        updateTargetScore(Constants.ONE, order.getDepositShopId());
        if (isRemote && order.getTakeShopId() != null) {
            updateTargetScore(Constants.TWO, order.getTakeShopId());
        }
        if (isRemote && order.getAcceptDriver() != null) {
            updateTargetScore(Constants.THREE, order.getAcceptDriver());
        }
    }
    /**
     * 更新评价对象(门店/司机)的平均评分
     */
    private void updateTargetScore(Integer targetType, Integer targetId) {
        List<OrderComment> comments = orderCommentMapper.selectList(new QueryWrapper<OrderComment>().lambda()
                .eq(OrderComment::getDeleted, Constants.ZERO)
                .eq(OrderComment::getTargetType, targetType)
                .eq(OrderComment::getTargetId, targetId));
        if (comments.isEmpty()) {
            return;
        }
        double avg = comments.stream()
                .mapToInt(OrderComment::getScore)
                .average()
                .orElse(0.0);
        BigDecimal score = BigDecimal.valueOf(avg).setScale(1, BigDecimal.ROUND_HALF_UP);
        Date now = new Date();
        if (Constants.equalsInteger(targetType, Constants.ONE) || Constants.equalsInteger(targetType, Constants.TWO)) {
            ShopInfo shopInfo = shopInfoMapper.selectById(targetId);
            if (shopInfo != null) {
                shopInfo.setScore(score);
                shopInfo.setUpdateTime(now);
                shopInfoMapper.updateById(shopInfo);
            }
        } else if (Constants.equalsInteger(targetType, Constants.THREE)) {
            DriverInfo driverInfo = driverInfoMapper.selectById(targetId);
            if (driverInfo != null) {
                driverInfo.setScore(score);
                driverInfo.setUpdateTime(now);
                driverInfoMapper.updateById(driverInfo);
            }
        }
    }
    /**
     * 唤起微信支付(其他订单)
     */
    private PayResponse wxPayForOtherOrder(OtherOrders otherOrders, String openid, Constants.OrdersAttach ordersAttach) {
        try {
            WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
            request.setBody(ordersAttach.getName());
            request.setAttach(ordersAttach.getKey());
            request.setOutTradeNo(otherOrders.getOutTradeNo());
            long totalFee = otherOrders.getPayAccount() != null ? otherOrders.getPayAccount() : 0L;
            request.setTotalFee((int) totalFee);
            request.setTimeStart(DateUtil.DateToString(new Date(), "yyyyMMddHHmmss"));
            request.setSpbillCreateIp(Constants.getIpAddr());
            request.setOpenid(openid);
            Object response = WxMiniConfig.wxPayService.createOrder(request);
            PayResponse payResponse = new PayResponse();
            payResponse.setResponse(response);
            payResponse.setOrderId(otherOrders.getId());
            return payResponse;
        } catch (WxPayException e) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "支付调起失败:" + e.getMessage());
        }
    }
    @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(), "核销码不能为空");
        }
        // 根据核销码查找订单(会员核销码)
        Orders order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getMemberVerifyCode, verifyCode)
                .eq(Orders::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (order == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "核销码无效");
        }
        // 查询门店名称用于日志
        String shopName = "";
        ShopInfo shopInfo = shopInfoMapper.selectById(shopId);
        if (shopInfo != null) {
            shopName = shopInfo.getName() != null ? shopInfo.getName() : "";
        }
        Integer status = order.getStatus();
        Date now = new Date();
        if (Constants.equalsInteger(status, Constants.OrderStatus.waitDeposit.getStatus())) {
            // 待寄存(1) → 已寄存(2),两种类型通用
            // 校验当前门店是否为订单的存件门店
            if (!shopId.equals(order.getDepositShopId())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店,无法核销");
            }
            order.setStatus(Constants.OrderStatus.deposited.getStatus());
            order.setDepositTime(now);
            // 释放当前核销码,生成新的核销码供取件时使用
            releaseVerifyCode(verifyCode);
            order.setMemberVerifyCode(generateVerifyCode());
            ordersMapper.updateById(order);
            // 保存寄存图片(obj_type=2 订单寄存图片,最多3张)
            saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_DEPOSIT.getKey(), shopId);
            // 记录订单日志
            saveShopVerifyLog(order, "门店确认寄存", "门店【" + shopName + "】确认寄存", remark, shopId);
        } 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(), "该订单不属于当前门店,无法核销");
            }
            // 待取件(5) → 已完成(7)
            order.setStatus(Constants.OrderStatus.finished.getStatus());
            order.setConfirmArriveTime(now);
            ordersMapper.updateById(order);
            // 订单完成,释放核销码
            releaseVerifyCode(verifyCode);
            // 保存出库图片(obj_type=13 门店出库图片,最多3张)
            saveVerifyImages(order.getId(), images, Constants.FileType.STORE_OUT.getKey(), shopId);
            // 生成收益记录
            calculateAndSaveOrderFees(order.getId());
            generateRevenueRecords(order.getId());
            // 记录订单日志
            saveShopVerifyLog(order, "门店确认取件", "门店【" + shopName + "】确认取件,订单完成", remark, shopId);
        } else {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许核销");
        }
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void confirmStoreOut(Integer orderId, Integer shopId, List<String> images, String remark) {
        // 1. 查询订单
        Orders order = ordersMapper.selectById(orderId);
        if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在");
        }
        // 2. 校验状态:待取件(5)
        if (!Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.arrived.getStatus())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许出库");
        }
        // 3. 校验逾期状态:0=未逾期 或 2=已支付
        if (order.getOverdueStatus() != null && Constants.equalsInteger(order.getOverdueStatus(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单存在逾期未支付费用,请先完成逾期费用支付");
        }
        // 4. 校验确认到店时间不为空
        if (order.getConfirmArriveTime() == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单尚未确认到店,无法出库");
        }
        // 5. 校验门店与订单关系
        if (Constants.equalsInteger(order.getType(), Constants.ZERO)) {
            // 就地寄存:取件门店即存件门店
            if (!shopId.equals(order.getDepositShopId())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店");
            }
        } else {
            // 异地寄存:校验取件门店
            if (order.getTakeShopId() == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单无取件门店,无法出库");
            }
            if (!shopId.equals(order.getTakeShopId())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店");
            }
        }
        // 6. 查询门店名称
        String shopName = "";
        ShopInfo shopInfo = shopInfoMapper.selectById(shopId);
        if (shopInfo != null) {
            shopName = shopInfo.getName() != null ? shopInfo.getName() : "";
        }
        // 7. 更新订单状态为已完成
        Date now = new Date();
        order.setStatus(Constants.OrderStatus.finished.getStatus());
        order.setFinishTime(now);
        order.setUpdateTime(now);
        ordersMapper.updateById(order);
        // 8. 释放核销码
        if (StringUtils.isNotBlank(order.getMemberVerifyCode())) {
            releaseVerifyCode(order.getMemberVerifyCode());
        }
        // 9. 保存出库图片(obj_type=13 门店出库图片,最多3张)
        saveVerifyImages(order.getId(), images, Constants.FileType.STORE_OUT.getKey(), shopId);
        // 10. 如果存在退款金额,先保存退款记录再调用微信退款
        //    退款记录在退款调用前落库,避免退款成功但本地异常导致无记录
        if (order.getRefundAmount() != null && order.getRefundAmount() > 0
                && StringUtils.isNotBlank(order.getOutTradeNo())
                && order.getPayAmount() != null && order.getPayAmount() > 0) {
            OrdersRefund refundRecord = new OrdersRefund();
            refundRecord.setOrderId(orderId);
            refundRecord.setType(3); // 出库退款
            refundRecord.setCreateTime(now);
            refundRecord.setRefundRemark(remark);
            refundRecord.setDeleted(Constants.ZERO);
            ordersRefundMapper.insert(refundRecord);
            // 调用微信退款(放在最后,确保前置操作全部成功)
            String refundCode = wxMiniUtilService.wxRefund(
                    order.getOutTradeNo(), order.getPayAmount(), order.getRefundAmount());
            // 退款成功后回填退款单号和时间
            refundRecord.setRefundCode(refundCode);
            refundRecord.setRefundTime(new Date());
            ordersRefundMapper.updateById(refundRecord);
        }
        // 11. 生成收益记录
        calculateAndSaveOrderFees(orderId);
        generateRevenueRecords(orderId);
        // 12. 记录订单日志
        String logInfo = "门店【" + shopName + "】确认出库,订单完成";
        if (order.getRefundAmount() != null && order.getRefundAmount() > 0) {
            logInfo += ",退款" + Constants.getFormatMoney(order.getRefundAmount()) + "元";
        }
        saveShopVerifyLog(order, "门店确认出库", logInfo, remark, shopId);
    }
    @Override
    public void calculateAndSaveOrderFees(Integer orderId) {
        Orders order = ordersMapper.selectById(orderId);
        if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在");
        }
        Long totalAmount = order.getTotalAmount() != null ? order.getTotalAmount() : 0L;
        // 费率(为空时默认0)
        BigDecimal depositRate = order.getDepositShopFeeRata() != null ? order.getDepositShopFeeRata() : BigDecimal.ZERO;
        BigDecimal takeRate = order.getTakeShopFeeRata() != null ? order.getTakeShopFeeRata() : BigDecimal.ZERO;
        BigDecimal driverRate = order.getDriverFeeRata() != null ? order.getDriverFeeRata() : BigDecimal.ZERO;
        Long exceptionFeeVal = order.getExceptionFee() != null ? order.getExceptionFee() : 0L;
        //存件门店收益
        Long depositShopFee = new BigDecimal(totalAmount)
                .multiply(depositRate)
                .setScale(0, RoundingMode.HALF_UP)
                .longValue();
        Long takeShopFee = 0L;
        Long driverFee = 0L;
        if (Constants.equalsInteger(order.getType(), Constants.TWO)) {
            // 异地寄存:存件门店 + 司机
            driverFee = new BigDecimal(totalAmount)
                    .multiply(driverRate)
                    .setScale(0, RoundingMode.HALF_UP)
                    .longValue()
                    + exceptionFeeVal;
            // 异地寄存且有取件门店:加上取件门店收益
            if (order.getTakeShopId() != null) {
                takeShopFee = new BigDecimal(totalAmount)
                        .multiply(takeRate)
                        .setScale(0, RoundingMode.HALF_UP)
                        .longValue();
            }
        }
        ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                .eq(Orders::getId, orderId)
                .set(Orders::getDepositShopFee, depositShopFee)
                .set(Orders::getTakeShopFee, takeShopFee)
                .set(Orders::getDriverFee, driverFee)
                .set(Orders::getUpdateTime, new Date()));
    }
    /**
     * 生成门店/司机收益记录(未结算)
     * 订单完成时调用,读取订单上已计算好的费用字段
     */
    private void generateRevenueRecords(Integer orderId) {
        Orders order = ordersMapper.selectById(orderId);
        if (order == null) {
            return;
        }
        Date now = new Date();
        Long depositShopFee = order.getDepositShopFee() != null ? order.getDepositShopFee() : 0L;
        Long takeShopFee = order.getTakeShopFee() != null ? order.getTakeShopFee() : 0L;
        Long driverFee = order.getDriverFee() != null ? order.getDriverFee() : 0L;
        // 存件门店收益
        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,
                        depositShopFee, orderId, order.getCode()));
            }
        }
        // 取件门店收益(异地寄存且有取件门店)
        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,
                        takeShopFee, orderId, order.getCode()));
            }
        }
        // 司机收益(异地寄存)
        if (driverFee > 0 && order.getAcceptDriver() != null) {
            DriverInfo driver = driverInfoMapper.selectById(order.getAcceptDriver());
            if (driver != null && driver.getMemberId() != null) {
                revenueMapper.insert(buildRevenue(driver.getMemberId(), Constants.ONE,
                        driverFee, orderId, order.getCode()));
            }
        }
    }
    /**
     * 构建收益记录
     */
    private Revenue buildRevenue(Integer memberId, Integer memberType, Long amount, Integer orderId, String orderNo) {
        Revenue revenue = new Revenue();
        revenue.setMemberId(memberId);
        revenue.setMemberType(memberType); // 1=司机, 2=门店
        revenue.setType(Constants.ZERO); // 0=完成订单
        revenue.setOptType(Constants.ONE); // 1=收入
        revenue.setAmount(amount);
        revenue.setVaildStatus(Constants.ZERO); // 0=入账中(未结算)
        revenue.setObjId(orderId);
        revenue.setObjType(Constants.ZERO); // 0=订单业务
        revenue.setStatus(Constants.ZERO); // 0=成功
        revenue.setOrderNo(orderNo);
        revenue.setDeleted(Constants.ZERO);
        revenue.setCreateTime(new Date());
        return revenue;
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void driverVerifyOrder(String verifyCode, List<String> images, String remark, Integer driverId) {
        if (StringUtils.isBlank(verifyCode)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "核销码不能为空");
        }
        // 根据司机核销码查找订单
        Orders order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getDriverVerifyCode, verifyCode)
                .eq(Orders::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (order == null) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "核销码无效");
        }
        // 仅异地寄存 + 有取件门店 + 派送中(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(), "当前订单状态不允许核销");
        }
        // 派送中(4) → 已到店(5)
        order.setStatus(Constants.OrderStatus.arrived.getStatus());
        order.setArriveTime(new Date());
        if (StringUtils.isNotBlank(remark)) {
            order.setRemark(remark);
        }
        ordersMapper.updateById(order);
        // 释放司机核销码
        releaseVerifyCode(verifyCode);
        // 保存附件(obj_type=3 门店入库图片,最多3张)
        saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_TAKE.getKey(), driverId);
    }
    /**
     * 保存核销附件到 multifile 表
     *
     * @param orderId  订单主键
     * @param images   图片地址列表(最多3张)
     * @param objType  附件类型
     * @param creator  创建人编码
     */
    private void saveVerifyImages(Integer orderId, List<String> images, int objType, Integer creator) {
        if (images == null || images.isEmpty()) return;
        List<String> saveImages = images.size() > 3 ? images.subList(0, 3) : images;
        Date now = new Date();
        int sortNum = 1;
        for (String imgUrl : saveImages) {
            if (StringUtils.isBlank(imgUrl)) continue;
            Multifile multifile = new Multifile();
            multifile.setObjId(orderId);
            multifile.setObjType(objType);
            multifile.setFileurl(imgUrl);
            multifile.setType(Constants.ZERO);
            multifile.setCreator(creator);
            multifile.setCreateDate(now);
            multifile.setIsdeleted(Constants.ZERO);
            multifile.setSortnum(sortNum++);
            multifileMapper.insert(multifile);
        }
    }
    @Override
    @Transactional(rollbackFor = {Exception.class, BusinessException.class})
    public void confirmCustomerArrived(Integer orderId, Integer shopId) {
        // 1. 查询订单
        Orders order = ordersMapper.selectById(orderId);
        if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在");
        }
        // 2. 校验订单状态:待取件(5)
        if (!Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.arrived.getStatus())) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许该操作");
        }
        // 3. 校验门店与订单关系
        if (Constants.equalsInteger(order.getType(), Constants.ONE) && order.getTakeShopId() != null) {
            // 异地寄存有取件门店:校验取件门店
            if (!shopId.equals(order.getTakeShopId())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店");
            }
        } else if (Constants.equalsInteger(order.getType(), Constants.ZERO)) {
            // 就地寄存:校验存件门店(取件门店同存件门店)
            if (!shopId.equals(order.getDepositShopId())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店");
            }
        } else {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单无取件门店,无法确认到店");
        }
        // 4. 查询门店名称(用于日志)
        String shopName = "";
        ShopInfo shopInfo = shopInfoMapper.selectById(shopId);
        if (shopInfo != null) {
            shopName = shopInfo.getName() != null ? shopInfo.getName() : "";
        }
        // 5. 计算逾期费用
        List<OrdersDetail> details = ordersDetailMapper.selectList(
                new QueryWrapper<OrdersDetail>().lambda()
                        .eq(OrdersDetail::getOrderId, orderId)
                        .eq(OrdersDetail::getDeleted, Constants.ZERO));
        OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(order, details);
        Date now = new Date();
        if (overdueInfo.getOverdue() && overdueInfo.getOverdueDays() > 0) {
            // 存在逾期:标记逾期状态,订单保持当前状态
            order.setConfirmArriveTime(now);
            order.setOverdueStatus(Constants.ONE);
            order.setOverdueDays(overdueInfo.getOverdueDays());
            order.setOverdueAmount(overdueInfo.getOverdueFee());
            order.setUpdateTime(now);
            ordersMapper.updateById(order);
            // 记录订单日志
            saveShopVerifyLog(order, "确认顾客到店(逾期)",
                    "门店【" + shopName + "】确认顾客到店,逾期" + overdueInfo.getOverdueDays()
                            + "天,逾期费用" + Constants.getFormatMoney(overdueInfo.getOverdueFee()) + "元",
                    null, shopId);
        } else {
            // 未逾期:标记逾期状态为0,订单保持当前状态
            order.setConfirmArriveTime(now);
            order.setOverdueStatus(Constants.ZERO);
            // 就地寄存:计算是否需要退款
            if (Constants.equalsInteger(order.getType(), Constants.ZERO) && !CollectionUtils.isEmpty(details)) {
                int actualDays = calcActualDepositDays(now, order.getDepositTime());
                order.setDepositDays(actualDays);
                int estimatedDays = order.getEstimatedDepositDays() != null ? order.getEstimatedDepositDays() : 1;
                int refundDays = estimatedDays - actualDays;
                if (refundDays > 0) {
                    // 退款金额 = 退款天数 × Σ(物品单价 × 数量)
                    long dailyBaseFee = 0L;
                    for (OrdersDetail d : details) {
                        dailyBaseFee += (d.getUnitPrice() != null ? d.getUnitPrice() : 0L)
                                * (d.getNum() != null ? d.getNum() : 0);
                    }
                    long refundAmount = (long) refundDays * dailyBaseFee;
                    order.setRefundAmount(refundAmount);
                }
            }
            order.setUpdateTime(now);
            ordersMapper.updateById(order);
            // 退款导致总金额变化,重算三方收益
            if (order.getRefundAmount() != null && order.getRefundAmount() > 0) {
                long newTotal = (order.getTotalAmount() != null ? order.getTotalAmount() : 0L) - order.getRefundAmount();
                order.setTotalAmount(newTotal);
                ordersMapper.update(new UpdateWrapper<Orders>().lambda()
                        .eq(Orders::getId, orderId)
                        .set(Orders::getTotalAmount, newTotal));
                calculateAndSaveOrderFees(orderId);
            }
            // 记录订单日志
            String logInfo = "门店【" + shopName + "】确认顾客到店,未逾期";
            if (order.getRefundAmount() != null && order.getRefundAmount() > 0) {
                logInfo += ",需退款" + Constants.getFormatMoney(order.getRefundAmount()) + "元";
            }
            saveShopVerifyLog(order, "确认顾客到店", logInfo, null, shopId);
        }
    }
    /**
     * 构建订单状态描述
     */
    private String buildStatusDesc(Orders order) {
        boolean isLocal = Constants.equalsInteger(order.getType(), Constants.ZERO);
        Integer status = order.getStatus();
        if (status == null) return "";
        if (Constants.equalsInteger(status, Constants.OrderStatus.waitPay.getStatus())) {
            String minutes = "";
            try {
                minutes = operationConfigBiz.getConfig().getAutoCancelTime();
            } catch (Exception ignored) {}
            return "请在" + (StringUtils.isNotBlank(minutes) ? minutes : "") + "分钟内完成支付,超时订单将自动取消";
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.waitDeposit.getStatus())) {
            return isLocal ? "订单已支付,请等待门店确认接单" : "订单已支付,请等待门店确认接单";
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.deposited.getStatus())) {
            return isLocal ? "行李已寄存,请凭取件码前往指定门店取件" : "门店已接单,正在为您安排取件司机";
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.accepted.getStatus())) {
            return isLocal ? "行李已寄存,请凭取件码前往指定门店取件" : "已有司机抢单,正前往取件地点";
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.delivering.getStatus())) {
            return "司机已取件,正运往目的地";
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.arrived.getStatus())) {
            return "行李已送达服务点,请及时前往取件";
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.finished.getStatus())) {
            if (Constants.equalsInteger(order.getCommentStatus(), Constants.ONE)) {
                return "感谢您的用心评价,祝您出行顺利,旅途愉快!";
            }
            return "订单已完成,感谢您的支持,请对本次服务做出评价";
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.cancelled.getStatus())) {
            return "订单已取消,感谢您的支持,欢迎下次再会!";
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.cancelling.getStatus())) {
            return "退款申请已提交,平台会尽快为您处理退款";
        }
        if (Constants.equalsInteger(status, Constants.OrderStatus.closed.getStatus())) {
            return "退款已成功原路返回,请注意查收";
        }
        return "";
    }
    /**
     * 计算支付倒计时毫秒
     */
    private Long calcPayCountdownMs(Orders order) {
        try {
            String minutesStr = operationConfigBiz.getConfig().getAutoCancelTime();
            if (StringUtils.isBlank(minutesStr)) return -1L;
            int minutes = Integer.parseInt(minutesStr);
            long deadline = order.getCreateTime().getTime() + minutes * 60 * 1000L;
            long remaining = deadline - System.currentTimeMillis();
            return remaining > 0 ? remaining : -1L;
        } catch (Exception e) {
            return -1L;
        }
    }
    public OverdueFeeVO calculateOverdueFee(Integer orderId) {
        Orders order = ordersMapper.selectById(orderId);
        if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        // 查询订单明细
        List<OrdersDetail> details = ordersDetailMapper.selectList(
                new QueryWrapper<OrdersDetail>().lambda()
                        .eq(OrdersDetail::getOrderId, orderId)
                        .eq(OrdersDetail::getDeleted, Constants.ZERO));
        return calculateOverdueFeeInternal(order, details);
    }
    /**
     * 逾期费用内部计算(不查库,接受预查询的数据)
     * 供分页等已查询明细的业务场景复用,避免重复查询
     */
    private OverdueFeeVO calculateOverdueFeeInternal(Orders order, List<OrdersDetail> details) {
        if (CollectionUtils.isEmpty(details)) {
            OverdueFeeVO vo = new OverdueFeeVO();
            vo.setOverdue(false);
            vo.setOverdueDays(0);
            vo.setOverdueFee(0L);
            vo.setDailyBaseFee(0L);
            return vo;
        }
        // 物品基础日费用 = Σ(单价 × 数量)
        long dailyBaseFee = 0L;
        for (OrdersDetail d : details) {
            dailyBaseFee += (d.getUnitPrice() != null ? d.getUnitPrice() : 0L)
                    * (d.getNum() != null ? d.getNum() : 0);
        }
        Date now = new Date();
        int overdueDays;
        long overdueFee;
        if (Constants.equalsInteger(order.getType(), Constants.ZERO)) {
            // ========== 就地寄存 ==========
            overdueDays = calcLocalOverdueDays(now, order.getExpectedTakeTime());
            overdueFee = (long) overdueDays * dailyBaseFee;
            OverdueFeeVO vo = new OverdueFeeVO();
            vo.setOverdue(overdueDays > 0);
            vo.setOverdueDays(overdueDays);
            vo.setOverdueFee(overdueFee);
            vo.setDailyBaseFee(dailyBaseFee);
            return vo;
        } else {
            // ========== 异地寄存 ==========
            // 条件:存在取件门店 且 订单处于已到店状态(5)
            if (order.getTakeShopId() == null
                    || !Constants.equalsInteger(order.getStatus(), Constants.FIVE)) {
                OverdueFeeVO vo = new OverdueFeeVO();
                vo.setOverdue(false);
                vo.setOverdueDays(0);
                vo.setOverdueFee(0L);
                vo.setDailyBaseFee(dailyBaseFee);
                vo.setDiscountRate(null);
                return vo;
            }
            overdueDays = calcRemoteOverdueDays(now, order.getArriveTime());
            // 折扣比率
            String discountStr = operationConfigBiz.getConfig().getUnpickedDiscount();
            BigDecimal discountRate = StringUtils.isNotBlank(discountStr)
                    ? new BigDecimal(discountStr) : BigDecimal.ONE;
            overdueFee = new BigDecimal(overdueDays)
                    .multiply(new BigDecimal(dailyBaseFee))
                    .multiply(discountRate)
                    .setScale(0, RoundingMode.HALF_UP)
                    .longValue();
            OverdueFeeVO vo = new OverdueFeeVO();
            vo.setOverdue(overdueDays > 0);
            vo.setOverdueDays(overdueDays);
            vo.setOverdueFee(overdueFee);
            vo.setDailyBaseFee(dailyBaseFee);
            vo.setDiscountRate(discountStr);
            return vo;
        }
    }
    /**
     * 计算实际寄存天数(depositTime 到 now 的天数差,最少1天)
     */
    private int calcActualDepositDays(Date now, Date depositTime) {
        if (depositTime == null || now == null) {
            return 1;
        }
        Calendar depositCal = Calendar.getInstance();
        depositCal.setTime(depositTime);
        depositCal.set(Calendar.HOUR_OF_DAY, 0);
        depositCal.set(Calendar.MINUTE, 0);
        depositCal.set(Calendar.SECOND, 0);
        depositCal.set(Calendar.MILLISECOND, 0);
        Calendar nowCal = Calendar.getInstance();
        nowCal.setTime(now);
        nowCal.set(Calendar.HOUR_OF_DAY, 0);
        nowCal.set(Calendar.MINUTE, 0);
        nowCal.set(Calendar.SECOND, 0);
        nowCal.set(Calendar.MILLISECOND, 0);
        long diffMs = nowCal.getTimeInMillis() - depositCal.getTimeInMillis();
        int days = (int) (diffMs / (1000 * 60 * 60 * 24));
        return Math.max(days, 1);
    }
    /**
     * 就地寄存逾期天数计算
     * 过了预计取件时间当天的12点后才算一天
     */
    private int calcLocalOverdueDays(Date now, Date expectedTakeTime) {
        if (expectedTakeTime == null || !now.after(expectedTakeTime)) {
            return 0;
        }
        // 基准时间 = 预计取件日期的12:00
        Calendar baseCal = Calendar.getInstance();
        baseCal.setTime(expectedTakeTime);
        baseCal.set(Calendar.HOUR_OF_DAY, 12);
        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);
        nowCal.set(Calendar.MINUTE, 0);
        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);
    }
    /**
     * 异地寄存逾期天数计算
     * 过了转移到店时间当天的晚上12点(24:00)后才算第一天
     */
    private int calcRemoteOverdueDays(Date now, Date arriveTime) {
        if (arriveTime == null || !now.after(arriveTime)) {
            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);
        nowCal.set(Calendar.MINUTE, 0);
        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);
    }
}