package com.ruoyi.inventory.service.impl;

import com.ruoyi.common.annotation.SerialExecution;
import com.ruoyi.common.config.WarehouseConfig;
import com.ruoyi.common.core.domain.entity.Materials;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.inventory.utils.InventoryCache;
import com.ruoyi.inventory.domain.*;
import com.ruoyi.inventory.domain.vo.OutboundTemplateVO;
import com.ruoyi.inventory.mapper.InventoryMapper;
import com.ruoyi.inventory.mapper.OutboundOrderItemsMapper;
import com.ruoyi.inventory.mapper.OutboundOrderLogMapper;
import com.ruoyi.inventory.mapper.OutboundOrdersMapper;
import com.ruoyi.inventory.service.IOutboundOrdersService;
import org.apache.commons.lang3.SystemUtils;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * 出库单主Service业务层处理
 * 最终修复版：
 * 1. 合并维度调整为「物料ID+库存类型+库位ID」，确保库位信息准确
 * 2. 扣减时按该维度分组，统一收集记录后合并
 * 3. 修复跨场景（无库位→有库位）扣减同一物料+库存类型的合并逻辑
 * 4. 新增相同库存维度的明细合并逻辑，插入前合并为一条记录
 *
 * @author ruoyi
 * @date 2025-12-03
 */
@Service
public class OutboundOrdersServiceImpl implements IOutboundOrdersService {
    @Autowired
    private OutboundOrdersMapper outboundOrdersMapper;

    @Autowired
    private OutboundOrderItemsMapper outboundOrderItemsMapper;
    @Autowired
    private OutboundOrderLogMapper outboundOrderLogMapper;

    @Autowired
    private OwnersServiceImpl ownersService;

    @Autowired
    private InventoryServiceImpl inventoryService;
    @Autowired
    private MaterialsServiceImpl materialsService;
    @Autowired
    private StorageLocationsServiceImpl storageLocationsService;

    @Autowired
    private InventoryMapper inventoryMapper;

    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    /**
     * 查询出库单主
     *
     * @param id 出库单主主键
     * @return 出库单主
     */
    @Override
    public OutboundOrders selectOutboundOrdersById(String id) {
        return outboundOrdersMapper.selectOutboundOrdersById(id);
    }

    /**
     * 查询出库单主列表
     *
     * @param outboundOrders 出库单主
     * @return 出库单主
     */
    @Override
    public List<OutboundOrders> selectOutboundOrdersList(OutboundOrders outboundOrders) {
        List<OutboundOrders> outboundOrders1 = outboundOrdersMapper.selectOutboundOrdersList(outboundOrders);
        return outboundOrders1;
    }

    /**
     * 新增出库单主
     *
     * @param outboundOrders 出库单主
     * @return 结果
     */
    @Transactional
    @Override
    public int insertOutboundOrders(OutboundOrders outboundOrders) {
        outboundOrders.setCreateTime(DateUtils.getNowDate());
        outboundOrders.setCreateUserCode(SystemUtils.getUserName());
        outboundOrders.setId(UUID.randomUUID().toString());
        outboundOrders.setIsImport(0l);

        int rows = outboundOrdersMapper.insertOutboundOrders(outboundOrders);
        insertOutboundOrderItems(outboundOrders);
        return rows;
    }

    /**
     * 修改出库单主（仅保留方法定义，移除更新逻辑）
     *
     * @param outboundOrders 出库单主
     * @return 结果
     */
    @Transactional
    @Override
    public int updateOutboundOrders(OutboundOrders outboundOrders) {
        throw new ServiceException("当前系统仅支持新增出库单，不支持修改操作");
    }

    /**
     * 批量删除出库单主
     *
     * @param ids 需要删除的出库单主主键
     * @return 结果
     */
    @Transactional
    @Override
    public int deleteOutboundOrdersByIds(String[] ids) {
        outboundOrdersMapper.deleteOutboundOrderItemsByOrderIds(ids);
        outboundOrderLogMapper.deleteOutboundOrderLogByOrdersIds(ids);
        return outboundOrdersMapper.deleteOutboundOrdersByIds(ids);
    }

    /**
     * 删除出库单主信息
     *
     * @param id 出库单主主键
     * @return 结果
     */
    @Transactional
    @Override
    public int deleteOutboundOrdersById(String id) {
        outboundOrdersMapper.deleteOutboundOrderItemsByOrderId(id);
        return outboundOrdersMapper.deleteOutboundOrdersById(id);
    }

