package cn.iocoder.yudao.module.pms.sap.service;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.pms.controller.admin.iotlockstock.vo.IotLockStockPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotsappickinglist.vo.IotSapPickingListPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotsapstock.vo.IotSapStockPageReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.iotlockstock.IotLockStockDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.iotmaterial.IotMaterialDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.iotsappickinglist.IotSapPickingListDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.iotsapstock.IotSapStockDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.iotsapstocklog.IotSapStockLogDO;
import cn.iocoder.yudao.module.pms.dal.mysql.iotlockstock.IotLockStockMapper;
import cn.iocoder.yudao.module.pms.dal.mysql.iotmaterial.IotMaterialMapper;
import cn.iocoder.yudao.module.pms.dal.mysql.iotmaterial.IotSapMaterialMapper;
import cn.iocoder.yudao.module.pms.dal.mysql.iotsappickinglist.IotSapPickingListMapper;
import cn.iocoder.yudao.module.pms.dal.mysql.iotsapstock.IotSapStockMapper;
import cn.iocoder.yudao.module.pms.dal.mysql.iotsapstocklog.IotSapStockLogMapper;
import cn.iocoder.yudao.module.pms.job.sap.SyncSapStockContext;
import cn.iocoder.yudao.module.pms.sap.vo.*;
import cn.iocoder.yudao.module.supplier.dal.dataobject.product.SupplierDO;
import cn.iocoder.yudao.module.supplier.dal.mysql.product.SupplierMapper;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.saporg.SapOrgApi;
import cn.iocoder.yudao.module.system.api.saporg.dto.SapOrgRespDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.module.pms.framework.config.MultiThreadConfiguration.PMS_THREAD_POOL_TASK_EXECUTOR;

/**
 * SAP 同步接口相关的服务
 */
@Service
@Validated
public class IotSapServiceImpl implements IotSapService {
    private static final Logger log = LoggerFactory.getLogger(IotSapServiceImpl.class);

    @Autowired
    private IotSapMaterialMapper iotSapMaterialMapper;
    @Autowired
    private IotMaterialMapper iotMaterialMapper;
    @Resource(name = PMS_THREAD_POOL_TASK_EXECUTOR)
    private ThreadPoolTaskExecutor pmsThreadPoolTaskExecutor;
    @Autowired
    private SupplierMapper supplierMapper;
    @Autowired
    private SapOrgApi sapOrgApi;
    @Autowired
    private DeptApi deptApi;
    @Autowired
    private IotLockStockMapper iotLockStockMapper;
    @Autowired
    private IotSapStockMapper iotSapStockMapper;
    @Autowired
    private IotSapStockLogMapper iotSapStockLogMapper;
    @Autowired
    private IotSapPickingListMapper iotSapPickingListMapper;

    // 批量操作的分批大小
    private static final int BATCH_SIZE = 1000;
    private static final int LOG_BATCH_SIZE = 2000;

    @Override
    public void processMaterials(List<IotSapMaterialVO> sapMaterials) {
        // SAP接口返回的物料编码 砍掉前端的 00000000
        if (CollUtil.isEmpty(sapMaterials)) {
            return;
        }
        // 1.新增物料
        // 查询所有物料集合A，如果 sapMaterials 中有物料不在A中 新增物料
        Set<String> existingMaterialCodes = new HashSet<>();
        List<IotMaterialDO> existMaterials = TenantUtils.execute(1L, () -> iotMaterialMapper.selectList());
        if (CollUtil.isNotEmpty(existMaterials)) {
            existingMaterialCodes = existMaterials.stream()
                    .filter(material -> ObjUtil.isNotEmpty(material) && StrUtil.isNotBlank(material.getCode()))  // 过滤非空对象和非空code
                    .map(IotMaterialDO::getCode)
                    .collect(Collectors.toSet());
        }
        System.out.println("当前库中已有物料数量：" + existMaterials.size());
        // 找出需要新增的物料（SAP物料编码去掉前导零后，不在现有物料集合中的记录）
        Set<String> finalExistingMaterialCodes = existingMaterialCodes;
        List<IotSapMaterialVO> newMaterials = sapMaterials.stream()
                .filter(sapMaterial -> {
                    // 处理前导零：移除MATNR前的00000000
                    String processedCode = sapMaterial.getMATNR().replaceFirst("^0+", "");
                    return !finalExistingMaterialCodes.contains(processedCode);
                })
                .collect(Collectors.toList());
        System.out.println("需要新添加的物料数量：" + newMaterials.size());
        // 需要新增的物料列表
        List<IotMaterialDO> tobeAddedMaterials = new ArrayList<>();
        if (CollUtil.isNotEmpty(newMaterials)) {
            newMaterials.forEach(nm -> {
                if (StrUtil.isNotBlank(nm.getMATNR())) {
                    IotMaterialDO tempMaterial = new IotMaterialDO();
                    tempMaterial.setCode(nm.getMATNR().replaceFirst("^0+", ""));    // 物料编码需要去除 前导0
                    // todo 根据现有物料组分类 设置物料组
                    tempMaterial.setName(nm.getMAKTX());
                    tempMaterial.setSyncTime(LocalDateTime.now());
                    tobeAddedMaterials.add(tempMaterial);
                }
            });
        }
        if (CollUtil.isNotEmpty(tobeAddedMaterials)) {
            int tempBatchSize = 500;    // 500条记录为一组 多线程新增
            CountDownLatch latch = new CountDownLatch((int)Math.ceil((double)tobeAddedMaterials.size() / tempBatchSize));;
            for (int i = 0; i < tobeAddedMaterials.size(); i += tempBatchSize) {
                List<IotMaterialDO> batchMaterials = tobeAddedMaterials.subList(i, Math.min(i + tempBatchSize, tobeAddedMaterials.size()));
                pmsThreadPoolTaskExecutor.execute(() -> {
                    try {
                        TenantUtils.execute(1L, () -> iotMaterialMapper.insertBatch(batchMaterials));
                    } finally {
                        latch.countDown();
                    }
                });
            }
        }

        // 2.更新已有物料 物料描述可能会变化 (初始化单位)
        // 更新当前库中已经存在的物料的 物料描述
        Map<String, String> sapMaterialPair = createMaterialMap(sapMaterials, existingMaterialCodes);

        Map<String, String> sapMaterialUnitPair = createMaterialUnitMap(sapMaterials, existingMaterialCodes);
        if (CollUtil.isNotEmpty(existMaterials) && CollUtil.isNotEmpty(sapMaterialPair)) {
            existMaterials.forEach(em -> {
                if (sapMaterialPair.containsKey(em.getCode())) {
                    em.setName(sapMaterialPair.get(em.getCode()));
                }
                if (sapMaterialUnitPair.containsKey(em.getCode())) {
                    em.setUnit(sapMaterialUnitPair.get(em.getCode()));
                }
            });
            // 分批多线程更新已有物料数据描述  1000条为一组
            int tempBatchSize = 1000;    // 1000条记录为一组 多线程更新
            CountDownLatch latch = new CountDownLatch((int)Math.ceil((double)existMaterials.size() / tempBatchSize));;
            for (int i = 0; i < existMaterials.size(); i += tempBatchSize) {
                List<IotMaterialDO> batchMaterials = existMaterials.subList(i, Math.min(i + tempBatchSize, existMaterials.size()));
                pmsThreadPoolTaskExecutor.execute(() -> {
                    try {
                        TenantUtils.execute(1L, () -> iotMaterialMapper.updateBatch(batchMaterials));
                    } finally {
                        latch.countDown();
                    }
                });
            }
        }
        // 3.记录接口调用日志
    }

