package cn.iocoder.yudao.module.mes.service.qc.ncr;
|
import java.util.List;
|
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrPageReqVO;
|
import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrSaveReqVO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcNcrMapper;
|
import cn.iocoder.yudao.module.mes.enums.qc.MesNcrStatusEnum;
|
import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
|
import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
|
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
|
import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
|
import cn.iocoder.yudao.module.mes.service.qc.ipqc.MesQcIpqcService;
|
import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcService;
|
import cn.iocoder.yudao.module.mes.service.qc.rqc.MesQcRqcService;
|
import cn.iocoder.yudao.module.mes.service.qc.support.MesQcItemResolver;
|
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
|
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 lombok.extern.slf4j.Slf4j;
|
|
import java.time.LocalDateTime;
|
import java.util.Collection;
|
import java.util.Map;
|
import java.util.Objects;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
|
import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrImportExcelVO;
|
import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrImportRespVO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
|
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.util.StrUtil;
|
import java.util.ArrayList;
|
import java.util.LinkedHashMap;
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
/**
|
* MES 不合格品处理单(NCR) Service 实现类
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
@Validated
|
@Slf4j
|
public class MesQcNcrServiceImpl implements MesQcNcrService {
|
|
@Resource
|
private MesMdItemService itemService;
|
|
@Resource
|
private MesQcNcrMapper ncrMapper;
|
|
@Resource
|
private MdmItemApi mdmItemApi;
|
|
@Resource
|
private MesQcItemResolver qcItemResolver;
|
|
@Resource
|
private MesMdAutoCodeRecordService autoCodeRecordService;
|
|
@Resource
|
@Lazy
|
private MesQcIqcService iqcService;
|
@Resource
|
@Lazy
|
private MesQcIpqcService ipqcService;
|
@Resource
|
@Lazy
|
private MesQcOqcService oqcService;
|
@Resource
|
@Lazy
|
private MesQcRqcService rqcService;
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Long createNcr(MesQcNcrSaveReqVO createReqVO) {
|
if (StrUtil.isBlank(createReqVO.getCode())) {
|
createReqVO.setCode(generateNcrCode());
|
}
|
// 1. 校验数据
|
validateNcrCodeUnique(null, createReqVO.getCode());
|
validateItemExists(createReqVO.getItemId());
|
|
// 2. 插入主表
|
MesQcNcrDO ncr = BeanUtils.toBean(createReqVO, MesQcNcrDO.class)
|
.setStatus(MesNcrStatusEnum.PENDING_REVIEW.getStatus());
|
// 如果传入了 qcId 和 qcType,则查询来源检验单编号并冗余存储
|
if (createReqVO.getQcType() != null && createReqVO.getQcId() != null) {
|
ncr.setQcCode(lookupQcCode(createReqVO.getQcType(), createReqVO.getQcId()));
|
}
|
ncrMapper.insert(ncr);
|
return ncr.getId();
|
}
|
|
private String generateNcrCode() {
|
try {
|
return autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.QC_NCR_CODE.getCode());
|
} catch (Exception e) {
|
log.warn("NCR 编码规则 {} 不可用,降级使用时间戳编码",
|
MesMdAutoCodeRuleCodeEnum.QC_NCR_CODE.getCode(), e);
|
return "NCR" + LocalDateTime.now()
|
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
|
}
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void updateNcr(MesQcNcrSaveReqVO updateReqVO) {
|
// 1. 校验存在 + 只允许待评审状态编辑
|
MesQcNcrDO ncr = validateNcrStatusPendingReview(updateReqVO.getId());
|
validateNcrCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
|
validateItemExists(updateReqVO.getItemId());
|
|
// 2. 更新主表(仅允许更新部分字段)
|
MesQcNcrDO updateObj = BeanUtils.toBean(updateReqVO, MesQcNcrDO.class);
|
updateObj.setQcType(null); // 不允许修改检验类型
|
updateObj.setQcId(null); // 不允许修改检验单
|
updateObj.setStatus(null); // 不允许修改状态
|
ncrMapper.updateById(updateObj);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void deleteNcr(Long id) {
|
// 校验存在 + 只允许删除待评审状态
|
validateNcrStatusPendingReview(id);
|
ncrMapper.deleteById(id);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void reviewNcr(Long id, Integer disposition, Long responsibleDeptId,
|
Integer defectLevel, String reviewOpinion) {
|
// 1. 校验存在 + 待评审状态
|
validateNcrStatusPendingReview(id);
|
|
// 2. 更新评审信息
|
MesQcNcrDO updateObj = new MesQcNcrDO()
|
.setId(id)
|
.setDisposition(disposition)
|
.setResponsibleDeptId(responsibleDeptId)
|
.setReviewOpinion(reviewOpinion)
|
.setReviewerUserId(getLoginUserId())
|
.setReviewDate(LocalDateTime.now())
|
.setStatus(MesNcrStatusEnum.REVIEWED.getStatus());
|
if (defectLevel != null) {
|
updateObj.setDefectLevel(defectLevel);
|
}
|
ncrMapper.updateById(updateObj);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void disposeNcr(Long id, Long handlerUserId, String handleResult) {
|
// 1. 校验存在 + 已评审状态
|
MesQcNcrDO ncr = validateNcrExists(id);
|
if (!MesNcrStatusEnum.REVIEWED.getStatus().equals(ncr.getStatus())) {
|
throw exception(QC_NCR_NOT_REVIEWED);
|
}
|
|
// 2. 更新处置信息
|
// TODO: WM 模块集成 — 处置为退货时触发供应商退货,处置为报废时触发杂项出库
|
ncrMapper.updateById(new MesQcNcrDO()
|
.setId(id)
|
.setHandlerUserId(handlerUserId)
|
.setHandleDate(LocalDateTime.now())
|
.setHandleResult(handleResult)
|
.setStatus(MesNcrStatusEnum.DISPOSED.getStatus()));
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void closeNcr(Long id, String closeOpinion) {
|
// 1. 校验存在 + 已处置状态
|
MesQcNcrDO ncr = validateNcrExists(id);
|
if (!MesNcrStatusEnum.DISPOSED.getStatus().equals(ncr.getStatus())) {
|
throw exception(QC_NCR_NOT_DISPOSED);
|
}
|
|
// 2. 更新关闭信息
|
ncrMapper.updateById(new MesQcNcrDO()
|
.setId(id)
|
.setCloseUserId(getLoginUserId())
|
.setCloseDate(LocalDateTime.now())
|
.setCloseOpinion(closeOpinion)
|
.setStatus(MesNcrStatusEnum.CLOSED.getStatus()));
|
}
|
|
// ==================== 校验方法 ====================
|
|
@Override
|
public MesQcNcrDO validateNcrExists(Long id) {
|
MesQcNcrDO ncr = ncrMapper.selectById(id);
|
if (ncr == null) {
|
throw exception(QC_NCR_NOT_EXISTS);
|
}
|
return ncr;
|
}
|
|
/**
|
* 校验 NCR 存在且为待评审状态
|
*/
|
private MesQcNcrDO validateNcrStatusPendingReview(Long id) {
|
MesQcNcrDO ncr = validateNcrExists(id);
|
if (!MesNcrStatusEnum.PENDING_REVIEW.getStatus().equals(ncr.getStatus())) {
|
throw exception(QC_NCR_NOT_PENDING_REVIEW);
|
}
|
return ncr;
|
}
|
|
/**
|
* 校验 NCR 编号唯一
|
*/
|
private void validateNcrCodeUnique(Long id, String code) {
|
MesQcNcrDO existing = ncrMapper.selectByCode(code);
|
if (existing != null && !existing.getId().equals(id)) {
|
throw exception(QC_NCR_CODE_DUPLICATE);
|
}
|
}
|
|
/**
|
* 校验物料存在
|
*/
|
private void validateItemExists(Long itemId) {
|
// 不合格品单的物料可能来自 MDM 单据或 MES 工单,需兼容两套 ID
|
qcItemResolver.validateItemExists(itemId);
|
}
|
|
/**
|
* 根据检验类型和检验单 ID 查询来源检验单编号
|
*/
|
private String lookupQcCode(Integer qcType, Long qcId) {
|
if (Objects.equals(qcType, MesQcTypeEnum.IQC.getType())) {
|
return iqcService.getIqc(qcId).getCode();
|
} else if (Objects.equals(qcType, MesQcTypeEnum.IPQC.getType())) {
|
return ipqcService.getIpqc(qcId).getCode();
|
} else if (Objects.equals(qcType, MesQcTypeEnum.OQC.getType())) {
|
return oqcService.getOqc(qcId).getCode();
|
} else if (Objects.equals(qcType, MesQcTypeEnum.RQC.getType())) {
|
return rqcService.getRqc(qcId).getCode();
|
}
|
return null;
|
}
|
|
// ==================== 查询方法 ====================
|
|
@Override
|
public MesQcNcrDO getNcr(Long id) {
|
return ncrMapper.selectById(id);
|
}
|
|
@Override
|
public PageResult<MesQcNcrDO> getNcrPage(MesQcNcrPageReqVO pageReqVO) {
|
return ncrMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
public Map<Long, MesQcNcrDO> getNcrMap(Collection<Long> ids) {
|
return convertMap(ncrMapper.selectBatchIds(ids), MesQcNcrDO::getId);
|
}
|
|
|
@Override
|
public MesQcNcrImportRespVO importNcrList(List<MesQcNcrImportExcelVO> importList, boolean updateSupport) {
|
if (CollUtil.isEmpty(importList)) {
|
throw exception(QC_NCR_IMPORT_LIST_IS_EMPTY);
|
}
|
MesQcNcrImportRespVO respVO = MesQcNcrImportRespVO.builder()
|
.createCodes(new ArrayList<>()).updateCodes(new ArrayList<>())
|
.failureCodes(new LinkedHashMap<>()).build();
|
AtomicInteger index = new AtomicInteger(1);
|
importList.forEach(importItem -> {
|
int currentIndex = index.getAndIncrement();
|
String key = StrUtil.blankToDefault(importItem.getCode(), "第 " + currentIndex + " 行");
|
if (StrUtil.isBlank(importItem.getCode())) {
|
respVO.getFailureCodes().put(key, "NCR单号不能为空");
|
return;
|
}
|
if (StrUtil.isBlank(importItem.getName())) {
|
respVO.getFailureCodes().put(key, "NCR名称不能为空");
|
return;
|
}
|
if (StrUtil.isBlank(importItem.getItemCode())) {
|
respVO.getFailureCodes().put(key, "不合格物料编码不能为空");
|
return;
|
}
|
MesMdItemDO item = itemService.getItemByCode(importItem.getItemCode());
|
if (item == null) {
|
respVO.getFailureCodes().put(key, "物料不存在:" + importItem.getItemCode());
|
return;
|
}
|
if (ncrMapper.selectByCode(importItem.getCode()) != null) {
|
respVO.getFailureCodes().put(key, "NCR单号已存在");
|
return;
|
}
|
try {
|
MesQcNcrSaveReqVO createReqVO = BeanUtils.toBean(importItem, MesQcNcrSaveReqVO.class);
|
createReqVO.setItemId(item.getId());
|
createNcr(createReqVO);
|
respVO.getCreateCodes().add(importItem.getCode());
|
} catch (ServiceException e) {
|
respVO.getFailureCodes().put(key, e.getMessage());
|
}
|
});
|
return respVO;
|
}
|
|
}
|