package cn.iocoder.yudao.module.system.service.biz;

import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.ImportResultVO;
import cn.iocoder.yudao.module.system.util.ImportUtil;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialInBalanceListReqVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialInBalanceRespVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialIoOrderItemSaveReqVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialIoOrderPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialIoOrderPageRespVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialIoOrderSaveReqVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inbound.*;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inboundreturn.MaterialInboundReturnOrderItemRespVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inboundreturn.MaterialInboundReturnOrderPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inboundreturn.MaterialInboundReturnOrderPageRespVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inboundreturn.MaterialInboundReturnOrderRespVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inventory.MaterialInventoryFlowReqVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inventory.MaterialInventoryFlowRespVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inventory.MaterialInventoryReportPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inventory.MaterialInventoryReportRespVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.outbound.*;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.outboundreturn.MaterialOutboundReturnOrderItemRespVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.outboundreturn.MaterialOutboundReturnOrderPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.outboundreturn.MaterialOutboundReturnOrderPageRespVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.outboundreturn.MaterialOutboundReturnOrderRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.*;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.mysql.biz.*;
import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.multipart.MultipartFile;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exceptionCustomMsg;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.COMMON_NOT_FOUND;

/**
 * 物资出入库 Service 实现类
 */
@Service
@Validated
public class MaterialIoServiceImpl implements MaterialIoService {
    @Autowired
    private MaterialCategoryMapper materialCategoryMapper;

    @Resource
    private MaterialIoOrderNoGenerator orderNoGenerator;

    @Resource
    private MaterialInboundOrderMapper inboundOrderMapper;

    @Resource
    private MaterialInboundOrderItemMapper inboundOrderItemMapper;

    @Resource
    private MaterialOutboundOrderMapper outboundOrderMapper;

    @Resource
    private MaterialOutboundOrderItemMapper outboundOrderItemMapper;

    @Resource
    private MaterialInBalanceMapper inBalanceMapper;

    @Resource
    private MaterialInfoMapper materialInfoMapper;

    @Resource
    private DeptMapper deptMapper;

    @Resource
    private StoreManageMapper storeManageMapper;

    @Resource
    private SupplierInfoMapper supplierInfoMapper;