    @Override
    public void processMerchants(List<IotSapMerchantVO> sapMerchants) {
        // 客商数据存储到表 rq_iot_supplier     todo sap中的客商可能会禁用 如果禁用的供应商不返回 可以考虑修改本地供应商的状态
        // 新增记录 查询当前数据库中已经存在的供应商 existSuppliers 与 sapMerchants 供应商对比
        // 如果 existSuppliers 中不包含 sapMerchants 中的客商 则新增客商
        if (CollUtil.isEmpty(sapMerchants)) {
            return;
        }
        Set<String> existingSupplierCodes = new HashSet<>();
        List<SupplierDO> existSuppliers = TenantUtils.execute(1L, () -> supplierMapper.selectList());
        if (CollUtil.isNotEmpty(existSuppliers)) {
            existingSupplierCodes = existSuppliers.stream()
                    .filter(material -> ObjUtil.isNotEmpty(material) && StrUtil.isNotBlank(material.getCode()))  // 过滤非空对象和非空code
                    .map(SupplierDO::getCode)
                    .collect(Collectors.toSet());
        }
        System.out.println("当前库中已有供应商数量：" + existingSupplierCodes.size());
        // 找出需要新增的客商
        Set<String> finalExistingSupplierCodes = existingSupplierCodes;
        List<IotSapMerchantVO> newMerchants = sapMerchants.stream()
                .filter(sapMerchant -> {
                    // 暂时不删除前导0
                    String processedCode = sapMerchant.getCode();
                    return !finalExistingSupplierCodes.contains(processedCode);
                })
                .collect(Collectors.toList());
        System.out.println("需要新添加的客商数量：" + newMerchants.size());
        // 需要新增的物料列表
        List<SupplierDO> tobeAddedMerchants = new ArrayList<>();
        if (CollUtil.isNotEmpty(newMerchants)) {

            newMerchants.forEach(nm -> {
                if (StrUtil.isNotBlank(nm.getCode()) && StrUtil.isNotBlank(nm.getName())) {
                    SupplierDO tempSupplier = new SupplierDO();
                    tempSupplier.setCode(nm.getCode());    // 供应商编码
                    tempSupplier.setName(nm.getName());    // 供应商名称
                    tempSupplier.setDeptId(156L);          // 部门暂时放到顶级部门
                    tempSupplier.setType("2");             // 供应商类型 2民企
                    tempSupplier.setNature("1");           // 供应商性质 1供应商
                    tempSupplier.setStatus(2);             // 状态 2活动
                    tempSupplier.setCustomerNature("supplier");     // 客商性质
                    tempSupplier.setSapCode(nm.getCode());  // sap编码
                    tempSupplier.setSapSyncTime(LocalDateTime.now());
                    tobeAddedMerchants.add(tempSupplier);
                }
            });
        }
        if (CollUtil.isNotEmpty(tobeAddedMerchants)) {
            int tempBatchSize = 500;    // 500条记录为一组 多线程新增
            CountDownLatch latch = new CountDownLatch((int)Math.ceil((double)tobeAddedMerchants.size() / tempBatchSize));;
            for (int i = 0; i < tobeAddedMerchants.size(); i += tempBatchSize) {
                List<SupplierDO> batchMerchants = tobeAddedMerchants.subList(i, Math.min(i + tempBatchSize, tobeAddedMerchants.size()));
                pmsThreadPoolTaskExecutor.execute(() -> {
                    try {
                        TenantUtils.execute(1L, () -> supplierMapper.insertBatch(batchMerchants));
                    } finally {
                        latch.countDown();
                    }
                });
            }
        }
    }