    @SerialExecution(group = "inventoryRefresh", fair = true)
    @Override
    public int ship(OutboundOrders outboundOrders) {
        // 1. 查询当前出库单的所有明细
        OutboundOrderItems query = new OutboundOrderItems();
        query.setOutboundOrderId(outboundOrders.getId());
        query.setDivisor(null);
        List<OutboundOrderItems> outboundOrderItems = outboundOrderItemsMapper.selectOutboundOrderItemsList(query);

        // 2. 更新明细和订单状态
        OutboundOrderLog outboundOrderLog = new OutboundOrderLog();
        String updateUser = SystemUtils.getUserName();
        Date updateTime = DateUtils.getNowDate();
        for (OutboundOrderItems item : outboundOrderItems) {
            item.setItemStatus(3L);
            item.setUpdateBy(updateUser);
            item.setUpdateTime(updateTime);
            outboundOrderItemsMapper.updateOutboundOrderItems(item);

            outboundOrderLog.setId(item.getId());
            outboundOrderLog.setItemStatus(item.getItemStatus());
            outboundOrderLogMapper.updateOutboundOrderLog(outboundOrderLog);
        }

        outboundOrders.setOrderStatus(2L);
        outboundOrders.setUpdateTime(updateTime);
        outboundOrders.setUpdateUserCode(updateUser);
        outboundOrdersMapper.updateOutboundOrders(outboundOrders);

        loadInventoryTOIdMap();

        // 3. 执行库存扣减（无库位不足时扣有库位）
        this.deductInventory(outboundOrderItems, updateUser, updateTime);

        return 1;
    }

    /**
     * 核心库存扣减逻辑（最终修复版）
     * 1. 扣减维度：物料ID+库存类型+库位ID（统一维度，跨场景合并）
     * 2. 扣减时先按该维度分组，再累计扣减数量
     * 3. 收集记录时按该维度统一收集，合并器仅做最终校验
     */
    private Map<String, List<Map<String, Object>>> deductInventory(List<OutboundOrderItems> outboundOrderItems, String updateUser, Date updateTime) {
        if (CollectionUtils.isEmpty(outboundOrderItems)) {
            return Collections.emptyMap();
        }

        Map<String, List<Map<String, Object>>> deductRecordMap = new HashMap<>();
        // 预加载库存：按「物料ID+库存类型+库位ID」分组（核心调整）
        Map<String, Inventory> inventoryFullMap = this.loadInventoryFullMap();
        Map<String, Long> deductQtyMap = this.buildDeductQtyMap(outboundOrderItems);

        // 库存更新Map（最终去重，保留最终状态）
        Map<String, Inventory> toUpdateInventoryMap = new LinkedHashMap<>();

        for (Map.Entry<String, Long> entry : deductQtyMap.entrySet()) {
            String key = entry.getKey();
            Long totalDeductQty = entry.getValue();
            if (totalDeductQty <= 0) continue;

            String[] keyParts = key.split("_");
            String materialId = keyParts.length > 0 ? keyParts[0] : "";
            String locationId = keyParts.length > 1 ? keyParts[1] : "";
            String inventoryType = keyParts.length > 2 ? keyParts[2] : "";
            String itemId = outboundOrderItems.stream()
                    .filter(item -> key.equals(buildDeductKey(item)))
                    .map(OutboundOrderItems::getId)
                    .findFirst()
                    .orElse(null);

            Long remainDeductQty = totalDeductQty;
            // 核心：按「物料ID+库存类型+库位ID」分组扣减（先处理无库位，再处理有库位）
            List<Map<String, Object>> tempDeductRecords = new ArrayList<>();

            // 步骤1：先扣指定维度的库存（无库位/有库位）
            if (StringUtils.isBlank(locationId)) {
                // 无库位：先扣「物料+库存类型+空库位」的库存
                String noLocKey = buildInventoryKey(materialId, "", inventoryType);
                remainDeductQty = deductByInventoryKey(noLocKey, remainDeductQty, updateUser, updateTime, inventoryFullMap, toUpdateInventoryMap, tempDeductRecords);

                // 无库位不足，扣「物料+库存类型+任意有库位」的库存
                if (remainDeductQty > 0) {
                    // 筛选该物料+库存类型的所有有库位库存
                    List<String> hasLocKeys = inventoryFullMap.keySet().stream()
                            .filter(k -> {
                                String[] parts = k.split("_");
                                return parts.length >= 3
                                        && parts[0].equals(materialId)
                                        && parts[2].equals(inventoryType)
                                        && StringUtils.isNotBlank(parts[1]);
                            })
                            .collect(Collectors.toList());

                    for (String hasLocKey : hasLocKeys) {
                        if (remainDeductQty <= 0) break;
                        remainDeductQty = deductByInventoryKey(hasLocKey, remainDeductQty, updateUser, updateTime, inventoryFullMap, toUpdateInventoryMap, tempDeductRecords);
                    }
                }
            } else {
                // 有库位：扣指定「物料+库存类型+库位ID」的库存
                String targetKey = buildInventoryKey(materialId, locationId, inventoryType);
                remainDeductQty = deductByInventoryKey(targetKey, remainDeductQty, updateUser, updateTime, inventoryFullMap, toUpdateInventoryMap, tempDeductRecords);
            }

            // 步骤2：剩余部分扣负数（最后一个库存）
            if (remainDeductQty > 0 && !tempDeductRecords.isEmpty()) {
                Map<String, Object> lastRecord = tempDeductRecords.get(tempDeductRecords.size() - 1);
                String lastInvId = (String) lastRecord.get("inventoryId");
                Inventory lastInv = toUpdateInventoryMap.get(lastInvId);
                if (lastInv == null) {
                    throw new ServiceException(String.format("物料[%s]库存类型[%s]扣减负数时未找到目标库存", materialId, inventoryType));
                }

                // 累计扣减负数数量
                Long finalDeduct = remainDeductQty;
                lastInv.setQuantity(lastInv.getQuantity() - finalDeduct);
                lastInv.setInventoryStatus(0L);
                lastInv.setUpdateBy(updateUser);
                lastInv.setUpdateTime(updateTime);
                toUpdateInventoryMap.put(lastInvId, lastInv);

                // 合并到最后一条记录
                lastRecord.put("deductQty", (Long) lastRecord.get("deductQty") + finalDeduct);
                remainDeductQty = 0L;
            }

            // 步骤3：统一合并（按物料+库存类型+库位ID）
            if (itemId != null && !tempDeductRecords.isEmpty()) {
                List<Map<String, Object>> mergedRecords = mergeDeductRecords(tempDeductRecords);
                deductRecordMap.put(itemId, mergedRecords);
            }

            // 校验是否扣减完成
            if (remainDeductQty > 0) {
                throw new ServiceException(String.format("物料[%s]库存类型[%s]扣减失败，剩余%d数量未扣减", materialId, inventoryType, remainDeductQty));
            }
        }

        // 批量更新库存（最终去重）
        if (!toUpdateInventoryMap.isEmpty()) {
            List<Inventory> needUpdateList = new ArrayList<>(toUpdateInventoryMap.values());
            inventoryMapper.batchUpdateInventory(needUpdateList);
            inventoryService.RefreshInventory(needUpdateList.stream().map(Inventory::getId).distinct().collect(Collectors.toList()));
        }

        return deductRecordMap;
    }

