From 1b9e55c492d8a9c84aaf43634a74c2f3f3ec84c6 Mon Sep 17 00:00:00 2001 From: 86158 <461356067@qq.com> Date: Wed, 4 Feb 2026 17:21:28 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E6=96=B0=E5=A2=9E=E4=BA=86?= =?UTF-8?q?=E9=87=87=E9=9B=86=E8=AE=BE=E5=A4=87=E6=A8=A1=E5=9E=8B-?= =?UTF-8?q?=E9=87=87=E9=9B=86=E7=82=B9=E3=80=81=E8=AE=BE=E5=A4=87=E7=AE=A1?= =?UTF-8?q?=E7=90=86-=E8=AE=BE=E5=A4=87=E5=85=B3=E9=94=AE=E4=BB=B6?= =?UTF-8?q?=E3=80=81=E5=A4=87=E4=BB=B6=E7=AE=A1=E7=90=86-=E5=A4=87?= =?UTF-8?q?=E4=BB=B6=E4=BF=A1=E6=81=AF=E7=9A=84=E6=95=B0=E6=8D=AE=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/product/ErpProductController.java | 52 ++++++- .../vo/product/ErpProductImportExcelVO.java | 68 ++++++++ .../vo/product/ErpProductImportRespVO.java | 44 ++++++ .../product/ErpProductCategoryMapper.java | 4 + .../dal/mysql/product/ErpProductMapper.java | 5 + .../product/ErpProductCategoryService.java | 8 + .../ErpProductCategoryServiceImpl.java | 5 + .../service/product/ErpProductService.java | 11 ++ .../product/ErpProductServiceImpl.java | 145 +++++++++++++++++- .../product/ErpProductUnitService.java | 7 + .../product/ErpProductUnitServiceImpl.java | 4 + .../DeviceModelAttributeController.java | 60 ++++++++ .../vo/DeviceModelAttributeImportExcelVO.java | 61 ++++++++ .../vo/DeviceModelAttributeImportRespVO.java | 28 ++++ .../vo/DeviceModelAttributeRespVO.java | 2 +- .../DeviceModelAttributeMapper.java | 4 + .../DeviceModelAttributeService.java | 8 + .../DeviceModelAttributeServiceImpl.java | 114 +++++++++++++- .../CriticalComponentController.java | 42 +++++ .../vo/CriticalComponentImportExcelVO.java | 40 +++++ .../vo/CriticalComponentImportRespVO.java | 44 ++++++ .../CriticalComponentMapper.java | 5 + .../CriticalComponentService.java | 5 + .../CriticalComponentServiceImpl.java | 59 +++++++ 24 files changed, 816 insertions(+), 9 deletions(-) create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductImportExcelVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductImportRespVO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/vo/DeviceModelAttributeImportExcelVO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/vo/DeviceModelAttributeImportRespVO.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/criticalcomponent/vo/CriticalComponentImportExcelVO.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/criticalcomponent/vo/CriticalComponentImportRespVO.java diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductController.java index 364189968c..83635f5fc0 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductController.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductController.java @@ -8,9 +8,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.module.erp.controller.admin.product.vo.category.ErpProductCategoryListReqVO; -import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductPageReqVO; -import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; -import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductSaveReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.*; import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductCategoryDO; import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper; @@ -19,14 +17,17 @@ import cn.iocoder.yudao.module.erp.service.product.ErpProductCategoryService; import cn.iocoder.yudao.module.erp.service.product.ErpProductService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.io.IOException; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @@ -169,4 +170,49 @@ public class ErpProductController { pageResult.getList()); } + @GetMapping("/get-import-template") + @Operation(summary = "获取产品导入模板") + @ApiAccessLog(operateType = EXPORT) + public void getImportTemplate(HttpServletResponse response) throws IOException { + // 准备模板数据 + List list = new ArrayList<>(); + list.add(ErpProductImportExcelVO.builder() + .name("备件1") + .barCode("BZ001") + .unitName("个") + .status(0) // 启用状态 + .standard("红色") + .remark("测试产品") + .expiryDay(30) + .safetyNumber(new BigDecimal("10.00")) + .build()); + list.add(ErpProductImportExcelVO.builder() + .name("备件2") + .barCode("BZ002") + .unitName("个") + .status(0) // 启用状态 + .standard("标准") + .remark("备件说明") + .expiryDay(30) + .safetyNumber(new BigDecimal("5.00")) + .build()); + // 输出 + ExcelUtils.write(response, "产品导入模板.xls", "数据", ErpProductImportExcelVO.class, list); + } + + @PostMapping("/import") + @Operation(summary = "导入产品") + @Parameters({ + @Parameter(name = "file", description = "Excel 文件", required = true), + @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true") + }) + public CommonResult importExcel( + @RequestParam("file") MultipartFile file, + @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception { + // 1. 读取 Excel 数据 + List list = ExcelUtils.read(file, ErpProductImportExcelVO.class); + // 2. 调用 Service 并返回结果 + return success(productService.importProductList(list, updateSupport)); + } + } \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductImportExcelVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductImportExcelVO.java new file mode 100644 index 0000000000..7aa223bcae --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductImportExcelVO.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product.vo.product; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * ERP 产品 Excel 导入 VO + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = false) +public class ErpProductImportExcelVO { + + @ExcelProperty("产品名称") + private String name; + + @ExcelProperty("产品条码") + private String barCode; + +// @ExcelIgnore +// private String categoryName; + + @ExcelIgnore + private String subCategoryName; + + @ExcelProperty("单位") + private String unitName; + + @ExcelProperty("产品状态") + private Integer status; + + @ExcelProperty("产品规格") + private String standard; + + @ExcelProperty("产品备注") + private String remark; + + @ExcelProperty("保质期天数") + private Integer expiryDay; + + @ExcelProperty("预警库存") + private BigDecimal safetyNumber; + + // 以下是辅助字段,不在Excel中显示 + @ExcelIgnore + private Long categoryId; // 分类ID + + @ExcelIgnore + private Long subCategoryId; // 子分类ID + + @ExcelIgnore + private Long unitId; // 单位ID + + @ExcelIgnore + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductImportRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductImportRespVO.java new file mode 100644 index 0000000000..4e886265b8 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductImportRespVO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product.vo.product; + +import lombok.Builder; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * ERP 产品 Excel 导入响应 VO + */ +@Data +@Builder +public class ErpProductImportRespVO { + + /** + * 成功导入的产品名称列表 + */ + private List createNames; + + /** + * 更新的产品名称列表 + */ + private List updateNames; + + /** + * 失败的产品名称列表,key: 产品名称,value: 失败原因 + */ + private Map failureNames; + + public ErpProductImportRespVO() { + this.createNames = new ArrayList<>(); + this.updateNames = new ArrayList<>(); + this.failureNames = new LinkedHashMap<>(); + } + + public ErpProductImportRespVO(List createNames, List updateNames, Map failureNames) { + this.createNames = createNames; + this.updateNames = updateNames; + this.failureNames = failureNames; + } +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductCategoryMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductCategoryMapper.java index 70bbc64299..987d148440 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductCategoryMapper.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductCategoryMapper.java @@ -27,6 +27,10 @@ public interface ErpProductCategoryMapper extends BaseMapperX { .in("category_id" ,categoryIds)); } + default List selectListByNameIn(Collection names) { + return selectList(new LambdaQueryWrapperX().in(ErpProductDO::getName, names)); + } + } \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryService.java index 629f58bd35..00a4cc1431 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryService.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryService.java @@ -76,4 +76,12 @@ public interface ErpProductCategoryService { return convertMap(getProductCategoryList(ids), ErpProductCategoryDO::getId); } + /** + * 根据名称获取产品分类 + * + * @param name 分类名称 + * @return 产品分类 + */ + ErpProductCategoryDO getProductCategoryByName(String name); + } \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryServiceImpl.java index 70062c0bb9..0b1b2384a6 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryServiceImpl.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryServiceImpl.java @@ -146,4 +146,9 @@ public class ErpProductCategoryServiceImpl implements ErpProductCategoryService return erpProductCategoryMapper.selectBatchIds(ids); } + @Override + public ErpProductCategoryDO getProductCategoryByName(String name) { + return erpProductCategoryMapper.selectByName(name); + } + } \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductService.java index ac7005db1a..66bc03d8fb 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductService.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductService.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.erp.service.product; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductImportExcelVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductImportRespVO; import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductPageReqVO; import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductSaveReqVO; @@ -123,4 +125,13 @@ public interface ErpProductService { return convertMap(getProductList(ids), ErpProductDO::getId); } List selectByCategorys(List categoryIds); + + /** + * 导入产品列表 + * + * @param importProducts 导入的产品列表 + * @param isUpdateSupport 是否支持更新 + * @return 导入结果 + */ + ErpProductImportRespVO importProductList(List importProducts, boolean isUpdateSupport); } \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java index a053e5d3e1..cfbc400fe8 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java @@ -1,10 +1,15 @@ package cn.iocoder.yudao.module.erp.service.product; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductImportExcelVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductImportRespVO; import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductPageReqVO; import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductSaveReqVO; @@ -16,17 +21,23 @@ import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductCategoryMapper; import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper; import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockMapper; import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; import org.springframework.context.annotation.Lazy; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import javax.validation.ConstraintViolationException; +import javax.validation.Valid; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; @@ -178,11 +189,15 @@ public class ErpProductServiceImpl implements ErpProductService { } Map categoryMap = productCategoryService.getProductCategoryMap( convertSet(list, ErpProductDO::getCategoryId)); + Map subCategoryMap = productCategoryService.getProductCategoryMap( + convertSet(list, ErpProductDO::getSubCategoryId)); Map unitMap = productUnitService.getProductUnitMap( convertSet(list, ErpProductDO::getUnitId)); return BeanUtils.toBean(list, ErpProductRespVO.class, product -> { MapUtils.findAndThen(categoryMap, product.getCategoryId(), category -> product.setCategoryName(category.getName())); + MapUtils.findAndThen(subCategoryMap, product.getSubCategoryId(), + subCategory -> product.setSubCategoryName(subCategory.getName())); MapUtils.findAndThen(unitMap, product.getUnitId(), unit -> product.setUnitName(unit.getName())); }); @@ -206,4 +221,126 @@ public class ErpProductServiceImpl implements ErpProductService { List selectByCategorys(List categoryIds){ return productMapper.selectByCategorys(categoryIds); } + + @Override + @Transactional(rollbackFor = Exception.class) + public ErpProductImportRespVO importProductList(List importProducts, boolean isUpdateSupport) { + // 1. 参数校验 + if (CollUtil.isEmpty(importProducts)) { + throw exception(PRODUCT_NOT_EXISTS); + } + + // 2. 初始化返回结果 + ErpProductImportRespVO respVO = ErpProductImportRespVO.builder() + .createNames(new ArrayList<>()) + .updateNames(new ArrayList<>()) + .failureNames(new LinkedHashMap<>()) + .build(); + + // 3. 检查Excel中是否有重复的产品名称 + Set excelNames = new HashSet<>(); + Set duplicateNamesInExcel = new HashSet<>(); + for (ErpProductImportExcelVO importProduct : importProducts) { + if (!excelNames.add(importProduct.getName())) { + duplicateNamesInExcel.add(importProduct.getName()); + } + } + + // 记录Excel中重复的产品名称 + for (String duplicateName : duplicateNamesInExcel) { + respVO.getFailureNames().put(duplicateName, "Excel中存在重复的产品名称: " + duplicateName); + } + + // 4. 获取数据库中已存在的产品 + Set uniqueNamesToCheck = new HashSet<>(excelNames); + uniqueNamesToCheck.removeAll(duplicateNamesInExcel); // 排除Excel中的重复项 + + List existingProducts = new ArrayList<>(); + if (!uniqueNamesToCheck.isEmpty()) { + existingProducts = productMapper.selectListByNameIn(uniqueNamesToCheck); + } + + Map existingProductMap = existingProducts.stream() + .collect(Collectors.toMap(ErpProductDO::getName, item -> item)); + + // 5. 遍历导入数据 + for (ErpProductImportExcelVO importProduct : importProducts) { + // 如果已在失败列表中,则跳过 + if (respVO.getFailureNames().containsKey(importProduct.getName())) { + continue; + } + + try { + // 5.1 先处理分类和单位ID,确保它们被设置 + ErpProductDO productDOForConversion = convertToProductDO(importProduct); + + // 5.2 转换为保存对象 + ProductSaveReqVO saveReqVO = BeanUtils.toBean(importProduct, ProductSaveReqVO.class); + + // 5.3 设置saveReqVO中的ID值,以通过验证 + saveReqVO.setCategoryId(productDOForConversion.getCategoryId()); + saveReqVO.setUnitId(productDOForConversion.getUnitId()); + + // 5.4 字段校验 + ValidationUtils.validate(saveReqVO); + } catch (ConstraintViolationException ex) { + respVO.getFailureNames().put(importProduct.getName(), ex.getMessage()); + continue; + } + + // 5.2 检查是否已存在 + ErpProductDO existProduct = existingProductMap.get(importProduct.getName()); + if (existProduct == null) { + try { + // 5.3 创建新记录 + ErpProductDO newProduct = convertToProductDO(importProduct); + // 设置创建时间为当前时间 + newProduct.setCreateTime(LocalDateTime.now()); + productMapper.insert(newProduct); + respVO.getCreateNames().add(importProduct.getName()); + } catch (DuplicateKeyException e) { + // 处理并发插入或其他原因导致的重复键异常 + respVO.getFailureNames().put(importProduct.getName(), "该产品已存在"); + } + } else if (isUpdateSupport) { + // 5.4 更新已有记录 + ErpProductDO updateProduct = convertToProductDO(importProduct); + updateProduct.setId(existProduct.getId()); + // 保持原有创建时间不变 + updateProduct.setCreateTime(existProduct.getCreateTime()); + productMapper.updateById(updateProduct); + respVO.getUpdateNames().add(importProduct.getName()); + } else { + // 5.5 已存在且不支持更新 + respVO.getFailureNames().put(importProduct.getName(), "该产品已存在"); + } + } + + return respVO; + } + + private ErpProductDO convertToProductDO(ErpProductImportExcelVO importProduct) { + ErpProductDO productDO = BeanUtils.toBean(importProduct, ErpProductDO.class); + + // 直接设置分类ID为5 + productDO.setCategoryId(5L); + + // 设置固定子分类名称为"备件" + productDO.setSubCategoryName("备件"); + // 查找对应的子分类ID + ErpProductCategoryDO subCategoryDO = productCategoryService.getProductCategoryByName("备件"); + if (subCategoryDO != null) { + productDO.setSubCategoryId(subCategoryDO.getId()); + } + + // 处理单位名称 + if (StrUtil.isNotBlank(importProduct.getUnitName())) { + ErpProductUnitDO unitDO = productUnitService.getProductUnitByName(importProduct.getUnitName()); + if (unitDO != null) { + productDO.setUnitId(unitDO.getId()); + } + } + + return productDO; + } } \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitService.java index 10dcb3dbd7..082031b6c7 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitService.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitService.java @@ -92,4 +92,11 @@ public interface ErpProductUnitService { */ List getProductUnitList(); + /** + * 根据名称获取产品单位 + * + * @param name 单位名称 + * @return 产品单位 + */ + ErpProductUnitDO getProductUnitByName(String name); } \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitServiceImpl.java index 22b1eee5ab..868ba7ed03 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitServiceImpl.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitServiceImpl.java @@ -125,4 +125,8 @@ public class ErpProductUnitServiceImpl implements ErpProductUnitService { return productUnitMapper.selectList(); } + @Override + public ErpProductUnitDO getProductUnitByName(String name) { + return productUnitMapper.selectByName(name); + } } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/DeviceModelAttributeController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/DeviceModelAttributeController.java index cf7ba4b025..71bfb7a49f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/DeviceModelAttributeController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/DeviceModelAttributeController.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.iot.controller.admin.devicemodelattribute; import cn.iocoder.yudao.module.iot.dal.dataobject.deviceattributetype.DeviceAttributeTypeDO; +import cn.iocoder.yudao.module.iot.dal.mysql.devicemodelattribute.DeviceModelAttributeMapper; +import io.swagger.v3.oas.annotations.Parameters; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import org.springframework.validation.annotation.Validated; @@ -30,6 +32,7 @@ import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*; import cn.iocoder.yudao.module.iot.controller.admin.devicemodelattribute.vo.*; import cn.iocoder.yudao.module.iot.dal.dataobject.devicemodelattribute.DeviceModelAttributeDO; import cn.iocoder.yudao.module.iot.service.devicemodelattribute.DeviceModelAttributeService; +import org.springframework.web.multipart.MultipartFile; @Tag(name = "管理后台 - 采集设备模型-点位管理") @RestController @@ -39,6 +42,8 @@ public class DeviceModelAttributeController { @Resource private DeviceModelAttributeService deviceModelAttributeService; + @Resource + private DeviceModelAttributeMapper deviceModelAttributeMapper; @PostMapping("/create") @Operation(summary = "创建采集设备模型-点位管理") @@ -120,4 +125,59 @@ public class DeviceModelAttributeController { List> deviceModelAttribute = deviceModelAttributeService.operationAnalysisDetails(deviceId,modelId,collectionStartTime,collectionEndTime); return success(deviceModelAttribute); } + + /////////////// 以下是新增的代码 /////////////// + @GetMapping("/get-import-template") + @Operation(summary = "获取采集设备模型-点位管理导入模板") + @PreAuthorize("@ss.hasPermission('iot:device-model-attribute:import')") + @ApiAccessLog(operateType = EXPORT) + public void getImportTemplate(HttpServletResponse response) throws IOException { + // 准备模板数据 + List list = new ArrayList<>(); + list.add(DeviceModelAttributeImportExcelVO.builder() + .attributeCode("test") + .attributeName("温度") +// .attributeType("1") + .typeName("报警信息") + .dataType("int64") + .address("AI0") + .dataUnit("s") + .ratio(1.0) + .remark("温度传感器") + .build()); + list.add(DeviceModelAttributeImportExcelVO.builder() + .attributeCode("humidity_001") + .attributeName("湿度") +// .attributeType("2") + .typeName("能源参数") + .dataType("int64") + .address("AI1") + .dataUnit("s") + .ratio(1.0) + .remark("湿度传感器") + .build()); + // 输出 + ExcelUtils.write(response, "采集设备模型-点位管理导入模板.xls", "数据", DeviceModelAttributeImportExcelVO.class, list); + } + + + @PostMapping("/import") + @Operation(summary = "导入设备采集点位") // 对应 User 的 "导入用户" + @Parameters({ + @Parameter(name = "file", description = "Excel 文件", required = true), + @Parameter(name = "deviceModelId", description = "设备型号ID", required = true, example = "1024"), + @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true") + }) // 完全复用你的 Parameters 注解格式 + @PreAuthorize("@ss.hasPermission('system:device-model-attribute:import')") // 对齐 User 的权限码风格 + public CommonResult importExcel( + @RequestParam("file") MultipartFile file, + @RequestParam("deviceModelId") Long deviceModelId, + @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception { + // 1. 读取 Excel 数据 - 完全复用你的 ExcelUtils.read 调用方式 + List list = ExcelUtils.read(file, DeviceModelAttributeImportExcelVO.class); + // 2. 调用 Service 并返回结果 - 完全复用你的 success 包装方式 + return success(deviceModelAttributeService.importAttributeList(list, deviceModelId, updateSupport)); + } + + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/vo/DeviceModelAttributeImportExcelVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/vo/DeviceModelAttributeImportExcelVO.java new file mode 100644 index 0000000000..9c37662ab9 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/vo/DeviceModelAttributeImportExcelVO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.iot.controller.admin.devicemodelattribute.vo; + +//import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +//import cn.iocoder.yudao.module.system.enums.DictTypeConstants; // 沿用你项目中的字典枚举 +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * 设备采集点位 Excel 导入 VO + * 【完全对齐 UserImportExcelVO 注解风格和结构】 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = false) // 完全复用你的配置,避免导入问题 +public class DeviceModelAttributeImportExcelVO { + + @ExcelProperty("点位编码") // 对应 User 的 "登录名称" + private String attributeCode; + + @ExcelProperty("点位名称") // 对应 User 的 "用户名称" + private String attributeName; + + // 点位类型,不在Excel中显示 + @ExcelIgnore + private String attributeType; + + @ExcelProperty("点位类型名称") + private String typeName; + + @ExcelProperty(value = "数据类型") // 对应 User 的 "用户性别" +// , converter = DictConvert.class +// @DictFormat("device_data_type") // 替换为你项目中数据类型的字典编码,对齐 User 的 DictFormat 用法 + private String dataType; + + @ExcelProperty("寄存器地址") + private String address; + + @ExcelProperty("单位") // 对应 User 的 "用户邮箱" + private String dataUnit; + + @ExcelProperty("倍率") + private Double ratio; + + @ExcelProperty("备注") + private String remark; + + // 设备型号ID,不在Excel中显示,由前端传入 + @ExcelIgnore + private Long deviceModelId; + + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/vo/DeviceModelAttributeImportRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/vo/DeviceModelAttributeImportRespVO.java new file mode 100644 index 0000000000..c4cb038984 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/vo/DeviceModelAttributeImportRespVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.iot.controller.admin.devicemodelattribute.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 管理后台 - 设备采集点位导入 Response VO + * 【完全对齐 UserImportRespVO 注解风格和结构】 + */ +@Schema(description = "管理后台 - 设备采集点位导入 Response VO") +@Data +@Builder +public class DeviceModelAttributeImportRespVO { + + @Schema(description = "创建成功的点位编码数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List createCodes; // 对应 User 的 createUsernames,替换为点位唯一标识(编码) + + @Schema(description = "更新成功的点位编码数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List updateCodes; // 对应 User 的 updateUsernames,替换为点位唯一标识(编码) + + @Schema(description = "导入失败的点位集合,key 为点位编码,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED) + private Map failureCodes; // 对应 User 的 failureUsernames,替换为点位唯一标识(编码) + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/vo/DeviceModelAttributeRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/vo/DeviceModelAttributeRespVO.java index 4d02dabfee..b82a719a4d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/vo/DeviceModelAttributeRespVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicemodelattribute/vo/DeviceModelAttributeRespVO.java @@ -27,7 +27,7 @@ public class DeviceModelAttributeRespVO { @Schema(description = "点位类型", example = "1") @ExcelProperty("点位类型") - private Long attributeType; + private String attributeType; @Schema(description = "类型名称", example = "1") @ExcelProperty("类型名称") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicemodelattribute/DeviceModelAttributeMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicemodelattribute/DeviceModelAttributeMapper.java index f33cb9ad72..16eb683b8a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicemodelattribute/DeviceModelAttributeMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicemodelattribute/DeviceModelAttributeMapper.java @@ -65,4 +65,8 @@ public interface DeviceModelAttributeMapper extends BaseMapperX> operationAnalysisDetails(Long deviceId, Long modelId,String collectionStartTime, String collectionEndTime); List getDeviceModelAttributeList(Long id); + /** + * 导入设备采集点位列表 + * 【对应 UserService 的 importUserList 方法】 + */ + DeviceModelAttributeImportRespVO importAttributeList(List importAttributes, Long deviceModelId, boolean isUpdateSupport); + + + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicemodelattribute/DeviceModelAttributeServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicemodelattribute/DeviceModelAttributeServiceImpl.java index 4c672d7485..521bccf305 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicemodelattribute/DeviceModelAttributeServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicemodelattribute/DeviceModelAttributeServiceImpl.java @@ -1,7 +1,11 @@ package cn.iocoder.yudao.module.iot.service.devicemodelattribute; +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.module.iot.dal.dataobject.deviceattributetype.DeviceAttributeTypeDO; import cn.iocoder.yudao.module.iot.dal.mysql.deviceattributetype.DeviceAttributeTypeMapper; +import cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.iot.service.device.TDengineService; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.fasterxml.jackson.core.type.TypeReference; @@ -10,6 +14,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; +import javax.validation.ConstraintViolationException; + import org.springframework.validation.annotation.Validated; import org.springframework.transaction.annotation.Transactional; @@ -36,7 +42,7 @@ import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; @Service @Validated @Slf4j -public class DeviceModelAttributeServiceImpl implements DeviceModelAttributeService { + public class DeviceModelAttributeServiceImpl implements DeviceModelAttributeService { @Resource private DeviceModelAttributeMapper deviceModelAttributeMapper; @@ -207,4 +213,110 @@ public class DeviceModelAttributeServiceImpl implements DeviceModelAttributeServ } + @Override + @Transactional(rollbackFor = Exception.class) // 完全复用你的事务注解,异常回滚所有导入 + public DeviceModelAttributeImportRespVO importAttributeList(List importAttributes, Long deviceModelId, boolean isUpdateSupport) { + // 1.1 参数校验 - 完全复刻 User 的空列表校验逻辑 + if (CollUtil.isEmpty(importAttributes)) { + throw exception(DEVICE_MODEL_ATTRIBUTE_NOT_EXISTS); + } + // 1.2 校验设备型号ID是否存在 + if (deviceModelId == null) { + throw exception(DEVICE_MODEL_NOT_EXISTS); + } + + // 2. 构建点位类型名称到ID的映射 + List attributeTypes = deviceAttributeTypeMapper.selectList(); + Map typeNameToIdMap = attributeTypes.stream() + .collect(Collectors.toMap(DeviceAttributeTypeDO::getName, type -> String.valueOf(type.getId()))); + + // 3. 初始化返回 VO - 完全复刻 User 的 builder 方式,使用 LinkedHashMap 保持顺序 + DeviceModelAttributeImportRespVO respVO = DeviceModelAttributeImportRespVO.builder() + .createCodes(new ArrayList<>()) + .updateCodes(new ArrayList<>()) + .failureCodes(new LinkedHashMap<>()) + .build(); + + // 4. 遍历,逐个创建 or 更新 - 完全复刻 User 的 forEach 逻辑 + importAttributes.forEach(importAttribute -> { + // 4.1 设置设备型号ID + importAttribute.setDeviceModelId(deviceModelId); + + // 4.2 处理点位类型转换 + String typeName = importAttribute.getTypeName(); + if (StringUtils.isBlank(typeName)) { + respVO.getFailureCodes().put(importAttribute.getAttributeCode(), "点位类型名称不能为空"); + return; + } + String attributeTypeId = typeNameToIdMap.get(typeName); + if (StringUtils.isBlank(attributeTypeId)) { + respVO.getFailureCodes().put(importAttribute.getAttributeCode(), "点位类型名称无效: " + typeName); + return; + } + // 设置点位类型ID + importAttribute.setAttributeType(attributeTypeId); + + // 4.3.1 校验字段是否符合要求 - 完全复刻 User 的 ValidationUtils.validate 调用 + try { + // 注意:如果有 DeviceModelAttributeSaveReqVO,替换为该 VO;无则直接校验 importAttribute + ValidationUtils.validate(BeanUtils.toBean(importAttribute, DeviceModelAttributeSaveReqVO.class)); + } catch (ConstraintViolationException ex) { + respVO.getFailureCodes().put(importAttribute.getAttributeCode(), ex.getMessage()); + return; + } + + // 4.3.2 业务规则校验 - 完全复刻 User 的 validateUserForCreateOrUpdate 逻辑 + try { + validateAttributeForCreateOrUpdate(deviceModelId, importAttribute.getAttributeCode()); + } catch (ServiceException ex) { + respVO.getFailureCodes().put(importAttribute.getAttributeCode(), ex.getMessage()); + return; + } + + // 4.4.1 判断如果不存在,进行插入 - 完全复刻 User 的 selectByUsername 逻辑 + DeviceModelAttributeDO existAttribute = deviceModelAttributeMapper.selectByAttributeCode(importAttribute.getAttributeCode()); + if (existAttribute == null) { + // 创建DO对象并设置点位类型名称 + DeviceModelAttributeDO deviceModelAttribute = BeanUtils.toBean(importAttribute, DeviceModelAttributeDO.class); + deviceModelAttribute.setTypeName(typeName); // 设置点位类型名称 + deviceModelAttributeMapper.insert(deviceModelAttribute); + respVO.getCreateCodes().add(importAttribute.getAttributeCode()); + return; + } + + // 4.4.2 如果存在,判断是否允许更新 - 完全复刻 User 的更新逻辑 + if (!isUpdateSupport) { + respVO.getFailureCodes().put(importAttribute.getAttributeCode(), DEVICE_MODEL_ATTRIBUTE_NOT_EXISTS.getMsg()); + return; + } + + // 创建更新对象并设置点位类型名称 + DeviceModelAttributeDO updateAttribute = BeanUtils.toBean(importAttribute, DeviceModelAttributeDO.class); + updateAttribute.setId(existAttribute.getId()); // 复用已有数据的 ID + updateAttribute.setTypeName(typeName); // 设置点位类型名称 + deviceModelAttributeMapper.updateById(updateAttribute); + respVO.getUpdateCodes().add(importAttribute.getAttributeCode()); + }); + + return respVO; + } + + /** + * 校验设备采集点位创建/更新的业务规则 + * 【对应 User 的 validateUserForCreateOrUpdate 方法】 + */ + private void validateAttributeForCreateOrUpdate(Long deviceModelId, String code) { + // 1. 校验设备型号是否存在(可根据你的业务补充,对应 User 的部门校验) + if (deviceModelId == null) { + throw exception(ErrorCodeConstants.DEVICE_MODEL_NOT_EXISTS); // 替换为你项目中的设备型号不存在错误码 + } + // 2. 可补充其他业务校验(如数据类型合法性、单位长度等) + } + + + + + + + } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/criticalcomponent/CriticalComponentController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/criticalcomponent/CriticalComponentController.java index b84d86a756..88a7300f29 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/criticalcomponent/CriticalComponentController.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/criticalcomponent/CriticalComponentController.java @@ -32,6 +32,9 @@ import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*; import cn.iocoder.yudao.module.mes.controller.admin.criticalcomponent.vo.*; import cn.iocoder.yudao.module.mes.dal.dataobject.criticalcomponent.CriticalComponentDO; import cn.iocoder.yudao.module.mes.service.criticalcomponent.CriticalComponentService; +import org.springframework.web.multipart.MultipartFile; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.Parameter; @Tag(name = "管理后台 - 设备关键件") @RestController @@ -128,4 +131,43 @@ public class CriticalComponentController { ExcelUtils.write(response, fileName, "数据", CriticalComponentExcelVO.class,list); } + @GetMapping("/get-import-template") + @Operation(summary = "获取设备关键件导入模板") + @PreAuthorize("@ss.hasPermission('mes:critical-component:import')") + @ApiAccessLog(operateType = EXPORT) + public void getImportTemplate(HttpServletResponse response) throws IOException { + // 准备模板数据 + List list = new ArrayList<>(); + list.add(CriticalComponentImportExcelVO.builder() + .code("TEST001") + .name("电机") + .description("主驱动电机") + .remark("西门子品牌") + .build()); + list.add(CriticalComponentImportExcelVO.builder() + .code("TEST002") + .name("传感器") + .description("温度传感器") + .remark("欧姆龙品牌") + .build()); + // 输出 + ExcelUtils.write(response, "设备关键件导入模板.xls", "数据", CriticalComponentImportExcelVO.class, list); + } + + @PostMapping("/import") + @Operation(summary = "导入设备关键件") + @Parameters({ + @Parameter(name = "file", description = "Excel 文件", required = true), + @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true") + }) + @PreAuthorize("@ss.hasPermission('mes:critical-component:import')") + public CommonResult importExcel( + @RequestParam("file") MultipartFile file, + @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception { + // 1. 读取 Excel 数据 + List list = ExcelUtils.read(file, CriticalComponentImportExcelVO.class); + // 2. 调用 Service 并返回结果 + return success(criticalComponentService.importCriticalComponentList(list, updateSupport)); + } + } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/criticalcomponent/vo/CriticalComponentImportExcelVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/criticalcomponent/vo/CriticalComponentImportExcelVO.java new file mode 100644 index 0000000000..83a3fe85e8 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/criticalcomponent/vo/CriticalComponentImportExcelVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.mes.controller.admin.criticalcomponent.vo; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +/** + * 设备关键件 Excel 导入 VO + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = false) +public class CriticalComponentImportExcelVO { + + @ExcelProperty("编码") + private String code; + + @ExcelProperty("名称") + private String name; + + @ExcelProperty("描述") + private String description; + + @ExcelProperty("备注") + private String remark; + + // 创建时间,不在Excel中显示,自动生成 + @ExcelIgnore + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/criticalcomponent/vo/CriticalComponentImportRespVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/criticalcomponent/vo/CriticalComponentImportRespVO.java new file mode 100644 index 0000000000..fc62102358 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/criticalcomponent/vo/CriticalComponentImportRespVO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.mes.controller.admin.criticalcomponent.vo; + +import lombok.Builder; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 设备关键件 Excel 导入响应 VO + */ +@Data +@Builder +public class CriticalComponentImportRespVO { + + /** + * 成功导入的编码列表 + */ + private List createCodes; + + /** + * 更新的编码列表 + */ + private List updateCodes; + + /** + * 失败的编码列表,key: 编码,value: 失败原因 + */ + private Map failureCodes; + + public CriticalComponentImportRespVO() { + this.createCodes = new ArrayList<>(); + this.updateCodes = new ArrayList<>(); + this.failureCodes = new LinkedHashMap<>(); + } + + public CriticalComponentImportRespVO(List createCodes, List updateCodes, Map failureCodes) { + this.createCodes = createCodes; + this.updateCodes = updateCodes; + this.failureCodes = failureCodes; + } +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/criticalcomponent/CriticalComponentMapper.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/criticalcomponent/CriticalComponentMapper.java index 2a36b61013..fb07bb3fb2 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/criticalcomponent/CriticalComponentMapper.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/criticalcomponent/CriticalComponentMapper.java @@ -6,6 +6,7 @@ import java.util.stream.Collectors; 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.module.iot.dal.dataobject.devicemodelattribute.DeviceModelAttributeDO; import cn.iocoder.yudao.module.mes.dal.dataobject.criticalcomponent.CriticalComponentDO; import cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO; import com.alibaba.excel.util.StringUtils; @@ -43,4 +44,8 @@ public interface CriticalComponentMapper extends BaseMapperX exportDeviceComponent(Long id); + /** + * 导入设备关键件列表 + */ + CriticalComponentImportRespVO importCriticalComponentList(List importComponents, boolean isUpdateSupport); + } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/criticalcomponent/CriticalComponentServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/criticalcomponent/CriticalComponentServiceImpl.java index 61fae06954..d7f5d5b09d 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/criticalcomponent/CriticalComponentServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/criticalcomponent/CriticalComponentServiceImpl.java @@ -1,16 +1,23 @@ package cn.iocoder.yudao.module.mes.service.criticalcomponent; +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO; import cn.iocoder.yudao.module.mes.dal.dataobject.repairtems.RepairTemsDO; import cn.iocoder.yudao.module.mes.dal.mysql.deviceledger.DeviceLedgerMapper; import cn.iocoder.yudao.module.mes.dal.mysql.repairtems.RepairTemsMapper; +import cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants; import com.alibaba.excel.util.StringUtils; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import org.springframework.stereotype.Service; import javax.annotation.Resource; +import javax.validation.ConstraintViolationException; + +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; @@ -183,4 +190,56 @@ public class CriticalComponentServiceImpl implements CriticalComponentService { return BeanUtils.toBean(componentList,CriticalComponentExcelVO.class); } + + @Override + @Transactional(rollbackFor = Exception.class) + public CriticalComponentImportRespVO importCriticalComponentList(List importComponents, boolean isUpdateSupport) { + // 1. 参数校验 + if (CollUtil.isEmpty(importComponents)) { + throw exception(ErrorCodeConstants.CRITICAL_COMPONENT_NOT_EXISTS); + } + + // 2. 初始化返回结果 + CriticalComponentImportRespVO respVO = CriticalComponentImportRespVO.builder() + .createCodes(new ArrayList<>()) + .updateCodes(new ArrayList<>()) + .failureCodes(new LinkedHashMap<>()) + .build(); + + // 3. 遍历导入数据 + importComponents.forEach(importComponent -> { + try { + // 3.1 字段校验 + ValidationUtils.validate(BeanUtils.toBean(importComponent, CriticalComponentSaveReqVO.class)); + } catch (ConstraintViolationException ex) { + respVO.getFailureCodes().put(importComponent.getCode(), ex.getMessage()); + return; + } + + // 3.2 检查是否已存在 + CriticalComponentDO existComponent = criticalComponentMapper.selectByCode(importComponent.getCode()); + if (existComponent == null) { + // 3.3 创建新记录 + CriticalComponentDO newComponent = BeanUtils.toBean(importComponent, CriticalComponentDO.class); + // 设置创建时间为当前时间 + newComponent.setCreateTime(LocalDateTime.now()); + criticalComponentMapper.insert(newComponent); + respVO.getCreateCodes().add(importComponent.getCode()); + } else if (isUpdateSupport) { + // 3.4 更新已有记录 + CriticalComponentDO updateComponent = BeanUtils.toBean(importComponent, CriticalComponentDO.class); + updateComponent.setId(existComponent.getId()); + // 保持原有创建时间不变 + updateComponent.setCreateTime(existComponent.getCreateTime()); + criticalComponentMapper.updateById(updateComponent); + respVO.getUpdateCodes().add(importComponent.getCode()); + } else { + // 3.5 已存在且不支持更新 + respVO.getFailureCodes().put(importComponent.getCode(), "该关键件已存在"); + } + }); + + return respVO; + } + } \ No newline at end of file