    @Override
    public void processPickingLists(String factoryCode, List<IotSapPickingListHeadVO> pickingListHeads, List<IotSapPickingListItemVO> pickingListItems) {
        // 查询出所有成本中心 对应的 deptId 新增本地库存时 可以匹配
        List<SapOrgRespDTO> costCenters = TenantUtils.execute(1L, () -> sapOrgApi.getSapOrgByType(2));
        // 查询所有工厂
        List<SapOrgRespDTO> factories = TenantUtils.execute(1L, () -> sapOrgApi.getSapOrgByType(1));
        // key成本中心id    value成本中心code
        Map<Long, String> costCenterCodeIdPair = new HashMap<>();
        // key工厂code    value工厂id
        Map<String, Long> factoryIdPair = new HashMap<>();
        // key成本中心code    value成本中心id
        Map<String, Long> costCenterIdPair = new HashMap<>();
        // key成本中心code    value工厂code
        Map<String, String> costCenterFactoryCodePair = new HashMap<>();
        // key成本中心code  value部门id
        Map<String, Long> costCenterCodeDeptIdPair = new HashMap<>();
        // 工厂集合
        factories.forEach(factory -> {
            factoryIdPair.put(factory.getFactoryCode(), factory.getId());
        });
        // 成本中心集合
        costCenters.forEach(center -> {
            costCenterCodeIdPair.put(center.getId(), center.getCostCenterCode());
            costCenterIdPair.put(center.getCostCenterCode(), center.getId());
            costCenterFactoryCodePair.put(center.getCostCenterCode(), center.getFactoryCode());
        });

        // 查询所有部门
        List<DeptRespDTO> depts = TenantUtils.execute(1L, () -> deptApi.getDeptList());
        // 筛选出所有设置了成本中心的部门
        depts.forEach(dept -> {
            Set<Long> costCenterIds = dept.getCostCenterIds();
            // 循环遍历 costCenterIds
            if (CollUtil.isNotEmpty(costCenterIds)) {
                costCenterIds.forEach(center -> {
                    if (costCenterCodeIdPair.containsKey(center)) {
                        // 当前部门设置的成本中心
                        String costCenterCode = costCenterCodeIdPair.get(center);
                        costCenterCodeDeptIdPair.put(costCenterCode, dept.getId());
                    }
                });
            }
        });
        // 将集合 pickingListHeads 设置成 Map<String, String> key领料单号 value成本中心
        Map<String, String> pickingCodeCostCenterPair = new HashMap<>();
        Map<String, String> notExistCostCenters = new HashMap<>();
        // 遍历主表
        pickingListHeads.forEach(head -> {
            pickingCodeCostCenterPair.put(head.getZLTNO(), head.getKOSTL());
            // 找出本地没有维护 过的成本中心
            String originCostCenterCode = head.getKOSTL();
            if (!costCenterIdPair.containsKey(originCostCenterCode)) {
                String tempMsg = "当前系统没有维护的成本中心：-工厂- " + head.getWERKS()
                        + " -工厂名称- " + head.getTXTMD() + " -领料/退料编号- " + head.getZLTNO() + " -成本中心 编码- " + head.getKOSTL() + " -成本中心描述- " + head.getLTEXT();
                notExistCostCenters.put(head.getKOSTL(), tempMsg);

            }
        });
        // 打印不存在的成本中心集合 notExistCostCenters
        if (CollUtil.isNotEmpty(notExistCostCenters)) {
            notExistCostCenters.forEach((k,v) -> {
                System.out.println(v);
            });
        }

        // 领料单表不合并物料数量、单价 todo(审核入本地库时 合并相同成本中心下的物料数量 单价)
        List<IotSapPickingListDO> pickingLists = new ArrayList<>();
        // 本地库存初始化时合并相同成本中心的 物料数量 单价
        List<IotLockStockDO> tobeAddedLockStocks = new ArrayList<>();
        // 需要更新的本地库存集合
        List<IotLockStockDO> tobeUpdatedLockStocks = new ArrayList<>();
        // 本地库存初始化 成本中心+物料编码，确定唯一明细记录，合并数量、单价
        // 合并的物料数量集合
        Map<String, BigDecimal> mergeMaterialQuantities = new HashMap<>();
        // 合并的物料单价
        Map<String, BigDecimal> mergeMaterialUnitPrices = new HashMap<>();
        // 合并的物料总价
        Map<String, BigDecimal> mergeMaterialTotalPrices = new HashMap<>();
        Map<String, IotSapPickingListItemVO> basicMaterials = new HashMap<>();
        // 首先查询领料单表中已经存在的领料单号 集合 existPickingNumbers
        // 筛选出 pickingListItems 中不在集合 existPickingNumbers 中的数据集合 A 新增到 领料单表
        // 本地库存表也要基于集合A 与 本地已有的库存数据 合并总数量 与 平均价 既有新增的物料 也有需要更新的物料
        IotSapPickingListPageReqVO reqVO = new IotSapPickingListPageReqVO();
        List<IotSapPickingListDO> existPickingList = TenantUtils.execute(1L, () -> iotSapPickingListMapper.selectList(reqVO));
        Set<String> distinctNumbers = new HashSet<>();
        if (CollUtil.isNotEmpty(existPickingList)) {
            distinctNumbers = existPickingList.stream()
                    .map(IotSapPickingListDO::getNumber)    // 提取 number 属性
                    .filter(Objects::nonNull)               // 过滤 null
                    .collect(Collectors.toSet());
        }
        Set<String> finalDistinctNumbers = distinctNumbers;
        // 以 成本中心-物料编码 维度统计 物料总数量 物料总金额

        // 先处理新增的本地库存 筛选出不在本地领料单集合中的数据 B 与 在本地领料单集合中的数据 C
        // 如果没有不在本地库存中的领料单 则不需要新增或更新本地库存
        Set<Long> relativeCostCenterIds = new HashSet<>();
        Set<String> relativeMaterialCodes = new HashSet<>();
        // 新的领料单包含的 成本中心-物料 集合
        Set<String> relativeUniqueKeys = new HashSet<>();
        pickingListItems.stream()
                .filter(item -> StrUtil.isNotBlank(item.getZLTNO()) && !finalDistinctNumbers.contains(item.getZLTNO()))
                .forEach(item -> {
            if (StrUtil.isNotBlank(item.getMATNR())
                    && StrUtil.isNotBlank(item.getZLTNO()) && pickingCodeCostCenterPair.containsKey(item.getZLTNO())) {
                // 领料单号<->成本中心编码
                String costCenterCode = pickingCodeCostCenterPair.get(item.getZLTNO());
                if (StrUtil.isNotBlank(costCenterCode)) {
                    if (costCenterIdPair.containsKey(costCenterCode)) {
                        relativeCostCenterIds.add(costCenterIdPair.get(costCenterCode));
                        relativeUniqueKeys.add(StrUtil.join("~~", costCenterIdPair.get(costCenterCode), item.getMATNR()));
                        System.out.println("costCenterId 唯一key:" + StrUtil.join("~~", costCenterIdPair.get(costCenterCode), item.getMATNR()));
                    }
                    relativeMaterialCodes.add(item.getMATNR());
                    String uniqueKey = StrUtil.join("~~", costCenterCode, item.getMATNR());
                    System.out.println("唯一key:" + uniqueKey);
                    // 当前明细行 物料数量
                    BigDecimal currentQuantity = ObjUtil.isEmpty(item.getMENGE()) ? BigDecimal.ZERO : item.getMENGE();
                    // 当前明细行 物料单价 移动平均价或标准价
                    BigDecimal currentUnitPrice = ObjUtil.isEmpty(item.getVERPR()) ? BigDecimal.ZERO : item.getVERPR();
                    // 当前明细行 物料总价
                    BigDecimal currentTotalPrice = currentQuantity.multiply(currentUnitPrice);
                    // 相同物料总数量
                    if (mergeMaterialQuantities.containsKey(uniqueKey)) {
                        BigDecimal tempQuantity = mergeMaterialQuantities.get(uniqueKey);
                        mergeMaterialQuantities.put(uniqueKey, tempQuantity.add(currentQuantity));
                    } else {
                        mergeMaterialQuantities.put(uniqueKey, currentQuantity);
                    }
                    // 相同物料 单价总和
                    if (mergeMaterialTotalPrices.containsKey(uniqueKey)) {
                        BigDecimal tempTotalPrice = mergeMaterialTotalPrices.get(uniqueKey);
                        // 查询已经暂存的 物料总数量
                        mergeMaterialTotalPrices.put(uniqueKey, tempTotalPrice.add(currentTotalPrice));
                    } else {
                        mergeMaterialTotalPrices.put(uniqueKey, currentTotalPrice);
                    }
                    // 基准物料集合
                    if (!basicMaterials.containsKey(uniqueKey)) {
                        basicMaterials.put(uniqueKey, item);
                    }
                }
            }
        });
        System.out.println(factoryCode + "基准物料集合容量：" + basicMaterials.size());
        // 根据筛选出的不在本地领料单库的领料单 组装的 ‘成本中心-物料编码’ 集合 查询本地库存中已有的物料库存 以 成本中心-物料编码 为唯一键 统计物料总数量 物料总金额
        Set<String> existUniqueKey = new HashSet<>();
        // 新领料单物料在本地库存中不存在 成本中心-物料 集合 新增物料本地库存
        Set<String> tobeAddedUniqueKeys = new HashSet<>();
        if (CollUtil.isNotEmpty(relativeCostCenterIds) && CollUtil.isNotEmpty(relativeMaterialCodes)) {
            IotLockStockPageReqVO lockStockReqVO = new IotLockStockPageReqVO();
            lockStockReqVO.setCostCenterIds(relativeCostCenterIds);
            lockStockReqVO.setMaterialCodes(relativeMaterialCodes);
            List<IotLockStockDO> existLockStocks = TenantUtils.execute(1L, () -> iotLockStockMapper.selectList(lockStockReqVO));
            System.out.println("本地库存已经存在的新领料单 成本中心-物料：" + existLockStocks.size());
            // 新增的领料单包含的物料 如果在本地库存中已经存在，需要合并计算总数量 平均单价
            // 如果新增的领料单包含的物料在本地库存中不存在，单向合并计算总数量 平均单价后 新增本地库存
            if (CollUtil.isNotEmpty(existLockStocks)) {
                existLockStocks.forEach(lockStock -> {
                    existUniqueKey.add(StrUtil.join("~~", lockStock.getCostCenterId(), lockStock.getMaterialCode()));
                    System.out.println("当前库中已经存在的成本中心-物料key：" + StrUtil.join("~~", lockStock.getCostCenterId(), lockStock.getMaterialCode()));
                });
            }
        }
        // 存在新增的领料单 找出新增领料单物料不在已有本地库存中的数据
        if (CollUtil.isNotEmpty(relativeUniqueKeys)) {
            relativeUniqueKeys.forEach(key -> {
                if (!existUniqueKey.contains(key)) {
                    // 新增的领料单 成本中心-物料编码 在本地库存不存在
                    tobeAddedUniqueKeys.add(key);
                    System.out.println("新增的领料单 成本中心-物料编码：" + key);
                }
            });
        }

        // 以 成本中心-物料编码 维度统计 物料平均单价=物料总金额/物料总数量
        if (CollUtil.isNotEmpty(mergeMaterialTotalPrices)) {
            mergeMaterialTotalPrices.forEach((k,v) -> {
                // k成本中心code+物料编码    v物料总价
                if (mergeMaterialQuantities.containsKey(k)) {
                    BigDecimal totalQuantity = mergeMaterialQuantities.get(k);
                    if (totalQuantity.compareTo(BigDecimal.ZERO) > 0) {
                        BigDecimal unitPrice = v.divide(totalQuantity, 2, RoundingMode.HALF_UP);
                        mergeMaterialUnitPrices.put(k, unitPrice);
                    }
                }
            });
        }
        // 用合并后的物料数量、单价 初始化本地库存
        if (CollUtil.isNotEmpty(basicMaterials)) {
            basicMaterials.forEach((k,v) -> {
                // k成本中心code-物料编码    v随机明细物料对象
                String[] costCenterMaterialAttr = k.split("~~");
                if (costCenterMaterialAttr.length > 1) {
                    // 领料单<->成本中心<->部门
                    String costCenterCode = costCenterMaterialAttr[0];
                    String materialCode = costCenterMaterialAttr[1];
                    if (costCenterCodeDeptIdPair.containsKey(costCenterCode)) {
                        if (costCenterIdPair.containsKey(costCenterCode)) {
                            // 只有部门设置了对应的 成本中心 才会新增 本地库存数据
                            String uniqueKey = StrUtil.join("~~", costCenterIdPair.get(costCenterCode), materialCode);
                            if (tobeAddedUniqueKeys.contains(uniqueKey)) {
                                IotLockStockDO lockStock = new IotLockStockDO();
                                if (costCenterCodeDeptIdPair.containsKey(costCenterCode)) {
                                    lockStock.setDeptId(costCenterCodeDeptIdPair.get(costCenterCode));
                                }
                                // sap成本中心 在pms系统中的id
                                if (costCenterIdPair.containsKey(costCenterCode)) {
                                    lockStock.setCostCenterId(costCenterIdPair.get(costCenterCode));
                                }
                                // sap工厂 在pms系统中的id
                                if (costCenterFactoryCodePair.containsKey(costCenterCode)) {
                                    String tempFactoryCode = costCenterFactoryCodePair.get(costCenterCode);
                                    if (factoryIdPair.containsKey(tempFactoryCode)) {
                                        lockStock.setFactoryId(factoryIdPair.get(tempFactoryCode));
                                    }
                                }
                                // 物料编码
                                lockStock.setMaterialCode(costCenterMaterialAttr[1]);
                                // 物料描述
                                lockStock.setMaterialName(v.getMAKTX());
                                // 单位
                                lockStock.setUnit(v.getMEINS());
                                // 物料数量
                                if (mergeMaterialQuantities.containsKey(k)) {
                                    lockStock.setQuantity(mergeMaterialQuantities.get(k));
                                }
                                // 单价
                                if (mergeMaterialUnitPrices.containsKey(k)) {
                                    lockStock.setUnitPrice(mergeMaterialUnitPrices.get(k));
                                }
                                tobeAddedLockStocks.add(lockStock);
                            }
                        }
                    }
                }
            });
        }
        // 从SAP返回数据中 筛选出不在本地领料单集合中的数据 新增
        pickingListItems.stream()
                .filter(item -> StrUtil.isNotBlank(item.getZLTNO()) && !finalDistinctNumbers.contains(item.getZLTNO()))
            .forEach(item -> {
            IotSapPickingListDO picking = new IotSapPickingListDO();
            // 领料单<->成本中心<->部门
            if (pickingCodeCostCenterPair.containsKey(item.getZLTNO())) {
                String costCenterCode = pickingCodeCostCenterPair.get(item.getZLTNO());
                if (costCenterCodeDeptIdPair.containsKey(costCenterCode)) {
                    picking.setDeptId(costCenterCodeDeptIdPair.get(costCenterCode));
                    // sap成本中心 在pms系统中的id
                    if (costCenterIdPair.containsKey(costCenterCode)) {
                        picking.setCostCenterId(costCenterIdPair.get(costCenterCode));
                    }
                    // sap工厂 在pms系统中的id
                    if (factoryIdPair.containsKey(item.getWERKS())) {
                        picking.setFactoryId(factoryIdPair.get(item.getWERKS()));
                    }
                    // 领料单号
                    picking.setNumber(item.getZLTNO());
                    // 物料编码
                    picking.setMaterialCode(item.getMATNR());
                    // 物料名称 描述
                    picking.setMaterialName(item.getMAKTX());
                    // 物料数量
                    picking.setQuantity(item.getMENGE());
                    // 单价
                    picking.setUnitPrice(item.getVERPR());
                    // 单位
                    picking.setUnit(item.getMEINS());
                    // 同步时间
                    picking.setSyncTime(LocalDateTime.now());
                    pickingLists.add(picking);
                }
            }
        });
        // 本地库存 初始化 批量插入本地库存 记录 新增本地库存
        if (CollUtil.isNotEmpty(tobeAddedLockStocks)) {
            TenantUtils.execute(1L, () -> iotLockStockMapper.insertBatch(tobeAddedLockStocks));
        }
        // 新增领料单
        if (CollUtil.isNotEmpty(pickingLists)) {
            TenantUtils.execute(1L, () -> iotSapPickingListMapper.insertBatch(pickingLists));
        }
        // 记录日志 所有领料单如果有的成本中心 在系统中不存在 或 没有对应的部门 记录到日志

    }