    /**
     * 按「物料ID+库位ID+库存类型」扣减指定库存
     * @param inventoryKey 库存Key（物料ID_库位ID_库存类型）
     * @param deductQty 待扣减数量
     * @return 剩余未扣减数量
     */
    private Long deductByInventoryKey(String inventoryKey, Long deductQty, String updateUser, Date updateTime,
                                      Map<String, Inventory> inventoryFullMap, Map<String, Inventory> toUpdateInventoryMap,
                                      List<Map<String, Object>> tempDeductRecords) {
        Inventory inv = inventoryFullMap.get(inventoryKey);
        if (inv == null) {
            return deductQty;
        }

        Long currentQty = Optional.ofNullable(inv.getQuantity()).orElse(0L);
        Long canDeduct = Math.min(deductQty, currentQty);

        // 更新库存状态
        inv.setQuantity(currentQty - canDeduct);
        inv.setInventoryStatus(inv.getQuantity() <= 0 ? 0L : 1L);
        inv.setUpdateBy(updateUser);
        inv.setUpdateTime(updateTime);
        toUpdateInventoryMap.put(inv.getId(), inv);

        // 收集扣减记录（按统一维度）
        Map<String, Object> record = buildDeductRecord(inv, inv.getInventoryType().toString(), canDeduct);
        tempDeductRecords.add(record);

        // 返回剩余未扣减数量
        return deductQty - canDeduct;
    }

    /**
     * 合并同一「物料ID+库存类型+库位ID」的扣减记录（最终版）
     * 核心：按「物料ID_库位ID_库存类型」合并，保留库位信息准确性
     */
    private List<Map<String, Object>> mergeDeductRecords(List<Map<String, Object>> deductRecords) {
        if (CollectionUtils.isEmpty(deductRecords)) {
            return Collections.emptyList();
        }

        // 合并Key：物料ID_库位ID_库存类型
        Map<String, Map<String, Object>> mergeMap = new LinkedHashMap<>();
        for (Map<String, Object> record : deductRecords) {
            String materialId = (String) record.get("materialId");
            String locationId = (String) record.get("locationId");
            String inventoryType = (String) record.get("inventoryType");
            Long deductQty = (Long) record.get("deductQty");

            if (StringUtils.isBlank(materialId) || StringUtils.isBlank(inventoryType) || deductQty <= 0) {
                continue;
            }

            String mergeKey = buildInventoryKey(materialId, locationId, inventoryType);
            if (mergeMap.containsKey(mergeKey)) {
                Map<String, Object> existRecord = mergeMap.get(mergeKey);
                existRecord.put("deductQty", (Long) existRecord.get("deductQty") + deductQty);
            } else {
                Map<String, Object> newRecord = new HashMap<>(record);
                mergeMap.put(mergeKey, newRecord);
            }
        }

        return new ArrayList<>(mergeMap.values());
    }

