From 2a4b95fd5718712ab08f63db49702dbbab2a158d Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期四, 10 九月 2026 10:38:36 +0800
Subject: [PATCH] fix: 添加产品导入模板,基础产品导入修改
---
src/main/java/com/ruoyi/basic/controller/ProductController.java | 9 -
src/main/resources/static/产品导入模板.xlsx | 0
src/main/java/com/ruoyi/basic/service/IProductService.java | 5
src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java | 44 +++++++-
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java | 200 +++++++++++++++++++++++++++++----------
src/main/java/com/ruoyi/basic/service/IProductModelService.java | 2
6 files changed, 194 insertions(+), 66 deletions(-)
diff --git a/src/main/java/com/ruoyi/basic/controller/ProductController.java b/src/main/java/com/ruoyi/basic/controller/ProductController.java
index 1c5224b..d6c8eda 100644
--- a/src/main/java/com/ruoyi/basic/controller/ProductController.java
+++ b/src/main/java/com/ruoyi/basic/controller/ProductController.java
@@ -5,13 +5,11 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ProductModelDto;
-import com.ruoyi.basic.dto.ProductModelExportDto;
import com.ruoyi.basic.dto.ProductTreeDto;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductModelService;
import com.ruoyi.basic.service.IProductService;
import com.ruoyi.basic.vo.ProductModelVo;
-import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
@@ -125,8 +123,8 @@
*/
@PostMapping("/import")
@Log(title = "瀵煎叆浜у搧", businessType = BusinessType.IMPORT)
- public AjaxResult importProductModel(@RequestParam("file") MultipartFile file, Integer productId) {
- return productModelService.importProductModel(file, productId);
+ public AjaxResult importProductModel(@RequestParam("file") MultipartFile file) {
+ return productModelService.importProductModel(file);
}
/**
@@ -136,7 +134,6 @@
@Operation(summary = "浜у搧瀵煎叆妯℃澘")
@Log(title = "浜у搧瀵煎叆妯℃澘", businessType = BusinessType.EXPORT)
public void importProduct(HttpServletResponse response) {
- ExcelUtil<ProductModelExportDto> excelUtil = new ExcelUtil<>(ProductModelExportDto.class);
- excelUtil.importTemplateExcel(response, "浜у搧瑙勬牸瀵煎叆妯℃澘");
+ productService.exportImportTemplate(response);
}
}
diff --git a/src/main/java/com/ruoyi/basic/service/IProductModelService.java b/src/main/java/com/ruoyi/basic/service/IProductModelService.java
index dd268d6..9361fee 100644
--- a/src/main/java/com/ruoyi/basic/service/IProductModelService.java
+++ b/src/main/java/com/ruoyi/basic/service/IProductModelService.java
@@ -34,5 +34,5 @@
*/
IPage<ProductModel> modelListPage(Page page , ProductDto productDto);
- AjaxResult importProductModel(MultipartFile file, Integer productId);
+ AjaxResult importProductModel(MultipartFile file);
}
diff --git a/src/main/java/com/ruoyi/basic/service/IProductService.java b/src/main/java/com/ruoyi/basic/service/IProductService.java
index 1abaad4..353cd28 100644
--- a/src/main/java/com/ruoyi/basic/service/IProductService.java
+++ b/src/main/java/com/ruoyi/basic/service/IProductService.java
@@ -8,11 +8,12 @@
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.vo.ProductModelVo;
+import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
public interface IProductService extends IService<Product> {
-
+
int addOrEditProduct(ProductDto productDto);
int delProductByIds(Long[] ids);
@@ -20,4 +21,6 @@
List<ProductTreeDto> selectProductList(ProductDto productDto);
IPage<ProductModelVo> listPageProductModel(Page<ProductModelVo> page, ProductModel productModel);
+
+ void exportImportTemplate(HttpServletResponse response);
}
diff --git a/src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
index be34fc0..bd46fb4 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -7,6 +7,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.ProductDto;
+import com.ruoyi.basic.dto.ProductImportRowDto;
import com.ruoyi.basic.dto.ProductModelDto;
import com.ruoyi.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
@@ -18,7 +19,6 @@
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.AjaxResult;
-import com.ruoyi.sales.dto.LossProductModelDto;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.technology.mapper.TechnologyBomMapper;
@@ -28,11 +28,13 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
-import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.concurrent.ThreadLocalRandom;
/**
* 銆愯濉啓鍔熻兘鍚嶇О銆慡ervice涓氬姟灞傚鐞�
@@ -48,6 +50,8 @@
private final SalesLedgerProductMapper salesLedgerProductMapper;
private final TechnologyBomMapper technologyBomMapper;
private ProductModelMapper productModelMapper;
+
+ private static final List<String> IMPORT_SHEET_NAMES = Arrays.asList("鍘熸枡", "鍗婃垚鍝�", "鎴愬搧");
@Override
public int addOrEditProductModel(ProductModelDto productModelDto) {
@@ -124,69 +128,161 @@
@Override
@Transactional(rollbackFor = Exception.class)
- public AjaxResult importProductModel(MultipartFile file, Integer productId) {
- if (productId == null) {
- return AjaxResult.error("璇峰厛閫夋嫨浜у搧鍐嶅鍏ヨ鏍煎瀷鍙�");
+ public AjaxResult importProductModel(MultipartFile file) {
+ if (file == null || file.isEmpty()) {
+ return AjaxResult.error("璇烽�夋嫨瑕佸鍏ョ殑鏂囦欢");
}
- Product product = productMapper.selectById(productId);
- if (product == null) {
- return AjaxResult.error("閫夋嫨鐨勪骇鍝佷笉瀛樺湪");
- }
-
+ Map<String, List<ProductImportRowDto>> sheetMap;
try {
- ExcelUtil<ProductModel> productModelExcelUtil = new ExcelUtil<>(ProductModel.class);
- List<ProductModel> productModelList = productModelExcelUtil.importExcel(file.getInputStream());
+ ExcelUtil<ProductImportRowDto> excelUtil = new ExcelUtil<>(ProductImportRowDto.class);
+ sheetMap = excelUtil.importExcelMultiSheet(IMPORT_SHEET_NAMES, file.getInputStream(), 0);
+ } catch (Exception e) {
+ log.error("瑙f瀽浜у搧瀵煎叆鏂囦欢寮傚父", e);
+ throw new ServiceException("浜у搧瀵煎叆鏂囦欢瑙f瀽澶辫触");
+ }
+ if (sheetMap.isEmpty()) {
+ return AjaxResult.error("鏈壘鍒� [鍘熸枡/鍗婃垚鍝�/鎴愬搧] 宸ヤ綔琛紝鎴栨枃浠朵腑娌℃湁鏁版嵁");
+ }
- if (CollectionUtils.isEmpty(productModelList)) {
- return AjaxResult.error("瀵煎叆鏁版嵁涓嶈兘涓虹┖");
+ int successCount = 0;
+ int skipCount = 0;
+ Set<String> generatedCodes = new HashSet<>();
+ for (String sheetName : IMPORT_SHEET_NAMES) {
+ List<ProductImportRowDto> rows = sheetMap.get(sheetName);
+ if (CollectionUtils.isEmpty(rows)) {
+ continue;
}
-
- // 鑾峰彇褰撳墠浜у搧涓嬫墍鏈夌殑瑙勬牸鍨嬪彿鍚�
- List<ProductModel> existingModels = list(new LambdaQueryWrapper<ProductModel>().eq(ProductModel::getProductId, productId));
- Set<String> existingModelNames = existingModels.stream().map(ProductModel::getModel).collect(Collectors.toSet());
-
- List<ProductModel> waitToSaveList = new ArrayList<>();
- int skipCount = 0;
-
- for (int i = 0; i < productModelList.size(); i++) {
- ProductModel item = productModelList.get(i);
+ Long topId = findOrCreateTopProduct(sheetName);
+ Map<String, Long> nodeCache = new HashMap<>();
+ Map<Long, Set<String>> codeCache = new HashMap<>();
+ for (int i = 0; i < rows.size(); i++) {
+ ProductImportRowDto row = rows.get(i);
int rowNum = i + 2;
-
- if (StringUtils.isEmpty(item.getProductCode())) {
- return AjaxResult.error("绗� " + rowNum + " 琛屽鍏ュけ璐�: [浜у搧缂栫爜] 涓嶈兘涓虹┖");
+ String parentName = trimToNull(row.getProductParentName());
+ String categoryName = trimToNull(row.getProductCategoryName());
+ String productName = trimToNull(row.getProductName());
+ if (categoryName == null) {
+ throw new ServiceException("绗� " + rowNum + " 琛� [浜у搧澶х被] 涓嶈兘涓虹┖");
}
- if (StringUtils.isEmpty(item.getModel())) {
- return AjaxResult.error("绗� " + rowNum + " 琛屽鍏ュけ璐�: [瑙勬牸鍨嬪彿] 涓嶈兘涓虹┖");
- }
- if (StringUtils.isEmpty(item.getUnit())) {
- return AjaxResult.error("绗� " + rowNum + " 琛屽鍏ュけ璐�: [鍗曚綅] 涓嶈兘涓虹┖");
+ if (productName == null) {
+ throw new ServiceException("绗� " + rowNum + " 琛� [浜у搧鍚嶇О] 涓嶈兘涓虹┖");
}
- // 鍘婚噸,濡傛灉宸插寘鍚鍨嬪彿,鍒欒烦杩�
- if (existingModelNames.contains(item.getModel())) {
+ String path = String.valueOf(topId);
+ Long parentNodeId = topId;
+ if (parentName != null) {
+ path = path + ">" + parentName;
+ parentNodeId = ensureChildNode(nodeCache, path, parentNodeId, parentName);
+ }
+ path = path + ">" + categoryName;
+ Long categoryId = ensureChildNode(nodeCache, path, parentNodeId, categoryName);
+ path = path + ">" + productName;
+ Long leafId = ensureChildNode(nodeCache, path, categoryId, productName);
+
+ String code = trimToNull(row.getProductCode());
+ if (code == null) {
+ code = generateUniqueCode(generatedCodes);
+ }
+ Set<String> codes = codeCache.computeIfAbsent(leafId, this::loadProductCodes);
+ if (!codes.add(code)) {
skipCount++;
continue;
}
- item.setProductId(product.getId());
- waitToSaveList.add(item);
-
- existingModelNames.add(item.getModel());
+ ProductModel productModel = new ProductModel();
+ productModel.setProductId(leafId);
+ productModel.setProductCode(code);
+ productModel.setModel(StringUtils.isEmpty(row.getModel()) ? "/" : row.getModel().trim());
+ productModel.setUnit(trimToNull(row.getUnit()));
+ productModelMapper.insert(productModel);
+ successCount++;
}
-
- if (!waitToSaveList.isEmpty()) {
- saveBatch(waitToSaveList);
- }
-
- if (skipCount == 0) {
- return AjaxResult.success(String.format("鎴愬姛瀵煎叆 %d 鏉℃暟鎹�", waitToSaveList.size()));
- } else {
- return AjaxResult.success(String.format("鎴愬姛瀵煎叆 %d 鏉★紝璺宠繃宸插瓨鍦ㄦ暟鎹� %d 鏉�", waitToSaveList.size(), skipCount));
- }
- } catch (Exception e) {
- log.error("瀵煎叆浜у搧瑙勬牸寮傚父", e);
- throw new ServiceException("瀵煎叆澶辫触");
}
+
+ String message = skipCount > 0
+ ? String.format("鎴愬姛瀵煎叆 %d 鏉★紝璺宠繃宸插瓨鍦� %d 鏉�", successCount, skipCount)
+ : String.format("鎴愬姛瀵煎叆 %d 鏉�", successCount);
+ return AjaxResult.success(message);
+ }
+
+ private Long findOrCreateTopProduct(String name) {
+ Product root = productMapper.selectOne(new LambdaQueryWrapper<Product>()
+ .isNull(Product::getParentId)
+ .eq(Product::getProductName, name)
+ .last("limit 1"));
+ if (root != null) {
+ return root.getId();
+ }
+ Product product = new Product();
+ product.setProductName(name);
+ productMapper.insert(product);
+ return product.getId();
+ }
+
+ private Long ensureChildNode(Map<String, Long> nodeCache, String path, Long parentId, String name) {
+ Long id = nodeCache.get(path);
+ if (id != null) {
+ return id;
+ }
+ Product existing = productMapper.selectOne(new LambdaQueryWrapper<Product>()
+ .eq(Product::getParentId, parentId)
+ .eq(Product::getProductName, name)
+ .last("limit 1"));
+ if (existing != null) {
+ nodeCache.put(path, existing.getId());
+ return existing.getId();
+ }
+ Product product = new Product();
+ product.setParentId(parentId);
+ product.setProductName(name);
+ productMapper.insert(product);
+ nodeCache.put(path, product.getId());
+ return product.getId();
+ }
+
+ private Set<String> loadProductCodes(Long productId) {
+ List<ProductModel> list = productModelMapper.selectList(new LambdaQueryWrapper<ProductModel>()
+ .eq(ProductModel::getProductId, productId));
+ Set<String> codes = new HashSet<>();
+ for (ProductModel productModel : list) {
+ if (productModel.getProductCode() != null) {
+ codes.add(productModel.getProductCode().trim());
+ }
+ }
+ return codes;
+ }
+
+ private String generateUniqueCode(Set<String> generatedCodes) {
+ for (int i = 0; i < 200; i++) {
+ String code = randomCode(5);
+ if (generatedCodes.contains(code)) {
+ continue;
+ }
+ Long count = productModelMapper.selectCount(new LambdaQueryWrapper<ProductModel>()
+ .eq(ProductModel::getProductCode, code));
+ if (count == null || count == 0) {
+ generatedCodes.add(code);
+ return code;
+ }
+ }
+ throw new ServiceException("鐢熸垚鍞竴浜у搧缂栫爜澶辫触锛岃閲嶈瘯");
+ }
+
+ private static String randomCode(int length) {
+ String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ sb.append(chars.charAt(ThreadLocalRandom.current().nextInt(chars.length())));
+ }
+ return sb.toString();
+ }
+
+ private static String trimToNull(String value) {
+ if (value == null) {
+ return null;
+ }
+ String trimmed = value.trim();
+ return trimmed.isEmpty() ? null : trimmed;
}
}
diff --git a/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
index 2b9eb40..b8f2f44 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
@@ -15,15 +15,23 @@
import com.ruoyi.basic.vo.ProductModelVo;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
+import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
@AllArgsConstructor
+@Slf4j
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {
private ProductMapper productMapper;
@@ -130,16 +138,40 @@
}
@Override
+ public void exportImportTemplate(HttpServletResponse response) {
+ String templatePath = "static/浜у搧瀵煎叆妯℃澘.xlsx";
+ try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(templatePath)) {
+ if (inputStream == null) {
+ throw new FileNotFoundException("妯℃澘鏂囦欢涓嶅瓨鍦細" + templatePath);
+ }
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+ response.setCharacterEncoding("utf-8");
+ String fileName = URLEncoder.encode("浜у搧瀵煎叆妯℃澘.xlsx", "utf-8").replaceAll("\\+", "%20");
+ response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
+ try (OutputStream outputStream = response.getOutputStream()) {
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = inputStream.read(buffer)) > 0) {
+ outputStream.write(buffer, 0, len);
+ }
+ outputStream.flush();
+ }
+ } catch (IOException e) {
+ log.error("瀵煎嚭浜у搧瀵煎叆妯℃澘澶辫触", e);
+ try {
+ response.getWriter().write("妯℃澘瀵煎嚭澶辫触锛�" + e.getMessage());
+ } catch (IOException ex) {
+ log.error("鍝嶅簲杈撳嚭閿欒", ex);
+ }
+ }
+ }
+
+ @Override
public int delProductByIds(Long[] ids) {
- // 1. 鍒犻櫎瀛愯〃 product_model 涓叧鑱旂殑鏁版嵁
LambdaQueryWrapper<ProductModel> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(ProductModel::getProductId, ids);
productModelMapper.delete(queryWrapper);
- // 2. 鍒犻櫎涓昏〃 product 鏁版嵁
- int deleteCount = productMapper.deleteBatchIds(Arrays.asList(ids));
-
- return deleteCount;
+ return productMapper.deleteBatchIds(Arrays.asList(ids));
}
-
}
diff --git "a/src/main/resources/static/\344\272\247\345\223\201\345\257\274\345\205\245\346\250\241\346\235\277.xlsx" "b/src/main/resources/static/\344\272\247\345\223\201\345\257\274\345\205\245\346\250\241\346\235\277.xlsx"
new file mode 100644
index 0000000..088e7a7
--- /dev/null
+++ "b/src/main/resources/static/\344\272\247\345\223\201\345\257\274\345\205\245\346\250\241\346\235\277.xlsx"
Binary files differ
--
Gitblit v1.9.3