|
|
@@ -20,6 +20,7 @@ import com.qdb.sdk.model.response.SysGoodsVO;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
import org.springframework.scheduling.annotation.Async;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
@@ -27,7 +28,13 @@ import org.springframework.util.StringUtils;
|
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.ZoneId;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.HashSet;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
|
|
|
@Slf4j
|
|
|
@Service
|
|
|
@@ -39,6 +46,9 @@ public class ErpGoodsServiceImpl extends ServiceImpl<ErpGoodsMapper, ErpGoods> i
|
|
|
@Autowired(required = false)
|
|
|
private QdbClient qdbClient;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private JdbcTemplate jdbcTemplate;
|
|
|
+
|
|
|
@Async("taskExecutor")
|
|
|
@Override
|
|
|
public void syncFromErp() {
|
|
|
@@ -171,6 +181,97 @@ public class ErpGoodsServiceImpl extends ServiceImpl<ErpGoodsMapper, ErpGoods> i
|
|
|
log.info("商品解除ERP商品关联: productId={}, productName={}", productId, product.getName());
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public Map<String, Integer> autoBind() {
|
|
|
+ List<ErpGoods> erpGoodsList = list(new LambdaQueryWrapper<ErpGoods>()
|
|
|
+ .eq(ErpGoods::getStatus, 1));
|
|
|
+ if (erpGoodsList.isEmpty()) {
|
|
|
+ Map<String, Integer> empty = new HashMap<>();
|
|
|
+ empty.put("bound", 0);
|
|
|
+ empty.put("skipped", 0);
|
|
|
+ return empty;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 收集所有 ERP 商品 ID,批量查已关联的本地商品
|
|
|
+ List<Long> allErpGoodsIds = erpGoodsList.stream().map(ErpGoods::getErpGoodsId).toList();
|
|
|
+ Set<Long> boundErpGoodsIds = new HashSet<>(productMapper.selectList(new LambdaQueryWrapper<Product>()
|
|
|
+ .in(Product::getErpGoodsId, allErpGoodsIds)
|
|
|
+ .eq(Product::getIsDeleted, 0))
|
|
|
+ .stream().map(Product::getErpGoodsId).toList());
|
|
|
+
|
|
|
+ // 2. 收集未关联的 ERP 商品 code,批量查本地商品
|
|
|
+ Map<String, Long> codeToErpGoodsId = new LinkedHashMap<>();
|
|
|
+ for (ErpGoods erp : erpGoodsList) {
|
|
|
+ if (!boundErpGoodsIds.contains(erp.getErpGoodsId())) {
|
|
|
+ codeToErpGoodsId.put(erp.getGoodsCode(), erp.getErpGoodsId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ int alreadyBoundSkip = erpGoodsList.size() - codeToErpGoodsId.size();
|
|
|
+
|
|
|
+ // 3. 分批查本地商品(每批 100 个 code)
|
|
|
+ Map<String, List<Product>> nameToProducts = new HashMap<>();
|
|
|
+ List<String> allCodes = new ArrayList<>(codeToErpGoodsId.keySet());
|
|
|
+ for (int i = 0; i < allCodes.size(); i += 100) {
|
|
|
+ int end = Math.min(i + 100, allCodes.size());
|
|
|
+ List<String> batch = allCodes.subList(i, end);
|
|
|
+ List<Product> batchProducts = productMapper.selectList(new LambdaQueryWrapper<Product>()
|
|
|
+ .in(Product::getName, batch)
|
|
|
+ .eq(Product::getIsDeleted, 0));
|
|
|
+ for (Product p : batchProducts) {
|
|
|
+ nameToProducts.computeIfAbsent(p.getName(), k -> new ArrayList<>()).add(p);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 收集唯一匹配的关联对
|
|
|
+ java.util.List<Long> bindProductIds = new ArrayList<>();
|
|
|
+ java.util.List<Long> bindErpIds = new ArrayList<>();
|
|
|
+ int skipped = alreadyBoundSkip;
|
|
|
+
|
|
|
+ for (Map.Entry<String, Long> entry : codeToErpGoodsId.entrySet()) {
|
|
|
+ String goodsCode = entry.getKey();
|
|
|
+ Long erpGoodsId = entry.getValue();
|
|
|
+ List<Product> matches = nameToProducts.getOrDefault(goodsCode, List.of());
|
|
|
+
|
|
|
+ if (matches.size() == 1) {
|
|
|
+ bindProductIds.add(matches.get(0).getId());
|
|
|
+ bindErpIds.add(erpGoodsId);
|
|
|
+ log.info("自动关联: erpGoodsCode={}, productName={}", goodsCode, matches.get(0).getName());
|
|
|
+ } else {
|
|
|
+ skipped++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 批量 UPDATE 用 CASE WHEN(100 条一批)
|
|
|
+ int bound = 0;
|
|
|
+ for (int i = 0; i < bindProductIds.size(); i += 100) {
|
|
|
+ int end = Math.min(i + 100, bindProductIds.size());
|
|
|
+ List<Long> batchIds = bindProductIds.subList(i, end);
|
|
|
+ List<Long> batchErpIds = bindErpIds.subList(i, end);
|
|
|
+
|
|
|
+ StringBuilder sql = new StringBuilder(
|
|
|
+ "UPDATE t_product SET erp_goods_id = CASE id ");
|
|
|
+ for (int j = 0; j < batchIds.size(); j++) {
|
|
|
+ sql.append("WHEN ").append(batchIds.get(j))
|
|
|
+ .append(" THEN ").append(batchErpIds.get(j)).append(" ");
|
|
|
+ }
|
|
|
+ sql.append("END, update_time = NOW() WHERE id IN (");
|
|
|
+ for (int j = 0; j < batchIds.size(); j++) {
|
|
|
+ if (j > 0) sql.append(",");
|
|
|
+ sql.append(batchIds.get(j));
|
|
|
+ }
|
|
|
+ sql.append(")");
|
|
|
+
|
|
|
+ bound += jdbcTemplate.update(sql.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("一键自动关联完成: 成功 {} 条, 跳过 {} 条", bound, skipped);
|
|
|
+ Map<String, Integer> result = new HashMap<>();
|
|
|
+ result.put("bound", bound);
|
|
|
+ result.put("skipped", skipped);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
private void upsertErpGoods(SysGoodsVO goods) {
|
|
|
log.info("同步ERP商品: goodsId={}, goodsCode={}, goodsName={}",
|
|
|
goods.getGoodsId(), goods.getGoodsCode(), goods.getGoodsName());
|