    /**
     * 构建库存Key：物料ID_库位ID_库存类型
     */
    private String buildInventoryKey(String materialId, String locationId, String inventoryType) {
        return String.join("_",
                Optional.ofNullable(materialId).orElse(""),
                Optional.ofNullable(locationId).orElse(""),
                Optional.ofNullable(inventoryType).orElse("")
        );
    }

    /**
     * 构建扣减记录
     */
    private Map<String, Object> buildDeductRecord(Inventory inv, String inventoryType, Long deductQty) {
        Map<String, Object> record = new HashMap<>();
        record.put("inventoryId", inv.getId());
        record.put("locationId", inv.getLocationId());
        record.put("materialId", inv.getMaterialId());
        record.put("inventoryType", inventoryType);
        record.put("deductQty", deductQty);
        return record;
    }

    /**
     * 预加载库存全量Map（按「物料ID_库位ID_库存类型」为Key）
     */
    private Map<String, Inventory> loadInventoryFullMap() {
        Collection<Inventory> allInventory = InventoryCache.getAll().values();
        if (CollectionUtils.isEmpty(allInventory)) {
            return Collections.emptyMap();
        }

        return allInventory.stream()
                .collect(Collectors.toMap(
                        inv -> buildInventoryKey(
                                inv.getMaterialId(),
                                inv.getLocationId(),
                                Optional.ofNullable(inv.getInventoryType()).map(String::valueOf).orElse("")
                        ),
                        inv -> inv,
                        (k1, k2) -> k1, // 重复Key保留第一个
                        LinkedHashMap::new
                ));
    }

    /**
     * 构建扣减数量Map
     * Key规则：物料ID_库位ID_库存类型
     */
    private Map<String, Long> buildDeductQtyMap(List<OutboundOrderItems> items) {
        Map<String, Long> deductQtyMap = new HashMap<>();
        for (OutboundOrderItems item : items) {
            String key = buildDeductKey(item);
            Long qty = Optional.ofNullable(item.getActualQuantity()).orElse(0L);
            deductQtyMap.put(key, deductQtyMap.getOrDefault(key, 0L) + qty);
        }
        return deductQtyMap;
    }

    /**
     * 构建明细的扣减Key
     */
    private String buildDeductKey(OutboundOrderItems item) {
        return buildInventoryKey(
                item.getMaterialId(),
                item.getLocationId(),
                Optional.ofNullable(item.getInventoryType()).map(String::valueOf).orElse("")
        );
    }

    @Override
    public List<Map<String, String>> outboundOrdersTopTenByQuantity() {
        return outboundOrdersMapper.SelectOutboundOrdersMaterialsTopTenByQuantity();
    }

    @Override
    public List<Map<String, String>> outboundOrdersTopTenByAmount() {
        return outboundOrdersMapper.SelectOutboundOrdersMaterialsTopTenByAmount();
    }

    @Override
    public String outboundOrdersCount() {
        return outboundOrdersMapper.outboundOrdersCount();
    }

    /**
     * 新增出库单明细信息
     */
    public void insertOutboundOrderItems(OutboundOrders outboundOrders) {
        List<OutboundOrderItems> outboundOrderItemsList = outboundOrders.getOutboundOrderItemsList();
        String id = outboundOrders.getId();
        if (CollectionUtils.isEmpty(outboundOrderItemsList)) {
            return;
        }

        // 库存校验：失败时抛异常
        boolean isValid = inventoryService.inventoryLockValidation(outboundOrderItemsList);
        if (!isValid) {
            throw new RuntimeException("库存被修改请重新确认");
        }

        // 合并相同库存维度的明细记录（简化版）
        List<OutboundOrderItems> mergedItemsList = mergeSameInventoryItems(outboundOrderItemsList);

        // 为明细设置订单ID和主键ID
        for (OutboundOrderItems items : mergedItemsList) {
            items.setOutboundOrderId(id);
            items.setOrderId(outboundOrders.getOrderId());
            items.setId(UUID.randomUUID().toString().replace("-", ""));
        }

        // 批量插入出库单明细
        outboundOrdersMapper.batchOutboundOrderItems(mergedItemsList);

        // 拷贝明细到日志列表
        List<String> inventoryIds = new ArrayList<>();
        List<OutboundOrderLog> outboundOrderLogs = new ArrayList<>();
        for (OutboundOrderItems items : mergedItemsList) {
            OutboundOrderLog log = new OutboundOrderLog();
            BeanUtils.copyProperties(items, log);
            log.setOrderId(items.getOutboundOrderId());
            outboundOrderLogs.add(log);
            inventoryIds.add(log.getInventoryId());
        }

        // 插入日志 + 刷新库存
        if (!outboundOrderLogs.isEmpty()) {
            outboundOrderLogMapper.batchOutboundOrderLog(outboundOrderLogs);
        }
        if (!inventoryIds.isEmpty()) {
            inventoryService.RefreshInventory(inventoryIds);
        }
    }