    @Override
    public void processSapStock(String factoryCode, List<IotSapStockVO> sapStocks) {
        // 查询出所有库存地点 对应的 deptId 新增本地库存时 可以匹配  工厂+库存地点 确定唯一值 因为 不同工厂的库存地点code可能重复
        List<SapOrgRespDTO> storageLocations = TenantUtils.execute(1L, () -> sapOrgApi.getSapOrgByType(3));
        // 查询所有工厂
        List<SapOrgRespDTO> factories = TenantUtils.execute(1L, () -> sapOrgApi.getSapOrgByType(1));
        // key工厂code    value工厂id
        Map<String, Long> factoryIdPair = new HashMap<>();
        // key工厂code    value工厂名称
        Map<String, String> factoryNamePair = new HashMap<>();
        // key工厂code-库存地点code    value库存地点id
        Map<String, Long> storageLocationIdPair = new HashMap<>();
        // key库存地点id    value工厂code-库存地点code
        Map<Long, String> stockLocationCodeIdPair = new HashMap<>();
        // key库存地点code  value部门id
        Map<String, Long> locationCodeDeptIdPair = new HashMap<>();
        // key工厂code-库存地点code    value库存地点名称描述
        Map<String, String> stockLocationNamePair = new HashMap<>();
        // 工厂集合
        factories.forEach(factory -> {
            factoryIdPair.put(factory.getFactoryCode(), factory.getId());
            factoryNamePair.put(factory.getFactoryCode(), factory.getFactoryName());
        });
        // pms已经维护的 库存地点 集合
        storageLocations.forEach(location -> {
            System.out.println("当前库存地点信息：" + location.getFactoryCode() + " - " + location.getStorageLocationCode());
            String uniqueKey = StrUtil.join("-", location.getFactoryCode(), location.getStorageLocationCode());
            stockLocationCodeIdPair.put(location.getId(), uniqueKey);
            storageLocationIdPair.put(uniqueKey, location.getId());
            stockLocationNamePair.put(uniqueKey, location.getStorageLocationName());
        });
        // 筛选出pms系统中未维护的 库存地点 信息(工厂-库存地点编码)
        Set<String> notExistStorageLocations = new HashSet<>();
        // 系统中未维护的 工厂 库存地点 也需要存储到数据库 但是 不设置 deptId
        sapStocks.forEach(stock -> {
            String uniqueKey = StrUtil.join("-", stock.getWERKS(), stock.getLGORT());
            if (!storageLocationIdPair.containsKey(uniqueKey)) {
                notExistStorageLocations.add(uniqueKey);
            }
        });
        if (CollUtil.isNotEmpty(notExistStorageLocations)) {
            notExistStorageLocations.forEach(location -> {
                System.out.println("系统中未维护的 工厂编码 - 库存地点编码：" + location);
            });
        }
        // 查询所有部门
        List<DeptRespDTO> depts = TenantUtils.execute(1L, () -> deptApi.getDeptList());
        // 筛选出所有设置了 库存地点 的部门
        depts.forEach(dept -> {
            Set<Long> stockLocationIds = dept.getStockLocationIds();
            // 循环遍历 stockLocationIds
            if (CollUtil.isNotEmpty(stockLocationIds)) {
                stockLocationIds.forEach(locationId -> {
                    if (stockLocationCodeIdPair.containsKey(locationId)) {
                        // 当前部门设置的 库存地点 uniqueKey = 工厂编码+库存地点编码
                        String uniqueKey = stockLocationCodeIdPair.get(locationId);
                        locationCodeDeptIdPair.put(uniqueKey, dept.getId());
                    }
                });
            }
        });
        // 查询所有SAP库存集合A，以 ‘工厂编码+库存地点code+物料编码’ 为唯一key 如果 sapStocks 中有物料不在A中 新增物料
        Set<String> existStockKeys = new HashSet<>();
        IotSapStockPageReqVO reqVO = new IotSapStockPageReqVO();
        reqVO.setFactoryCode(factoryCode);
        List<IotSapStockDO> existStocks = TenantUtils.execute(1L, () -> iotSapStockMapper.selectList(reqVO));
        if (CollUtil.isNotEmpty(existStocks)) {
            log.info("当前工厂{} 当前库中已有sap库存数量{}：", factoryCode, existStockKeys.size());
            existStockKeys = existStocks.stream()
                    // 过滤非空对象和非空code
                    .filter(stk -> ObjUtil.isNotEmpty(stk) && StrUtil.isNotBlank(stk.getMaterialCode())
                            && StrUtil.isNotBlank(stk.getFactoryCode()) && StrUtil.isNotBlank(stk.getStorageLocationCode()))
                    .map(stk -> StrUtil.join("-", stk.getFactoryCode(), stk.getStorageLocationCode(), stk.getMaterialCode()))
                    .collect(Collectors.toSet());
        }
        System.out.println(factoryCode + "当前库中已有sap库存数量：" + existStockKeys.size());

        // 将已经存在的库存数据 设置成 Map<String, IotSapStockDO> 的形式 便于后续更新
        // key工厂code-库存地点code-物料编码       value库存对象   从此集合中筛选将要被更新的库存对象
        Map<String, IotSapStockDO> tobeUpdatedStockPair = new HashMap<>();
        tobeUpdatedStockPair = existStocks.stream()
                .filter(stk -> StrUtil.isNotBlank(stk.getMaterialCode()) && StrUtil.isNotBlank(stk.getFactoryCode())
                        && StrUtil.isNotBlank(stk.getStorageLocationCode()))
                .collect(Collectors.toMap(
                        stk -> StrUtil.join("-", stk.getFactoryCode(), stk.getStorageLocationCode(), stk.getMaterialCode()),
                        stk -> stk, // 值为对象本身
                        (existing, replacement) -> existing // 处理键冲突: 保留先出现的元素
                ));

        // 找出当前工厂 需要新增的库存（SAP物料编码去掉前导零后，不在现有物料集合中的记录）
        Set<String> finalExistStockKeys = existStockKeys;
        List<IotSapStockVO> newStocks = sapStocks.stream()
                .filter(stk -> factoryCode.equals(stk.getWERKS()) && StrUtil.isNotBlank(stk.getMATNR()) && StrUtil.isNotBlank(stk.getLGORT())
                        && storageLocationIdPair.containsKey(StrUtil.join("-", stk.getWERKS(), stk.getLGORT())))
                .filter(stk -> {
                    // 处理前导零：移除MATNR前的 00000000
                    String processedCode = stk.getMATNR().replaceFirst("^0+", "");
                    // 本地已经配置过库存地点 包含SAP库存接口返回的库存地点
                    // Long storageLocationId = storageLocationIdPair.get(StrUtil.join("-", stk.getWERKS(), stk.getLGORT()));
                    String uniqueKey = StrUtil.join("-", stk.getWERKS(), stk.getLGORT());
                    return !finalExistStockKeys.contains(StrUtil.join("-", uniqueKey, processedCode));
                })
                .collect(Collectors.toList());

        System.out.println(factoryCode + "需要新增的sap库存数量：" + newStocks.size());

        // 新增SAP库存数据
        List<IotSapStockDO> tobeAddedStocks = new ArrayList<>();
        if (CollUtil.isNotEmpty(newStocks)) {
            newStocks.forEach(stock -> {
                // 只保存pms维护过库存地点的数据
                String uniqueKey = StrUtil.join("-", stock.getWERKS(), stock.getLGORT());
                IotSapStockDO sapStock = new IotSapStockDO();
                // 部门id 已经关联的组织部门的SAP工厂 库存地点
                if (locationCodeDeptIdPair.containsKey(uniqueKey)) {
                    sapStock.setDeptId(locationCodeDeptIdPair.get(uniqueKey));
                } else {
                    // 没有关联组织部门的SAP工厂 库存地点 暂时设置部门 为 163 科瑞石油技术
                    sapStock.setDeptId(163l);
                }
                // 工厂id
                if (factoryIdPair.containsKey(stock.getWERKS())) {
                    sapStock.setFactoryId(factoryIdPair.get(stock.getWERKS()));
                }
                // 工厂 code
                sapStock.setFactoryCode(stock.getWERKS());
                // 工厂名称
                if (factoryNamePair.containsKey(stock.getWERKS())) {
                    sapStock.setFactory(factoryNamePair.get(stock.getWERKS()));
                }
                // 库存地点id
                if (storageLocationIdPair.containsKey(uniqueKey)) {
                    sapStock.setStorageLocationId(storageLocationIdPair.get(uniqueKey));
                }
                // 库存地点code
                sapStock.setStorageLocationCode(stock.getLGORT());
                // 库存地点名称
                if (stockLocationNamePair.containsKey(uniqueKey)) {
                    sapStock.setProjectDepartment(stockLocationNamePair.get(uniqueKey));
                }
                // 物料编码 需要去掉前缀 00000000
                sapStock.setMaterialCode(stock.getMATNR().replaceFirst("^0+", ""));
                // 物料描述
                sapStock.setMaterialName(stock.getMAKTX());
                // 库存数量
                sapStock.setQuantity(stock.getLABST());
                // 单价
                sapStock.setUnitPrice(stock.getJIAGE());
                // 基本单位
                sapStock.setUnit(stock.getMEINS());
                // 同步时间
                sapStock.setSyncTime(LocalDateTime.now());
                tobeAddedStocks.add(sapStock);
            });
        }

        // 使用返回的SAP库存数据更新已有的 库存数据
        // pms中已有 SAP返回数据中也存在 则更新 再次判断更新deptId 因为有可能又将SAP库存地点关联到了 组织部门
        List<IotSapStockDO> actualUpdatedStocks = new ArrayList<>();
        // key库存id-物料编码      value库存对象
        Map<String, IotSapStockVO> existStockPair = createExistStockMap(factoryCode, sapStocks, existStockKeys, storageLocationIdPair);
        if (CollUtil.isNotEmpty(existStockPair)) {
            Map<String, IotSapStockDO> finalTobeUpdatedStockPair = tobeUpdatedStockPair;
            existStockPair.forEach((k, v) -> {
                if (finalTobeUpdatedStockPair.containsKey(k)) {
                    // 再次查询当前 工厂-库存地点-物料编码 是否已经关联了组织部门
                    IotSapStockDO sapStock = finalTobeUpdatedStockPair.get(k);
                    String uniqueKey = StrUtil.join("-", sapStock.getFactoryCode(), sapStock.getStorageLocationCode());
                    if (locationCodeDeptIdPair.containsKey(uniqueKey)) {
                        sapStock.setDeptId(locationCodeDeptIdPair.get(uniqueKey));
                    }
                    sapStock.setUnitPrice(v.getJIAGE());    // 更新库存价格
                    sapStock.setQuantity(v.getLABST());     // 更新库存数量
                    sapStock.setUnit(v.getMEINS());         // 更新基本单位
                    actualUpdatedStocks.add(sapStock);
                }
            });
        }
        System.out.println(factoryCode + "需要更新的SAP库存数量：" + actualUpdatedStocks.size());

        // 每次请求都 保存 SAP接口返回的原始库存数据
        List<IotSapStockLogDO> tobeAddedSapStockLogs = new ArrayList<>();
        if (CollUtil.isNotEmpty(sapStocks)) {
            sapStocks.forEach(stock -> {
                IotSapStockLogDO sapStockLog = new IotSapStockLogDO();
                sapStockLog.setFactoryCode(stock.getWERKS());
                sapStockLog.setStorageLocationCode(stock.getLGORT());
                sapStockLog.setMaterialCode(stock.getMATNR().replaceFirst("^0+", ""));
                sapStockLog.setMaterialName(stock.getMAKTX());
                // 库存数量
                sapStockLog.setQuantity(stock.getLABST());
                // 单价
                sapStockLog.setUnitPrice(stock.getJIAGE());
                // 基本单位
                sapStockLog.setUnit(stock.getMEINS());
                // 同步时间
                sapStockLog.setSyncTime(LocalDateTime.now());
                tobeAddedSapStockLogs.add(sapStockLog);
            });
        }

        // pms中已有 但是 SAP返回数据不存在 如果设置了安全库存，则库存为0，若没有设置安全库存，则可以删除

        // 保存SAP库存原始数据 先删除当前工厂下的数据 再新增
        TenantUtils.execute(1L, () -> iotSapStockLogMapper.deleteSapStockLogs(factoryCode));
        if (CollUtil.isNotEmpty(tobeAddedSapStockLogs)) {
            TenantUtils.execute(1L, () -> iotSapStockLogMapper.insertBatch(tobeAddedSapStockLogs));
        }
        // 本地库存 初始化 批量插入SAP 库存 工厂 库存地点 已经关联了组织架构部门
        if (CollUtil.isNotEmpty(tobeAddedStocks)) {
            TenantUtils.execute(1L, () -> iotSapStockMapper.insertBatch(tobeAddedStocks));
        }

        // pms中存在而且SAP接口也返回的库存数据：更新
        if (CollUtil.isNotEmpty(actualUpdatedStocks)) {
            TenantUtils.execute(1L, () -> iotSapStockMapper.updateBatch(actualUpdatedStocks));
        }
    }

