indicatorIds = convertSet(items, MesQcIndicatorResultSaveReqVO.Item::getIndicatorId);
+ return indicatorService.validateIndicatorListExists(indicatorIds);
+ }
+
+ /**
+ * 按检测项的 resultType 校验明细值格式
+ *
+ * FLOAT → 必须可解析为 BigDecimal;INTEGER → 必须可解析为整数;
+ * DICT → 必须属于字典值域;FILE → 必须为 http/https URL;TEXT → 放行
+ */
+ private void validateDetailValues(List items,
+ Map indicatorMap) {
if (CollUtil.isEmpty(items)) {
return;
}
- Set indicatorIds = convertSet(items, MesQcIndicatorResultSaveReqVO.Item::getIndicatorId);
- List indicators = indicatorService.getIndicatorList(indicatorIds);
- if (indicators.size() != indicatorIds.size()) {
- Set existIds = convertSet(indicators, MesQcIndicatorDO::getId);
- indicatorIds.removeAll(existIds);
- throw exception(QC_INDICATOR_NOT_EXISTS);
+ for (MesQcIndicatorResultSaveReqVO.Item item : items) {
+ if (item.getIndicatorId() == null || StrUtil.isBlank(item.getValue())) {
+ continue;
+ }
+ MesQcIndicatorDO indicator = indicatorMap.get(item.getIndicatorId());
+ if (indicator == null || indicator.getResultType() == null) {
+ continue;
+ }
+ Integer resultType = indicator.getResultType();
+ if (Objects.equals(resultType, MesQcResultValueTypeEnum.FLOAT.getType())) {
+ try {
+ new BigDecimal(item.getValue());
+ } catch (NumberFormatException e) {
+ throw exception(QC_RESULT_VALUE_FORMAT_INVALID,
+ "检测项[" + indicator.getName() + "]要求浮点数,实际值=" + item.getValue());
+ }
+ } else if (Objects.equals(resultType, MesQcResultValueTypeEnum.INTEGER.getType())) {
+ try {
+ Long.parseLong(item.getValue());
+ } catch (NumberFormatException e) {
+ throw exception(QC_RESULT_VALUE_FORMAT_INVALID,
+ "检测项[" + indicator.getName() + "]要求整数,实际值=" + item.getValue());
+ }
+ }
+ // DICT:校验值属于对应字典类型
+ if (Objects.equals(resultType, MesQcResultValueTypeEnum.DICT.getType())) {
+ String dictType = indicator.getResultSpecification();
+ if (StrUtil.isNotBlank(dictType)) {
+ dictDataApi.validateDictDataList(dictType, Collections.singleton(item.getValue()));
+ }
+ }
+ if (Objects.equals(resultType, MesQcResultValueTypeEnum.FILE.getType())
+ && !isHttpUrl(item.getValue())) {
+ throw exception(QC_RESULT_VALUE_FORMAT_INVALID,
+ "检测项[" + indicator.getName() + "]要求文件 URL,实际值=" + item.getValue());
+ }
+ // TEXT 不做格式校验
+ }
+ }
+
+ private boolean isHttpUrl(String value) {
+ try {
+ URI uri = URI.create(value);
+ String scheme = uri.getScheme();
+ return StrUtil.isNotBlank(uri.getHost())
+ && ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme));
+ } catch (IllegalArgumentException e) {
+ return false;
}
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcLineService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcLineService.java
index df2d09e49..677c098b4 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcLineService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcLineService.java
@@ -69,4 +69,12 @@ public interface MesQcIpqcLineService {
*/
void deleteListByIpqcId(Long ipqcId);
+ /**
+ * 统计使用指定计量单位的过程检验行数量
+ *
+ * @param unitMeasureId 计量单位编号
+ * @return 引用数量
+ */
+ Long getIpqcLineCountByUnitMeasureId(Long unitMeasureId);
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcLineServiceImpl.java
index 7bf2ec8d2..03bd3ebc5 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcLineServiceImpl.java
@@ -9,13 +9,13 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcLineMapper;
-import cn.iocoder.yudao.module.mes.dal.mysql.qc.template.MesQcTemplateIndicatorMapper;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
+import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateIndicatorService;
+import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -37,11 +37,11 @@ public class MesQcIpqcLineServiceImpl implements MesQcIpqcLineService {
@Resource
private MesQcIpqcLineMapper ipqcLineMapper;
- @Resource
- private MesQcTemplateIndicatorMapper templateIndicatorMapper;
@Resource
private MesQcIndicatorService indicatorService;
+ @Resource
+ private MesQcTemplateIndicatorService templateIndicatorService;
@Override
public MesQcIpqcLineDO validateIpqcLineExists(Long id) {
@@ -64,7 +64,7 @@ public class MesQcIpqcLineServiceImpl implements MesQcIpqcLineService {
@Override
public void createLinesFromTemplate(Long ipqcId, Long templateId) {
- List templateIndicators = templateIndicatorMapper.selectListByTemplateId(templateId);
+ List templateIndicators = templateIndicatorService.getTemplateIndicatorListByTemplateId(templateId);
if (CollUtil.isEmpty(templateIndicators)) {
return;
}
@@ -127,4 +127,9 @@ public class MesQcIpqcLineServiceImpl implements MesQcIpqcLineService {
ipqcLineMapper.deleteByIpqcId(ipqcId);
}
+ @Override
+ public Long getIpqcLineCountByUnitMeasureId(Long unitMeasureId) {
+ return ipqcLineMapper.selectCountByUnitMeasureId(unitMeasureId);
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java
index fb533397e..519964f2b 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java
@@ -3,12 +3,15 @@ package cn.iocoder.yudao.module.mes.service.qc.ipqc;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
@@ -23,9 +26,12 @@ import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationServic
import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
+import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
+import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
+import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -76,12 +82,21 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
@Resource
@Lazy
private MesProRouteProcessService routeProcessService;
-
@Resource
- private AdminUserApi adminUserApi;
+ @Lazy
+ private MesProTaskService taskService;
@Resource
@Lazy
private MesProFeedbackService feedbackService;
+ @Resource
+ @Lazy
+ private MesQcIndicatorResultService indicatorResultService;
+ @Resource
+ @Lazy
+ private MesWmProductProduceLineService productProduceLineService;
+
+ @Resource
+ private AdminUserApi adminUserApi;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -96,7 +111,7 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
Long templateId = templateItem.getTemplateId();
// 1.4 获取来源单据编号
String sourceDocCode = validateAndGetSourceDocCode(
- createReqVO.getSourceDocType(), createReqVO.getSourceDocId());
+ createReqVO.getSourceDocType(), createReqVO.getSourceDocId(), createReqVO.getSourceLineId());
// 2. 插入主表
MesQcIpqcDO ipqc = BeanUtils.toBean(createReqVO, MesQcIpqcDO.class)
@@ -136,9 +151,39 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
// 校验工位、检测人员存在
MesMdWorkstationDO workstation = workstationService.validateWorkstationExists(reqVO.getWorkstationId());
adminUserApi.validateUser(reqVO.getInspectorUserId());
+ // 校验工单存在
+ MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
+ if (reqVO.getItemId() != null && ObjUtil.notEqual(reqVO.getItemId(), workOrder.getProductId())) {
+ throw exception(PRO_WORK_ORDER_PRODUCT_MISMATCH);
+ }
+ if (reqVO.getProcessId() != null && ObjUtil.notEqual(workstation.getProcessId(), reqVO.getProcessId())) {
+ throw exception(PRO_WORKSTATION_PROCESS_MISMATCH);
+ }
+ // 校验任务关系(如果指定了任务)
+ if (reqVO.getTaskId() != null) {
+ MesProTaskDO task = taskService.validateTaskNotFinished(reqVO.getTaskId());
+ validateTaskRelation(task, workstation, workOrder, reqVO);
+ }
return workstation;
}
+ private void validateTaskRelation(MesProTaskDO task, MesMdWorkstationDO workstation,
+ MesProWorkOrderDO workOrder, MesQcIpqcSaveReqVO reqVO) {
+ if (ObjUtil.notEqual(task.getWorkOrderId(), workOrder.getId())) {
+ throw exception(PRO_TASK_WORK_ORDER_MISMATCH);
+ }
+ if (ObjUtil.notEqual(task.getWorkstationId(), workstation.getId())) {
+ throw exception(PRO_TASK_WORKSTATION_MISMATCH);
+ }
+ Long expectedProcessId = reqVO.getProcessId() != null ? reqVO.getProcessId() : workstation.getProcessId();
+ if (expectedProcessId != null && ObjUtil.notEqual(task.getProcessId(), expectedProcessId)) {
+ throw exception(PRO_TASK_ROUTE_PROCESS_MISMATCH);
+ }
+ if (ObjUtil.notEqual(task.getItemId(), workOrder.getProductId())) {
+ throw exception(PRO_TASK_ITEM_MISMATCH);
+ }
+ }
+
@Override
@Transactional(rollbackFor = Exception.class)
public void finishIpqc(Long id) {
@@ -148,6 +193,8 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
if (ipqc.getCheckResult() == null) {
throw exception(QC_IPQC_CHECK_RESULT_EMPTY);
}
+ // 1.3 校验至少存在一条检测结果
+ indicatorResultService.validateIndicatorResultExistsByQcIdAndType(id, MesQcTypeEnum.IPQC.getType());
// 2. 更新状态为已完成
ipqcMapper.updateById(new MesQcIpqcDO()
@@ -174,7 +221,7 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
}
if (Objects.equals(ipqc.getSourceDocType(), MesBizTypeConstants.PRO_FEEDBACK)) {
- feedbackService.updateProFeedbackWhenIpqcFinish(ipqc.getSourceDocId(),
+ feedbackService.updateProFeedbackWhenIpqcFinish(ipqc.getSourceDocId(), ipqc.getSourceLineId(),
ObjectUtil.defaultIfNull(ipqc.getQualifiedQuantity(), BigDecimal.ZERO),
ObjectUtil.defaultIfNull(ipqc.getUnqualifiedQuantity(), BigDecimal.ZERO),
ObjectUtil.defaultIfNull(ipqc.getLaborScrapQuantity(), BigDecimal.ZERO),
@@ -233,15 +280,30 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
}
}
- private String validateAndGetSourceDocCode(Integer sourceDocType, Long sourceDocId) {
+ private String validateAndGetSourceDocCode(Integer sourceDocType, Long sourceDocId, Long sourceLineId) {
if (sourceDocType == null || sourceDocId == null) {
return null;
}
if (Objects.equals(sourceDocType, MesBizTypeConstants.PRO_FEEDBACK)) {
- MesProFeedbackDO feedback = feedbackService.getFeedback(sourceDocId);
- return feedback != null ? feedback.getCode() : null;
+ MesProFeedbackDO feedback = feedbackService.validateFeedbackExists(sourceDocId);
+ // 校验 sourceLineId 必填
+ if (sourceLineId == null) {
+ throw exception(QC_IPQC_SOURCE_LINE_REQUIRED);
+ }
+ // 校验 sourceLineId 存在,且属于该报工的产出行
+ MesWmProductProduceLineDO targetLine = productProduceLineService
+ .validateProductProduceLineExists(sourceLineId);
+ if (ObjUtil.notEqual(targetLine.getFeedbackId(), sourceDocId)) {
+ throw exception(QC_IPQC_SOURCE_LINE_NOT_BELONG);
+ }
+ // 校验该产出行为待检验状态
+ if (ObjUtil.notEqual(targetLine.getQualityStatus(), MesWmQualityStatusEnum.PENDING.getStatus())) {
+ throw exception(QC_IPQC_SOURCE_LINE_NOT_PENDING);
+ }
+ return feedback.getCode();
}
- return null;
+ // 未知来源类型应报错,而不是静默忽略
+ throw exception(QC_IPQC_SOURCE_DOC_TYPE_UNKNOWN);
}
/**
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcLineService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcLineService.java
index 6f5263a90..965f0e059 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcLineService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcLineService.java
@@ -69,4 +69,12 @@ public interface MesQcIqcLineService {
*/
void deleteListByIqcId(Long iqcId);
+ /**
+ * 统计使用指定计量单位的来料检验行数量
+ *
+ * @param unitMeasureId 计量单位编号
+ * @return 引用数量
+ */
+ Long getIqcLineCountByUnitMeasureId(Long unitMeasureId);
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcLineServiceImpl.java
index 47ae97135..f5420e19d 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcLineServiceImpl.java
@@ -5,17 +5,17 @@ import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.line.MesQcIqcLinePageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
-import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcLineDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.iqc.MesQcIqcLineMapper;
-import cn.iocoder.yudao.module.mes.dal.mysql.qc.template.MesQcTemplateIndicatorMapper;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
+import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateIndicatorService;
+import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -37,11 +37,11 @@ public class MesQcIqcLineServiceImpl implements MesQcIqcLineService {
@Resource
private MesQcIqcLineMapper iqcLineMapper;
- @Resource
- private MesQcTemplateIndicatorMapper templateIndicatorMapper;
@Resource
private MesQcIndicatorService indicatorService;
+ @Resource
+ private MesQcTemplateIndicatorService templateIndicatorService;
@Override
public MesQcIqcLineDO validateIqcLineExists(Long id) {
@@ -64,7 +64,7 @@ public class MesQcIqcLineServiceImpl implements MesQcIqcLineService {
@Override
public void createLinesFromTemplate(Long iqcId, Long templateId) {
- List templateIndicators = templateIndicatorMapper.selectListByTemplateId(templateId);
+ List templateIndicators = templateIndicatorService.getTemplateIndicatorListByTemplateId(templateId);
if (CollUtil.isEmpty(templateIndicators)) {
return;
}
@@ -127,4 +127,9 @@ public class MesQcIqcLineServiceImpl implements MesQcIqcLineService {
iqcLineMapper.deleteByIqcId(iqcId);
}
+ @Override
+ public Long getIqcLineCountByUnitMeasureId(Long unitMeasureId) {
+ return iqcLineMapper.selectCountByUnitMeasureId(unitMeasureId);
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcService.java
index 6b7f9d1a8..3314f5dc5 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcService.java
@@ -87,4 +87,12 @@ public interface MesQcIqcService {
*/
Map getIqcMap(Collection ids);
+ /**
+ * 根据供应商 ID 统计来料检验单数量
+ *
+ * @param vendorId 供应商 ID
+ * @return 数量
+ */
+ Long getIqcCountByVendorId(Long vendorId);
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java
index c9b3f82a0..b6715ff50 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java
@@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.mes.service.qc.iqc;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
-import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.MesQcIqcPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.MesQcIqcSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
@@ -18,21 +18,24 @@ import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
+import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
import cn.iocoder.yudao.module.mes.service.wm.outsourcereceipt.MesWmOutsourceReceiptService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+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 javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
-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.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
@@ -61,6 +64,9 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
private MesMdVendorService vendorService;
@Resource
private MesMdItemService itemService;
+ @Resource
+ @Lazy
+ private MesQcIndicatorResultService indicatorResultService;
@Resource
private AdminUserApi adminUserApi;
@@ -124,6 +130,8 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
if (iqc.getCheckResult() == null) {
throw exception(QC_IQC_CHECK_RESULT_EMPTY);
}
+ // 1.2 校验至少存在一条检测结果
+ indicatorResultService.validateIndicatorResultExistsByQcIdAndType(id, MesQcTypeEnum.IQC.getType());
// 2. 更新状态为已完成
MesQcIqcDO updateObj = new MesQcIqcDO()
@@ -295,4 +303,9 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
return convertMap(list, MesQcIqcDO::getId);
}
+ @Override
+ public Long getIqcCountByVendorId(Long vendorId) {
+ return iqcMapper.selectCountByVendorId(vendorId);
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcLineService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcLineService.java
index 1f8fbb2b4..26de52409 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcLineService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcLineService.java
@@ -69,4 +69,12 @@ public interface MesQcOqcLineService {
*/
void recalculateLineDefectStats(Long oqcId, List records);
+ /**
+ * 统计使用指定计量单位的出货检验行数量
+ *
+ * @param unitMeasureId 计量单位编号
+ * @return 引用数量
+ */
+ Long getOqcLineCountByUnitMeasureId(Long unitMeasureId);
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcLineServiceImpl.java
index 4da4187ff..cc4135bed 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcLineServiceImpl.java
@@ -9,13 +9,13 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcLineMapper;
-import cn.iocoder.yudao.module.mes.dal.mysql.qc.template.MesQcTemplateIndicatorMapper;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
+import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateIndicatorService;
+import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -37,10 +37,11 @@ public class MesQcOqcLineServiceImpl implements MesQcOqcLineService {
@Resource
private MesQcOqcLineMapper oqcLineMapper;
- @Resource
- private MesQcTemplateIndicatorMapper templateIndicatorMapper;
+
@Resource
private MesQcIndicatorService indicatorService;
+ @Resource
+ private MesQcTemplateIndicatorService templateIndicatorService;
@Override
public MesQcOqcLineDO validateOqcLineExists(Long id) {
@@ -63,7 +64,7 @@ public class MesQcOqcLineServiceImpl implements MesQcOqcLineService {
@Override
public void createLinesFromTemplate(Long oqcId, Long templateId) {
- List templateIndicators = templateIndicatorMapper.selectListByTemplateId(templateId);
+ List templateIndicators = templateIndicatorService.getTemplateIndicatorListByTemplateId(templateId);
if (CollUtil.isEmpty(templateIndicators)) {
return;
}
@@ -124,4 +125,9 @@ public class MesQcOqcLineServiceImpl implements MesQcOqcLineService {
oqcLineMapper.deleteByOqcId(oqcId);
}
+ @Override
+ public Long getOqcLineCountByUnitMeasureId(Long unitMeasureId) {
+ return oqcLineMapper.selectCountByUnitMeasureId(unitMeasureId);
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java
index 45e2adbc4..0cf4eb6ee 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java
@@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.client.MesMdClientService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
+import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesLineService;
import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesService;
@@ -47,6 +48,7 @@ public class MesQcOqcServiceImpl implements MesQcOqcService {
@Resource
private MesQcOqcMapper oqcMapper;
+
@Resource
private MesQcTemplateItemService templateItemService;
@Resource
@@ -66,6 +68,9 @@ public class MesQcOqcServiceImpl implements MesQcOqcService {
@Resource
@Lazy
private MesWmProductSalesService productSalesService;
+ @Resource
+ @Lazy
+ private MesQcIndicatorResultService indicatorResultService;
@Resource
private AdminUserApi adminUserApi;
@@ -128,6 +133,8 @@ public class MesQcOqcServiceImpl implements MesQcOqcService {
if (oqc.getCheckResult() == null) {
throw exception(QC_OQC_CHECK_RESULT_EMPTY);
}
+ // 1.3 校验至少存在一条检测结果
+ indicatorResultService.validateIndicatorResultExistsByQcIdAndType(id, MesQcTypeEnum.OQC.getType());
// 2. 更新状态为已完成
MesQcOqcDO updateObj = new MesQcOqcDO()
@@ -221,13 +228,15 @@ public class MesQcOqcServiceImpl implements MesQcOqcService {
return null;
}
if (Objects.equals(sourceDocType, MesBizTypeConstants.WM_PRODUCT_SALES)) {
- MesWmProductSalesLineDO salesLine = productSalesLineService.getProductSalesLine(sourceLineId);
- if (salesLine != null && salesLine.getSalesId() != null) {
+ MesWmProductSalesLineDO salesLine = productSalesLineService.validateProductSalesLineExists(sourceLineId);
+ if (salesLine.getSalesId() != null) {
MesWmProductSalesDO sales = productSalesService.getProductSales(salesLine.getSalesId());
return sales != null ? sales.getCode() : null;
}
+ return null;
}
- return null;
+ // 未知来源类型应报错,而不是静默忽略
+ throw exception(QC_OQC_SOURCE_DOC_TYPE_UNKNOWN);
}
@Override
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcLineService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcLineService.java
index 299233580..64c504efd 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcLineService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcLineService.java
@@ -69,4 +69,12 @@ public interface MesQcRqcLineService {
*/
void deleteByRqcId(Long rqcId);
+ /**
+ * 统计使用指定计量单位的退货检验行数量
+ *
+ * @param unitMeasureId 计量单位编号
+ * @return 引用数量
+ */
+ Long getRqcLineCountByUnitMeasureId(Long unitMeasureId);
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcLineServiceImpl.java
index 7b5cba5dd..a14001d5f 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcLineServiceImpl.java
@@ -9,13 +9,13 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcLineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.rqc.MesQcRqcLineMapper;
-import cn.iocoder.yudao.module.mes.dal.mysql.qc.template.MesQcTemplateIndicatorMapper;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
+import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateIndicatorService;
+import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -37,11 +37,11 @@ public class MesQcRqcLineServiceImpl implements MesQcRqcLineService {
@Resource
private MesQcRqcLineMapper rqcLineMapper;
- @Resource
- private MesQcTemplateIndicatorMapper templateIndicatorMapper;
@Resource
private MesQcIndicatorService indicatorService;
+ @Resource
+ private MesQcTemplateIndicatorService templateIndicatorService;
@Override
public MesQcRqcLineDO validateRqcLineExists(Long id) {
@@ -64,7 +64,7 @@ public class MesQcRqcLineServiceImpl implements MesQcRqcLineService {
@Override
public void createLinesFromTemplate(Long rqcId, Long templateId) {
- List templateIndicators = templateIndicatorMapper.selectListByTemplateId(templateId);
+ List templateIndicators = templateIndicatorService.getTemplateIndicatorListByTemplateId(templateId);
if (CollUtil.isEmpty(templateIndicators)) {
return;
}
@@ -125,4 +125,9 @@ public class MesQcRqcLineServiceImpl implements MesQcRqcLineService {
rqcLineMapper.deleteByRqcId(rqcId);
}
+ @Override
+ public Long getRqcLineCountByUnitMeasureId(Long unitMeasureId) {
+ return rqcLineMapper.selectCountByUnitMeasureId(unitMeasureId);
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcServiceImpl.java
index 7e3bacf1b..0273c7529 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcServiceImpl.java
@@ -9,24 +9,25 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRec
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.rqc.MesQcRqcMapper;
-import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
-import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
+import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
+import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
+import cn.iocoder.yudao.module.mes.service.wm.returnissue.MesWmReturnIssueService;
+import cn.iocoder.yudao.module.mes.service.wm.returnsales.MesWmReturnSalesService;
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
import cn.iocoder.yudao.module.mes.service.wm.returnissue.MesWmReturnIssueLineService;
-import cn.iocoder.yudao.module.mes.service.wm.returnissue.MesWmReturnIssueService;
import cn.iocoder.yudao.module.mes.service.wm.returnsales.MesWmReturnSalesLineService;
-import cn.iocoder.yudao.module.mes.service.wm.returnsales.MesWmReturnSalesService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+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 javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
@@ -46,6 +47,7 @@ public class MesQcRqcServiceImpl implements MesQcRqcService {
@Resource
private MesQcRqcMapper rqcMapper;
+
@Resource
private MesQcTemplateItemService templateItemService;
@Resource
@@ -68,6 +70,9 @@ public class MesQcRqcServiceImpl implements MesQcRqcService {
@Resource
@Lazy
private MesWmReturnSalesService returnSalesService;
+ @Resource
+ @Lazy
+ private MesQcIndicatorResultService indicatorResultService;
@Resource
private AdminUserApi adminUserApi;
@@ -137,6 +142,8 @@ public class MesQcRqcServiceImpl implements MesQcRqcService {
throw exception(QC_RQC_QUANTITY_MISMATCH);
}
}
+ // 1.4 校验至少存在一条检测结果
+ indicatorResultService.validateIndicatorResultExistsByQcIdAndType(id, MesQcTypeEnum.RQC.getType());
// 2. 更新状态为已完成
MesQcRqcDO updateObj = new MesQcRqcDO().setId(id).setStatus(MesQcStatusEnum.FINISHED.getStatus());
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateIndicatorService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateIndicatorService.java
index 08ddf5006..df4f989da 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateIndicatorService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateIndicatorService.java
@@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateIndic
import javax.validation.Valid;
+import java.util.List;
+
/**
* MES 质检方案-检测指标项 Service 接口
*
@@ -59,4 +61,20 @@ public interface MesQcTemplateIndicatorService {
*/
void deleteTemplateIndicatorByTemplateId(Long templateId);
+ /**
+ * 根据方案编号,获得检测指标项列表
+ *
+ * @param templateId 质检方案编号
+ * @return 检测指标项列表
+ */
+ List getTemplateIndicatorListByTemplateId(Long templateId);
+
+ /**
+ * 获取指定计量单位的检测指标项数量
+ *
+ * @param unitMeasureId 计量单位编号
+ * @return 检测指标项数量
+ */
+ Long getTemplateIndicatorCountByUnitMeasureId(Long unitMeasureId);
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateIndicatorServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateIndicatorServiceImpl.java
index 51a3f195c..667226f06 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateIndicatorServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateIndicatorServiceImpl.java
@@ -6,11 +6,12 @@ import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.indicator.Mes
import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.indicator.MesQcTemplateIndicatorSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateIndicatorDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.template.MesQcTemplateIndicatorMapper;
+import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
+import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.QC_TEMPLATE_INDICATOR_NOT_EXISTS;
@@ -81,4 +82,14 @@ public class MesQcTemplateIndicatorServiceImpl implements MesQcTemplateIndicator
templateIndicatorMapper.deleteByTemplateId(templateId);
}
+ @Override
+ public List getTemplateIndicatorListByTemplateId(Long templateId) {
+ return templateIndicatorMapper.selectListByTemplateId(templateId);
+ }
+
+ @Override
+ public Long getTemplateIndicatorCountByUnitMeasureId(Long unitMeasureId) {
+ return templateIndicatorMapper.selectCountByUnitMeasureId(unitMeasureId);
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/tm/tool/MesTmToolService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/tm/tool/MesTmToolService.java
index daf3b9e58..a1381812c 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/tm/tool/MesTmToolService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/tm/tool/MesTmToolService.java
@@ -64,6 +64,14 @@ public interface MesTmToolService {
*/
List getToolList();
+ /**
+ * 获得指定工具类型下的工具数量
+ *
+ * @param toolTypeId 工具类型编号
+ * @return 工具数量
+ */
+ Long getToolCountByToolTypeId(Long toolTypeId);
+
/**
* 获得工具列表
*
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/tm/tool/MesTmToolServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/tm/tool/MesTmToolServiceImpl.java
index 34c7b65b0..21bfc6c66 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/tm/tool/MesTmToolServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/tm/tool/MesTmToolServiceImpl.java
@@ -10,19 +10,19 @@ import cn.iocoder.yudao.module.mes.dal.mysql.tm.tool.MesTmToolMapper;
import cn.iocoder.yudao.module.mes.enums.tm.MesTmMaintenTypeEnum;
import cn.iocoder.yudao.module.mes.enums.wm.BarcodeBizTypeEnum;
import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
+import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.TM_TOOL_CODE_DUPLICATE;
-import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.TM_TOOL_NOT_EXISTS;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
* MES 工具台账 Service 实现类
@@ -39,9 +39,11 @@ public class MesTmToolServiceImpl implements MesTmToolService {
@Resource
@Lazy // 延迟加载,避免循环依赖
private MesTmToolTypeService toolTypeService;
-
@Resource
private MesWmBarcodeService barcodeService;
+ @Resource
+ @Lazy
+ private MesWmBatchService batchService;
@Override
public Long createTool(MesTmToolSaveReqVO createReqVO) {
@@ -87,6 +89,11 @@ public class MesTmToolServiceImpl implements MesTmToolService {
public void deleteTool(Long id) {
// 校验存在
validateToolExists(id);
+ // 校验是否被批次引用
+ if (batchService.getBatchCountByToolId(id) > 0) {
+ throw exception(TM_TOOL_HAS_BATCH);
+ }
+
// 删除
toolMapper.deleteById(id);
}
@@ -120,6 +127,11 @@ public class MesTmToolServiceImpl implements MesTmToolService {
return toolMapper.selectPage(pageReqVO);
}
+ @Override
+ public Long getToolCountByToolTypeId(Long toolTypeId) {
+ return toolMapper.selectCountByToolTypeId(toolTypeId);
+ }
+
@Override
public List getToolList() {
return toolMapper.selectList();
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/tm/tool/MesTmToolTypeServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/tm/tool/MesTmToolTypeServiceImpl.java
index 9a7cc092b..4737ddca3 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/tm/tool/MesTmToolTypeServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/tm/tool/MesTmToolTypeServiceImpl.java
@@ -1,18 +1,20 @@
package cn.iocoder.yudao.module.mes.service.tm.tool;
import cn.hutool.core.collection.CollUtil;
-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.mes.controller.admin.tm.tool.vo.type.MesTmToolTypePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.tm.tool.vo.type.MesTmToolTypeSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.tm.tool.MesTmToolTypeDO;
-import cn.iocoder.yudao.module.mes.dal.mysql.tm.tool.MesTmToolMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.tm.tool.MesTmToolTypeMapper;
+import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationToolService;
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
+import cn.hutool.core.util.ObjUtil;
+
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -33,7 +35,11 @@ public class MesTmToolTypeServiceImpl implements MesTmToolTypeService {
private MesTmToolTypeMapper toolTypeMapper;
@Resource
- private MesTmToolMapper toolMapper;
+ @Lazy
+ private MesTmToolService toolService;
+ @Resource
+ @Lazy
+ private MesMdWorkstationToolService workstationToolService;
@Override
public Long createToolType(MesTmToolTypeSaveReqVO createReqVO) {
@@ -70,9 +76,13 @@ public class MesTmToolTypeServiceImpl implements MesTmToolTypeService {
// 校验存在
validateToolTypeExists(id);
// 校验是否被工具引用
- if (toolMapper.selectCountByToolTypeId(id) > 0) {
+ if (toolService.getToolCountByToolTypeId(id) > 0) {
throw exception(TM_TOOL_TYPE_HAS_TOOL);
}
+ // 校验是否被工作站工装资源引用
+ if (workstationToolService.getWorkstationToolCountByToolTypeId(id) > 0) {
+ throw exception(TM_TOOL_TYPE_HAS_WORKSTATION_TOOL);
+ }
// 删除
toolTypeMapper.deleteById(id);
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java
index d37c76161..e54ac8120 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java
@@ -66,7 +66,7 @@ public class MesWmArrivalNoticeLineServiceImpl implements MesWmArrivalNoticeLine
// 校验父单据存在且为草稿状态
arrivalNoticeService.validateArrivalNoticeExistsAndDraft(reqVO.getNoticeId());
// 校验物料存在
- itemService.validateItemExists(reqVO.getItemId());
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
}
/**
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeService.java
index 30add613d..6d27c55fd 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeService.java
@@ -117,12 +117,13 @@ public interface MesWmArrivalNoticeService {
void validateArrivalNoticeAndLineExists(Long noticeId, Long lineId);
/**
- * 按状态获得到货通知单列表
+ * 校验到货通知单已就绪可被采购入库引用
+ * 校验状态为待入库 + 所有需检行都已完成 IQC
*
- * @param status 状态
- * @return 到货通知单列表
+ * @param id 到货通知单编号
+ * @return 到货通知单
*/
- List getArrivalNoticeListByStatus(Integer status);
+ MesWmArrivalNoticeDO validateArrivalNoticeReadyForReceipt(Long id);
/**
* 查询指定供应商的到货通知单数量
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
index c9a675fff..1be4914ce 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
@@ -38,11 +38,13 @@ public class MesWmArrivalNoticeServiceImpl implements MesWmArrivalNoticeService
@Resource
private MesWmArrivalNoticeLineService arrivalNoticeLineService;
+ @Resource
+ private cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService vendorService;
@Override
public Long createArrivalNotice(MesWmArrivalNoticeSaveReqVO createReqVO) {
- // 校验编码唯一
- validateCodeUnique(null, createReqVO.getCode());
+ // 校验数据
+ validateArrivalNoticeSaveData(createReqVO);
// 插入
MesWmArrivalNoticeDO notice = BeanUtils.toBean(createReqVO, MesWmArrivalNoticeDO.class);
@@ -55,14 +57,21 @@ public class MesWmArrivalNoticeServiceImpl implements MesWmArrivalNoticeService
public void updateArrivalNotice(MesWmArrivalNoticeSaveReqVO updateReqVO) {
// 校验存在 + 草稿状态
validateArrivalNoticeExistsAndDraft(updateReqVO.getId());
- // 校验编码唯一
- validateCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
+ // 校验数据
+ validateArrivalNoticeSaveData(updateReqVO);
// 更新
MesWmArrivalNoticeDO updateObj = BeanUtils.toBean(updateReqVO, MesWmArrivalNoticeDO.class);
arrivalNoticeMapper.updateById(updateObj);
}
+ private void validateArrivalNoticeSaveData(MesWmArrivalNoticeSaveReqVO reqVO) {
+ // 校验编码唯一
+ validateCodeUnique(reqVO.getId(), reqVO.getCode());
+ // 校验供应商存在且启用
+ vendorService.validateVendorExistsAndEnable(reqVO.getVendorId());
+ }
+
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteArrivalNotice(Long id) {
@@ -144,6 +153,13 @@ public class MesWmArrivalNoticeServiceImpl implements MesWmArrivalNoticeService
if (ObjUtil.notEqual(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus(), notice.getStatus())) {
throw exception(WM_ARRIVAL_NOTICE_STATUS_NOT_PENDING_RECEIPT);
}
+ // 行级防御校验:确保所有需检行都已完成 IQC
+ List lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(id);
+ boolean hasUnchecked = CollectionUtils.anyMatch(lines,
+ line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcId() == null);
+ if (hasUnchecked) {
+ throw exception(WM_ARRIVAL_NOTICE_IQC_PENDING);
+ }
// 完成
arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO()
@@ -158,14 +174,6 @@ public class MesWmArrivalNoticeServiceImpl implements MesWmArrivalNoticeService
return arrivalNoticeMapper.selectByIds(ids);
}
- @Override
- public List getArrivalNoticeListByStatus(Integer status) {
- if (status == null) {
- return arrivalNoticeMapper.selectList();
- }
- return arrivalNoticeMapper.selectListByStatus(status);
- }
-
@Override
public void validateArrivalNoticeAndLineExists(Long noticeId, Long lineId) {
// 1. 校验通知单存在
@@ -202,6 +210,23 @@ public class MesWmArrivalNoticeServiceImpl implements MesWmArrivalNoticeService
return notice;
}
+ @Override
+ public MesWmArrivalNoticeDO validateArrivalNoticeReadyForReceipt(Long id) {
+ // 1. 校验到货通知单存在且状态为待入库
+ MesWmArrivalNoticeDO notice = validateArrivalNoticeExists(id);
+ if (ObjUtil.notEqual(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus(), notice.getStatus())) {
+ throw exception(WM_ARRIVAL_NOTICE_STATUS_NOT_PENDING_RECEIPT);
+ }
+ // 2. 行级防御校验:确保所有需检行都已完成 IQC
+ List lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(id);
+ boolean hasUnchecked = CollectionUtils.anyMatch(lines,
+ line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcId() == null);
+ if (hasUnchecked) {
+ throw exception(WM_ARRIVAL_NOTICE_IQC_PENDING);
+ }
+ return notice;
+ }
+
private void validateCodeUnique(Long id, String code) {
MesWmArrivalNoticeDO notice = arrivalNoticeMapper.selectByCode(code);
if (notice == null) {
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java
index 2fafc2568..258bb9c3e 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java
@@ -69,10 +69,31 @@ public interface MesWmBatchService {
List getBackwardBatchList(String code);
/**
- * 获取批次列表
+ * 校验批次存在,并校验批次与物料的归属关系
*
- * @return 批次列表
+ * @param batchId 批次ID
+ * @param itemId 物料ID
+ * @return 批次记录
*/
- List getBatchList();
+ MesWmBatchDO validateBatchExists(Long batchId, Long itemId);
+
+ /**
+ * 校验批次存在,并校验批次与物料、客户/供应商的归属关系
+ *
+ * @param batchId 批次ID
+ * @param itemId 物料ID
+ * @param clientId 客户ID(可选,不为空时校验)
+ * @param vendorId 供应商ID(可选,不为空时校验)
+ * @return 批次记录
+ */
+ MesWmBatchDO validateBatchExists(Long batchId, Long itemId, Long clientId, Long vendorId);
+
+ /**
+ * 获取指定工具的批次数量
+ *
+ * @param toolId 工具编号
+ * @return 批次数量
+ */
+ Long getBatchCountByToolId(Long toolId);
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
index dd22f5bf0..bddd33a32 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
@@ -39,8 +39,14 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@Slf4j
public class MesWmBatchServiceImpl implements MesWmBatchService {
+ /**
+ * 批次追溯最大递归深度,防止极端场景下性能问题
+ */
+ private static final int MAX_TRACE_DEPTH = 20;
+
@Resource
private MesWmBatchMapper batchMapper;
+
@Resource
private MesMdItemService itemService;
@Resource
@@ -196,11 +202,11 @@ public class MesWmBatchServiceImpl implements MesWmBatchService {
@Override
public List getForwardBatchList(String code) {
- return getForwardBatchList(code, new HashSet<>());
+ return getForwardBatchList(code, new HashSet<>(), 0);
}
- private List getForwardBatchList(String code, Set visited) {
- if (code == null || !visited.add(code)) {
+ private List getForwardBatchList(String code, Set visited, int depth) {
+ if (code == null || !visited.add(code) || depth >= MAX_TRACE_DEPTH) {
return new ArrayList<>();
}
List list = batchMapper.selectListByForward(code);
@@ -210,18 +216,18 @@ public class MesWmBatchServiceImpl implements MesWmBatchService {
// 继续递归查询下游批次
List results = new ArrayList<>(list);
for (MesWmBatchDO batch : list) {
- results.addAll(getForwardBatchList(batch.getCode(), visited));
+ results.addAll(getForwardBatchList(batch.getCode(), visited, depth + 1));
}
return results;
}
@Override
public List getBackwardBatchList(String code) {
- return getBackwardBatchList(code, new HashSet<>());
+ return getBackwardBatchList(code, new HashSet<>(), 0);
}
- private List getBackwardBatchList(String code, Set visited) {
- if (code == null || !visited.add(code)) {
+ private List getBackwardBatchList(String code, Set visited, int depth) {
+ if (code == null || !visited.add(code) || depth >= MAX_TRACE_DEPTH) {
return new ArrayList<>();
}
List list = batchMapper.selectListByBackward(code);
@@ -231,14 +237,38 @@ public class MesWmBatchServiceImpl implements MesWmBatchService {
// 继续递归查询上游批次
List results = new ArrayList<>(list);
for (MesWmBatchDO batch : list) {
- results.addAll(getBackwardBatchList(batch.getCode(), visited));
+ results.addAll(getBackwardBatchList(batch.getCode(), visited, depth + 1));
}
return results;
}
@Override
- public List getBatchList() {
- return batchMapper.selectList();
+ public MesWmBatchDO validateBatchExists(Long batchId, Long itemId) {
+ MesWmBatchDO batch = batchMapper.selectById(batchId);
+ if (batch == null) {
+ throw exception(WM_BATCH_NOT_EXISTS);
+ }
+ if (ObjUtil.notEqual(batch.getItemId(), itemId)) {
+ throw exception(WM_BATCH_ITEM_MISMATCH);
+ }
+ return batch;
+ }
+
+ @Override
+ public MesWmBatchDO validateBatchExists(Long batchId, Long itemId, Long clientId, Long vendorId) {
+ MesWmBatchDO batch = validateBatchExists(batchId, itemId);
+ if (clientId != null && ObjUtil.notEqual(batch.getClientId(), clientId)) {
+ throw exception(WM_BATCH_CLIENT_MISMATCH);
+ }
+ if (vendorId != null && ObjUtil.notEqual(batch.getVendorId(), vendorId)) {
+ throw exception(WM_BATCH_VENDOR_MISMATCH);
+ }
+ return batch;
+ }
+
+ @Override
+ public Long getBatchCountByToolId(Long toolId) {
+ return batchMapper.selectCountByToolId(toolId);
}
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptService.java
index dd4dcc43c..eecab63f4 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptService.java
@@ -4,9 +4,12 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDO;
-
-import javax.validation.Valid;
+import jakarta.validation.Valid;
+import java.util.Collection;
import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* MES 采购入库单 Service 接口
@@ -103,4 +106,22 @@ public interface MesWmItemReceiptService {
*/
List getItemReceiptListByVendorId(Long vendorId);
+ /**
+ * 批量获得采购入库单列表
+ *
+ * @param ids 编号数组
+ * @return 入库单列表
+ */
+ List getItemReceiptList(Collection ids);
+
+ /**
+ * 批量获得采购入库单 Map
+ *
+ * @param ids 编号数组
+ * @return 入库单 Map
+ */
+ default Map getItemReceiptMap(Collection ids) {
+ return convertMap(getItemReceiptList(ids), MesWmItemReceiptDO::getId);
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java
index 2b5fe076d..8c8cbadef 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptLineDO;
@@ -27,6 +28,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.math.BigDecimal;
+import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -55,7 +57,6 @@ public class MesWmItemReceiptServiceImpl implements MesWmItemReceiptService {
@Resource
@Lazy
private MesQcIqcService iqcService;
-
@Resource
private MesWmTransactionService wmTransactionService;
@@ -87,10 +88,13 @@ public class MesWmItemReceiptServiceImpl implements MesWmItemReceiptService {
// 校验编码唯一
validateCodeUnique(reqVO.getId(), reqVO.getCode());
// 校验供应商存在
- vendorService.validateVendorExists(reqVO.getVendorId());
+ vendorService.validateVendorExistsAndEnable(reqVO.getVendorId());
// 校验到货通知单存在
if (reqVO.getNoticeId() != null) {
- arrivalNoticeService.validateArrivalNoticeExists(reqVO.getNoticeId());
+ MesWmArrivalNoticeDO notice = arrivalNoticeService.validateArrivalNoticeReadyForReceipt(reqVO.getNoticeId());
+ if (ObjUtil.notEqual(notice.getVendorId(), reqVO.getVendorId())) {
+ throw exception(WM_ARRIVAL_NOTICE_VENDOR_MISMATCH);
+ }
}
// 校验来料检验单存在
if (reqVO.getIqcId() != null) {
@@ -263,4 +267,9 @@ public class MesWmItemReceiptServiceImpl implements MesWmItemReceiptService {
return itemReceiptMapper.selectListByVendorId(vendorId);
}
+ @Override
+ public List getItemReceiptList(Collection ids) {
+ return itemReceiptMapper.selectByIds(ids);
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
index 4af8e2428..ab24be058 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
@@ -132,4 +132,20 @@ public interface MesWmMaterialStockService {
*/
void checkAreaMixingRule(Long areaId, Long itemId, Long batchId);
+ /**
+ * 校验前端选择的库存记录,兜底避免串单或越权提交
+ *
+ * @param materialStockId 库存记录编号
+ * @param itemId 物料编号
+ * @param batchId 批次编号
+ * @param batchCode 批次号(可选)
+ * @param warehouseId 仓库编号
+ * @param locationId 库区编号
+ * @param areaId 库位编号
+ * @param quantity 数量(可选,校验库存充足)
+ * @return 校验通过的库存记录
+ */
+ MesWmMaterialStockDO validateSelectedStock(Long materialStockId, Long itemId, Long batchId, String batchCode,
+ Long warehouseId, Long locationId, Long areaId, BigDecimal quantity);
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
index 651eef175..50405b650 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.mes.service.wm.materialstock;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
@@ -11,14 +12,17 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
@@ -43,6 +47,9 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService
private MesMdItemTypeService itemTypeService;
@Resource
private MesWmWarehouseAreaService areaService;
+ @Resource
+ @Lazy
+ private MesWmWarehouseService warehouseService;
@Override
public MesWmMaterialStockDO getMaterialStock(Long id) {
@@ -77,9 +84,19 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService
if (pageReqVO.getItemId() != null) {
itemIds = SetUtils.asSet(pageReqVO.getItemId());
}
+ // 1.3 解析 virtualFilter:转换为虚拟仓 warehouseId
+ Long virtualWarehouseId = null;
+ String virtualFilter = pageReqVO.getVirtualFilter();
+ if (MesWmMaterialStockPageReqVO.VIRTUAL_FILTER_EXCLUDE.equals(virtualFilter)
+ || MesWmMaterialStockPageReqVO.VIRTUAL_FILTER_ONLY.equals(virtualFilter)) {
+ MesWmWarehouseDO virtualWarehouse = warehouseService.getWarehouseByCode(
+ MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE);
+ Assert.notNull(virtualWarehouse, "虚拟仓库(WIP_VIRTUAL_WAREHOUSE)不存在");
+ virtualWarehouseId = virtualWarehouse.getId();
+ }
// 2. 分页查询
- return materialStockMapper.selectPage(pageReqVO, itemTypeIds, itemIds);
+ return materialStockMapper.selectPage(pageReqVO, itemTypeIds, itemIds, virtualWarehouseId);
}
@Override
@@ -185,4 +202,34 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService
}
}
+ @Override
+ public MesWmMaterialStockDO validateSelectedStock(Long materialStockId,
+ Long itemId,
+ Long batchId,
+ String batchCode,
+ Long warehouseId,
+ Long locationId,
+ Long areaId,
+ BigDecimal quantity) {
+ // 1. 校验库存记录必须存在
+ if (materialStockId == null) {
+ throw exception(WM_MATERIAL_STOCK_REQUIRED);
+ }
+ MesWmMaterialStockDO stock = validateMaterialStockExists(materialStockId);
+ // 2. 校验库存记录的物料、批次、仓库、库区、库位等信息与前端选择的一致,避免串单或越权提交
+ if (ObjUtil.notEqual(stock.getItemId(), itemId)
+ || ObjUtil.notEqual(stock.getBatchId(), batchId)
+ || (batchCode != null && ObjUtil.notEqual(stock.getBatchCode(), batchCode))
+ || ObjUtil.notEqual(stock.getWarehouseId(), warehouseId)
+ || ObjUtil.notEqual(stock.getLocationId(), locationId)
+ || ObjUtil.notEqual(stock.getAreaId(), areaId)) {
+ throw exception(WM_MATERIAL_STOCK_SELECTION_MISMATCH);
+ }
+ // 3. 校验库存数量充足(如果前端传了 quantity,则必须保证库存数量 >= quantity)
+ if (quantity != null && stock.getQuantity() != null && stock.getQuantity().compareTo(quantity) < 0) {
+ throw exception(WM_MATERIAL_STOCK_INSUFFICIENT);
+ }
+ return stock;
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueLineServiceImpl.java
index 80abd1b56..005c95a86 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueLineServiceImpl.java
@@ -32,14 +32,11 @@ public class MesWmMiscIssueLineServiceImpl implements MesWmMiscIssueLineService
@Resource
@Lazy
private MesWmMiscIssueService miscIssueService;
-
@Resource
@Lazy
private MesWmMiscIssueDetailService miscIssueDetailService;
-
@Resource
private MesMdItemService itemService;
-
@Resource
private MesWmWarehouseAreaService warehouseAreaService;
@@ -132,7 +129,7 @@ public class MesWmMiscIssueLineServiceImpl implements MesWmMiscIssueLineService
// 校验父单据存在且为可编辑状态
miscIssueService.validateMiscIssueEditable(issueId);
// 校验物料存在
- itemService.validateItemExists(reqVO.getItemId());
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
// 校验仓库、库区、库位的父子关系
warehouseAreaService.validateWarehouseAreaExists(reqVO.getWarehouseId(),
reqVO.getLocationId(), reqVO.getAreaId());
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueServiceImpl.java
index 621df346a..72fe5425b 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscissue/MesWmMiscIssueServiceImpl.java
@@ -132,6 +132,7 @@ public class MesWmMiscIssueServiceImpl implements MesWmMiscIssueService {
wmTransactionService.createTransactionList(convertList(lines, line -> new MesWmTransactionSaveReqDTO()
.setType(MesWmTransactionTypeEnum.OUT.getType()).setItemId(line.getItemId())
.setQuantity(line.getQuantity().negate()) // 出库数量为负数
+ .setBatchId(line.getBatchId()).setBatchCode(line.getBatchCode())
.setWarehouseId(line.getWarehouseId()).setLocationId(line.getLocationId()).setAreaId(line.getAreaId())
.setBizType(MesBizTypeConstants.WM_MISC_ISSUE).setBizId(issue.getId()).setBizCode(issue.getCode()).setBizLineId(line.getId())));
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptLineServiceImpl.java
index 2cccbcd14..d4dff91a6 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/miscreceipt/MesWmMiscReceiptLineServiceImpl.java
@@ -125,7 +125,7 @@ public class MesWmMiscReceiptLineServiceImpl implements MesWmMiscReceiptLineServ
// 校验父单据存在且为可编辑状态
miscReceiptService.validateMiscReceiptEditable(reqVO.getReceiptId());
// 校验物料存在
- itemService.validateItemExists(reqVO.getItemId());
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
// 校验仓库层级关系(仓库 - 库位 - 库区)
warehouseAreaService.validateWarehouseAreaExists(reqVO.getWarehouseId(),
reqVO.getLocationId(), reqVO.getAreaId());
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueDetailServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueDetailServiceImpl.java
index 0ebd5c2d8..9a149367f 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueDetailServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueDetailServiceImpl.java
@@ -1,19 +1,23 @@
package cn.iocoder.yudao.module.mes.service.wm.outsourceissue;
+import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.outsourceissue.vo.detail.MesWmOutsourceIssueDetailSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourceissue.MesWmOutsourceIssueDetailDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourceissue.MesWmOutsourceIssueLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.outsourceissue.MesWmOutsourceIssueDetailMapper;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
+import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_OUTSOURCE_ISSUE_DETAIL_NOT_EXISTS;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
* MES 外协发料单明细 Service 实现类
@@ -26,11 +30,16 @@ public class MesWmOutsourceIssueDetailServiceImpl implements MesWmOutsourceIssue
@Resource
private MesWmOutsourceIssueDetailMapper outsourceIssueDetailMapper;
+
@Resource
@Lazy
- private MesWmOutsourceIssueService outsourceIssueService;
+ private MesWmOutsourceIssueLineService outsourceIssueLineService;
@Resource
private MesMdItemService itemService;
+ @Resource
+ private MesWmWarehouseAreaService warehouseAreaService;
+ @Resource
+ private MesWmMaterialStockService materialStockService;
@Override
public Long createOutsourceIssueDetail(MesWmOutsourceIssueDetailSaveReqVO createReqVO) {
@@ -95,10 +104,26 @@ public class MesWmOutsourceIssueDetailServiceImpl implements MesWmOutsourceIssue
}
private void validateOutsourceIssueDetailSaveData(MesWmOutsourceIssueDetailSaveReqVO saveReqVO) {
- // 校验关联的发料单存在
- outsourceIssueService.getOutsourceIssue(saveReqVO.getIssueId());
- // 校验关联的物料存在
- itemService.validateItemExists(saveReqVO.getItemId());
+ // 校验父数据(行)存在
+ MesWmOutsourceIssueLineDO line = outsourceIssueLineService.getOutsourceIssueLine(saveReqVO.getLineId());
+ if (line == null) {
+ throw exception(WM_OUTSOURCE_ISSUE_LINE_NOT_EXISTS);
+ }
+ if (ObjUtil.notEqual(line.getIssueId(), saveReqVO.getIssueId())) {
+ throw exception(WM_OUTSOURCE_ISSUE_DETAIL_LINE_NOT_MATCH);
+ }
+ // 校验物料存在
+ itemService.validateItemExistsAndEnable(saveReqVO.getItemId());
+ if (ObjUtil.notEqual(line.getItemId(), saveReqVO.getItemId())) {
+ throw exception(WM_OUTSOURCE_ISSUE_DETAIL_ITEM_MISMATCH);
+ }
+ // 校验仓库、库区、库位的关联关系
+ warehouseAreaService.validateWarehouseAreaExists(
+ saveReqVO.getWarehouseId(), saveReqVO.getLocationId(), saveReqVO.getAreaId());
+ // 校验库存记录存在且物料一致
+ materialStockService.validateSelectedStock(
+ saveReqVO.getMaterialStockId(), saveReqVO.getItemId(), saveReqVO.getBatchId(), null,
+ saveReqVO.getWarehouseId(), saveReqVO.getLocationId(), saveReqVO.getAreaId(), saveReqVO.getQuantity());
}
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueLineServiceImpl.java
index dbbe5528f..edef2a292 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueLineServiceImpl.java
@@ -121,7 +121,7 @@ public class MesWmOutsourceIssueLineServiceImpl implements MesWmOutsourceIssueLi
throw exception(WM_OUTSOURCE_ISSUE_NOT_EXISTS);
}
// 校验关联的物料存在
- itemService.validateItemExists(saveReqVO.getItemId());
+ itemService.validateItemExistsAndEnable(saveReqVO.getItemId());
// 校验物料是否在工单 BOM 中
validateItemInWorkOrderBom(issue.getWorkOrderId(), saveReqVO.getItemId());
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueService.java
index ec5c4b642..538d46cbd 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueService.java
@@ -88,4 +88,12 @@ public interface MesWmOutsourceIssueService {
*/
Boolean checkOutsourceIssueQuantity(Long id);
+ /**
+ * 根据供应商 ID 统计外协发料单数量
+ *
+ * @param vendorId 供应商 ID
+ * @return 数量
+ */
+ Long getOutsourceIssueCountByVendorId(Long vendorId);
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueServiceImpl.java
index 6a476a31e..b308efc93 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourceissue/MesWmOutsourceIssueServiceImpl.java
@@ -258,10 +258,10 @@ public class MesWmOutsourceIssueServiceImpl implements MesWmOutsourceIssueServic
validateCodeUnique(saveReqVO.getId(), saveReqVO.getCode());
// 校验供应商存在
if (saveReqVO.getVendorId() != null) {
- vendorService.validateVendorExists(saveReqVO.getVendorId());
+ vendorService.validateVendorExistsAndEnable(saveReqVO.getVendorId());
}
// 校验工单存在且类型为外协(代工)
- MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderExists(saveReqVO.getWorkOrderId());
+ MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(saveReqVO.getWorkOrderId());
if (ObjUtil.notEqual(workOrder.getType(), MesProWorkOrderTypeEnum.OUTSOURCE.getType())) {
throw exception(WM_OUTSOURCE_ISSUE_WORK_ORDER_TYPE_INVALID);
}
@@ -280,4 +280,9 @@ public class MesWmOutsourceIssueServiceImpl implements MesWmOutsourceIssueServic
}
}
+ @Override
+ public Long getOutsourceIssueCountByVendorId(Long vendorId) {
+ return outsourceIssueMapper.selectCountByVendorId(vendorId);
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptDetailServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptDetailServiceImpl.java
index a41b2570d..e59639bdd 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptDetailServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptDetailServiceImpl.java
@@ -6,12 +6,10 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.outsourcereceipt.MesWmOutso
import cn.iocoder.yudao.module.mes.dal.mysql.wm.outsourcereceipt.MesWmOutsourceReceiptDetailMapper;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
-import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
-import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
+import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -29,30 +27,13 @@ public class MesWmOutsourceReceiptDetailServiceImpl implements MesWmOutsourceRec
@Resource
private MesMdItemService itemService;
-
- @Resource
- private MesWmWarehouseService warehouseService;
-
- @Resource
- private MesWmWarehouseLocationService locationService;
-
@Resource
private MesWmWarehouseAreaService areaService;
@Override
public Long createOutsourceReceiptDetail(MesWmOutsourceReceiptDetailSaveReqVO createReqVO) {
- // 校验物料存在
- itemService.validateItemExists(createReqVO.getItemId());
- // 校验仓库、库区、库位存在
- if (createReqVO.getWarehouseId() != null) {
- warehouseService.validateWarehouseExists(createReqVO.getWarehouseId());
- }
- if (createReqVO.getLocationId() != null) {
- locationService.validateWarehouseLocationExists(createReqVO.getLocationId());
- }
- if (createReqVO.getAreaId() != null) {
- areaService.validateWarehouseAreaExists(createReqVO.getAreaId());
- }
+ // 校验数据
+ validateDetailSaveData(createReqVO);
// 插入
MesWmOutsourceReceiptDetailDO detail = BeanUtils.toBean(createReqVO, MesWmOutsourceReceiptDetailDO.class);
@@ -64,18 +45,8 @@ public class MesWmOutsourceReceiptDetailServiceImpl implements MesWmOutsourceRec
public void updateOutsourceReceiptDetail(MesWmOutsourceReceiptDetailSaveReqVO updateReqVO) {
// 校验存在
validateOutsourceReceiptDetailExists(updateReqVO.getId());
- // 校验物料存在
- itemService.validateItemExists(updateReqVO.getItemId());
- // 校验仓库、库区、库位存在
- if (updateReqVO.getWarehouseId() != null) {
- warehouseService.validateWarehouseExists(updateReqVO.getWarehouseId());
- }
- if (updateReqVO.getLocationId() != null) {
- locationService.validateWarehouseLocationExists(updateReqVO.getLocationId());
- }
- if (updateReqVO.getAreaId() != null) {
- areaService.validateWarehouseAreaExists(updateReqVO.getAreaId());
- }
+ // 校验数据
+ validateDetailSaveData(updateReqVO);
// 更新
MesWmOutsourceReceiptDetailDO updateObj = BeanUtils.toBean(updateReqVO, MesWmOutsourceReceiptDetailDO.class);
@@ -115,6 +86,16 @@ public class MesWmOutsourceReceiptDetailServiceImpl implements MesWmOutsourceRec
detailMapper.deleteByLineId(lineId);
}
+ /**
+ * 校验保存时的关联数据
+ */
+ private void validateDetailSaveData(MesWmOutsourceReceiptDetailSaveReqVO reqVO) {
+ // 校验物料存在
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
+ // 校验仓库、库区、库位的层级关系
+ areaService.validateWarehouseAreaExists(reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId());
+ }
+
private void validateOutsourceReceiptDetailExists(Long id) {
if (detailMapper.selectById(id) == null) {
throw exception(WM_OUTSOURCE_RECEIPT_DETAIL_NOT_EXISTS);
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptLineServiceImpl.java
index 72dfd83eb..aecc1515b 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptLineServiceImpl.java
@@ -37,26 +37,22 @@ public class MesWmOutsourceReceiptLineServiceImpl implements MesWmOutsourceRecei
@Resource
private MesMdItemService itemService;
-
@Resource
@Lazy
private MesWmOutsourceReceiptDetailService outsourceReceiptDetailService;
-
@Resource
@Lazy
private MesWmOutsourceReceiptService outsourceReceiptService;
-
@Resource
@Lazy
private MesProWorkOrderService workOrderService;
-
@Resource
private MesWmBatchService batchService;
@Override
public Long createOutsourceReceiptLine(MesWmOutsourceReceiptLineSaveReqVO createReqVO) {
// 校验物料存在
- itemService.validateItemExists(createReqVO.getItemId());
+ itemService.validateItemExistsAndEnable(createReqVO.getItemId());
// 插入
MesWmOutsourceReceiptLineDO line = BeanUtils.toBean(createReqVO, MesWmOutsourceReceiptLineDO.class);
@@ -76,7 +72,7 @@ public class MesWmOutsourceReceiptLineServiceImpl implements MesWmOutsourceRecei
// 校验存在
validateOutsourceReceiptLineExists(updateReqVO.getId());
// 校验物料存在
- itemService.validateItemExists(updateReqVO.getItemId());
+ itemService.validateItemExistsAndEnable(updateReqVO.getItemId());
// 更新
MesWmOutsourceReceiptLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmOutsourceReceiptLineDO.class);
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptService.java
index d76e6d34f..5c4d0b19a 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptService.java
@@ -99,4 +99,12 @@ public interface MesWmOutsourceReceiptService {
*/
void validateOutsourceReceiptAndLineExists(Long receiptId, Long lineId);
+ /**
+ * 根据供应商 ID 统计外协入库单数量
+ *
+ * @param vendorId 供应商 ID
+ * @return 数量
+ */
+ Long getOutsourceReceiptCountByVendorId(Long vendorId);
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptServiceImpl.java
index 90c9c0e5c..8b26d330c 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/outsourcereceipt/MesWmOutsourceReceiptServiceImpl.java
@@ -63,14 +63,8 @@ public class MesWmOutsourceReceiptServiceImpl implements MesWmOutsourceReceiptSe
@Override
public Long createOutsourceReceipt(MesWmOutsourceReceiptSaveReqVO createReqVO) {
- // 校验编码唯一
- validateCodeUnique(null, createReqVO.getCode());
- // 校验供应商存在
- vendorService.validateVendorExists(createReqVO.getVendorId());
- // 校验外协工单存在
- if (createReqVO.getWorkOrderId() != null) {
- workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId());
- }
+ // 校验数据
+ validateOutsourceReceiptSaveData(null, createReqVO);
// 插入
MesWmOutsourceReceiptDO receipt = BeanUtils.toBean(createReqVO, MesWmOutsourceReceiptDO.class);
@@ -83,14 +77,8 @@ public class MesWmOutsourceReceiptServiceImpl implements MesWmOutsourceReceiptSe
public void updateOutsourceReceipt(MesWmOutsourceReceiptSaveReqVO updateReqVO) {
// 校验存在 + 草稿状态
validateOutsourceReceiptExistsAndDraft(updateReqVO.getId());
- // 校验编码唯一
- validateCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
- // 校验供应商存在
- vendorService.validateVendorExists(updateReqVO.getVendorId());
- // 校验外协工单存在
- if (updateReqVO.getWorkOrderId() != null) {
- workOrderService.validateWorkOrderExists(updateReqVO.getWorkOrderId());
- }
+ // 校验数据
+ validateOutsourceReceiptSaveData(updateReqVO.getId(), updateReqVO);
// 更新
MesWmOutsourceReceiptDO updateObj = BeanUtils.toBean(updateReqVO, MesWmOutsourceReceiptDO.class);
@@ -171,7 +159,7 @@ public class MesWmOutsourceReceiptServiceImpl implements MesWmOutsourceReceiptSe
MesWmOutsourceReceiptDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO);
// 对比行数量与明细总数量,不满足直接抛出
if (line.getQuantity().compareTo(totalDetailQuantity) > 0) {
- MesMdItemDO item = itemService.validateItemExists(line.getItemId());
+ MesMdItemDO item = itemService.validateItemExistsAndEnable(line.getItemId());
throw exception(WM_OUTSOURCE_RECEIPT_DETAIL_QUANTITY_MISMATCH,
item.getCode() + " " + item.getName() + " 未完成上架");
}
@@ -316,6 +304,17 @@ public class MesWmOutsourceReceiptServiceImpl implements MesWmOutsourceReceiptSe
return receipt;
}
+ private void validateOutsourceReceiptSaveData(Long id, MesWmOutsourceReceiptSaveReqVO reqVO) {
+ // 校验编码唯一
+ validateCodeUnique(id, reqVO.getCode());
+ // 校验供应商存在
+ vendorService.validateVendorExistsAndEnable(reqVO.getVendorId());
+ // 校验外协工单存在
+ if (reqVO.getWorkOrderId() != null) {
+ workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
+ }
+ }
+
private void validateCodeUnique(Long id, String code) {
MesWmOutsourceReceiptDO receipt = outsourceReceiptMapper.selectByCode(code);
if (receipt == null) {
@@ -326,4 +325,9 @@ public class MesWmOutsourceReceiptServiceImpl implements MesWmOutsourceReceiptSe
}
}
+ @Override
+ public Long getOutsourceReceiptCountByVendorId(Long vendorId) {
+ return outsourceReceiptMapper.selectCountByVendorId(vendorId);
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/packages/MesWmPackageLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/packages/MesWmPackageLineServiceImpl.java
index 0a8c092de..a04cd6ba6 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/packages/MesWmPackageLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/packages/MesWmPackageLineServiceImpl.java
@@ -7,11 +7,12 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.packages.vo.line.MesWmPac
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.packages.MesWmPackageLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.packages.MesWmPackageLineMapper;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
+import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -28,18 +29,19 @@ public class MesWmPackageLineServiceImpl implements MesWmPackageLineService {
@Resource
private MesWmPackageLineMapper packageLineMapper;
+
@Resource
@Lazy
private MesWmPackageService packageService;
@Resource
private MesMdItemService itemService;
+ @Resource
+ private MesProWorkOrderService workOrderService;
@Override
public Long createPackageLine(MesWmPackageLineSaveReqVO createReqVO) {
- // 校验装箱单状态为草稿
- packageService.validatePackageStatusDraft(createReqVO.getPackageId());
- // 校验产品物料存在
- itemService.validateItemExists(createReqVO.getItemId());
+ // 校验数据
+ validateLineSaveData(createReqVO);
// 插入
MesWmPackageLineDO line = BeanUtils.toBean(createReqVO, MesWmPackageLineDO.class);
@@ -51,10 +53,9 @@ public class MesWmPackageLineServiceImpl implements MesWmPackageLineService {
public void updatePackageLine(MesWmPackageLineSaveReqVO updateReqVO) {
// 校验存在
MesWmPackageLineDO line = validatePackageLineExists(updateReqVO.getId());
- // 校验装箱单状态为草稿
- packageService.validatePackageStatusDraft(line.getPackageId());
- // 校验产品物料存在
- itemService.validateItemExists(updateReqVO.getItemId());
+ // 校验数据
+ updateReqVO.setPackageId(line.getPackageId());
+ validateLineSaveData(updateReqVO);
// 更新
MesWmPackageLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmPackageLineDO.class);
@@ -93,6 +94,18 @@ public class MesWmPackageLineServiceImpl implements MesWmPackageLineService {
// ========== 校验方法 ==========
+ /**
+ * 校验保存时的关联数据
+ */
+ private void validateLineSaveData(MesWmPackageLineSaveReqVO reqVO) {
+ // 校验装箱单状态为草稿
+ packageService.validatePackageStatusDraft(reqVO.getPackageId());
+ // 校验产品物料存在
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
+ // 校验工单已确认
+ workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
+ }
+
private MesWmPackageLineDO validatePackageLineExists(Long id) {
MesWmPackageLineDO line = packageLineMapper.selectById(id);
if (line == null) {
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/packages/MesWmPackageService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/packages/MesWmPackageService.java
index fff563f2a..7f841ad44 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/packages/MesWmPackageService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/packages/MesWmPackageService.java
@@ -82,19 +82,6 @@ public interface MesWmPackageService {
*/
void removeChildPackage(Long childId);
- /**
- * 可添加为子箱的装箱单列表(无父箱 + 已完成状态)
- *
- * @return 装箱单列表
- */
- List getChildablePackageList();
-
- /**
- * 获得装箱单精简列表
- *
- * @return 装箱单列表
- */
- List getPackageSimpleList();
/**
* 获取指定装箱单及其所有子孙箱的 ID 列表
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/packages/MesWmPackageServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/packages/MesWmPackageServiceImpl.java
index 9fcf63578..671479ddb 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/packages/MesWmPackageServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/packages/MesWmPackageServiceImpl.java
@@ -34,6 +34,7 @@ public class MesWmPackageServiceImpl implements MesWmPackageService {
@Resource
private MesWmPackageMapper packageMapper;
+
@Resource
private MesWmPackageLineService packageLineService;
@Resource
@@ -137,16 +138,6 @@ public class MesWmPackageServiceImpl implements MesWmPackageService {
packageMapper.updateById(new MesWmPackageDO().setId(childId).setParentId(MesWmPackageDO.PARENT_ID_ROOT));
}
- @Override
- public List getChildablePackageList() {
- return packageMapper.selectChildableList();
- }
-
- @Override
- public List getPackageSimpleList() {
- return packageMapper.selectList();
- }
-
@Override
public List getPackageAndDescendantIds(Long packageId) {
List result = CollUtil.newArrayList(packageId);
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueDetailServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueDetailServiceImpl.java
index 337d7b4d2..55d188768 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueDetailServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueDetailServiceImpl.java
@@ -4,16 +4,18 @@ import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productissue.vo.detail.MesWmProductIssueDetailSaveReqVO;
-import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueDetailDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productissue.MesWmProductIssueLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productissue.MesWmProductIssueDetailMapper;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductIssueStatusEnum;
+import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
+import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -35,6 +37,10 @@ public class MesWmProductIssueDetailServiceImpl implements MesWmProductIssueDeta
@Resource
@Lazy
private MesWmProductIssueLineService issueLineService;
+ @Resource
+ private MesWmWarehouseAreaService warehouseAreaService;
+ @Resource
+ private MesWmMaterialStockService materialStockService;
@Override
public Long createProductIssueDetail(MesWmProductIssueDetailSaveReqVO createReqVO) {
@@ -116,8 +122,23 @@ public class MesWmProductIssueDetailServiceImpl implements MesWmProductIssueDeta
private void validateProductIssueDetailSaveData(MesWmProductIssueDetailSaveReqVO reqVO) {
// 校验父数据存在
MesWmProductIssueLineDO line = issueLineService.validateProductIssueLineExists(reqVO.getLineId());
+ if (ObjUtil.notEqual(line.getIssueId(), reqVO.getIssueId())) {
+ throw exception(WM_PRODUCT_ISSUE_DETAIL_LINE_NOT_MATCH);
+ }
+ // 校验主单状态为待拣货(只有 APPROVING 状态才允许新增/修改明细)
+ MesWmProductIssueDO issue = issueService.validateProductIssueExists(line.getIssueId());
+ if (ObjUtil.notEqual(MesWmProductIssueStatusEnum.APPROVING.getStatus(), issue.getStatus())) {
+ throw exception(WM_PRODUCT_ISSUE_STATUS_INVALID);
+ }
// 校验物料匹配(明细 itemId 必须与行 itemId 一致)
validateDetailItemMatch(reqVO.getItemId(), line.getItemId());
+ // 校验仓库、库区、库位的关联关系
+ warehouseAreaService.validateWarehouseAreaExists(
+ reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId());
+ // 校验库存记录
+ materialStockService.validateSelectedStock(
+ reqVO.getMaterialStockId(), reqVO.getItemId(), reqVO.getBatchId(), reqVO.getBatchCode(),
+ reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId(), reqVO.getQuantity());
}
/**
@@ -130,4 +151,3 @@ public class MesWmProductIssueDetailServiceImpl implements MesWmProductIssueDeta
}
}
-
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueLineServiceImpl.java
index 64b9cd847..88511f248 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueLineServiceImpl.java
@@ -109,10 +109,12 @@ public class MesWmProductIssueLineServiceImpl implements MesWmProductIssueLineSe
* 校验保存时的关联数据
*/
private void validateProductIssueLineSaveData(MesWmProductIssueLineSaveReqVO reqVO) {
- // 校验父数据存在 + 校验物料在工单 BOM 中
+ // 校验父数据存在 + 草稿状态才允许新增/修改行
+ issueService.validateProductIssueExistsAndPrepare(reqVO.getIssueId());
+ // 校验物料在工单 BOM 中
validateItemInWorkOrderBom(reqVO.getIssueId(), reqVO.getItemId());
// 校验物料存在
- itemService.validateItemExists(reqVO.getItemId());
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
}
private void validateItemInWorkOrderBom(Long issueId, Long itemId) {
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java
index afbdb906d..bb49d7d74 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productissue/MesWmProductIssueServiceImpl.java
@@ -138,6 +138,11 @@ public class MesWmProductIssueServiceImpl implements MesWmProductIssueService {
if (ObjUtil.notEqual(MesWmProductIssueStatusEnum.APPROVING.getStatus(), issue.getStatus())) {
throw exception(WM_PRODUCT_ISSUE_STATUS_INVALID);
}
+ // 校验拣货明细闭环:行数量 = 明细数量
+ if (!checkProductIssueQuantity(id)) {
+ throw exception(WM_PRODUCT_ISSUE_DETAIL_QUANTITY_MISMATCH);
+ }
+
// 执行拣货(待拣货 → 待执行领出)
issueMapper.updateById(new MesWmProductIssueDO()
.setId(id).setStatus(MesWmProductIssueStatusEnum.APPROVED.getStatus()));
@@ -151,6 +156,15 @@ public class MesWmProductIssueServiceImpl implements MesWmProductIssueService {
if (ObjUtil.notEqual(MesWmProductIssueStatusEnum.APPROVED.getStatus(), issue.getStatus())) {
throw exception(WM_PRODUCT_ISSUE_STATUS_INVALID);
}
+ // 校验至少有一条明细
+ List details = issueDetailService.getProductIssueDetailListByIssueId(id);
+ if (CollUtil.isEmpty(details)) {
+ throw exception(WM_PRODUCT_ISSUE_NO_DETAIL);
+ }
+ // 校验行数量 = 明细数量
+ if (!checkProductIssueQuantity(id)) {
+ throw exception(WM_PRODUCT_ISSUE_DETAIL_QUANTITY_MISMATCH);
+ }
// 2. 遍历所有明细,创建库存事务(扣减库存 + 记录流水)
createTransactionList(issue);
@@ -260,9 +274,9 @@ public class MesWmProductIssueServiceImpl implements MesWmProductIssueService {
*/
private void validateProductIssueSaveData(MesWmProductIssueSaveReqVO reqVO) {
validateCodeUnique(reqVO.getId(), reqVO.getCode());
- workOrderService.validateWorkOrderExists(reqVO.getWorkOrderId());
+ workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
if (reqVO.getWorkstationId() != null) {
- workstationService.validateWorkstationExists(reqVO.getWorkstationId());
+ workstationService.validateWorkstationExistsAndEnable(reqVO.getWorkstationId());
}
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineService.java
index 3623070c2..874ed3d32 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineService.java
@@ -18,6 +18,14 @@ public interface MesWmProductProduceLineService {
*/
void createProductProduceLine(MesWmProductProduceLineDO line);
+ /**
+ * 校验生产入库单行存在
+ *
+ * @param id 行 ID
+ * @return 行数据
+ */
+ MesWmProductProduceLineDO validateProductProduceLineExists(Long id);
+
/**
* 更新生产入库单行(内部使用)
*
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineServiceImpl.java
index 19222cd3d..7ac68bed7 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineServiceImpl.java
@@ -10,6 +10,9 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
+
/**
* MES 生产入库单行 Service 实现类
*/
@@ -25,6 +28,15 @@ public class MesWmProductProduceLineServiceImpl implements MesWmProductProduceLi
productProduceLineMapper.insert(line);
}
+ @Override
+ public MesWmProductProduceLineDO validateProductProduceLineExists(Long id) {
+ MesWmProductProduceLineDO line = productProduceLineMapper.selectById(id);
+ if (line == null) {
+ throw exception(WM_PRODUCT_PRODUCE_LINE_NOT_EXISTS);
+ }
+ return line;
+ }
+
@Override
public void updateProductProduceLine(MesWmProductProduceLineDO line) {
productProduceLineMapper.updateById(line);
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceService.java
index d612272c0..76188cf29 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceService.java
@@ -41,9 +41,11 @@ public interface MesWmProductProduceService {
* 待检产出({@code checkFlag=true})按检验结果拆分行,并执行入库。
*
* @param feedbackId 报工记录 ID
+ * @param sourceLineId 来源产出行 ID(直接定位待检行)
* @param qualifiedQty 合格品数量
* @param unqualifiedQty 不合格品数量
*/
- void splitPendingAndFinishProduce(Long feedbackId, BigDecimal qualifiedQty, BigDecimal unqualifiedQty);
+ void splitPendingAndFinishProduce(Long feedbackId, Long sourceLineId,
+ BigDecimal qualifiedQty, BigDecimal unqualifiedQty);
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java
index c9de8ffbd..bc70ab9c7 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java
@@ -222,7 +222,8 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic
@Override
@Transactional(rollbackFor = Exception.class)
- public void splitPendingAndFinishProduce(Long feedbackId, BigDecimal qualifiedQty, BigDecimal unqualifiedQty) {
+ public void splitPendingAndFinishProduce(Long feedbackId, Long sourceLineId,
+ BigDecimal qualifiedQty, BigDecimal unqualifiedQty) {
// 1.1 查询产出单
MesWmProductProduceDO produce = productProduceMapper.selectByFeedbackId(feedbackId);
if (produce == null) {
@@ -233,13 +234,8 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic
MesWmWarehouseLocationDO virtualLocation = locationService.getWarehouseLocationByCode(MesWmWarehouseLocationDO.WIP_VIRTUAL_LOCATION);
MesWmWarehouseAreaDO virtualArea = areaService.getWarehouseAreaByCode(MesWmWarehouseAreaDO.WIP_VIRTUAL_AREA);
- // 2. 查找待检验行(checkFlag=true 时只有一行 PENDING)
- List lines = productProduceLineService.getProductProduceLineListByProduceId(produce.getId());
- MesWmProductProduceLineDO pendingLine = CollUtil.findOne(lines,
- l -> ObjUtil.equal(l.getQualityStatus(), MesWmQualityStatusEnum.PENDING.getStatus()));
- if (pendingLine == null) {
- throw exception(WM_PRODUCT_PRODUCE_LINE_NOT_EXISTS);
- }
+ // 2. 通过 sourceLineId 直接定位待检验行
+ MesWmProductProduceLineDO pendingLine = productProduceLineService.validateProductProduceLineExists(sourceLineId);
// 3A. 情况一:存在不合格品数量,需要拆分行
if (unqualifiedQty != null && unqualifiedQty.compareTo(BigDecimal.ZERO) > 0) {
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptLineServiceImpl.java
index caecbd583..9968c5321 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptLineServiceImpl.java
@@ -31,10 +31,8 @@ public class MesWmProductReceiptLineServiceImpl implements MesWmProductReceiptLi
@Resource
@Lazy
private MesWmProductReceiptService productReceiptService;
-
@Resource
private MesWmProductReceiptDetailService productReceiptDetailService;
-
@Resource
private MesMdItemService itemService;
@@ -111,7 +109,7 @@ public class MesWmProductReceiptLineServiceImpl implements MesWmProductReceiptLi
// 校验父单据存在且为可编辑状态
productReceiptService.validateProductReceiptEditable(reqVO.getReceiptId());
// 校验物料存在
- itemService.validateItemExists(reqVO.getItemId());
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
}
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptServiceImpl.java
index 23148a2fa..ae7d31290 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productreceipt/MesWmProductReceiptServiceImpl.java
@@ -48,13 +48,10 @@ public class MesWmProductReceiptServiceImpl implements MesWmProductReceiptServic
@Resource
private MesWmProductReceiptLineService productReceiptLineService;
-
@Resource
private MesWmProductReceiptDetailService productReceiptDetailService;
-
@Resource
private MesWmTransactionService wmTransactionService;
-
@Resource
private MesProWorkOrderService workOrderService;
@Resource
@@ -267,7 +264,7 @@ public class MesWmProductReceiptServiceImpl implements MesWmProductReceiptServic
validateCodeUnique(reqVO.getId(), reqVO.getCode());
// 校验工单存在
return reqVO.getWorkOrderId() != null ?
- workOrderService.validateWorkOrderExists(reqVO.getWorkOrderId()) : null;
+ workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId()) : null;
}
private void validateCodeUnique(Long id, String code) {
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesDetailServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesDetailServiceImpl.java
index 64f10ad6d..8cb2e81a2 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesDetailServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesDetailServiceImpl.java
@@ -1,15 +1,19 @@
package cn.iocoder.yudao.module.mes.service.wm.productsales;
+import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.detail.MesWmProductSalesDetailSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesDetailMapper;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -29,17 +33,18 @@ public class MesWmProductSalesDetailServiceImpl implements MesWmProductSalesDeta
@Resource
private MesMdItemService itemService;
-
+ @Resource
+ @Lazy
+ private MesWmProductSalesLineService productSalesLineService;
+ @Resource
+ private MesWmMaterialStockService materialStockService;
@Resource
private MesWmWarehouseAreaService warehouseAreaService;
@Override
public Long createProductSalesDetail(MesWmProductSalesDetailSaveReqVO createReqVO) {
- // 校验物料存在
- itemService.validateItemExists(createReqVO.getItemId());
- // 校验库区关系
- warehouseAreaService.validateWarehouseAreaExists(
- createReqVO.getWarehouseId(), createReqVO.getLocationId(), createReqVO.getAreaId());
+ // 校验数据
+ validateProductSalesDetailSaveData(createReqVO);
// 插入
MesWmProductSalesDetailDO detail = BeanUtils.toBean(createReqVO, MesWmProductSalesDetailDO.class);
@@ -51,11 +56,8 @@ public class MesWmProductSalesDetailServiceImpl implements MesWmProductSalesDeta
public void updateProductSalesDetail(MesWmProductSalesDetailSaveReqVO updateReqVO) {
// 校验存在
validateProductSalesDetailExists(updateReqVO.getId());
- // 校验物料存在
- itemService.validateItemExists(updateReqVO.getItemId());
- // 校验库区关系
- warehouseAreaService.validateWarehouseAreaExists(
- updateReqVO.getWarehouseId(), updateReqVO.getLocationId(), updateReqVO.getAreaId());
+ // 校验数据
+ validateProductSalesDetailSaveData(updateReqVO);
// 更新
MesWmProductSalesDetailDO updateObj = BeanUtils.toBean(updateReqVO, MesWmProductSalesDetailDO.class);
@@ -103,4 +105,27 @@ public class MesWmProductSalesDetailServiceImpl implements MesWmProductSalesDeta
return detail;
}
+ private void validateProductSalesDetailSaveData(MesWmProductSalesDetailSaveReqVO reqVO) {
+ // 校验父数据(行)存在
+ MesWmProductSalesLineDO line = productSalesLineService.getProductSalesLine(reqVO.getLineId());
+ if (line == null) {
+ throw exception(WM_PRODUCT_SALES_LINE_NOT_EXISTS);
+ }
+ if (ObjUtil.notEqual(line.getSalesId(), reqVO.getSalesId())) {
+ throw exception(WM_PRODUCT_SALES_DETAIL_LINE_NOT_MATCH);
+ }
+ // 校验物料存在
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
+ if (ObjUtil.notEqual(line.getItemId(), reqVO.getItemId())) {
+ throw exception(WM_PRODUCT_SALES_DETAIL_ITEM_MISMATCH);
+ }
+ // 校验库位存在
+ warehouseAreaService.validateWarehouseAreaExists(
+ reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId());
+ // 校验库存记录存在且物料一致
+ materialStockService.validateSelectedStock(
+ reqVO.getMaterialStockId(), reqVO.getItemId(), reqVO.getBatchId(), reqVO.getBatchCode(),
+ reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId(), reqVO.getQuantity());
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineService.java
index 2e7f98cfa..d59418e24 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineService.java
@@ -68,6 +68,14 @@ public interface MesWmProductSalesLineService {
*/
PageResult getProductSalesLinePage(MesWmProductSalesLinePageReqVO pageReqVO);
+ /**
+ * 校验销售出库单行存在
+ *
+ * @param id 编号
+ * @return 销售出库单行
+ */
+ MesWmProductSalesLineDO validateProductSalesLineExists(Long id);
+
/**
* OQC 检验完成后,更新销售出库单行的 OQC 质检状态
*
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java
index 79532da1c..5d112b8c7 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java
@@ -1,12 +1,12 @@
package cn.iocoder.yudao.module.mes.service.wm.productsales;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLinePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLineSaveReqVO;
-import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper;
@@ -14,19 +14,22 @@ import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeLineDO;
+import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeLineService;
+import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.List;
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.convertList;
-import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_PRODUCT_SALES_LINE_NOT_EXISTS;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
* MES 销售出库单行 Service 实现类
@@ -40,6 +43,7 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe
@Resource
private MesWmProductSalesLineMapper productSalesLineMapper;
+
@Resource
private MesWmProductSalesDetailService productSalesDetailService;
@Resource
@@ -49,13 +53,14 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe
@Resource
@Lazy
private MesWmProductSalesService productSalesService;
+ @Resource
+ @Lazy
+ private MesWmSalesNoticeLineService salesNoticeLineService;
@Override
public Long createProductSalesLine(MesWmProductSalesLineSaveReqVO createReqVO) {
- // 校验物料存在
- itemService.validateItemExists(createReqVO.getItemId());
- // 根据 batchCode 解析 batchId
- fillBatchId(createReqVO);
+ // 校验数据
+ validateLineSaveData(createReqVO);
// 新增
MesWmProductSalesLineDO line = BeanUtils.toBean(createReqVO, MesWmProductSalesLineDO.class);
@@ -66,11 +71,10 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe
@Override
public void updateProductSalesLine(MesWmProductSalesLineSaveReqVO updateReqVO) {
// 校验存在
- validateProductSalesLineExists(updateReqVO.getId());
- // 校验物料存在
- itemService.validateItemExists(updateReqVO.getItemId());
- // 根据 batchCode 解析 batchId
- fillBatchId(updateReqVO);
+ MesWmProductSalesLineDO oldLine = validateProductSalesLineExists(updateReqVO.getId());
+ // 校验数据
+ updateReqVO.setSalesId(oldLine.getSalesId());
+ validateLineSaveData(updateReqVO);
// 更新
MesWmProductSalesLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmProductSalesLineDO.class);
@@ -81,7 +85,9 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe
@Transactional(rollbackFor = Exception.class)
public void deleteProductSalesLine(Long id) {
// 校验存在
- validateProductSalesLineExists(id);
+ MesWmProductSalesLineDO line = validateProductSalesLineExists(id);
+ // 校验主单为草稿状态才允许删除行
+ productSalesService.validateProductSalesExistsAndDraft(line.getSalesId());
// 级联删除明细
productSalesDetailService.deleteProductSalesDetailByLineId(id);
@@ -119,7 +125,8 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe
return productSalesLineMapper.selectPage(pageReqVO);
}
- private MesWmProductSalesLineDO validateProductSalesLineExists(Long id) {
+ @Override
+ public MesWmProductSalesLineDO validateProductSalesLineExists(Long id) {
MesWmProductSalesLineDO line = productSalesLineMapper.selectById(id);
if (line == null) {
throw exception(WM_PRODUCT_SALES_LINE_NOT_EXISTS);
@@ -139,6 +146,60 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe
reqVO.setBatchId(batch != null ? batch.getId() : null);
}
+ /**
+ * 校验保存时的关联数据
+ */
+ private void validateLineSaveData(MesWmProductSalesLineSaveReqVO reqVO) {
+ // 校验主单存在且为草稿状态
+ MesWmProductSalesDO sales = productSalesService.validateProductSalesExistsAndDraft(reqVO.getSalesId());
+ // 校验物料存在
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
+ // 校验关联发货通知单行(存在性 + 归属 + 字段一致性)
+ validateSalesNoticeLine(sales, reqVO);
+ // 根据 batchCode 解析 batchId
+ fillBatchId(reqVO);
+ }
+
+ /**
+ * 校验发货通知单行
+ *
+ * @param sales 出库单
+ * @param reqVO 出库行请求
+ */
+ private void validateSalesNoticeLine(MesWmProductSalesDO sales, MesWmProductSalesLineSaveReqVO reqVO) {
+ Long noticeLineId = reqVO.getNoticeLineId();
+ // 情况一:如果出库单关联了发货通知单,则必须关联发货通知单行
+ if (sales.getNoticeId() != null) {
+ if (noticeLineId == null) {
+ throw exception(WM_PRODUCT_SALES_LINE_SALES_NOTICE_LINE_REQUIRED);
+ }
+ MesWmSalesNoticeLineDO noticeLine = salesNoticeLineService.validateSalesNoticeLineExists(
+ noticeLineId, sales.getNoticeId());
+ // 校验关键字段一致性:物料、数量、批次号、OQC 检验标识
+ if (ObjUtil.notEqual(reqVO.getItemId(), noticeLine.getItemId())) {
+ throw exception(WM_PRODUCT_SALES_LINE_NOTICE_LINE_ITEM_MISMATCH);
+ }
+ if (noticeLine.getQuantity() != null && reqVO.getQuantity() != null
+ && reqVO.getQuantity().compareTo(noticeLine.getQuantity()) != 0) {
+ throw exception(WM_PRODUCT_SALES_LINE_NOTICE_LINE_QUANTITY_MISMATCH);
+ }
+ if (StrUtil.isNotBlank(noticeLine.getBatchCode())
+ && ObjUtil.notEqual(reqVO.getBatchCode(), noticeLine.getBatchCode())) {
+ throw exception(WM_PRODUCT_SALES_LINE_NOTICE_LINE_BATCH_MISMATCH);
+ }
+ if (noticeLine.getOqcCheckFlag() != null
+ && ObjUtil.notEqual(reqVO.getOqcCheckFlag(), noticeLine.getOqcCheckFlag())) {
+ throw exception(WM_PRODUCT_SALES_LINE_NOTICE_LINE_OQC_MISMATCH);
+ }
+ return;
+ }
+
+ // 情况二:如果出库单没有关联发货通知单,则不允许关联发货通知单行
+ if (noticeLineId != null) {
+ throw exception(WM_PRODUCT_SALES_LINE_SALES_NOTICE_LINE_NOT_ALLOWED);
+ }
+ }
+
@Override
@Transactional(rollbackFor = Exception.class)
public void updateProductSalesLineWhenOqcFinish(Long id, Long oqcId, Integer checkResult) {
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesService.java
index 979d7d40c..f248e664d 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesService.java
@@ -116,4 +116,13 @@ public interface MesWmProductSalesService {
*/
List getProductSalesListByClientId(Long clientId);
+ /**
+ * 校验销售出库单存在且为草稿状态
+ *
+ * @param id 编号
+ * @return 销售出库单
+ */
+ MesWmProductSalesDO validateProductSalesExistsAndDraft(Long id);
+
}
+
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
index 71d92b1c6..f461b58f2 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
@@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesShippingReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO;
@@ -16,8 +17,10 @@ import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMa
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmSalesNoticeStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.client.MesMdClientService;
+import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import org.springframework.stereotype.Service;
@@ -46,13 +49,12 @@ public class MesWmProductSalesServiceImpl implements MesWmProductSalesService {
@Resource
private MesWmProductSalesLineService productSalesLineService;
-
@Resource
private MesWmProductSalesDetailService productSalesDetailService;
-
@Resource
private MesMdClientService clientService;
-
+ @Resource
+ private MesWmSalesNoticeService salesNoticeService;
@Resource
private MesWmTransactionService wmTransactionService;
@@ -218,7 +220,7 @@ public class MesWmProductSalesServiceImpl implements MesWmProductSalesService {
wmTransactionService.createTransactionList(convertList(details, detail -> new MesWmTransactionSaveReqDTO()
.setType(MesWmTransactionTypeEnum.OUT.getType()).setItemId(detail.getItemId())
.setQuantity(detail.getQuantity().negate()) // 出库数量为负数
- .setBatchId(detail.getBatchId())
+ .setBatchId(detail.getBatchId()).setBatchCode(detail.getBatchCode())
.setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId())
.setBizType(MesBizTypeConstants.WM_PRODUCT_SALES).setBizId(sales.getId())
.setBizCode(sales.getCode()).setBizLineId(detail.getLineId())));
@@ -261,7 +263,17 @@ public class MesWmProductSalesServiceImpl implements MesWmProductSalesService {
// 校验编码唯一
validateCodeUnique(id, reqVO.getCode());
// 校验客户存在
- clientService.validateClientExists(reqVO.getClientId());
+ clientService.validateClientExistsAndEnable(reqVO.getClientId());
+ // 校验发货通知单
+ if (reqVO.getNoticeId() != null) {
+ MesWmSalesNoticeDO notice = salesNoticeService.validateSalesNoticeExists(reqVO.getNoticeId());
+ if (ObjUtil.notEqual(notice.getStatus(), MesWmSalesNoticeStatusEnum.APPROVED.getStatus())) {
+ throw exception(WM_SALES_NOTICE_STATUS_NOT_APPROVED);
+ }
+ if (ObjUtil.notEqual(notice.getClientId(), reqVO.getClientId())) {
+ throw exception(WM_SALES_NOTICE_CLIENT_MISMATCH);
+ }
+ }
}
private MesWmProductSalesDO validateProductSalesExists(Long id) {
@@ -275,7 +287,8 @@ public class MesWmProductSalesServiceImpl implements MesWmProductSalesService {
/**
* 校验销售出库单存在且为草稿状态
*/
- private MesWmProductSalesDO validateProductSalesExistsAndDraft(Long id) {
+ @Override
+ public MesWmProductSalesDO validateProductSalesExistsAndDraft(Long id) {
MesWmProductSalesDO sales = validateProductSalesExists(id);
if (ObjUtil.notEqual(MesWmProductSalesStatusEnum.PREPARE.getStatus(), sales.getStatus())) {
throw exception(WM_PRODUCT_SALES_NOT_PREPARE);
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueDetailServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueDetailServiceImpl.java
index 8a8b94e16..4c39cb365 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueDetailServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueDetailServiceImpl.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.service.wm.returnissue;
+import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnissue.vo.detail.MesWmReturnIssueDetailSaveReqVO;
@@ -17,8 +18,7 @@ import java.math.BigDecimal;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_RETURN_ISSUE_DETAIL_NOT_EXISTS;
-import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_RETURN_ISSUE_DETAIL_QUANTITY_EXCEED;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
* MES 生产退料明细 Service 实现类
@@ -105,11 +105,21 @@ public class MesWmReturnIssueDetailServiceImpl implements MesWmReturnIssueDetail
private void validateReturnIssueDetailSaveData(MesWmReturnIssueDetailSaveReqVO reqVO) {
// 校验父数据存在
MesWmReturnIssueLineDO line = issueLineService.validateReturnIssueLineExists(reqVO.getLineId());
+ if (ObjUtil.notEqual(line.getIssueId(), reqVO.getIssueId())) {
+ throw exception(WM_RETURN_ISSUE_DETAIL_LINE_NOT_MATCH);
+ }
+ if (ObjUtil.notEqual(line.getItemId(), reqVO.getItemId())) {
+ throw exception(WM_RETURN_ISSUE_DETAIL_ITEM_MISMATCH);
+ }
// 校验仓库、库区、库位的关联关系
warehouseAreaService.validateWarehouseAreaExists(
reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId());
// 校验库位物料/批次混放规则
materialStockService.checkAreaMixingRule(reqVO.getAreaId(), reqVO.getItemId(), reqVO.getBatchId());
+ // 校验库存记录
+ materialStockService.validateSelectedStock(
+ reqVO.getMaterialStockId(), reqVO.getItemId(), reqVO.getBatchId(), reqVO.getBatchCode(),
+ reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId(), reqVO.getQuantity());
// 校验明细总数量不超过行数量(排除自身)
validateDetailQuantityNotExceed(reqVO.getLineId(), reqVO.getQuantity(), reqVO.getId(), line);
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueLineServiceImpl.java
index 9d1ca5dd4..d38654c8f 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueLineServiceImpl.java
@@ -117,7 +117,7 @@ public class MesWmReturnIssueLineServiceImpl implements MesWmReturnIssueLineServ
// 校验父数据存在 + 草稿状态
MesWmReturnIssueDO issue = issueService.validateReturnIssueExistsAndPrepare(reqVO.getIssueId());
// 校验物料存在
- itemService.validateItemExists(reqVO.getItemId());
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
return issue;
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueServiceImpl.java
index 073bba123..6cd6aad09 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnissue/MesWmReturnIssueServiceImpl.java
@@ -167,7 +167,7 @@ public class MesWmReturnIssueServiceImpl implements MesWmReturnIssueService {
MesWmReturnIssueDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO);
// 对比行数量与明细总数量,不满足直接抛出
if (line.getQuantity().compareTo(totalDetailQuantity) > 0) {
- MesMdItemDO item = itemService.validateItemExists(line.getItemId());
+ MesMdItemDO item = itemService.validateItemExistsAndEnable(line.getItemId());
throw exception(WM_RETURN_ISSUE_DETAIL_QUANTITY_MISMATCH,
item.getCode() + " " + item.getName() + " 未完成上架");
}
@@ -288,9 +288,9 @@ public class MesWmReturnIssueServiceImpl implements MesWmReturnIssueService {
*/
private void validateReturnIssueSaveData(MesWmReturnIssueSaveReqVO reqVO) {
validateCodeUnique(reqVO.getId(), reqVO.getCode());
- workOrderService.validateWorkOrderExists(reqVO.getWorkOrderId());
+ workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
if (reqVO.getWorkstationId() != null) {
- workstationService.validateWorkstationExists(reqVO.getWorkstationId());
+ workstationService.validateWorkstationExistsAndEnable(reqVO.getWorkstationId());
}
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesDetailServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesDetailServiceImpl.java
index 26131e8ce..68640e7b7 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesDetailServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesDetailServiceImpl.java
@@ -1,14 +1,17 @@
package cn.iocoder.yudao.module.mes.service.wm.returnsales;
import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.detail.MesWmReturnSalesDetailSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesDetailMapper;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnSalesStatusEnum;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import org.springframework.context.annotation.Lazy;
@@ -40,16 +43,17 @@ public class MesWmReturnSalesDetailServiceImpl implements MesWmReturnSalesDetail
@Resource
@Lazy
private MesWmReturnSalesService returnSalesService;
-
@Resource
private MesWmWarehouseAreaService warehouseAreaService;
-
+ @Resource
+ private MesWmBatchService batchService;
@Resource
@Lazy
private MesWmMaterialStockService materialStockService;
@Override
public Long createReturnSalesDetail(MesWmReturnSalesDetailSaveReqVO createReqVO) {
+ // 校验保存数据
validateReturnSalesDetailSaveData(createReqVO);
// 插入
@@ -62,6 +66,7 @@ public class MesWmReturnSalesDetailServiceImpl implements MesWmReturnSalesDetail
public void updateReturnSalesDetail(MesWmReturnSalesDetailSaveReqVO updateReqVO) {
// 校验存在
validateReturnSalesDetailExists(updateReqVO.getId());
+ // 校验保存数据
validateReturnSalesDetailSaveData(updateReqVO);
// 更新
@@ -138,6 +143,9 @@ public class MesWmReturnSalesDetailServiceImpl implements MesWmReturnSalesDetail
materialStockService.checkAreaMixingRule(reqVO.getAreaId(), reqVO.getItemId(), reqVO.getBatchId());
// 校验明细总数量不超过行数量(排除自身)
validateDetailQuantityNotExceed(reqVO.getLineId(), reqVO.getQuantity(), reqVO.getId(), line);
+
+ // 根据 batchCode 解析 batchId(必须在混放规则校验之前,因为它依赖 batchId)
+ fillBatchId(reqVO);
}
/**
@@ -162,4 +170,17 @@ public class MesWmReturnSalesDetailServiceImpl implements MesWmReturnSalesDetail
}
}
+ /**
+ * 根据 batchCode 解析并回填 batchId
+ */
+ private void fillBatchId(MesWmReturnSalesDetailSaveReqVO reqVO) {
+ if (StrUtil.isBlank(reqVO.getBatchCode())) {
+ return;
+ }
+ MesWmBatchDO batch = batchService.getBatchByCode(reqVO.getBatchCode());
+ if (batch != null) {
+ reqVO.setBatchId(batch.getId());
+ }
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesLineServiceImpl.java
index 050d8952a..0aa1fd8e8 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesLineServiceImpl.java
@@ -8,15 +8,17 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.line.MesWm
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.line.MesWmReturnSalesLineSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesLineDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesLineMapper;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnSalesStatusEnum;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
+import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
@@ -43,6 +45,8 @@ public class MesWmReturnSalesLineServiceImpl implements MesWmReturnSalesLineServ
@Resource
private MesMdItemService itemService;
@Resource
+ private MesWmBatchService batchService;
+ @Resource
@Lazy
private MesWmReturnSalesDetailService returnSalesDetailService;
@@ -61,7 +65,9 @@ public class MesWmReturnSalesLineServiceImpl implements MesWmReturnSalesLineServ
@Override
public void updateReturnSalesLine(MesWmReturnSalesLineSaveReqVO updateReqVO) {
// 校验存在
- validateReturnSalesLineExists(updateReqVO.getId());
+ MesWmReturnSalesLineDO oldLine = validateReturnSalesLineExists(updateReqVO.getId());
+ // 固定父单 ID,防止通过接口篡改
+ updateReqVO.setReturnId(oldLine.getReturnId());
validateLineSaveData(updateReqVO);
// 更新
@@ -78,7 +84,7 @@ public class MesWmReturnSalesLineServiceImpl implements MesWmReturnSalesLineServ
// 校验退货单存在 + 草稿状态
returnSalesService.validateReturnSalesExistsAndPrepare(reqVO.getReturnId());
// 校验物料存在 + 批次管理
- validateItemBatchManagement(reqVO.getItemId(), reqVO.getBatchId());
+ validateItemBatchManagement(reqVO.getReturnId(), reqVO.getItemId(), reqVO.getBatchId());
}
@Override
@@ -144,15 +150,22 @@ public class MesWmReturnSalesLineServiceImpl implements MesWmReturnSalesLineServ
/**
* 校验物料批次管理
*
- * @param itemId 物料ID
- * @param batchId 批次ID
+ * @param returnId 退货单ID
+ * @param itemId 物料ID
+ * @param batchId 批次ID
*/
- private void validateItemBatchManagement(Long itemId, Long batchId) {
- MesMdItemDO item = itemService.validateItemExists(itemId);
+ private void validateItemBatchManagement(Long returnId, Long itemId, Long batchId) {
+ MesMdItemDO item = itemService.validateItemExistsAndEnable(itemId);
// 如果物料启用了批次管理,则必须选择批次
if (Boolean.TRUE.equals(item.getBatchFlag()) && batchId == null) {
throw exception(MD_ITEM_BATCH_REQUIRED);
}
+ // 校验批次存在且属于当前物料和客户
+ if (batchId != null) {
+ // 从父单获取客户ID,校验批次归属
+ MesWmReturnSalesDO returnSales = returnSalesService.validateReturnSalesExistsAndPrepare(returnId);
+ batchService.validateBatchExists(batchId, itemId, returnSales.getClientId(), null);
+ }
}
@Override
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesServiceImpl.java
index e813685f2..c94bef47c 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesServiceImpl.java
@@ -169,7 +169,7 @@ public class MesWmReturnSalesServiceImpl implements MesWmReturnSalesService {
MesWmReturnSalesDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO);
// 对比行数量与明细总数量,不满足直接抛出
if (line.getQuantity().compareTo(totalDetailQuantity) > 0) {
- MesMdItemDO item = itemService.validateItemExists(line.getItemId());
+ MesMdItemDO item = itemService.validateItemExistsAndEnable(line.getItemId());
throw exception(WM_RETURN_SALES_DETAIL_QUANTITY_MISMATCH,
item.getCode() + " " + item.getName() + " 未完成上架");
}
@@ -189,7 +189,7 @@ public class MesWmReturnSalesServiceImpl implements MesWmReturnSalesService {
wmTransactionService.createTransactionList(convertList(details, detail -> new MesWmTransactionSaveReqDTO()
.setType(MesWmTransactionTypeEnum.IN.getType()).setItemId(detail.getItemId())
.setQuantity(detail.getQuantity()) // 入库数量为正数
- .setBatchId(detail.getBatchId())
+ .setBatchId(detail.getBatchId()).setBatchCode(detail.getBatchCode())
.setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId())
.setBizType(MesBizTypeConstants.WM_RETURN_SALES).setBizId(returnSales.getId())
.setBizCode(returnSales.getCode()).setBizLineId(detail.getLineId())));
@@ -271,7 +271,7 @@ public class MesWmReturnSalesServiceImpl implements MesWmReturnSalesService {
*/
private void validateSaveData(MesWmReturnSalesSaveReqVO reqVO) {
validateCodeUnique(reqVO.getId(), reqVO.getCode());
- clientService.validateClientExists(reqVO.getClientId());
+ clientService.validateClientExistsAndEnable(reqVO.getClientId());
}
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorDetailServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorDetailServiceImpl.java
index cea680532..985e80f94 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorDetailServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorDetailServiceImpl.java
@@ -3,17 +3,17 @@ package cn.iocoder.yudao.module.mes.service.wm.returnvendor;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.detail.MesWmReturnVendorDetailSaveReqVO;
-import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnvendor.MesWmReturnVendorDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnvendor.MesWmReturnVendorLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnvendor.MesWmReturnVendorDetailMapper;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
+import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -32,12 +32,12 @@ public class MesWmReturnVendorDetailServiceImpl implements MesWmReturnVendorDeta
@Resource
@Lazy
private MesWmReturnVendorLineService returnVendorLineService;
-
@Resource
private MesMdItemService itemService;
-
@Resource
private MesWmMaterialStockService materialStockService;
+ @Resource
+ private MesWmWarehouseAreaService warehouseAreaService;
@Override
public Long createReturnVendorDetail(MesWmReturnVendorDetailSaveReqVO createReqVO) {
@@ -68,12 +68,18 @@ public class MesWmReturnVendorDetailServiceImpl implements MesWmReturnVendorDeta
private void validateDetailSaveData(MesWmReturnVendorDetailSaveReqVO reqVO) {
// 校验父数据(行)存在
MesWmReturnVendorLineDO line = returnVendorLineService.validateReturnVendorLineExists(reqVO.getLineId());
+ if (ObjUtil.notEqual(line.getReturnId(), reqVO.getReturnId())) {
+ throw exception(WM_RETURN_VENDOR_DETAIL_LINE_NOT_MATCH);
+ }
// 校验物料存在
- itemService.validateItemExists(reqVO.getItemId());
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
// 校验物料与行物料一致
validateItemConsistency(reqVO.getItemId(), line);
+ // 校验库位存在
+ warehouseAreaService.validateWarehouseAreaExists(
+ reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId());
// 校验库存记录存在且物料一致
- validateMaterialStock(reqVO.getMaterialStockId(), line);
+ validateMaterialStock(reqVO, line);
}
@Override
@@ -127,15 +133,11 @@ public class MesWmReturnVendorDetailServiceImpl implements MesWmReturnVendorDeta
/**
* 校验库存记录存在且物料与行物料一致
*/
- private void validateMaterialStock(Long materialStockId, MesWmReturnVendorLineDO line) {
- if (materialStockId == null) {
- return;
- }
- MesWmMaterialStockDO stock = materialStockService.getMaterialStock(materialStockId);
- if (stock == null) {
- throw exception(WM_MATERIAL_STOCK_NOT_EXISTS);
- }
- if (ObjUtil.notEqual(stock.getItemId(), line.getItemId())) {
+ private void validateMaterialStock(MesWmReturnVendorDetailSaveReqVO reqVO, MesWmReturnVendorLineDO line) {
+ materialStockService.validateSelectedStock(
+ reqVO.getMaterialStockId(), reqVO.getItemId(), reqVO.getBatchId(), reqVO.getBatchCode(),
+ reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId(), reqVO.getQuantity());
+ if (ObjUtil.notEqual(line.getItemId(), reqVO.getItemId())) {
throw exception(WM_RETURN_VENDOR_DETAIL_ITEM_MISMATCH);
}
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorLineServiceImpl.java
index aa5c0532f..6b70f9277 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorLineServiceImpl.java
@@ -5,14 +5,16 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.line.MesWmReturnVendorLinePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.line.MesWmReturnVendorLineSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnvendor.MesWmReturnVendorLineDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnvendor.MesWmReturnVendorDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnvendor.MesWmReturnVendorLineMapper;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
+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 javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -36,6 +38,8 @@ public class MesWmReturnVendorLineServiceImpl implements MesWmReturnVendorLineSe
private MesWmReturnVendorDetailService returnVendorDetailService;
@Resource
private MesMdItemService itemService;
+ @Resource
+ private MesWmBatchService batchService;
@Override
public Long createReturnVendorLine(MesWmReturnVendorLineSaveReqVO createReqVO) {
@@ -51,7 +55,9 @@ public class MesWmReturnVendorLineServiceImpl implements MesWmReturnVendorLineSe
@Override
public void updateReturnVendorLine(MesWmReturnVendorLineSaveReqVO updateReqVO) {
// 校验存在
- validateReturnVendorLineExists(updateReqVO.getId());
+ MesWmReturnVendorLineDO oldLine = validateReturnVendorLineExists(updateReqVO.getId());
+ // 固定父单 ID,防止通过接口篡改
+ updateReqVO.setReturnId(oldLine.getReturnId());
// 校验数据
validateReturnVendorLineSaveData(updateReqVO);
@@ -105,9 +111,13 @@ public class MesWmReturnVendorLineServiceImpl implements MesWmReturnVendorLineSe
private void validateReturnVendorLineSaveData(MesWmReturnVendorLineSaveReqVO reqVO) {
// 校验父数据存在且为草稿状态
- returnVendorService.validateReturnVendorExistsAndPrepare(reqVO.getReturnId());
+ MesWmReturnVendorDO returnVendor = returnVendorService.validateReturnVendorExistsAndPrepare(reqVO.getReturnId());
// 校验物料存在
- itemService.validateItemExists(reqVO.getItemId());
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
+ // 校验批次存在且属于当前物料和供应商
+ if (reqVO.getBatchId() != null) {
+ batchService.validateBatchExists(reqVO.getBatchId(), reqVO.getItemId(), null, returnVendor.getVendorId());
+ }
}
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
index a3bed7b39..8bd7fac32 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
@@ -217,7 +217,7 @@ public class MesWmReturnVendorServiceImpl implements MesWmReturnVendorService {
validateReturnVendorExistsAndPrepare(reqVO.getId());
}
// 校验供应商存在
- vendorService.validateVendorExists(reqVO.getVendorId());
+ vendorService.validateVendorExistsAndEnable(reqVO.getVendorId());
// 校验编码唯一
validateCodeUnique(reqVO.getId(), reqVO.getCode());
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineService.java
index 17cdad419..8e9286469 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineService.java
@@ -66,4 +66,21 @@ public interface MesWmSalesNoticeLineService {
*/
void deleteSalesNoticeLineByNoticeId(Long noticeId);
+ /**
+ * 校验发货通知单行存在
+ *
+ * @param id 行编号
+ * @return 发货通知单行
+ */
+ MesWmSalesNoticeLineDO validateSalesNoticeLineExists(Long id);
+
+ /**
+ * 校验发货通知单行存在,并且属于指定的通知单
+ *
+ * @param lineId 行编号
+ * @param noticeId 通知单编号
+ * @return 发货通知单行
+ */
+ MesWmSalesNoticeLineDO validateSalesNoticeLineExists(Long lineId, Long noticeId);
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineServiceImpl.java
index f926cf6d2..fca67d4df 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineServiceImpl.java
@@ -36,10 +36,8 @@ public class MesWmSalesNoticeLineServiceImpl implements MesWmSalesNoticeLineServ
@Resource
@Lazy
private MesWmSalesNoticeService salesNoticeService;
-
@Resource
private MesWmBatchService batchService;
-
@Resource
private MesMdItemService itemService;
@@ -100,7 +98,8 @@ public class MesWmSalesNoticeLineServiceImpl implements MesWmSalesNoticeLineServ
salesNoticeLineMapper.deleteByNoticeId(noticeId);
}
- private MesWmSalesNoticeLineDO validateSalesNoticeLineExists(Long id) {
+ @Override
+ public MesWmSalesNoticeLineDO validateSalesNoticeLineExists(Long id) {
MesWmSalesNoticeLineDO line = salesNoticeLineMapper.selectById(id);
if (line == null) {
throw exception(WM_SALES_NOTICE_LINE_NOT_EXISTS);
@@ -108,12 +107,22 @@ public class MesWmSalesNoticeLineServiceImpl implements MesWmSalesNoticeLineServ
return line;
}
+ @Override
+ public MesWmSalesNoticeLineDO validateSalesNoticeLineExists(Long lineId, Long noticeId) {
+ MesWmSalesNoticeLineDO line = validateSalesNoticeLineExists(lineId);
+ // 进一步校验行的 noticeId 与传入的 noticeId 是否匹配
+ if (noticeId != null && ObjUtil.notEqual(line.getNoticeId(), noticeId)) {
+ throw exception(WM_SALES_NOTICE_LINE_NOT_MATCH);
+ }
+ return line;
+ }
+
private void validateLineSaveData(MesWmSalesNoticeLineSaveReqVO reqVO) {
// 校验父单据存在且为草稿状态
validateNoticeStatusDraft(reqVO.getNoticeId());
// 校验物料存在
if (reqVO.getItemId() != null) {
- itemService.validateItemExists(reqVO.getItemId());
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
}
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeService.java
index 2d12ce116..5ff40fdd3 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeService.java
@@ -39,6 +39,14 @@ public interface MesWmSalesNoticeService {
*/
void deleteSalesNotice(Long id);
+ /**
+ * 校验发货通知单存在
+ *
+ * @param id 编号
+ * @return 发货通知单
+ */
+ MesWmSalesNoticeDO validateSalesNoticeExists(Long id);
+
/**
* 获得发货通知单
*
@@ -80,12 +88,4 @@ public interface MesWmSalesNoticeService {
return convertMap(getSalesNoticeList(ids), MesWmSalesNoticeDO::getId);
}
- /**
- * 按状态获得发货通知单列表
- *
- * @param status 状态
- * @return 发货通知单列表
- */
- List getSalesNoticeListByStatus(Integer status);
-
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeServiceImpl.java
index 76080684f..7193cd346 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeServiceImpl.java
@@ -37,7 +37,6 @@ public class MesWmSalesNoticeServiceImpl implements MesWmSalesNoticeService {
@Resource
private MesWmSalesNoticeLineService salesNoticeLineService;
-
@Resource
private MesMdClientService clientService;
@@ -112,14 +111,7 @@ public class MesWmSalesNoticeServiceImpl implements MesWmSalesNoticeService {
}
@Override
- public List getSalesNoticeListByStatus(Integer status) {
- if (status == null) {
- return salesNoticeMapper.selectList();
- }
- return salesNoticeMapper.selectListByStatus(status);
- }
-
- private MesWmSalesNoticeDO validateSalesNoticeExists(Long id) {
+ public MesWmSalesNoticeDO validateSalesNoticeExists(Long id) {
MesWmSalesNoticeDO notice = salesNoticeMapper.selectById(id);
if (notice == null) {
throw exception(WM_SALES_NOTICE_NOT_EXISTS);
@@ -140,15 +132,15 @@ public class MesWmSalesNoticeServiceImpl implements MesWmSalesNoticeService {
private void validateSalesNoticeSave(MesWmSalesNoticeSaveReqVO saveReqVO) {
// 校验编码唯一
- validateNoticeCodeUnique(saveReqVO.getId(), saveReqVO.getNoticeCode());
+ validateNoticeCodeUnique(saveReqVO.getId(), saveReqVO.getCode());
// 校验客户存在
if (saveReqVO.getClientId() != null) {
- clientService.validateClientExists(saveReqVO.getClientId());
+ clientService.validateClientExistsAndEnable(saveReqVO.getClientId());
}
}
- private void validateNoticeCodeUnique(Long id, String noticeCode) {
- MesWmSalesNoticeDO notice = salesNoticeMapper.selectByNoticeCode(noticeCode);
+ private void validateNoticeCodeUnique(Long id, String code) {
+ MesWmSalesNoticeDO notice = salesNoticeMapper.selectByCode(code);
if (notice == null) {
return;
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnServiceImpl.java
index 59d30c691..0f9ebedf5 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/sn/MesWmSnServiceImpl.java
@@ -9,12 +9,14 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.sn.MesWmSnDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.sn.MesWmSnMapper;
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.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
+import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
@@ -30,12 +32,24 @@ public class MesWmSnServiceImpl implements MesWmSnService {
@Resource
private MesWmSnMapper snMapper;
+
@Resource
private MesMdAutoCodeRecordService autoCodeRecordService;
+ @Resource
+ private MesMdItemService itemService;
+ @Resource
+ private MesProWorkOrderService workOrderService;
@Override
@Transactional(rollbackFor = Exception.class)
public void generateSnCodes(MesWmSnGenerateReqVO reqVO) {
+ // 校验物料是否存在
+ itemService.validateItemExists(reqVO.getItemId());
+ // 校验工单是否存在
+ if (reqVO.getWorkOrderId() != null) {
+ workOrderService.validateWorkOrderExists(reqVO.getWorkOrderId());
+ }
+
List sns = new ArrayList<>(reqVO.getCount());
// 生成批次 UUID
String uuid = IdUtil.fastSimpleUUID();
@@ -66,4 +80,4 @@ public class MesWmSnServiceImpl implements MesWmSnService {
snMapper.deleteByUuid(uuid);
}
-}
+}
\ No newline at end of file
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanService.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanService.java
index 0544c85f1..e1ec1bc19 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanService.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanService.java
@@ -107,12 +107,4 @@ public interface MesWmStockTakingPlanService {
*/
PageResult getStockTakingPlanPage(MesWmStockTakingPlanPageReqVO pageReqVO);
- /**
- * 根据状态获得盘点方案列表。
- *
- * @param status 状态
- * @return 盘点方案列表
- */
- List getStockTakingPlanListByStatus(Integer status);
-
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanServiceImpl.java
index 9bb17674a..16fcf731b 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/plan/MesWmStockTakingPlanServiceImpl.java
@@ -34,6 +34,7 @@ public class MesWmStockTakingPlanServiceImpl implements MesWmStockTakingPlanServ
@Resource
private MesWmStockTakingPlanMapper stockTakingPlanMapper;
+
@Resource
private MesWmStockTakingPlanParamService stockTakingPlanParamService;
@@ -121,11 +122,6 @@ public class MesWmStockTakingPlanServiceImpl implements MesWmStockTakingPlanServ
return stockTakingPlanMapper.selectPage(pageReqVO);
}
- @Override
- public List getStockTakingPlanListByStatus(Integer status) {
- return stockTakingPlanMapper.selectListByStatus(status);
- }
-
private void validatePlanCodeUnique(Long id, String code) {
MesWmStockTakingPlanDO plan = stockTakingPlanMapper.selectByCode(code);
if (plan == null) {
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferLineServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferLineServiceImpl.java
index cd1d34c1a..f340f27f5 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferLineServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferLineServiceImpl.java
@@ -109,19 +109,17 @@ public class MesWmTransferLineServiceImpl implements MesWmTransferLineService {
// 校验父数据可编辑
transferService.validateTransferEditable(reqVO.getTransferId());
// 校验产品存在
- itemService.validateItemExists(reqVO.getItemId());
+ itemService.validateItemExistsAndEnable(reqVO.getItemId());
// 校验来源仓库、库区、库位的关联关系
warehouseAreaService.validateWarehouseAreaExists(reqVO.getFromWarehouseId(),
reqVO.getFromLocationId(), reqVO.getFromAreaId());
// 校验库存记录存在,且转移数量不超过库存数量
- if (reqVO.getMaterialStockId() != null) {
- MesWmMaterialStockDO stock = materialStockService.getMaterialStock(reqVO.getMaterialStockId());
- if (stock == null) {
- throw exception(WM_MATERIAL_STOCK_NOT_EXISTS);
- }
- if (stock.getQuantity() != null && reqVO.getQuantity().compareTo(stock.getQuantity()) > 0) {
- throw exception(WM_TRANSFER_LINE_QUANTITY_EXCEED_STOCK);
- }
+ MesWmMaterialStockDO stock = materialStockService.getMaterialStock(reqVO.getMaterialStockId());
+ if (stock == null) {
+ throw exception(WM_MATERIAL_STOCK_NOT_EXISTS);
+ }
+ if (stock.getQuantity() != null && reqVO.getQuantity().compareTo(stock.getQuantity()) > 0) {
+ throw exception(WM_TRANSFER_LINE_QUANTITY_EXCEED_STOCK);
}
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warehouse/MesWmWarehouseLocationServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warehouse/MesWmWarehouseLocationServiceImpl.java
index 321f712d7..9d5bd2a38 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warehouse/MesWmWarehouseLocationServiceImpl.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warehouse/MesWmWarehouseLocationServiceImpl.java
@@ -190,7 +190,6 @@ public class MesWmWarehouseLocationServiceImpl implements MesWmWarehouseLocation
// 2.2 自动初始化
MesWmWarehouseLocationDO newLocation = MesWmWarehouseLocationDO.builder()
.warehouseId(warehouse.getId()).code(code).name("虚拟线边库区")
- .areaStatus(cn.iocoder.yudao.framework.common.enums.CommonStatusEnum.ENABLE.getStatus())
.frozen(false).remark("系统自动初始化的虚拟线边库区")
.build();
locationMapper.insert(newLocation);
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapperTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapperTest.java
new file mode 100644
index 000000000..60c057094
--- /dev/null
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapperTest.java
@@ -0,0 +1,280 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.pro.task;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.route.MesProRouteProcessMapper;
+import jakarta.annotation.Resource;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link MesProTaskMapper#selectPage(MesProTaskPageReqVO)} 的单元测试
+ *
+ * @author 芋道源码
+ */
+public class MesProTaskMapperTest extends BaseDbUnitTest {
+
+ @Resource
+ private MesProTaskMapper taskMapper;
+
+ @Resource
+ private MesProRouteProcessMapper routeProcessMapper;
+
+ /**
+ * 创建 Task 时统一设置 BigDecimal 字段为 2 位小数,避免 H2 decimal(14,2) 精度不匹配
+ */
+ private MesProTaskDO createTaskPojo(Consumer consumer) {
+ return randomPojo(MesProTaskDO.class, o -> {
+ o.setQuantity(new BigDecimal("10.00"));
+ o.setProducedQuantity(new BigDecimal("5.00"));
+ o.setQualifyQuantity(new BigDecimal("4.00"));
+ o.setUnqualifyQuantity(new BigDecimal("1.00"));
+ o.setChangedQuantity(new BigDecimal("0.00"));
+ consumer.accept(o);
+ });
+ }
+
+ // ==================== selectPage 基础过滤 ====================
+
+ @Test
+ public void testSelectPage_byCode() {
+ MesProTaskDO matchTask = createTaskPojo(o -> {
+ o.setCode("PT202501010001");
+ o.setStatus(0);
+ });
+ taskMapper.insert(matchTask);
+ taskMapper.insert(cloneIgnoreId(matchTask, o -> o.setCode("OTHER_CODE")));
+
+ MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+ reqVO.setCode("PT2025");
+ PageResult result = taskMapper.selectPage(reqVO);
+
+ assertEquals(1, result.getTotal());
+ assertPojoEquals(matchTask, result.getList().get(0));
+ }
+
+ @Test
+ public void testSelectPage_byName() {
+ MesProTaskDO matchTask = createTaskPojo(o -> {
+ o.setName("注塑任务-A");
+ o.setStatus(0);
+ });
+ taskMapper.insert(matchTask);
+ taskMapper.insert(cloneIgnoreId(matchTask, o -> o.setName("OTHER_NAME")));
+
+ MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+ reqVO.setName("注塑");
+ PageResult result = taskMapper.selectPage(reqVO);
+
+ assertEquals(1, result.getTotal());
+ assertPojoEquals(matchTask, result.getList().get(0));
+ }
+
+ @Test
+ public void testSelectPage_byWorkOrderId() {
+ MesProTaskDO matchTask = createTaskPojo(o -> {
+ o.setWorkOrderId(100L);
+ o.setStatus(0);
+ });
+ taskMapper.insert(matchTask);
+ taskMapper.insert(cloneIgnoreId(matchTask, o -> o.setWorkOrderId(999L)));
+
+ MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+ reqVO.setWorkOrderId(100L);
+ PageResult result = taskMapper.selectPage(reqVO);
+
+ assertEquals(1, result.getTotal());
+ assertPojoEquals(matchTask, result.getList().get(0));
+ }
+
+ @Test
+ public void testSelectPage_byStatus() {
+ MesProTaskDO matchTask = createTaskPojo(o -> o.setStatus(1));
+ taskMapper.insert(matchTask);
+ taskMapper.insert(cloneIgnoreId(matchTask, o -> o.setStatus(2)));
+
+ MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+ reqVO.setStatus(1);
+ PageResult result = taskMapper.selectPage(reqVO);
+
+ assertEquals(1, result.getTotal());
+ assertPojoEquals(matchTask, result.getList().get(0));
+ }
+
+ @Test
+ public void testSelectPage_byStatuses() {
+ MesProTaskDO task1 = createTaskPojo(o -> o.setStatus(1));
+ MesProTaskDO task2 = createTaskPojo(o -> o.setStatus(2));
+ MesProTaskDO task3 = createTaskPojo(o -> o.setStatus(3));
+ taskMapper.insert(task1);
+ taskMapper.insert(task2);
+ taskMapper.insert(task3);
+
+ MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+ reqVO.setStatuses(ListUtil.of(1, 2));
+ PageResult result = taskMapper.selectPage(reqVO);
+
+ assertEquals(2, result.getTotal());
+ }
+
+ @Test
+ public void testSelectPage_noFilter() {
+ MesProTaskDO task1 = createTaskPojo(o -> o.setStatus(0));
+ MesProTaskDO task2 = createTaskPojo(o -> o.setStatus(1));
+ taskMapper.insert(task1);
+ taskMapper.insert(task2);
+
+ MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+ PageResult result = taskMapper.selectPage(reqVO);
+
+ assertEquals(2, result.getTotal());
+ }
+
+ // ==================== selectPage + checkFlag (MPJ LEFT JOIN) ====================
+
+ @Test
+ public void testSelectPage_checkFlagTrue() {
+ Long routeId = 10L;
+ Long processId1 = 100L;
+ Long processId2 = 200L;
+
+ MesProTaskDO task1 = createTaskPojo(o -> {
+ o.setRouteId(routeId);
+ o.setProcessId(processId1);
+ o.setStatus(0);
+ });
+ MesProTaskDO task2 = createTaskPojo(o -> {
+ o.setRouteId(routeId);
+ o.setProcessId(processId2);
+ o.setStatus(0);
+ });
+ taskMapper.insert(task1);
+ taskMapper.insert(task2);
+
+ routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
+ o.setRouteId(routeId);
+ o.setProcessId(processId1);
+ o.setCheckFlag(true);
+ }));
+ routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
+ o.setRouteId(routeId);
+ o.setProcessId(processId2);
+ o.setCheckFlag(false);
+ }));
+
+ // 查询:checkFlag = true,只返回 task1
+ MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+ reqVO.setCheckFlag(true);
+ PageResult result = taskMapper.selectPage(reqVO);
+
+ assertEquals(1, result.getTotal());
+ assertPojoEquals(task1, result.getList().get(0));
+ }
+
+ @Test
+ public void testSelectPage_checkFlagFalse() {
+ Long routeId = 20L;
+ Long processId1 = 300L;
+ Long processId2 = 400L;
+
+ MesProTaskDO task1 = createTaskPojo(o -> {
+ o.setRouteId(routeId);
+ o.setProcessId(processId1);
+ o.setStatus(0);
+ });
+ MesProTaskDO task2 = createTaskPojo(o -> {
+ o.setRouteId(routeId);
+ o.setProcessId(processId2);
+ o.setStatus(0);
+ });
+ taskMapper.insert(task1);
+ taskMapper.insert(task2);
+
+ routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
+ o.setRouteId(routeId);
+ o.setProcessId(processId1);
+ o.setCheckFlag(true);
+ }));
+ routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
+ o.setRouteId(routeId);
+ o.setProcessId(processId2);
+ o.setCheckFlag(false);
+ }));
+
+ // 查询:checkFlag = false,只返回 task2
+ MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+ reqVO.setCheckFlag(false);
+ PageResult result = taskMapper.selectPage(reqVO);
+
+ assertEquals(1, result.getTotal());
+ assertPojoEquals(task2, result.getList().get(0));
+ }
+
+ @Test
+ public void testSelectPage_checkFlagWithOtherFilter() {
+ Long routeId = 30L;
+ Long processId = 500L;
+
+ MesProTaskDO taskMatch = createTaskPojo(o -> {
+ o.setRouteId(routeId);
+ o.setProcessId(processId);
+ o.setCode("PT-MATCH-001");
+ o.setStatus(0);
+ });
+ MesProTaskDO taskNoMatch = createTaskPojo(o -> {
+ o.setRouteId(routeId);
+ o.setProcessId(processId);
+ o.setCode("OTHER-CODE");
+ o.setStatus(0);
+ });
+ taskMapper.insert(taskMatch);
+ taskMapper.insert(taskNoMatch);
+
+ routeProcessMapper.insert(randomPojo(MesProRouteProcessDO.class, o -> {
+ o.setRouteId(routeId);
+ o.setProcessId(processId);
+ o.setCheckFlag(true);
+ }));
+
+ // 查询:checkFlag=true 且 code like 'PT-MATCH'
+ MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+ reqVO.setCheckFlag(true);
+ reqVO.setCode("PT-MATCH");
+ PageResult result = taskMapper.selectPage(reqVO);
+
+ assertEquals(1, result.getTotal());
+ assertPojoEquals(taskMatch, result.getList().get(0));
+ }
+
+ @Test
+ public void testSelectPage_checkFlagNull_noJoin() {
+ Long routeId = 40L;
+ Long processId = 600L;
+
+ MesProTaskDO task = createTaskPojo(o -> {
+ o.setRouteId(routeId);
+ o.setProcessId(processId);
+ o.setStatus(0);
+ });
+ taskMapper.insert(task);
+
+ MesProTaskPageReqVO reqVO = new MesProTaskPageReqVO();
+ PageResult result = taskMapper.selectPage(reqVO);
+
+ assertEquals(1, result.getTotal());
+ assertPojoEquals(task, result.getList().get(0));
+ }
+
+}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesDetailMapperTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesDetailMapperTest.java
new file mode 100644
index 000000000..bd080ad66
--- /dev/null
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesDetailMapperTest.java
@@ -0,0 +1,220 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO;
+import jakarta.annotation.Resource;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * {@link MesWmProductSalesDetailMapper} 的单元测试
+ *
+ * 覆盖:selectListByLineId、selectListBySalesId、deleteByLineId、deleteBySalesId
+ */
+public class MesWmProductSalesDetailMapperTest extends BaseDbUnitTest {
+
+ @Resource
+ private MesWmProductSalesDetailMapper detailMapper;
+
+ // ==================== 辅助方法 ====================
+
+ /**
+ * 创建明细 DO,固定 BigDecimal 精度为 2 位,避免 H2 decimal(14,2) 精度不匹配
+ */
+ private MesWmProductSalesDetailDO createDetailPojo(Consumer consumer) {
+ return randomPojo(MesWmProductSalesDetailDO.class, o -> {
+ o.setQuantity(new BigDecimal("10.00"));
+ consumer.accept(o);
+ });
+ }
+
+ // ==================== selectListByLineId ====================
+
+ @Test
+ public void testSelectListByLineId_match() {
+ Long lineId = 100L;
+ MesWmProductSalesDetailDO match = createDetailPojo(o -> {
+ o.setLineId(lineId);
+ o.setSalesId(1L);
+ });
+ detailMapper.insert(match);
+ // 插入另一条不同 lineId 的记录,不应被返回
+ detailMapper.insert(cloneIgnoreId(match, o -> o.setLineId(999L)));
+
+ List result = detailMapper.selectListByLineId(lineId);
+
+ assertEquals(1, result.size());
+ assertPojoEquals(match, result.get(0));
+ }
+
+ @Test
+ public void testSelectListByLineId_multipleRows() {
+ Long lineId = 200L;
+ MesWmProductSalesDetailDO detail1 = createDetailPojo(o -> {
+ o.setLineId(lineId);
+ o.setSalesId(2L);
+ });
+ MesWmProductSalesDetailDO detail2 = createDetailPojo(o -> {
+ o.setLineId(lineId);
+ o.setSalesId(2L);
+ });
+ detailMapper.insert(detail1);
+ detailMapper.insert(detail2);
+
+ List result = detailMapper.selectListByLineId(lineId);
+
+ assertEquals(2, result.size());
+ }
+
+ @Test
+ public void testSelectListByLineId_noMatch() {
+ MesWmProductSalesDetailDO detail = createDetailPojo(o -> {
+ o.setLineId(300L);
+ o.setSalesId(3L);
+ });
+ detailMapper.insert(detail);
+
+ List result = detailMapper.selectListByLineId(999L);
+
+ assertTrue(result.isEmpty());
+ }
+
+ // ==================== selectListBySalesId ====================
+
+ @Test
+ public void testSelectListBySalesId_match() {
+ Long salesId = 10L;
+ MesWmProductSalesDetailDO match = createDetailPojo(o -> {
+ o.setSalesId(salesId);
+ o.setLineId(1L);
+ });
+ detailMapper.insert(match);
+ // 插入另一条不同 salesId 的记录
+ detailMapper.insert(cloneIgnoreId(match, o -> o.setSalesId(888L)));
+
+ List result = detailMapper.selectListBySalesId(salesId);
+
+ assertEquals(1, result.size());
+ assertPojoEquals(match, result.get(0));
+ }
+
+ @Test
+ public void testSelectListBySalesId_multipleRows() {
+ Long salesId = 20L;
+ MesWmProductSalesDetailDO detail1 = createDetailPojo(o -> {
+ o.setSalesId(salesId);
+ o.setLineId(11L);
+ });
+ MesWmProductSalesDetailDO detail2 = createDetailPojo(o -> {
+ o.setSalesId(salesId);
+ o.setLineId(12L);
+ });
+ detailMapper.insert(detail1);
+ detailMapper.insert(detail2);
+
+ List result = detailMapper.selectListBySalesId(salesId);
+
+ assertEquals(2, result.size());
+ }
+
+ @Test
+ public void testSelectListBySalesId_noMatch() {
+ MesWmProductSalesDetailDO detail = createDetailPojo(o -> {
+ o.setSalesId(30L);
+ o.setLineId(2L);
+ });
+ detailMapper.insert(detail);
+
+ List result = detailMapper.selectListBySalesId(9999L);
+
+ assertTrue(result.isEmpty());
+ }
+
+ // ==================== deleteByLineId ====================
+
+ @Test
+ public void testDeleteByLineId_deletesOnlyTargetLine() {
+ Long lineId = 400L;
+ MesWmProductSalesDetailDO toDelete = createDetailPojo(o -> {
+ o.setLineId(lineId);
+ o.setSalesId(4L);
+ });
+ MesWmProductSalesDetailDO toKeep = createDetailPojo(o -> {
+ o.setLineId(500L);
+ o.setSalesId(4L);
+ });
+ detailMapper.insert(toDelete);
+ detailMapper.insert(toKeep);
+
+ detailMapper.deleteByLineId(lineId);
+
+ // lineId=400 的记录已被逻辑删除
+ List deletedResult = detailMapper.selectListByLineId(lineId);
+ assertTrue(deletedResult.isEmpty(), "lineId=400 的记录应被删除");
+
+ // lineId=500 的记录保留
+ List keptResult = detailMapper.selectListByLineId(500L);
+ assertEquals(1, keptResult.size());
+ }
+
+ @Test
+ public void testDeleteByLineId_multipleRows() {
+ Long lineId = 600L;
+ detailMapper.insert(createDetailPojo(o -> { o.setLineId(lineId); o.setSalesId(5L); }));
+ detailMapper.insert(createDetailPojo(o -> { o.setLineId(lineId); o.setSalesId(5L); }));
+
+ detailMapper.deleteByLineId(lineId);
+
+ List result = detailMapper.selectListByLineId(lineId);
+ assertTrue(result.isEmpty(), "同 lineId 下的所有记录均应被删除");
+ }
+
+ // ==================== deleteBySalesId ====================
+
+ @Test
+ public void testDeleteBySalesId_deletesOnlyTargetSales() {
+ Long salesId = 700L;
+ MesWmProductSalesDetailDO toDelete = createDetailPojo(o -> {
+ o.setSalesId(salesId);
+ o.setLineId(6L);
+ });
+ MesWmProductSalesDetailDO toKeep = createDetailPojo(o -> {
+ o.setSalesId(800L);
+ o.setLineId(7L);
+ });
+ detailMapper.insert(toDelete);
+ detailMapper.insert(toKeep);
+
+ detailMapper.deleteBySalesId(salesId);
+
+ // salesId=700 的记录已被逻辑删除
+ List deletedResult = detailMapper.selectListBySalesId(salesId);
+ assertTrue(deletedResult.isEmpty(), "salesId=700 的记录应被删除");
+
+ // salesId=800 的记录保留
+ List keptResult = detailMapper.selectListBySalesId(800L);
+ assertEquals(1, keptResult.size());
+ }
+
+ @Test
+ public void testDeleteBySalesId_multipleRows() {
+ Long salesId = 900L;
+ detailMapper.insert(createDetailPojo(o -> { o.setSalesId(salesId); o.setLineId(8L); }));
+ detailMapper.insert(createDetailPojo(o -> { o.setSalesId(salesId); o.setLineId(9L); }));
+ detailMapper.insert(createDetailPojo(o -> { o.setSalesId(salesId); o.setLineId(10L); }));
+
+ detailMapper.deleteBySalesId(salesId);
+
+ List result = detailMapper.selectListBySalesId(salesId);
+ assertTrue(result.isEmpty(), "同 salesId 下的所有记录均应被删除");
+ }
+
+}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanServiceImplTest.java
new file mode 100644
index 000000000..9f0016e99
--- /dev/null
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanServiceImplTest.java
@@ -0,0 +1,119 @@
+package cn.iocoder.yudao.module.mes.service.cal.plan;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.MesCalPlanSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.cal.plan.MesCalPlanMapper;
+import cn.iocoder.yudao.module.mes.enums.cal.MesCalPlanStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftMethodEnum;
+import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum;
+import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamShiftService;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+
+import jakarta.annotation.Resource;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link MesCalPlanServiceImpl} 的单元测试
+ *
+ * @author 芋道源码
+ */
+@Import(MesCalPlanServiceImpl.class)
+public class MesCalPlanServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private MesCalPlanServiceImpl planService;
+
+ @Resource
+ private MesCalPlanMapper planMapper;
+
+ @MockitoBean
+ private MesCalPlanShiftService planShiftService;
+ @MockitoBean
+ private MesCalPlanTeamService planTeamService;
+ @MockitoBean
+ private MesCalTeamShiftService teamShiftService;
+
+ @Test
+ public void testCreatePlan_singleShift_addDefaultShift() {
+ // 准备参数
+ MesCalPlanSaveReqVO reqVO = new MesCalPlanSaveReqVO();
+ reqVO.setCode("PLAN-SINGLE-001");
+ reqVO.setName("单白班默认班次");
+ reqVO.setCalendarType(1);
+ reqVO.setStartDate(LocalDateTime.of(2026, 4, 1, 0, 0));
+ reqVO.setEndDate(LocalDateTime.of(2026, 4, 30, 0, 0));
+ reqVO.setShiftType(MesCalShiftTypeEnum.SINGLE.getType());
+ reqVO.setShiftMethod(MesCalShiftMethodEnum.DAY.getMethod());
+ reqVO.setShiftCount(1);
+
+ // 调用
+ Long planId = planService.createPlan(reqVO);
+
+ // 断言 1:计划状态为草稿
+ MesCalPlanDO plan = planMapper.selectById(planId);
+ assertEquals(MesCalPlanStatusEnum.PREPARE.getStatus(), plan.getStatus());
+ // 断言 2:自动生成默认班次
+ verify(planShiftService).addDefaultPlanShift(planId, MesCalShiftTypeEnum.SINGLE.getType());
+ }
+
+ @Test
+ public void testConfirmPlan_syncGenerateTeamShiftRecords() {
+ // mock 数据:插入一条草稿状态的排班计划
+ MesCalPlanDO plan = randomPojo(MesCalPlanDO.class, o -> {
+ o.setCalendarType(1);
+ o.setStartDate(LocalDateTime.of(2026, 4, 1, 0, 0));
+ o.setEndDate(LocalDateTime.of(2026, 4, 3, 0, 0));
+ o.setStatus(MesCalPlanStatusEnum.PREPARE.getStatus());
+ o.setShiftType(MesCalShiftTypeEnum.SINGLE.getType());
+ o.setShiftMethod(MesCalShiftMethodEnum.DAY.getMethod());
+ o.setShiftCount(1);
+ });
+ planMapper.insert(plan);
+ // mock 方法:班组数量满足要求
+ when(planTeamService.getPlanTeamCountByPlanId(plan.getId())).thenReturn(1L);
+
+ // 调用
+ planService.confirmPlan(plan.getId());
+
+ // 断言 1:计划状态更新为已确认
+ MesCalPlanDO updatePlan = planMapper.selectById(plan.getId());
+ assertEquals(MesCalPlanStatusEnum.CONFIRMED.getStatus(), updatePlan.getStatus());
+ // 断言 2:触发生成班组排班记录
+ verify(teamShiftService).generateTeamShiftRecords(plan.getId());
+ }
+
+ @Test
+ public void testDeletePlan_notCascadeDeleteTeamShift() {
+ // mock 数据:插入一条草稿状态的排班计划
+ MesCalPlanDO plan = randomPojo(MesCalPlanDO.class, o -> {
+ o.setCalendarType(1);
+ o.setStartDate(LocalDateTime.of(2026, 4, 1, 0, 0));
+ o.setEndDate(LocalDateTime.of(2026, 4, 3, 0, 0));
+ o.setStatus(MesCalPlanStatusEnum.PREPARE.getStatus());
+ o.setShiftType(MesCalShiftTypeEnum.SINGLE.getType());
+ o.setShiftMethod(MesCalShiftMethodEnum.DAY.getMethod());
+ o.setShiftCount(1);
+ });
+ planMapper.insert(plan);
+
+ // 调用
+ planService.deletePlan(plan.getId());
+
+ // 断言 1:计划被删除
+ assertNull(planMapper.selectById(plan.getId()));
+ // 断言 2:级联删除班次和班组关联
+ verify(planShiftService).deletePlanShiftByPlanId(plan.getId());
+ verify(planTeamService).deleteByPlanId(plan.getId());
+ // 断言 3:不级联删除班组排班记录
+ verify(teamShiftService, never()).deleteByPlanId(plan.getId());
+ }
+
+}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanShiftServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanShiftServiceImplTest.java
new file mode 100644
index 000000000..da31e70f6
--- /dev/null
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanShiftServiceImplTest.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.mes.service.cal.plan;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.cal.plan.MesCalPlanShiftMapper;
+import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+
+import jakarta.annotation.Resource;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * {@link MesCalPlanShiftServiceImpl} 的单元测试
+ *
+ * @author 芋道源码
+ */
+@Import(MesCalPlanShiftServiceImpl.class)
+public class MesCalPlanShiftServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private MesCalPlanShiftServiceImpl planShiftService;
+
+ @Resource
+ private MesCalPlanShiftMapper planShiftMapper;
+
+ @MockitoBean
+ private MesCalPlanService planService;
+
+ @Test
+ public void testAddDefaultPlanShift_singleShift_use18Clock() {
+ // 准备参数
+ Long planId = 1001L;
+
+ // 调用
+ planShiftService.addDefaultPlanShift(planId, MesCalShiftTypeEnum.SINGLE.getType());
+
+ // 断言:生成 1 条默认班次(白班 08:00~18:00)
+ List shifts = planShiftMapper.selectListByPlanId(planId);
+ assertEquals(1, shifts.size());
+ MesCalPlanShiftDO shift = shifts.get(0);
+ assertEquals(planId, shift.getPlanId());
+ assertEquals(1, shift.getSort());
+ assertEquals("白班", shift.getName());
+ assertEquals("08:00", shift.getStartTime());
+ assertEquals("18:00", shift.getEndTime());
+ }
+
+}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImplTest.java
new file mode 100644
index 000000000..9f89dd075
--- /dev/null
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImplTest.java
@@ -0,0 +1,108 @@
+package cn.iocoder.yudao.module.mes.service.cal.team;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanTeamDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamShiftDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamShiftMapper;
+import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftMethodEnum;
+import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum;
+import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanService;
+import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanShiftService;
+import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanTeamService;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+
+import jakarta.annotation.Resource;
+
+import java.time.LocalDateTime;
+import java.util.Comparator;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link MesCalTeamShiftServiceImpl} 的单元测试
+ *
+ * @author 芋道源码
+ */
+@Import(MesCalTeamShiftServiceImpl.class)
+public class MesCalTeamShiftServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private MesCalTeamShiftServiceImpl teamShiftService;
+
+ @Resource
+ private MesCalTeamShiftMapper teamShiftMapper;
+
+ @MockitoBean
+ private MesCalPlanService planService;
+ @MockitoBean
+ private MesCalPlanShiftService planShiftService;
+ @MockitoBean
+ private MesCalPlanTeamService planTeamService;
+
+ @Test
+ public void testGenerateTeamShiftRecords_threeShift_rotateThreeStates() {
+ // 准备参数
+ Long planId = 3001L;
+ // mock 方法:排班计划(三班倒、按天轮班、3 天)
+ MesCalPlanDO plan = MesCalPlanDO.builder()
+ .id(planId)
+ .code("PLAN-THREE-001")
+ .name("三班倒轮转")
+ .calendarType(1)
+ .startDate(LocalDateTime.of(2026, 4, 1, 0, 0))
+ .endDate(LocalDateTime.of(2026, 4, 3, 0, 0))
+ .shiftType(MesCalShiftTypeEnum.THREE.getType())
+ .shiftMethod(MesCalShiftMethodEnum.DAY.getMethod())
+ .shiftCount(1)
+ .build();
+ List shifts = ListUtil.of(
+ MesCalPlanShiftDO.builder().id(11L).planId(planId).sort(1).name("白班").startTime("08:00").endTime("16:00").build(),
+ MesCalPlanShiftDO.builder().id(12L).planId(planId).sort(2).name("中班").startTime("16:00").endTime("00:00").build(),
+ MesCalPlanShiftDO.builder().id(13L).planId(planId).sort(3).name("夜班").startTime("00:00").endTime("08:00").build()
+ );
+ List teams = ListUtil.of(
+ MesCalPlanTeamDO.builder().id(21L).planId(planId).teamId(101L).build(),
+ MesCalPlanTeamDO.builder().id(22L).planId(planId).teamId(102L).build(),
+ MesCalPlanTeamDO.builder().id(23L).planId(planId).teamId(103L).build()
+ );
+ when(planService.getPlan(planId)).thenReturn(plan);
+ when(planShiftService.getPlanShiftListByPlanId(planId)).thenReturn(shifts);
+ when(planTeamService.getPlanTeamListByPlanId(planId)).thenReturn(teams);
+
+ // 调用
+ teamShiftService.generateTeamShiftRecords(planId);
+
+ // 断言:3 天 × 3 班次 = 9 条排班记录,班组按天轮转
+ List records = teamShiftMapper.selectListByPlanId(planId);
+ records.sort(Comparator.comparing(MesCalTeamShiftDO::getDay).thenComparing(MesCalTeamShiftDO::getSort));
+ assertEquals(9, records.size());
+ // 断言:第 1 天(shiftIndex=0):A 白班、B 中班、C 夜班
+ assertRecord(records.get(0), LocalDateTime.of(2026, 4, 1, 0, 0), 1, 101L, 11L);
+ assertRecord(records.get(1), LocalDateTime.of(2026, 4, 1, 0, 0), 2, 102L, 12L);
+ assertRecord(records.get(2), LocalDateTime.of(2026, 4, 1, 0, 0), 3, 103L, 13L);
+ // 断言:第 2 天(shiftIndex=1):C 白班、A 中班、B 夜班
+ assertRecord(records.get(3), LocalDateTime.of(2026, 4, 2, 0, 0), 1, 103L, 11L);
+ assertRecord(records.get(4), LocalDateTime.of(2026, 4, 2, 0, 0), 2, 101L, 12L);
+ assertRecord(records.get(5), LocalDateTime.of(2026, 4, 2, 0, 0), 3, 102L, 13L);
+ // 断言:第 3 天(shiftIndex=2):B 白班、C 中班、A 夜班
+ assertRecord(records.get(6), LocalDateTime.of(2026, 4, 3, 0, 0), 1, 102L, 11L);
+ assertRecord(records.get(7), LocalDateTime.of(2026, 4, 3, 0, 0), 2, 103L, 12L);
+ assertRecord(records.get(8), LocalDateTime.of(2026, 4, 3, 0, 0), 3, 101L, 13L);
+ }
+
+ private static void assertRecord(MesCalTeamShiftDO actual, LocalDateTime day, Integer sort,
+ Long teamId, Long shiftId) {
+ assertEquals(day, actual.getDay());
+ assertEquals(sort, actual.getSort());
+ assertEquals(teamId, actual.getTeamId());
+ assertEquals(shiftId, actual.getShiftId());
+ }
+
+}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java
index 89ba3aa05..ee01f7da2 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java
@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.mes.service.pro.feedback;
import cn.hutool.core.collection.ListUtil;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
@@ -23,8 +25,7 @@ import java.math.BigDecimal;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
@@ -61,6 +62,7 @@ public class MesProFeedbackServiceImplTest extends BaseDbUnitTest {
// 准备数据:插入一条待检验状态的报工单
Long taskId = randomLongId();
Long workOrderId = randomLongId();
+ Long sourceLineId = randomLongId();
MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
o.setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus());
o.setTaskId(taskId);
@@ -87,13 +89,12 @@ public class MesProFeedbackServiceImplTest extends BaseDbUnitTest {
BigDecimal laborScrapQty = BigDecimal.valueOf(5);
BigDecimal materialScrapQty = BigDecimal.valueOf(10);
BigDecimal otherScrapQty = BigDecimal.valueOf(5);
-
- feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(),
+ feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(), sourceLineId,
qualifiedQty, unqualifiedQty, laborScrapQty, materialScrapQty, otherScrapQty);
// 断言 1:调用了 splitPendingAndFinishProduce
verify(productProduceService).splitPendingAndFinishProduce(
- eq(feedback.getId()), eq(qualifiedQty), eq(unqualifiedQty));
+ eq(feedback.getId()), eq(sourceLineId), eq(qualifiedQty), eq(unqualifiedQty));
// 断言 2:报工单状态更新为已完成
MesProFeedbackDO updatedFeedback = feedbackMapper.selectById(feedback.getId());
@@ -137,7 +138,7 @@ public class MesProFeedbackServiceImplTest extends BaseDbUnitTest {
.thenReturn(ListUtil.of(qualifiedLine));
// 调用
- feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(),
+ feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(), randomLongId(),
BigDecimal.valueOf(50), BigDecimal.ZERO,
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO);
@@ -156,7 +157,7 @@ public class MesProFeedbackServiceImplTest extends BaseDbUnitTest {
// 调用不存在的 feedbackId,应该抛异常
Long feedbackId = randomLongId();
assertThrows(Exception.class, () ->
- feedbackService.updateProFeedbackWhenIpqcFinish(feedbackId,
+ feedbackService.updateProFeedbackWhenIpqcFinish(feedbackId, randomLongId(),
BigDecimal.TEN, BigDecimal.ZERO,
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO));
}
@@ -171,12 +172,12 @@ public class MesProFeedbackServiceImplTest extends BaseDbUnitTest {
// 调用,应该抛异常
assertThrows(Exception.class, () ->
- feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(),
+ feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(), randomLongId(),
BigDecimal.TEN, BigDecimal.ZERO,
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO));
// 断言:不应该调用任何产出单方法
- verify(productProduceService, never()).splitPendingAndFinishProduce(anyLong(), any(), any());
+ verify(productProduceService, never()).splitPendingAndFinishProduce(anyLong(), anyLong(), any(), any());
}
@Test
@@ -189,9 +190,208 @@ public class MesProFeedbackServiceImplTest extends BaseDbUnitTest {
// 调用,应该抛异常
assertThrows(Exception.class, () ->
- feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(),
+ feedbackService.updateProFeedbackWhenIpqcFinish(feedback.getId(), randomLongId(),
BigDecimal.TEN, BigDecimal.ZERO,
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO));
}
+ // ==================== approveFeedback 测试 ====================
+
+ @Test
+ public void testApproveFeedback_keyNonCheck_success() {
+ // 准备数据:关键非质检工序,常规审批
+ Long routeId = randomLongId();
+ Long processId = randomLongId();
+ MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
+ o.setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus());
+ o.setFeedbackQuantity(BigDecimal.valueOf(100));
+ o.setQualifiedQuantity(BigDecimal.valueOf(80));
+ o.setUnqualifiedQuantity(BigDecimal.valueOf(20));
+ o.setUncheckQuantity(BigDecimal.ZERO);
+ o.setRouteId(routeId);
+ o.setProcessId(processId);
+ });
+ feedbackMapper.insert(feedback);
+
+ // mock: 工序配置 key=true, check=false
+ MesProRouteProcessDO routeProcess = MesProRouteProcessDO.builder()
+ .routeId(routeId).processId(processId)
+ .keyFlag(true).checkFlag(false).build();
+ when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
+ .thenReturn(routeProcess);
+
+ // mock: 产品产出单
+ MesWmProductProduceDO produce = randomPojo(MesWmProductProduceDO.class);
+ when(productProduceService.generateProductProduce(any(), eq(false))).thenReturn(produce);
+
+ // mock: 产出行(用于 updateTaskAndWorkOrderByFeedback)
+ MesWmProductProduceLineDO qualifiedLine = MesWmProductProduceLineDO.builder()
+ .quantity(BigDecimal.valueOf(80))
+ .qualityStatus(MesWmQualityStatusEnum.PASS.getStatus()).build();
+ when(produceLineService.getProductProduceLineListByFeedbackId(feedback.getId()))
+ .thenReturn(ListUtil.of(qualifiedLine));
+
+ // 调用
+ boolean result = feedbackService.approveFeedback(feedback.getId());
+
+ // 断言 1:返回 true(已完成)
+ assertTrue(result);
+
+ // 断言 2:状态为已完成,uncheckQuantity 清零
+ MesProFeedbackDO updated = feedbackMapper.selectById(feedback.getId());
+ assertEquals(MesProFeedbackStatusEnum.FINISHED.getStatus(), updated.getStatus());
+ assertEquals(0, BigDecimal.ZERO.compareTo(updated.getUncheckQuantity()));
+
+ // 断言 3:调用了产出单生成 + 入库 + 任务/工单更新
+ verify(productProduceService).generateProductProduce(any(), eq(false));
+ verify(productProduceService).finishProductProduce(produce.getId());
+ verify(taskService).updateProducedQuantity(eq(feedback.getTaskId()),
+ any(BigDecimal.class), any(BigDecimal.class), any(BigDecimal.class));
+ verify(workOrderService).updateProducedQuantity(eq(feedback.getWorkOrderId()),
+ any(BigDecimal.class));
+ }
+
+ @Test
+ public void testApproveFeedback_keyCheck_enterUncheck() {
+ // 准备数据:关键质检工序,应进入待检验
+ Long routeId = randomLongId();
+ Long processId = randomLongId();
+ MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
+ o.setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus());
+ o.setFeedbackQuantity(BigDecimal.valueOf(50));
+ o.setUncheckQuantity(BigDecimal.valueOf(50)); // 质检工序 uncheckQuantity > 0
+ o.setRouteId(routeId);
+ o.setProcessId(processId);
+ });
+ feedbackMapper.insert(feedback);
+
+ // mock: key=true, check=true
+ MesProRouteProcessDO routeProcess = MesProRouteProcessDO.builder()
+ .routeId(routeId).processId(processId)
+ .keyFlag(true).checkFlag(true).build();
+ when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
+ .thenReturn(routeProcess);
+
+ // 调用
+ boolean result = feedbackService.approveFeedback(feedback.getId());
+
+ // 断言 1:返回 false(待检验)
+ assertFalse(result);
+
+ // 断言 2:状态为待检验
+ MesProFeedbackDO updated = feedbackMapper.selectById(feedback.getId());
+ assertEquals(MesProFeedbackStatusEnum.UNCHECK.getStatus(), updated.getStatus());
+
+ // 断言 3:生成了待检产出单,但没有 finishProductProduce
+ verify(productProduceService).generateProductProduce(any(), eq(true));
+ verify(productProduceService, never()).finishProductProduce(anyLong());
+
+ // 断言 4:没有更新任务/工单数量(等 IPQC 回调)
+ verify(taskService, never()).updateProducedQuantity(anyLong(), any(), any(), any());
+ verify(workOrderService, never()).updateProducedQuantity(anyLong(), any());
+ }
+
+ @Test
+ public void testApproveFeedback_nonKey_directFinish() {
+ // 准备数据:非关键工序,直接完结
+ Long routeId = randomLongId();
+ Long processId = randomLongId();
+ MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
+ o.setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus());
+ o.setFeedbackQuantity(BigDecimal.valueOf(30));
+ o.setUncheckQuantity(BigDecimal.ZERO);
+ o.setRouteId(routeId);
+ o.setProcessId(processId);
+ });
+ feedbackMapper.insert(feedback);
+
+ // mock: key=false, check=false
+ MesProRouteProcessDO routeProcess = MesProRouteProcessDO.builder()
+ .routeId(routeId).processId(processId)
+ .keyFlag(false).checkFlag(false).build();
+ when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
+ .thenReturn(routeProcess);
+
+ // 调用
+ boolean result = feedbackService.approveFeedback(feedback.getId());
+
+ // 断言 1:返回 true(已完成)
+ assertTrue(result);
+
+ // 断言 2:状态为已完成
+ MesProFeedbackDO updated = feedbackMapper.selectById(feedback.getId());
+ assertEquals(MesProFeedbackStatusEnum.FINISHED.getStatus(), updated.getStatus());
+
+ // 断言 3:不生成产出单,不更新任务/工单
+ verify(productProduceService, never()).generateProductProduce(any(), anyBoolean());
+ verify(taskService, never()).updateProducedQuantity(anyLong(), any(), any(), any());
+ }
+
+ @Test
+ public void testApproveFeedback_nonCheck_uncheckQuantityReject() {
+ // 准备数据:非质检工序,但 uncheckQuantity > 0(异常数据),应被拦截
+ Long routeId = randomLongId();
+ Long processId = randomLongId();
+ MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
+ o.setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus());
+ o.setFeedbackQuantity(BigDecimal.valueOf(10));
+ o.setUncheckQuantity(BigDecimal.valueOf(10)); // 非质检工序不应有待检数量
+ o.setRouteId(routeId);
+ o.setProcessId(processId);
+ });
+ feedbackMapper.insert(feedback);
+
+ // mock: key=true, check=false(非质检)
+ MesProRouteProcessDO routeProcess = MesProRouteProcessDO.builder()
+ .routeId(routeId).processId(processId)
+ .keyFlag(true).checkFlag(false).build();
+ when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
+ .thenReturn(routeProcess);
+
+ // 调用,应该抛异常
+ assertThrows(Exception.class, () ->
+ feedbackService.approveFeedback(feedback.getId()));
+
+ // 断言:不应该执行任何后续操作
+ verify(productProduceService, never()).generateProductProduce(any(), anyBoolean());
+ verify(itemConsumeService, never()).generateItemConsume(any());
+ }
+
+ @Test
+ public void testApproveFeedback_nonKeyCheck_directFinishAndCleanUncheck() {
+ // 准备数据:非关键 + 质检工序(!key+check),uncheckQuantity > 0
+ // 应放行(checkFlag=true 不拦截 uncheckQuantity),直接完结并清零 uncheckQuantity
+ Long routeId = randomLongId();
+ Long processId = randomLongId();
+ MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
+ o.setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus());
+ o.setFeedbackQuantity(BigDecimal.valueOf(20));
+ o.setUncheckQuantity(BigDecimal.valueOf(20));
+ o.setRouteId(routeId);
+ o.setProcessId(processId);
+ });
+ feedbackMapper.insert(feedback);
+
+ // mock: key=false, check=true
+ MesProRouteProcessDO routeProcess = MesProRouteProcessDO.builder()
+ .routeId(routeId).processId(processId)
+ .keyFlag(false).checkFlag(true).build();
+ when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
+ .thenReturn(routeProcess);
+
+ // 调用
+ boolean result = feedbackService.approveFeedback(feedback.getId());
+
+ // 断言 1:返回 true(直接完成,不走 UNCHECK)
+ assertTrue(result);
+
+ // 断言 2:状态为已完成,uncheckQuantity 被清零
+ MesProFeedbackDO updated = feedbackMapper.selectById(feedback.getId());
+ assertEquals(MesProFeedbackStatusEnum.FINISHED.getStatus(), updated.getStatus());
+ assertEquals(0, BigDecimal.ZERO.compareTo(updated.getUncheckQuantity()));
+
+ // 断言 3:非关键工序不生成产出单
+ verify(productProduceService, never()).generateProductProduce(any(), anyBoolean());
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/indicatorresult/MesQcIndicatorResultServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/indicatorresult/MesQcIndicatorResultServiceImplTest.java
new file mode 100644
index 000000000..008a90d0a
--- /dev/null
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/indicatorresult/MesQcIndicatorResultServiceImplTest.java
@@ -0,0 +1,288 @@
+package cn.iocoder.yudao.module.mes.service.qc.indicatorresult;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.indicatorresult.vo.MesQcIndicatorResultPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.indicatorresult.vo.MesQcIndicatorResultSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.qc.indicatorresult.MesQcIndicatorResultMapper;
+import cn.iocoder.yudao.module.mes.enums.qc.MesQcResultValueTypeEnum;
+import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
+import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
+import cn.iocoder.yudao.module.mes.service.qc.ipqc.MesQcIpqcService;
+import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
+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.system.api.dict.DictDataApi;
+import jakarta.annotation.Resource;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link MesQcIndicatorResultServiceImpl} 的单元测试
+ *
+ * @author 芋道源码
+ */
+@Import(MesQcIndicatorResultServiceImpl.class)
+public class MesQcIndicatorResultServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private MesQcIndicatorResultServiceImpl indicatorResultService;
+
+ @Resource
+ private MesQcIndicatorResultMapper resultMapper;
+
+ @MockitoBean
+ private MesQcIndicatorResultDetailService resultDetailService;
+ @MockitoBean
+ private MesQcIndicatorService indicatorService;
+ @MockitoBean
+ private MesQcIqcService iqcService;
+ @MockitoBean
+ private MesQcIpqcService ipqcService;
+ @MockitoBean
+ private MesQcOqcService oqcService;
+ @MockitoBean
+ private MesQcRqcService rqcService;
+ @MockitoBean
+ private DictDataApi dictDataApi;
+
+ @Test
+ public void testCreateIndicatorResult_success() {
+ // 准备参数
+ MesQcIndicatorResultSaveReqVO reqVO = buildReqVO("plain text value", MesQcResultValueTypeEnum.TEXT.getType());
+ // mock 方法
+ mockIqcAndIndicator(reqVO, MesQcResultValueTypeEnum.TEXT.getType());
+
+ // 调用
+ Long resultId = indicatorResultService.createIndicatorResult(reqVO);
+
+ // 断言
+ assertNotNull(resultId);
+ MesQcIndicatorResultDO dbResult = resultMapper.selectById(resultId);
+ assertNotNull(dbResult);
+ assertEquals(reqVO.getCode(), dbResult.getCode());
+ assertEquals(reqVO.getQcId(), dbResult.getQcId());
+ assertEquals(reqVO.getQcType(), dbResult.getQcType());
+ assertEquals(100L, dbResult.getItemId()); // 从 mock IQC 获取
+ verify(resultDetailService).createDetailList(anyList());
+ }
+
+ @Test
+ public void testCreateIndicatorResult_fileValueInvalid() {
+ // 准备参数
+ MesQcIndicatorResultSaveReqVO reqVO = buildReqVO("not-a-url", MesQcResultValueTypeEnum.FILE.getType());
+ // mock 方法
+ mockIqcAndIndicator(reqVO, MesQcResultValueTypeEnum.FILE.getType());
+
+ // 调用,并断言异常
+ assertServiceException(() -> indicatorResultService.createIndicatorResult(reqVO),
+ QC_RESULT_VALUE_FORMAT_INVALID, "检测项[检测项A]要求文件 URL,实际值=not-a-url");
+
+ // 断言:未入库
+ assertEquals(0, resultMapper.selectCount());
+ verify(resultDetailService, never()).createDetailList(anyList());
+ }
+
+ @Test
+ public void testUpdateIndicatorResult_success() {
+ // mock 数据
+ MesQcIndicatorResultDO dbResult = randomPojo(MesQcIndicatorResultDO.class, o -> {
+ o.setQcType(MesQcTypeEnum.IQC.getType());
+ });
+ resultMapper.insert(dbResult);
+ // 准备参数
+ MesQcIndicatorResultSaveReqVO reqVO = buildReqVO("updated text", MesQcResultValueTypeEnum.TEXT.getType());
+ reqVO.setId(dbResult.getId());
+ // mock 方法
+ MesQcIndicatorDO indicator = new MesQcIndicatorDO();
+ indicator.setId(reqVO.getItems().get(0).getIndicatorId());
+ indicator.setName("检测项A");
+ indicator.setResultType(MesQcResultValueTypeEnum.TEXT.getType());
+ when(indicatorService.validateIndicatorListExists(anySet()))
+ .thenReturn(MapUtil.of(indicator.getId(), indicator));
+
+ // 调用
+ indicatorResultService.updateIndicatorResult(reqVO);
+
+ // 断言
+ MesQcIndicatorResultDO updatedResult = resultMapper.selectById(dbResult.getId());
+ assertEquals(reqVO.getCode(), updatedResult.getCode());
+ // qcId/qcType/itemId 不允许改挂,应保持原值
+ assertEquals(dbResult.getQcId(), updatedResult.getQcId());
+ assertEquals(dbResult.getQcType(), updatedResult.getQcType());
+ assertEquals(dbResult.getItemId(), updatedResult.getItemId());
+ verify(resultDetailService).createOrUpdateDetailList(anyList());
+ }
+
+ @Test
+ public void testUpdateIndicatorResult_notExists() {
+ // 准备参数
+ MesQcIndicatorResultSaveReqVO reqVO = buildReqVO("text", MesQcResultValueTypeEnum.TEXT.getType());
+ reqVO.setId(randomLongId());
+
+ // 调用,并断言异常
+ assertServiceException(() -> indicatorResultService.updateIndicatorResult(reqVO),
+ QC_RESULT_NOT_EXISTS);
+ }
+
+ @Test
+ public void testDeleteIndicatorResult_success() {
+ // mock 数据
+ MesQcIndicatorResultDO dbResult = randomPojo(MesQcIndicatorResultDO.class);
+ resultMapper.insert(dbResult);
+
+ // 调用
+ indicatorResultService.deleteIndicatorResult(dbResult.getId());
+
+ // 断言
+ assertNull(resultMapper.selectById(dbResult.getId()));
+ verify(resultDetailService).deleteDetailByResultId(dbResult.getId());
+ }
+
+ @Test
+ public void testDeleteIndicatorResult_notExists() {
+ // 调用,并断言异常
+ assertServiceException(() -> indicatorResultService.deleteIndicatorResult(randomLongId()),
+ QC_RESULT_NOT_EXISTS);
+ }
+
+ @Test
+ public void testGetIndicatorResult() {
+ // mock 数据
+ MesQcIndicatorResultDO dbResult = randomPojo(MesQcIndicatorResultDO.class);
+ resultMapper.insert(dbResult);
+
+ // 调用
+ MesQcIndicatorResultDO result = indicatorResultService.getIndicatorResult(dbResult.getId());
+
+ // 断言
+ assertPojoEquals(dbResult, result);
+ }
+
+ @Test
+ public void testGetIndicatorResultPage() {
+ // mock 数据
+ MesQcIndicatorResultDO dbResult = randomPojo(MesQcIndicatorResultDO.class, o -> {
+ o.setQcId(1L);
+ o.setQcType(MesQcTypeEnum.IQC.getType());
+ o.setCode("SPL-001");
+ o.setItemId(100L);
+ });
+ resultMapper.insert(dbResult);
+ // 测试 qcId 不匹配
+ resultMapper.insert(cloneIgnoreId(dbResult, o -> o.setQcId(2L)));
+ // 测试 qcType 不匹配
+ resultMapper.insert(cloneIgnoreId(dbResult, o -> o.setQcType(MesQcTypeEnum.IPQC.getType())));
+ // 测试 code 不匹配
+ resultMapper.insert(cloneIgnoreId(dbResult, o -> o.setCode("SPL-999")));
+ // 测试 itemId 不匹配
+ resultMapper.insert(cloneIgnoreId(dbResult, o -> o.setItemId(999L)));
+ // 准备参数
+ MesQcIndicatorResultPageReqVO pageReqVO = new MesQcIndicatorResultPageReqVO();
+ pageReqVO.setQcId(1L);
+ pageReqVO.setQcType(MesQcTypeEnum.IQC.getType());
+ pageReqVO.setCode("SPL-001");
+ pageReqVO.setItemId(100L);
+
+ // 调用
+ PageResult pageResult = indicatorResultService.getIndicatorResultPage(pageReqVO);
+
+ // 断言
+ assertEquals(1, pageResult.getTotal());
+ assertEquals(1, pageResult.getList().size());
+ assertPojoEquals(dbResult, pageResult.getList().get(0));
+ }
+
+ @Test
+ public void testGetIndicatorResultCountByQcIdAndType() {
+ // mock 数据
+ MesQcIndicatorResultDO result1 = randomPojo(MesQcIndicatorResultDO.class, o -> {
+ o.setQcId(1L);
+ o.setQcType(MesQcTypeEnum.IQC.getType());
+ });
+ resultMapper.insert(result1);
+ MesQcIndicatorResultDO result2 = randomPojo(MesQcIndicatorResultDO.class, o -> {
+ o.setQcId(1L);
+ o.setQcType(MesQcTypeEnum.IQC.getType());
+ });
+ resultMapper.insert(result2);
+ // 不匹配
+ resultMapper.insert(randomPojo(MesQcIndicatorResultDO.class, o -> {
+ o.setQcId(2L);
+ o.setQcType(MesQcTypeEnum.IPQC.getType());
+ }));
+
+ // 调用
+ Long count = indicatorResultService.getIndicatorResultCountByQcIdAndType(1L, MesQcTypeEnum.IQC.getType());
+
+ // 断言
+ assertEquals(2L, count);
+ }
+
+ @Test
+ public void testValidateIndicatorResultExistsByQcIdAndType_success() {
+ // mock 数据
+ resultMapper.insert(randomPojo(MesQcIndicatorResultDO.class, o -> {
+ o.setQcId(1L);
+ o.setQcType(MesQcTypeEnum.IQC.getType());
+ }));
+
+ // 调用(不抛异常即通过)
+ indicatorResultService.validateIndicatorResultExistsByQcIdAndType(1L, MesQcTypeEnum.IQC.getType());
+ }
+
+ @Test
+ public void testValidateIndicatorResultExistsByQcIdAndType_notExists() {
+ // 调用,并断言异常
+ assertServiceException(
+ () -> indicatorResultService.validateIndicatorResultExistsByQcIdAndType(randomLongId(), MesQcTypeEnum.IQC.getType()),
+ QC_FINISH_INDICATOR_RESULT_REQUIRED);
+ }
+
+ // ==================== 辅助方法 ====================
+
+ private void mockIqcAndIndicator(MesQcIndicatorResultSaveReqVO reqVO, Integer resultType) {
+ MesQcIqcDO iqc = new MesQcIqcDO();
+ iqc.setId(reqVO.getQcId());
+ iqc.setItemId(100L);
+ when(iqcService.validateIqcExists(reqVO.getQcId())).thenReturn(iqc);
+
+ MesQcIndicatorDO indicator = new MesQcIndicatorDO();
+ indicator.setId(reqVO.getItems().get(0).getIndicatorId());
+ indicator.setName("检测项A");
+ indicator.setResultType(resultType);
+ when(indicatorService.validateIndicatorListExists(anySet()))
+ .thenReturn(MapUtil.of(indicator.getId(), indicator));
+ }
+
+ private MesQcIndicatorResultSaveReqVO buildReqVO(String value, Integer resultType) {
+ MesQcIndicatorResultSaveReqVO.Item item = new MesQcIndicatorResultSaveReqVO.Item();
+ item.setIndicatorId(10L);
+ item.setValue(value);
+
+ MesQcIndicatorResultSaveReqVO reqVO = new MesQcIndicatorResultSaveReqVO();
+ reqVO.setCode("SPL-001");
+ reqVO.setQcId(1L);
+ reqVO.setQcType(MesQcTypeEnum.IQC.getType());
+ reqVO.setItems(ListUtil.of(item));
+ return reqVO;
+ }
+
+}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImplTest.java
index b71081fef..9d653aeec 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImplTest.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImplTest.java
@@ -1,16 +1,32 @@
package cn.iocoder.yudao.module.mes.service.qc.ipqc;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService;
+import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
+import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
+import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
+import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
+import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
@@ -21,6 +37,7 @@ import java.math.BigDecimal;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
@@ -54,11 +71,22 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
private AdminUserApi adminUserApi;
@MockBean
private MesProFeedbackService feedbackService;
+ @MockitoBean
+ private MesProRouteProductService routeProductService;
+ @MockitoBean
+ private MesProRouteProcessService routeProcessService;
+ @MockitoBean
+ private MesProTaskService taskService;
+ @MockitoBean
+ private MesQcIndicatorResultService indicatorResultService;
+ @MockitoBean
+ private MesWmProductProduceLineService productProduceLineService;
@Test
public void testFinishIpqc_writeBack_feedback() {
// 准备数据:插入一条草稿状态的 IPQC 单,来源为报工单(PRO_FEEDBACK=304)
Long sourceDocId = randomLongId();
+ Long sourceLineId = randomLongId();
BigDecimal qualifiedQty = BigDecimal.valueOf(80);
BigDecimal unqualifiedQty = BigDecimal.valueOf(20);
BigDecimal laborScrapQty = BigDecimal.valueOf(5);
@@ -70,7 +98,7 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
o.setCheckResult(1); // 非空,满足 finishIpqc 的校验
o.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
o.setSourceDocId(sourceDocId);
- o.setSourceLineId(null); // 报工场景下预留不使用
+ o.setSourceLineId(sourceLineId);
o.setQualifiedQuantity(qualifiedQty);
o.setUnqualifiedQuantity(unqualifiedQty);
o.setLaborScrapQuantity(laborScrapQty);
@@ -86,10 +114,11 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
MesQcIpqcDO updatedIpqc = ipqcMapper.selectById(ipqc.getId());
assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIpqc.getStatus());
- // 断言 2:调用了 feedbackService.completeFeedbackFromIpqc,传递正确参数
+ // 断言 2:调用了 feedbackService,传递 sourceLineId
// 注意:数量经过 DB 存取后 scale 可能变化(例如 80 → 80.00),所以用 any() 匹配
verify(feedbackService).updateProFeedbackWhenIpqcFinish(
eq(sourceDocId),
+ eq(sourceLineId),
any(BigDecimal.class),
any(BigDecimal.class),
any(BigDecimal.class),
@@ -101,12 +130,13 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
public void testFinishIpqc_writeBack_feedback_withNullQuantities() {
// 准备数据:合格品/不合格品/废品数量为 null 的场景,应该 defaultIfNull 为 0
Long sourceDocId = randomLongId();
+ Long sourceLineId = randomLongId();
MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> {
o.setStatus(MesQcStatusEnum.DRAFT.getStatus());
o.setCheckResult(1);
o.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
o.setSourceDocId(sourceDocId);
- o.setSourceLineId(null);
+ o.setSourceLineId(sourceLineId);
o.setQualifiedQuantity(null);
o.setUnqualifiedQuantity(null);
o.setLaborScrapQuantity(null);
@@ -121,6 +151,7 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
// 断言:所有数量参数都是 BigDecimal.ZERO(defaultIfNull 处理)
verify(feedbackService).updateProFeedbackWhenIpqcFinish(
eq(sourceDocId),
+ eq(sourceLineId),
eq(BigDecimal.ZERO),
eq(BigDecimal.ZERO),
eq(BigDecimal.ZERO),
@@ -149,7 +180,7 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
// 断言 2:不应该调用 feedbackService
verify(feedbackService, never()).updateProFeedbackWhenIpqcFinish(
- anyLong(), any(), any(), any(), any(), any());
+ anyLong(), anyLong(), any(), any(), any(), any(), any());
}
@Test
@@ -211,4 +242,355 @@ public class MesQcIpqcServiceImplTest extends BaseDbUnitTest {
assertThrows(Exception.class, () -> ipqcService.finishIpqc(ipqc.getId()));
}
+ // ==================== processId 推导行为测试 ====================
+
+ /**
+ * 工位有工序 + 该工序在产品工艺路线中 → processId 应该被设置为工位工序
+ */
+ @Test
+ public void testCreateIpqc_processId_workstationHasProcess() {
+ // 准备参数
+ Long workstationId = randomLongId();
+ Long workOrderId = randomLongId();
+ Long productId = randomLongId();
+ Long processId = randomLongId();
+ Long routeId = randomLongId();
+ Long templateId = randomLongId();
+
+ MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
+ reqVO.setCode("IPQC-TEST-001");
+ reqVO.setWorkstationId(workstationId);
+ reqVO.setWorkOrderId(workOrderId);
+ reqVO.setInspectorUserId(randomLongId());
+
+ // mock 工位返回有工序
+ MesMdWorkstationDO workstation = new MesMdWorkstationDO();
+ workstation.setId(workstationId);
+ workstation.setProcessId(processId);
+ when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
+ // mock 工单
+ MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
+ workOrder.setId(workOrderId);
+ workOrder.setProductId(productId);
+ when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
+ when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
+ // mock 模板匹配
+ MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
+ templateItem.setTemplateId(templateId);
+ when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
+ // mock 工艺路线
+ MesProRouteProductDO routeProduct = new MesProRouteProductDO();
+ routeProduct.setRouteId(routeId);
+ when(routeProductService.getRouteProductByItemId(productId)).thenReturn(routeProduct);
+ // mock 工序在路线中
+ when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
+ .thenReturn(new MesProRouteProcessDO());
+
+ // 调用
+ Long ipqcId = ipqcService.createIpqc(reqVO);
+
+ // 断言:processId 应该是工位工序
+ MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
+ assertEquals(processId, ipqc.getProcessId());
+ }
+
+ /**
+ * 工位没有工序(processId=null)→ processId 应该留 null(不回退到任务工序)
+ */
+ @Test
+ public void testCreateIpqc_processId_workstationNoProcess_shouldBeNull() {
+ // 准备参数
+ Long workstationId = randomLongId();
+ Long workOrderId = randomLongId();
+ Long productId = randomLongId();
+ Long taskId = randomLongId();
+ Long templateId = randomLongId();
+
+ MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
+ reqVO.setCode("IPQC-TEST-002");
+ reqVO.setWorkstationId(workstationId);
+ reqVO.setWorkOrderId(workOrderId);
+ reqVO.setTaskId(taskId); // 故意传入 taskId,验证不会回退
+ reqVO.setInspectorUserId(randomLongId());
+
+ // mock 工位没有工序
+ MesMdWorkstationDO workstation = new MesMdWorkstationDO();
+ workstation.setId(workstationId);
+ workstation.setProcessId(null); // 无工序
+ when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
+ // mock 工单
+ MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
+ workOrder.setId(workOrderId);
+ workOrder.setProductId(productId);
+ when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
+ when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
+ // mock 任务
+ MesProTaskDO task = new MesProTaskDO();
+ task.setId(taskId);
+ task.setWorkOrderId(workOrderId);
+ task.setWorkstationId(workstationId);
+ task.setItemId(productId);
+ when(taskService.validateTaskNotFinished(taskId)).thenReturn(task);
+ // mock 模板匹配
+ MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
+ templateItem.setTemplateId(templateId);
+ when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
+
+ // 调用
+ Long ipqcId = ipqcService.createIpqc(reqVO);
+
+ // 断言:processId 应该是 null,即使传入了 taskId 也不应该回退
+ MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
+ assertNull(ipqc.getProcessId(),
+ "工位无工序时 processId 应为 null,不应回退到任务工序");
+ // 断言:不应该读取任务的 processId
+ verify(routeProductService, never()).getRouteProductByItemId(any());
+ }
+
+ /**
+ * 工位有工序但该工序不在产品工艺路线中 → processId 应该留 null
+ */
+ @Test
+ public void testCreateIpqc_processId_processNotInRoute() {
+ // 准备参数
+ Long workstationId = randomLongId();
+ Long workOrderId = randomLongId();
+ Long productId = randomLongId();
+ Long processId = randomLongId();
+ Long routeId = randomLongId();
+ Long templateId = randomLongId();
+
+ MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
+ reqVO.setCode("IPQC-TEST-003");
+ reqVO.setWorkstationId(workstationId);
+ reqVO.setWorkOrderId(workOrderId);
+ reqVO.setInspectorUserId(randomLongId());
+
+ // mock 工位有工序
+ MesMdWorkstationDO workstation = new MesMdWorkstationDO();
+ workstation.setId(workstationId);
+ workstation.setProcessId(processId);
+ when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
+ // mock 工单
+ MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
+ workOrder.setId(workOrderId);
+ workOrder.setProductId(productId);
+ when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
+ when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
+ // mock 模板匹配
+ MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
+ templateItem.setTemplateId(templateId);
+ when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
+ // mock 工艺路线存在
+ MesProRouteProductDO routeProduct = new MesProRouteProductDO();
+ routeProduct.setRouteId(routeId);
+ when(routeProductService.getRouteProductByItemId(productId)).thenReturn(routeProduct);
+ // mock 工序不在路线中
+ when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, processId))
+ .thenReturn(null);
+
+ // 调用
+ Long ipqcId = ipqcService.createIpqc(reqVO);
+
+ // 断言:processId 应该是 null
+ MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
+ assertNull(ipqc.getProcessId());
+ }
+
+ // ==================== sourceLineId 校验测试 ====================
+
+ /**
+ * sourceDocType=PRO_FEEDBACK 时 sourceLineId 为空 → 创建失败
+ */
+ @Test
+ public void testCreateIpqc_sourceLineId_required() {
+ // 准备参数
+ Long workstationId = randomLongId();
+ Long workOrderId = randomLongId();
+ Long productId = randomLongId();
+ Long feedbackId = randomLongId();
+ Long templateId = randomLongId();
+
+ MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
+ reqVO.setCode("IPQC-SL-001");
+ reqVO.setWorkstationId(workstationId);
+ reqVO.setWorkOrderId(workOrderId);
+ reqVO.setInspectorUserId(randomLongId());
+ reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
+ reqVO.setSourceDocId(feedbackId);
+ reqVO.setSourceLineId(null); // 不传 sourceLineId
+
+ // mock
+ MesMdWorkstationDO workstation = new MesMdWorkstationDO();
+ workstation.setId(workstationId);
+ workstation.setProcessId(null);
+ when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
+ MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
+ workOrder.setId(workOrderId);
+ workOrder.setProductId(productId);
+ when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
+ when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
+ MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
+ templateItem.setTemplateId(templateId);
+ when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
+ when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO());
+
+ // 调用,断言异常
+ ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO));
+ assertEquals(QC_IPQC_SOURCE_LINE_REQUIRED.getCode(), ex.getCode());
+ }
+
+ /**
+ * sourceLineId 不属于该报工的产出行 → 创建失败
+ */
+ @Test
+ public void testCreateIpqc_sourceLineId_notBelong() {
+ // 准备参数
+ Long workstationId = randomLongId();
+ Long workOrderId = randomLongId();
+ Long productId = randomLongId();
+ Long feedbackId = randomLongId();
+ Long sourceLineId = randomLongId();
+ Long templateId = randomLongId();
+
+ MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
+ reqVO.setCode("IPQC-SL-002");
+ reqVO.setWorkstationId(workstationId);
+ reqVO.setWorkOrderId(workOrderId);
+ reqVO.setInspectorUserId(randomLongId());
+ reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
+ reqVO.setSourceDocId(feedbackId);
+ reqVO.setSourceLineId(sourceLineId);
+
+ // mock
+ MesMdWorkstationDO workstation = new MesMdWorkstationDO();
+ workstation.setId(workstationId);
+ workstation.setProcessId(null);
+ when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
+ MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
+ workOrder.setId(workOrderId);
+ workOrder.setProductId(productId);
+ when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
+ when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
+ MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
+ templateItem.setTemplateId(templateId);
+ when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
+ when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO());
+ // mock:产出行存在但 feedbackId 不匹配
+ MesWmProductProduceLineDO line = new MesWmProductProduceLineDO();
+ line.setId(sourceLineId);
+ line.setFeedbackId(randomLongId()); // 不同的 feedbackId
+ line.setQualityStatus(MesWmQualityStatusEnum.PENDING.getStatus());
+ when(productProduceLineService.validateProductProduceLineExists(sourceLineId)).thenReturn(line);
+
+ // 调用,断言异常
+ ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO));
+ assertEquals(QC_IPQC_SOURCE_LINE_NOT_BELONG.getCode(), ex.getCode());
+ }
+
+ /**
+ * sourceLineId 对应的产出行不是待检验状态 → 创建失败
+ */
+ @Test
+ public void testCreateIpqc_sourceLineId_notPending() {
+ // 准备参数
+ Long workstationId = randomLongId();
+ Long workOrderId = randomLongId();
+ Long productId = randomLongId();
+ Long feedbackId = randomLongId();
+ Long sourceLineId = randomLongId();
+ Long templateId = randomLongId();
+
+ MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
+ reqVO.setCode("IPQC-SL-003");
+ reqVO.setWorkstationId(workstationId);
+ reqVO.setWorkOrderId(workOrderId);
+ reqVO.setInspectorUserId(randomLongId());
+ reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
+ reqVO.setSourceDocId(feedbackId);
+ reqVO.setSourceLineId(sourceLineId);
+
+ // mock
+ MesMdWorkstationDO workstation = new MesMdWorkstationDO();
+ workstation.setId(workstationId);
+ workstation.setProcessId(null);
+ when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
+ MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
+ workOrder.setId(workOrderId);
+ workOrder.setProductId(productId);
+ when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
+ when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
+ MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
+ templateItem.setTemplateId(templateId);
+ when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
+ when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(new MesProFeedbackDO());
+ // mock:产出行存在且归属正确,但不是 PENDING 状态
+ MesWmProductProduceLineDO line = new MesWmProductProduceLineDO();
+ line.setId(sourceLineId);
+ line.setFeedbackId(feedbackId);
+ line.setQualityStatus(MesWmQualityStatusEnum.PASS.getStatus()); // 已合格,非待检
+ when(productProduceLineService.validateProductProduceLineExists(sourceLineId)).thenReturn(line);
+
+ // 调用,断言异常
+ ServiceException ex = assertThrows(ServiceException.class, () -> ipqcService.createIpqc(reqVO));
+ assertEquals(QC_IPQC_SOURCE_LINE_NOT_PENDING.getCode(), ex.getCode());
+ }
+
+ /**
+ * sourceLineId 校验全部通过 → 创建成功
+ */
+ @Test
+ public void testCreateIpqc_sourceLineId_success() {
+ // 准备参数
+ Long workstationId = randomLongId();
+ Long workOrderId = randomLongId();
+ Long productId = randomLongId();
+ Long feedbackId = randomLongId();
+ Long sourceLineId = randomLongId();
+ Long templateId = randomLongId();
+
+ MesQcIpqcSaveReqVO reqVO = new MesQcIpqcSaveReqVO();
+ reqVO.setCode("IPQC-SL-004");
+ reqVO.setWorkstationId(workstationId);
+ reqVO.setWorkOrderId(workOrderId);
+ reqVO.setInspectorUserId(randomLongId());
+ reqVO.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK);
+ reqVO.setSourceDocId(feedbackId);
+ reqVO.setSourceLineId(sourceLineId);
+
+ // mock
+ MesMdWorkstationDO workstation = new MesMdWorkstationDO();
+ workstation.setId(workstationId);
+ workstation.setProcessId(null);
+ when(workstationService.validateWorkstationExists(workstationId)).thenReturn(workstation);
+ MesProWorkOrderDO workOrder = new MesProWorkOrderDO();
+ workOrder.setId(workOrderId);
+ workOrder.setProductId(productId);
+ when(workOrderService.validateWorkOrderExists(workOrderId)).thenReturn(workOrder);
+ when(workOrderService.validateWorkOrderConfirmed(workOrderId)).thenReturn(workOrder);
+ MesQcTemplateItemDO templateItem = new MesQcTemplateItemDO();
+ templateItem.setTemplateId(templateId);
+ when(templateItemService.getRequiredTemplateByItemIdAndType(eq(productId), anyInt())).thenReturn(templateItem);
+ MesProFeedbackDO feedback = new MesProFeedbackDO();
+ feedback.setId(feedbackId);
+ feedback.setCode("FB-001");
+ when(feedbackService.validateFeedbackExists(feedbackId)).thenReturn(feedback);
+ // mock:产出行存在、归属正确、PENDING 状态
+ MesWmProductProduceLineDO line = new MesWmProductProduceLineDO();
+ line.setId(sourceLineId);
+ line.setFeedbackId(feedbackId);
+ line.setQualityStatus(MesWmQualityStatusEnum.PENDING.getStatus());
+ when(productProduceLineService.validateProductProduceLineExists(sourceLineId)).thenReturn(line);
+
+ // 调用
+ Long ipqcId = ipqcService.createIpqc(reqVO);
+
+ // 断言:创建成功,sourceDocCode 回写
+ MesQcIpqcDO ipqc = ipqcMapper.selectById(ipqcId);
+ assertNotNull(ipqc);
+ assertEquals(feedbackId, ipqc.getSourceDocId());
+ assertEquals(sourceLineId, ipqc.getSourceLineId());
+ assertEquals("FB-001", ipqc.getSourceDocCode());
+ }
+
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImplTest.java
index 9e2984c39..305556a62 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImplTest.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImplTest.java
@@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordServ
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
import cn.iocoder.yudao.module.mes.service.wm.outsourcereceipt.MesWmOutsourceReceiptService;
+import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
@@ -55,6 +56,8 @@ public class MesQcIqcServiceImplTest extends BaseDbUnitTest {
private MesQcTemplateItemService templateItemService;
@MockBean
private AdminUserApi adminUserApi;
+ @MockitoBean
+ private MesQcIndicatorResultService indicatorResultService;
@Test
public void testFinishIqc_writeBack_arrivalNotice() {
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImplTest.java
index 25e5e850d..fcc022b8c 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImplTest.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImplTest.java
@@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordServ
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesLineService;
import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesService;
+import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
@@ -56,6 +57,8 @@ public class MesQcOqcServiceImplTest extends BaseDbUnitTest {
private MesQcDefectRecordService defectRecordService;
@MockBean
private AdminUserApi adminUserApi;
+ @MockitoBean
+ private MesQcIndicatorResultService indicatorResultService;
@Test
public void testFinishOqc_writeBack_productSales() {
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcServiceImplTest.java
index 3e4b8d93e..55a19d682 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcServiceImplTest.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcServiceImplTest.java
@@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordServ
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
import cn.iocoder.yudao.module.mes.service.wm.returnissue.MesWmReturnIssueLineService;
import cn.iocoder.yudao.module.mes.service.wm.returnsales.MesWmReturnSalesLineService;
+import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
@@ -56,6 +57,8 @@ public class MesQcRqcServiceImplTest extends BaseDbUnitTest {
private MesQcDefectRecordService defectRecordService;
@MockBean
private AdminUserApi adminUserApi;
+ @MockitoBean
+ private MesQcIndicatorResultService indicatorResultService;
@Test
public void testFinishRqc_successWithReturnIssue() {
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImplTest.java
index d5275a867..097222549 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImplTest.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImplTest.java
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalN
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum;
+import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
@@ -37,6 +38,8 @@ public class MesWmArrivalNoticeServiceImplTest extends BaseDbUnitTest {
@MockBean
private MesWmArrivalNoticeLineService arrivalNoticeLineService;
+ @MockitoBean
+ private MesMdVendorService vendorService;
@Test
public void testUpdateArrivalNoticeWhenIqcFinish_success_allLinesChecked() {
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImplTest.java
new file mode 100644
index 000000000..5814c4ce9
--- /dev/null
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImplTest.java
@@ -0,0 +1,110 @@
+package cn.iocoder.yudao.module.mes.service.wm.materialstock;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
+import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
+import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
+import jakarta.annotation.Resource;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.function.Consumer;
+
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.when;
+
+/**
+ * {@link MesWmMaterialStockServiceImpl} 的单元测试
+ *
+ * @author 芋道源码
+ */
+@Import(MesWmMaterialStockServiceImpl.class)
+public class MesWmMaterialStockServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private MesWmMaterialStockServiceImpl materialStockService;
+
+ @Resource
+ private MesWmMaterialStockMapper materialStockMapper;
+
+ @MockitoBean
+ private MesMdItemService itemService;
+ @MockitoBean
+ private MesMdItemTypeService itemTypeService;
+ @MockitoBean
+ private MesWmWarehouseAreaService areaService;
+ @MockitoBean
+ private MesWmWarehouseService warehouseService;
+
+ @Test
+ public void testGetMaterialStockPage_virtualFilterOnly() {
+ // 准备数据:虚拟仓库存 + 普通仓库存
+ Long virtualWarehouseId = 100L;
+ Long normalWarehouseId = 200L;
+ MesWmMaterialStockDO virtualStock = createMaterialStockPojo(o -> o.setWarehouseId(virtualWarehouseId));
+ MesWmMaterialStockDO normalStock = createMaterialStockPojo(o -> o.setWarehouseId(normalWarehouseId));
+ materialStockMapper.insert(virtualStock);
+ materialStockMapper.insert(normalStock);
+ mockVirtualWarehouse(virtualWarehouseId);
+
+ // 调用:只看虚拟仓
+ MesWmMaterialStockPageReqVO reqVO = new MesWmMaterialStockPageReqVO();
+ reqVO.setVirtualFilter(MesWmMaterialStockPageReqVO.VIRTUAL_FILTER_ONLY);
+ PageResult result = materialStockService.getMaterialStockPage(reqVO);
+
+ // 断言:只返回虚拟仓库存,且 total 与列表一致
+ assertEquals(1, result.getTotal());
+ assertEquals(1, result.getList().size());
+ assertEquals(virtualStock.getId(), result.getList().get(0).getId());
+ }
+
+ @Test
+ public void testGetMaterialStockPage_virtualFilterExclude() {
+ // 准备数据:虚拟仓库存 + 普通仓库存
+ Long virtualWarehouseId = 100L;
+ Long normalWarehouseId = 200L;
+ MesWmMaterialStockDO virtualStock = createMaterialStockPojo(o -> o.setWarehouseId(virtualWarehouseId));
+ MesWmMaterialStockDO normalStock = createMaterialStockPojo(o -> o.setWarehouseId(normalWarehouseId));
+ materialStockMapper.insert(virtualStock);
+ materialStockMapper.insert(normalStock);
+ mockVirtualWarehouse(virtualWarehouseId);
+
+ // 调用:排除虚拟仓
+ MesWmMaterialStockPageReqVO reqVO = new MesWmMaterialStockPageReqVO();
+ reqVO.setVirtualFilter(MesWmMaterialStockPageReqVO.VIRTUAL_FILTER_EXCLUDE);
+ PageResult result = materialStockService.getMaterialStockPage(reqVO);
+
+ // 断言:只返回普通仓库存,且 total 与列表一致
+ assertEquals(1, result.getTotal());
+ assertEquals(1, result.getList().size());
+ assertEquals(normalStock.getId(), result.getList().get(0).getId());
+ }
+
+ private MesWmMaterialStockDO createMaterialStockPojo(Consumer consumer) {
+ return randomPojo(MesWmMaterialStockDO.class, o -> {
+ o.setQuantity(new BigDecimal("10.00"));
+ o.setReceiptTime(LocalDateTime.of(2026, 1, 1, 0, 0));
+ o.setFrozen(false);
+ o.setDeleted(false);
+ consumer.accept(o);
+ });
+ }
+
+ private void mockVirtualWarehouse(Long virtualWarehouseId) {
+ MesWmWarehouseDO virtualWarehouse = new MesWmWarehouseDO();
+ virtualWarehouse.setId(virtualWarehouseId);
+ when(warehouseService.getWarehouseByCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE))
+ .thenReturn(virtualWarehouse);
+ }
+
+}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImplTest.java
index 51cd7093b..e7bf99a86 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImplTest.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImplTest.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.mes.service.wm.productproduce;
import cn.hutool.core.collection.ListUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDetailDO;
@@ -17,18 +18,18 @@ import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionServic
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
+import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
-import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
-import javax.annotation.Resource;
import java.math.BigDecimal;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
+import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
@@ -45,27 +46,28 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
@Resource
private MesWmProductProduceMapper productProduceMapper;
- @MockBean
+ @MockitoBean
private MesWmProductProduceLineService productProduceLineService;
- @MockBean
+ @MockitoBean
private MesWmProductProduceDetailService productProduceDetailService;
- @MockBean
+ @MockitoBean
private MesProWorkOrderService workOrderService;
- @MockBean
+ @MockitoBean
private MesWmBatchService batchService;
- @MockBean
+ @MockitoBean
private MesWmTransactionService wmTransactionService;
- @MockBean
+ @MockitoBean
private MesWmWarehouseService warehouseService;
- @MockBean
+ @MockitoBean
private MesWmWarehouseLocationService locationService;
- @MockBean
+ @MockitoBean
private MesWmWarehouseAreaService areaService;
@Test
public void testSplitPendingAndFinishProduce_withUnqualified() {
// 准备数据:插入一个 PREPARE 状态的产出单
Long feedbackId = randomLongId();
+ Long sourceLineId = 100L;
Long itemId = randomLongId();
Long batchId = randomLongId();
String batchCode = "BATCH-001";
@@ -85,19 +87,17 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
when(locationService.getWarehouseLocationByCode(MesWmWarehouseLocationDO.WIP_VIRTUAL_LOCATION)).thenReturn(loc);
when(areaService.getWarehouseAreaByCode(MesWmWarehouseAreaDO.WIP_VIRTUAL_AREA)).thenReturn(area);
- // mock: 返回一条 PENDING 的产出行
+ // mock: 通过 sourceLineId 直接定位 PENDING 产出行
MesWmProductProduceLineDO pendingLine = MesWmProductProduceLineDO.builder()
- .id(100L).produceId(produce.getId()).feedbackId(feedbackId)
+ .id(sourceLineId).produceId(produce.getId()).feedbackId(feedbackId)
.itemId(itemId).quantity(BigDecimal.valueOf(100))
.batchId(batchId).batchCode(batchCode)
.qualityStatus(MesWmQualityStatusEnum.PENDING.getStatus())
.build();
- when(productProduceLineService.getProductProduceLineListByProduceId(produce.getId()))
- .thenReturn(ListUtil.of(pendingLine));
+ when(productProduceLineService.validateProductProduceLineExists(sourceLineId))
+ .thenReturn(pendingLine);
- // mock: finishProductProduce 内部会再查一次行和明细(用于创建库存事务)
- // 由于 finishProductProduce 在拆分行后调用,此时行已经变了,但由于 line/detail 是 mock 的,
- // 我们需要 mock 已拆分后的行给 finishProductProduce
+ // mock: finishProductProduce 内部查行和明细(用于创建库存事务)
MesWmProductProduceLineDO qualifiedLine = MesWmProductProduceLineDO.builder()
.id(100L).produceId(produce.getId()).itemId(itemId)
.quantity(BigDecimal.valueOf(80))
@@ -108,10 +108,8 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
.quantity(BigDecimal.valueOf(20))
.qualityStatus(MesWmQualityStatusEnum.FAIL.getStatus())
.build();
- // finishProductProduce 内部第二次调用 getProductProduceLineListByProduceId
when(productProduceLineService.getProductProduceLineListByProduceId(produce.getId()))
- .thenReturn(ListUtil.of(pendingLine)) // 第 1 次:splitPendingAndFinishProduce 查待检行
- .thenReturn(ListUtil.of(qualifiedLine, unqualifiedLine)); // 第 2 次:finishProductProduce 查所有行
+ .thenReturn(ListUtil.of(qualifiedLine, unqualifiedLine));
// mock: finishProductProduce 中按行查明细
MesWmProductProduceDetailDO qualifiedDetail = MesWmProductProduceDetailDO.builder()
@@ -124,7 +122,8 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
.thenReturn(ListUtil.of(unqualifiedDetail));
// 调用
- productProduceService.splitPendingAndFinishProduce(feedbackId, BigDecimal.valueOf(80), BigDecimal.valueOf(20));
+ productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId,
+ BigDecimal.valueOf(80), BigDecimal.valueOf(20));
// 断言 1:不合格品行 - 新建了一行
ArgumentCaptor lineCaptor = ArgumentCaptor.forClass(MesWmProductProduceLineDO.class);
@@ -156,6 +155,7 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
public void testSplitPendingAndFinishProduce_allQualified() {
// 准备数据:全部合格品场景
Long feedbackId = randomLongId();
+ Long sourceLineId = 200L;
Long itemId = randomLongId();
MesWmProductProduceDO produce = randomPojo(MesWmProductProduceDO.class, o -> {
@@ -173,30 +173,31 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
when(locationService.getWarehouseLocationByCode(any())).thenReturn(loc);
when(areaService.getWarehouseAreaByCode(any())).thenReturn(area);
+ // mock: 通过 sourceLineId 直接定位 PENDING 产出行
MesWmProductProduceLineDO pendingLine = MesWmProductProduceLineDO.builder()
- .id(200L).produceId(produce.getId()).feedbackId(feedbackId)
+ .id(sourceLineId).produceId(produce.getId()).feedbackId(feedbackId)
.itemId(itemId).quantity(BigDecimal.valueOf(50))
.qualityStatus(MesWmQualityStatusEnum.PENDING.getStatus())
.build();
+ when(productProduceLineService.validateProductProduceLineExists(sourceLineId))
+ .thenReturn(pendingLine);
- // 第 1 次查行(拆分阶段)返回 PENDING 行;第 2 次查行(finishProductProduce)返回已更新的合格行
+ // mock: finishProductProduce 查行和明细
MesWmProductProduceLineDO qualifiedLine = MesWmProductProduceLineDO.builder()
.id(200L).produceId(produce.getId()).itemId(itemId)
.quantity(BigDecimal.valueOf(50))
.qualityStatus(MesWmQualityStatusEnum.PASS.getStatus())
.build();
when(productProduceLineService.getProductProduceLineListByProduceId(produce.getId()))
- .thenReturn(ListUtil.of(pendingLine))
.thenReturn(ListUtil.of(qualifiedLine));
-
- // mock: finishProductProduce 中按行查明细
MesWmProductProduceDetailDO detail = MesWmProductProduceDetailDO.builder()
.lineId(200L).quantity(BigDecimal.valueOf(50)).build();
when(productProduceDetailService.getProductProduceDetailListByLineId(200L))
.thenReturn(ListUtil.of(detail));
// 调用:不合格品数量 = 0
- productProduceService.splitPendingAndFinishProduce(feedbackId, BigDecimal.valueOf(50), BigDecimal.ZERO);
+ productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId,
+ BigDecimal.valueOf(50), BigDecimal.ZERO);
// 断言 1:没有新建行(不合格品数量为 0)
verify(productProduceLineService, never()).createProductProduceLine(any());
@@ -218,14 +219,18 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
public void testSplitPendingAndFinishProduce_produceNotExists() {
// 调用不存在的 feedbackId,应该抛异常
Long feedbackId = randomLongId();
- assertThrows(Exception.class, () ->
- productProduceService.splitPendingAndFinishProduce(feedbackId, BigDecimal.TEN, BigDecimal.ZERO));
+ Long sourceLineId = randomLongId();
+ ServiceException ex = assertThrows(ServiceException.class, () ->
+ productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId,
+ BigDecimal.TEN, BigDecimal.ZERO));
+ assertEquals(WM_PRODUCT_PRODUCE_NOT_EXISTS.getCode(), ex.getCode());
}
@Test
- public void testSplitPendingAndFinishProduce_noPendingLine() {
- // 准备数据:产出单存在,但没有 PENDING 行
+ public void testSplitPendingAndFinishProduce_lineNotExists() {
+ // 准备数据:产出单存在,但 sourceLineId 对应的行不存在
Long feedbackId = randomLongId();
+ Long sourceLineId = randomLongId();
MesWmProductProduceDO produce = randomPojo(MesWmProductProduceDO.class, o -> {
o.setFeedbackId(feedbackId);
o.setStatus(MesWmProductProduceStatusEnum.PREPARE.getStatus());
@@ -240,17 +245,15 @@ public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest {
when(locationService.getWarehouseLocationByCode(any())).thenReturn(loc);
when(areaService.getWarehouseAreaByCode(any())).thenReturn(area);
- // mock: 返回一条 PASS 状态的行(不是 PENDING)
- MesWmProductProduceLineDO passLine = MesWmProductProduceLineDO.builder()
- .id(300L).produceId(produce.getId())
- .qualityStatus(MesWmQualityStatusEnum.PASS.getStatus())
- .build();
- when(productProduceLineService.getProductProduceLineListByProduceId(produce.getId()))
- .thenReturn(ListUtil.of(passLine));
+ // mock: validateProductProduceLineExists 抛异常
+ when(productProduceLineService.validateProductProduceLineExists(sourceLineId))
+ .thenThrow(new ServiceException(WM_PRODUCT_PRODUCE_LINE_NOT_EXISTS));
- // 调用,应该抛异常(找不到 PENDING 行)
- assertThrows(Exception.class, () ->
- productProduceService.splitPendingAndFinishProduce(feedbackId, BigDecimal.TEN, BigDecimal.ZERO));
+ // 调用,应该抛异常
+ ServiceException ex = assertThrows(ServiceException.class, () ->
+ productProduceService.splitPendingAndFinishProduce(feedbackId, sourceLineId,
+ BigDecimal.TEN, BigDecimal.ZERO));
+ assertEquals(WM_PRODUCT_PRODUCE_LINE_NOT_EXISTS.getCode(), ex.getCode());
}
}
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesLineServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesLineServiceImplTest.java
index fef292404..674a1d432 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesLineServiceImplTest.java
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/wm/returnsales/MesWmReturnSalesLineServiceImplTest.java
@@ -5,12 +5,14 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSale
import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesLineMapper;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
-import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+
+import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
-import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
@@ -32,12 +34,18 @@ public class MesWmReturnSalesLineServiceImplTest extends BaseDbUnitTest {
@Resource
private MesWmReturnSalesLineMapper returnSalesLineMapper;
- @MockBean
+ @MockitoBean
private MesWmReturnSalesService returnSalesService;
- @MockBean
+ @MockitoBean
private MesMdItemService itemService;
+ @MockitoBean
+ private MesWmBatchService batchService;
+
+ @MockitoBean
+ private MesWmReturnSalesDetailService returnSalesDetailService;
+
@Test
public void testUpdateReturnSalesLineWhenRqcFinish_allPass() {
// 准备参数:全部合格
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/resources/application-unit-test.yaml b/yudao-module-mes/yudao-module-mes-server/src/test/resources/application-unit-test.yaml
index 1df2760c3..db5aa6f05 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/test/resources/application-unit-test.yaml
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/resources/application-unit-test.yaml
@@ -10,7 +10,7 @@ spring:
# 数据源配置项
datasource:
name: ruoyi-vue-pro
- url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
+ url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value,day; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
driver-class-name: org.h2.Driver
username: sa
password:
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/resources/sql/clean.sql b/yudao-module-mes/yudao-module-mes-server/src/test/resources/sql/clean.sql
index eda0d8c6d..8cb28c31e 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/test/resources/sql/clean.sql
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/resources/sql/clean.sql
@@ -1,3 +1,7 @@
+DELETE FROM "mes_cal_team_shift";
+DELETE FROM "mes_cal_plan_team";
+DELETE FROM "mes_cal_plan_shift";
+DELETE FROM "mes_cal_plan";
DELETE FROM "mes_md_auto_code_rule";
DELETE FROM "mes_md_auto_code_part";
DELETE FROM "mes_md_auto_code_record";
@@ -13,3 +17,7 @@ DELETE FROM "mes_wm_item_receipt";
DELETE FROM "mes_wm_item_receipt_line";
DELETE FROM "mes_wm_item_receipt_detail";
DELETE FROM "mes_wm_batch";
+DELETE FROM "mes_wm_material_stock";
+DELETE FROM "mes_pro_task";
+DELETE FROM "mes_pro_route_process";
+DELETE FROM "mes_qc_indicator_result";
diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/resources/sql/create_tables.sql b/yudao-module-mes/yudao-module-mes-server/src/test/resources/sql/create_tables.sql
index a37c9e977..fd7361362 100644
--- a/yudao-module-mes/yudao-module-mes-server/src/test/resources/sql/create_tables.sql
+++ b/yudao-module-mes/yudao-module-mes-server/src/test/resources/sql/create_tables.sql
@@ -1,3 +1,84 @@
+-- ----------------------------
+-- MES 排班计划
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS "mes_cal_plan" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "code" varchar(64) DEFAULT NULL,
+ "name" varchar(255) DEFAULT NULL,
+ "calendar_type" tinyint DEFAULT NULL,
+ "start_date" timestamp DEFAULT NULL,
+ "end_date" timestamp DEFAULT NULL,
+ "shift_type" tinyint DEFAULT NULL,
+ "shift_method" tinyint DEFAULT NULL,
+ "shift_count" int DEFAULT NULL,
+ "status" tinyint DEFAULT NULL,
+ "remark" varchar(500) DEFAULT NULL,
+ "creator" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ "tenant_id" bigint NOT NULL DEFAULT 0,
+ PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- MES 排班计划班次
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS "mes_cal_plan_shift" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "plan_id" bigint DEFAULT NULL,
+ "sort" int DEFAULT NULL,
+ "name" varchar(64) DEFAULT NULL,
+ "start_time" varchar(10) DEFAULT NULL,
+ "end_time" varchar(10) DEFAULT NULL,
+ "remark" varchar(500) DEFAULT NULL,
+ "creator" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ "tenant_id" bigint NOT NULL DEFAULT 0,
+ PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- MES 排班计划班组
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS "mes_cal_plan_team" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "plan_id" bigint DEFAULT NULL,
+ "team_id" bigint DEFAULT NULL,
+ "remark" varchar(500) DEFAULT NULL,
+ "creator" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ "tenant_id" bigint NOT NULL DEFAULT 0,
+ PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- MES 班组排班
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS "mes_cal_team_shift" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "plan_id" bigint DEFAULT NULL,
+ "team_id" bigint DEFAULT NULL,
+ "shift_id" bigint DEFAULT NULL,
+ "day" timestamp DEFAULT NULL,
+ "sort" int DEFAULT NULL,
+ "remark" varchar(500) DEFAULT NULL,
+ "creator" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ "tenant_id" bigint NOT NULL DEFAULT 0,
+ PRIMARY KEY ("id")
+);
+
-- ----------------------------
-- MES 编码规则表
-- ----------------------------
@@ -300,7 +381,6 @@ CREATE TABLE IF NOT EXISTS "mes_wm_product_produce_detail" (
PRIMARY KEY ("id")
);
-
-- ----------------------------
-- MES 出货检验单(OQC)
-- ----------------------------
@@ -309,8 +389,8 @@ CREATE TABLE IF NOT EXISTS "mes_qc_oqc" (
"code" varchar(64) NOT NULL,
"name" varchar(500) NOT NULL,
"template_id" bigint NOT NULL,
- "source_doc_id" bigint DEFAULT NULL,
"source_doc_type" int DEFAULT NULL,
+ "source_doc_id" bigint DEFAULT NULL,
"source_line_id" bigint DEFAULT NULL,
"source_doc_code" varchar(64) DEFAULT NULL,
"client_id" bigint NOT NULL,
@@ -351,8 +431,8 @@ CREATE TABLE IF NOT EXISTS "mes_qc_rqc" (
"code" varchar(64) NOT NULL,
"name" varchar(500) NOT NULL,
"template_id" bigint NOT NULL,
- "source_doc_id" bigint DEFAULT NULL,
"source_doc_type" int DEFAULT NULL,
+ "source_doc_id" bigint DEFAULT NULL,
"source_line_id" bigint DEFAULT NULL,
"source_doc_code" varchar(64) DEFAULT NULL,
"type" int DEFAULT NULL,
@@ -361,17 +441,17 @@ CREATE TABLE IF NOT EXISTS "mes_qc_rqc" (
"check_quantity" decimal(14,2) DEFAULT NULL,
"qualified_quantity" decimal(14,2) DEFAULT 0.00,
"unqualified_quantity" decimal(14,2) DEFAULT 0.00,
+ "critical_rate" decimal(14,2) DEFAULT 0.00,
+ "major_rate" decimal(14,2) DEFAULT 0.00,
+ "minor_rate" decimal(14,2) DEFAULT 0.00,
+ "critical_quantity" int DEFAULT 0,
+ "major_quantity" int DEFAULT 0,
+ "minor_quantity" int DEFAULT 0,
"check_result" tinyint DEFAULT NULL,
"inspect_date" timestamp DEFAULT NULL,
"inspector_user_id" bigint DEFAULT NULL,
"status" tinyint NOT NULL DEFAULT 0,
"remark" varchar(500) DEFAULT '',
- "critical_quantity" int DEFAULT 0,
- "major_quantity" int DEFAULT 0,
- "minor_quantity" int DEFAULT 0,
- "critical_rate" decimal(14,2) DEFAULT 0.00,
- "major_rate" decimal(14,2) DEFAULT 0.00,
- "minor_rate" decimal(14,2) DEFAULT 0.00,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
@@ -387,6 +467,7 @@ CREATE TABLE IF NOT EXISTS "mes_qc_rqc" (
CREATE TABLE IF NOT EXISTS "mes_wm_product_sales_line" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"sales_id" bigint NOT NULL,
+ "notice_line_id" bigint DEFAULT NULL,
"item_id" bigint NOT NULL,
"quantity" decimal(20,6) NOT NULL,
"batch_id" bigint DEFAULT NULL,
@@ -438,6 +519,7 @@ CREATE TABLE IF NOT EXISTS "mes_wm_return_sales_line" (
"item_id" bigint NOT NULL,
"quantity" decimal(12,2) NOT NULL DEFAULT 0.00,
"batch_id" bigint DEFAULT NULL,
+ "batch_code" varchar(255) DEFAULT NULL,
"rqc_id" bigint DEFAULT NULL,
"rqc_check_flag" bit NOT NULL DEFAULT FALSE,
"quality_status" int DEFAULT NULL,
@@ -618,3 +700,133 @@ CREATE TABLE IF NOT EXISTS "mes_wm_batch" (
"tenant_id" bigint NOT NULL DEFAULT 0,
PRIMARY KEY ("id")
);
+
+-- ----------------------------
+-- MES 库存台账
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS "mes_wm_material_stock" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "item_type_id" bigint DEFAULT NULL,
+ "item_id" bigint DEFAULT NULL,
+ "batch_id" bigint DEFAULT NULL,
+ "batch_code" varchar(255) DEFAULT NULL,
+ "warehouse_id" bigint DEFAULT NULL,
+ "location_id" bigint DEFAULT NULL,
+ "area_id" bigint DEFAULT NULL,
+ "vendor_id" bigint DEFAULT NULL,
+ "quantity" decimal(14,2) DEFAULT NULL,
+ "receipt_time" timestamp DEFAULT NULL,
+ "frozen" bit DEFAULT FALSE,
+ "creator" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ "tenant_id" bigint NOT NULL DEFAULT 0,
+ PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- MES 生产任务
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS "mes_pro_task" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "code" varchar(64) DEFAULT NULL,
+ "name" varchar(255) DEFAULT NULL,
+ "work_order_id" bigint DEFAULT NULL,
+ "workstation_id" bigint DEFAULT NULL,
+ "route_id" bigint DEFAULT NULL,
+ "process_id" bigint DEFAULT NULL,
+ "item_id" bigint DEFAULT NULL,
+ "quantity" decimal(14,2) DEFAULT NULL,
+ "produced_quantity" decimal(14,2) DEFAULT NULL,
+ "qualify_quantity" decimal(14,2) DEFAULT NULL,
+ "unqualify_quantity" decimal(14,2) DEFAULT NULL,
+ "changed_quantity" decimal(14,2) DEFAULT NULL,
+ "client_id" bigint DEFAULT NULL,
+ "start_time" timestamp DEFAULT NULL,
+ "duration" int DEFAULT NULL,
+ "end_time" timestamp DEFAULT NULL,
+ "color_code" varchar(20) DEFAULT NULL,
+ "finish_date" timestamp DEFAULT NULL,
+ "cancel_date" timestamp DEFAULT NULL,
+ "status" int DEFAULT NULL,
+ "remark" varchar(500) DEFAULT NULL,
+ "creator" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ "tenant_id" bigint NOT NULL DEFAULT 0,
+ PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- MES 工艺路线工序
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS "mes_pro_route_process" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "route_id" bigint DEFAULT NULL,
+ "process_id" bigint DEFAULT NULL,
+ "sort" int DEFAULT NULL,
+ "next_process_id" bigint DEFAULT NULL,
+ "link_type" int DEFAULT NULL,
+ "prepare_time" int DEFAULT NULL,
+ "wait_time" int DEFAULT NULL,
+ "color_code" varchar(20) DEFAULT NULL,
+ "key_flag" bit DEFAULT FALSE,
+ "check_flag" bit DEFAULT FALSE,
+ "remark" varchar(500) DEFAULT NULL,
+ "creator" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ "tenant_id" bigint NOT NULL DEFAULT 0,
+ PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- MES 销售出库明细
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS "mes_wm_product_sales_detail" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "line_id" bigint NOT NULL,
+ "sales_id" bigint NOT NULL,
+ "item_id" bigint NOT NULL,
+ "quantity" decimal(14,2) DEFAULT NULL,
+ "material_stock_id" bigint DEFAULT NULL,
+ "batch_id" bigint DEFAULT NULL,
+ "batch_code" varchar(255) DEFAULT NULL,
+ "warehouse_id" bigint DEFAULT NULL,
+ "location_id" bigint DEFAULT NULL,
+ "area_id" bigint DEFAULT NULL,
+ "remark" varchar(500) DEFAULT NULL,
+ "creator" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ "tenant_id" bigint NOT NULL DEFAULT 0,
+ PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- MES 检验结果记录
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS "mes_qc_indicator_result" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "code" varchar(64) DEFAULT NULL,
+ "qc_id" bigint DEFAULT NULL,
+ "qc_type" int DEFAULT NULL,
+ "item_id" bigint DEFAULT NULL,
+ "sn" varchar(128) DEFAULT NULL,
+ "remark" varchar(500) DEFAULT NULL,
+ "creator" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ "tenant_id" bigint NOT NULL DEFAULT 0,
+ PRIMARY KEY ("id")
+);