package cn.iocoder.yudao.module.mes.service.qc.retentionsample;
|
|
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
|
import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO;
|
import cn.iocoder.yudao.module.mes.controller.admin.qc.retentionsample.vo.MesQcRetentionSamplePageReqVO;
|
import cn.iocoder.yudao.module.mes.controller.admin.qc.retentionsample.vo.MesQcRetentionSampleSaveReqVO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.mps.MesProMpsDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.retentionsample.MesQcRetentionSampleDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.qc.retentionsample.MesQcRetentionSampleMapper;
|
import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
|
import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderSourceTypeEnum;
|
import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum;
|
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
|
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
|
import jakarta.annotation.Resource;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.validation.annotation.Validated;
|
|
import java.time.LocalDateTime;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
|
|
/**
|
* MES 留样记录 Service 实现类
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
@Validated
|
public class MesQcRetentionSampleServiceImpl implements MesQcRetentionSampleService {
|
|
/**
|
* 默认留样保存时长(月)
|
*/
|
private static final int DEFAULT_RETENTION_MONTHS = 6;
|
|
@Resource
|
private MesQcRetentionSampleMapper retentionSampleMapper;
|
|
@Resource
|
private MesProWorkOrderService workOrderService;
|
@Resource
|
private MesProMpsMapper mpsMapper;
|
@Resource
|
private MesMdAutoCodeRecordService autoCodeRecordService;
|
@Resource
|
private ErpSaleOrderApi saleOrderApi;
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Long createRetentionSample(MesQcRetentionSampleSaveReqVO createReqVO) {
|
// 1. 校验工单
|
MesProWorkOrderDO workOrder = validateWorkOrder(createReqVO.getWorkOrderId());
|
// 2. 校验该工单是否已登记
|
MesQcRetentionSampleDO existing = retentionSampleMapper.selectByWorkOrderId(workOrder.getId());
|
if (existing != null) {
|
throw exception(QC_RETENTION_SAMPLE_WORK_ORDER_DUPLICATE,
|
workOrder.getCode(), existing.getCode());
|
}
|
|
// 3. 组装留样记录(工单相关字段以后端查询结果为准)
|
MesQcRetentionSampleDO sample = BeanUtils.toBean(createReqVO, MesQcRetentionSampleDO.class);
|
sample.setId(null);
|
sample.setCode(autoCodeRecordService.generateAutoCode(
|
MesMdAutoCodeRuleCodeEnum.QC_RETENTION_SAMPLE_CODE.getCode()));
|
sample.setWorkOrderCode(workOrder.getCode());
|
sample.setProductId(workOrder.getProductId());
|
sample.setBatchCode(workOrder.getBatchCode());
|
sample.setExpiryDate(resolveExpiryDate(sample.getSampleDate(), createReqVO.getExpiryDate()));
|
if (sample.getKeeperUserId() == null) {
|
sample.setKeeperUserId(getLoginUserId());
|
}
|
fillSaleOrder(workOrder, sample, createReqVO.getSaleOrderId());
|
|
// 4. 插入
|
retentionSampleMapper.insert(sample);
|
return sample.getId();
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void updateRetentionSample(MesQcRetentionSampleSaveReqVO updateReqVO) {
|
// 1. 校验存在
|
MesQcRetentionSampleDO oldSample = validateRetentionSampleExists(updateReqVO.getId());
|
// 2.1 不允许修改所属工单
|
if (ObjUtil.notEqual(oldSample.getWorkOrderId(), updateReqVO.getWorkOrderId())) {
|
throw exception(QC_RETENTION_SAMPLE_WORK_ORDER_CHANGED);
|
}
|
// 2.2 校验工单
|
MesProWorkOrderDO workOrder = validateWorkOrder(oldSample.getWorkOrderId());
|
|
// 3. 组装更新对象(编码、工单、产品、批次不允许修改)
|
MesQcRetentionSampleDO updateObj = BeanUtils.toBean(updateReqVO, MesQcRetentionSampleDO.class);
|
updateObj.setCode(null);
|
updateObj.setWorkOrderId(null);
|
updateObj.setWorkOrderCode(null);
|
updateObj.setProductId(null);
|
updateObj.setBatchCode(null);
|
updateObj.setExpiryDate(resolveExpiryDate(updateReqVO.getSampleDate(), updateReqVO.getExpiryDate()));
|
fillSaleOrder(workOrder, updateObj, updateReqVO.getSaleOrderId());
|
|
// 4. 更新
|
retentionSampleMapper.updateById(updateObj);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void deleteRetentionSample(Long id) {
|
// 1. 校验存在
|
MesQcRetentionSampleDO sample = validateRetentionSampleExists(id);
|
// 2. 工单已完成时不允许删除
|
MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(sample.getWorkOrderId());
|
if (workOrder != null
|
&& ObjUtil.equal(workOrder.getStatus(), MesProWorkOrderStatusEnum.FINISHED.getStatus())) {
|
throw exception(QC_RETENTION_SAMPLE_FINISHED_DELETE, workOrder.getCode());
|
}
|
|
retentionSampleMapper.deleteById(id);
|
}
|
|
// ==================== 校验与填充方法 ====================
|
|
private MesProWorkOrderDO validateWorkOrder(Long workOrderId) {
|
MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(workOrderId);
|
if (workOrder == null) {
|
throw exception(QC_RETENTION_SAMPLE_WORK_ORDER_NOT_EXISTS);
|
}
|
if (ObjUtil.equal(workOrder.getStatus(), MesProWorkOrderStatusEnum.CANCELED.getStatus())) {
|
throw exception(QC_RETENTION_SAMPLE_WORK_ORDER_CANCELED, workOrder.getCode());
|
}
|
return workOrder;
|
}
|
|
private MesQcRetentionSampleDO validateRetentionSampleExists(Long id) {
|
MesQcRetentionSampleDO sample = retentionSampleMapper.selectById(id);
|
if (sample == null) {
|
throw exception(QC_RETENTION_SAMPLE_NOT_EXISTS);
|
}
|
return sample;
|
}
|
|
private LocalDateTime resolveExpiryDate(LocalDateTime sampleDate, LocalDateTime expiryDate) {
|
if (expiryDate != null) {
|
return expiryDate;
|
}
|
return sampleDate.plusMonths(DEFAULT_RETENTION_MONTHS);
|
}
|
|
/**
|
* 解析并校验销售订单:
|
* 1. 由 MPS 下发的工单,销售订单以 MPS 为准;合并下发时由用户指定本次留样对应的销售订单;
|
* 2. 客户订单来源、但无 MPS 的工单,按工单来源单号校验所选销售订单;
|
* 3. 库存备货工单可不绑定销售订单。
|
*/
|
private void fillSaleOrder(MesProWorkOrderDO workOrder, MesQcRetentionSampleDO sample,
|
Long submittedSaleOrderId) {
|
// 1. 优先使用 MPS 记录的销售订单(合并下发时一个工单对应多张销售订单)
|
List<MesProMpsDO> mpsList = mpsMapper.selectListByWorkOrderId(workOrder.getId());
|
Map<Long, String> saleOrderMap = new LinkedHashMap<>();
|
for (MesProMpsDO mps : mpsList) {
|
if (mps.getSaleOrderId() != null) {
|
saleOrderMap.putIfAbsent(mps.getSaleOrderId(), mps.getSaleOrderNo());
|
}
|
}
|
if (saleOrderMap.size() == 1) {
|
Map.Entry<Long, String> entry = saleOrderMap.entrySet().iterator().next();
|
if (submittedSaleOrderId != null
|
&& ObjUtil.notEqual(submittedSaleOrderId, entry.getKey())) {
|
throw exception(QC_RETENTION_SAMPLE_SALE_ORDER_MISMATCH,
|
workOrder.getCode(), entry.getValue(),
|
resolveSaleOrderNoOrId(submittedSaleOrderId));
|
}
|
// 校验 MPS 记录的销售订单真实存在,避免把不存在的订单静默带入留样记录
|
validateMpsSaleOrderExists(workOrder, entry.getKey(), entry.getValue());
|
sample.setSaleOrderId(entry.getKey());
|
sample.setSaleOrderNo(entry.getValue());
|
return;
|
}
|
if (saleOrderMap.size() > 1) {
|
String saleOrderNos = String.join("、", saleOrderMap.values());
|
if (submittedSaleOrderId == null) {
|
throw exception(QC_RETENTION_SAMPLE_SALE_ORDER_AMBIGUOUS,
|
workOrder.getCode(), saleOrderNos);
|
}
|
if (!saleOrderMap.containsKey(submittedSaleOrderId)) {
|
throw exception(QC_RETENTION_SAMPLE_SALE_ORDER_MISMATCH,
|
workOrder.getCode(), saleOrderNos,
|
resolveSaleOrderNoOrId(submittedSaleOrderId));
|
}
|
validateMpsSaleOrderExists(workOrder, submittedSaleOrderId,
|
saleOrderMap.get(submittedSaleOrderId));
|
sample.setSaleOrderId(submittedSaleOrderId);
|
sample.setSaleOrderNo(saleOrderMap.get(submittedSaleOrderId));
|
return;
|
}
|
|
// 2. 客户订单来源工单:必须选择与来源单号一致的销售订单
|
if (ObjUtil.equal(workOrder.getOrderSourceType(), MesProWorkOrderSourceTypeEnum.ORDER.getType())
|
&& StrUtil.isNotEmpty(workOrder.getOrderSourceCode())) {
|
if (submittedSaleOrderId == null) {
|
throw exception(QC_RETENTION_SAMPLE_SALE_ORDER_REQUIRED,
|
workOrder.getCode(), workOrder.getOrderSourceCode());
|
}
|
ErpSaleOrderRespDTO saleOrder = getSaleOrderOrThrow(submittedSaleOrderId);
|
if (StrUtil.isNotEmpty(saleOrder.getNo())
|
&& ObjUtil.notEqual(saleOrder.getNo(), workOrder.getOrderSourceCode())) {
|
throw exception(QC_RETENTION_SAMPLE_SALE_ORDER_MISMATCH,
|
workOrder.getCode(), workOrder.getOrderSourceCode(), saleOrder.getNo());
|
}
|
sample.setSaleOrderId(saleOrder.getId());
|
sample.setSaleOrderNo(saleOrder.getNo());
|
return;
|
}
|
|
// 3. 库存备货工单:可选绑定
|
if (submittedSaleOrderId != null) {
|
ErpSaleOrderRespDTO saleOrder = getSaleOrderOrThrow(submittedSaleOrderId);
|
sample.setSaleOrderId(saleOrder.getId());
|
sample.setSaleOrderNo(saleOrder.getNo());
|
}
|
}
|
|
private void validateMpsSaleOrderExists(MesProWorkOrderDO workOrder, Long saleOrderId,
|
String saleOrderNo) {
|
CommonResult<ErpSaleOrderRespDTO> result = saleOrderApi.getSaleOrder(saleOrderId);
|
if (result.isError() || result.getData() == null) {
|
throw exception(QC_RETENTION_SAMPLE_MPS_SALE_ORDER_NOT_EXISTS,
|
workOrder.getCode(), StrUtil.blankToDefault(saleOrderNo, "未记录"), saleOrderId);
|
}
|
}
|
|
private ErpSaleOrderRespDTO getSaleOrderOrThrow(Long saleOrderId) {
|
CommonResult<ErpSaleOrderRespDTO> result = saleOrderApi.getSaleOrder(saleOrderId);
|
if (result.isError() || result.getData() == null) {
|
throw exception(QC_RETENTION_SAMPLE_SALE_ORDER_NOT_EXISTS, "ID=" + saleOrderId);
|
}
|
return result.getData();
|
}
|
|
private String resolveSaleOrderNoOrId(Long saleOrderId) {
|
CommonResult<ErpSaleOrderRespDTO> result = saleOrderApi.getSaleOrder(saleOrderId);
|
if (result.isSuccess() && result.getData() != null) {
|
return result.getData().getNo();
|
}
|
return "ID=" + saleOrderId;
|
}
|
|
// ==================== 查询方法 ====================
|
|
@Override
|
public MesQcRetentionSampleDO getRetentionSample(Long id) {
|
return retentionSampleMapper.selectById(id);
|
}
|
|
@Override
|
public PageResult<MesQcRetentionSampleDO> getRetentionSamplePage(
|
MesQcRetentionSamplePageReqVO pageReqVO) {
|
return retentionSampleMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
public MesQcRetentionSampleDO getRetentionSampleByWorkOrderId(Long workOrderId) {
|
return retentionSampleMapper.selectByWorkOrderId(workOrderId);
|
}
|
|
}
|