package com.ruoyi.basic.service.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
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;
|
import com.ruoyi.basic.pojo.Product;
|
import com.ruoyi.basic.pojo.ProductModel;
|
import com.ruoyi.basic.service.IProductModelService;
|
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.utils.StringUtils;
|
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.mapper.SalesLedgerProductMapper;
|
import com.ruoyi.sales.pojo.SalesLedgerProduct;
|
import com.ruoyi.technology.mapper.TechnologyBomMapper;
|
import com.ruoyi.technology.pojo.TechnologyBom;
|
import lombok.AllArgsConstructor;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.web.multipart.MultipartFile;
|
|
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.concurrent.ThreadLocalRandom;
|
|
/**
|
* 【请填写功能名称】Service业务层处理
|
*
|
* @author ruoyi
|
* @date 2025-05-19
|
*/
|
@Service
|
@AllArgsConstructor
|
public class ProductModelServiceImpl extends ServiceImpl<ProductModelMapper, ProductModel> implements IProductModelService {
|
|
private final ProductMapper productMapper;
|
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) {
|
String model = StringUtils.trim(productModelDto.getModel());
|
String productCode = StringUtils.trim(productModelDto.getProductCode());
|
productModelDto.setModel(model);
|
productModelDto.setProductCode(productCode);
|
checkModelAndProductCodeUnique(model, productCode, productModelDto.getId());
|
|
if (productModelDto.getId() == null) {
|
ProductModel productModel = new ProductModel();
|
BeanUtils.copyProperties(productModelDto, productModel);
|
return productModelMapper.insert(productModel);
|
} else {
|
return productModelMapper.updateById(productModelDto);
|
}
|
}
|
|
private void checkModelAndProductCodeUnique(String model, String productCode, Long currentId) {
|
if (StringUtils.isEmpty(model) || StringUtils.isEmpty(productCode)) {
|
return;
|
}
|
LambdaQueryWrapper<ProductModel> queryWrapper = new LambdaQueryWrapper<>();
|
queryWrapper.eq(ProductModel::getModel, model)
|
.eq(ProductModel::getProductCode, productCode)
|
.ne(currentId != null, ProductModel::getId, currentId)
|
.last("limit 1");
|
ProductModel duplicateProductModel = productModelMapper.selectOne(queryWrapper);
|
if (duplicateProductModel != null) {
|
throw new ServiceException("对应的型号" + model + "的产品编码" + productCode + "已经存在");
|
}
|
}
|
|
|
@Override
|
public int delProductModel(Long[] ids) {
|
List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(new QueryWrapper<SalesLedgerProduct>()
|
.lambda().in(SalesLedgerProduct::getProductModelId, ids));
|
if (salesLedgerProducts != null && salesLedgerProducts.size() > 0) {
|
|
throw new RuntimeException("已经存在该产品的销售台账和采购台账");
|
}
|
|
// 是否存在BOM
|
List<TechnologyBom> technologyBoms = technologyBomMapper.selectList(new QueryWrapper<TechnologyBom>()
|
.lambda().in(TechnologyBom::getProductModelId, ids));
|
if (CollectionUtils.isNotEmpty(technologyBoms)) {
|
throw new RuntimeException("已经存在该产品的BOM数据");
|
}
|
|
return productModelMapper.deleteBatchIds(Arrays.asList(ids));
|
}
|
|
@Override
|
public List<ProductModel> selectModelList(ProductDto productDto) {
|
LambdaQueryWrapper<ProductModel> queryWrapper = new LambdaQueryWrapper<>();
|
queryWrapper.eq(ProductModel::getProductId, productDto.getId());
|
return productModelMapper.selectList(queryWrapper);
|
}
|
|
/**
|
* 根据id查询产品规格分页查询
|
*
|
* @param page
|
* @param productDto
|
* @return
|
*/
|
@Override
|
public IPage<ProductModel> modelListPage(Page page, ProductDto productDto) {
|
LambdaQueryWrapper<ProductModel> queryWrapper = new LambdaQueryWrapper<>();
|
queryWrapper.eq(ProductModel::getProductId, productDto.getId());
|
return productModelMapper.selectPage(page, queryWrapper);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public AjaxResult importProductModel(MultipartFile file) {
|
if (file == null || file.isEmpty()) {
|
return AjaxResult.error("请选择要导入的文件");
|
}
|
|
Map<String, List<ProductImportRowDto>> sheetMap;
|
try {
|
ExcelUtil<ProductImportRowDto> excelUtil = new ExcelUtil<>(ProductImportRowDto.class);
|
sheetMap = excelUtil.importExcelMultiSheet(IMPORT_SHEET_NAMES, file.getInputStream(), 0);
|
} catch (Exception e) {
|
log.error("解析产品导入文件异常", e);
|
throw new ServiceException("产品导入文件解析失败");
|
}
|
if (sheetMap.isEmpty()) {
|
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;
|
}
|
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;
|
String parentName = trimToNull(row.getProductParentName());
|
String categoryName = trimToNull(row.getProductCategoryName());
|
String productName = trimToNull(row.getProductName());
|
if (categoryName == null) {
|
throw new ServiceException("第 " + rowNum + " 行 [产品大类] 不能为空");
|
}
|
if (productName == null) {
|
throw new ServiceException("第 " + rowNum + " 行 [产品名称] 不能为空");
|
}
|
|
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;
|
}
|
|
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++;
|
}
|
}
|
|
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;
|
}
|
}
|