|
|
@@ -20,18 +20,21 @@ import com.platform.dao.dto.part.PartInfoDTO;
|
|
|
import com.platform.dao.entity.check.*;
|
|
|
import com.platform.dao.entity.part.PartInfo;
|
|
|
import com.platform.dao.entity.sb.SbInfo;
|
|
|
+import com.platform.dao.entity.upms.SysDept;
|
|
|
import com.platform.dao.entity.upms.SysFile;
|
|
|
import com.platform.dao.entity.upms.SysUser;
|
|
|
import com.platform.dao.enums.*;
|
|
|
import com.platform.dao.mapper.check.*;
|
|
|
import com.platform.dao.mapper.sb.SbInfoMapper;
|
|
|
import com.platform.dao.mapper.sb.SbTypeMapper;
|
|
|
+import com.platform.dao.mapper.upms.SysDeptMapper;
|
|
|
import com.platform.dao.mapper.upms.SysFileMapper;
|
|
|
import com.platform.dao.mapper.upms.SysUserMapper;
|
|
|
import com.platform.dao.util.CustomExcelImportUtil;
|
|
|
import com.platform.dao.util.ExcelUtil;
|
|
|
import com.platform.dao.util.MessageTemplateUtil;
|
|
|
import com.platform.dao.vo.export.check.ExportCheckStandardVO2;
|
|
|
+import com.platform.dao.vo.export.check.ImportCheckStandardVO;
|
|
|
import com.platform.dao.vo.query.check.CheckStandardSpareVO;
|
|
|
import com.platform.dao.vo.query.check.CheckStandardVO;
|
|
|
import com.platform.service.base.impl.BaseServiceImpl;
|
|
|
@@ -89,6 +92,8 @@ public class CheckStandardServiceImpl extends BaseServiceImpl<CheckStandardMappe
|
|
|
|
|
|
private final SbTypeMapper sbTypeMapper;
|
|
|
|
|
|
+ private final SysDeptMapper sysDeptMapper;
|
|
|
+
|
|
|
@Override
|
|
|
public CheckStandard getModelkById(String id) {
|
|
|
// 使用新的方法查询,包含保养要求
|
|
|
@@ -181,11 +186,9 @@ public class CheckStandardServiceImpl extends BaseServiceImpl<CheckStandardMappe
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public void deleteByPrimaryKey(String id) {
|
|
|
- // 1. 删除保养要求关联关系
|
|
|
- checkStandardMapper.deleteRequirementsByStandardId(id);
|
|
|
-
|
|
|
- // 2. 删除点检标准
|
|
|
- checkStandardMapper.deleteByPrimaryKey(id);
|
|
|
+ // 与批量删除保持一致的完整级联:标准+保养项关联+项目关联+任务+备件+参数,
|
|
|
+ // 避免单条删除后残留孤儿任务(打开任务会报“保养标准已删除”)
|
|
|
+ cascadingDeleteByKey(id);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -205,8 +208,11 @@ public class CheckStandardServiceImpl extends BaseServiceImpl<CheckStandardMappe
|
|
|
|
|
|
|
|
|
@Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
public boolean cascadingDeleteByKey(String id) {
|
|
|
int result = mapper.deleteByPrimaryKey(id);
|
|
|
+ // 删除保养项关联关系(t_check_standard_requirement),避免孤儿关联
|
|
|
+ checkStandardMapper.deleteRequirementsByStandardId(id);
|
|
|
Weekend<CheckProjectStandardRelation> weekend = new Weekend<>(CheckProjectStandardRelation.class);
|
|
|
weekend.weekendCriteria().andEqualTo(CheckProjectStandardRelation::getStandardId, id);
|
|
|
relationMapper.deleteByExample(weekend);
|
|
|
@@ -216,10 +222,15 @@ public class CheckStandardServiceImpl extends BaseServiceImpl<CheckStandardMappe
|
|
|
checkJobWeekend.weekendCriteria().andEqualTo(CheckJob::getStandardId, id);
|
|
|
checkJobMapper.deleteByExample(checkJobWeekend);
|
|
|
|
|
|
- // 删除标准参数
|
|
|
+ // 删除标准关联的备件明细
|
|
|
+ Weekend<CheckStandardSpare> spareWeekend = new Weekend<>(CheckStandardSpare.class);
|
|
|
+ spareWeekend.weekendCriteria().andEqualTo(CheckStandardSpare::getCheckId, id);
|
|
|
+ spareMapper.deleteByExample(spareWeekend);
|
|
|
+
|
|
|
+ // 删除标准参数(修复:原误传了项目关联的 weekend,应使用参数自身的查询条件)
|
|
|
Weekend<CheckStandardParam> checkStandardParamWeekend = new Weekend<>(CheckStandardParam.class);
|
|
|
checkStandardParamWeekend.weekendCriteria().andEqualTo(CheckStandardParam::getStandardId, id);
|
|
|
- paramMapper.deleteByExample(weekend);
|
|
|
+ paramMapper.deleteByExample(checkStandardParamWeekend);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@@ -1319,6 +1330,303 @@ public class CheckStandardServiceImpl extends BaseServiceImpl<CheckStandardMappe
|
|
|
return count;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 批量导入保养标准(仅新增)。
|
|
|
+ * 逐行校验并组装 DTO,任一行有误则整批拒绝(先校验后落库,保证事务干净);
|
|
|
+ * 全部通过后逐行复用 {@link #saveModelByDTO},确保编码生成、保养项关联、首期任务生成与界面新增完全一致。
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public String importBatch(MultipartFile file) {
|
|
|
+ List<ImportCheckStandardVO> rows;
|
|
|
+ try {
|
|
|
+ // 模板由 exportResponseDict 生成:第 1 行为标题、第 2 行为表头,故 titleRows=1
|
|
|
+ rows = ExcelUtil.importExcel(file.getInputStream(), ImportCheckStandardVO.class, 1);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("保养标准导入文件解析失败", e);
|
|
|
+ throw new BusinessException("导入失败:文件解析异常,请使用系统提供的模板");
|
|
|
+ }
|
|
|
+ if (CollectionUtil.isEmpty(rows)) {
|
|
|
+ throw new BusinessException("导入失败:Excel 文件内容为空");
|
|
|
+ }
|
|
|
+ // 防大文件长事务:逐行复用界面新增逻辑(含首期任务生成),限制单次行数
|
|
|
+ if (rows.size() > 1000) {
|
|
|
+ throw new BusinessException("导入失败:单次最多导入 1000 行,请拆分文件后分批导入");
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> errors = new ArrayList<>();
|
|
|
+ // 校验通过的待保存 DTO,与原始行号一一对应,便于错误定位
|
|
|
+ List<CheckStandardDTO> readyList = new ArrayList<>();
|
|
|
+ // 文件内防重:同一“设备+保养项类别”组合只允许出现一次
|
|
|
+ Map<String, Integer> seenKeys = new HashMap<>();
|
|
|
+ int rowNo = 0;
|
|
|
+ for (ImportCheckStandardVO vo : rows) {
|
|
|
+ rowNo++;
|
|
|
+ // 跳过完全空白的行
|
|
|
+ if (vo == null || isAllBlank(vo)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ List<String> rowErrors = new ArrayList<>();
|
|
|
+ CheckStandardDTO dto = buildDtoFromRow(vo, rowNo, rowErrors, seenKeys);
|
|
|
+ if (!rowErrors.isEmpty()) {
|
|
|
+ errors.addAll(rowErrors);
|
|
|
+ } else {
|
|
|
+ readyList.add(dto);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (readyList.isEmpty()) {
|
|
|
+ throw new BusinessException("导入失败:未解析到有效数据行,请检查模板内容");
|
|
|
+ }
|
|
|
+ // 整批拒绝:存在任何错误行则全部不导入
|
|
|
+ if (!errors.isEmpty()) {
|
|
|
+ String msg = String.join(";", errors);
|
|
|
+ if (errors.size() > 20) {
|
|
|
+ msg = errors.subList(0, 20).stream().collect(Collectors.joining(";"))
|
|
|
+ + ";(其余 " + (errors.size() - 20) + " 条错误略)";
|
|
|
+ }
|
|
|
+ throw new BusinessException("导入失败,共 " + errors.size() + " 行有误,已全部回滚:" + msg);
|
|
|
+ }
|
|
|
+
|
|
|
+ int count = 0;
|
|
|
+ // 记录本次导入生成的编码区间,便于误导入后按编码定位回滚
|
|
|
+ String firstNo = null;
|
|
|
+ String lastNo = null;
|
|
|
+ for (CheckStandardDTO item : readyList) {
|
|
|
+ CheckStandard saved = this.saveModelByDTO(item);
|
|
|
+ if (saved != null && StringUtils.isNotBlank(saved.getNo())) {
|
|
|
+ if (firstNo == null) {
|
|
|
+ firstNo = saved.getNo();
|
|
|
+ }
|
|
|
+ lastNo = saved.getNo();
|
|
|
+ }
|
|
|
+ count++;
|
|
|
+ }
|
|
|
+ log.info("保养标准批量导入成功,共 {} 条,编码区间 {}~{}", count, firstNo, lastNo);
|
|
|
+ String rangeTip = StringUtils.isNotBlank(firstNo) ? "(编码 " + firstNo + " ~ " + lastNo + ")" : "";
|
|
|
+ return "成功导入 " + count + " 条保养标准" + rangeTip + ",如需撤销可按编码在列表定位后批量删除";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将一行导入数据解析、校验为可保存的 CheckStandardDTO。
|
|
|
+ * 校验失败时把错误写入 rowErrors 并返回 null,不落库。
|
|
|
+ */
|
|
|
+ private CheckStandardDTO buildDtoFromRow(ImportCheckStandardVO vo, int rowNo, List<String> rowErrors, Map<String, Integer> seenKeys) {
|
|
|
+ String prefix = "第" + rowNo + "行:";
|
|
|
+ CheckStandardDTO dto = new CheckStandardDTO();
|
|
|
+
|
|
|
+ // 1. 设备:按新号或旧号双查
|
|
|
+ String sbNo = trim(vo.getSbNo());
|
|
|
+ if (StringUtils.isBlank(sbNo)) {
|
|
|
+ rowErrors.add(prefix + "设备编号不能为空");
|
|
|
+ } else {
|
|
|
+ SbInfo sbInfo = findSbInfoByNo(sbNo);
|
|
|
+ if (sbInfo == null) {
|
|
|
+ rowErrors.add(prefix + "未找到设备编号【" + sbNo + "】");
|
|
|
+ } else {
|
|
|
+ dto.setSbId(sbInfo.getId());
|
|
|
+ dto.setSbNo(sbInfo.getNo());
|
|
|
+ dto.setSbName(sbInfo.getName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 保养项类别:按名称解析 t_sb_type.id,并校验该类别下存在保养项
|
|
|
+ String categoryName = trim(vo.getCategoryName());
|
|
|
+ String resolvedTypeId = null;
|
|
|
+ List<String> categoryReqIds = null;
|
|
|
+ if (StringUtils.isBlank(categoryName)) {
|
|
|
+ rowErrors.add(prefix + "保养项类别不能为空");
|
|
|
+ } else {
|
|
|
+ String typeId = sbTypeMapper.selectIdByName(categoryName);
|
|
|
+ if (StringUtils.isBlank(typeId)) {
|
|
|
+ rowErrors.add(prefix + "未找到保养项类别【" + categoryName + "】");
|
|
|
+ } else {
|
|
|
+ Weekend<CheckRequirement> weekendR = Weekend.of(CheckRequirement.class);
|
|
|
+ weekendR.weekendCriteria().andEqualTo(CheckRequirement::getSbType, typeId);
|
|
|
+ List<CheckRequirement> reqs = checkRequirementMapper.selectByExample(weekendR);
|
|
|
+ if (CollectionUtil.isEmpty(reqs)) {
|
|
|
+ rowErrors.add(prefix + "保养项类别【" + categoryName + "】下没有保养项");
|
|
|
+ } else {
|
|
|
+ resolvedTypeId = typeId;
|
|
|
+ categoryReqIds = reqs.stream().map(CheckRequirement::getId).collect(Collectors.toList());
|
|
|
+ // saveModelByDTO 会按 requirement.sbType 加载该类别下全部保养项并建立关联
|
|
|
+ CheckRequirement marker = new CheckRequirement();
|
|
|
+ marker.setSbType(typeId);
|
|
|
+ dto.setRequirementList(new ArrayList<>(Collections.singletonList(marker)));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 维护等级
|
|
|
+ Integer level = parseLevel(trim(vo.getLevel()));
|
|
|
+ if (level == null) {
|
|
|
+ rowErrors.add(prefix + "维护等级填写错误(应为:一级/二级/三级)");
|
|
|
+ } else {
|
|
|
+ dto.setLevel(level);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 执行人方式
|
|
|
+ Integer checkUserType = parseCheckUserType(trim(vo.getCheckUserType()));
|
|
|
+ if (checkUserType == null) {
|
|
|
+ rowErrors.add(prefix + "执行人方式填写错误(应为:使用人/维修人/指定人/班组)");
|
|
|
+ } else {
|
|
|
+ dto.setCheckUserType(checkUserType);
|
|
|
+ // 班组方式必须解析到执行班组
|
|
|
+ if (CheckUserTypeEnum.TEAM.getValue().equals(checkUserType)) {
|
|
|
+ String deptName = trim(vo.getDeptName());
|
|
|
+ if (StringUtils.isBlank(deptName)) {
|
|
|
+ rowErrors.add(prefix + "执行人方式为班组时,执行班组不能为空");
|
|
|
+ } else {
|
|
|
+ Weekend<SysDept> deptWeekend = new Weekend<>(SysDept.class);
|
|
|
+ deptWeekend.weekendCriteria().andEqualTo(SysDept::getName, deptName);
|
|
|
+ List<SysDept> depts = sysDeptMapper.selectByExample(deptWeekend);
|
|
|
+ if (CollectionUtil.isEmpty(depts)) {
|
|
|
+ rowErrors.add(prefix + "未找到执行班组【" + deptName + "】");
|
|
|
+ } else {
|
|
|
+ dto.setCheckDeptId(depts.get(0).getDeptId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 计划周期 + 周期类型
|
|
|
+ String periodStr = trim(vo.getPeriod());
|
|
|
+ if (StringUtils.isBlank(periodStr)) {
|
|
|
+ rowErrors.add(prefix + "计划周期不能为空");
|
|
|
+ } else {
|
|
|
+ try {
|
|
|
+ dto.setPeriod(Integer.valueOf(periodStr));
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ rowErrors.add(prefix + "计划周期必须为数字");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Integer periodType = parsePeriodType(trim(vo.getPeriodType()));
|
|
|
+ if (periodType == null) {
|
|
|
+ rowErrors.add(prefix + "周期类型填写错误(应为:天/周/月/季度/年/公里/台时)");
|
|
|
+ } else {
|
|
|
+ dto.setPeriodType(periodType);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 上次执行日期(可空)
|
|
|
+ String lastDateStr = trim(vo.getLastDate());
|
|
|
+ if (StringUtils.isNotBlank(lastDateStr)) {
|
|
|
+ try {
|
|
|
+ dto.setLastDate(LocalDate.parse(lastDateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
|
|
+ } catch (Exception e) {
|
|
|
+ rowErrors.add(prefix + "上次执行日期格式错误(应为 yyyy-MM-dd)");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 可选字段
|
|
|
+ dto.setStandardHours(trim(vo.getStandardHours()));
|
|
|
+ dto.setRequirement(trim(vo.getRequirement()));
|
|
|
+ dto.setRemark(trim(vo.getRemark()));
|
|
|
+
|
|
|
+ // 8. 防重复导入:文件内“设备+类别”组合唯一,且库内该设备尚未绑定同类别标准
|
|
|
+ if (StringUtils.isNotBlank(dto.getSbId()) && StringUtils.isNotBlank(resolvedTypeId)) {
|
|
|
+ String key = dto.getSbId() + "|" + resolvedTypeId;
|
|
|
+ Integer firstRow = seenKeys.get(key);
|
|
|
+ if (firstRow != null) {
|
|
|
+ rowErrors.add(prefix + "与第" + firstRow + "行重复(同一设备+同一保养项类别只允许一条标准)");
|
|
|
+ } else {
|
|
|
+ seenKeys.put(key, rowNo);
|
|
|
+ }
|
|
|
+ if (CollectionUtil.isNotEmpty(categoryReqIds)) {
|
|
|
+ Weekend<CheckStandard> existWeekend = new Weekend<>(CheckStandard.class);
|
|
|
+ existWeekend.weekendCriteria().andEqualTo(CheckStandard::getSbId, dto.getSbId());
|
|
|
+ List<CheckStandard> existStandards = mapper.selectByExample(existWeekend);
|
|
|
+ if (CollectionUtil.isNotEmpty(existStandards)) {
|
|
|
+ List<String> existIds = existStandards.stream().map(CheckStandard::getId).collect(Collectors.toList());
|
|
|
+ Weekend<CheckStandardRequirement> relWeekend = new Weekend<>(CheckStandardRequirement.class);
|
|
|
+ relWeekend.weekendCriteria().andIn(CheckStandardRequirement::getStandardId, existIds)
|
|
|
+ .andIn(CheckStandardRequirement::getRequirementId, categoryReqIds);
|
|
|
+ if (checkStandardRequirementMapper.selectCountByExample(relWeekend) > 0) {
|
|
|
+ rowErrors.add(prefix + "该设备已存在类别【" + categoryName + "】的保养标准,不允许重复导入");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 强制走新增分支并对齐界面新增默认值
|
|
|
+ dto.setId(null);
|
|
|
+ dto.setType(CheckStandardTypeEnum.POLLING.getValue());
|
|
|
+ dto.setEnable(1);
|
|
|
+ dto.setName(StringUtils.isNotBlank(dto.getSbName())
|
|
|
+ ? dto.getSbName() + "-" + trim(vo.getCategoryName()) : trim(vo.getCategoryName()));
|
|
|
+ if (dto.getDetailList() == null) {
|
|
|
+ dto.setDetailList(new ArrayList<>());
|
|
|
+ }
|
|
|
+ return rowErrors.isEmpty() ? dto : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按新号(no)或旧号(zbh)查询设备
|
|
|
+ */
|
|
|
+ private SbInfo findSbInfoByNo(String no) {
|
|
|
+ Weekend<SbInfo> byNo = new Weekend<>(SbInfo.class);
|
|
|
+ byNo.weekendCriteria().andEqualTo(SbInfo::getNo, no);
|
|
|
+ List<SbInfo> list = sbInfoMapper.selectByExample(byNo);
|
|
|
+ if (!CollectionUtil.isEmpty(list)) {
|
|
|
+ return list.get(0);
|
|
|
+ }
|
|
|
+ Weekend<SbInfo> byZbh = new Weekend<>(SbInfo.class);
|
|
|
+ byZbh.weekendCriteria().andEqualTo(SbInfo::getZbh, no);
|
|
|
+ list = sbInfoMapper.selectByExample(byZbh);
|
|
|
+ return CollectionUtil.isEmpty(list) ? null : list.get(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Integer parseLevel(String text) {
|
|
|
+ if ("一级".equals(text)) {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ if ("二级".equals(text)) {
|
|
|
+ return 2;
|
|
|
+ }
|
|
|
+ if ("三级".equals(text)) {
|
|
|
+ return 3;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Integer parseCheckUserType(String text) {
|
|
|
+ if ("使用人".equals(text)) {
|
|
|
+ return CheckUserTypeEnum.USE_USER.getValue();
|
|
|
+ }
|
|
|
+ if ("维修人".equals(text)) {
|
|
|
+ return CheckUserTypeEnum.REPAIR_USER.getValue();
|
|
|
+ }
|
|
|
+ if ("指定人".equals(text) || "指定".equals(text)) {
|
|
|
+ return CheckUserTypeEnum.ZHIDING.getValue();
|
|
|
+ }
|
|
|
+ if ("班组".equals(text)) {
|
|
|
+ return CheckUserTypeEnum.TEAM.getValue();
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Integer parsePeriodType(String text) {
|
|
|
+ for (CheckPlanPeriodTypeEnum item : CheckPlanPeriodTypeEnum.values()) {
|
|
|
+ if (item.getDescription().equals(text)) {
|
|
|
+ return item.getValue();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String trim(String s) {
|
|
|
+ return s == null ? null : s.trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isAllBlank(ImportCheckStandardVO vo) {
|
|
|
+ return StringUtils.isBlank(vo.getSbNo()) && StringUtils.isBlank(vo.getCategoryName())
|
|
|
+ && StringUtils.isBlank(vo.getLevel()) && StringUtils.isBlank(vo.getCheckUserType())
|
|
|
+ && StringUtils.isBlank(vo.getDeptName()) && StringUtils.isBlank(vo.getPeriod())
|
|
|
+ && StringUtils.isBlank(vo.getPeriodType()) && StringUtils.isBlank(vo.getLastDate())
|
|
|
+ && StringUtils.isBlank(vo.getStandardHours()) && StringUtils.isBlank(vo.getRequirement())
|
|
|
+ && StringUtils.isBlank(vo.getRemark());
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
@Override
|
|
|
public List<CheckStandardVO> selectListBySbId(String sbId) {
|
|
|
if (StringUtils.isBlank(sbId)) {
|