Przeglądaj źródła

pms 同步客商 物料主数据

zhangcl 2 miesięcy temu
rodzic
commit
7544a7a96e

+ 8 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotmaterial/IotMaterialMapper.java

@@ -61,6 +61,14 @@ public interface IotMaterialMapper extends BaseMapperX<IotMaterialDO> {
     @Update("UPDATE rq_iot_material SET sync_time = NULL")
     void resetMaterialSyncTime();
 
+    /**
+     * 查找出表中最大的 ID
+     * @return
+     */
+    default List<IotMaterialDO> theMaxOne(){
+        return selectList(new LambdaQueryWrapperX<IotMaterialDO>().orderByDesc(IotMaterialDO::getId));
+    }
+
     default List<IotMaterialDO> selectListByStatus(Integer status) {
         return selectList(IotMaterialDO::getStatus, status);
     }

+ 65 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/sap/SyncSapMerchantJob.java

@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.pms.job.sap;
+
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.module.pms.sap.SapConnector;
+import cn.iocoder.yudao.module.pms.sap.service.IotSapService;
+import cn.iocoder.yudao.module.pms.sap.vo.IotSapMerchantVO;
+import com.sap.conn.jco.*;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 同步 SAP 客商主数据 定时任务
+ */
+@Component
+@Slf4j
+public class SyncSapMerchantJob implements JobHandler {
+    @Autowired
+    private SapConnector sapConnector;
+    @Autowired
+    private IotSapService iotSapService;
+
+    /**
+     * 暂时只同步当前租户的数据
+     * @param param 参数
+     * @return
+     * @throws Exception
+     */
+    @Override
+    public String execute(String param) throws Exception {
+        try {
+            JCoDestination destination = sapConnector.getDestination();
+            JCoFunction function = destination.getRepository().getFunction("ZPMS_001");
+
+            // 设置输入参数
+            JCoParameterList input = function.getImportParameterList();
+            input.setValue("CODE", "LFA1"); // 供应商
+
+            // 执行 RFC 调用
+            function.execute(destination);
+
+            // 获取输出参数
+            JCoParameterList output = function.getTableParameterList();
+            JCoTable texportTable = function.getTableParameterList().getTable("TEXPORT");
+            if (texportTable != null && texportTable.getNumRows() > 0) {
+                List<IotSapMerchantVO> sapMerchants = new ArrayList<>();
+                for (int i = 0; i < texportTable.getNumRows(); i++) {
+                    texportTable.setRow(i);
+                    IotSapMerchantVO tempMerchant = new IotSapMerchantVO();
+                    tempMerchant.setCode(texportTable.getString("KV_KEY"));
+                    tempMerchant.setName(texportTable.getString("KV_VALUE"));
+                    sapMerchants.add(tempMerchant);
+                }
+                iotSapService.processMerchants(sapMerchants);
+            }
+            return "Merchant Info: " + output.getString("TEXPORT");
+        } catch (JCoException e) {
+            return "Error calling SAP Merchant Master Data: " + e.getMessage();
+        }
+    }
+
+}

+ 60 - 42
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/sap/SapController.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pms.sap;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.pms.sap.service.IotSapService;
 import cn.iocoder.yudao.module.pms.sap.vo.IotSapMaterialVO;
 import cn.iocoder.yudao.module.system.api.saporg.SapOrgApi;
@@ -88,11 +89,11 @@ public class SapController {
             if (ObjUtil.isEmpty(function)) {
                 return "No SAP Function";
             }
-            // 设置输入参数
+            // 设置输入参数   暂时只同步2025年的数据
             JCoParameterList input = function.getImportParameterList();
             input.setValue("I_WERKS", "6011");
             // 暂时只设置同步 2025年后的 领料单 2025年之前的领料单 手动入库
-            input.setValue("I_DATUM_F", "20250101"); // 开始日期
+            // input.setValue("I_DATUM_F", "20250101"); // 开始日期
             // input.setValue("I_DATUM_T", "20251231"); // 结束日期
 
             // 执行 RFC 调用
@@ -110,21 +111,21 @@ public class SapController {
             if (headTable != null && headTable.getNumRows() > 0) {
                 for (int i = 0; i < headTable.getNumRows(); i++) {
                     headTable.setRow(i);
-                    System.out.println(String.format("行号 %d: 工厂=%s, 名称 1=%s, 领料/退料编号=%s, ZLLLX=%s, " +
-                                    "日期=%tT, 项目定义=%s, WBS 元素=%s, 描述=%s, 所属项目部=%s, 描述=%s, 成本中心=%s, 描述=%s\n",
+                    System.out.println(String.format("行号 %d: 工厂=%s, 名称 1=%s, 领料/退料编号=%s, 领料/退料单名称=%s, " +
+                                    "日期=%tT, 项目定义=%s, WBS 元素=%s, 描述=%s, 所属项目部=%s, 所属项目部描述=%s, 成本中心=%s, 成本中心描述=%s\n",
                             i,
-                            headTable.getString("WERKS"),
-                            headTable.getString("TXTMD"),
-                            headTable.getString("ZLTNO"),
-                            headTable.getString("ZLLLX"),
-                            headTable.getString("DATUM"),
-                            headTable.getString("PSPID"),
-                            headTable.getString("POSID"),
-                            headTable.getString("POST1"),
-                            headTable.getString("ZSSXMB"),
-                            headTable.getString("LGOBE"),
-                            headTable.getString("KOSTL"),
-                            headTable.getString("LTEXT")
+                            headTable.getString("WERKS"),   // 工厂code
+                            headTable.getString("TXTMD"),   // 工厂名称
+                            headTable.getString("ZLTNO"),   // 领料/退料编号
+                            headTable.getString("ZLLLX"),   // 领料/退料单名称
+                            headTable.getDate("DATUM"),   // 日期
+                            headTable.getString("PSPID"),   // 项目定义
+                            headTable.getString("POSID"),   // WBS元素
+                            headTable.getString("POST1"),   // 描述 50032队/YB058-H02井
+                            headTable.getString("ZSSXMB"),  // 所属项目部
+                            headTable.getString("LGOBE"),   // 所属项目部描述
+                            headTable.getString("KOSTL"),   // 成本中心
+                            headTable.getString("LTEXT")    // 成本中心描述
                     ));
                 }
             }
@@ -133,23 +134,35 @@ public class SapController {
             if (itemTable != null && itemTable.getNumRows() > 0) {
                 for (int i = 0; i < itemTable.getNumRows(); i++) {
                     itemTable.setRow(i);
-                    System.out.println(String.format("行号 %d: ZNUMC=%s, 物料=%s, 描述=%s, 数量=%f, " +
-                                    "基本单位=%s, 特殊库存=%s, 移动原因=%s, 原因=%s, 库存地点=%s, 描述=%s, 库存仓位=%s, 批次=%s, 转入工厂=%s, BEIZHU=%s\n",
+                    System.out.println(String.format("行号 %d: 工厂编号=%s, 领料/退料编号=%s, 物料编码=%s, 物料描述=%s, " +
+                                    "物料数量=%s, 基本单位=%s, 特殊库存=%s, 移动原因=%s, 原因=%s, 库存地点编码=%s, 库存地点描述=%s, 库存仓位=%s, 批次=%s, 转入工厂=%s\n" +
+                                    "备注=%s, 物料凭证编号=%s, 物料凭证年度=%s, 行项目号=%s, 本位币金额(单价)=%s, 货币码=%s, 交易后库存数量 - 以输入单位计=%s, 本位币库存总价值=%s, 价格控制标识=%s, 移动平均价或标准价=%s\n",
                             i,
-                            headTable.getString("ZNUMC"),
-                            headTable.getString("MATNR"),
-                            headTable.getString("MAKTX"),
-                            headTable.getString("MENGE"),
-                            headTable.getString("MEINS"),
-                            headTable.getString("SOBKZ"),
-                            headTable.getString("GRUND"),
-                            headTable.getString("GRTXT"),
-                            headTable.getString("LGORT"),
-                            headTable.getString("LGOBE"),
-                            headTable.getString("LGPBE"),
-                            headTable.getString("CHARG"),
-                            headTable.getString("WERKS_ZR"),
-                            headTable.getString("BEIZHU")
+                            itemTable.getString("WERKS"),       // 工厂编号
+                            itemTable.getString("ZLTNO"),       // 领料/退料编号
+                            itemTable.getString("MATNR"),       // 物料编码
+                            itemTable.getString("MAKTX"),       // 物料描述
+                            itemTable.getBigDecimal("MENGE"),   // 物料数量
+                            itemTable.getString("MEINS"),       // 基本单位
+                            itemTable.getString("SOBKZ"),       // 特殊库存
+                            itemTable.getString("GRUND"),       // 移动原因
+                            itemTable.getString("GRTXT"),       // 原因
+                            itemTable.getString("LGORT"),       // 库存地点编码
+                            itemTable.getString("LGOBE"),       // 库存地点描述
+                            itemTable.getString("LGPBE"),       // 库存仓位
+                            itemTable.getString("CHARG"),       // 批次
+                            itemTable.getString("WERKS_ZR"),    // 转入工厂
+                            itemTable.getString("BEIZHU"),      // 备注
+                            // 无文档描述
+                            itemTable.getString("MBLNR"),       // 物料凭证编号
+                            itemTable.getString("MJAHR"),       // 物料凭证年度
+                            itemTable.getString("ZEILE"),       // 行项目号
+                            itemTable.getBigDecimal("DMBTR"),   // 本位币金额(单价)
+                            itemTable.getString("WAERS"),       // 货币码
+                            itemTable.getBigDecimal("LBKUM"),   // 交易后库存数量 - 以输入单位计
+                            itemTable.getBigDecimal("SALK3"),   // 本位币库存总价值
+                            itemTable.getString("VPRSV"),       // 价格控制标识
+                            itemTable.getBigDecimal("VERPR")    // 移动平均价或标准价
                     ));
                 }
             }
@@ -223,7 +236,7 @@ public class SapController {
         //
         try {
             JCoDestination destination = sapConnector.getDestination();
-            JCoFunction function = destination.getRepository().getFunction("BAPI_MATERIAL_GETLIST");
+            JCoFunction function = destination.getRepository().getFunction("ZPMS_001");
 
             // 设置输入参数
             /* JCoParameterList input = function.getImportParameterList();
@@ -232,7 +245,7 @@ public class SapController {
             input.setValue("IV_MAKTX", ""); // 物料描述 */
 
             // JCoTable materialRange = function.getTableParameterList().getTable("MATNR_RANGE");
-            function.getImportParameterList().setValue("MATERIAL_TYPE", "ALL");
+            function.getImportParameterList().setValue("CODE", "LFA1");
             // 执行 RFC 调用
             function.execute(destination);
 
@@ -245,20 +258,24 @@ public class SapController {
             } */
             List<SapMaterial> materialList = new ArrayList<>();
             // 处理返回的物料列表
-            JCoTable materialTable = function.getTableParameterList().getTable("RETURN");
-            for (int i = 0; i < materialTable.getNumRows(); i++) {
-                materialTable.setRow(i);
+            JCoTable timportTable = function.getTableParameterList().getTable("TIMPORT");
+            JCoTable texportTable = function.getTableParameterList().getTable("TEXPORT");
+            for (int i = 0; i < texportTable.getNumRows(); i++) {
+                texportTable.setRow(i);
 
-                SapMaterial material = new SapMaterial();
-                material.setMaterialNumber(materialTable.getString("MATERIAL"));
+                // SapMaterial material = new SapMaterial();
+                /* material.setMaterialNumber(materialTable.getString("MATERIAL"));
                 material.setMaterialDescription(materialTable.getString("MATL_DESC"));
                 material.setMaterialType(materialTable.getString("MATL_TYPE"));
                 material.setPlant(materialTable.getString("PLANT"));
                 material.setBaseUnit(materialTable.getString("BASE_UOM"));
                 material.setIndustrySector(materialTable.getString("INDUSTRY"));
 
-                materialList.add(material);
-                System.out.println("物料编码:" + material.getMaterialNumber());
+                materialList.add(material); */
+                System.out.println("当前供应商KV_CODE:" + texportTable.getString("KV_CODE"));
+                System.out.println("当前供应商KV_NAME:" + texportTable.getString("KV_NAME"));
+                System.out.println("当前供应商KV_KEY:" + texportTable.getString("KV_KEY"));
+                System.out.println("当前供应商KV_VALUE:" + texportTable.getString("KV_VALUE"));
             }
 
             // 释放资源
@@ -277,7 +294,8 @@ public class SapController {
      * 查询所有 SAP 工厂
      */
     private List<String> factoryCodes(){
-        List<SapOrgRespDTO> sapOrgs = sapOrgApi.getSapOrgByType(1);
+        List<SapOrgRespDTO> sapOrgs = TenantUtils.execute(1L, () -> sapOrgApi.getSapOrgByType(1));
+
         if (CollUtil.isEmpty(sapOrgs)) {
             return CollUtil.empty(String.class);
         }

+ 10 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/sap/service/IotSapService.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.pms.sap.service;
 
 import cn.iocoder.yudao.module.pms.sap.vo.IotSapMaterialVO;
+import cn.iocoder.yudao.module.pms.sap.vo.IotSapMerchantVO;
 
 import java.util.List;
 
@@ -13,8 +14,16 @@ public interface IotSapService {
      * 处理SAP接口返回的 物料主数据
      *
      * @param
-     * @return 设备
+     * @return
      */
     void processMaterials(List<IotSapMaterialVO>  sapMaterials);
 
+    /**
+     * 处理SAP接口返回的 客商 主数据
+     *
+     * @param
+     * @return
+     */
+    void processMerchants(List<IotSapMerchantVO>  sapMerchants);
+
 }

+ 128 - 20
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/sap/service/IotSapServiceImpl.java

@@ -4,10 +4,14 @@ 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.dal.dataobject.iotmaterial.IotMaterialDO;
 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.sap.vo.IotSapMaterialVO;
+import cn.iocoder.yudao.module.pms.sap.vo.IotSapMerchantVO;
+import cn.iocoder.yudao.module.supplier.dal.dataobject.product.SupplierDO;
+import cn.iocoder.yudao.module.supplier.dal.mysql.product.SupplierMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -17,10 +21,7 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 import java.util.concurrent.CountDownLatch;
 import java.util.stream.Collectors;
 
@@ -40,6 +41,8 @@ public class IotSapServiceImpl implements IotSapService {
     private IotMaterialMapper iotMaterialMapper;
     @Resource(name = PMS_THREAD_POOL_TASK_EXECUTOR)
     private ThreadPoolTaskExecutor pmsThreadPoolTaskExecutor;
+    @Autowired
+    private SupplierMapper supplierMapper;
 
     @Override
     public void processMaterials(List<IotSapMaterialVO> sapMaterials) {
@@ -50,8 +53,7 @@ public class IotSapServiceImpl implements IotSapService {
         // 1.新增物料
         // 查询所有物料集合A,如果 sapMaterials 中有物料不在A中 新增物料
         Set<String> existingMaterialCodes = new HashSet<>();
-        // List<String> materialCodes = iotSapMaterialMapper.allMaterialCodes();
-        List<IotMaterialDO> existMaterials = iotMaterialMapper.selectList();
+        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
@@ -76,6 +78,7 @@ public class IotSapServiceImpl implements IotSapService {
                 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);
@@ -83,24 +86,129 @@ public class IotSapServiceImpl implements IotSapService {
             });
         }
         if (CollUtil.isNotEmpty(tobeAddedMaterials)) {
-            CountDownLatch latch = new CountDownLatch(tobeAddedMaterials.size());
-            tobeAddedMaterials.forEach(material -> {
-                int tempBatchSize = 500;
-                for (int i = 0; i < tobeAddedMaterials.size(); i += tempBatchSize) {
-                    List<IotMaterialDO> batchMaterials = tobeAddedMaterials.subList(i, Math.min(i + tempBatchSize, tobeAddedMaterials.size()));
-                    pmsThreadPoolTaskExecutor.execute(() -> {
-                        try {
-                            iotMaterialMapper.insertBatch(batchMaterials);
-                        } finally {
-                            latch.countDown();
-                        }
-                    });
+            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);
+        if (CollUtil.isNotEmpty(existMaterials) && CollUtil.isNotEmpty(sapMaterialPair)) {
+            existMaterials.forEach(em -> {
+                if (sapMaterialPair.containsKey(em.getCode())) {
+                    em.setName(sapMaterialPair.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.记录接口调用日志
+    }
 
-        // 2.更新已有物料 物料描述可能会变化
+    @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)) {
 
-        // 3.记录接口调用日志
+            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();
+                    }
+                });
+            }
+        }
+    }
+
+    /**
+     * 创建物料编码到描述的映射
+     * @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,保留第一个值
+                ));
     }
 }

+ 18 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/sap/vo/IotSapMerchantVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.pms.sap.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - SAP 同步接口 客商 VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotSapMerchantVO {
+
+    @Schema(description = "客商编码", example = "000000003000000689")
+    private String code;
+
+    @Schema(description = "客商名称", example = "中国电信")
+    private String name;
+
+}

+ 9 - 2
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmaterial/IotMaterialService.java

@@ -1,8 +1,8 @@
 package cn.iocoder.yudao.module.pms.service.iotmaterial;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmaterial.vo.*;
-import cn.iocoder.yudao.module.pms.dal.dataobject.iotdevicecategorytemplateattrs.IotDeviceCategoryTemplateAttrsDO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotmaterial.vo.IotMaterialPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotmaterial.vo.IotMaterialSaveReqVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmaterial.IotMaterialDO;
 
 import javax.validation.Valid;
@@ -84,4 +84,11 @@ public interface IotMaterialService {
      * @return 物料列表
      */
     PageResult<IotMaterialDO> getMaterialsByDeviceBomId(IotMaterialPageReqVO reqVO);
+
+    /**
+     * 查询物料表中最大的id
+     *
+     * @return 最大的工单id
+     */
+    Long theMaxId();
 }

+ 9 - 5
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmaterial/IotMaterialServiceImpl.java

@@ -1,19 +1,17 @@
 package cn.iocoder.yudao.module.pms.service.iotmaterial;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmaterial.vo.*;
-import cn.iocoder.yudao.module.pms.dal.dataobject.iotbom.IotBomDO;
-import cn.iocoder.yudao.module.pms.dal.dataobject.iotdevicecategorytemplateattrs.IotDeviceCategoryTemplateAttrsDO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotmaterial.vo.IotMaterialPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotmaterial.vo.IotMaterialSaveReqVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmaterial.IotMaterialDO;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotmaterial.IotMaterialMapper;
 import cn.iocoder.yudao.module.pms.service.iotmaterialgroup.IotMaterialGroupService;
-import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
-
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -111,4 +109,10 @@ public class IotMaterialServiceImpl implements IotMaterialService {
         return iotMaterialMapper.getMaterialsByDeviceBomId(reqVO);
     }
 
+    @Override
+    public Long theMaxId() {
+        List<IotMaterialDO> materials = iotMaterialMapper.theMaxOne();
+        return CollUtil.isNotEmpty(materials) ? materials.get(0).getId() : 0l;
+    }
+
 }

+ 5 - 0
yudao-module-supplier/yudao-module-supplier-biz/src/main/java/cn/iocoder/yudao/module/supplier/dal/dataobject/product/SupplierDO.java

@@ -110,4 +110,9 @@ public class SupplierDO extends BaseDO {
     private String customerNature;
 
     private String sapCode;
+
+    /**
+     * sap 客商 同步日期
+     */
+    private LocalDateTime sapSyncTime;
 }

+ 1 - 4
yudao-module-supplier/yudao-module-supplier-biz/src/main/java/cn/iocoder/yudao/module/supplier/dal/mysql/product/SupplierMapper.java

@@ -1,12 +1,9 @@
 package cn.iocoder.yudao.module.supplier.dal.mysql.product;
 
-import java.util.*;
-
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.supplier.controller.admin.product.vo.SupplierPageReqVO;
-import cn.iocoder.yudao.module.supplier.dal.dataobject.product.CertificateDO;
 import cn.iocoder.yudao.module.supplier.dal.dataobject.product.SupplierDO;
 import org.apache.ibatis.annotations.Mapper;