    /**
     * 简化版：合并相同库存维度的明细记录
     * 按「物料ID+库存类型+库位ID」维度合并，仅累加实际数量
     */
    private List<OutboundOrderItems> mergeSameInventoryItems(List<OutboundOrderItems> itemsList) {
        if (CollectionUtils.isEmpty(itemsList)) {
            return Collections.emptyList();
        }

        // 按「物料ID+库存类型+库位ID」分组合并
        Map<String, OutboundOrderItems> mergeMap = new LinkedHashMap<>();

        for (OutboundOrderItems item : itemsList) {
            String mergeKey = buildInventoryKey(
                    item.getMaterialId(),
                    item.getLocationId(),
                    Optional.ofNullable(item.getInventoryType()).map(String::valueOf).orElse("")
            );

            if (mergeMap.containsKey(mergeKey)) {
                // 只合并实际数量
                OutboundOrderItems existItem = mergeMap.get(mergeKey);
                Long newActualQty = Optional.ofNullable(existItem.getActualQuantity()).orElse(0L)
                        + Optional.ofNullable(item.getActualQuantity()).orElse(0L);
                existItem.setActualQuantity(newActualQty);
            } else {
                // 新记录直接放入
                mergeMap.put(mergeKey, item);
            }
        }

        return new ArrayList<>(mergeMap.values());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public String importOutboundOrders(List<OutboundTemplateVO> inboundOrdersList, Boolean isUpdateSupport, String operName, Integer orderType) {
        // 1. 基础空值校验
        if (CollectionUtils.isEmpty(inboundOrdersList)) {
            throw new ServiceException("导入数据不能为空！");
        }

        // 2. 初始化变量
        int totalMainSuccess = 0;
        int totalMainFailure = 0;
        int totalItemSuccess = 0;
        int totalItemFailure = 0;
        StringBuilder successMsg = new StringBuilder();
        StringBuilder failureMsg = new StringBuilder();
        Date now = DateUtils.getNowDate();
        Long userId = SecurityUtils.getUserId();
        String operId = userId != null ? userId.toString() : "system";

        Map<String, OutboundOrders> validMainMap = new HashMap<>();
        Map<String, List<OutboundOrderItems>> validItemMap = new HashMap<>();
        boolean hasValidateError = false;

        // 3. 预加载映射缓存
        Map<String, String> sapToMaterialIdMap = loadSapToMaterialIdMap();
        Map<String, String> locationNameToIdMap = loadLocationNameToIdMap();
        Map<String, String> ownerNameToIdMap = loadOwnerNameToIdMap();
        Map<String, AbstractMap.SimpleEntry<String, Long>> inventoryTOIdMap = loadInventoryTOIdMap();

        // 4. 按入库单号分组
        Map<String, List<OutboundTemplateVO>> orderGroupMap = inboundOrdersList.stream()
                .filter(vo -> StringUtils.isNotBlank(vo.getOrderId()))
                .collect(Collectors.groupingBy(OutboundTemplateVO::getOrderId));

        // 5. 数据验证（仅新增逻辑）
        for (Map.Entry<String, List<OutboundTemplateVO>> entry : orderGroupMap.entrySet()) {
            String orderId = entry.getKey();
            List<OutboundTemplateVO> voList = entry.getValue();
            OutboundOrders mainDO = null;
            List<OutboundOrderItems> itemDOList = new ArrayList<>();

            try {
                OutboundTemplateVO firstVO = voList.get(0);
                // 检查出库单是否已存在
                OutboundOrders outboundOrdersQuery = new OutboundOrders();
                outboundOrdersQuery.setOrderId(orderId);
                List<OutboundOrders> existMains = outboundOrdersMapper.selectOutboundOrdersList(outboundOrdersQuery);

                if (existMains != null && !existMains.isEmpty()) {
                    throw new ServiceException(String.format("入库单号【%s】已存在，当前系统仅支持新增，不支持更新", orderId));
                }

                // 构建新出库单主数据
                mainDO = new OutboundOrders();
                BeanUtils.copyProperties(firstVO, mainDO,
                        "sapNo", "materialName", "plannedQuantity", "actualQuantity",
                        "plannedPackages", "materialUnit", "materialRemark", "warehouseName", "warehouseId");

                // 货主校验
                String ownerName = firstVO.getOwnerName();
                String ownerId = firstVO.getOwnerId();
                if (StringUtils.isNotBlank(ownerName)) {
                    String mappedOwnerId = ownerNameToIdMap.get(ownerName.trim());
                    if (StringUtils.isBlank(mappedOwnerId)) {
                        throw new ServiceException(String.format("业主【%s】不存在，无法新增入库单【%s】",
                                ownerName, orderId));
                    }
                    if (ownerId != null && !ownerId.equals(mappedOwnerId)) {
                        throw new ServiceException(String.format("入库单号【%s】的业主ID【%s】与业主名称【%s】不匹配",
                                orderId, ownerId, ownerName));
                    }
                    mainDO.setOwnerId(mappedOwnerId);
                } else if (ownerId != null) {
                    Owners ownerById = ownersService.selectOwnersById(ownerId);
                    if (ownerById == null) {
                        throw new ServiceException(String.format("入库单号【%s】的业主ID【%s】不存在",
                                orderId, ownerId));
                    }
                    mainDO.setOwnerId(ownerId);
                } else {
                    throw new ServiceException(String.format("入库单号【%s】的业主名称/ID不能为空", orderId));
                }

                // 填充主表必填字段
                mainDO.setId(UUID.randomUUID().toString());
                mainDO.setOrderId(orderId);
                mainDO.setOrderStatus(2L);
                mainDO.setCreateBy(operId);
                mainDO.setCreateTime(now);
                mainDO.setOrderTypeId(String.valueOf(orderType));
                mainDO.setCreateUserCode(operId);
                mainDO.setUpdateBy(operId);
                mainDO.setUpdateTime(now);
                mainDO.setUpdateUserCode(operId);
                mainDO.setSortNo(Optional.ofNullable(mainDO.getSortNo()).orElse(0L));
                mainDO.setIsImport(0L);
                //添加日期
                mainDO.setInboundDate(DateUtils.getNowDate());

                // 明细校验
                for (int i = 0; i < voList.size(); i++) {
                    OutboundTemplateVO vo = voList.get(i);
                    int lineNo = i + 1;
                    OutboundOrderItems itemDO = new OutboundOrderItems();

                    BeanUtils.copyProperties(vo, itemDO,
                            "orderId", "systemNo", "orderTypeId", "batchId", "warehouseName", "warehouseId");

                    // 填充明细必填字段
                    itemDO.setId(UUID.randomUUID().toString());
                    itemDO.setOrderId(orderId);
                    itemDO.setBatchCode(Optional.ofNullable(mainDO.getBatchCode()).orElse(""));
                    itemDO.setOutboundOrderId(mainDO.getId());
                    itemDO.setCreateBy(operId);
                    itemDO.setCreateTime(now);
                    itemDO.setCreateUserCode(operId);
                    itemDO.setSortNo(0L);
                    itemDO.setItemStatus(3L); // 设置为已出库状态
                    itemDO.setShippedAt(mainDO.getInboundDate()); //

                    // 物料SAP校验
                    String sapNo = vo.getSapNo() != null ? vo.getSapNo().trim() : "";
                    if (StringUtils.isBlank(sapNo)) {
                        throw new ServiceException(String.format("入库单号【%s】第%d条明细的物料SAP号不能为空",
                                orderId, lineNo));
                    }
                    String materialId = sapToMaterialIdMap.get(sapNo);
                    if (StringUtils.isBlank(materialId)) {
                        throw new ServiceException(String.format("入库单号【%s】第%d条明细的SAP号【%s】对应的物料不存在",
                                orderId, lineNo, sapNo));
                    }
                    itemDO.setMaterialId(materialId);

                    // 库位校验
                    String locationName = vo.getLocationName() != null ? vo.getLocationName().trim() : "";
                    String locationId = locationNameToIdMap.get(locationName);
                    if (StringUtils.isNotBlank(locationName)) {
                        if (StringUtils.isBlank(locationId)) {
                            throw new ServiceException(String.format("入库单号【%s】第%d条明细的库位【%s】不存在",
                                    orderId, lineNo, locationName));
                        }
                        itemDO.setLocationId(locationId);
                    }

                    // 库存类型设置
                    itemDO.setInventoryType(orderType);

                    // 实际出库数量（使用计划数量）
                    itemDO.setActualQuantity(Optional.ofNullable(vo.getActualQuantity()).orElse(0L));

                    // 库存校验（包含inventoryType）- 有库位才校验，无库位直接跳过
                    if (StringUtils.isNotBlank(locationName)) {
                        String inventoryTypeStr = Optional.ofNullable(orderType).map(String::valueOf).orElse("");
                        String inventoryMatchKey = buildInventoryKey(materialId, locationId, inventoryTypeStr);
                        AbstractMap.SimpleEntry<String, Long> inventoryEntry = inventoryTOIdMap.get(inventoryMatchKey);
                        String inventoryId = "";
                        if (inventoryEntry == null) {
                            Inventory inventory = new Inventory();
                            BeanUtils.copyProperties(itemDO, inventory);
                            inventoryId = UUID.randomUUID().toString();
                            inventory.setInventoryType(Long.valueOf(orderType));
                            inventory.setBatchId(itemDO.getBatchCode());
                            inventory.setWarehousesId("local");
                            inventory.setQuantity(0L);
                            inventory.setInventoryStatus(1L);
                            inventory.setIsUsed(1L);
                            inventory.setId(inventoryId);
                            int insertCount = inventoryMapper.insertInventory(inventory);
                            if (insertCount != 1) {
                                throw new ServiceException(String.format("入库单号【%s】第%d条明细新增库存失败，插入行数为0", orderId, lineNo));
                            }

                            // 插入后直接添加到全局缓存
                            InventoryCache.addInventory(inventoryMatchKey, inventory);

                            sqlSessionTemplate.clearCache();
                            sqlSessionTemplate.flushStatements();
                        } else {
                            System.out.println("库存已存在，使用已有库存ID：" + inventoryEntry.getKey());
                            inventoryId = inventoryEntry.getKey();
                        }
                        itemDO.setInventoryId(inventoryId);
                    }
                    // 无库位时不校验库存，也不设置inventoryId

                    itemDOList.add(itemDO);
                }

                // 合并相同库存维度的明细
                List<OutboundOrderItems> mergedItemList = mergeSameInventoryItems(itemDOList);
                validMainMap.put(orderId, mainDO);
                validItemMap.put(orderId, mergedItemList);

            } catch (Exception e) {
                hasValidateError = true;
                totalMainFailure++;
                totalItemFailure += voList.size();
                failureMsg.append(String.format("入库单号【%s】验证失败：%s；\n", orderId, e.getMessage()));
            }
        }

        // 6. 有验证失败直接抛异常
        if (hasValidateError) {
            throw new ServiceException(String.format("验证失败，导入终止！失败详情：%s", failureMsg.toString()));
        }

        // 7. 执行新增操作
        Map<String, List<OutboundOrderItems>> allItemListMap = new HashMap<>();
        for (Map.Entry<String, OutboundOrders> entry : validMainMap.entrySet()) {
            String orderId = entry.getKey();
            OutboundOrders mainDO = entry.getValue();
            List<OutboundOrderItems> itemDOList = validItemMap.get(orderId);

            // 新增主单
            outboundOrdersMapper.insertOutboundOrders(mainDO);
            totalMainSuccess++;
            successMsg.append(String.format("入库单号【%s】已新增；\n", orderId));

            // 插入明细
            if (!CollectionUtils.isEmpty(itemDOList)) {
                int itemSuccess = outboundOrderItemsMapper.batchInsertOutboundOrderItems(itemDOList);
                totalItemSuccess += itemSuccess;
                int itemFail = itemDOList.size() - itemSuccess;
                totalItemFailure += itemFail;

                successMsg.append(String.format("入库单号【%s】成功导入%d条物料明细（已合并相同库存维度）；\n", orderId, itemSuccess));
                if (itemFail > 0) {
                    failureMsg.append(String.format("入库单号【%s】有%d条物料明细导入失败；\n", orderId, itemFail));
                }

                allItemListMap.put(orderId, itemDOList);
            }
        }

        // 8. 异步执行库存扣减和无库位明细拆分
        CompletableFuture.runAsync(() -> {
            try {
                for (Map.Entry<String, List<OutboundOrderItems>> entry : allItemListMap.entrySet()) {
                    List<OutboundOrderItems> itemList = entry.getValue();
                    // 执行库存扣减
                    Map<String, List<Map<String, Object>>> deductRecordMap = deductInventory(itemList, operId, now);

                    // 处理无库位明细拆分
                    boolean hasNoLocationItem = itemList.stream()
                            .anyMatch(item -> StringUtils.isBlank(item.getLocationId()));
                    if (hasNoLocationItem && !deductRecordMap.isEmpty()) {
                        handleNoLocationItemSplit(itemList, deductRecordMap, operId, now);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        // 9. 结果汇总
        if (totalMainFailure > 0 || totalItemFailure > 0) {
            String finalFailureMsg = String.format(
                    "导入结果：成功新增%d个入库单，失败%d个；成功导入%d条明细（已合并相同库存维度），失败%d条。失败详情：%s",
                    totalMainSuccess, totalMainFailure, totalItemSuccess, totalItemFailure, failureMsg.toString()
            );
            throw new ServiceException(finalFailureMsg);
        } else {
            String finalSuccessMsg = String.format(
                    "恭喜您，数据已全部导入成功！共新增%d个入库单，成功导入%d条物料明细（已合并相同库存维度）。详情：%s",
                    totalMainSuccess, totalItemSuccess, successMsg.toString()
            );
            return finalSuccessMsg;
        }
    }

    /**
     * 处理无库位明细拆分（适配新的合并维度）
     */
    private void handleNoLocationItemSplit(List<OutboundOrderItems> itemList,
                                           Map<String, List<Map<String, Object>>> deductRecordMap,
                                           String operId, Date now) {
        List<OutboundOrderItems> newValidItemList = new ArrayList<>();
        Set<String> orderIdSet = new HashSet<>();

        for (OutboundOrderItems item : itemList) {
            String itemId = item.getId();
            List<Map<String, Object>> deductRecords = deductRecordMap.get(itemId);
            if (CollectionUtils.isEmpty(deductRecords)) continue;

            orderIdSet.add(item.getOutboundOrderId());

            // 直接遍历合并后的记录（已按物料+库存类型+库位ID合并）
            for (Map<String, Object> rec : deductRecords) {
                String inventoryId = (String) rec.get("inventoryId");
                String locId = (String) rec.get("locationId");
                Long validQty = (Long) rec.get("deductQty");

                if (validQty <= 0 || StringUtils.isBlank(inventoryId)) continue;

                OutboundOrderItems newItem = new OutboundOrderItems();
                BeanUtils.copyProperties(item, newItem);
                newItem.setId(UUID.randomUUID().toString().replace("-", ""));
                newItem.setLocationId(locId);
                newItem.setActualQuantity(validQty);
                newItem.setInventoryId(inventoryId);
                newItem.setCreateBy(operId);
                newItem.setCreateTime(now);
                newItem.setUpdateBy(operId);
                newItem.setUpdateTime(now);
                newValidItemList.add(newItem);
            }
        }

        // 删除临时明细
        for (String orderId : orderIdSet) {
            outboundOrderItemsMapper.deleteOutboundOrderItemsByOrderId(orderId);
        }

        // 批量插入有效明细（插入前再次合并）
        List<OutboundOrderItems> mergedNewItems = mergeSameInventoryItems(newValidItemList);
        if (!mergedNewItems.isEmpty()) {
            outboundOrderItemsMapper.batchInsertOutboundOrderItems(mergedNewItems);

            // 生成日志
            List<OutboundOrderLog> logList = new ArrayList<>();
            for (OutboundOrderItems newItem : mergedNewItems) {
                OutboundOrderLog log = new OutboundOrderLog();
                BeanUtils.copyProperties(newItem, log);
                log.setOrderId(newItem.getOutboundOrderId());
                log.setItemStatus(3L);
                logList.add(log);
            }
            outboundOrderLogMapper.batchOutboundOrderLog(logList);
        }
    }

    // ========== 预加载映射辅助方法 ==========
    private Map<String, String> loadSapToMaterialIdMap() {
        List<Materials> materialsList = materialsService.selectMaterialsList(new Materials());
        if (CollectionUtils.isEmpty(materialsList)) {
            return Collections.emptyMap();
        }
        return materialsList.stream()
                .filter(m -> StringUtils.isNotBlank(m.getSapNo()))
                .collect(Collectors.toMap(
                        m -> m.getSapNo().trim(),
                        Materials::getId,
                        (k1, k2) -> k1
                ));
    }

    private Map<String, String> loadLocationNameToIdMap() {
        StorageLocations query = new StorageLocations();
        query.setIsUsed(1L);
        List<StorageLocations> locationList = storageLocationsService.selectStorageLocationsList(query);
        if (CollectionUtils.isEmpty(locationList)) {
            return Collections.emptyMap();
        }
        return locationList.stream()
                .filter(l -> StringUtils.isNotBlank(l.getLocationCode()))
                .collect(Collectors.toMap(
                        l -> l.getLocationCode().trim(),
                        StorageLocations::getId,
                        (k1, k2) -> k1
                ));
    }

    private Map<String, String> loadOwnerNameToIdMap() {
        List<Owners> ownerList = ownersService.selectOwnersList(new Owners());
        if (CollectionUtils.isEmpty(ownerList)) {
            return Collections.emptyMap();
        }
        return ownerList.stream()
                .filter(o -> StringUtils.isNotBlank(o.getOwnerName()))
                .collect(Collectors.toMap(
                        o -> o.getOwnerName().trim(),
                        Owners::getId,
                        (k1, k2) -> k1
                ));
    }

    /**
     * 加载库存映射Map（按新维度）+ 同步填充全局缓存
     */
    private Map<String, AbstractMap.SimpleEntry<String, Long>> loadInventoryTOIdMap() {
        Inventory inventory = new Inventory();
        inventory.setInventoryStatus(1L);
        inventory.setIsUsed(1L);
        List<Inventory> inventoryList = inventoryService.selectInventoryList(inventory);

        // 清空全局缓存
        InventoryCache.clear();

        if (CollectionUtils.isEmpty(inventoryList)) {
            return Collections.emptyMap();
        }

        return inventoryList.stream()
                .filter(inv -> StringUtils.isNotBlank(inv.getMaterialId())
                        && inv.getInventoryType() != null
                        && StringUtils.isNotBlank(inv.getId()))
                .peek(inv -> {
                    // 同步到全局缓存（按新维度）
                    String key = buildInventoryKey(
                            inv.getMaterialId(),
                            inv.getLocationId(),
                            inv.getInventoryType().toString()
                    );
                    InventoryCache.addInventory(key, inv);
                })
                .collect(Collectors.toMap(
                        inv -> buildInventoryKey(
                                inv.getMaterialId(),
                                inv.getLocationId(),
                                inv.getInventoryType().toString()
                        ),
                        inv -> new AbstractMap.SimpleEntry<>(inv.getId().trim(), Optional.ofNullable(inv.getQuantity()).orElse(0L)),
                        (k1, k2) -> k1,
                        HashMap::new
                ));
    }
}