    @Override
    public void processSapStockBatch(List<IotSapStockVO> allStockData, Object contextObj) {
        if (!(contextObj instanceof SyncSapStockContext)) {
            throw new IllegalArgumentException("无效的上下文对象");
        }

        SyncSapStockContext context = (SyncSapStockContext) contextObj;

        if (CollUtil.isEmpty(allStockData)) {
            log.error("没有需要处理的SAP库存数据");
            return;
        }

        log.error("开始批量处理SAP库存数据，总计 {} 条记录", allStockData.size());
        long startTime = System.currentTimeMillis();

        try {
            // 按工厂分组处理
            Map<String, List<IotSapStockVO>> factoryStocksMap = allStockData.stream()
                    .collect(Collectors.groupingBy(IotSapStockVO::getWERKS));

            log.error("按工厂分组完成，共 {} 个工厂的数据需要处理", factoryStocksMap.size());

            List<IotSapStockDO> totalActualUpdatedStocks = new ArrayList<>();
            List<IotSapStockDO> totalTobeAddedStocks = new ArrayList<>();

            // 处理每个工厂的数据
            factoryStocksMap.forEach((factoryCode, stocks) -> {
                try {
                    processSingleFactoryStocks(factoryCode, stocks, context, totalTobeAddedStocks, totalActualUpdatedStocks);
                    log.error("工厂 {} 库存数据处理完成，共处理 {} 条记录", factoryCode, stocks.size());
                } catch (Exception e) {
                    log.error("处理工厂 {} 库存数据时发生错误", factoryCode, e);
                    // 记录错误但继续处理其他工厂
                }
            });

            // 分批处理库存日志数据
            processStockLogsInBatches(allStockData);

            // 分批处理新增库存数据
            processNewStocksInBatches(totalTobeAddedStocks);

            // 分批处理更新库存数据
            processUpdatedStocksInBatches(totalActualUpdatedStocks);

            long endTime = System.currentTimeMillis();
            log.error("所有工厂库存数据处理完成，总耗时: {}ms", (endTime - startTime));

        } catch (Exception e) {
            log.error("批量处理SAP库存数据时发生错误", e);
            throw new RuntimeException("SAP库存数据处理失败", e);
        }
    }


