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.util.CollectionUtils;

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

/**
 * 出库单主Service业务层处理
 * 最终适配版：
 * 1. 合并维度「物料ID+库存类型+库位ID」，支持相同维度多库存ID遍历扣减
 * 2. 库存加载改为按维度收集所有库存，扣减时按数量从多到少排序
 * 3. 无库位明细拆分精准匹配实际扣减的库存ID+数量
 * 4. 缓存与数据库实时同步，解决数据不一致问题
 * 5. 合并/扣减/拆分逻辑完全对齐维度，杜绝数据错乱
 * 6. 核心：扣减哪个库存ID就提取哪个，精准赋值到明细
 *
 * @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);
        // 默认启用
        outboundOrders.setIsUsed(1L);
        // Ensure orderType is consistent with orderTypeId to avoid mixed-type exports
        outboundOrders.setOrderType(outboundOrders.getOrderTypeId());

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

    /**
     * 修改出库单主（仅保留方法定义，移除更新逻辑）
     *
     * @param outboundOrders 出库单主
     * @return 结果
     */
    @Transactional
    @Override
    public int updateOutboundOrders(OutboundOrders outboundOrders) {
        outboundOrdersMapper.deleteOutboundOrderItemsByOrderId(outboundOrders.getId());
        outboundOrderLogMapper.deleteOutboundOrderLogByOrdersId(outboundOrders.getId());

        outboundOrders.setUpdateUserCode(SystemUtils.getUserName());
        outboundOrders.setUpdateTime(DateUtils.getNowDate());
        insertOutboundOrderItems(outboundOrders);
        return outboundOrdersMapper.updateOutboundOrders(outboundOrders);

    }

    /**
     * 批量删除出库单主
     *
     * @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);

        inventoryService.ship(outboundOrderItems);
        return 1;
    }

    /**
     * 核心库存扣减逻辑（精准提取被扣减的库存ID）
     * 核心：扣减哪个库存ID就提取哪个，仅保留实际被扣减的ID
     */
    /**
     * 核心库存扣减逻辑（确保每个明细仅对应一个库存ID）
     */
    private Map<String, List<Map<String, Object>>> deductInventory(List<OutboundOrderItems> outboundOrderItems, String updateUser, Date updateTime) {
        return deductInventory(outboundOrderItems, updateUser, updateTime, false);
    }

    /**
     * 核心库存扣减逻辑（确保每个明细仅对应一个库存ID）
     * 增加参数 ignoreInventoryType：为 true 时在库存分组与扣减时忽略库存类型（用于导入场景）
     */
    private Map<String, List<Map<String, Object>>> deductInventory(List<OutboundOrderItems> outboundOrderItems, String updateUser, Date updateTime, boolean ignoreInventoryType) {
        if (CollectionUtils.isEmpty(outboundOrderItems)) {
            return Collections.emptyMap();
        }

        Map<String, List<Map<String, Object>>> deductRecordMap = new HashMap<>();
        Map<String, List<Inventory>> inventoryGroupMap = this.loadInventoryGroupMap(ignoreInventoryType);
        Map<String, Long> deductQtyMap = this.buildDeductQtyMap(outboundOrderItems, ignoreInventoryType);
        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, ignoreInventoryType)))
                    .map(OutboundOrderItems::getId)
                    .findFirst()
                    .orElse(null);

            Long remainDeductQty = totalDeductQty;
            List<Map<String, Object>> tempDeductRecords = new ArrayList<>();

            if (StringUtils.isBlank(locationId)) {
                String noLocKey = buildInventoryKey(materialId, "", inventoryType);
                remainDeductQty = deductByInventoryGroup(noLocKey, remainDeductQty, updateUser, updateTime, inventoryGroupMap, toUpdateInventoryMap, tempDeductRecords);

                if (remainDeductQty > 0) {
                    List<String> hasLocKeys = inventoryGroupMap.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());

                    hasLocKeys.sort((k1, k2) -> {
                        // 处理k1的最后入库时间：转时间戳，null则用极小值（确保无入库时间的排在最前面）
                        Long time1 = inventoryGroupMap.get(k1).stream()
                                .map(inv -> Optional.ofNullable(inv.getLastInboundTime())
                                        // 无入库时间时，用Long.MIN_VALUE（比所有正常时间戳都小）
                                        .map(Date::getTime) // 将Date转成毫秒级时间戳（Long）
                                        .orElse(Long.MIN_VALUE))
                                .max(Long::compare) // 取该key下所有库存的最晚入库时间（业务合理）
                                .orElse(Long.MIN_VALUE); // 若该key无库存数据，也兜底为极小值

                        // 处理k2的最后入库时间（逻辑同k1）
                        Long time2 = inventoryGroupMap.get(k2).stream()
                                .map(inv -> Optional.ofNullable(inv.getLastInboundTime())
                                        .map(Date::getTime)
                                        .orElse(Long.MIN_VALUE))
                                .max(Long::compare)
                                .orElse(Long.MIN_VALUE);
                        return Long.compare(time1, time2);
                    });

                    for (String hasLocKey : hasLocKeys) {
                        if (remainDeductQty <= 0) break;
                        remainDeductQty = deductByInventoryGroup(hasLocKey, remainDeductQty, updateUser, updateTime, inventoryGroupMap, toUpdateInventoryMap, tempDeductRecords);
                    }
                }
            } else {
                String targetKey = buildInventoryKey(materialId, locationId, inventoryType);
                remainDeductQty = deductByInventoryGroup(targetKey, remainDeductQty, updateUser, updateTime, inventoryGroupMap, toUpdateInventoryMap, tempDeductRecords);
            }

            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));
                }
                // 使用可用库存（quantity - lockedQuantity）计算后，将“预占/锁定量”进行调整（沿用原来逐步扣减的遍历逻辑）
                Long currentLocked = Optional.ofNullable(lastInv.getLockedQuantity()).orElse(0L);
                lastInv.setLockedQuantity(currentLocked + remainDeductQty);
                lastInv.setInventoryStatus(1L);
                lastInv.setUpdateBy(updateUser);
                lastInv.setUpdateTime(updateTime);
                toUpdateInventoryMap.put(lastInvId, lastInv);

                lastRecord.put("deductQty", (Long) lastRecord.get("deductQty") + remainDeductQty);
                remainDeductQty = 0L;
            }

            if (itemId != null && !tempDeductRecords.isEmpty()) {
                deductRecordMap.put(itemId, tempDeductRecords);
            }

            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);
            needUpdateList.forEach(inv -> {
                String cacheKey = buildInventoryKey(inv.getMaterialId(), inv.getLocationId(), inv.getInventoryType().toString());
                InventoryCache.removeInventory(cacheKey, inv.getId());
                InventoryCache.addInventory(cacheKey, inv);
            });
        }

        // 核心修改：不再拼接ID，而是直接拆分为单ID明细
        splitToSingleInvIdItems(outboundOrderItems, deductRecordMap, updateUser, updateTime);

        return deductRecordMap;
    }
    // splitToSingleInvIdItems is defined below (keeps single implementation with isUsed set)

    private void splitToSingleInvIdItems(List<OutboundOrderItems> oldItems, Map<String, List<Map<String, Object>>> deductRecordMap, String updateUser, Date updateTime) {
        if (CollectionUtils.isEmpty(oldItems) || CollectionUtils.isEmpty(deductRecordMap)) {
            return;
        }

        List<OutboundOrderItems> newSingleItems = new ArrayList<>();
        Set<String> orderIdSet = new HashSet<>();

        for (OutboundOrderItems oldItem : oldItems) {
            String oldItemId = oldItem.getId();
            List<Map<String, Object>> deductRecords = deductRecordMap.get(oldItemId);
            if (CollectionUtils.isEmpty(deductRecords)) {
                continue;
            }

            orderIdSet.add(oldItem.getOutboundOrderId());
            // 每一条扣减记录对应一个新明细（单库存ID）
            for (Map<String, Object> rec : deductRecords) {
                String inventoryId = (String) rec.get("inventoryId");
                String locationId = (String) rec.get("locationId");
                Long deductQty = (Long) rec.get("deductQty");

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

                OutboundOrderItems newItem = new OutboundOrderItems();
                BeanUtils.copyProperties(oldItem, newItem);
                newItem.setId(UUID.randomUUID().toString().replace("-", "")); // 新主键
                newItem.setInventoryId(inventoryId); // 仅单个库存ID（无逗号）
                newItem.setLocationId(locationId);   // 同步库位
                newItem.setActualQuantity(deductQty); // 仅当前库存ID的扣减数量
                newItem.setUpdateBy(updateUser);
                newItem.setUpdateTime(updateTime);

                // 记录出库事务日志：获取对应库存并调用库存服务的日志方法
                try {
                    Inventory inv = inventoryMapper.selectInventoryById(inventoryId);
                    if (inv != null) {
                        inventoryService.createInventoryOutboundLog(inv, newItem, deductQty, updateUser, updateTime);
                    }
                } catch (Exception ignore) {
                    // 保持原有流程稳定性，记录日志失败不应阻塞扣减流程
                }

                newSingleItems.add(newItem);
            }
        }

        // 删除原明细，插入拆分后的单ID明细
        if (!orderIdSet.isEmpty() && !newSingleItems.isEmpty()) {
            // 1. 删除原明细
            orderIdSet.forEach(orderId -> outboundOrderItemsMapper.deleteOutboundOrderItemsByOrderId(orderId));
            // 2. 插入单ID明细
            outboundOrderItemsMapper.batchInsertOutboundOrderItems(newSingleItems);
            // 3. 更新日志（同步单ID明细）
            List<OutboundOrderLog> logList = newSingleItems.stream().map(item -> {
                OutboundOrderLog log = new OutboundOrderLog();
                BeanUtils.copyProperties(item, log);
                log.setOrderId(item.getOutboundOrderId());
                log.setItemStatus(1L);
                // 默认启用
                log.setIsUsed(1L);
                return log;
            }).collect(Collectors.toList());
            outboundOrderLogMapper.batchOutboundOrderLog(logList);
        }
    }

    /**
     * 为所有明细回填实际被扣减的inventoryId（仅保留被扣减的ID）
     * @param items 出库明细列表
     * @param deductRecordMap 扣减记录Map（itemId → 实际扣减的库存ID列表）
     */
    private void fillInventoryIdToItems(List<OutboundOrderItems> items, Map<String, List<Map<String, Object>>> deductRecordMap) {
        if (CollectionUtils.isEmpty(items) || CollectionUtils.isEmpty(deductRecordMap)) {
            return;
        }

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

            // 仅收集当前明细实际被扣减的库存ID（去重，无冗余）
            List<String> deductedInventoryIds = deductRecords.stream()
                    .map(rec -> (String) rec.get("inventoryId"))
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.toList());

            // 仅赋值实际被扣减的库存ID
            if (!deductedInventoryIds.isEmpty()) {
                item.setInventoryId(String.join(",", deductedInventoryIds));
                // 同步更新数据库，确保持久化
                outboundOrderItemsMapper.updateOutboundOrderItems(item);
            }
        }
    }

    /**
     * 按维度分组扣减库存（核心：扣哪个库存ID就记录哪个）
     * @param inventoryKey 维度Key
     * @param deductQty 待扣减数量
     * @param tempDeductRecords 仅记录实际被扣减的库存ID
     * @return 剩余未扣减数量
     */
    private Long deductByInventoryGroup(String inventoryKey, Long deductQty, String updateUser, Date updateTime,
                                        Map<String, List<Inventory>> inventoryGroupMap,
                                        Map<String, Inventory> toUpdateInventoryMap,
                                        List<Map<String, Object>> tempDeductRecords) {
        List<Inventory> invList = inventoryGroupMap.getOrDefault(inventoryKey, new ArrayList<>());
        // 如果该维度下没有任何库存，按新的策略应直接报错（不再新增库存）
        if (CollectionUtils.isEmpty(invList)) {
            throw new ServiceException(String.format("维度[%s]无可用库存，请确认库存是否存在", inventoryKey));
        }

        // 同维度库存按数量从多到少排序（优先扣减数量多的）
        invList.sort((a, b) -> {
            Long qtyA = Optional.ofNullable(a.getQuantity()).orElse(0L);
            Long qtyB = Optional.ofNullable(b.getQuantity()).orElse(0L);
            return Long.compare(qtyB, qtyA);
        });

        Long remainDeduct = deductQty;
        for (Inventory inv : invList) {
            if (remainDeduct <= 0) break;
            String currentInvId = inv.getId(); // 提取当前要扣减的库存ID
            Long currentQty = Optional.ofNullable(inv.getQuantity()).orElse(0L);
            Long currentLocked = Optional.ofNullable(inv.getLockedQuantity()).orElse(0L);
            // 可用库存 = quantity - lockedQuantity
            Long available = Math.max(0L, currentQty - currentLocked);
            Long canDeduct = Math.min(remainDeduct, available); // 实际可预占/锁定数量

            if (canDeduct <= 0) {
                continue; // 当前库存无可用量，跳过
            }

            // 更新库存对象：不直接减少实际库存quantity，而是调整锁定数量（预占）
            inv.setLockedQuantity(currentLocked + canDeduct);
            inv.setUpdateBy(updateUser);
            inv.setUpdateTime(updateTime);
            toUpdateInventoryMap.put(currentInvId, inv);

            // 仅记录当前被扣减（预占/锁定）的库存ID（扣哪个记哪个）
            Map<String, Object> record = buildDeductRecord(inv, inv.getInventoryType().toString(), canDeduct);
            tempDeductRecords.add(record);

            remainDeduct -= canDeduct;
        }

        return remainDeduct;
    }

    /**
     * 构建扣减记录（仅包含实际被扣减的库存ID）
     */
    private Map<String, Object> buildDeductRecord(Inventory inv, String inventoryType, Long deductQty) {
        Map<String, Object> record = new HashMap<>();
        record.put("inventoryId", inv.getId()); // 仅记录实际被扣减的库存ID
        record.put("locationId", inv.getLocationId());
        record.put("materialId", inv.getMaterialId());
        record.put("inventoryType", inventoryType);
        record.put("deductQty", deductQty); // 该库存ID实际被扣减的数量
        return record;
    }

    /**
     * 预加载库存分组Map（按维度Key分组）
     */
    private Map<String, List<Inventory>> loadInventoryGroupMap() {
        Inventory query = new Inventory();
        query.setInventoryStatus(1L); // 只查可用状态库存
        query.setIsUsed(1L);          // 只查启用的库存
        List<Inventory> allInventory = inventoryMapper.selectInventoryList(query);

        Map<String, List<Inventory>> inventoryGroupMap = new LinkedHashMap<>();
        for (Inventory inv : allInventory) {
            String key = buildInventoryKey(
                    inv.getMaterialId(),
                    inv.getLocationId(),
                    Optional.ofNullable(inv.getInventoryType()).map(String::valueOf).orElse("")
            );
            inventoryGroupMap.computeIfAbsent(key, k -> new ArrayList<>()).add(inv);
        }
        return inventoryGroupMap;
    }

    /**
     * 预加载库存分组Map（按维度Key分组）
     * 可选是否忽略库存类型（用于导入时不按orderType过滤库存）
     */
    private Map<String, List<Inventory>> loadInventoryGroupMap(boolean ignoreInventoryType) {
        Inventory query = new Inventory();
        query.setInventoryStatus(1L); // 只查可用状态库存
        query.setIsUsed(1L);          // 只查启用的库存
        List<Inventory> allInventory = inventoryMapper.selectInventoryList(query);

        Map<String, List<Inventory>> inventoryGroupMap = new LinkedHashMap<>();
        for (Inventory inv : allInventory) {
            String invTypePart = ignoreInventoryType ? "" : Optional.ofNullable(inv.getInventoryType()).map(String::valueOf).orElse("");
            String key = buildInventoryKey(
                    inv.getMaterialId(),
                    inv.getLocationId(),
                    invTypePart
            );
            inventoryGroupMap.computeIfAbsent(key, k -> new ArrayList<>()).add(inv);
        }
        return inventoryGroupMap;
    }

    /**
     * 构建扣减数量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;
    }

    /**
     * 构建扣减数量Map（可选择是否忽略库存类型）
     * Key规则：物料ID_库位ID_库存类型（inventoryType 可为空）
     */
    private Map<String, Long> buildDeductQtyMap(List<OutboundOrderItems> items, boolean ignoreInventoryType) {
        Map<String, Long> deductQtyMap = new HashMap<>();
        for (OutboundOrderItems item : items) {
            String key = buildDeductKey(item, ignoreInventoryType);
            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("")
        );
    }

    /**
     * 构建明细的扣减Key（可选择是否忽略库存类型）
     */
    private String buildDeductKey(OutboundOrderItems item, boolean ignoreInventoryType) {
        String invType = ignoreInventoryType ? "" : Optional.ofNullable(item.getInventoryType()).map(String::valueOf).orElse("");
        return buildInventoryKey(
                item.getMaterialId(),
                item.getLocationId(),
                invType
        );
    }

    /**
     * 构建库存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("")
        );
    }

    @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("库存被修改请重新确认");
        }

        // 合并相同维度明细（仅合并数量，不拼接库存ID）
        List<OutboundOrderItems> mergedItemsList = mergeSameInventoryItems(outboundOrderItemsList);

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

        // 批量插入出库单明细
        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());
            // 默认启用
            log.setIsUsed(1L);
            outboundOrderLogs.add(log);
            // 仅拆分实际被扣减的库存ID
            if (StringUtils.isNotBlank(items.getInventoryId())) {
                String[] invIds = items.getInventoryId().split(",");
                for (String invId : invIds) {
                    if (StringUtils.isNotBlank(invId)) {
                        inventoryIds.add(invId.trim());
                    }
                }
            }
        }

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

    /**
     * 合并相同维度明细（仅合并数量，不拼接库存ID）
     */
    private List<OutboundOrderItems> mergeSameInventoryItems(List<OutboundOrderItems> itemsList) {
        if (CollectionUtils.isEmpty(itemsList)) {
            return Collections.emptyList();
        }

        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, List<Inventory>> inventoryGroupMap = loadInventoryGroupMap();

        // 4. 按入库单号 + 订单类型 分组（复合键），保证不同类型即使 orderId 相同也会生成不同主单
        Map<String, List<OutboundTemplateVO>> orderGroupMap = inboundOrdersList.stream()
                .filter(vo -> StringUtils.isNotBlank(vo.getOrderId()))
                .collect(Collectors.groupingBy(vo -> {
                    String typePart = StringUtils.isNotBlank(vo.getOrderTypeId()) ? vo.getOrderTypeId().trim() : String.valueOf(orderType);
                    return vo.getOrderId().trim() + "_" + typePart;
                }));

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

            try {
                OutboundTemplateVO firstVO = voList.get(0);
                String orderId = firstVO.getOrderId() != null ? firstVO.getOrderId().trim() : "";
                String typeKey = StringUtils.isNotBlank(firstVO.getOrderTypeId()) ? firstVO.getOrderTypeId().trim() : String.valueOf(orderType);
                // 检查出库单是否已存在（按 orderId + orderType 判重）
                OutboundOrders outboundOrdersQuery = new OutboundOrders();
                outboundOrdersQuery.setOrderId(orderId);
                outboundOrdersQuery.setOrderTypeId(typeKey);
                List<OutboundOrders> existMains = outboundOrdersMapper.selectOutboundOrdersList(outboundOrdersQuery);

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

                // 构建新出库单主数据
                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(1L);
                mainDO.setCreateBy(operId);
                mainDO.setCreateTime(now);
                // 使用实际类型（VO 中的 orderTypeId 优先，否则使用导入参数）
                String actualOrderType = StringUtils.isNotBlank(firstVO.getOrderTypeId()) ? firstVO.getOrderTypeId().trim() : String.valueOf(orderType);
                mainDO.setOrderTypeId(actualOrderType);
                mainDO.setOrderType(actualOrderType);
                mainDO.setCreateUserCode(operId);
                mainDO.setUpdateBy(operId);
                mainDO.setUpdateTime(now);
                mainDO.setUpdateUserCode(operId);
                mainDO.setSortNo(Optional.ofNullable(mainDO.getSortNo()).orElse(0L));
                mainDO.setIsImport(0L);
                // 默认启用
                mainDO.setIsUsed(1L);
                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().replace("-", ""));
                    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(1L);
                    itemDO.setShippedAt(mainDO.getInboundDate());
                    // 默认启用
                    itemDO.setIsUsed(1L);

                    // 物料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() : "";
                    if (StringUtils.isNotBlank(locationName)) {
                        String locationId = locationNameToIdMap.get(locationName);
                        if (StringUtils.isBlank(locationId)) {
                            throw new ServiceException(String.format("入库单号【%s】第%d条明细的库位【%s】不存在",
                                    orderId, lineNo, locationName));
                        }
                        itemDO.setLocationId(locationId);
                    }

                    // 库存类型设置
                    itemDO.setInventoryType(Integer.parseInt(actualOrderType));
                    // 实际出库数量
                    itemDO.setActualQuantity(Optional.ofNullable(vo.getActualQuantity()).orElse(0L));

                    itemDOList.add(itemDO);
                }

                // 合并相同维度明细
                List<OutboundOrderItems> mergedItemList = mergeSameInventoryItems(itemDOList);
                // 使用 compositeKey 存储，保证同 orderId 不同类型不会覆盖
                validMainMap.put(compositeKey, mainDO);
                validItemMap.put(compositeKey, mergedItemList);

            } catch (Exception e) {
                hasValidateError = true;
                totalMainFailure++;
                totalItemFailure += voList.size();
                // 尽量从 firstVO 中取 orderId 显示错误信息
                String errOrderId = (voList != null && !voList.isEmpty() && voList.get(0).getOrderId() != null) ? voList.get(0).getOrderId() : "";
                failureMsg.append(String.format("入库单号【%s】验证失败：%s；\n", errOrderId, 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 compositeKey = entry.getKey();
            OutboundOrders mainDO = entry.getValue();
            List<OutboundOrderItems> itemDOList = validItemMap.get(compositeKey);

            // 新增主单
            outboundOrdersMapper.insertOutboundOrders(mainDO);
            totalMainSuccess++;
            successMsg.append(String.format("出库单号【%s】(类型:%s)已新增；\n", mainDO.getOrderId(), Optional.ofNullable(mainDO.getOrderTypeId()).orElse("")));

            // 插入明细
            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", mainDO.getOrderId(), itemSuccess));
                if (itemFail > 0) {
                    failureMsg.append(String.format("出库单号【%s】有%d条物料明细导入失败；\n", mainDO.getOrderId(), itemFail));
                }

                allItemListMap.put(compositeKey, itemDOList);
            }
        }

        // ========== 关键修改：移除 CompletableFuture 异步执行，改为同步调用 ==========
        // 8. 同步执行库存扣减和无库位明细拆分（仅处理实际被扣减的库存ID）
        try {
            for (Map.Entry<String, List<OutboundOrderItems>> entry : allItemListMap.entrySet()) {
                List<OutboundOrderItems> itemList = entry.getValue();
                // 执行库存扣减（精准提取被扣减的库存ID）
                // 导入场景：orderType 不参与库存查询，因此传入 ignoreInventoryType = true
                Map<String, List<Map<String, Object>>> deductRecordMap = deductInventory(itemList, operId, now, true);
                // 处理无库位明细拆分（仅拆分实际被扣减的库存ID）
                handleNoLocationItemSplit(itemList, deductRecordMap, operId, now);
            }
        } catch (Exception e) {
            // 可添加日志记录异常
        }

        // 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;
        }
    }

    @Override
    public List<OutboundTemplateVO> selectOutboundOrdersExportList(OutboundOrders outboundOrders) {
        List<OutboundTemplateVO> list = outboundOrdersMapper.selectOutboundOrdersExportList(outboundOrders);
        return list;
    }

    /**
     * 处理无库位明细拆分（仅拆分实际被扣减的库存ID）
     */
    private void handleNoLocationItemSplit(List<OutboundOrderItems> itemList,
                                           Map<String, List<Map<String, Object>>> deductRecordMap,
                                           String operId, Date now) {
        List<OutboundOrderItems> newItemList = new ArrayList<>();
        Set<String> orderIdSet = new HashSet<>();

        for (OutboundOrderItems item : itemList) {
            if (StringUtils.isNotBlank(item.getLocationId())) {
                continue; // 跳过有库位明细（已赋值实际被扣减的库存ID）
            }
            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 deductQty = (Long) rec.get("deductQty");

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

                OutboundOrderItems newItem = new OutboundOrderItems();
                BeanUtils.copyProperties(item, newItem);
                newItem.setId(UUID.randomUUID().toString().replace("-", ""));
                newItem.setLocationId(locId);
                newItem.setActualQuantity(deductQty);
                newItem.setInventoryId(inventoryId); // 仅赋值实际被扣减的单个库存ID
                newItem.setUpdateBy(operId);
                newItem.setUpdateTime(now);
                newItemList.add(newItem);
            }
        }

        // 删除原无库位明细，插入拆分后的新明细（仅包含实际被扣减的库存ID）
        if (!orderIdSet.isEmpty() && !newItemList.isEmpty()) {
            orderIdSet.forEach(orderId -> outboundOrderItemsMapper.deleteOutboundOrderItemsByOrderId(orderId));
            outboundOrderItemsMapper.batchInsertOutboundOrderItems(newItemList);

            // 生成拆分后的日志（仅包含实际被扣减的库存ID）
            List<OutboundOrderLog> logList = newItemList.stream().map(item -> {
                OutboundOrderLog log = new OutboundOrderLog();
                BeanUtils.copyProperties(item, log);
                log.setOrderId(item.getOutboundOrderId());
                log.setItemStatus(1L);
                // 默认启用
                log.setIsUsed(1L);
                return log;
            }).collect(Collectors.toList());
            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
                ));
    }
}