yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringServiceImpl.java
@@ -1,6 +1,12 @@
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;
@@ -14,9 +20,14 @@
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;
@@ -54,6 +65,11 @@
    @Resource
    private ConfigApi configApi;
    @Resource
    private DeptApi deptApi;
    @Resource
    private AdminUserApi adminUserApi;
    @Override
    public Long createMetering(MesDvMeteringSaveReqVO createReqVO) {
        // 校验编码唯一
@@ -66,6 +82,163 @@
    }
    @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());