    /**
     * 分批处理库存日志数据
     */
    private void processStockLogsInBatches(List<IotSapStockVO> allStockData) {
        if (CollUtil.isEmpty(allStockData)) {
            return;
        }

        // 准备SAP库存日志数据
        List<IotSapStockLogDO> tobeAddedSapStockLogs = allStockData.stream()
                .map(stock -> {
                    IotSapStockLogDO sapStockLog = new IotSapStockLogDO();
                    sapStockLog.setFactoryCode(stock.getWERKS());
                    sapStockLog.setStorageLocationCode(stock.getLGORT());
                    sapStockLog.setMaterialCode(stock.getMATNR().replaceFirst("^0+", ""));
                    sapStockLog.setMaterialName(stock.getMAKTX());
                    sapStockLog.setQuantity(stock.getLABST());
                    sapStockLog.setUnitPrice(stock.getJIAGE());
                    sapStockLog.setUnit(stock.getMEINS());
                    sapStockLog.setSyncTime(LocalDateTime.now());
                    return sapStockLog;
                })
                .collect(Collectors.toList());

        try {
            // 先删除所有日志记录（分批删除）
            deleteAllStockLogsInBatches();

            // 分批插入新的日志记录
            insertStockLogsInBatches(tobeAddedSapStockLogs);

        } catch (Exception e) {
            log.error("处理库存日志数据时发生错误", e);
            // 不抛出异常，继续处理其他数据
        }
    }

    /**
     * 分批删除所有库存日志
     */
    private void deleteAllStockLogsInBatches() {
        try {
            // 直接调用删除方法，不传递工厂代码
            TenantUtils.execute(1L, () -> {
                iotSapStockLogMapper.deleteSapStockLogs(null);
                return null;
            });
            log.error("成功删除所有库存日志记录");
        } catch (Exception e) {
            log.error("删除库存日志记录时发生错误", e);
            throw new RuntimeException("删除库存日志失败", e);
        }
    }

    /**
     * 分批插入库存日志
     */
    private void insertStockLogsInBatches(List<IotSapStockLogDO> stockLogs) {
        if (CollUtil.isEmpty(stockLogs)) {
            return;
        }

        log.error("开始分批插入库存日志，总计 {} 条记录", stockLogs.size());

        // 分批处理
        List<List<IotSapStockLogDO>> batches = CollUtil.split(stockLogs, LOG_BATCH_SIZE);
        CountDownLatch latch = new CountDownLatch(batches.size());

        for (List<IotSapStockLogDO> batch : batches) {
            pmsThreadPoolTaskExecutor.execute(() -> {
                try {
                    TenantUtils.execute(1L, () -> {
                        try {
                            iotSapStockLogMapper.insertBatch(batch);
                            log.error("成功插入一批库存日志，数量: {}", batch.size());
                        } catch (Exception e) {
                            log.error("插入库存日志批次时发生错误，批次大小: {}", batch.size(), e);
                            // 记录错误但继续处理其他批次
                        }
                        return null;
                    });
                } catch (Exception e) {
                    log.error("执行租户上下文操作时发生错误", e);
                } finally {
                    latch.countDown();
                }
            });
        }

        try {
            latch.await();
            log.error("所有库存日志批次插入完成");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("等待库存日志插入完成时被中断", e);
        }
    }

    /**
     * 分批处理新增库存数据
     */
    private void processNewStocksInBatches(List<IotSapStockDO> newStocks) {
        if (CollUtil.isEmpty(newStocks)) {
            log.error("没有需要新增的库存数据");
            return;
        }

        log.error("开始分批插入新增库存数据，总计 {} 条记录", newStocks.size());

        List<List<IotSapStockDO>> batches = CollUtil.split(newStocks, BATCH_SIZE);
        CountDownLatch latch = new CountDownLatch(batches.size());
        final int[] successCount = {0};

        for (List<IotSapStockDO> batch : batches) {
            pmsThreadPoolTaskExecutor.execute(() -> {
                try {
                    TenantUtils.execute(1L, () -> {
                        try {
                            iotSapStockMapper.insertBatch(batch);
                            successCount[0] += batch.size();
                            log.error("成功插入一批新增库存，数量: {}", batch.size());
                        } catch (Exception e) {
                            log.error("插入新增库存批次时发生错误，批次大小: {}", batch.size(), e);
                        }
                        return null;
                    });
                } catch (Exception e) {
                    log.error("执行新增库存租户上下文操作时发生错误", e);
                } finally {
                    latch.countDown();
                }
            });
        }

        try {
            latch.await();
            log.error("新增库存数据插入完成，成功插入 {} 条记录", successCount[0]);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("等待新增库存插入完成时被中断", e);
        }
    }

