diff --git a/cf-module-prod-executor/cf-module-prod-executor-api/src/main/java/com/cf/imes/module/executor/enums/ErrorCodeConstants.java b/cf-module-prod-executor/cf-module-prod-executor-api/src/main/java/com/cf/imes/module/executor/enums/ErrorCodeConstants.java index f907b2f77..53c42f679 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-api/src/main/java/com/cf/imes/module/executor/enums/ErrorCodeConstants.java +++ b/cf-module-prod-executor/cf-module-prod-executor-api/src/main/java/com/cf/imes/module/executor/enums/ErrorCodeConstants.java @@ -7,6 +7,8 @@ import com.cf.imes.framework.common.exception.ErrorCode; * 生产执行服务错误码枚举 使用 1-003-000-000 段 */ public class ErrorCodeConstants { + private ErrorCodeConstants() { + } // ========== 生产单开料排单 public static final ErrorCode PLAN_NOT_EXISTS = new ErrorCode(1_003_107_000, "生产单开料排单不存在"); @@ -177,4 +179,8 @@ public class ErrorCodeConstants { public static final ErrorCode VENEER_IMPORT_DIGITAL_TYPE_ERROR = new ErrorCode(1_003_009_004, "veneer.import.digital.type.error"); public static final ErrorCode VENEER_IMPORT_POSITIVE_NUMBER_ERROR = new ErrorCode(1_003_009_005, "veneer.import.positive.number.error"); public static final ErrorCode VENEER_IMPORT_NUMBER_LENGTH_ERROR = new ErrorCode(1_003_009_006, "veneer.import.number.length.error"); + public static final ErrorCode VENEER_IMPORT_TEMPLATE_DOWNLOAD_ERROR = new ErrorCode(1_003_009_007, "veneer.import.template.download.error"); + public static final ErrorCode VENEER_IMPORT_ORG_CLASSIFICATION_NAME_NOT_EXIST = new ErrorCode(1_003_009_008, "veneer.import.org.classification.name.not.exist"); + public static final ErrorCode VENEER_ORG_CLASSIFICATION_NAME_EXIST = new ErrorCode(1_003_009_009, "veneer.org.classification.name.exist"); + public static final ErrorCode VENEER_ORG_CLASSIFICATION_NAME_EMPTY = new ErrorCode(1_003_009_010, "veneer.org.classification.name.empty"); } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerController.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerController.java index b5a545a23..e493fb596 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerController.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerController.java @@ -6,7 +6,8 @@ import com.cf.imes.framework.common.pojo.PageParam; import com.cf.imes.framework.common.pojo.PageResult; import com.cf.imes.framework.excel.core.util.ExcelUtils; import com.cf.imes.framework.operatelog.core.annotations.OperateLog; -import com.cf.imes.module.executor.constants.file.TemplateFilePathConstants; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameSaveReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDefectSaveReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerImportExcelVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPageReqVO; @@ -14,13 +15,11 @@ import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerSaveReqVO; import com.cf.imes.module.executor.service.veneer.VeneerExcelListener; import com.cf.imes.module.executor.service.veneer.VeneerService; -import com.cf.imes.module.executor.util.file.FileHelperUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; -import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -77,8 +76,7 @@ public class VeneerController { @Operation(summary = "下载贴皮导入模版") @OperateLog(type = EXPORT) public void getVeneerImportTemplate(HttpServletResponse response) { - String veneerImportTemplatePath = TemplateFilePathConstants.getVeneerImportTemplatePath(LocaleContextHolder.getLocale().getLanguage()); - FileHelperUtil.getFirstFile(veneerImportTemplatePath, response); + veneerService.importTemplateDownload(response); } @PostMapping("/import") @@ -128,4 +126,16 @@ public class VeneerController { veneerService.saveDefect(reqVO); return success(true); } + + @PostMapping("/classificationName") + @Operation(summary = "创建分类名称") + public CommonResult createClassificationName(@Valid @RequestBody VeneerClassificationNameSaveReqVO saveReqVO) { + return success(veneerService.addClassificationName(saveReqVO)); + } + + @GetMapping("/org/classificationName") + @Operation(summary = "获取分类名称列表") + public CommonResult> getOrgVeneerClassificationNameList() { + return success(veneerService.getOrgVeneerClassificationNameList()); + } } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerClassificationNameRespVO.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerClassificationNameRespVO.java new file mode 100644 index 000000000..5cee3436b --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerClassificationNameRespVO.java @@ -0,0 +1,20 @@ +package com.cf.imes.module.executor.controller.admin.veneer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * + * + * @author Gqr + * @since 2026/1/22 17:21 + */ +@Schema(description = "生产管理 - 贴皮分类名称列表 Response VO") +@Data +public class VeneerClassificationNameRespVO { + @Schema(description = "分类名称id") + private Long id; + + @Schema(description = "分类名称") + private String name; +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerClassificationNameSaveReqVO.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerClassificationNameSaveReqVO.java new file mode 100644 index 000000000..4ba2710ec --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/controller/admin/veneer/vo/VeneerClassificationNameSaveReqVO.java @@ -0,0 +1,20 @@ +package com.cf.imes.module.executor.controller.admin.veneer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * + * + * @author Gqr + * @since 2026/1/23 10:56 + */ +@Data +public class VeneerClassificationNameSaveReqVO { + @Schema(description = "分类名称") + @NotNull(message = "{veneer.classification.name.save.name.not.null}") + @Length(max = 64, message = "{veneer.classification.name.save.name.length.max}") + private String name; +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/veneer/VeneerClassificationNameDO.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/veneer/VeneerClassificationNameDO.java new file mode 100644 index 000000000..f8ba68441 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/veneer/VeneerClassificationNameDO.java @@ -0,0 +1,42 @@ +package com.cf.imes.module.executor.dal.dataobject.veneer; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableName; +import com.cf.imes.framework.mybatis.core.dataobject.BaseDO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * 贴皮 veneers_classification_name_{N} DO + * + * @author Gqr + * @since 2026/1/22 17:07 + */ +@TableName("veneers_classification_name") +@KeySequence("veneers_classification_name_n_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class VeneerClassificationNameDO extends BaseDO { + /** + * 主键 + */ + private Long id; + + /** + * 分类名称 + */ + private String name; + + /** + * 组织ID + */ + private Long organId; +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/mysql/veneer/VeneerClassificationNameMapper.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/mysql/veneer/VeneerClassificationNameMapper.java new file mode 100644 index 000000000..ff1d081bf --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/mysql/veneer/VeneerClassificationNameMapper.java @@ -0,0 +1,15 @@ +package com.cf.imes.module.executor.dal.mysql.veneer; + +import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX; +import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerClassificationNameDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 贴皮分类管理 Mapper + * + * @author Gqr + * @since 2026/1/22 17:12 + */ +@Mapper +public interface VeneerClassificationNameMapper extends BaseMapperX { +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerExcelListener.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerExcelListener.java index 44ba487f7..118113a85 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerExcelListener.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerExcelListener.java @@ -10,6 +10,7 @@ import com.alibaba.excel.read.metadata.holder.ReadRowHolder; import com.alibaba.excel.util.ListUtils; import com.cf.imes.framework.common.exception.ServiceException; import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerImportExcelVO; import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerDO; import com.cf.imes.module.executor.enums.veneer.VeneerStatusEnum; @@ -19,10 +20,13 @@ import org.apache.commons.lang3.StringUtils; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_DIGITAL_TYPE_ERROR; import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_LENGTH_ERROR; import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_NUMBER_LENGTH_ERROR; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_ORG_CLASSIFICATION_NAME_NOT_EXIST; import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_POSITIVE_NUMBER_ERROR; import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_REQUIRED_LACK; @@ -33,7 +37,7 @@ import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT * @since 2026/1/5 18:18 */ @Slf4j -public class VeneerExcelListener extends AnalysisEventListener { +public class VeneerExcelListener extends AnalysisEventListener { /** * 每500条操作入库一次 */ @@ -48,6 +52,8 @@ public class VeneerExcelListener extends AnalysisEventListener headMap; + private Set orgClassificationNameSet; + private Long organId; public VeneerExcelListener(VeneerService veneerService) { @@ -55,6 +61,10 @@ public class VeneerExcelListener extends AnalysisEventListener orgVeneerClassificationNameList = veneerService.getOrgVeneerClassificationNameList(); + if (CollUtil.isNotEmpty(orgVeneerClassificationNameList)) { + orgClassificationNameSet = orgVeneerClassificationNameList.stream().map(VeneerClassificationNameRespVO::getName).collect(Collectors.toSet()); + } } /** @@ -74,22 +84,10 @@ public class VeneerExcelListener extends AnalysisEventListener= BATCH_COUNT) { - updateData(); - // 存储完成清理 list - cachedUpdateDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); - } - return; - } + // 校验分类名称组织内是否存在 + validClassificationName(data.getClassificationName(), rowIndex); // 校验属性长度 validFieldLength(headMap.get(0), veneerNo, 32, rowIndex); @@ -108,6 +106,22 @@ public class VeneerExcelListener extends AnalysisEventListener= BATCH_COUNT) { + updateData(); + // 存储完成清理 list + cachedUpdateDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); + } + return; + } + // 累计新增列表 cachedInsertDataList.add(data); @@ -119,11 +133,6 @@ public class VeneerExcelListener extends AnalysisEventListener extends AnalysisEventListener headMap, AnalysisContext context) { this.headMap = headMap; @@ -172,6 +176,16 @@ public class VeneerExcelListener extends AnalysisEventListener extends AnalysisEventListener 0) { - return true; - } else { - return false; - } + return number > 0; } catch (NumberFormatException e) { return false; } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerExcelTemplateClassificationNameWriteHandler.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerExcelTemplateClassificationNameWriteHandler.java new file mode 100644 index 000000000..3135934d7 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerExcelTemplateClassificationNameWriteHandler.java @@ -0,0 +1,55 @@ +package com.cf.imes.module.executor.service.veneer; + +import com.alibaba.excel.write.handler.SheetWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationConstraint; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.util.CellRangeAddressList; + +/** + * 贴皮excel模版分类名称填充处理器 + * + * @author Gqr + * @since 2026/1/22 18:50 + */ +public class VeneerExcelTemplateClassificationNameWriteHandler implements SheetWriteHandler { + private final int columnIndex; // 0-based,C 列 = 2 + private final String[] options; + private final int firstRow; + private final int lastRow; + + public VeneerExcelTemplateClassificationNameWriteHandler(int columnIndex, + String[] options, + int firstRow, + int lastRow) { + this.columnIndex = columnIndex; + this.options = options; + this.firstRow = firstRow; + this.lastRow = lastRow; + } + + @Override + public void afterSheetCreate(WriteWorkbookHolder workbookHolder, + WriteSheetHolder sheetHolder) { + + Sheet sheet = sheetHolder.getSheet(); + + DataValidationHelper helper = sheet.getDataValidationHelper(); + DataValidationConstraint constraint = + helper.createExplicitListConstraint(options); + + CellRangeAddressList addressList = + new CellRangeAddressList(firstRow, lastRow, columnIndex, columnIndex); + + DataValidation validation = + helper.createValidation(constraint, addressList); + + validation.setShowErrorBox(true); + validation.setSuppressDropDownArrow(true); + + sheet.addValidationData(validation); + } +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerService.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerService.java index c4bce888f..4b13c63e9 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerService.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerService.java @@ -1,11 +1,14 @@ package com.cf.imes.module.executor.service.veneer; import com.cf.imes.framework.common.pojo.PageResult; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameSaveReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDefectSaveReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPageReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerSaveReqVO; import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerDO; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; @@ -52,6 +55,13 @@ public interface VeneerService { */ Long uploadPic(MultipartFile file) throws IOException; + /** + * 贴皮导入模版下载 + * + * @throws IOException + */ + void importTemplateDownload(HttpServletResponse response); + /** * 分页查询贴皮 * @@ -96,4 +106,19 @@ public interface VeneerService { * @return */ void saveDefect(VeneerDefectSaveReqVO defectSaveReqVO); + + /** + * 新增分类名称 + * + * @param saveReqVO 名称 + * @return + */ + Long addClassificationName(VeneerClassificationNameSaveReqVO saveReqVO); + + /** + * 获取组织分类名称列表 + * + * @return + */ + List getOrgVeneerClassificationNameList(); } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImpl.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImpl.java index 948f8f265..7519a593a 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImpl.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImpl.java @@ -1,8 +1,11 @@ package com.cf.imes.module.executor.service.veneer; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.ObjectUtil; +import com.alibaba.excel.EasyExcelFactory; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.cf.imes.framework.common.exception.ServiceException; @@ -11,23 +14,33 @@ import com.cf.imes.framework.common.pojo.PageResult; import com.cf.imes.framework.common.util.json.JsonUtils; import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX; import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; +import com.cf.imes.module.executor.constants.file.TemplateFilePathConstants; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameSaveReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDefectSaveReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPageReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerSaveReqVO; +import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerClassificationNameDO; import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerDO; import com.cf.imes.module.executor.dal.mysql.veneer.VeneerBatchMapper; +import com.cf.imes.module.executor.dal.mysql.veneer.VeneerClassificationNameMapper; import com.cf.imes.module.executor.dal.mysql.veneer.VeneerMapper; +import com.cf.imes.module.executor.util.file.FileHelperUtil; import com.cf.imes.module.infra.api.file.FileApi; import com.cf.imes.module.system.api.organ.OrganApi; import com.cf.imes.module.system.api.organ.dto.OrganizationDTO; import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; +import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.io.File; import java.io.IOException; import java.time.LocalDateTime; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; @@ -35,8 +48,11 @@ import java.util.Optional; import java.util.stream.Collectors; import static cn.hutool.core.date.DatePattern.PURE_DATETIME_PATTERN; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_TEMPLATE_DOWNLOAD_ERROR; import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_NOT_EXIST; import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_NO_EXIST; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_ORG_CLASSIFICATION_NAME_EMPTY; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_ORG_CLASSIFICATION_NAME_EXIST; /** * 贴皮管理ServiceImpl 实现类 @@ -58,6 +74,9 @@ public class VeneerServiceImpl implements VeneerService { @Resource private VeneerBatchMapper veneerBatchMapper; + @Resource + private VeneerClassificationNameMapper veneerClassificationNameMapper; + @Override public Long create(VeneerSaveReqVO reqVO) { // 1、校验贴皮编号 @@ -120,6 +139,39 @@ public class VeneerServiceImpl implements VeneerService { return fileApi.createFileAndReturnId(file.getBytes(), String.format("%s-veneer-pic-file-%s.%s", organizationDTO.getName(), LocalDateTimeUtil.format(LocalDateTime.now(), PURE_DATETIME_PATTERN), getFileExtension(file.getOriginalFilename()))); } + @Override + public void importTemplateDownload(HttpServletResponse response) { + try { + // 根据语种获取模版的路径 + String veneerImportTemplatePath = TemplateFilePathConstants.getVeneerImportTemplatePath(LocaleContextHolder.getLocale().getLanguage()); + + // 获取文件 + File templateFile = FileHelperUtil.getFirstFile(veneerImportTemplatePath); + + // 获取组织分类名称列表 + List orgVeneerClassificationNameList = getOrgVeneerClassificationNameList(); + if (CollUtil.isEmpty(orgVeneerClassificationNameList)) { + throw new ServiceException(VENEER_ORG_CLASSIFICATION_NAME_EMPTY); + } + + String[] options = orgVeneerClassificationNameList.stream().map(VeneerClassificationNameRespVO::getName).toArray(String[]::new); + + // 从第2行开始, 到200行写入数据源 + EasyExcelFactory.write(response.getOutputStream()) + .withTemplate(FileUtil.getInputStream(templateFile)) + .registerWriteHandler(new VeneerExcelTemplateClassificationNameWriteHandler( + 2, + options, + 1, + 200 + )) + .sheet(0) + .doWrite(Collections.emptyList()); + } catch (IOException ie) { + throw new ServiceException(VENEER_IMPORT_TEMPLATE_DOWNLOAD_ERROR); + } + } + @Override public PageResult getPage(VeneerPageReqVO pageReqVO) { PageResult veneerDOPageResult = veneerMapper.selectPage(pageReqVO); @@ -217,4 +269,33 @@ public class VeneerServiceImpl implements VeneerService { .map(ext -> ext.toLowerCase(Locale.ROOT)) .orElse(".jpg"); } + + @Override + public Long addClassificationName(VeneerClassificationNameSaveReqVO saveReqVO) { + String classificationName = saveReqVO.getName(); + // 校验组织下分类名称唯一 + validOrgClassificationName(classificationName); + + VeneerClassificationNameDO veneerClassificationNameDO = new VeneerClassificationNameDO(); + veneerClassificationNameDO.setName(classificationName); + veneerClassificationNameMapper.insert(veneerClassificationNameDO); + return veneerClassificationNameDO.getId(); + } + + /** + * 校验组织下分类名称唯一 + * + * @param classificationName + */ + private void validOrgClassificationName(String classificationName) { + if (veneerClassificationNameMapper.selectCount(new LambdaQueryWrapper() + .eq(VeneerClassificationNameDO::getName, classificationName)) > 0) { + throw new ServiceException(VENEER_ORG_CLASSIFICATION_NAME_EXIST); + } + } + + @Override + public List getOrgVeneerClassificationNameList() { + return BeanUtil.copyToList(veneerClassificationNameMapper.selectList(), VeneerClassificationNameRespVO.class); + } } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/util/file/FileHelperUtil.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/util/file/FileHelperUtil.java index 7068da420..14c8500f4 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/util/file/FileHelperUtil.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/util/file/FileHelperUtil.java @@ -32,7 +32,6 @@ public class FileHelperUtil { OutputStream outputStream = new BufferedOutputStream(response.getOutputStream())) { byte[] buffer = new byte[fis.available()]; fis.read(buffer); - fis.close(); response.reset(); response.setCharacterEncoding(CharsetUtil.UTF_8); @@ -67,12 +66,11 @@ public class FileHelperUtil { } /** - * 下载目录下第一个文件 + * 获取目录下第一个文件 * * @param dirPath - * @param response */ - public static void getFirstFile(String dirPath, HttpServletResponse response) { + public static File getFirstFile(String dirPath) { File dir = new File(dirPath); if (!dir.exists() || !dir.isDirectory()) { throw new ServiceException(FILE_NOT_EXISTS); @@ -83,26 +81,6 @@ public class FileHelperUtil { throw new ServiceException(FILE_NOT_EXISTS); } - File file = files[0]; - // 获取文件名 - String filename = file.getName(); - // 将文件写入输入流 - try (FileInputStream fis = new FileInputStream(file); - OutputStream outputStream = new BufferedOutputStream(response.getOutputStream())) { - byte[] buffer = new byte[fis.available()]; - fis.read(buffer); - fis.close(); - - response.reset(); - response.setCharacterEncoding(CharsetUtil.UTF_8); - response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, CharsetUtil.UTF_8)); - response.addHeader("Content-Length", "" + file.length()); - - response.setContentType("application/octet-stream"); - outputStream.write(buffer); - outputStream.flush(); - } catch (IOException ex) { - throw new ServiceException(FILE_NOT_EXISTS); - } + return files[0]; } } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message.properties b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message.properties index d60296ba9..e19b7d112 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message.properties +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message.properties @@ -15,4 +15,6 @@ veneer.save.veneerNo.not.null=贴皮编号不能为空 veneer.save.width.not.null=宽度不能为空 veneer.save.veneerName.not.null=贴皮名称不能为空 veneer.save.veneerName.length.max=贴皮名称不能超过64个字符 -veneer.defect.save.id.not.null=贴皮ID不能为空 \ No newline at end of file +veneer.defect.save.id.not.null=贴皮ID不能为空 +veneer.classification.name.save.name.not.null=分类名称不能为空 +veneer.classification.name.save.name.length.max=分类名称长度不能超过64 \ No newline at end of file diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_cht.properties b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_cht.properties index d86a20658..dd9b18351 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_cht.properties +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_cht.properties @@ -14,5 +14,7 @@ veneer.save.veneerNo.length.max=貼皮編號不能超過32個字元 veneer.save.veneerNo.not.null=貼皮編號不能為空 veneer.save.width.not.null=寬度不能為空 veneer.save.veneerName.not.null=貼皮名稱不能為空 -veneer.save.veneerName.length.max=貼皮名稱不能超过64个字符 -veneer.defect.save.id.not.null=貼皮ID不能為空 \ No newline at end of file +veneer.save.veneerName.length.max=貼皮名稱不能超过64个字元 +veneer.defect.save.id.not.null=貼皮ID不能為空 +veneer.classification.name.save.name.not.null=分類名稱不能為空 +veneer.classification.name.save.name.length.max=分類名稱不能超過64個字元 \ No newline at end of file diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_en.properties b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_en.properties index 36aa2f147..bbe6fddbd 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_en.properties +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_en.properties @@ -15,4 +15,6 @@ veneer.save.veneerNo.not.null=veener no cannot be empty veneer.save.width.not.null=width cannot be empty veneer.save.veneerName.not.null=veneer name cannot be empty veneer.save.veneerName.length.max=veneer name length cannot exceed 64 characters -veneer.defect.save.id.not.null=veneer id cannot be empty \ No newline at end of file +veneer.defect.save.id.not.null=veneer id cannot be empty +veneer.classification.name.save.name.not.null=classification name cannot be empty +veneer.classification.name.save.name.length.max=classification name length cannot exceed 64 characters \ No newline at end of file diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode.properties b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode.properties index 12de73fc6..8a52ec963 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode.properties +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode.properties @@ -20,4 +20,8 @@ veneer.import.required.lack=第{0}行存在带*必填项为空,请检查文件 veneer.import.length.error=第{0}行{1}长度不能超过{2}个字符,请检查文件 veneer.import.digital.type.error=第{0}行{1}只能输入数字,请检查文件 veneer.import.positive.number.error=第{0}行{1}必须大于0,请检查文件 -veneer.import.number.length.error=第{0}行{1}长度不能超过整数{2}位、小数{3}位,请检查文件 \ No newline at end of file +veneer.import.number.length.error=第{0}行{1}长度不能超过整数{2}位、小数{3}位,请检查文件 +veneer.org.classification.name.exist=组织下分类名称已存在,请检查 +veneer.org.classification.name.empty=组织下分类名称不存在,请前往 [新增] 窗口维护分类名称 +veneer.import.template.download.error=导入模版下载失败 +veneer.import.org.classification.name.not.exist=第{0}行分类名称{1}不存在,请前往 [新增] 窗口维护分类名称 \ No newline at end of file diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_cht.properties b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_cht.properties index 0ae72d7a4..7665ecb11 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_cht.properties +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_cht.properties @@ -20,4 +20,8 @@ veneer.import.required.lack=第{0}行存在帶*必填項為空,請檢查檔案 veneer.import.length.error=第{0}行{1}長度不能超過{2}個字元,請檢查檔案 veneer.import.digital.type.error=第{0}行{1}只能輸入數位,請檢查檔案 veneer.import.positive.number.error=第{0}行{1}必須大於0,請檢查檔案 -veneer.import.number.length.error=第{0}行{1}長度不能超過整數{2}位、小數{3}位,請檢查檔案 \ No newline at end of file +veneer.import.number.length.error=第{0}行{1}長度不能超過整數{2}位、小數{3}位,請檢查檔案 +veneer.org.classification.name.exist=組織下分類名稱已存在,請檢查 +veneer.org.classification.name.empty=組織下分類名稱不存在,請前往 [新增] 視窗維護分類名稱 +veneer.import.template.download.error=導入模版下載失敗 +veneer.import.org.classification.name.not.exist=第{0}行分類名稱{1}不存在,請前往[新增]視窗維護分類名稱 \ No newline at end of file diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_en.properties b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_en.properties index 0cd8c16da..4905eb39c 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_en.properties +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_en.properties @@ -20,4 +20,8 @@ veneer.import.required.lack=The required field is empty in line {0}, please chec veneer.import.length.error=The length of the field cannot exceed {0} characters in line {1}, please check the file veneer.import.digital.type.error=The field can only be filled in with numbers in line {0}, please check the file veneer.import.positive.number.error=The field must be greater than 0 in line {0}, please check the file -veneer.import.number.length.error=The length of the field cannot exceed integer {0} digits and decimal {1} digits in line {2}, please check the file \ No newline at end of file +veneer.import.number.length.error=The length of the field cannot exceed integer {0} digits and decimal {1} digits in line {2}, please check the file +veneer.org.classification.name.exist=The classification name already exists under the organization, please check +veneer.org.classification.name.empty=The classification name does not exist under the organization, Please go to the [Add] window to maintain the classification name +veneer.import.template.download.error=Import template download failed +veneer.import.org.classification.name.not.exist=The classification name {1} does not exist in line {0}, please go to the [Add] window to maintain the classification name \ No newline at end of file diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/type/veneer/cht/貼皮導入範本.xlsx b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/type/veneer/cht/貼皮導入範本.xlsx index a4fe4a0b6..8f5928e24 100644 Binary files a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/type/veneer/cht/貼皮導入範本.xlsx and b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/type/veneer/cht/貼皮導入範本.xlsx differ diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/type/veneer/en/veneer_import_template.xlsx b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/type/veneer/en/veneer_import_template.xlsx index 4ccf27b8d..9e4d97a1b 100644 Binary files a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/type/veneer/en/veneer_import_template.xlsx and b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/type/veneer/en/veneer_import_template.xlsx differ diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/type/veneer/zh/贴皮导入模板.xlsx b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/type/veneer/zh/贴皮导入模板.xlsx index 587bc5755..2cdda5a13 100644 Binary files a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/type/veneer/zh/贴皮导入模板.xlsx and b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/type/veneer/zh/贴皮导入模板.xlsx differ diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerControllerTest.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerControllerTest.java index 073921f15..157cead9b 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerControllerTest.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/controller/admin/veneer/VeneerControllerTest.java @@ -1,17 +1,23 @@ package com.cf.imes.module.executor.controller.admin.veneer; +import com.alibaba.excel.EasyExcelFactory; +import com.alibaba.excel.read.builder.ExcelReaderBuilder; +import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder; import com.cf.imes.framework.common.pojo.PageResult; -import com.cf.imes.framework.security.test.WithMockLoginUser; import com.cf.imes.framework.test.core.ut.BaseWebUnitTest; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameSaveReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDefectSaveReqVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerImportExcelVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerSaveReqVO; import com.cf.imes.module.executor.enums.veneer.VeneerProductLevelEnum; +import com.cf.imes.module.executor.service.veneer.VeneerExcelListener; import com.cf.imes.module.executor.service.veneer.VeneerService; -import com.cf.imes.module.executor.util.file.FileHelperUtil; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; +import org.mockito.MockedConstruction; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; @@ -22,11 +28,14 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; +import java.io.InputStream; import java.math.BigDecimal; import java.util.Collections; +import java.util.List; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -161,32 +170,55 @@ class VeneerControllerTest extends BaseWebUnitTest { } @Test - @WithMockLoginUser void importExcel_success() throws Exception { MockMultipartFile file = new MockMultipartFile( - "file", "test.xlsx", - "application/vnd.ms-excel", - new byte[]{1, 2, 3} + "file", + "veneer.xlsx", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "dummy excel content".getBytes() ); - mockMvc.perform(multipart("/executor/veneer/import").file(file)) - .andExpect(status().isOk()); + try ( + MockedStatic excelMock = + Mockito.mockStatic(EasyExcelFactory.class); + MockedConstruction listenerMock = + Mockito.mockConstruction(VeneerExcelListener.class) + ) { + + ExcelReaderBuilder readerBuilder = Mockito.mock(ExcelReaderBuilder.class); + ExcelReaderSheetBuilder sheetBuilder = Mockito.mock(ExcelReaderSheetBuilder.class); + + excelMock.when(() -> + EasyExcelFactory.read( + any(InputStream.class), + eq(VeneerImportExcelVO.class), + any() + ) + ).thenReturn(readerBuilder); + + Mockito.when(readerBuilder.headRowNumber(anyInt())) + .thenReturn(readerBuilder); + Mockito.when(readerBuilder.sheet()) + .thenReturn(sheetBuilder); + Mockito.doNothing().when(sheetBuilder).doRead(); + + mockMvc.perform( + multipart("/executor/veneer/import").file(file) + ).andExpect(status().isOk()); + } } @Test void getVeneerImportTemplate_success() throws Exception { - try (MockedStatic mocked = - Mockito.mockStatic(FileHelperUtil.class)) { + Mockito.doNothing() + .when(veneerService) + .importTemplateDownload(any(HttpServletResponse.class)); - mocked.when(() -> - FileHelperUtil.getFirstFile( - anyString(), - any(HttpServletResponse.class)) - ).thenAnswer(invocation -> null); + mockMvc.perform(get("/executor/veneer/import/template")) + .andExpect(status().isOk()); - mockMvc.perform(get("/executor/veneer/import/template")) - .andExpect(status().isOk()); - } + Mockito.verify(veneerService) + .importTemplateDownload(any(HttpServletResponse.class)); } @Test @@ -203,4 +235,48 @@ class VeneerControllerTest extends BaseWebUnitTest { .andExpect(jsonPath("$.data").value(true)); } + @Test + void createClassificationName_success() throws Exception { + + VeneerClassificationNameSaveReqVO reqVO = new VeneerClassificationNameSaveReqVO(); + reqVO.setName("测试分类"); + + Mockito.when(veneerService.addClassificationName(any())) + .thenReturn(1L); + + mockMvc.perform( + post("/executor/veneer/classificationName") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO)) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").value(1L)); + + Mockito.verify(veneerService) + .addClassificationName(any(VeneerClassificationNameSaveReqVO.class)); + } + + @Test + void getOrgVeneerClassificationNameList_success() throws Exception { + + VeneerClassificationNameRespVO respVO = new VeneerClassificationNameRespVO(); + respVO.setId(1L); + respVO.setName("测试分类"); + + Mockito.when(veneerService.getOrgVeneerClassificationNameList()) + .thenReturn(List.of(respVO)); + + mockMvc.perform( + get("/executor/veneer/org/classificationName") + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data[0].id").value(1L)) + .andExpect(jsonPath("$.data[0].name").value("测试分类")); + + Mockito.verify(veneerService) + .getOrgVeneerClassificationNameList(); + } + } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImplTest.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImplTest.java index 3611e7fea..0c8f418ad 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImplTest.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/service/veneer/VeneerServiceImplTest.java @@ -1,6 +1,10 @@ package com.cf.imes.module.executor.service.veneer; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.ObjectUtil; +import com.alibaba.excel.EasyExcelFactory; +import com.alibaba.excel.write.builder.ExcelWriterBuilder; +import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; @@ -11,25 +15,41 @@ import com.cf.imes.framework.common.util.json.JsonUtils; import com.cf.imes.framework.security.test.WithMockLoginUser; import com.cf.imes.framework.test.core.ut.BaseMockitoUnitTest; import com.cf.imes.module.executor.common.WithMockLoginUserExtension; +import com.cf.imes.module.executor.constants.file.TemplateFilePathConstants; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameRespVO; +import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameSaveReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDefectSaveReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPageReqVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerRespVO; import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerSaveReqVO; +import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerClassificationNameDO; import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerDO; import com.cf.imes.module.executor.dal.mysql.veneer.VeneerBatchMapper; +import com.cf.imes.module.executor.dal.mysql.veneer.VeneerClassificationNameMapper; import com.cf.imes.module.executor.dal.mysql.veneer.VeneerMapper; import com.cf.imes.module.executor.enums.veneer.VeneerProductLevelEnum; +import com.cf.imes.module.executor.util.file.FileHelperUtil; import com.cf.imes.module.infra.api.file.FileApi; import com.cf.imes.module.system.api.organ.OrganApi; import com.cf.imes.module.system.api.organ.dto.OrganizationDTO; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletResponse; import org.apache.ibatis.builder.MapperBuilderAssistant; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.springframework.mock.web.MockMultipartFile; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @@ -39,6 +59,7 @@ import static com.cf.imes.framework.test.core.util.AssertUtils.assertServiceExce import static com.cf.imes.framework.test.core.util.RandomUtils.randomPojo; import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_NOT_EXIST; import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_NO_EXIST; +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_ORG_CLASSIFICATION_NAME_EXIST; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -46,10 +67,13 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -78,6 +102,12 @@ class VeneerServiceImplTest extends BaseMockitoUnitTest { @Mock private VeneerBatchMapper veneerBatchMapper; + @Mock + private VeneerClassificationNameMapper veneerClassificationNameMapper; + + @Mock + private HttpServletResponse response; + @BeforeAll static void init(){ TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), VeneerDO.class); @@ -481,6 +511,208 @@ class VeneerServiceImplTest extends BaseMockitoUnitTest { verify(veneerMapper, never()).update(any()); } + @Test + void addClassificationName_success() { + + // given + VeneerClassificationNameSaveReqVO reqVO = + new VeneerClassificationNameSaveReqVO(); + reqVO.setName("测试分类"); + + // 唯一校验通过 + Mockito.when(veneerClassificationNameMapper.selectCount(any())) + .thenReturn(0L); + + // mock insert 时给 id + Mockito.doAnswer(invocation -> { + VeneerClassificationNameDO entity = invocation.getArgument(0); + entity.setId(1L); + return 1; + }).when(veneerClassificationNameMapper).insert(any(VeneerClassificationNameDO.class)); + + // when + Long id = veneerService.addClassificationName(reqVO); + + // then + assertEquals(1L, id); + + Mockito.verify(veneerClassificationNameMapper) + .selectCount(any()); + Mockito.verify(veneerClassificationNameMapper) + .insert(any(VeneerClassificationNameDO.class)); + } + + @Test + void addClassificationName_exist_throwException() { + + VeneerClassificationNameSaveReqVO reqVO = + new VeneerClassificationNameSaveReqVO(); + reqVO.setName("重复分类"); + + // 已存在 + Mockito.when(veneerClassificationNameMapper.selectCount(any())) + .thenReturn(1L); + + assertServiceException(() -> veneerService.addClassificationName(reqVO), + VENEER_ORG_CLASSIFICATION_NAME_EXIST); + + // insert 一定不会被调用 + Mockito.verify(veneerClassificationNameMapper, Mockito.never()) + .insert(any(VeneerClassificationNameDO.class)); + } + + @Test + void getOrgVeneerClassificationNameList_success() { + + VeneerClassificationNameDO entity = new VeneerClassificationNameDO(); + entity.setId(1L); + entity.setName("分类A"); + + Mockito.when(veneerClassificationNameMapper.selectList()) + .thenReturn(List.of(entity)); + + List result = + veneerService.getOrgVeneerClassificationNameList(); + + assertEquals(1, result.size()); + assertEquals("分类A", result.get(0).getName()); + + Mockito.verify(veneerClassificationNameMapper) + .selectList(); + } + + @Test + void importTemplateDownload_success() throws IOException { + File mockFile = Mockito.mock(File.class); + + try ( + MockedStatic pathMock = + Mockito.mockStatic(TemplateFilePathConstants.class); + MockedStatic fileUtilMock = + Mockito.mockStatic(FileUtil.class); + MockedStatic fileMock = + Mockito.mockStatic(FileHelperUtil.class); + MockedStatic excelMock = + Mockito.mockStatic(EasyExcelFactory.class) + ) { + + VeneerClassificationNameDO veneerClassificationNameDO = new VeneerClassificationNameDO(); + veneerClassificationNameDO.setName("分类名称"); + when(veneerClassificationNameMapper.selectList()).thenReturn(List.of(veneerClassificationNameDO)); + + pathMock.when(() -> + TemplateFilePathConstants.getVeneerImportTemplatePath(any()) + ).thenReturn("/mock/path"); + + fileMock.when(() -> + FileHelperUtil.getFirstFile(anyString()) + ).thenReturn(mockFile); + + // ⭐ 关键 1:mock 模板流(不碰文件) + fileUtilMock.when(() -> FileUtil.getInputStream(any(File.class))) + .thenReturn(new BufferedInputStream(new ByteArrayInputStream(new byte[]{1}))); + + // mock response OutputStream + ServletOutputStream outputStream = Mockito.mock(ServletOutputStream.class); + Mockito.when(response.getOutputStream()) + .thenReturn(outputStream); + + // mock EasyExcel 写链 + ExcelWriterBuilder writerBuilder = Mockito.mock(ExcelWriterBuilder.class); + ExcelWriterSheetBuilder sheetBuilder = Mockito.mock(ExcelWriterSheetBuilder.class); + + excelMock.when(() -> EasyExcelFactory.write(any(OutputStream.class))) + .thenReturn(writerBuilder); + + when(writerBuilder.withTemplate( + any(InputStream.class))) + .thenReturn(writerBuilder); + + when(writerBuilder.registerWriteHandler(any())) + .thenReturn(writerBuilder); + + when(writerBuilder.sheet(anyInt())) + .thenReturn(sheetBuilder); + doNothing().when(sheetBuilder).doWrite(anyList()); + + // 执行 + assertDoesNotThrow(() -> + veneerService.importTemplateDownload(response)); + } + } + + + @Test + void importTemplateDownload_fail_when_org_classification_name_list_is_empty() { + // 1️⃣ mock 模板路径 & 文件 + File mockFile = Mockito.mock(File.class); + + try ( + MockedStatic pathMock = + Mockito.mockStatic(TemplateFilePathConstants.class); + MockedStatic fileMock = + Mockito.mockStatic(FileHelperUtil.class) + ) { + + pathMock.when(() -> + TemplateFilePathConstants.getVeneerImportTemplatePath(any()) + ).thenReturn("/mock/path"); + + fileMock.when(() -> + FileHelperUtil.getFirstFile(anyString()) + ).thenReturn(mockFile); + + // 2️⃣ mock 分类名称列表为空 + Mockito.when(veneerClassificationNameMapper.selectList()) + .thenReturn(new ArrayList<>()); + + // 3️⃣ 验证抛出异常 + assertServiceException(() -> veneerService.importTemplateDownload(response), + com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_ORG_CLASSIFICATION_NAME_EMPTY); + } + } + + @Test + void importTemplateDownload_fail_when_io_exception_occurs() throws Exception { + // 1️⃣ mock 模板路径 & 文件 + File mockFile = Mockito.mock(File.class); + + try ( + MockedStatic pathMock = + Mockito.mockStatic(TemplateFilePathConstants.class); + MockedStatic fileMock = + Mockito.mockStatic(FileHelperUtil.class); + MockedStatic excelMock = + Mockito.mockStatic(EasyExcelFactory.class) + ) { + + pathMock.when(() -> + TemplateFilePathConstants.getVeneerImportTemplatePath(any()) + ).thenReturn("/mock/path"); + + fileMock.when(() -> + FileHelperUtil.getFirstFile(anyString()) + ).thenReturn(mockFile); + + // 2️⃣ mock 分类名称列表 + VeneerClassificationNameDO entity = new VeneerClassificationNameDO(); + entity.setName("分类A"); + + Mockito.when(veneerClassificationNameMapper.selectList()) + .thenReturn(List.of(entity)); + + // 3️⃣ mock response OutputStream 并抛出IOException + Mockito.when(response.getOutputStream()) + .thenThrow(new java.io.IOException("IO Error")); + + // 4️⃣ 验证抛出异常 + assertServiceException(() -> veneerService.importTemplateDownload(response), + com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_TEMPLATE_DOWNLOAD_ERROR); + } + } + + + @Test void getFileExtension_whenEmpty() { assertEquals(".jpg", veneerService.getFileExtension("file."));