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 cn.iocoder.yudao.module.infra.api.config.ConfigApi;
|
import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringPageReqVO;
|
import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringSaveReqVO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.dv.metering.MesDvMeteringDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.dv.metering.MesDvMeteringMapper;
|
import cn.iocoder.yudao.module.mes.service.dv.meteringcheck.MesDvMeteringCheckService;
|
import cn.hutool.core.util.StrUtil;
|
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;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.DV_METERING_CODE_DUPLICATE;
|
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.DV_METERING_HAS_CHECK_RECORD;
|
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.DV_METERING_NOT_EXISTS;
|
|
/**
|
* MES 计量器具 Service 实现类
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
@Validated
|
public class MesDvMeteringServiceImpl implements MesDvMeteringService {
|
|
/**
|
* 提醒天数配置 KEY(infra_config)
|
*/
|
private static final String REMIND_DAYS_CONFIG_KEY = "mes.dv-metering.remind-days";
|
/**
|
* 提醒天数默认值
|
*/
|
private static final int DEFAULT_REMIND_DAYS = 30;
|
|
@Resource
|
private MesDvMeteringMapper meteringMapper;
|
@Resource
|
@Lazy // 延迟加载,避免与计量检测记录 Service 循环依赖
|
private MesDvMeteringCheckService meteringCheckService;
|
@Resource
|
private MesDvMeteringRemindService meteringRemindService;
|
@Resource
|
private ConfigApi configApi;
|
|
@Resource
|
private DeptApi deptApi;
|
@Resource
|
private AdminUserApi adminUserApi;
|
|
@Override
|
public Long createMetering(MesDvMeteringSaveReqVO createReqVO) {
|
// 校验编码唯一
|
validateMeteringCodeUnique(null, createReqVO.getCode());
|
|
// 插入
|
MesDvMeteringDO metering = BeanUtils.toBean(createReqVO, MesDvMeteringDO.class);
|
meteringMapper.insert(metering);
|
return metering.getId();
|
}
|
|
@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());
|
// 校验编码唯一
|
validateMeteringCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
|
|
// 更新
|
MesDvMeteringDO updateObj = BeanUtils.toBean(updateReqVO, MesDvMeteringDO.class);
|
meteringMapper.updateById(updateObj);
|
}
|
|
@Override
|
public void deleteMetering(Long id) {
|
// 校验存在
|
validateMeteringExists(id);
|
// 校验无关联检测记录
|
if (meteringCheckService.getMeteringCheckCountByMeteringId(id) > 0) {
|
throw exception(DV_METERING_HAS_CHECK_RECORD);
|
}
|
|
// 删除
|
meteringMapper.deleteById(id);
|
}
|
|
@Override
|
public void validateMeteringExists(Long id) {
|
if (meteringMapper.selectById(id) == null) {
|
throw exception(DV_METERING_NOT_EXISTS);
|
}
|
}
|
|
private void validateMeteringCodeUnique(Long id, String code) {
|
if (StrUtil.isBlank(code)) {
|
return;
|
}
|
MesDvMeteringDO metering = meteringMapper.selectByCode(code);
|
if (metering == null) {
|
return;
|
}
|
if (ObjUtil.notEqual(metering.getId(), id)) {
|
throw exception(DV_METERING_CODE_DUPLICATE);
|
}
|
}
|
|
@Override
|
public MesDvMeteringDO getMetering(Long id) {
|
return meteringMapper.selectById(id);
|
}
|
|
@Override
|
public PageResult<MesDvMeteringDO> getMeteringPage(MesDvMeteringPageReqVO pageReqVO) {
|
// 1. 到期状态转换为日期范围,走 DB 过滤
|
Integer expireStatus = pageReqVO.getExpireStatus();
|
if (expireStatus != null) {
|
LocalDate today = LocalDate.now();
|
int remindDays = getRemindDays();
|
switch (expireStatus) {
|
case 1 -> { // 已过期:到期日 < 今天
|
pageReqVO.setNextCheckDateEnd(today.minusDays(1));
|
}
|
case 2 -> { // 即将到期:到期日在 [今天, 今天+提醒天数]
|
pageReqVO.setNextCheckDateBegin(today);
|
pageReqVO.setNextCheckDateEnd(today.plusDays(remindDays));
|
}
|
case 3 -> { // 正常:到期日 > 今天+提醒天数
|
pageReqVO.setNextCheckDateBegin(today.plusDays(remindDays + 1));
|
}
|
default -> {
|
}
|
}
|
}
|
// 2. 查询
|
PageResult<MesDvMeteringDO> pageResult = meteringMapper.selectPage(pageReqVO);
|
// 3. 懒触发到期提醒站内信(best-effort,不影响查询)
|
meteringRemindService.sendExpireRemind();
|
return pageResult;
|
}
|
|
@Override
|
public int getRemindDays() {
|
String value = configApi.getConfigValueByKey(REMIND_DAYS_CONFIG_KEY);
|
if (StrUtil.isBlank(value)) {
|
return DEFAULT_REMIND_DAYS;
|
}
|
try {
|
return Integer.parseInt(value.trim());
|
} catch (NumberFormatException ex) {
|
return DEFAULT_REMIND_DAYS;
|
}
|
}
|
|
@Override
|
public void updateMeteringCheckInfo(Long meteringId, LocalDate lastCheckDate, LocalDate nextCheckDate) {
|
meteringMapper.updateById(new MesDvMeteringDO()
|
.setId(meteringId)
|
.setLastCheckDate(lastCheckDate)
|
.setNextCheckDate(nextCheckDate));
|
}
|
|
@Override
|
public List<MesDvMeteringDO> getMeteringList() {
|
return meteringMapper.selectList();
|
}
|
|
@Override
|
public List<MesDvMeteringDO> getMeteringList(Collection<Long> ids) {
|
if (CollUtil.isEmpty(ids)) {
|
return Collections.emptyList();
|
}
|
return meteringMapper.selectByIds(ids);
|
}
|
|
}
|