    /**
     * 分批处理更新库存数据
     */
    private void processUpdatedStocksInBatches(List<IotSapStockDO> updatedStocks) {
        if (CollUtil.isEmpty(updatedStocks)) {
            log.error("没有需要更新的库存数据");
            return;
        }

        log.error("开始分批更新库存数据，总计 {} 条记录", updatedStocks.size());

        List<List<IotSapStockDO>> batches = CollUtil.split(updatedStocks, BATCH_SIZE);
        CountDownLatch latch = new CountDownLatch(batches.size());
        final int[] successCount = {0};

        for (List<IotSapStockDO> batch : batches) {
            pmsThreadPoolTaskExecutor.execute(() -> {
                try {
                    TenantUtils.execute(1L, () -> {
                        try {
                            iotSapStockMapper.updateBatch(batch);
                            successCount[0] += batch.size();
                            log.error("成功更新一批库存，数量: {}", batch.size());
                        } catch (Exception e) {
                            log.error("更新库存批次时发生错误，批次大小: {}", batch.size(), e);
                        }
                        return null;
                    });
                } catch (Exception e) {
                    log.error("执行更新库存租户上下文操作时发生错误", e);
                } finally {
                    latch.countDown();
                }
            });
        }

        try {
            latch.await();
            log.error("库存数据更新完成，成功更新 {} 条记录", successCount[0]);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("等待库存更新完成时被中断", e);
        }
    }