    @Resource
    private MaterialOutboundReturnOrderMapper outboundReturnOrderMapper;
    @Resource
    private MaterialOutboundReturnOrderItemMapper outboundReturnOrderItemMapper;
    @Resource
    private MaterialInboundReturnOrderMapper inboundReturnOrderMapper;
    @Resource
    private MaterialInboundReturnOrderItemMapper inboundReturnOrderItemMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long inbound(MaterialIoOrderSaveReqVO reqVO) {
        reqVO.setBizType("IN");
        return createInboundOrderWithPrefix(reqVO, "IN");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long outbound(MaterialIoOrderSaveReqVO reqVO) {
        reqVO.setBizType("OUT");
        return createOutboundOrderWithPrefix(reqVO, "OUT");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long outboundByInbound(MaterialOutboundByInboundReqVO reqVO) {
        MaterialInboundOrderDO inboundOrder = inboundOrderMapper.selectById(reqVO.getInboundOrderId());
        if (inboundOrder == null) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "入库单不存在");
        }

        List<MaterialInboundOrderItemDO> inboundItems = inboundOrderItemMapper.selectList(
                new LambdaQueryWrapper<MaterialInboundOrderItemDO>()
                        .eq(MaterialInboundOrderItemDO::getInboundOrderId, reqVO.getInboundOrderId()));
        if (inboundItems == null || inboundItems.isEmpty()) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "入库单明细不存在");
        }

        for (MaterialInboundOrderItemDO inboundItem : inboundItems) {
            BigDecimal outQty = inboundItem.getOutQty() == null ? BigDecimal.ZERO : inboundItem.getOutQty();
            BigDecimal inQty = inboundItem.getInQty() == null ? BigDecimal.ZERO : inboundItem.getInQty();
            if (outQty.compareTo(BigDecimal.ZERO) > 0) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "入库单已发生出库，无法整单出库");
            }
            if (inQty.compareTo(BigDecimal.ZERO) <= 0) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "入库单明细出库数量必须大于0");
            }
        }

        MaterialOutboundOrderDO order = MaterialOutboundOrderDO.builder()
                .orderNo(orderNoGenerator.generate("OUT", reqVO.getOrderDate()))
                .orderDate(reqVO.getOrderDate())
                .outDeptId(reqVO.getOutDeptId())
                .outUser(reqVO.getOutUser())
                .outReason(reqVO.getOutReason())
                .status(1)
                .remark(reqVO.getRemark())
                .tenantId(reqVO.getTenantId())
                .build();
        outboundOrderMapper.insert(order);

        List<MaterialIoOrderItemSaveReqVO> items = new ArrayList<>();
        for (MaterialInboundOrderItemDO inboundItem : inboundItems) {
            BigDecimal quantity = inboundItem.getInQty();
            MaterialOutboundOrderItemDO outboundItem = new MaterialOutboundOrderItemDO();
            outboundItem.setOutboundOrderId(order.getId());
            outboundItem.setRefInboundItemId(inboundItem.getId());
            outboundItem.setMaterialId(inboundItem.getMaterialId());
            outboundItem.setQuantity(quantity);
            outboundItem.setPrice(inboundItem.getInUnitPrice());
            outboundItem.setTenantId(reqVO.getTenantId());
            outboundOrderItemMapper.insert(outboundItem);

            inboundItem.setOutQty(inboundItem.getOutQty().add(quantity));
            inboundItem.setAvailableQty(inboundItem.getAvailableQty().subtract(quantity));
            inboundOrderItemMapper.updateById(inboundItem);

            MaterialIoOrderItemSaveReqVO itemReq = new MaterialIoOrderItemSaveReqVO();
            itemReq.setInboundItemId(inboundItem.getId());
            itemReq.setMaterialId(inboundItem.getMaterialId());
            itemReq.setQuantity(quantity);
            items.add(itemReq);
        }

        updateMaterialStockForOutbound(items, reqVO.getTenantId());
        return order.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long outboundReturn(MaterialIoOrderSaveReqVO reqVO) {
        // 1. 参数校验
        if (reqVO.getSourceOutboundOrderId() == null) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "原出库单ID不能为空");
        }
        if (reqVO.getOrderDate() == null) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "退回日期不能为空");
        }

        // 2. 生成退回单号
        String orderNo = orderNoGenerator.generate("OUT_RETURN", reqVO.getOrderDate());

        // 3. 创建退回单主表
        MaterialOutboundReturnOrderDO returnOrder = MaterialOutboundReturnOrderDO.builder()
                .orderNo(orderNo)
                .sourceOutboundOrderId(reqVO.getSourceOutboundOrderId())
                .orderDate(reqVO.getOrderDate())
                .status(1) // 默认草稿
                .remark(reqVO.getRemark())
                .returnReason(reqVO.getReturnReason())
                .tenantId(reqVO.getTenantId())
                .build();
        outboundReturnOrderMapper.insert(returnOrder);

        // 4. 处理明细
        for (MaterialIoOrderItemSaveReqVO itemVO : reqVO.getItems()) {
            // 4.1 校验必填字段
            if (itemVO.getInboundItemId() == null) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "必须指定退回的入库批次");
            }
            if (itemVO.getQuantity() == null || itemVO.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "退回数量必须大于0");
            }

            // 4.2 查询原入库批次
            MaterialInboundOrderItemDO inboundItem = getInboundItemOrThrow(itemVO.getInboundItemId());

            // 校验退回数量不能超过该明细已出库数量（避免回扣后出现负数）
            MaterialOutboundOrderItemDO outboundItem = outboundOrderItemMapper.selectById(itemVO.getOutboundItemId());
            if (outboundItem == null) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "原出库明细不存在");
            }
            BigDecimal originalOutQty = outboundItem.getQuantity() == null ? BigDecimal.ZERO : outboundItem.getQuantity();
            if (originalOutQty.compareTo(itemVO.getQuantity()) < 0) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "退回数量不能大于原出库数量");
            }

            // 出库退回应当对出库数量进行回扣：减少出库明细数量
            outboundItem.setQuantity(originalOutQty.subtract(itemVO.getQuantity()));
            outboundOrderItemMapper.updateById(outboundItem);

            // 4.3 更新批次信息（回补）
            applyOutboundReturnToBatch(inboundItem, itemVO.getQuantity());

            // 4.4 更新总库存
            updateMaterialStock(itemVO.getMaterialId(), itemVO.getQuantity());

            // 4.5 保存退回明细
            MaterialOutboundReturnOrderItemDO item = MaterialOutboundReturnOrderItemDO.builder()
                    .returnOrderId(returnOrder.getId())
                    .inboundItemId(itemVO.getInboundItemId())
                    .outboundItemId(itemVO.getOutboundItemId())
                    .materialId(itemVO.getMaterialId())
                    .quantity(itemVO.getQuantity())
                    .tenantId(reqVO.getTenantId())
                    .build();
            outboundReturnOrderItemMapper.insert(item);
        }

        return returnOrder.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long inboundReturn(MaterialIoOrderSaveReqVO reqVO) {
        // 1. 参数校验
        if (reqVO.getSourceInboundOrderId() == null) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "原入库单ID不能为空");
        }
        if (reqVO.getOrderDate() == null) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "退回日期不能为空");
        }

        // 2. 生成退回单号
        String orderNo = orderNoGenerator.generate("IN_RETURN", reqVO.getOrderDate());

        // 3. 创建退回单主表
        MaterialInboundReturnOrderDO returnOrder = MaterialInboundReturnOrderDO.builder()
                .orderNo(orderNo)
                .sourceInboundOrderId(reqVO.getSourceInboundOrderId())
                .orderDate(reqVO.getOrderDate())
                .status(1) // 默认草稿
                .remark(reqVO.getRemark())
                .returnReason(reqVO.getReturnReason())
                .tenantId(reqVO.getTenantId())
                .build();
        inboundReturnOrderMapper.insert(returnOrder);

        // 4. 处理明细
        for (MaterialIoOrderItemSaveReqVO itemVO : reqVO.getItems()) {
            // 4.1 校验必填字段
            if (itemVO.getInboundItemId() == null) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "必须指定扣减的入库批次");
            }
            if (itemVO.getQuantity() == null || itemVO.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "退回数量必须大于0");
            }

            // 4.2 查询原入库批次
            MaterialInboundOrderItemDO inboundItem = getInboundItemOrThrow(itemVO.getInboundItemId());

            // 4.3 校验可用数量
            if (inboundItem.getAvailableQty().compareTo(itemVO.getQuantity()) < 0) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "批次可用数量不足");
            }

            // 4.4 更新批次信息（扣减：按你的口径，减少 inQty，并同步 availableQty）
            applyInboundReturnToBatch(inboundItem, itemVO.getQuantity());

            // 4.5 更新总库存
            updateMaterialStock(itemVO.getMaterialId(), itemVO.getQuantity().negate());

            // 4.6 保存退回明细
            MaterialInboundReturnOrderItemDO item = MaterialInboundReturnOrderItemDO.builder()
                    .returnOrderId(returnOrder.getId())
                    .refInboundItemId(itemVO.getInboundItemId())
                    .materialId(itemVO.getMaterialId())
                    .quantity(itemVO.getQuantity())
                    .tenantId(reqVO.getTenantId())
                    .build();
            inboundReturnOrderItemMapper.insert(item);
        }

        return returnOrder.getId();
    }

    /**
     * 创建入库单（指定订单号前缀）
     */
    private Long createInboundOrderWithPrefix(MaterialIoOrderSaveReqVO createReqVO, String prefixType) {
        // 1. 创建入库单主表
        MaterialInboundOrderDO order = BeanUtils.toBean(createReqVO, MaterialInboundOrderDO.class);
        if (StringUtils.isNotBlank(createReqVO.getOrderNo())) {
            order.setOrderNo(createReqVO.getOrderNo().trim());
        } else {
            order.setOrderNo(orderNoGenerator.generate(prefixType, createReqVO.getOrderDate()));
        }
        if (order.getStatus() == null) {
            order.setStatus(1); // 默认草稿
        }
        inboundOrderMapper.insert(order);
        List<MaterialInfoDO> updateMaterialPriceList = new ArrayList<>();
        // 2. 创建入库明细（兼批次余额）
        List<MaterialIoOrderItemSaveReqVO> items = createReqVO.getItems();
        for (MaterialIoOrderItemSaveReqVO itemVO : items) {
            MaterialInboundOrderItemDO item = BeanUtils.toBean(itemVO, MaterialInboundOrderItemDO.class);
            item.setInboundOrderId(order.getId());
            item.setInQty(itemVO.getQuantity());
            item.setOutQty(BigDecimal.ZERO);
            item.setAvailableQty(itemVO.getQuantity());
            item.setTenantId(createReqVO.getTenantId());
            item.setStoreId(itemVO.getRefInStoreId());
            inboundOrderItemMapper.insert(item);
            updateMaterialPriceList.add(new MaterialInfoDO().setId(itemVO.getMaterialId()).setMaterialInPrice(itemVO.getInUnitPrice()));
        }

        // 3. 更新物资总库存
        updateMaterialStockForInbound(items);
        materialInfoMapper.updateBatch(updateMaterialPriceList);
        return order.getId();
    }

    /**
     * 创建出库单（指定订单号前缀）
     */
    private Long createOutboundOrderWithPrefix(MaterialIoOrderSaveReqVO createReqVO, String prefixType) {
        // 1. 创建出库单主表
        MaterialOutboundOrderDO order = BeanUtils.toBean(createReqVO, MaterialOutboundOrderDO.class);
        order.setOrderNo(orderNoGenerator.generate(prefixType, createReqVO.getOrderDate()));
        if (order.getStatus() == null) {
            order.setStatus(1); // 默认草稿
        }
        outboundOrderMapper.insert(order);

        // 2. 创建出库明细并扣减批次余额
        List<MaterialIoOrderItemSaveReqVO> items = createReqVO.getItems();
        for (MaterialIoOrderItemSaveReqVO itemVO : items) {
            // 校验并扣减批次可用数量
            // 入库明细ID（material_inbound_order_item.id）
            MaterialInboundOrderItemDO inboundItem = inboundOrderItemMapper.selectById(itemVO.getInboundItemId());
            if (inboundItem == null) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "入库批次不存在");
            }
            if (inboundItem.getAvailableQty().compareTo(itemVO.getQuantity()) < 0) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "批次可用数量不足");
            }

            // 创建出库明细
            MaterialOutboundOrderItemDO outboundItem = new MaterialOutboundOrderItemDO();
            outboundItem.setOutboundOrderId(order.getId());
            outboundItem.setRefInboundItemId(itemVO.getInboundItemId()); // 入库明细ID
            outboundItem.setMaterialId(itemVO.getMaterialId());
            outboundItem.setQuantity(itemVO.getQuantity());
            outboundItem.setPrice(itemVO.getSaleUnitPrice());
            outboundItem.setTenantId(createReqVO.getTenantId());
            outboundOrderItemMapper.insert(outboundItem);

            // 更新批次余额
            inboundItem.setOutQty(inboundItem.getOutQty().add(itemVO.getQuantity()));
            inboundItem.setAvailableQty(inboundItem.getAvailableQty().subtract(itemVO.getQuantity()));
            inboundOrderItemMapper.updateById(inboundItem);
        }

        // 3. 更新物资总库存
        updateMaterialStockForOutbound(items, createReqVO.getTenantId());

        return order.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(MaterialIoOrderSaveReqVO updateReqVO) {
        String bizType = updateReqVO.getBizType();
        switch (bizType) {
            case "IN":
                updateInboundOrder(updateReqVO);
                break;
            case "OUT":
                updateOutboundOrder(updateReqVO);
                break;
            case "IN_RETURN":
                updateInboundReturnOrder(updateReqVO);
                break;
            case "OUT_RETURN":
                updateOutboundReturnOrder(updateReqVO);
                break;
            default:
                throw new IllegalArgumentException("不支持的业务类型: " + bizType);
        }
    }

    /**
     * 更新入库单
     */
    private void updateInboundOrder(MaterialIoOrderSaveReqVO updateReqVO) {
        MaterialInboundOrderDO order = inboundOrderMapper.selectById(updateReqVO.getId());
        if (order == null) {
            throw exception(COMMON_NOT_FOUND);
        }

        // 1. 查询旧明细
        List<MaterialInboundOrderItemDO> oldItems = inboundOrderItemMapper.selectList(
                new LambdaQueryWrapper<MaterialInboundOrderItemDO>()
                        .eq(MaterialInboundOrderItemDO::getInboundOrderId, order.getId()));
        Map<Long, MaterialInboundOrderItemDO> oldItemMap = oldItems.stream()
                .filter(e -> e.getId() != null)
                .collect(Collectors.toMap(MaterialInboundOrderItemDO::getId, e -> e));

        // 2. 按入参拆分：更新 / 新增
        List<MaterialIoOrderItemSaveReqVO> reqItems = updateReqVO.getItems() == null ? new ArrayList<>() : updateReqVO.getItems();
        Map<Long, MaterialIoOrderItemSaveReqVO> updateReqMap = reqItems.stream()
                .filter(e -> e.getInboundItemId() != null)
                .collect(Collectors.toMap(MaterialIoOrderItemSaveReqVO::getInboundItemId, e -> e, (a, b) -> a));

        // 3. 计算删除集合（旧有但本次未提交）
        Set<Long> deleteIds = new HashSet<>();
        for (MaterialInboundOrderItemDO oldItem : oldItems) {
            if (oldItem.getId() != null && !updateReqMap.containsKey(oldItem.getId())) {
                deleteIds.add(oldItem.getId());
            }
        }

        // 4. 删除：仅未被出库引用的明细允许删除
        for (Long deleteId : deleteIds) {
            long refCount = outboundOrderItemMapper.selectCount(
                    new LambdaQueryWrapper<MaterialOutboundOrderItemDO>()
                            .eq(MaterialOutboundOrderItemDO::getRefInboundItemId, deleteId));
            if (refCount > 0) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "入库明细已被出库单引用，无法删除");
            }

            MaterialInboundOrderItemDO oldItem = oldItemMap.get(deleteId);
            if (oldItem != null) {
                // 恢复库存：删除入库明细等价于减少入库数量
                MaterialInfoDO material = materialInfoMapper.selectById(oldItem.getMaterialId());
                if (material != null) {
                    material.setMaterialCount(material.getMaterialCount().subtract(oldItem.getInQty()));
                    materialInfoMapper.updateById(material);
                }
                inboundOrderItemMapper.deleteById(deleteId);
            }
        }

        // 5. 更新主表
        MaterialInboundOrderDO updateObj = BeanUtils.toBean(updateReqVO, MaterialInboundOrderDO.class);
        inboundOrderMapper.updateById(updateObj);

        // 6. 更新已有明细（不允许修改 materialId；允许修改数量但需 >= outQty）
        Map<Long, BigDecimal> materialDeltaMap = new HashMap<>();
        // 更新物资信息的入库单价
        List<MaterialInfoDO> updateMaterialPriceList = new ArrayList<>();
        for (Map.Entry<Long, MaterialIoOrderItemSaveReqVO> entry : updateReqMap.entrySet()) {
            Long itemId = entry.getKey();
            MaterialIoOrderItemSaveReqVO itemVO = entry.getValue();
            MaterialInboundOrderItemDO oldItem = oldItemMap.get(itemId);
            if (oldItem == null) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "入库明细不存在: " + itemId);
            }

            // 不允许改物资
            if (itemVO.getMaterialId() != null && !itemVO.getMaterialId().equals(oldItem.getMaterialId())) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "入库明细已存在，不允许修改物资");
            }

            BigDecimal newInQty = itemVO.getQuantity();
            if (newInQty == null) {
                newInQty = BigDecimal.ZERO;
            }
            BigDecimal outQty = oldItem.getOutQty() == null ? BigDecimal.ZERO : oldItem.getOutQty();
            if (newInQty.compareTo(outQty) < 0) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "入库数量不能小于已出库数量");
            }

            BigDecimal oldInQty = oldItem.getInQty() == null ? BigDecimal.ZERO : oldItem.getInQty();
            BigDecimal delta = newInQty.subtract(oldInQty);
            if (delta.compareTo(BigDecimal.ZERO) != 0) {
                materialDeltaMap.merge(oldItem.getMaterialId(), delta, BigDecimal::add);
            }
            MaterialInboundOrderItemDO updateItem = BeanUtils.toBean(itemVO, MaterialInboundOrderItemDO.class);
            updateItem.setId(itemId);
            updateItem.setInboundOrderId(order.getId());
            updateItem.setInQty(newInQty);
            updateItem.setAvailableQty(newInQty.subtract(outQty));
            updateItem.setOutQty(outQty);
            updateItem.setTenantId(updateReqVO.getTenantId());
            updateItem.setStoreId(itemVO.getRefInStoreId());
            inboundOrderItemMapper.updateById(updateItem);
            updateMaterialPriceList.add(new MaterialInfoDO().setId(itemVO.getMaterialId()).setMaterialInPrice(itemVO.getInUnitPrice()));
        }

        // 7. 新增明细
        List<MaterialIoOrderItemSaveReqVO> insertReqItems = reqItems.stream()
                .filter(e -> e.getInboundItemId() == null)
                .collect(Collectors.toList());
        for (MaterialIoOrderItemSaveReqVO itemVO : insertReqItems) {
            MaterialInboundOrderItemDO item = BeanUtils.toBean(itemVO, MaterialInboundOrderItemDO.class);
            item.setInboundOrderId(order.getId());
            item.setInQty(itemVO.getQuantity());
            item.setOutQty(BigDecimal.ZERO);
            item.setAvailableQty(itemVO.getQuantity());
            item.setTenantId(updateReqVO.getTenantId());
            item.setStoreId(itemVO.getRefInStoreId());
            inboundOrderItemMapper.insert(item);
            updateMaterialPriceList.add(new MaterialInfoDO().setId(itemVO.getMaterialId()).setMaterialInPrice(itemVO.getInUnitPrice()));
            BigDecimal addQty = itemVO.getQuantity() == null ? BigDecimal.ZERO : itemVO.getQuantity();
            if (addQty.compareTo(BigDecimal.ZERO) != 0) {
                materialDeltaMap.merge(itemVO.getMaterialId(), addQty, BigDecimal::add);
            }
        }

        // 8. 按差量更新物资总库存
        for (Map.Entry<Long, BigDecimal> entry : materialDeltaMap.entrySet()) {
            MaterialInfoDO material = materialInfoMapper.selectById(entry.getKey());
            if (material != null) {
                material.setMaterialCount(material.getMaterialCount().add(entry.getValue()));
                materialInfoMapper.updateById(material);
            }
        }
        materialInfoMapper.updateBatch(updateMaterialPriceList);
    }

    /**
     * 更新出库单
     */
    private void updateOutboundOrder(MaterialIoOrderSaveReqVO updateReqVO) {
        MaterialOutboundOrderDO order = outboundOrderMapper.selectById(updateReqVO.getId());
        if (order == null) {
            throw exception(COMMON_NOT_FOUND);
        }

        // 1. 恢复批次余额和库存（删除旧明细的影响）
        List<MaterialOutboundOrderItemDO> oldItems = outboundOrderItemMapper.selectList(
                new LambdaQueryWrapper<MaterialOutboundOrderItemDO>()
                        .eq(MaterialOutboundOrderItemDO::getOutboundOrderId, order.getId()));
        for (MaterialOutboundOrderItemDO oldItem : oldItems) {
            MaterialInboundOrderItemDO inboundItem = inboundOrderItemMapper.selectById(oldItem.getRefInboundItemId());
            if (inboundItem != null) {
                inboundItem.setOutQty(inboundItem.getOutQty().subtract(oldItem.getQuantity()));
                inboundItem.setAvailableQty(inboundItem.getAvailableQty().add(oldItem.getQuantity()));
                inboundOrderItemMapper.updateById(inboundItem);
            }

            MaterialInfoDO material = materialInfoMapper.selectById(oldItem.getMaterialId());
            if (material != null) {
                material.setMaterialCount(material.getMaterialCount().add(oldItem.getQuantity()));
                materialInfoMapper.updateById(material);
            }
        }

        // 2. 删除旧明细
        outboundOrderItemMapper.delete(new LambdaQueryWrapper<MaterialOutboundOrderItemDO>()
                .eq(MaterialOutboundOrderItemDO::getOutboundOrderId, order.getId()));

        // 3. 更新主表
        MaterialOutboundOrderDO updateObj = BeanUtils.toBean(updateReqVO, MaterialOutboundOrderDO.class);
        outboundOrderMapper.updateById(updateObj);

        // 4. 插入新明细并扣减批次
        for (MaterialIoOrderItemSaveReqVO itemVO : updateReqVO.getItems()) {
            MaterialInboundOrderItemDO inboundItem = inboundOrderItemMapper.selectById(itemVO.getInboundItemId());
            if (inboundItem == null) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "入库批次不存在");
            }
            if (inboundItem.getAvailableQty().compareTo(itemVO.getQuantity()) < 0) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "批次可用数量不足");
            }

            MaterialOutboundOrderItemDO outboundItem = new MaterialOutboundOrderItemDO();
            outboundItem.setOutboundOrderId(order.getId());
            outboundItem.setRefInboundItemId(itemVO.getInboundItemId());
            outboundItem.setMaterialId(itemVO.getMaterialId());
            outboundItem.setQuantity(itemVO.getQuantity());
            outboundItem.setTenantId(updateReqVO.getTenantId());
            outboundOrderItemMapper.insert(outboundItem);

            inboundItem.setOutQty(inboundItem.getOutQty().add(itemVO.getQuantity()));
            inboundItem.setAvailableQty(inboundItem.getAvailableQty().subtract(itemVO.getQuantity()));
            inboundOrderItemMapper.updateById(inboundItem);
        }

        // 5. 重新计算库存
        updateMaterialStockForOutbound(updateReqVO.getItems(), updateReqVO.getTenantId());
    }

    private void updateInboundReturnOrder(MaterialIoOrderSaveReqVO updateReqVO) {
        // 1. 校验主表存在
        MaterialInboundReturnOrderDO order = inboundReturnOrderMapper.selectById(updateReqVO.getId());
        if (order == null) {
            throw exception(COMMON_NOT_FOUND, "入库退回单不存在");
        }

        // 2. 回滚旧明细对库存和批次的影响
        List<MaterialInboundReturnOrderItemDO> oldItems = inboundReturnOrderItemMapper.selectList(
                new LambdaQueryWrapper<MaterialInboundReturnOrderItemDO>().eq(MaterialInboundReturnOrderItemDO::getReturnOrderId, order.getId()));
        for (MaterialInboundReturnOrderItemDO oldItem : oldItems) {
            // 回滚批次：按你的口径，入库退回是减少 inQty，回滚就是增加 inQty，并同步 availableQty
            MaterialInboundOrderItemDO inboundItem = inboundOrderItemMapper.selectById(oldItem.getRefInboundItemId());
            if (inboundItem != null) {
                rollbackInboundReturnOnBatch(inboundItem, oldItem.getQuantity());
            }
            // 回滚库存：入库退回是减少，回滚就是增加
            updateMaterialStock(oldItem.getMaterialId(), oldItem.getQuantity());
        }

        // 3. 删除旧明细
        inboundReturnOrderItemMapper.delete(new LambdaQueryWrapper<MaterialInboundReturnOrderItemDO>()
                .eq(MaterialInboundReturnOrderItemDO::getReturnOrderId, order.getId()));

        // 4. 更新主表
        MaterialInboundReturnOrderDO updateObj = BeanUtils.toBean(updateReqVO, MaterialInboundReturnOrderDO.class);
        inboundReturnOrderMapper.updateById(updateObj);

        // 5. 插入新明细并重新计算库存和批次
        for (MaterialIoOrderItemSaveReqVO itemVO : updateReqVO.getItems()) {
            MaterialInboundOrderItemDO inboundItem = getInboundItemOrThrow(itemVO.getInboundItemId());
            if (inboundItem.getAvailableQty().compareTo(itemVO.getQuantity()) < 0) {
                throw exception(COMMON_NOT_FOUND, "批次可用数量不足");
            }

            // 更新批次（扣减：按你的口径，减少 inQty，并同步 availableQty）
            applyInboundReturnToBatch(inboundItem, itemVO.getQuantity());

            // 更新库存（减少）
            updateMaterialStock(itemVO.getMaterialId(), itemVO.getQuantity().negate());

            // 插入新明细
            MaterialInboundReturnOrderItemDO newItem = BeanUtils.toBean(itemVO, MaterialInboundReturnOrderItemDO.class);
            newItem.setReturnOrderId(order.getId());
            newItem.setRefInboundItemId(itemVO.getInboundItemId());
            newItem.setTenantId(updateReqVO.getTenantId());
            inboundReturnOrderItemMapper.insert(newItem);
        }
    }

    private void updateOutboundReturnOrder(MaterialIoOrderSaveReqVO updateReqVO) {
        // 1. 校验主表存在
        MaterialOutboundReturnOrderDO order = outboundReturnOrderMapper.selectById(updateReqVO.getId());
        if (order == null) {
            throw exception(COMMON_NOT_FOUND, "出库退回单不存在");
        }

        // 2. 回滚旧明细对库存、批次和出库明细的影响
        List<MaterialOutboundReturnOrderItemDO> oldItems = outboundReturnOrderItemMapper.selectList(
                new LambdaQueryWrapper<MaterialOutboundReturnOrderItemDO>().eq(MaterialOutboundReturnOrderItemDO::getReturnOrderId, order.getId()));
        for (MaterialOutboundReturnOrderItemDO oldItem : oldItems) {
            // 回滚批次：出库退回是增加，回滚就是扣减
            MaterialInboundOrderItemDO inboundItem = inboundOrderItemMapper.selectById(oldItem.getInboundItemId());
            if (inboundItem != null) {
                rollbackOutboundReturnOnBatch(inboundItem, oldItem.getQuantity());
            }
            // 回滚库存：出库退回是增加，回滚就是减少
            updateMaterialStock(oldItem.getMaterialId(), oldItem.getQuantity().negate());

            // 回滚出库明细：把之前扣掉的出库数量加回来
            MaterialOutboundOrderItemDO outboundItem = outboundOrderItemMapper.selectById(oldItem.getOutboundItemId());
            if (outboundItem != null) {
                BigDecimal currentQty = outboundItem.getQuantity() == null ? BigDecimal.ZERO : outboundItem.getQuantity();
                outboundItem.setQuantity(currentQty.add(oldItem.getQuantity()));
                outboundOrderItemMapper.updateById(outboundItem);
            }
        }

        // 3. 删除旧明细
        outboundReturnOrderItemMapper.delete(new LambdaQueryWrapper<MaterialOutboundReturnOrderItemDO>()
                .eq(MaterialOutboundReturnOrderItemDO::getReturnOrderId, order.getId()));

        // 4. 更新主表
        MaterialOutboundReturnOrderDO updateObj = BeanUtils.toBean(updateReqVO, MaterialOutboundReturnOrderDO.class);
        outboundReturnOrderMapper.updateById(updateObj);

        // 5. 插入新明细并重新计算库存、批次、出库明细
        for (MaterialIoOrderItemSaveReqVO itemVO : updateReqVO.getItems()) {
            MaterialInboundOrderItemDO inboundItem = getInboundItemOrThrow(itemVO.getInboundItemId());

            // 校验退回数量不能超过该出库明细当前数量（避免回扣后出现负数）
            MaterialOutboundOrderItemDO outboundItem = outboundOrderItemMapper.selectById(itemVO.getOutboundItemId());
            if (outboundItem == null) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "原出库明细不存在");
            }
            BigDecimal currentOutQty = outboundItem.getQuantity() == null ? BigDecimal.ZERO : outboundItem.getQuantity();
            if (currentOutQty.compareTo(itemVO.getQuantity()) < 0) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "退回数量不能大于原出库数量");
            }

            // 回扣出库明细数量
            outboundItem.setQuantity(currentOutQty.subtract(itemVO.getQuantity()));
            outboundOrderItemMapper.updateById(outboundItem);

            // 更新批次（增加）
            applyOutboundReturnToBatch(inboundItem, itemVO.getQuantity());

            // 更新库存（增加）
            updateMaterialStock(itemVO.getMaterialId(), itemVO.getQuantity());

            // 插入新明细
            MaterialOutboundReturnOrderItemDO newItem = BeanUtils.toBean(itemVO, MaterialOutboundReturnOrderItemDO.class);
            newItem.setReturnOrderId(order.getId());
            newItem.setInboundItemId(itemVO.getInboundItemId());
            newItem.setOutboundItemId(itemVO.getOutboundItemId());
            newItem.setTenantId(updateObj.getTenantId());
            outboundReturnOrderItemMapper.insert(newItem);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long id) {
        // 尝试查询入库单
        MaterialInboundOrderDO inboundOrder = inboundOrderMapper.selectById(id);
        if (inboundOrder != null) {
            deleteInboundOrder(id);
            return;
        }

        // 尝试查询出库单
        MaterialOutboundOrderDO outboundOrder = outboundOrderMapper.selectById(id);
        if (outboundOrder != null) {
            deleteOutboundOrder(id);
            return;
        }

        throw exception(COMMON_NOT_FOUND);
    }

    /**
     * 删除入库单
     */
    private void deleteInboundOrder(Long id) {
        MaterialInboundOrderDO order = inboundOrderMapper.selectById(id);
        if (order == null) {
            throw exception(COMMON_NOT_FOUND);
        }

        // 检查是否有出库单引用此入库单
        List<MaterialInboundOrderItemDO> items = inboundOrderItemMapper.selectList(
                new LambdaQueryWrapper<MaterialInboundOrderItemDO>()
                        .eq(MaterialInboundOrderItemDO::getInboundOrderId, id));
        
        for (MaterialInboundOrderItemDO item : items) {
            long refCount = outboundOrderItemMapper.selectCount(
                    new LambdaQueryWrapper<MaterialOutboundOrderItemDO>()
                            .eq(MaterialOutboundOrderItemDO::getRefInboundItemId, item.getId()));
            if (refCount > 0) {
                throw exceptionCustomMsg(COMMON_NOT_FOUND, "该入库单已被出库单引用，无法删除");
            }
        }

        // 恢复库存
        for (MaterialInboundOrderItemDO item : items) {
            MaterialInfoDO material = materialInfoMapper.selectById(item.getMaterialId());
            if (material != null) {
                material.setMaterialCount(material.getMaterialCount().subtract(item.getInQty()));
                materialInfoMapper.updateById(material);
            }
        }

        // 删除明细
        inboundOrderItemMapper.delete(new LambdaQueryWrapper<MaterialInboundOrderItemDO>()
                .eq(MaterialInboundOrderItemDO::getInboundOrderId, id));

        // 删除主表
        inboundOrderMapper.deleteById(id);
    }

    /**
     * 删除出库单
     */
    private void deleteOutboundOrder(Long id) {
        MaterialOutboundOrderDO order = outboundOrderMapper.selectById(id);
        if (order == null) {
            throw exception(COMMON_NOT_FOUND);
        }

        // 恢复批次余额和库存
        List<MaterialOutboundOrderItemDO> items = outboundOrderItemMapper.selectList(
                new LambdaQueryWrapper<MaterialOutboundOrderItemDO>()
                        .eq(MaterialOutboundOrderItemDO::getOutboundOrderId, id));

        for (MaterialOutboundOrderItemDO item : items) {
            MaterialInboundOrderItemDO inboundItem = inboundOrderItemMapper.selectById(item.getRefInboundItemId());
            if (inboundItem != null) {
                inboundItem.setOutQty(inboundItem.getOutQty().subtract(item.getQuantity()));
                inboundItem.setAvailableQty(inboundItem.getAvailableQty().add(item.getQuantity()));
                inboundOrderItemMapper.updateById(inboundItem);
            }

            MaterialInfoDO material = materialInfoMapper.selectById(item.getMaterialId());
            if (material != null) {
                material.setMaterialCount(material.getMaterialCount().add(item.getQuantity()));
                materialInfoMapper.updateById(material);
            }
        }

        // 删除明细
        outboundOrderItemMapper.delete(new LambdaQueryWrapper<MaterialOutboundOrderItemDO>()
                .eq(MaterialOutboundOrderItemDO::getOutboundOrderId, id));

        // 删除主表
        outboundOrderMapper.deleteById(id);
    }


    @Override
    public MaterialInboundOrderRespVO getInboundOrder(Long id) {
        MaterialInboundOrderDO order = inboundOrderMapper.selectById(id);
        if (order == null) {
            throw exception(COMMON_NOT_FOUND);
        }

        MaterialInboundOrderRespVO respVO = BeanUtils.toBean(order, MaterialInboundOrderRespVO.class);

        // 查询明细
        List<MaterialInboundOrderItemDO> items = inboundOrderItemMapper.selectList(
                new LambdaQueryWrapper<MaterialInboundOrderItemDO>()
                        .eq(MaterialInboundOrderItemDO::getInboundOrderId, id));

        List<MaterialInboundOrderItemRespVO> itemRespVOs = new ArrayList<>();
        Set<Long> storeIds = new HashSet<>();
        Set<Long> supplierIds = new HashSet<>();
        Set<Long> materialIds = new HashSet<>();

        for (MaterialInboundOrderItemDO item : items) {
            MaterialInboundOrderItemRespVO itemRespVO = new MaterialInboundOrderItemRespVO();
            itemRespVO.setId(item.getId());
            itemRespVO.setOrderId(id);
            itemRespVO.setMaterialId(item.getMaterialId());
            itemRespVO.setInQty(item.getInQty());
            itemRespVO.setOutQty(item.getOutQty());
            itemRespVO.setAvailableQty(item.getAvailableQty());
            itemRespVO.setInUnitPrice(item.getInUnitPrice());
            itemRespVO.setSaleUnitPrice(item.getSaleUnitPrice());
            itemRespVO.setProduceDate(item.getProduceDate());
            // 计算金额
            if (item.getInQty() != null && item.getInUnitPrice() != null) {
                itemRespVO.setAmount(item.getInUnitPrice().multiply(item.getInQty()));
            }
            itemRespVOs.add(itemRespVO);

            materialIds.add(item.getMaterialId());
        }

        // 填充物资信息（包括仓库ID和供应商ID）
        if (!materialIds.isEmpty()) {
            Map<Long, MaterialInfoDO> materialMap = materialInfoMapper.selectList(
                    new LambdaQueryWrapperX<MaterialInfoDO>().in(MaterialInfoDO::getId, materialIds)).stream()
                    .collect(Collectors.toMap(MaterialInfoDO::getId, material -> material));
            for (MaterialInboundOrderItemRespVO itemRespVO : itemRespVOs) {
                MaterialInfoDO material = materialMap.get(itemRespVO.getMaterialId());
                if (material != null) {
                    itemRespVO.setMaterialNumber(material.getMaterialNumber());
                    itemRespVO.setMaterialName(material.getMaterialName());
                    itemRespVO.setSpecification(material.getSpecification());
                    itemRespVO.setMaterialUnit(material.getMaterialUnit());
                    // 从material_info获取仓库ID和供应商ID
                    if (material.getStoreId() != null) {
                        itemRespVO.setRefInStoreId(material.getStoreId());
                        storeIds.add(material.getStoreId());
                    }
                    if (material.getSupplierId() != null) {
                        itemRespVO.setSupplierId(material.getSupplierId());
                        supplierIds.add(material.getSupplierId());
                    }
                }
            }

            // 填充物资类别信息
            Set<Long> categoryIds = materialMap.values().stream()
                    .map(MaterialInfoDO::getCategoryId)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet());
            if (!categoryIds.isEmpty()) {
                Map<Long, String> categoryNameMap = materialCategoryMapper.selectBatchIds(categoryIds).stream()
                        .collect(Collectors.toMap(MaterialCategoryDO::getId, MaterialCategoryDO::getCategoryName));
                for (MaterialInboundOrderItemRespVO itemRespVO : itemRespVOs) {
                    MaterialInfoDO material = materialMap.get(itemRespVO.getMaterialId());
                    if (material != null && material.getCategoryId() != null) {
                        itemRespVO.setCategoryName(categoryNameMap.get(material.getCategoryId()));
                    }
                }
            }
        }

        // 填充仓库名称
        if (!storeIds.isEmpty()) {
            Map<Long, String> storeNameMap = storeManageMapper.selectList(
                    new LambdaQueryWrapperX<StoreManageDO>().in(StoreManageDO::getId, storeIds)).stream()
                    .collect(Collectors.toMap(StoreManageDO::getId, StoreManageDO::getStoreName));
            itemRespVOs.forEach(item -> {
                if (item.getRefInStoreId() != null) {
                    item.setRefInStoreName(storeNameMap.get(item.getRefInStoreId()));
                }
            });
        }

        // 填充供应商名称
        if (!supplierIds.isEmpty()) {
            Map<Long, String> supplierNameMap = supplierInfoMapper.selectList(
                    new LambdaQueryWrapperX<SupplierInfoDO>().in(SupplierInfoDO::getId, supplierIds)).stream()
                    .collect(Collectors.toMap(SupplierInfoDO::getId, SupplierInfoDO::getSupplierName));
            itemRespVOs.forEach(item -> {
                if (item.getSupplierId() != null) {
                    item.setSupplierName(supplierNameMap.get(item.getSupplierId()));
                }
            });
        }

        respVO.setItems(itemRespVOs);
        return respVO;
    }

    @Override
    public MaterialOutboundOrderRespVO getOutboundOrder(Long id) {
        MaterialOutboundOrderDO order = outboundOrderMapper.selectById(id);
        if (order == null) {
            throw exception(COMMON_NOT_FOUND);
        }
        MaterialOutboundOrderRespVO respVO = BeanUtils.toBean(order, MaterialOutboundOrderRespVO.class);
        // 填充部门名称
        if (order.getOutDeptId() != null) {
            DeptDO dept = deptMapper.selectById(order.getOutDeptId());
            if (dept != null) {
                respVO.setOutDeptName(dept.getName());
            }
        }
        // 查询明细
        List<MaterialOutboundOrderItemDO> items = outboundOrderItemMapper.selectList(
                new LambdaQueryWrapper<MaterialOutboundOrderItemDO>()
                        .eq(MaterialOutboundOrderItemDO::getOutboundOrderId, id));

        List<MaterialOutboundOrderItemRespVO> itemRespVOs = new ArrayList<>();
        Set<Long> inboundItemIds = new HashSet<>();
        Set<Long> materialIds = new HashSet<>();
        Set<Long> storeIds = new HashSet<>();
        for (MaterialOutboundOrderItemDO item : items) {
            MaterialOutboundOrderItemRespVO itemRespVO = new MaterialOutboundOrderItemRespVO();
            itemRespVO.setId(item.getId());
            itemRespVO.setOrderId(id);
            itemRespVO.setMaterialId(item.getMaterialId());
            itemRespVO.setQuantity(item.getQuantity());
            itemRespVO.setSaleUnitPrice(item.getPrice());
            itemRespVO.setRefInboundItemId(item.getRefInboundItemId());
            itemRespVOs.add(itemRespVO);
            inboundItemIds.add(item.getRefInboundItemId());
            materialIds.add(item.getMaterialId());
        }
        // 查询入库明细信息
        if (!inboundItemIds.isEmpty()) {
            Map<Long, MaterialInboundOrderItemDO> inboundItemMap = inboundOrderItemMapper.selectList(
                    new LambdaQueryWrapperX<MaterialInboundOrderItemDO>().in(MaterialInboundOrderItemDO::getId, inboundItemIds)).stream()
                    .collect(Collectors.toMap(MaterialInboundOrderItemDO::getId, item -> item));

            // 收集入库单ID，用于查询入库单主表获取订单号
            Set<Long> inboundOrderIds = inboundItemMap.values().stream()
                    .map(MaterialInboundOrderItemDO::getInboundOrderId)
                    .collect(Collectors.toSet());
            Map<Long, MaterialInboundOrderDO> inboundOrderMap = new HashMap<>();
            if (!inboundOrderIds.isEmpty()) {
                inboundOrderMap = inboundOrderMapper.selectList(
                        new LambdaQueryWrapperX<MaterialInboundOrderDO>().in(MaterialInboundOrderDO::getId, inboundOrderIds)).stream()
                        .collect(Collectors.toMap(MaterialInboundOrderDO::getId, e -> e));
            }

            for (MaterialOutboundOrderItemRespVO itemRespVO : itemRespVOs) {
                MaterialInboundOrderItemDO inboundItem = inboundItemMap.get(itemRespVO.getRefInboundItemId());
                if (inboundItem != null) {
                    itemRespVO.setInUnitPrice(inboundItem.getInUnitPrice());
                    itemRespVO.setProduceDate(inboundItem.getProduceDate());
                    itemRespVO.setLatestAvailableQty(inboundItem.getAvailableQty().add(itemRespVO.getQuantity()));
                    // 填充入库单号
                    MaterialInboundOrderDO inboundOrder = inboundOrderMap.get(inboundItem.getInboundOrderId());
                    if (inboundOrder != null) {
                        itemRespVO.setRefInOrderNo(inboundOrder.getOrderNo());
                    }
                    // 计算金额
                    if (itemRespVO.getQuantity() != null && itemRespVO.getInUnitPrice() != null) {
                        itemRespVO.setAmount(itemRespVO.getInUnitPrice().multiply(itemRespVO.getQuantity()));
                    }
                }
            }
        }

        // 填充物资信息和供应商信息
        if (!materialIds.isEmpty()) {
            Map<Long, MaterialInfoDO> materialMap = materialInfoMapper.selectList(
                    new LambdaQueryWrapperX<MaterialInfoDO>().in(MaterialInfoDO::getId, materialIds)).stream()
                    .collect(Collectors.toMap(MaterialInfoDO::getId, material -> material));
            for (MaterialOutboundOrderItemRespVO itemRespVO : itemRespVOs) {
                MaterialInfoDO material = materialMap.get(itemRespVO.getMaterialId());
                if (material != null) {
                    itemRespVO.setMaterialNumber(material.getMaterialNumber());
                    itemRespVO.setMaterialName(material.getMaterialName());
                    itemRespVO.setSpecification(material.getSpecification());
                    itemRespVO.setMaterialUnit(material.getMaterialUnit());
                    // 从material_info获取仓库ID和供应商ID
                    if (material.getStoreId() != null) {
                        itemRespVO.setRefInStoreId(material.getStoreId());
                        storeIds.add(material.getStoreId());
                    }
                }
            }
        }

        // 填充仓库名称
        if (!storeIds.isEmpty()) {
            Map<Long, String> storeNameMap = storeManageMapper.selectList(
                            new LambdaQueryWrapperX<StoreManageDO>().in(StoreManageDO::getId, storeIds)).stream()
                    .collect(Collectors.toMap(StoreManageDO::getId, StoreManageDO::getStoreName));
            itemRespVOs.forEach(item -> {
                if (item.getRefInStoreId() != null) {
                    item.setRefInStoreName(storeNameMap.get(item.getRefInStoreId()));
                }
            });
        }
        respVO.setItems(itemRespVOs);
        return respVO;
    }

    @Override
    public PageResult<MaterialIoOrderPageRespVO> page(MaterialIoOrderPageReqVO pageReqVO) {
        // 根据 bizType 查询不同的表
        String bizType = pageReqVO.getBizType();
        if (bizType == null || bizType.isEmpty()) {
            // 如果没有指定 bizType，返回空结果（或者可以合并查询，但性能较差）
            return new PageResult<>(new ArrayList<>(), 0L);
        }

        switch (bizType) {
            case "IN":
            case "OUT_RETURN":
                return pageInboundOrders(pageReqVO, bizType);
            case "OUT":
            case "IN_RETURN":
                return pageOutboundOrders(pageReqVO, bizType);
            default:
                return new PageResult<>(new ArrayList<>(), 0L);
        }
    }

    /**
     * 分页查询入库单
     */
    private PageResult<MaterialIoOrderPageRespVO> pageInboundOrders(MaterialIoOrderPageReqVO pageReqVO, String bizType) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<MaterialInboundOrderDO> page =
                new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());

        LambdaQueryWrapper<MaterialInboundOrderDO> wrapper = new LambdaQueryWrapper<>();
        if (pageReqVO.getOrderNo() != null) {
            wrapper.like(MaterialInboundOrderDO::getOrderNo, pageReqVO.getOrderNo());
        }
        if (pageReqVO.getStatus() != null) {
            wrapper.eq(MaterialInboundOrderDO::getStatus, pageReqVO.getStatus());
        }
        if (pageReqVO.getOrderDate() != null && pageReqVO.getOrderDate().length == 2) {
            wrapper.between(MaterialInboundOrderDO::getOrderDate, pageReqVO.getOrderDate()[0], pageReqVO.getOrderDate()[1]);
        }
        if (pageReqVO.getTenantIds() != null && pageReqVO.getTenantIds().length > 0) {
            wrapper.in(MaterialInboundOrderDO::getTenantId, (Object[]) pageReqVO.getTenantIds());
        }
        wrapper.orderByDesc(MaterialInboundOrderDO::getId);

        com.baomidou.mybatisplus.core.metadata.IPage<MaterialInboundOrderDO> pageResult = inboundOrderMapper.selectPage(page, wrapper);

        // 批量查询明细用于生成汇总
        List<Long> orderIds = pageResult.getRecords().stream().map(MaterialInboundOrderDO::getId).collect(Collectors.toList());
        Map<Long, List<MaterialInboundOrderItemDO>> itemsMap = new HashMap<>();
        if (!orderIds.isEmpty()) {
            List<MaterialInboundOrderItemDO> allItems = inboundOrderItemMapper.selectList(
                    new LambdaQueryWrapperX<MaterialInboundOrderItemDO>().in(MaterialInboundOrderItemDO::getInboundOrderId, orderIds));
            itemsMap = allItems.stream().collect(Collectors.groupingBy(MaterialInboundOrderItemDO::getInboundOrderId));
        }

        // 批量查询物资信息
        Set<Long> materialIds = itemsMap.values().stream()
                .flatMap(List::stream)
                .map(MaterialInboundOrderItemDO::getMaterialId)
                .collect(Collectors.toSet());
        Map<Long, MaterialInfoDO> materialMap = new HashMap<>();
        if (!materialIds.isEmpty()) {
            materialMap = materialInfoMapper.selectList(
                    new LambdaQueryWrapperX<MaterialInfoDO>().in(MaterialInfoDO::getId, materialIds)).stream()
                    .collect(Collectors.toMap(MaterialInfoDO::getId, material -> material));
        }

        Map<Long, List<MaterialInboundOrderItemDO>> finalItemsMap = itemsMap;
        Map<Long, MaterialInfoDO> finalMaterialMap = materialMap;
        List<MaterialIoOrderPageRespVO> respVOs = pageResult.getRecords().stream()
                .map(order -> {
                    MaterialIoOrderPageRespVO vo = BeanUtils.toBean(order, MaterialIoOrderPageRespVO.class);
                    vo.setBizType(bizType);
                    // 生成明细汇总
                    List<MaterialInboundOrderItemDO> items = finalItemsMap.getOrDefault(order.getId(), new ArrayList<>());
                    vo.setDetailSummary(buildDetailSummary(items, finalMaterialMap));
                    return vo;
                })
                .collect(Collectors.toList());

        return new PageResult<>(respVOs, pageResult.getTotal());
    }

    /**
     * 分页查询出库单
     */
    private PageResult<MaterialIoOrderPageRespVO> pageOutboundOrders(MaterialIoOrderPageReqVO pageReqVO, String bizType) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<MaterialOutboundOrderDO> page =
                new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());

        LambdaQueryWrapper<MaterialOutboundOrderDO> wrapper = new LambdaQueryWrapper<>();
        if (pageReqVO.getOrderNo() != null) {
            wrapper.like(MaterialOutboundOrderDO::getOrderNo, pageReqVO.getOrderNo());
        }
        if (pageReqVO.getStatus() != null) {
            wrapper.eq(MaterialOutboundOrderDO::getStatus, pageReqVO.getStatus());
        }
        if (pageReqVO.getOrderDate() != null && pageReqVO.getOrderDate().length == 2) {
            wrapper.between(MaterialOutboundOrderDO::getOrderDate, pageReqVO.getOrderDate()[0], pageReqVO.getOrderDate()[1]);
        }
        if (pageReqVO.getTenantIds() != null && pageReqVO.getTenantIds().length > 0) {
            wrapper.in(MaterialOutboundOrderDO::getTenantId, (Object[]) pageReqVO.getTenantIds());
        }
        wrapper.orderByDesc(MaterialOutboundOrderDO::getId);

        com.baomidou.mybatisplus.core.metadata.IPage<MaterialOutboundOrderDO> pageResult = outboundOrderMapper.selectPage(page, wrapper);

        // 填充部门名称
        Set<Long> deptIds = pageResult.getRecords().stream()
                .map(MaterialOutboundOrderDO::getOutDeptId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        Map<Long, String> deptNameMap = new HashMap<>();
        if (!deptIds.isEmpty()) {
            deptNameMap = deptMapper.selectList(
                    new LambdaQueryWrapperX<DeptDO>().in(DeptDO::getId, deptIds)).stream()
                    .collect(Collectors.toMap(DeptDO::getId, DeptDO::getName));
        }

        // 批量查询明细用于生成汇总
        List<Long> orderIds = pageResult.getRecords().stream().map(MaterialOutboundOrderDO::getId).collect(Collectors.toList());
        Map<Long, List<MaterialOutboundOrderItemDO>> itemsMap = new HashMap<>();
        if (!orderIds.isEmpty()) {
            List<MaterialOutboundOrderItemDO> allItems = outboundOrderItemMapper.selectList(
                    new LambdaQueryWrapperX<MaterialOutboundOrderItemDO>().in(MaterialOutboundOrderItemDO::getOutboundOrderId, orderIds));
            itemsMap = allItems.stream().collect(Collectors.groupingBy(MaterialOutboundOrderItemDO::getOutboundOrderId));
        }

        // 批量查询入库明细和物资信息
        Set<Long> inboundItemIds = itemsMap.values().stream()
                .flatMap(List::stream)
                .map(MaterialOutboundOrderItemDO::getRefInboundItemId)
                .collect(Collectors.toSet());
        Map<Long, MaterialInboundOrderItemDO> inboundItemMap = new HashMap<>();
        if (!inboundItemIds.isEmpty()) {
            inboundItemMap = inboundOrderItemMapper.selectList(
                    new LambdaQueryWrapperX<MaterialInboundOrderItemDO>().in(MaterialInboundOrderItemDO::getId, inboundItemIds)).stream()
                    .collect(Collectors.toMap(MaterialInboundOrderItemDO::getId, item -> item));
        }

        Set<Long> materialIds = itemsMap.values().stream()
                .flatMap(List::stream)
                .map(MaterialOutboundOrderItemDO::getMaterialId)
                .collect(Collectors.toSet());
        Map<Long, MaterialInfoDO> materialMap = new HashMap<>();
        if (!materialIds.isEmpty()) {
            materialMap = materialInfoMapper.selectList(
                    new LambdaQueryWrapperX<MaterialInfoDO>().in(MaterialInfoDO::getId, materialIds)).stream()
                    .collect(Collectors.toMap(MaterialInfoDO::getId, material -> material));
        }

        Map<Long, String> finalDeptNameMap = deptNameMap;
        Map<Long, List<MaterialOutboundOrderItemDO>> finalItemsMap = itemsMap;
        Map<Long, MaterialInboundOrderItemDO> finalInboundItemMap = inboundItemMap;
        Map<Long, MaterialInfoDO> finalMaterialMap = materialMap;
        List<MaterialIoOrderPageRespVO> respVOs = pageResult.getRecords().stream()
                .map(order -> {
                    MaterialIoOrderPageRespVO vo = BeanUtils.toBean(order, MaterialIoOrderPageRespVO.class);
                    vo.setBizType(bizType);
                    if (order.getOutDeptId() != null) {
                        vo.setOutDeptName(finalDeptNameMap.get(order.getOutDeptId()));
                    }
                    // 生成明细汇总
                    List<MaterialOutboundOrderItemDO> items = finalItemsMap.getOrDefault(order.getId(), new ArrayList<>());
                    vo.setDetailSummary(buildDetailSummaryForOutbound(items, finalInboundItemMap, finalMaterialMap));
                    return vo;
                })
                .collect(Collectors.toList());

        return new PageResult<>(respVOs, pageResult.getTotal());
    }

    /**
     * 构建入库单明细汇总
     * 格式：物资名称 (数量: X; 单价: Y.YY)
     */
    private String buildDetailSummary(List<MaterialInboundOrderItemDO> items, Map<Long, MaterialInfoDO> materialMap) {
        if (items == null || items.isEmpty()) {
            return "";
        }
        return items.stream()
                .map(item -> {
                    MaterialInfoDO material = materialMap.get(item.getMaterialId());
                    String materialName = material != null ? material.getMaterialName() : "未知";
                    String quantity = item.getInQty() != null ? String.valueOf(item.getInQty()) : "0";
                    String price = item.getInUnitPrice() != null ? String.format("%.2f", item.getInUnitPrice()) : "0.00";
                    return materialName + " (数量: " + quantity + "; 单价: " + price + ")";
                })
                .collect(Collectors.joining(", "));
    }

    /**
     * 构建出库单明细汇总
     * 格式：物资名称 (数量: X; 单价: Y.YY)
     */
    private String buildDetailSummaryForOutbound(List<MaterialOutboundOrderItemDO> items, 
                                                  Map<Long, MaterialInboundOrderItemDO> inboundItemMap,
                                                  Map<Long, MaterialInfoDO> materialMap) {
        if (items == null || items.isEmpty()) {
            return "";
        }
        return items.stream()
                .map(item -> {
                    MaterialInfoDO material = materialMap.get(item.getMaterialId());
                    String materialName = material != null ? material.getMaterialName() : "未知";
                    String quantity = item.getQuantity() != null ? String.valueOf(item.getQuantity()) : "0";
                    // 从入库明细中获取单价
                    MaterialInboundOrderItemDO inboundItem = inboundItemMap.get(item.getRefInboundItemId());
                    String price = "0.00";
                    if (inboundItem != null && inboundItem.getInUnitPrice() != null) {
                        price = String.format("%.2f", inboundItem.getInUnitPrice());
                    }
                    return materialName + " (数量: " + quantity + "; 单价: " + price + ")";
                })
                .collect(Collectors.joining(", "));
    }

    @Override
    public List<MaterialInBalanceRespVO> listInboundBatches(MaterialInBalanceListReqVO reqVO) {
        return inBalanceMapper.selectInBalanceList(reqVO);
    }

    @Override
    public List<MaterialInboundOrderDO> inboundList(Long[] tenantIds) {
        return inboundOrderMapper.selectList(new LambdaQueryWrapperX<MaterialInboundOrderDO>().in(MaterialInboundOrderDO::getTenantId,tenantIds));
    }

    @Override
    public List<MaterialOutboundOrderDO> outboundList(Long[] tenantIds) {
        return outboundOrderMapper.selectList(new LambdaQueryWrapperX<MaterialOutboundOrderDO>().in(MaterialOutboundOrderDO::getTenantId,tenantIds));
    }

    @Override
    public MaterialOutboundReturnOrderRespVO getOutboundReturn(Long id) {
        // 1. 查询主表
        MaterialOutboundReturnOrderDO order = outboundReturnOrderMapper.selectById(id);
        if (order == null) {
            throw exception(COMMON_NOT_FOUND, "出库退回单不存在");
        }
        // 2. 查询明细
        List<MaterialOutboundReturnOrderItemDO> items = outboundReturnOrderItemMapper.selectList(
                new LambdaQueryWrapper<MaterialOutboundReturnOrderItemDO>()
                        .eq(MaterialOutboundReturnOrderItemDO::getReturnOrderId, id));

        // 3. 转换为VO
        MaterialOutboundReturnOrderRespVO respVO = BeanUtils.toBean(order, MaterialOutboundReturnOrderRespVO.class);
        MaterialOutboundOrderDO materialOutboundOrderDO = outboundOrderMapper.selectById(order.getSourceOutboundOrderId());
        respVO.setSourceOutboundOrderNo(materialOutboundOrderDO.getOrderNo());
        // 4. 填充明细
        if (!items.isEmpty()) {
            // 4.1 获取物资ID列表并批量加载
            Set<Long> materialIds = items.stream()
                    .map(MaterialOutboundReturnOrderItemDO::getMaterialId)
                    .collect(Collectors.toSet());
            Map<Long, MaterialInfoDO> materialMap = loadMaterialMap(materialIds);

            // 4.2 获取入库批次信息
            Set<Long> inboundItemIds = items.stream()
                    .map(MaterialOutboundReturnOrderItemDO::getInboundItemId)
                    .collect(Collectors.toSet());
            Map<Long, MaterialInboundOrderItemDO> inboundItemMap = inboundOrderItemMapper.selectBatchIds(inboundItemIds).stream()
                    .collect(Collectors.toMap(MaterialInboundOrderItemDO::getId, Function.identity()));

            // 4.3 通过物资信息联查仓库名称（material_info.store_id -> store_manage.store_name）
            Map<Long, String> finalStoreNameMap = loadStoreNameMapByMaterialMap(materialMap);

            // 4.4 转换明细
            List<MaterialOutboundReturnOrderItemRespVO> itemVOs = items.stream().map(item -> {
                MaterialOutboundReturnOrderItemRespVO itemVO = BeanUtils.toBean(item, MaterialOutboundReturnOrderItemRespVO.class);

                // 填充物资信息
                MaterialInfoDO material = materialMap.get(item.getMaterialId());
                if (material != null) {
                    itemVO.setMaterialName(material.getMaterialName());
                    itemVO.setMaterialNumber(material.getMaterialNumber());
                    itemVO.setMaterialUnit(material.getMaterialUnit());
                    if (material.getStoreId() != null) {
                        itemVO.setStoreName(finalStoreNameMap.get(material.getStoreId()));
                    }
                }

                // 填充批次信息
                MaterialInboundOrderItemDO inboundItem = inboundItemMap.get(item.getInboundItemId());
                if (inboundItem != null) {
                    itemVO.setInUnitPrice(inboundItem.getInUnitPrice());
                    if (inboundItem.getInUnitPrice() != null && item.getQuantity() != null) {
                        itemVO.setAmount(inboundItem.getInUnitPrice().multiply(item.getQuantity()));
                    }
                }

                return itemVO;
            }).collect(Collectors.toList());

            respVO.setItems(itemVOs);
        }

        return respVO;
    }

    @Override
    public MaterialInboundReturnOrderRespVO getInboundReturn(Long id) {
        // 1. 查询主表
        MaterialInboundReturnOrderDO order = inboundReturnOrderMapper.selectById(id);
        if (order == null) {
            throw exception(COMMON_NOT_FOUND, "入库退回单不存在");
        }

        // 2. 查询明细
        List<MaterialInboundReturnOrderItemDO> items = inboundReturnOrderItemMapper.selectList(
                new LambdaQueryWrapper<MaterialInboundReturnOrderItemDO>()
                        .eq(MaterialInboundReturnOrderItemDO::getReturnOrderId, id));

        // 3. 转换为VO
        MaterialInboundReturnOrderRespVO respVO = BeanUtils.toBean(order, MaterialInboundReturnOrderRespVO.class);
        MaterialInboundOrderDO materialInboundOrderDO = inboundOrderMapper.selectById(order.getSourceInboundOrderId());
        respVO.setSourceInboundOrderNo(materialInboundOrderDO.getOrderNo());
        // 4. 填充明细
        if (!items.isEmpty()) {
            // 4.1 获取物资ID列表并批量加载
            Set<Long> materialIds = items.stream()
                    .map(MaterialInboundReturnOrderItemDO::getMaterialId)
                    .collect(Collectors.toSet());
            Map<Long, MaterialInfoDO> materialMap = loadMaterialMap(materialIds);

            // 4.2 获取入库批次信息
            Set<Long> inboundItemIds = items.stream()
                    .map(MaterialInboundReturnOrderItemDO::getRefInboundItemId)
                    .collect(Collectors.toSet());
            Map<Long, MaterialInboundOrderItemDO> inboundItemMap = inboundOrderItemMapper.selectBatchIds(inboundItemIds).stream()
                    .collect(Collectors.toMap(MaterialInboundOrderItemDO::getId, Function.identity()));

            // 4.3 通过物资信息联查仓库名称（material_info.store_id -> store_manage.store_name）
            Map<Long, String> finalStoreNameMap = loadStoreNameMapByMaterialMap(materialMap);

            // 4.4 转换明细
            List<MaterialInboundReturnOrderItemRespVO> itemVOs = items.stream().map(item -> {
                MaterialInboundReturnOrderItemRespVO itemVO = BeanUtils.toBean(item, MaterialInboundReturnOrderItemRespVO.class);

                // 填充物资信息
                MaterialInfoDO material = materialMap.get(item.getMaterialId());
                if (material != null) {
                    itemVO.setMaterialName(material.getMaterialName());
                    itemVO.setMaterialNumber(material.getMaterialNumber());
                    itemVO.setMaterialUnit(material.getMaterialUnit());
                    if (material.getStoreId() != null) {
                        itemVO.setStoreName(finalStoreNameMap.get(material.getStoreId()));
                    }
                }

                // 填充批次信息
                MaterialInboundOrderItemDO inboundItem = inboundItemMap.get(item.getRefInboundItemId());
                if (inboundItem != null) {
                    itemVO.setInUnitPrice(inboundItem.getInUnitPrice());
                    itemVO.setAvailableQty(inboundItem.getAvailableQty().add(item.getQuantity()));
                    if (inboundItem.getInUnitPrice() != null && item.getQuantity() != null) {
                        itemVO.setAmount(inboundItem.getInUnitPrice().multiply(item.getQuantity()));
                    }
                }

                return itemVO;
            }).collect(Collectors.toList());

            respVO.setItems(itemVOs);
        }

        return respVO;
    }

    @Override
    public PageResult<MaterialOutboundReturnOrderPageRespVO> pageOutboundReturn(MaterialOutboundReturnOrderPageReqVO reqVO) {
        Page<MaterialOutboundReturnOrderDO> page = new Page<>(reqVO.getPageNo(), reqVO.getPageSize());
        LambdaQueryWrapper<MaterialOutboundReturnOrderDO> wrapper = new LambdaQueryWrapper<>();
        if (reqVO.getOrderNo() != null) {
            wrapper.like(MaterialOutboundReturnOrderDO::getOrderNo, reqVO.getOrderNo());
        }
        if (reqVO.getStatus() != null) {
            wrapper.eq(MaterialOutboundReturnOrderDO::getStatus, reqVO.getStatus());
        }
        if (reqVO.getSourceOutboundOrderId() != null) {
            wrapper.eq(MaterialOutboundReturnOrderDO::getSourceOutboundOrderId, reqVO.getSourceOutboundOrderId());
        }
        if (reqVO.getOrderDate() != null && reqVO.getOrderDate().length == 2) {
            wrapper.between(MaterialOutboundReturnOrderDO::getOrderDate, reqVO.getOrderDate()[0], reqVO.getOrderDate()[1]);
        }
        if (reqVO.getTenantIds() != null && reqVO.getTenantIds().length > 0) {
            wrapper.in(MaterialOutboundReturnOrderDO::getTenantId, (Object[]) reqVO.getTenantIds());
        }
        wrapper.orderByDesc(MaterialOutboundReturnOrderDO::getId);

        IPage<MaterialOutboundReturnOrderDO> pageResult = outboundReturnOrderMapper.selectPage(page, wrapper);
        List<MaterialOutboundReturnOrderPageRespVO> list = BeanUtils.toBean(pageResult.getRecords(), MaterialOutboundReturnOrderPageRespVO.class);

        // 联查原出库单号
        Set<Long> sourceOrderIds = list.stream()
                .map(MaterialOutboundReturnOrderPageRespVO::getSourceOutboundOrderId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (!sourceOrderIds.isEmpty()) {
            Map<Long, String> sourceOrderNoMap = outboundOrderMapper.selectList(
                    new LambdaQueryWrapperX<MaterialOutboundOrderDO>().in(MaterialOutboundOrderDO::getId, sourceOrderIds)).stream()
                    .collect(Collectors.toMap(MaterialOutboundOrderDO::getId, MaterialOutboundOrderDO::getOrderNo));
            list.forEach(vo -> {
                if (vo.getSourceOutboundOrderId() != null) {
                    vo.setSourceOutboundOrderNo(sourceOrderNoMap.get(vo.getSourceOutboundOrderId()));
                }
            });
        }

        return new PageResult<>(list, pageResult.getTotal());
    }

    @Override
    public PageResult<MaterialInboundReturnOrderPageRespVO> pageInboundReturn(MaterialInboundReturnOrderPageReqVO reqVO) {
        Page<MaterialInboundReturnOrderDO> page = new Page<>(reqVO.getPageNo(), reqVO.getPageSize());
        LambdaQueryWrapper<MaterialInboundReturnOrderDO> wrapper = new LambdaQueryWrapper<>();
        if (reqVO.getOrderNo() != null) {
            wrapper.like(MaterialInboundReturnOrderDO::getOrderNo, reqVO.getOrderNo());
        }
        if (reqVO.getStatus() != null) {
            wrapper.eq(MaterialInboundReturnOrderDO::getStatus, reqVO.getStatus());
        }
        if (reqVO.getSourceInboundOrderId() != null) {
            wrapper.eq(MaterialInboundReturnOrderDO::getSourceInboundOrderId, reqVO.getSourceInboundOrderId());
        }
        if (reqVO.getOrderDate() != null && reqVO.getOrderDate().length == 2) {
            wrapper.between(MaterialInboundReturnOrderDO::getOrderDate, reqVO.getOrderDate()[0], reqVO.getOrderDate()[1]);
        }
        if (reqVO.getTenantIds() != null && reqVO.getTenantIds().length > 0) {
            wrapper.in(MaterialInboundReturnOrderDO::getTenantId, (Object[]) reqVO.getTenantIds());
        }
        wrapper.orderByDesc(MaterialInboundReturnOrderDO::getId);

        IPage<MaterialInboundReturnOrderDO> pageResult = inboundReturnOrderMapper.selectPage(page, wrapper);
        List<MaterialInboundReturnOrderPageRespVO> list = BeanUtils.toBean(pageResult.getRecords(), MaterialInboundReturnOrderPageRespVO.class);

        // 联查原入库单号
        Set<Long> sourceOrderIds = list.stream()
                .map(MaterialInboundReturnOrderPageRespVO::getSourceInboundOrderId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (!sourceOrderIds.isEmpty()) {
            Map<Long, String> sourceOrderNoMap = inboundOrderMapper.selectList(
                    new LambdaQueryWrapperX<MaterialInboundOrderDO>().in(MaterialInboundOrderDO::getId, sourceOrderIds)).stream()
                    .collect(Collectors.toMap(MaterialInboundOrderDO::getId, MaterialInboundOrderDO::getOrderNo));
            list.forEach(vo -> {
                if (vo.getSourceInboundOrderId() != null) {
                    vo.setSourceInboundOrderNo(sourceOrderNoMap.get(vo.getSourceInboundOrderId()));
                }
            });
        }

        return new PageResult<>(list, pageResult.getTotal());
    }

    @Override
    public PageResult<MaterialInboundSummaryRespVO> pageInboundSummary(MaterialInboundSummaryPageReqVO reqVO) {
        // 说明：这里的分页维度是“供应商”，不是明细行。不能直接用明细分页的 total。
        // 处理方式：先算出满足条件的 supplierId 全量集合得到 total，再按 supplierId 做分页，最后仅聚合当前页供应商的数据。

        if (reqVO.getOrderBeginDate() == null) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "开始日期不能为空");
        }


        // 1) 汇总不按供应商关键字过滤（关键字仅用于明细查询）

        // 2) 找出满足条件的明细 materialId 集合（只查 materialId 字段）
        LambdaQueryWrapper<MaterialInboundOrderItemDO> baseItemWrapper = new LambdaQueryWrapperX<MaterialInboundOrderItemDO>()
                .inIfPresent(MaterialInboundOrderItemDO::getTenantId, (Object[]) reqVO.getTenantIds())
                .leIfPresent(MaterialInboundOrderItemDO::getCreateTime, reqVO.getOrderEndDate())
                .ge(MaterialInboundOrderItemDO::getCreateTime, reqVO.getOrderBeginDate())
                .select(MaterialInboundOrderItemDO::getMaterialId);

        List<MaterialInboundOrderItemDO> allMatchedItems = inboundOrderItemMapper.selectList(baseItemWrapper);
        if (allMatchedItems.isEmpty()) {
            return new PageResult<>(Collections.emptyList(), 0L);
        }

        Set<Long> allMaterialIds = allMatchedItems.stream()
                .map(MaterialInboundOrderItemDO::getMaterialId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (allMaterialIds.isEmpty()) {
            return new PageResult<>(Collections.emptyList(), 0L);
        }

        Map<Long, MaterialInfoDO> allMaterialMap = loadMaterialMap(allMaterialIds);

        // 3) 计算满足条件的 supplierId 全量集合（用于 total）
        Set<Long> allSupplierIds = allMaterialMap.values().stream()
                .map(MaterialInfoDO::getSupplierId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());


        long total = allSupplierIds.size();
        if (total == 0) {
            return new PageResult<>(Collections.emptyList(), 0L);
        }

        // 4) supplierId 分页
        List<Long> sortedSupplierIds = new ArrayList<>(allSupplierIds);
        sortedSupplierIds.sort(Comparator.reverseOrder());

        int pageNo = reqVO.getPageNo();
        int pageSize = reqVO.getPageSize();
        int fromIndex = Math.max((pageNo - 1) * pageSize, 0);
        if (fromIndex >= sortedSupplierIds.size()) {
            return new PageResult<>(Collections.emptyList(), total);
        }
        int toIndex = Math.min(fromIndex + pageSize, sortedSupplierIds.size());
        List<Long> pageSupplierIds = sortedSupplierIds.subList(fromIndex, toIndex);

        // 5) 取当前页供应商对应的物资ID
        Set<Long> pageMaterialIds = allMaterialMap.values().stream()
                .filter(m -> m.getSupplierId() != null && pageSupplierIds.contains(m.getSupplierId()))
                .map(MaterialInfoDO::getId)
                .collect(Collectors.toSet());

        if (pageMaterialIds.isEmpty()) {
            return new PageResult<>(Collections.emptyList(), total);
        }

        // 6) 查当前页供应商相关明细，聚合金额与单据数
        LambdaQueryWrapper<MaterialInboundOrderItemDO> pageItemWrapper = new LambdaQueryWrapperX<MaterialInboundOrderItemDO>()
                .inIfPresent(MaterialInboundOrderItemDO::getTenantId, (Object[]) reqVO.getTenantIds())
                .leIfPresent(MaterialInboundOrderItemDO::getCreateTime, reqVO.getOrderEndDate())
                .ge(MaterialInboundOrderItemDO::getCreateTime, reqVO.getOrderBeginDate())
                .in(MaterialInboundOrderItemDO::getMaterialId, pageMaterialIds)
                .select(MaterialInboundOrderItemDO::getInboundOrderId,
                        MaterialInboundOrderItemDO::getMaterialId,
                        MaterialInboundOrderItemDO::getInQty,
                        MaterialInboundOrderItemDO::getInUnitPrice);

        List<MaterialInboundOrderItemDO> pageItems = inboundOrderItemMapper.selectList(pageItemWrapper);
        if (pageItems.isEmpty()) {
            return new PageResult<>(Collections.emptyList(), total);
        }

        Map<Long, SupplierInfoDO> supplierMap = supplierInfoMapper.selectBatchIds(pageSupplierIds).stream()
                .collect(Collectors.toMap(SupplierInfoDO::getId, Function.identity()));

        Map<Long, MaterialInboundSummaryRespVO> summaryMap = new HashMap<>();
        Map<Long, Set<Long>> supplierOrderIdsMap = new HashMap<>();

        for (MaterialInboundOrderItemDO item : pageItems) {
            MaterialInfoDO material = allMaterialMap.get(item.getMaterialId());
            if (material == null || material.getSupplierId() == null) {
                continue;
            }
            Long supplierId = material.getSupplierId();
            if (!pageSupplierIds.contains(supplierId)) {
                continue;
            }

            SupplierInfoDO supplier = supplierMap.get(supplierId);
            if (supplier == null) {
                continue;
            }

            MaterialInboundSummaryRespVO vo = summaryMap.computeIfAbsent(supplierId, k -> {
                MaterialInboundSummaryRespVO r = new MaterialInboundSummaryRespVO();
                r.setSupplierId(supplierId);
                r.setSupplierCode(supplier.getSupplierNumber());
                r.setSupplierName(supplier.getSupplierName());
                r.setOrderCount(0);
                r.setTotalAmount(BigDecimal.ZERO);
                return r;
            });

            if (item.getInboundOrderId() != null) {
                supplierOrderIdsMap.computeIfAbsent(supplierId, k -> new HashSet<>()).add(item.getInboundOrderId());
            }

            if (item.getInQty() != null && item.getInUnitPrice() != null) {
                vo.setTotalAmount(vo.getTotalAmount().add(item.getInUnitPrice().multiply(item.getInQty())));
            }
        }

        for (Map.Entry<Long, MaterialInboundSummaryRespVO> entry : summaryMap.entrySet()) {
            entry.getValue().setOrderCount(supplierOrderIdsMap.getOrDefault(entry.getKey(), Collections.emptySet()).size());
        }

        List<MaterialInboundSummaryRespVO> result = pageSupplierIds.stream()
                .map(summaryMap::get)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        return new PageResult<>(result, total);
    }

    @Override
    public PageResult<MaterialInboundDetailRespVO> pageInboundDetail(MaterialInboundDetailPageReqVO reqVO) {
        // 1. 构建查询条件
        LambdaQueryWrapper<MaterialInboundOrderItemDO> queryWrapper = new LambdaQueryWrapperX<MaterialInboundOrderItemDO>()
                .inIfPresent(MaterialInboundOrderItemDO::getTenantId, (Object[]) reqVO.getTenantIds())
                .ge(MaterialInboundOrderItemDO::getCreateTime, reqVO.getOrderBeginDate() != null ? reqVO.getOrderBeginDate() : null)
                .le(reqVO.getOrderEndDate() != null,
                        MaterialInboundOrderItemDO::getCreateTime,
                        reqVO.getOrderEndDate());

        // 2. 如果有供应商关键字，先查询符合条件的供应商ID
        if (StringUtils.isNotBlank(reqVO.getKeyword())) {
            List<Long> supplierIds = supplierInfoMapper.selectList(new LambdaQueryWrapper<SupplierInfoDO>()
                            .like(SupplierInfoDO::getSupplierNumber, reqVO.getKeyword())
                            .or()
                            .like(SupplierInfoDO::getSupplierName, reqVO.getKeyword()))
                    .stream()
                    .map(SupplierInfoDO::getId)
                    .collect(Collectors.toList());

            if (supplierIds.isEmpty()) {
                return new PageResult<>(Collections.emptyList(), 0L);
            }

            // 查询这些供应商的物资ID
            List<Long> materialIds = materialInfoMapper.selectList(
                            new LambdaQueryWrapper<MaterialInfoDO>()
                                    .in(MaterialInfoDO::getSupplierId, supplierIds))
                    .stream()
                    .map(MaterialInfoDO::getId)
                    .collect(Collectors.toList());

            if (materialIds.isEmpty()) {
                return new PageResult<>(Collections.emptyList(), 0L);
            }

            queryWrapper.in(MaterialInboundOrderItemDO::getMaterialId, materialIds);
        }

        // 3. 分页查询
        Page<MaterialInboundOrderItemDO> page = new Page<>(reqVO.getPageNo(), reqVO.getPageSize());
        IPage<MaterialInboundOrderItemDO> pageResult = inboundOrderItemMapper.selectPage(page, queryWrapper);

        if (pageResult.getRecords().isEmpty()) {
            return new PageResult<>(Collections.emptyList(), pageResult.getTotal());
        }

        // 4. 获取物资ID和入库单ID
        List<Long> materialIds = pageResult.getRecords().stream()
                .map(MaterialInboundOrderItemDO::getMaterialId)
                .distinct()
                .collect(Collectors.toList());

        List<Long> orderIds = pageResult.getRecords().stream()
                .map(MaterialInboundOrderItemDO::getInboundOrderId)
                .distinct()
                .collect(Collectors.toList());

        // 5. 批量查询物资信息
        Map<Long, MaterialInfoDO> materialMap = materialInfoMapper.selectBatchIds(materialIds).stream()
                .collect(Collectors.toMap(MaterialInfoDO::getId, Function.identity()));

        // 6. 批量查询入库单信息
        Map<Long, MaterialInboundOrderDO> orderMap = inboundOrderMapper.selectBatchIds(orderIds).stream()
                .collect(Collectors.toMap(MaterialInboundOrderDO::getId, Function.identity()));

        // 7. 获取供应商ID
        List<Long> supplierIds = materialMap.values().stream()
                .map(MaterialInfoDO::getSupplierId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());

        // 8. 批量查询供应商信息
        Map<Long, SupplierInfoDO> supplierMap = supplierIds.isEmpty() ? Collections.emptyMap() :
                supplierInfoMapper.selectBatchIds(supplierIds).stream()
                        .collect(Collectors.toMap(SupplierInfoDO::getId, Function.identity()));

        // 9. 转换为VO
        List<MaterialInboundDetailRespVO> result = pageResult.getRecords().stream()
                .map(item -> {
                    MaterialInboundDetailRespVO vo = new MaterialInboundDetailRespVO();

                    // 设置物资信息
                    MaterialInfoDO material = materialMap.get(item.getMaterialId());
                    if (material != null) {
                        vo.setMaterialNumber(material.getMaterialNumber());
                        vo.setMaterialName(material.getMaterialName());
                        vo.setSpecification(material.getSpecification());
                        vo.setMaterialUnit(material.getMaterialUnit());
                    }
                    // 设置数量和金额
                    vo.setQuantity(item.getInQty());
                    vo.setInUnitPrice(item.getInUnitPrice());
                    if (item.getInQty() != null && item.getInUnitPrice() != null) {
                        vo.setAmount(item.getInUnitPrice().multiply(item.getInQty()));
                    }
                    return vo;
                })
                .collect(Collectors.toList());
        return new PageResult<>(result, pageResult.getTotal());
    }

    @Override
    public PageResult<MaterialOutboundSummaryRespVO> pageOutboundSummary(MaterialOutboundSummaryPageReqVO reqVO) {
        if (reqVO.getOrderBeginDate() == null) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "开始日期不能为空");
        }

        // 1) 找出满足日期/租户条件的出库单ID（仅取 id/outDeptId）
        LambdaQueryWrapper<MaterialOutboundOrderDO> baseOrderWrapper = new LambdaQueryWrapperX<MaterialOutboundOrderDO>()
                .inIfPresent(MaterialOutboundOrderDO::getTenantId, (Object[]) reqVO.getTenantIds())
                .leIfPresent(MaterialOutboundOrderDO::getOrderDate, reqVO.getOrderEndDate())
                .ge(MaterialOutboundOrderDO::getOrderDate, reqVO.getOrderBeginDate())
                .select(MaterialOutboundOrderDO::getId, MaterialOutboundOrderDO::getOutDeptId);

        List<MaterialOutboundOrderDO> allOrders = outboundOrderMapper.selectList(baseOrderWrapper);
        if (allOrders.isEmpty()) {
            return new PageResult<>(Collections.emptyList(), 0L);
        }

        // 2) 统计满足条件的部门集合（total）
        Set<Long> allDeptIds = allOrders.stream()
                .map(MaterialOutboundOrderDO::getOutDeptId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        long total = allDeptIds.size();
        if (total == 0) {
            return new PageResult<>(Collections.emptyList(), 0L);
        }

        // 3) deptId 分页（稳定排序：倒序）
        List<Long> sortedDeptIds = new ArrayList<>(allDeptIds);
        sortedDeptIds.sort(Comparator.reverseOrder());

        int pageNo = reqVO.getPageNo();
        int pageSize = reqVO.getPageSize();
        int fromIndex = Math.max((pageNo - 1) * pageSize, 0);
        if (fromIndex >= sortedDeptIds.size()) {
            return new PageResult<>(Collections.emptyList(), total);
        }
        int toIndex = Math.min(fromIndex + pageSize, sortedDeptIds.size());
        List<Long> pageDeptIds = sortedDeptIds.subList(fromIndex, toIndex);

        // 4) 当前页部门对应的出库单ID
        Set<Long> pageOrderIds = allOrders.stream()
                .filter(o -> o.getOutDeptId() != null && pageDeptIds.contains(o.getOutDeptId()))
                .map(MaterialOutboundOrderDO::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        if (pageOrderIds.isEmpty()) {
            return new PageResult<>(Collections.emptyList(), total);
        }

        // 5) 取出库明细 + 入库批次单价，聚合金额；单据数按出库单去重计数
        List<MaterialOutboundOrderItemDO> items = outboundOrderItemMapper.selectList(
                new LambdaQueryWrapperX<MaterialOutboundOrderItemDO>()
                        .in(MaterialOutboundOrderItemDO::getOutboundOrderId, pageOrderIds)
                        .select(MaterialOutboundOrderItemDO::getOutboundOrderId,
                                MaterialOutboundOrderItemDO::getRefInboundItemId,
                                MaterialOutboundOrderItemDO::getQuantity));

        Map<Long, MaterialOutboundOrderDO> pageOrderMap = allOrders.stream()
                .filter(o -> pageOrderIds.contains(o.getId()))
                .collect(Collectors.toMap(MaterialOutboundOrderDO::getId, Function.identity(), (a, b) -> a));

        Set<Long> inboundItemIds = items.stream()
                .map(MaterialOutboundOrderItemDO::getRefInboundItemId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, MaterialInboundOrderItemDO> inboundItemMap = inboundItemIds.isEmpty() ? Collections.emptyMap() :
                inboundOrderItemMapper.selectBatchIds(inboundItemIds).stream()
                        .collect(Collectors.toMap(MaterialInboundOrderItemDO::getId, Function.identity()));

        Map<Long, DeptDO> deptMap = deptMapper.selectBatchIds(pageDeptIds).stream()
                .collect(Collectors.toMap(DeptDO::getId, Function.identity()));

        Map<Long, MaterialOutboundSummaryRespVO> summaryMap = new HashMap<>();
        Map<Long, Set<Long>> deptOrderIdsMap = new HashMap<>();

        for (MaterialOutboundOrderItemDO item : items) {
            MaterialOutboundOrderDO order = pageOrderMap.get(item.getOutboundOrderId());
            if (order == null || order.getOutDeptId() == null) {
                continue;
            }
            Long deptId = order.getOutDeptId();
            if (!pageDeptIds.contains(deptId)) {
                continue;
            }

            DeptDO dept = deptMap.get(deptId);
            MaterialOutboundSummaryRespVO vo = summaryMap.computeIfAbsent(deptId, k -> {
                MaterialOutboundSummaryRespVO r = new MaterialOutboundSummaryRespVO();
                r.setDeptId(deptId);
                r.setDeptName(dept != null ? dept.getName() : null);
                r.setOrderCount(0);
                r.setTotalAmount(BigDecimal.ZERO);
                return r;
            });

            if (order.getId() != null) {
                deptOrderIdsMap.computeIfAbsent(deptId, k -> new HashSet<>()).add(order.getId());
            }

            MaterialInboundOrderItemDO inboundItem = inboundItemMap.get(item.getRefInboundItemId());
            if (inboundItem != null && inboundItem.getInUnitPrice() != null && item.getQuantity() != null) {
                vo.setTotalAmount(vo.getTotalAmount().add(inboundItem.getInUnitPrice().multiply(item.getQuantity())));
            }
        }

        for (Map.Entry<Long, MaterialOutboundSummaryRespVO> entry : summaryMap.entrySet()) {
            entry.getValue().setOrderCount(deptOrderIdsMap.getOrDefault(entry.getKey(), Collections.emptySet()).size());
        }

        List<MaterialOutboundSummaryRespVO> result = pageDeptIds.stream()
                .map(summaryMap::get)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        return new PageResult<>(result, total);
    }

    @Override
    public PageResult<MaterialOutboundDetailRespVO> pageOutboundDetail(MaterialOutboundDetailPageReqVO reqVO) {
        if (reqVO.getOrderBeginDate() == null) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "开始日期不能为空");
        }

        // 1) 关键字（部门ID/部门名称） -> deptIds 过滤
        Set<Long> keywordDeptIds = null;
        if (StringUtils.isNotBlank(reqVO.getKeyword())) {
            LambdaQueryWrapper<DeptDO> deptWrapper = new LambdaQueryWrapperX<DeptDO>()
                    .like(DeptDO::getName, reqVO.getKeyword());
            // 若关键字是纯数字，则支持按部门ID精确匹配
            if (reqVO.getKeyword().matches("\\d+")) {
                deptWrapper.or().eq(DeptDO::getId, Long.valueOf(reqVO.getKeyword()));
            }
            List<Long> ids = deptMapper.selectList(deptWrapper).stream()
                    .map(DeptDO::getId)
                    .collect(Collectors.toList());
            if (ids.isEmpty()) {
                return new PageResult<>(Collections.emptyList(), 0L);
            }
            keywordDeptIds = new HashSet<>(ids);
        }

        // 2) 日期/租户过滤出库单，若有关键字再按 outDeptId 过滤
        LambdaQueryWrapper<MaterialOutboundOrderDO> orderWrapper = new LambdaQueryWrapperX<MaterialOutboundOrderDO>()
                .inIfPresent(MaterialOutboundOrderDO::getTenantId, (Object[]) reqVO.getTenantIds())
                .leIfPresent(MaterialOutboundOrderDO::getOrderDate, reqVO.getOrderEndDate())
                .inIfPresent(MaterialOutboundOrderDO::getOutDeptId, keywordDeptIds == null ? null : keywordDeptIds.toArray(new Long[0]))
                .ge(MaterialOutboundOrderDO::getOrderDate, reqVO.getOrderBeginDate())
                .select(MaterialOutboundOrderDO::getId);

        List<MaterialOutboundOrderDO> matchedOrders = outboundOrderMapper.selectList(orderWrapper);
        if (matchedOrders.isEmpty()) {
            return new PageResult<>(Collections.emptyList(), 0L);
        }

        Set<Long> orderIds = matchedOrders.stream().map(MaterialOutboundOrderDO::getId).collect(Collectors.toSet());

        // 3) 明细分页（维度：出库明细）
        LambdaQueryWrapperX<MaterialOutboundOrderItemDO> itemWrapper = new LambdaQueryWrapperX<MaterialOutboundOrderItemDO>()
                .in(MaterialOutboundOrderItemDO::getOutboundOrderId, orderIds)
                .orderByDesc(MaterialOutboundOrderItemDO::getId);

        Page<MaterialOutboundOrderItemDO> page = new Page<>(reqVO.getPageNo(), reqVO.getPageSize());
        IPage<MaterialOutboundOrderItemDO> pageResult = outboundOrderItemMapper.selectPage(page, itemWrapper);

        if (pageResult.getRecords().isEmpty()) {
            return new PageResult<>(Collections.emptyList(), pageResult.getTotal());
        }

        // 4) 批量物资、批次，组装返回
        Set<Long> materialIds = pageResult.getRecords().stream()
                .map(MaterialOutboundOrderItemDO::getMaterialId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, MaterialInfoDO> materialMap = loadMaterialMap(materialIds);

        Set<Long> inboundItemIds = pageResult.getRecords().stream()
                .map(MaterialOutboundOrderItemDO::getRefInboundItemId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, MaterialInboundOrderItemDO> inboundItemMap = inboundItemIds.isEmpty() ? Collections.emptyMap() :
                inboundOrderItemMapper.selectBatchIds(inboundItemIds).stream()
                        .collect(Collectors.toMap(MaterialInboundOrderItemDO::getId, Function.identity()));

        List<MaterialOutboundDetailRespVO> list = pageResult.getRecords().stream().map(item -> {
            MaterialOutboundDetailRespVO vo = new MaterialOutboundDetailRespVO();
            MaterialInfoDO material = materialMap.get(item.getMaterialId());
            if (material != null) {
                vo.setMaterialNumber(material.getMaterialNumber());
                vo.setMaterialName(material.getMaterialName());
                vo.setSpecification(material.getSpecification());
                vo.setMaterialUnit(material.getMaterialUnit());
            }
            vo.setQuantity(item.getQuantity());

            MaterialInboundOrderItemDO inboundItem = inboundItemMap.get(item.getRefInboundItemId());
            if (inboundItem != null) {
                vo.setInUnitPrice(inboundItem.getInUnitPrice());
                if (inboundItem.getInUnitPrice() != null && item.getQuantity() != null) {
                    vo.setAmount(inboundItem.getInUnitPrice().multiply(item.getQuantity()));
                }
            }
            return vo;
        }).collect(Collectors.toList());

        return new PageResult<>(list, pageResult.getTotal());
    }

    @Override
    public PageResult<MaterialInventoryReportRespVO> pageInventoryReport(MaterialInventoryReportPageReqVO reqVO) {
        // 1. 校验并解析月份范围
        MonthRange monthRange = parseMonthRange(reqVO.getMonths());
        LocalDate monthStart = monthRange.getStart();
        LocalDate monthEnd = monthRange.getEnd();

        // 2. 当月入库批次分页（以入库明细批次 material_inbound_order_item 为维度）
        LambdaQueryWrapper<MaterialInboundOrderItemDO> wrapper = new LambdaQueryWrapperX<MaterialInboundOrderItemDO>()
                .inIfPresent(MaterialInboundOrderItemDO::getTenantId, (Object[]) reqVO.getTenantIds())
                .eqIfPresent(MaterialInboundOrderItemDO::getStoreId, reqVO.getStoreId())
                // 口径：
                // 1) 入库单为当月；
                // 2) 或者该入库批次在当月发生过出库（即使入库单不是当月）
                .and(w -> w.inSql(MaterialInboundOrderItemDO::getInboundOrderId,
                                "SELECT id FROM material_inbound_order WHERE order_date >= '" + monthStart + "'" + buildEndDateCondition("order_date", monthEnd))
                        .or()
                        .inSql(MaterialInboundOrderItemDO::getId,
                                "SELECT oi.ref_inbound_item_id FROM material_outbound_order_item oi " +
                                        "INNER JOIN material_outbound_order o ON oi.outbound_order_id = o.id " +
                                        "WHERE o.order_date >= '" + monthStart + "'" + buildEndDateCondition("o.order_date", monthEnd)));

        // 关键字匹配物资编码/名称
        if (StringUtils.isNotBlank(reqVO.getKeyword())) {
            List<Long> materialIds = materialInfoMapper.selectList(
                            new LambdaQueryWrapperX<MaterialInfoDO>()
                                    .like(MaterialInfoDO::getMaterialNumber, reqVO.getKeyword())
                                    .or()
                                    .like(MaterialInfoDO::getMaterialName, reqVO.getKeyword()))
                    .stream()
                    .map(MaterialInfoDO::getId)
                    .collect(Collectors.toList());
            if (materialIds.isEmpty()) {
                return PageResult.empty();
            }
            wrapper.in(MaterialInboundOrderItemDO::getMaterialId, materialIds);
        }
        wrapper.orderByDesc(MaterialInboundOrderItemDO::getId);

        Page<MaterialInboundOrderItemDO> page = new Page<>(reqVO.getPageNo(), reqVO.getPageSize());
        IPage<MaterialInboundOrderItemDO> pageResult = inboundOrderItemMapper.selectPage(page, wrapper);
        if (pageResult.getRecords().isEmpty()) {
            return PageResult.empty(pageResult.getTotal());
        }

        List<MaterialInventoryReportRespVO> list = buildInventoryReportResp(monthStart, monthEnd, pageResult.getRecords());
        return new PageResult<>(list, pageResult.getTotal());
    }

    @Override
    public List<MaterialInventoryReportRespVO> listInventoryReport(MaterialInventoryReportPageReqVO reqVO) {
        // 导出/列表不走分页时，这里用同一套条件构造 wrapper，但查询用 selectList
        MonthRange monthRange = parseMonthRange(reqVO.getMonths());
        LocalDate monthStart = monthRange.getStart();
        LocalDate nextMonthStart = monthRange.getEnd();

        LambdaQueryWrapper<MaterialInboundOrderItemDO> wrapper = new LambdaQueryWrapperX<MaterialInboundOrderItemDO>()
                .inIfPresent(MaterialInboundOrderItemDO::getTenantId, (Object[]) reqVO.getTenantIds())
                .eqIfPresent(MaterialInboundOrderItemDO::getStoreId, reqVO.getStoreId())
                // 口径：
                // 1) 入库单为当月；
                // 2) 或者该入库批次在当月发生过出库（即使入库单不是当月）
                .and(w -> w.inSql(MaterialInboundOrderItemDO::getInboundOrderId,
                                "SELECT id FROM material_inbound_order WHERE order_date >= '" + monthStart + "'" + buildEndDateCondition("order_date", nextMonthStart))
                        .or()
                        .inSql(MaterialInboundOrderItemDO::getId,
                                "SELECT oi.ref_inbound_item_id FROM material_outbound_order_item oi " +
                                        "INNER JOIN material_outbound_order o ON oi.outbound_order_id = o.id " +
                                        "WHERE o.order_date >= '" + monthStart + "'" + buildEndDateCondition("o.order_date", nextMonthStart)));

        if (StringUtils.isNotBlank(reqVO.getKeyword())) {
            List<Long> materialIds = materialInfoMapper.selectList(
                            new LambdaQueryWrapperX<MaterialInfoDO>()
                                    .like(MaterialInfoDO::getMaterialNumber, reqVO.getKeyword())
                                    .or()
                                    .like(MaterialInfoDO::getMaterialName, reqVO.getKeyword()))
                    .stream()
                    .map(MaterialInfoDO::getId)
                    .collect(Collectors.toList());
            if (materialIds.isEmpty()) {
                return Collections.emptyList();
            }
            wrapper.in(MaterialInboundOrderItemDO::getMaterialId, materialIds);
        }
        wrapper.orderByDesc(MaterialInboundOrderItemDO::getId);

        List<MaterialInboundOrderItemDO> records = inboundOrderItemMapper.selectList(wrapper);
        if (records.isEmpty()) {
            return Collections.emptyList();
        }
        return buildInventoryReportResp(monthStart, nextMonthStart, records);
    }

    private List<MaterialInventoryReportRespVO> buildInventoryReportResp(LocalDate monthStart, LocalDate monthEnd,
                                                                        List<MaterialInboundOrderItemDO> records) {
        Set<Long> inboundItemIds = records.stream().map(MaterialInboundOrderItemDO::getId).collect(Collectors.toSet());
        Map<Long, BigDecimal> outQtyMap = getOutboundQtySumByInboundItemIds(monthStart, monthEnd, inboundItemIds);

        Set<Long> materialIds = records.stream().map(MaterialInboundOrderItemDO::getMaterialId).collect(Collectors.toSet());
        Map<Long, MaterialInfoDO> materialMap = materialIds.isEmpty() ? Collections.emptyMap()
                : materialInfoMapper.selectBatchIds(materialIds).stream()
                .collect(Collectors.toMap(MaterialInfoDO::getId, Function.identity()));

        Set<Long> storeIds = records.stream().map(MaterialInboundOrderItemDO::getStoreId).filter(Objects::nonNull).collect(Collectors.toSet());
        Map<Long, StoreManageDO> storeMap = storeIds.isEmpty() ? Collections.emptyMap()
                : storeManageMapper.selectBatchIds(storeIds).stream()
                .collect(Collectors.toMap(StoreManageDO::getId, Function.identity()));

        Set<Long> categoryIds = materialMap.values().stream().map(MaterialInfoDO::getCategoryId).filter(Objects::nonNull).collect(Collectors.toSet());
        Map<Long, MaterialCategoryDO> categoryMap = categoryIds.isEmpty() ? Collections.emptyMap()
                : materialCategoryMapper.selectBatchIds(categoryIds).stream()
                .collect(Collectors.toMap(MaterialCategoryDO::getId, Function.identity()));

        List<MaterialInventoryReportRespVO> collect = records.stream().map(item -> {
            MaterialInventoryReportRespVO vo = new MaterialInventoryReportRespVO();
            vo.setInboundItemId(item.getId());
            vo.setStoreId(item.getStoreId());
            vo.setMaterialId(item.getMaterialId());

            MaterialInfoDO material = materialMap.get(item.getMaterialId());
            StoreManageDO store = storeMap.get(item.getStoreId());
            MaterialCategoryDO category = material != null ? categoryMap.get(material.getCategoryId()) : null;

            if (store != null) {
                vo.setStoreName(store.getStoreName());
            }
            if (material != null) {
                vo.setMaterialName(material.getMaterialName());
                vo.setMaterialUnit(material.getMaterialUnit());
            }
            if (category != null) {
                vo.setCategoryName(category.getCategoryName());
            }

            BigDecimal price = item.getInUnitPrice() != null ? item.getInUnitPrice() : BigDecimal.ZERO;
            BigDecimal inQty = item.getInQty() != null ? item.getInQty() : BigDecimal.ZERO;
            BigDecimal outQty = outQtyMap.getOrDefault(item.getId(), BigDecimal.ZERO);
            BigDecimal endQty = inQty.subtract(outQty);

            vo.setBeginQty(BigDecimal.ZERO);
            vo.setBeginPrice(price);
            vo.setBeginAmount(BigDecimal.ZERO);

            vo.setInQty(inQty);
            vo.setInPrice(price);
            vo.setInAmount(price.multiply(inQty));

            vo.setOutQty(outQty);
            vo.setOutPrice(price);
            vo.setOutAmount(price.multiply(outQty));

            vo.setEndQty(endQty);
            vo.setEndPrice(price);
            vo.setEndAmount(price.multiply(endQty));

            return vo;
        }).collect(Collectors.toList());
        // 合计
        BigDecimal totalBegin = BigDecimal.ZERO;
        BigDecimal totalIn = BigDecimal.ZERO;
        BigDecimal totalOut = BigDecimal.ZERO;
        BigDecimal totalEnd = BigDecimal.ZERO;

        for (MaterialInventoryReportRespVO vo : collect) {
            if (vo.getBeginAmount() != null) {
                totalBegin = totalBegin.add(vo.getBeginAmount());
            }
            if (vo.getInAmount() != null) {
                totalIn = totalIn.add(vo.getInAmount());
            }
            if (vo.getOutAmount() != null) {
                totalOut = totalOut.add(vo.getOutAmount());
            }
            if (vo.getEndAmount() != null) {
                totalEnd = totalEnd.add(vo.getEndAmount());
            }
        }
        MaterialInventoryReportRespVO vo = new MaterialInventoryReportRespVO();
        vo.setBeginAmount(totalBegin);
        vo.setInAmount(totalIn);
        vo.setOutAmount(totalOut);
        vo.setEndAmount(totalEnd);
        collect.add(vo);
        return collect;
    }

    @Override
    public MaterialInventoryFlowRespVO getInventoryFlow(MaterialInventoryFlowReqVO reqVO) {
        // 1. 解析月份范围
        MonthRange monthRange = parseMonthRange(reqVO.getMonths());
        LocalDate monthStart = monthRange.getStart();
        LocalDate nextMonthStart = monthRange.getEnd();

        // 2. 查询入库明细批次
        MaterialInboundOrderItemDO inboundItem = inboundOrderItemMapper.selectById(reqVO.getInboundItemId());
        if (inboundItem == null) {
            throw exception(COMMON_NOT_FOUND);
        }

        // 3. 查询物资信息
        MaterialInfoDO material = materialInfoMapper.selectById(inboundItem.getMaterialId());
        if (material == null) {
            throw exception(COMMON_NOT_FOUND);
        }

        // 4. 查询仓库信息
        StoreManageDO store = inboundItem.getStoreId() != null ?
                storeManageMapper.selectById(inboundItem.getStoreId()) : null;

        // 5. 查询类别信息
        MaterialCategoryDO category = material.getCategoryId() != null ?
                materialCategoryMapper.selectById(material.getCategoryId()) : null;

        // 5.1 查询供应商信息（用于入库流水显示）
        SupplierInfoDO supplier = material.getSupplierId() != null ?
                supplierInfoMapper.selectById(material.getSupplierId()) : null;

        // 6. 组装库存信息
        MaterialInventoryFlowRespVO respVO = new MaterialInventoryFlowRespVO();
        MaterialInventoryFlowRespVO.InventoryInfo inventoryInfo = new MaterialInventoryFlowRespVO.InventoryInfo();
        inventoryInfo.setStoreName(store != null ? store.getStoreName() : null);
        inventoryInfo.setMaterialName(material.getMaterialName());
        inventoryInfo.setInventoryQty(inboundItem.getAvailableQty() != null ? inboundItem.getAvailableQty() : BigDecimal.ZERO);
        inventoryInfo.setCategoryName(category != null ? category.getCategoryName() : null);
        inventoryInfo.setSpecification(material.getSpecification());
        inventoryInfo.setMaterialUnit(material.getMaterialUnit());
        respVO.setInventoryInfo(inventoryInfo);

        // 7. 查询入库流水（该批次所属的入库单，如果订单日期在月份范围内）
        List<MaterialInventoryFlowRespVO.FlowItem> flowList = new ArrayList<>();
        MaterialInboundOrderDO inboundOrder = inboundOrderMapper.selectById(inboundItem.getInboundOrderId());
        if (inboundOrder != null && !inboundOrder.getOrderDate().isBefore(monthStart)
                && (nextMonthStart == null || inboundOrder.getOrderDate().isBefore(nextMonthStart))) {
            MaterialInventoryFlowRespVO.FlowItem inFlow = new MaterialInventoryFlowRespVO.FlowItem();
            inFlow.setTime(inboundOrder.getOrderDate().toString());
            inFlow.setOrderId(inboundOrder.getId());
            inFlow.setOrderNo(inboundOrder.getOrderNo());
            inFlow.setOperationType("入库");
            inFlow.setQuantity(inboundItem.getInQty());
            inFlow.setDeptName(supplier != null ? supplier.getSupplierName() : null); // 使用供应商名称作为部门显示
            flowList.add(inFlow);
        }

        // 8. 查询出库流水（通过ref_inbound_item_id关联该批次的所有出库记录）
        List<MaterialOutboundOrderItemDO> outboundItems = outboundOrderItemMapper.selectList(
                new LambdaQueryWrapper<MaterialOutboundOrderItemDO>()
                        .eq(MaterialOutboundOrderItemDO::getRefInboundItemId, reqVO.getInboundItemId()));
        if (!outboundItems.isEmpty()) {
            Set<Long> outboundOrderIds = outboundItems.stream()
                    .map(MaterialOutboundOrderItemDO::getOutboundOrderId)
                    .collect(Collectors.toSet());
            Map<Long, MaterialOutboundOrderDO> outboundOrderMap = outboundOrderMapper.selectBatchIds(outboundOrderIds).stream()
                    .filter(order -> !order.getOrderDate().isBefore(monthStart)
                            && (nextMonthStart == null || order.getOrderDate().isBefore(nextMonthStart)))
                    .collect(Collectors.toMap(MaterialOutboundOrderDO::getId, Function.identity()));

            // 查询部门信息
            Set<Long> deptIds = outboundOrderMap.values().stream()
                    .map(MaterialOutboundOrderDO::getOutDeptId)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet());
            Map<Long, DeptDO> deptMap = deptIds.isEmpty() ? Collections.emptyMap() :
                    deptMapper.selectBatchIds(deptIds).stream()
                            .collect(Collectors.toMap(DeptDO::getId, Function.identity()));

            for (MaterialOutboundOrderItemDO outItem : outboundItems) {
                MaterialOutboundOrderDO outOrder = outboundOrderMap.get(outItem.getOutboundOrderId());
                if (outOrder != null) {
                    MaterialInventoryFlowRespVO.FlowItem outFlow = new MaterialInventoryFlowRespVO.FlowItem();
                    outFlow.setTime(outOrder.getOrderDate().toString());
                    outFlow.setOrderId(outOrder.getId());
                    outFlow.setOrderNo(outOrder.getOrderNo());
                    outFlow.setOperationType("出库");
                    outFlow.setQuantity(outItem.getQuantity());
                    DeptDO dept = deptMap.get(outOrder.getOutDeptId());
                    outFlow.setDeptName(dept != null ? dept.getName() : null);
                    flowList.add(outFlow);
                }
            }
        }

        // 9. 按时间倒序排序（最新的在前）
        flowList.sort((a, b) -> b.getTime().compareTo(a.getTime()));

        respVO.setFlowList(flowList);
        return respVO;
    }

    /**
     * 统计一批入库明细（批次）在指定时间范围内的出库总数量
     */
    private Map<Long, BigDecimal> getOutboundQtySumByInboundItemIds(LocalDate start, LocalDate end, Set<Long> inboundItemIds) {
        if (inboundItemIds == null || inboundItemIds.isEmpty()) {
            return Collections.emptyMap();
        }
        List<Map<String, Object>> result = outboundOrderItemMapper.selectSumOutQtyByInboundItemIdsAndDate(start, end, inboundItemIds);
        if (result == null || result.isEmpty()) {
            return Collections.emptyMap();
        }
        return result.stream().collect(Collectors.toMap(
                m -> ((Number) m.get("refInboundItemId")).longValue(),
                m -> ((BigDecimal) m.get("totalQty"))
        ));
    }

    private MonthRange parseMonthRange(String[] months) {
        if (months == null || months.length == 0 || StringUtils.isBlank(months[0])) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "月份开始不能为空");
        }
        YearMonth startMonth;
        try {
            startMonth = YearMonth.parse(months[0]);
        } catch (Exception e) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "月份开始格式不正确，应为 YYYY-MM");
        }
        LocalDate start = startMonth.atDay(1);
        if (months.length < 2 || StringUtils.isBlank(months[1])) {
            return new MonthRange(start, null);
        }
        YearMonth endMonth;
        try {
            endMonth = YearMonth.parse(months[1]);
        } catch (Exception e) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "月份结束格式不正确，应为 YYYY-MM");
        }
        LocalDate end = endMonth.plusMonths(1).atDay(1);
        if (!end.isAfter(start)) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "月份结束必须大于开始月份");
        }
        return new MonthRange(start, end);
    }

    private String buildEndDateCondition(String column, LocalDate end) {
        if (end == null) {
            return "";
        }
        return " AND " + column + " < '" + end + "'";
    }

    private static class MonthRange {
        private final LocalDate start;
        private final LocalDate end;

        private MonthRange(LocalDate start, LocalDate end) {
            this.start = start;
            this.end = end;
        }

        private LocalDate getStart() {
            return start;
        }

        private LocalDate getEnd() {
            return end;
        }
    }

    /**
     * 更新物资总库存（入库）
     */
    private void updateMaterialStockForInbound(List<MaterialIoOrderItemSaveReqVO> items) {
        Map<Long, BigDecimal> materialQtyMap = items.stream()
                .collect(Collectors.toMap(MaterialIoOrderItemSaveReqVO::getMaterialId,
                        MaterialIoOrderItemSaveReqVO::getQuantity,
                        BigDecimal::add));
        for (Map.Entry<Long, BigDecimal> entry : materialQtyMap.entrySet()) {
            MaterialInfoDO material = materialInfoMapper.selectById(entry.getKey());
            if (material != null) {
                material.setMaterialCount(material.getMaterialCount().add(entry.getValue()));
                materialInfoMapper.updateById(material);
            }
        }
    }

    /**
     * 更新物资总库存（出库）
     */
    private void updateMaterialStockForOutbound(List<MaterialIoOrderItemSaveReqVO> items, Long tenantId) {
        Map<Long, BigDecimal> materialQtyMap = items.stream()
                .collect(Collectors.toMap(
                        MaterialIoOrderItemSaveReqVO::getMaterialId,
                        MaterialIoOrderItemSaveReqVO::getQuantity,
                        BigDecimal::add));

        for (Map.Entry<Long, BigDecimal> entry : materialQtyMap.entrySet()) {
            MaterialInfoDO material = materialInfoMapper.selectById(entry.getKey());
            if (material != null) {
                if (material.getMaterialCount().compareTo( entry.getValue()) < 0) {
                    throw exceptionCustomMsg(COMMON_NOT_FOUND, "物资库存不足: " + material.getMaterialName());
                }
                material.setMaterialCount(material.getMaterialCount().subtract(entry.getValue()));
                materialInfoMapper.updateById(material);
            }
        }
    }

    // =================== 抽取的通用私有方法 ===================

    /**
     * 获取入库批次，不存在则抛出异常
     */
    private MaterialInboundOrderItemDO getInboundItemOrThrow(Long id) {
        MaterialInboundOrderItemDO inboundItem = inboundOrderItemMapper.selectById(id);
        if (inboundItem == null) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "入库批次不存在");
        }
        return inboundItem;
    }

    /**
     * 更新物资总库存（增量更新）
     * @param materialId 物资ID
     * @param quantityDelta 数量变化量（正数为增加，负数为减少）
     */
    private void updateMaterialStock(Long materialId, BigDecimal quantityDelta) {
        if (quantityDelta.compareTo(BigDecimal.ZERO) == 0) {
            return;
        }
        MaterialInfoDO material = materialInfoMapper.selectById(materialId);
        if (material == null) {
            return;
        }
        BigDecimal current = material.getMaterialCount() == null ? BigDecimal.ZERO : material.getMaterialCount();
        BigDecimal newStock = current.add(quantityDelta);
        if (newStock.compareTo(BigDecimal.ZERO) < 0) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "物资库存不足: " + material.getMaterialName());
        }
        material.setMaterialCount(newStock);
        materialInfoMapper.updateById(material);
    }

    /**
     * 应用入库退回对批次的影响（减少 inQty）
     */
    private void applyInboundReturnToBatch(MaterialInboundOrderItemDO inboundItem, BigDecimal returnQty) {
        BigDecimal oldInQty = inboundItem.getInQty() == null ? BigDecimal.ZERO : inboundItem.getInQty();
        BigDecimal outQty = inboundItem.getOutQty() == null ? BigDecimal.ZERO : inboundItem.getOutQty();
        BigDecimal newInQty = oldInQty.subtract(returnQty);
        if (newInQty.compareTo(outQty) < 0) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "退回后入库数量不能小于已出库数量");
        }
        inboundItem.setInQty(newInQty);
        inboundItem.setAvailableQty(newInQty.subtract(outQty));
        inboundOrderItemMapper.updateById(inboundItem);
    }

    /**
     * 回滚入库退回对批次的影响（恢复 inQty）
     */
    private void rollbackInboundReturnOnBatch(MaterialInboundOrderItemDO inboundItem, BigDecimal returnQty) {
        BigDecimal oldInQty = inboundItem.getInQty() == null ? BigDecimal.ZERO : inboundItem.getInQty();
        BigDecimal outQty = inboundItem.getOutQty() == null ? BigDecimal.ZERO : inboundItem.getOutQty();
        BigDecimal newInQty = oldInQty.add(returnQty);
        inboundItem.setInQty(newInQty);
        inboundItem.setAvailableQty(newInQty.subtract(outQty));
        inboundOrderItemMapper.updateById(inboundItem);
    }

    /**
     * 应用出库退回对批次的影响（减少 outQty）
     */
    private void applyOutboundReturnToBatch(MaterialInboundOrderItemDO inboundItem, BigDecimal returnQty) {
        BigDecimal outQty = inboundItem.getOutQty() == null ? BigDecimal.ZERO : inboundItem.getOutQty();
        BigDecimal availableQty = inboundItem.getAvailableQty() == null ? BigDecimal.ZERO : inboundItem.getAvailableQty();
        BigDecimal newOutQty = outQty.subtract(returnQty);
        if (newOutQty.compareTo(BigDecimal.ZERO) < 0) {
            throw exceptionCustomMsg(COMMON_NOT_FOUND, "退回数量不能大于该批次已出库数量");
        }

        inboundItem.setOutQty(newOutQty);
        inboundItem.setAvailableQty(availableQty.add(returnQty));
        inboundOrderItemMapper.updateById(inboundItem);
    }

    /**
     * 回滚出库退回对批次的影响（恢复 outQty）
     */
    private void rollbackOutboundReturnOnBatch(MaterialInboundOrderItemDO inboundItem, BigDecimal returnQty) {
        BigDecimal outQty = inboundItem.getOutQty() == null ? BigDecimal.ZERO : inboundItem.getOutQty();
        BigDecimal availableQty = inboundItem.getAvailableQty() == null ? BigDecimal.ZERO : inboundItem.getAvailableQty();
        inboundItem.setOutQty(outQty.add(returnQty));
        inboundItem.setAvailableQty(availableQty.subtract(returnQty));
        inboundOrderItemMapper.updateById(inboundItem);
    }

    @Override
    public void exportInboundImportTemplate(HttpServletResponse response) throws IOException {
        ExcelUtils.write(response, "物资入库导入模板.xlsx", "模板", MaterialInboundImportExcelVO.class, Collections.emptyList());
    }

    @Override
    public void exportOutboundImportTemplate(HttpServletResponse response) throws IOException {
        ExcelUtils.write(response, "物资出库导入模板.xlsx", "模板", MaterialOutboundImportExcelVO.class, Collections.emptyList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ImportResultVO importInbound(MultipartFile file) throws IOException {
        List<MaterialInboundImportExcelVO> rows = ExcelUtils.read(file, MaterialInboundImportExcelVO.class);
        int success = 0;
        List<ImportResultVO.RowError> failures = new ArrayList<>();
        Map<String, List<MaterialInboundImportExcelVO>> grouped = new LinkedHashMap<>();

        int rowNum = 1;
        for (MaterialInboundImportExcelVO row : rows) {
            rowNum++;
            if (StringUtils.isBlank(row.getOrderNo())) {
                ImportUtil.addFailure(failures, rowNum, null, null, "入库单号不能为空");
                continue;
            }
            grouped.computeIfAbsent(row.getOrderNo().trim(), k -> new ArrayList<>()).add(row);
        }

        for (Map.Entry<String, List<MaterialInboundImportExcelVO>> entry : grouped.entrySet()) {
            try {
                if (existsInboundOrderNo(entry.getKey())) {
                    throw new IllegalArgumentException("入库单号已存在，不允许重复导入");
                }
                inbound(buildInboundReq(entry.getKey(), entry.getValue()));
                success++;
            } catch (Exception e) {
                ImportUtil.addFailure(failures, null, null, entry.getKey(), e.getMessage(), e);
            }
        }
        return ImportUtil.buildResult(success, failures);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ImportResultVO importOutbound(MultipartFile file) throws IOException {
        List<MaterialOutboundImportExcelVO> rows = ExcelUtils.read(file, MaterialOutboundImportExcelVO.class);
        int success = 0;
        List<ImportResultVO.RowError> failures = new ArrayList<>();
        Map<String, List<MaterialOutboundImportExcelVO>> grouped = new LinkedHashMap<>();

        int rowNum = 1;
        for (MaterialOutboundImportExcelVO row : rows) {
            rowNum++;
            if (StringUtils.isBlank(row.getOrderNo())) {
                ImportUtil.addFailure(failures, rowNum, null, null, "单据号不能为空");
                continue;
            }
            grouped.computeIfAbsent(row.getOrderNo().trim(), k -> new ArrayList<>()).add(row);
        }

        for (Map.Entry<String, List<MaterialOutboundImportExcelVO>> entry : grouped.entrySet()) {
            try {
                if (existsOutboundOrderNo(entry.getKey())) {
                    throw new IllegalArgumentException("出库单号已存在，不允许重复导入");
                }
                outbound(buildOutboundReq(entry.getValue()));
                success++;
            } catch (Exception e) {
                ImportUtil.addFailure(failures, null, null, entry.getKey(), e.getMessage(), e);
            }
        }
        return ImportUtil.buildResult(success, failures);
    }

    private MaterialIoOrderSaveReqVO buildInboundReq(String orderNo, List<MaterialInboundImportExcelVO> groupRows) {
        MaterialInboundImportExcelVO head = groupRows.get(0);
        if (head.getOrderDate() == null) {
            throw new IllegalArgumentException("单据日期不能为空");
        }
        MaterialIoOrderSaveReqVO reqVO = new MaterialIoOrderSaveReqVO();
        reqVO.setBizType("IN");
        reqVO.setOrderNo(orderNo);
        reqVO.setOrderDate(head.getOrderDate());
        reqVO.setRemark(head.getRemark());
        reqVO.setTenantId(TenantContextHolder.getTenantId());

        List<MaterialIoOrderItemSaveReqVO> items = new ArrayList<>();
        for (MaterialInboundImportExcelVO row : groupRows) {
            if (!Objects.equals(head.getOrderDate(), row.getOrderDate())) {
                throw new IllegalArgumentException("同单号的单据日期必须一致");
            }
            if (!Objects.equals(StringUtils.trimToEmpty(head.getRemark()), StringUtils.trimToEmpty(row.getRemark()))) {
                throw new IllegalArgumentException("同单号的备注必须一致");
            }
            if (row.getQuantity() == null || row.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
                throw new IllegalArgumentException("入库明细数量必须填写且数量大于0");
            }
            Long materialId = resolveMaterialId(row.getMaterialNumber(), null);
            Long storeId = resolveStoreId(row.getRefInStoreNumber(), null);

            MaterialIoOrderItemSaveReqVO item = new MaterialIoOrderItemSaveReqVO();
            item.setMaterialId(materialId);
            item.setRefInStoreId(storeId);
            item.setQuantity(row.getQuantity());
            item.setInUnitPrice(row.getInUnitPrice());
            item.setAmount(row.getAmount());
            item.setProduceDate(row.getProduceDate());
            items.add(item);
        }
        reqVO.setItems(items);
        return reqVO;
    }

    private MaterialIoOrderSaveReqVO buildOutboundReq(List<MaterialOutboundImportExcelVO> groupRows) {
        MaterialOutboundImportExcelVO head = groupRows.get(0);
        if (head.getOrderDate() == null) {
            throw new IllegalArgumentException("单据日期不能为空");
        }

        Long outDeptId = resolveDeptId(head.getOutDeptNumber(), head.getOutDeptName());

        MaterialIoOrderSaveReqVO reqVO = new MaterialIoOrderSaveReqVO();
        reqVO.setBizType("OUT");
        reqVO.setOrderDate(head.getOrderDate());
        reqVO.setOutDeptId(outDeptId);
        reqVO.setOutUser(head.getOutUser());
        reqVO.setOutReason(head.getOutReason());
        reqVO.setRemark(head.getRemark());
        reqVO.setTenantId(TenantContextHolder.getTenantId());

        List<MaterialIoOrderItemSaveReqVO> items = new ArrayList<>();
        for (MaterialOutboundImportExcelVO row : groupRows) {
            if (!Objects.equals(head.getOrderDate(), row.getOrderDate())) {
                throw new IllegalArgumentException("同单号的单据日期必须一致");
            }
            if (!Objects.equals(StringUtils.trimToEmpty(head.getOutDeptNumber()), StringUtils.trimToEmpty(row.getOutDeptNumber()))
                    || !Objects.equals(StringUtils.trimToEmpty(head.getOutDeptName()), StringUtils.trimToEmpty(row.getOutDeptName()))) {
                throw new IllegalArgumentException("同单号的领用部门必须一致");
            }
            if (!Objects.equals(StringUtils.trimToEmpty(head.getOutUser()), StringUtils.trimToEmpty(row.getOutUser()))) {
                throw new IllegalArgumentException("同单号的领用人必须一致");
            }
            if (!Objects.equals(StringUtils.trimToEmpty(head.getOutReason()), StringUtils.trimToEmpty(row.getOutReason()))) {
                throw new IllegalArgumentException("同单号的出库原因必须一致");
            }
            if (!Objects.equals(StringUtils.trimToEmpty(head.getRemark()), StringUtils.trimToEmpty(row.getRemark()))) {
                throw new IllegalArgumentException("同单号的备注必须一致");
            }
            if (row.getQuantity() == null || row.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
                throw new IllegalArgumentException("出库明细数量必须填写且数量大于0");
            }

            Long materialId = resolveMaterialId(row.getMaterialNumber(), row.getMaterialName());
            Long inboundItemId = resolveInboundItemId(row.getInboundOrderNo(), materialId);

            MaterialIoOrderItemSaveReqVO item = new MaterialIoOrderItemSaveReqVO();
            item.setMaterialId(materialId);
            item.setInboundItemId(inboundItemId);
            item.setQuantity(row.getQuantity());
            item.setSaleUnitPrice(row.getSaleUnitPrice());
            item.setAmount(row.getAmount());
            item.setProduceDate(row.getProduceDate());
            items.add(item);
        }
        reqVO.setItems(items);
        return reqVO;
    }

    private boolean existsInboundOrderNo(String orderNo) {
        return inboundOrderMapper.selectCount(new LambdaQueryWrapperX<MaterialInboundOrderDO>()
                .eq(MaterialInboundOrderDO::getOrderNo, orderNo)
                .eq(MaterialInboundOrderDO::getTenantId, TenantContextHolder.getTenantId())) > 0;
    }

    private boolean existsOutboundOrderNo(String orderNo) {
        return outboundOrderMapper.selectCount(new LambdaQueryWrapperX<MaterialOutboundOrderDO>()
                .eq(MaterialOutboundOrderDO::getOrderNo, orderNo)
                .eq(MaterialOutboundOrderDO::getTenantId, TenantContextHolder.getTenantId())) > 0;
    }

    private Long resolveMaterialId(String materialNumber, String materialName) {
        LambdaQueryWrapperX<MaterialInfoDO> wrapper = new LambdaQueryWrapperX<>();
        if (StringUtils.isNotBlank(materialNumber)) {
            wrapper.eq(MaterialInfoDO::getMaterialNumber, materialNumber.trim());
        } else if (StringUtils.isNotBlank(materialName)) {
            wrapper.eq(MaterialInfoDO::getMaterialName, materialName.trim());
        } else {
            throw new IllegalArgumentException("物资编号和物资名称不能同时为空");
        }
        wrapper.eq(MaterialInfoDO::getTenantId, TenantContextHolder.getTenantId());
        List<MaterialInfoDO> list = materialInfoMapper.selectList(wrapper);
        if (list.isEmpty()) {
            throw new IllegalArgumentException("未找到匹配的物资");
        }
        if (list.size() > 1) {
            throw new IllegalArgumentException("匹配到多个物资，请优先填写唯一物资编号");
        }
        return list.get(0).getId();
    }

    private Long resolveStoreId(String storeNumber, String storeName) {
        LambdaQueryWrapperX<StoreManageDO> wrapper = new LambdaQueryWrapperX<>();
        if (StringUtils.isNotBlank(storeNumber)) {
            wrapper.eq(StoreManageDO::getStoreNumber, storeNumber.trim());
        } else if (StringUtils.isNotBlank(storeName)) {
            wrapper.eq(StoreManageDO::getStoreName, storeName.trim());
        } else {
            throw new IllegalArgumentException("入库仓库编号和名称不能同时为空");
        }
        wrapper.eq(StoreManageDO::getTenantId, TenantContextHolder.getTenantId());
        List<StoreManageDO> list = storeManageMapper.selectList(wrapper);
        if (list.isEmpty()) {
            throw new IllegalArgumentException("未找到匹配的仓库");
        }
        if (list.size() > 1) {
            throw new IllegalArgumentException("匹配到多个仓库，请优先填写唯一仓库编号");
        }
        return list.get(0).getId();
    }

    private Long resolveDeptId(String deptNumber, String deptName) {
        LambdaQueryWrapperX<DeptDO> wrapper = new LambdaQueryWrapperX<>();
        if (StringUtils.isNotBlank(deptNumber)) {
            if (!deptNumber.trim().matches("\\d+")) {
                throw new IllegalArgumentException("领用部门编号必须为数字ID");
            }
            wrapper.eq(DeptDO::getId, Long.valueOf(deptNumber.trim()));
        } else if (StringUtils.isNotBlank(deptName)) {
            wrapper.eq(DeptDO::getName, deptName.trim());
        } else {
            throw new IllegalArgumentException("领用部门编号和名称不能同时为空");
        }
        wrapper.eq(DeptDO::getTenantId, TenantContextHolder.getTenantId());
        List<DeptDO> list = deptMapper.selectList(wrapper);
        if (list.isEmpty()) {
            throw new IllegalArgumentException("未找到匹配的领用部门");
        }
        if (list.size() > 1) {
            throw new IllegalArgumentException("匹配到多个领用部门，请优先填写唯一部门编号");
        }
        return list.get(0).getId();
    }

    private Long resolveInboundItemId(String inboundOrderNo, Long materialId) {
        if (StringUtils.isBlank(inboundOrderNo)) {
            throw new IllegalArgumentException("关联入库单号不能为空");
        }
        MaterialInboundOrderDO inboundOrder = inboundOrderMapper.selectOne(new LambdaQueryWrapperX<MaterialInboundOrderDO>()
                .eq(MaterialInboundOrderDO::getOrderNo, inboundOrderNo.trim())
                .eq(MaterialInboundOrderDO::getTenantId, TenantContextHolder.getTenantId()));
        if (inboundOrder == null) {
            throw new IllegalArgumentException("未找到关联入库单号: " + inboundOrderNo);
        }
        List<MaterialInboundOrderItemDO> items = inboundOrderItemMapper.selectList(new LambdaQueryWrapperX<MaterialInboundOrderItemDO>()
                .eq(MaterialInboundOrderItemDO::getInboundOrderId, inboundOrder.getId())
                .eq(MaterialInboundOrderItemDO::getMaterialId, materialId)
                .orderByDesc(MaterialInboundOrderItemDO::getId));
        if (items.isEmpty()) {
            throw new IllegalArgumentException("关联入库单中未找到该物资对应批次");
        }
        return items.get(0).getId();
    }

    /**
     * 批量加载物资信息
     */
    private Map<Long, MaterialInfoDO> loadMaterialMap(Set<Long> materialIds) {
        if (materialIds == null || materialIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return materialInfoMapper.selectBatchIds(materialIds).stream()
                .collect(Collectors.toMap(MaterialInfoDO::getId, Function.identity()));
    }

    /**
     * 根据物资信息批量加载仓库名称
     */
    private Map<Long, String> loadStoreNameMapByMaterialMap(Map<Long, MaterialInfoDO> materialMap) {
        if (materialMap == null || materialMap.isEmpty()) {
            return Collections.emptyMap();
        }
        Set<Long> storeIds = materialMap.values().stream()
                .map(MaterialInfoDO::getStoreId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (storeIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return storeManageMapper.selectList(
                        new LambdaQueryWrapperX<StoreManageDO>().in(StoreManageDO::getId, storeIds)).stream()
                .collect(Collectors.toMap(StoreManageDO::getId, StoreManageDO::getStoreName));
    }

}


