| | |
| | | package cn.iocoder.yudao.module.mes.service.dv.metering; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringImportExcelVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringImportRespVO; |
| | | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| | | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.Map; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | |
| | | @Resource |
| | | private ConfigApi configApi; |
| | | |
| | | @Resource |
| | | private DeptApi deptApi; |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | |
| | | @Override |
| | | public Long createMetering(MesDvMeteringSaveReqVO createReqVO) { |
| | | // 校验编码唯一 |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesDvMeteringImportRespVO importMeteringList(List<MesDvMeteringImportExcelVO> importList) { |
| | | MesDvMeteringImportRespVO respVO = MesDvMeteringImportRespVO.builder() |
| | | .createList(new ArrayList<>()) |
| | | .updateList(new ArrayList<>()) |
| | | .failureList(new LinkedHashMap<>()) |
| | | .build(); |
| | | if (CollUtil.isEmpty(importList)) { |
| | | return respVO; |
| | | } |
| | | |
| | | // 1. 预热“部门名称 -> 部门编号”映射(部门/责任人为非必填,匹配不到该行失败) |
| | | Map<String, Long> deptIdMap = new HashMap<>(); |
| | | List<DeptRespDTO> depts = deptApi.getDeptList(); |
| | | if (CollUtil.isNotEmpty(depts)) { |
| | | depts.forEach(dept -> { |
| | | if (StrUtil.isNotBlank(dept.getName())) { |
| | | deptIdMap.putIfAbsent(dept.getName().trim(), dept.getId()); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | // 2. 逐行导入(单行失败不影响其它行) |
| | | for (int i = 0; i < importList.size(); i++) { |
| | | MesDvMeteringImportExcelVO row = importList.get(i); |
| | | if (row == null || (StrUtil.isBlank(row.getCode()) && StrUtil.isBlank(row.getName()))) { |
| | | continue; // 跳过空行 |
| | | } |
| | | String label = StrUtil.isNotBlank(row.getCode()) ? row.getCode().trim() : "第 " + (i + 2) + " 行"; |
| | | try { |
| | | boolean restored = createMeteringByImport(row, deptIdMap); |
| | | if (restored) { |
| | | respVO.getUpdateList().add(row.getCode().trim()); // 恢复/覆盖更新 |
| | | } else { |
| | | respVO.getCreateList().add(row.getCode().trim()); |
| | | } |
| | | } catch (Exception ex) { |
| | | respVO.getFailureList().put(label, resolveImportErrorMessage(ex)); |
| | | } |
| | | } |
| | | return respVO; |
| | | } |
| | | |
| | | /** |
| | | * 依据导入行创建一条计量器具记录 |
| | | */ |
| | | private boolean createMeteringByImport(MesDvMeteringImportExcelVO row, Map<String, Long> deptIdMap) { |
| | | // 1. 必填校验 |
| | | if (StrUtil.isBlank(row.getCode())) { |
| | | throw new IllegalArgumentException("计量器具编码不能为空"); |
| | | } |
| | | if (StrUtil.isBlank(row.getName())) { |
| | | throw new IllegalArgumentException("计量器具名称不能为空"); |
| | | } |
| | | if (row.getCategory() == null) { |
| | | throw new IllegalArgumentException("器具类别不能为空(请填字典中文,如 卡尺/其他)"); |
| | | } |
| | | if (row.getStatus() == null) { |
| | | throw new IllegalArgumentException("状态不能为空(请填字典中文,如 在用/停用/报废)"); |
| | | } |
| | | // 2. 编码判重(含“已被逻辑删除”的记录):未删除 => 报错;已删除 => 稍后恢复并更新 |
| | | MesDvMeteringDO exist = meteringMapper.selectByCodeIncludeDeleted(row.getCode().trim()); |
| | | if (exist != null && !Boolean.TRUE.equals(exist.getDeleted())) { |
| | | throw new IllegalArgumentException("计量器具编码已存在:" + row.getCode()); |
| | | } |
| | | |
| | | // 3. 组装 |
| | | MesDvMeteringSaveReqVO reqVO = new MesDvMeteringSaveReqVO(); |
| | | reqVO.setCode(row.getCode().trim()); |
| | | reqVO.setName(row.getName().trim()); |
| | | reqVO.setCategory(row.getCategory()); |
| | | reqVO.setSpecification(row.getSpecification()); |
| | | reqVO.setBrand(row.getBrand()); |
| | | reqVO.setPrecision(row.getPrecision()); |
| | | reqVO.setRange(row.getRange()); |
| | | reqVO.setCheckCycleCount(row.getCheckCycleCount() == null ? 12 : row.getCheckCycleCount()); |
| | | reqVO.setUseDate(parseImportUseDate(row.getUseDate())); |
| | | reqVO.setStatus(row.getStatus()); |
| | | reqVO.setRemark(row.getRemark()); |
| | | // 使用部门:按名称匹配 |
| | | if (StrUtil.isNotBlank(row.getDeptName())) { |
| | | Long deptId = deptIdMap.get(row.getDeptName().trim()); |
| | | if (deptId == null) { |
| | | throw new IllegalArgumentException("使用部门不存在:" + row.getDeptName()); |
| | | } |
| | | reqVO.setDeptId(deptId); |
| | | } |
| | | // 责任人:按昵称匹配 |
| | | if (StrUtil.isNotBlank(row.getChargeUserName())) { |
| | | Long userId = matchUserIdByNickname(row.getChargeUserName().trim()); |
| | | if (userId == null) { |
| | | throw new IllegalArgumentException("责任人不存在:" + row.getChargeUserName()); |
| | | } |
| | | reqVO.setChargeUserId(userId); |
| | | } |
| | | |
| | | // 4. 落库:命中“已删除”的同编码 => 先恢复再更新;否则新增 |
| | | if (exist != null) { |
| | | meteringMapper.restoreDeletedById(exist.getId()); |
| | | reqVO.setId(exist.getId()); |
| | | updateMetering(reqVO); |
| | | return true; // 表示“恢复/更新” |
| | | } |
| | | createMetering(reqVO); |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * 按昵称匹配用户编号(精确匹配优先,取不到则用模糊结果第一条) |
| | | */ |
| | | private Long matchUserIdByNickname(String nickname) { |
| | | List<AdminUserRespDTO> users = adminUserApi.getUserListByNickname(nickname); |
| | | if (CollUtil.isEmpty(users)) { |
| | | return null; |
| | | } |
| | | return users.stream() |
| | | .filter(user -> nickname.equals(user.getNickname())) |
| | | .map(AdminUserRespDTO::getId) |
| | | .findFirst() |
| | | .orElseGet(() -> users.get(0).getId()); |
| | | } |
| | | |
| | | /** |
| | | * 解析投入使用日期(yyyy-MM-dd / yyyy/M/d);为空返回 null |
| | | */ |
| | | private LocalDate parseImportUseDate(String text) { |
| | | if (StrUtil.isBlank(text)) { |
| | | return null; |
| | | } |
| | | String value = text.trim(); |
| | | for (String pattern : new String[]{"yyyy-MM-dd", "yyyy/M/d"}) { |
| | | try { |
| | | return LocalDate.parse(value, java.time.format.DateTimeFormatter.ofPattern(pattern)); |
| | | } catch (Exception ignored) { |
| | | // 继续尝试下一个格式 |
| | | } |
| | | } |
| | | try { |
| | | return java.time.LocalDateTime.parse(value, |
| | | java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).toLocalDate(); |
| | | } catch (Exception ignored) { |
| | | // 忽略 |
| | | } |
| | | throw new IllegalArgumentException("投入使用日期格式不正确:" + text + ",示例 2026-01-01"); |
| | | } |
| | | |
| | | /** |
| | | * 提取导入失败原因(ServiceException 取业务提示) |
| | | */ |
| | | private String resolveImportErrorMessage(Exception ex) { |
| | | if (ex instanceof cn.iocoder.yudao.framework.common.exception.ServiceException serviceException) { |
| | | return serviceException.getMessage(); |
| | | } |
| | | return StrUtil.blankToDefault(ex.getMessage(), "导入失败"); |
| | | } |
| | | |
| | | @Override |
| | | public void updateMetering(MesDvMeteringSaveReqVO updateReqVO) { |
| | | // 校验存在 |
| | | validateMeteringExists(updateReqVO.getId()); |