    /**
     * 创建物料编码到描述的映射
     * @param sapMaterials SAP返回的物料列表
     * @param existingMaterialCodes 数据库中已存在的物料编码集合
     * @return 物料编码到描述的映射
     */
    private Map<String, String> createMaterialMap(List<IotSapMaterialVO> sapMaterials, Set<String> existingMaterialCodes) {
        return sapMaterials.stream()
                .filter(sapMaterial -> StrUtil.isNotBlank(sapMaterial.getMATNR()) &&
                        StrUtil.isNotBlank(sapMaterial.getMAKTX()))
                .map(sapMaterial -> {
                    // 处理前导零：移除MATNR前的00000000
                    String processedCode = sapMaterial.getMATNR().replaceFirst("^0+", "");
                    return new AbstractMap.SimpleEntry<>(processedCode, sapMaterial.getMAKTX());
                })
                .filter(entry -> existingMaterialCodes.contains(entry.getKey()))
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (existing, replacement) -> existing // 如果有重复的key，保留第一个值
                ));
    }

    /**
     * 创建物料编码到 单位 的映射 (SAP接口返回单位后 初始化数据)
     * @param sapMaterials SAP返回的物料列表
     * @param existingMaterialCodes 数据库中已存在的物料编码集合
     * @return 物料编码到 单位 的映射
     */
    private Map<String, String> createMaterialUnitMap(List<IotSapMaterialVO> sapMaterials, Set<String> existingMaterialCodes) {
        return sapMaterials.stream()
                .filter(sapMaterial -> StrUtil.isNotBlank(sapMaterial.getMATNR()) &&
                        StrUtil.isNotBlank(sapMaterial.getMEINS()))
                .map(sapMaterial -> {
                    // 处理前导零：移除MATNR前的00000000
                    String processedCode = sapMaterial.getMATNR().replaceFirst("^0+", "");
                    return new AbstractMap.SimpleEntry<>(processedCode, sapMaterial.getMEINS());
                })
                .filter(entry -> existingMaterialCodes.contains(entry.getKey()))
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (existing, replacement) -> existing // 如果有重复的key，保留第一个值
                ));
    }

    /**
     * sap库存 更新 创建 唯一key(库存地点id-物料编码) 到 sap库存对象 的映射
     * pms中已有的库存  SAP库存接口也返回了 需要更新
     * @param sapStocks SAP库存列表
     * @param existStockKeys 数据库中已存在的 sap库存 集合
     * @param storageLocationIdPair key库存地点code    value库存地点id
     * @return 物料编码到描述的映射
     */
    private Map<String, IotSapStockVO> createExistStockMap(String factoryCode, List<IotSapStockVO> sapStocks, Set<String> existStockKeys,
                                                           Map<String, Long> storageLocationIdPair) {
        return sapStocks.stream()
                .filter(sapStock -> factoryCode.equals(sapStock.getWERKS()) && StrUtil.isNotBlank(sapStock.getMATNR()) &&
                        StrUtil.isNotBlank(sapStock.getLGORT()) &&
                        storageLocationIdPair.containsKey(StrUtil.join("-", sapStock.getWERKS(), sapStock.getLGORT())))
                .map(sapStock -> {
                    // 处理前导零：移除MATNR前的 00000000
                    String processedCode = sapStock.getMATNR().replaceFirst("^0+", "");
                    // 本地已经配置过库存地点 包含SAP库存接口返回的库存地点
                    // Long storageLocationId = storageLocationIdPair.get(StrUtil.join("-", sapStock.getWERKS(), sapStock.getLGORT()));
                    String uniqueKey = StrUtil.join("-", sapStock.getWERKS(), sapStock.getLGORT());
                    return new AbstractMap.SimpleEntry<>(StrUtil.join("-", uniqueKey, processedCode), sapStock);
                })
                .filter(entry -> existStockKeys.contains(entry.getKey()))
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (existing, replacement) -> existing // 如果有重复的key，保留第一个值
                ));
    }

    /**
     * 处理本地未维护的工厂 库存地点 同步过来的物料
     * sap库存 更新 创建 唯一key(工厂code+库存地点code-物料编码) 到 sap库存对象 的映射
     * pms中已有的库存  SAP库存接口也返回了 需要更新
     * @param sapStocks SAP库存列表
     * @param existStockKeys 数据库中已存在的 sap库存 集合
     * @param storageLocationIdPair key库存地点code    value库存地点id
     * @return 物料编码到描述的映射
     */
    private Map<String, IotSapStockVO> createNoFactoryExistStockMap(List<IotSapStockVO> sapStocks,
                                                                    Set<String> existStockKeys, Map<String, Long> storageLocationIdPair) {
        return sapStocks.stream()
                .filter(sapStock -> StrUtil.isNotBlank(sapStock.getMATNR()) && StrUtil.isNotBlank(sapStock.getLGORT()))
                .map(sapStock -> {
                    // 处理前导零：移除MATNR前的 00000000
                    String processedCode = sapStock.getMATNR().replaceFirst("^0+", "");
                    // 本地已经配置过库存地点 包含SAP库存接口返回的库存地点
                    return new AbstractMap.SimpleEntry<>(StrUtil.join("-", sapStock.getWERKS(), sapStock.getLGORT(), processedCode), sapStock);
                })
                .filter(entry -> existStockKeys.contains(entry.getKey()))
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (existing, replacement) -> existing // 如果有重复的key，保留第一个值
                ));
    }

    private void processSingleFactoryStocks(String factoryCode, List<IotSapStockVO> stocks, SyncSapStockContext context,
                                            List<IotSapStockDO> totalTobeAddedStocks, List<IotSapStockDO> totalActualUpdatedStocks) {
        if (CollUtil.isEmpty(stocks)) {
            log.error("工厂 {} 没有库存数据需要处理", factoryCode);
            return;
        }

        // 筛选出pms系统中未维护的库存地点信息(工厂-库存地点编码)
        Set<String> notExistStorageLocations = new HashSet<>();
        stocks.forEach(stock -> {
            String uniqueKey = StrUtil.join("-", stock.getWERKS(), stock.getLGORT());
            if (!context.storageLocationIdPair.containsKey(uniqueKey)) {
                notExistStorageLocations.add(uniqueKey);
            }
        });

        if (CollUtil.isNotEmpty(notExistStorageLocations)) {
            log.error("工厂 {} 发现 {} 个未维护的库存地点: {}", factoryCode,
                    notExistStorageLocations.size(), notExistStorageLocations);
            notExistStorageLocations.forEach(location ->
                    log.error("系统中未维护的工厂编码-库存地点编码: {}", location));
        }

        // 查询当前工厂的现有库存数据
        Set<String> existStockKeys = new HashSet<>();
        IotSapStockPageReqVO reqVO = new IotSapStockPageReqVO();
        reqVO.setFactoryCode(factoryCode);
        log.error("当前工厂 {} 已经存在的SAP库存数量：{}", factoryCode, context.existStocks.size());
        // 从预加载的数据中过滤出当前工厂的库存
        List<IotSapStockDO> factoryExistStocks = context.existStocks.stream()
                .filter(stock -> factoryCode.equals(stock.getFactoryCode()))
                .collect(Collectors.toList());

        if (CollUtil.isNotEmpty(factoryExistStocks)) {
            log.error("工厂 {} 当前库中已有sap库存数量: {}", factoryCode, factoryExistStocks.size());
            existStockKeys = factoryExistStocks.stream()
                    .filter(stk -> ObjUtil.isNotEmpty(stk) && StrUtil.isNotBlank(stk.getMaterialCode())
                            && StrUtil.isNotBlank(stk.getFactoryCode()) && StrUtil.isNotBlank(stk.getStorageLocationCode()))
                    .map(stk -> StrUtil.join("-", stk.getFactoryCode(), stk.getStorageLocationCode(), stk.getMaterialCode()))
                    .collect(Collectors.toSet());
        }

        log.error("工厂 {} 当前库中已有sap库存唯一键数量: {}", factoryCode, existStockKeys.size());

        // 构建待更新库存映射
        Map<String, IotSapStockDO> tobeUpdatedStockPair = factoryExistStocks.stream()
                .filter(stk -> StrUtil.isNotBlank(stk.getMaterialCode()) && StrUtil.isNotBlank(stk.getFactoryCode())
                        && StrUtil.isNotBlank(stk.getStorageLocationCode()))
                .collect(Collectors.toMap(
                        stk -> StrUtil.join("-", stk.getFactoryCode(), stk.getStorageLocationCode(), stk.getMaterialCode()),
                        stk -> stk,
                        (existing, replacement) -> existing
                ));

        // 找出需要新增的库存记录
        Set<String> finalExistStockKeys = existStockKeys;
        List<IotSapStockVO> newStocks = stocks.stream()
                .filter(stk -> factoryCode.equals(stk.getWERKS()) && StrUtil.isNotBlank(stk.getMATNR()) && StrUtil.isNotBlank(stk.getLGORT())
                        && context.storageLocationIdPair.containsKey(StrUtil.join("-", stk.getWERKS(), stk.getLGORT())))
                .filter(stk -> {
                    String processedCode = stk.getMATNR().replaceFirst("^0+", "");
                    String uniqueKey = StrUtil.join("-", stk.getWERKS(), stk.getLGORT());
                    return !finalExistStockKeys.contains(StrUtil.join("-", uniqueKey, processedCode));
                })
                .collect(Collectors.toList());

        log.error("工厂 {} 需要新增的sap库存数量: {}", factoryCode, newStocks.size());

        // 准备新增的库存数据
        List<IotSapStockDO> tobeAddedStocks = new ArrayList<>();
        if (CollUtil.isNotEmpty(newStocks)) {
            newStocks.forEach(stock -> {
                String uniqueKey = StrUtil.join("-", stock.getWERKS(), stock.getLGORT());
                IotSapStockDO sapStock = new IotSapStockDO();

                // 设置部门ID
                if (context.locationCodeDeptIdPair.containsKey(uniqueKey)) {
                    sapStock.setDeptId(context.locationCodeDeptIdPair.get(uniqueKey));
                } else {
                    // 没有关联组织部门的SAP工厂库存地点，设置默认部门
                    sapStock.setDeptId(163L); // 科瑞石油技术
                }

                // 设置工厂信息
                if (context.factoryIdPair.containsKey(stock.getWERKS())) {
                    sapStock.setFactoryId(context.factoryIdPair.get(stock.getWERKS()));
                }
                sapStock.setFactoryCode(stock.getWERKS());
                if (context.factoryNamePair.containsKey(stock.getWERKS())) {
                    sapStock.setFactory(context.factoryNamePair.get(stock.getWERKS()));
                }

                // 设置库存地点信息
                if (context.storageLocationIdPair.containsKey(uniqueKey)) {
                    sapStock.setStorageLocationId(context.storageLocationIdPair.get(uniqueKey));
                }
                sapStock.setStorageLocationCode(stock.getLGORT());
                if (context.stockLocationNamePair.containsKey(uniqueKey)) {
                    sapStock.setProjectDepartment(context.stockLocationNamePair.get(uniqueKey));
                }

                // 设置物料信息
                sapStock.setMaterialCode(stock.getMATNR().replaceFirst("^0+", ""));
                sapStock.setMaterialName(stock.getMAKTX());
                // 数量
                BigDecimal quantity = stock.getLABST();
                sapStock.setQuantity(quantity);
                // 总价
                BigDecimal totalPrice = stock.getJIAGE();
                // 单价 总价/数量
                quantity = ObjUtil.isEmpty(quantity) ? BigDecimal.ZERO : quantity;
                totalPrice = ObjUtil.isEmpty(totalPrice) ? BigDecimal.ZERO : totalPrice;
                if (quantity.compareTo(BigDecimal.ZERO) > 0) {
                    BigDecimal unitPrice = totalPrice.divide(quantity, 2, RoundingMode.HALF_UP);
                    sapStock.setUnitPrice(unitPrice);
                } else {
                    sapStock.setUnitPrice(stock.getJIAGE());
                }
                // 总价
                sapStock.setTotalPrice(totalPrice);
                sapStock.setUnit(stock.getMEINS());
                sapStock.setSyncTime(LocalDateTime.now());

                tobeAddedStocks.add(sapStock);
            });
        }

        // 准备更新的库存数据
        List<IotSapStockDO> actualUpdatedStocks = new ArrayList<>();
        Map<String, IotSapStockVO> existStockPair = createExistStockMap(factoryCode, stocks, existStockKeys, context.storageLocationIdPair);

        if (CollUtil.isNotEmpty(existStockPair)) {
            Map<String, IotSapStockDO> finalTobeUpdatedStockPair = tobeUpdatedStockPair;
            existStockPair.forEach((key, sapStockVO) -> {
                if (finalTobeUpdatedStockPair.containsKey(key)) {
                    IotSapStockDO existingStock = finalTobeUpdatedStockPair.get(key);
                    String uniqueKey = StrUtil.join("-", existingStock.getFactoryCode(), existingStock.getStorageLocationCode());

                    // 更新部门关联
                    if (context.locationCodeDeptIdPair.containsKey(uniqueKey)) {
                        existingStock.setDeptId(context.locationCodeDeptIdPair.get(uniqueKey));
                    }

                    // 更新库存信息
                    existingStock.setUnitPrice(sapStockVO.getJIAGE());
                    existingStock.setQuantity(sapStockVO.getLABST());
                    existingStock.setUnit(sapStockVO.getMEINS());
                    existingStock.setSyncTime(LocalDateTime.now());

                    actualUpdatedStocks.add(existingStock);
                }
            });
        }

        log.error("工厂 {} 需要更新的SAP库存数量: {}", factoryCode, actualUpdatedStocks.size());

        // 本地库存 初始化 批量插入SAP 库存 工厂 库存地点 已经关联了组织架构部门
        if (CollUtil.isNotEmpty(tobeAddedStocks)) {
            totalTobeAddedStocks.addAll(tobeAddedStocks);
        }

        // pms中存在而且SAP接口也返回的库存数据：更新
        if (CollUtil.isNotEmpty(actualUpdatedStocks)) {
            totalActualUpdatedStocks.addAll(actualUpdatedStocks);
        }

        log.error("工厂 {} 库存数据处理完成: 新增 {} 条, 更新 {} 条, 日志 {} 条",
                factoryCode,
                CollUtil.size(tobeAddedStocks),
                CollUtil.size(actualUpdatedStocks));
    }
}
