From fa473f7da9f9e9763b73089ffad7d7f535151a06 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 24 Mar 2026 14:03:16 +0800 Subject: [PATCH] ✨ feat(mes): 更新来源单据类型为整型,优化数据一致性 --- yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcRespVO.java | 4 ++-- yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcSaveReqVO.java | 4 ++-- yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/ipqc/MesQcIpqcDO.java | 19 ++++++++++++++----- yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productproduce/MesWmProductProduceMapper.java | 4 ++++ yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java | 1 + yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcSourceDocTypeEnum.java | 3 ++- yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java | 18 ++++++++++++++++++ yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java | 31 +++++++++++++++++++++++++++++-- yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java | 46 +++++++++++++++++++++++++++++++++++++++++----- yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineService.java | 7 +++++++ yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineServiceImpl.java | 5 +++++ yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceService.java | 14 ++++++++++++++ yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImplTest.java | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImplTest.java | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-mes/src/test/resources/sql/clean.sql | 5 +++++ yudao-module-mes/src/test/resources/sql/create_tables.sql | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 18 files changed, 1026 insertions(+), 17 deletions(-) create mode 100644 yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java create mode 100644 yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImplTest.java create mode 100644 yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImplTest.java diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcRespVO.java index 2af1f9c..eef567a 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcRespVO.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcRespVO.java @@ -37,8 +37,8 @@ public class MesQcIpqcRespVO { @Schema(description = "来源单据 ID", example = "200") private Long sourceDocId; - @Schema(description = "来源单据类型", example = "FEEDBACK") - private String sourceDocType; + @Schema(description = "来源单据类型", example = "304") + private Integer sourceDocType; @Schema(description = "来源单据编号", example = "FB20250101001") private String sourceDocCode; diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcSaveReqVO.java index fae9698..543b03f 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcSaveReqVO.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcSaveReqVO.java @@ -33,8 +33,8 @@ public class MesQcIpqcSaveReqVO { // ========== 来源单据 ========== - @Schema(description = "来源单据类型", example = "FEEDBACK") - private String sourceDocType; + @Schema(description = "来源单据类型", example = "304") + private Integer sourceDocType; @Schema(description = "来源单据 ID", example = "200") private Long sourceDocId; diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/ipqc/MesQcIpqcDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/ipqc/MesQcIpqcDO.java index 63e1b86..905359e 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/ipqc/MesQcIpqcDO.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/ipqc/MesQcIpqcDO.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO; +import cn.iocoder.yudao.module.mes.enums.qc.MesQcSourceDocTypeEnum; import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; @@ -55,18 +56,26 @@ public class MesQcIpqcDO extends BaseDO { */ private Long templateId; - // ========== 来源单据(TODO @芋艿:PRO/WM 模块联动后接入) ========== + // ========== 来源单据 ========== /** - * 来源单据 ID + * 来源单据类型 + * + * 枚举 {@link MesQcSourceDocTypeEnum} */ - private Long sourceDocId; + private Integer sourceDocType; /** - * 来源单据类型 + * 来源单据 ID + * + * 关联:根据 {@link #sourceDocType} 不同: + * 1. {@link MesQcSourceDocTypeEnum#FEEDBACK} 时,关联 {@link cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO#getId()} */ - private String sourceDocType; + private Long sourceDocId; /** * 来源单据行 ID + * + * 关联:根据 {@link #sourceDocType} 不同: + * 1. {@link MesQcSourceDocTypeEnum#FEEDBACK} 时,暂不使用(预留) */ private Long sourceLineId; diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productproduce/MesWmProductProduceMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productproduce/MesWmProductProduceMapper.java index f93fbbe..8928ea4 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productproduce/MesWmProductProduceMapper.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productproduce/MesWmProductProduceMapper.java @@ -12,4 +12,8 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface MesWmProductProduceMapper extends BaseMapperX { + default MesWmProductProduceDO selectByFeedbackId(Long feedbackId) { + return selectOne(MesWmProductProduceDO::getFeedbackId, feedbackId); + } + } diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java index b0e0d47..cb3363d 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java @@ -57,6 +57,7 @@ public final class MesBizTypeConstants { public static final int PRO_WORKORDER = 301; // 工单:MesProWorkOrderDO public static final int PRO_TRANS_ORDER = 302; // 流转单:MesProTransOrderDO public static final int PRO_TASK = 303; // 生产任务:MesProTaskDO + public static final int PRO_FEEDBACK = 304; // 生产报工:MesProFeedbackDO // ========== DV 设备模块 [400, 500) ========== diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcSourceDocTypeEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcSourceDocTypeEnum.java index db0ece1..eeed840 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcSourceDocTypeEnum.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcSourceDocTypeEnum.java @@ -17,7 +17,8 @@ import java.util.Arrays; public enum MesQcSourceDocTypeEnum implements ArrayValuable { ARRIVAL_NOTICE(MesBizTypeConstants.WM_ARRIVAL_NOTICE, "到货通知单"), - // OUTSOURCE_RECPT(MesBizTypeConstants.WM_OUTSOURCE_RECPT, "外协入库单"), // TODO 未实现,占位 + OUTSOURCE_RECPT(MesBizTypeConstants.WM_OUTSOURCE_RECPT, "外协入库单"), + FEEDBACK(MesBizTypeConstants.PRO_FEEDBACK, "生产报工"), ; public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesQcSourceDocTypeEnum::getType).toArray(Integer[]::new); diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java index 0837837..5e6e988 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java @@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedba import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; import jakarta.validation.Valid; +import java.math.BigDecimal; + /** * MES 生产报工 Service 接口 * @@ -87,5 +89,21 @@ public interface MesProFeedbackService { */ boolean approveFeedback(Long id, Long userId); + /** + * IPQC 完成后回调:完成报工单并更新任务/工单进度 + * + *

由 IPQC 检验完成时调用,将报工单状态从待检验改为已完成, + * 并根据检验结果回写合格/不合格/废品数量,同时更新任务/工单的已生产数量。 + * + * @param feedbackId 报工记录 ID + * @param qualifiedQty 合格品数量 + * @param unqualifiedQty 不合格品数量 + * @param laborScrapQty 工废数量 + * @param materialScrapQty 料废数量 + * @param otherScrapQty 其他废品数量 + */ + void completeFeedbackFromIpqc(Long feedbackId, BigDecimal qualifiedQty, BigDecimal unqualifiedQty, + BigDecimal laborScrapQty, BigDecimal materialScrapQty, BigDecimal otherScrapQty); + } diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java index a566be2..88b7cd7 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java @@ -165,8 +165,7 @@ public class MesProFeedbackServiceImpl implements MesProFeedbackService { if (keyFlag) { // 4.1 需要检验:生成产出单(质量状态=待检验),更新报工状态为待检验,等质检完成回调后再入库 if (checkFlag) { - // TODO @AI(from codex):当前这里只生成待检产出并把报工置为待检验;后续还需要在 IPQC 完成时继续拆分产出行、完成产出入库、 - // 回写任务/工单进度,并把报工状态改为已完成。 + // 完成时回调见:MesQcIpqcServiceImpl#finishIpqc → splitPendingAndFinishProduce + completeFeedbackFromIpqc productProduceService.generateProductProduce(feedback, true); feedbackMapper.updateById(new MesProFeedbackDO().setId(id) .setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus())); @@ -284,4 +283,32 @@ public class MesProFeedbackServiceImpl implements MesProFeedbackService { return taskService.validateTaskNotFinished(reqVO.getTaskId()); } + @Override + @Transactional(rollbackFor = Exception.class) + public void completeFeedbackFromIpqc(Long feedbackId, BigDecimal qualifiedQty, BigDecimal unqualifiedQty, + BigDecimal laborScrapQty, BigDecimal materialScrapQty, BigDecimal otherScrapQty) { + // 1. 校验报工单存在且为待检验状态 + MesProFeedbackDO feedback = validateFeedbackExists(feedbackId); + if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.UNCHECK.getStatus())) { + throw exception(PRO_FEEDBACK_NOT_UNCHECK); + } + + // 2. 拆分待检产出行(合格/不合格),生成明细,完成产出入库 + productProduceService.splitPendingAndFinishProduce(feedbackId, qualifiedQty, unqualifiedQty); + + // 3. 回写合格/不合格/废品数量,更新状态为已完成 + feedbackMapper.updateById(new MesProFeedbackDO().setId(feedbackId) + .setQualifiedQuantity(qualifiedQty) + .setUnqualifiedQuantity(unqualifiedQty) + .setUncheckQuantity(BigDecimal.ZERO) + .setLaborScrapQuantity(laborScrapQty) + .setMaterialScrapQuantity(materialScrapQty) + .setOtherScrapQuantity(otherScrapQty) + .setStatus(MesProFeedbackStatusEnum.FINISHED.getStatus())); + + // 4. 更新任务/工单的已生产数量 + feedback.setQualifiedQuantity(qualifiedQty).setUnqualifiedQuantity(unqualifiedQty); + updateTaskAndWorkOrderByFeedback(feedback); + } + } diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java index e3fb154..a97d8a6 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java @@ -1,6 +1,7 @@ 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.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcPageReqVO; @@ -13,8 +14,10 @@ import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper; 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.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.md.workstation.MesMdWorkstationService; +import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService; 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.template.MesQcTemplateDetailService; @@ -42,6 +45,7 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; @Validated public class MesQcIpqcServiceImpl implements MesQcIpqcService { + @Resource private MesQcIpqcMapper ipqcMapper; @@ -64,6 +68,9 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService { @Resource private AdminUserApi adminUserApi; + @Resource + @Lazy + private MesProFeedbackService feedbackService; @Override @Transactional(rollbackFor = Exception.class) @@ -114,6 +121,7 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService { } @Override + @Transactional(rollbackFor = Exception.class) public void finishIpqc(Long id) { // 1.1 校验存在 + 草稿状态 MesQcIpqcDO ipqc = validateIpqcStatusPrepare(id); @@ -123,12 +131,40 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService { } // 2. 更新状态为已完成 - MesQcIpqcDO updateObj = new MesQcIpqcDO() - .setId(id).setStatus(MesQcStatusEnum.FINISHED.getStatus()); - ipqcMapper.updateById(updateObj); + ipqcMapper.updateById(new MesQcIpqcDO() + .setId(id).setStatus(MesQcStatusEnum.FINISHED.getStatus())); + + // 3. 回写来源单据 + writeBackSourceDoc(ipqc); + } + + /** + * 回写来源单据(IPQC 完成后) + * + *

当来源为报工时,拆分待检产出行、完成产出入库、回写报工状态和任务/工单进度 + * + * @param ipqc 过程检验单 + */ + private void writeBackSourceDoc(MesQcIpqcDO ipqc) { + if (ipqc.getSourceDocType() == null) { + return; + } + if (ipqc.getSourceDocId() == null) { + throw new IllegalArgumentException( + "IPQC 单[" + ipqc.getId() + "] sourceDocType 非空但 sourceDocId 为空"); + } - // TODO @AI(from codex):如果检验来源是报工,需要对待检产出按检验结果拆分合格/不合格行、生成产出明细、完成产品产出入库、 - // 回写任务/工单数量,并将报工状态从待检验改为已完成。 + if (Objects.equals(ipqc.getSourceDocType(), MesBizTypeConstants.PRO_FEEDBACK)) { + feedbackService.completeFeedbackFromIpqc(ipqc.getSourceDocId(), + ObjectUtil.defaultIfNull(ipqc.getQualifiedQuantity(), BigDecimal.ZERO), + ObjectUtil.defaultIfNull(ipqc.getUnqualifiedQuantity(), BigDecimal.ZERO), + ObjectUtil.defaultIfNull(ipqc.getLaborScrapQuantity(), BigDecimal.ZERO), + ObjectUtil.defaultIfNull(ipqc.getMaterialScrapQuantity(), BigDecimal.ZERO), + ObjectUtil.defaultIfNull(ipqc.getOtherScrapQuantity(), BigDecimal.ZERO)); + } else { + throw new IllegalArgumentException( + "IPQC 单[" + ipqc.getId() + "] sourceDocType=" + ipqc.getSourceDocType() + " 无法识别,无法回写来源单据"); + } } @Override diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineService.java index 75ca17c..3623070 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineService.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineService.java @@ -19,6 +19,13 @@ public interface MesWmProductProduceLineService { void createProductProduceLine(MesWmProductProduceLineDO line); /** + * 更新生产入库单行(内部使用) + * + * @param line 行数据 + */ + void updateProductProduceLine(MesWmProductProduceLineDO line); + + /** * 根据入库单 ID 获取行列表 * * @param produceId 入库单 ID diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineServiceImpl.java index d497dd7..e88a352 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineServiceImpl.java @@ -26,6 +26,11 @@ public class MesWmProductProduceLineServiceImpl implements MesWmProductProduceLi } @Override + public void updateProductProduceLine(MesWmProductProduceLineDO line) { + productProduceLineMapper.updateById(line); + } + + @Override public List getProductProduceLineListByProduceId(Long produceId) { return productProduceLineMapper.selectListByProduceId(produceId); } diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceService.java index 3fa65f8..d612272 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceService.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceService.java @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.mes.service.wm.productproduce; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO; +import java.math.BigDecimal; + /** * MES 生产入库单 Service 接口 */ @@ -32,4 +34,16 @@ public interface MesWmProductProduceService { */ MesWmProductProduceDO generateProductProduce(MesProFeedbackDO feedback, boolean checkFlag); + /** + * IPQC 检验完成回调:将待检产出拆分为合格/不合格行、生成明细、完成入库 + * + *

调用场景:IPQC 完成时,需要将之前 {@link #generateProductProduce} 生成的 + * 待检产出({@code checkFlag=true})按检验结果拆分行,并执行入库。 + * + * @param feedbackId 报工记录 ID + * @param qualifiedQty 合格品数量 + * @param unqualifiedQty 不合格品数量 + */ + void splitPendingAndFinishProduce(Long feedbackId, BigDecimal qualifiedQty, BigDecimal unqualifiedQty); + } diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java index 7c49f0d..cb1b6a8 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java @@ -221,4 +221,70 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic return produce; } + @Override + @Transactional(rollbackFor = Exception.class) + public void splitPendingAndFinishProduce(Long feedbackId, BigDecimal qualifiedQty, BigDecimal unqualifiedQty) { + // 1.1 查询产出单 + MesWmProductProduceDO produce = productProduceMapper.selectByFeedbackId(feedbackId); + if (produce == null) { + throw exception(WM_PRODUCT_PRODUCE_NOT_EXISTS); + } + // 1.2 获取虚拟线边库 + MesWmWarehouseDO virtualWarehouse = warehouseService.getWarehouseByCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE); + 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()); + // TODO @AI:hutool 的 findOne 方法,替代掉 stream;手动 if 判断为空,抛出异常; + MesWmProductProduceLineDO pendingLine = lines.stream() + .filter(l -> ObjUtil.equal(l.getQualityStatus(), MesWmQualityStatusEnum.PENDING.getStatus())) + .findFirst() + .orElseThrow(() -> exception(WM_PRODUCT_PRODUCE_LINE_NOT_EXISTS)); + + // 3A. 情况一:存在不合格品数量,需要拆分行 + if (unqualifiedQty != null && unqualifiedQty.compareTo(BigDecimal.ZERO) > 0) { + // 3A.1 不合格品:新建一行 + 明细 + MesWmProductProduceLineDO unqualifiedLine = MesWmProductProduceLineDO.builder() + .produceId(produce.getId()).feedbackId(feedbackId) + .itemId(pendingLine.getItemId()).quantity(unqualifiedQty) + .batchId(pendingLine.getBatchId()).batchCode(pendingLine.getBatchCode()) + .expireDate(pendingLine.getExpireDate()).lotNumber(pendingLine.getLotNumber()) + .qualityStatus(MesWmQualityStatusEnum.FAIL.getStatus()) + .build(); + productProduceLineService.createProductProduceLine(unqualifiedLine); + productProduceDetailService.createProductProduceDetail(MesWmProductProduceDetailDO.builder() + .produceId(produce.getId()).lineId(unqualifiedLine.getId()) + .itemId(pendingLine.getItemId()).quantity(unqualifiedQty) + .batchId(pendingLine.getBatchId()).batchCode(pendingLine.getBatchCode()) + .warehouseId(virtualWarehouse.getId()).locationId(virtualLocation.getId()).areaId(virtualArea.getId()) + .build()); + // 3A.2 合格品:复用原待检行,更新数量和状态 + 明细 + pendingLine.setQuantity(ObjectUtil.defaultIfNull(qualifiedQty, BigDecimal.ZERO)); + pendingLine.setQualityStatus(MesWmQualityStatusEnum.PASS.getStatus()); + productProduceLineService.updateProductProduceLine(pendingLine); + if (qualifiedQty != null && qualifiedQty.compareTo(BigDecimal.ZERO) > 0) { + productProduceDetailService.createProductProduceDetail(MesWmProductProduceDetailDO.builder() + .produceId(produce.getId()).lineId(pendingLine.getId()) + .itemId(pendingLine.getItemId()).quantity(qualifiedQty) + .batchId(pendingLine.getBatchId()).batchCode(pendingLine.getBatchCode()) + .warehouseId(virtualWarehouse.getId()).locationId(virtualLocation.getId()).areaId(virtualArea.getId()) + .build()); + } + } else { + // 3B. 情况二:全部合格,直接更新行状态 + 明细 + pendingLine.setQualityStatus(MesWmQualityStatusEnum.PASS.getStatus()); + productProduceLineService.updateProductProduceLine(pendingLine); + productProduceDetailService.createProductProduceDetail(MesWmProductProduceDetailDO.builder() + .produceId(produce.getId()).lineId(pendingLine.getId()) + .itemId(pendingLine.getItemId()).quantity(pendingLine.getQuantity()) + .batchId(pendingLine.getBatchId()).batchCode(pendingLine.getBatchCode()) + .warehouseId(virtualWarehouse.getId()).locationId(virtualLocation.getId()).areaId(virtualArea.getId()) + .build()); + } + + // 4. 完成产出单(创建库存事务 + 更新状态为已完成) + finishProductProduce(produce.getId()); + } + } diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java new file mode 100644 index 0000000..fccbcd4 --- /dev/null +++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java @@ -0,0 +1,196 @@ +package cn.iocoder.yudao.module.mes.service.pro.feedback; + +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.wm.productproduce.MesWmProductProduceLineDO; +import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper; +import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum; +import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; +import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; +import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService; +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.wm.itemconsume.MesWmItemConsumeService; +import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService; +import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceService; +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.util.List; + +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.*; +import static org.mockito.Mockito.*; + +/** + * {@link MesProFeedbackServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +@Import(MesProFeedbackServiceImpl.class) +public class MesProFeedbackServiceImplTest extends BaseDbUnitTest { + + @Resource + private MesProFeedbackServiceImpl feedbackService; + + @Resource + private MesProFeedbackMapper feedbackMapper; + + @MockitoBean + private MesProWorkOrderService workOrderService; + @MockitoBean + private MesProRouteProcessService routeProcessService; + @MockitoBean + private MesMdWorkstationService workstationService; + @MockitoBean + private MesProTaskService taskService; + @MockitoBean + private MesWmItemConsumeService itemConsumeService; + @MockitoBean + private MesWmProductProduceService productProduceService; + @MockitoBean + private MesWmProductProduceLineService produceLineService; + + @Test + public void testCompleteFeedbackFromIpqc_success_withUnqualified() { + // 准备数据:插入一条待检验状态的报工单 + Long taskId = randomLongId(); + Long workOrderId = randomLongId(); + MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> { + o.setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus()); + o.setTaskId(taskId); + o.setWorkOrderId(workOrderId); + o.setFeedbackQuantity(BigDecimal.valueOf(100)); + }); + feedbackMapper.insert(feedback); + + // mock: 产出行(用于 updateTaskAndWorkOrderByFeedback 内的数量聚合) + MesWmProductProduceLineDO qualifiedLine = MesWmProductProduceLineDO.builder() + .quantity(BigDecimal.valueOf(80)) + .qualityStatus(MesWmQualityStatusEnum.PASS.getStatus()) + .build(); + MesWmProductProduceLineDO unqualifiedLine = MesWmProductProduceLineDO.builder() + .quantity(BigDecimal.valueOf(20)) + .qualityStatus(MesWmQualityStatusEnum.FAIL.getStatus()) + .build(); + when(produceLineService.getProductProduceLineListByFeedbackId(feedback.getId())) + .thenReturn(List.of(qualifiedLine, unqualifiedLine)); + + // 调用 + BigDecimal qualifiedQty = BigDecimal.valueOf(80); + BigDecimal unqualifiedQty = BigDecimal.valueOf(20); + BigDecimal laborScrapQty = BigDecimal.valueOf(5); + BigDecimal materialScrapQty = BigDecimal.valueOf(10); + BigDecimal otherScrapQty = BigDecimal.valueOf(5); + + feedbackService.completeFeedbackFromIpqc(feedback.getId(), + qualifiedQty, unqualifiedQty, laborScrapQty, materialScrapQty, otherScrapQty); + + // 断言 1:调用了 splitPendingAndFinishProduce + verify(productProduceService).splitPendingAndFinishProduce( + eq(feedback.getId()), eq(qualifiedQty), eq(unqualifiedQty)); + + // 断言 2:报工单状态更新为已完成 + MesProFeedbackDO updatedFeedback = feedbackMapper.selectById(feedback.getId()); + assertEquals(MesProFeedbackStatusEnum.FINISHED.getStatus(), updatedFeedback.getStatus()); + + // 断言 3:数量回写正确 + assertEquals(0, qualifiedQty.compareTo(updatedFeedback.getQualifiedQuantity())); + assertEquals(0, unqualifiedQty.compareTo(updatedFeedback.getUnqualifiedQuantity())); + assertEquals(0, BigDecimal.ZERO.compareTo(updatedFeedback.getUncheckQuantity())); + assertEquals(0, laborScrapQty.compareTo(updatedFeedback.getLaborScrapQuantity())); + assertEquals(0, materialScrapQty.compareTo(updatedFeedback.getMaterialScrapQuantity())); + assertEquals(0, otherScrapQty.compareTo(updatedFeedback.getOtherScrapQuantity())); + + // 断言 4:更新了任务和工单的已生产数量 + // 注意:数量经过 DB 存取后 scale 可能变化,用 any() 匹配 + verify(taskService).updateProducedQuantity(eq(taskId), + any(BigDecimal.class), any(BigDecimal.class), any(BigDecimal.class)); + verify(workOrderService).updateProducedQuantity(eq(workOrderId), + any(BigDecimal.class)); + } + + @Test + public void testCompleteFeedbackFromIpqc_success_allQualified() { + // 准备数据:全部合格 + Long taskId = randomLongId(); + Long workOrderId = randomLongId(); + MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> { + o.setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus()); + o.setTaskId(taskId); + o.setWorkOrderId(workOrderId); + o.setFeedbackQuantity(BigDecimal.valueOf(50)); + }); + feedbackMapper.insert(feedback); + + // mock: 产出行(全部合格) + MesWmProductProduceLineDO qualifiedLine = MesWmProductProduceLineDO.builder() + .quantity(BigDecimal.valueOf(50)) + .qualityStatus(MesWmQualityStatusEnum.PASS.getStatus()) + .build(); + when(produceLineService.getProductProduceLineListByFeedbackId(feedback.getId())) + .thenReturn(List.of(qualifiedLine)); + + // 调用 + feedbackService.completeFeedbackFromIpqc(feedback.getId(), + BigDecimal.valueOf(50), BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO); + + // 断言 1:报工单状态为已完成 + MesProFeedbackDO updatedFeedback = feedbackMapper.selectById(feedback.getId()); + assertEquals(MesProFeedbackStatusEnum.FINISHED.getStatus(), updatedFeedback.getStatus()); + + // 断言 2:合格品数量正确,不合格品和废品为 0 + assertEquals(0, BigDecimal.valueOf(50).compareTo(updatedFeedback.getQualifiedQuantity())); + assertEquals(0, BigDecimal.ZERO.compareTo(updatedFeedback.getUnqualifiedQuantity())); + assertEquals(0, BigDecimal.ZERO.compareTo(updatedFeedback.getLaborScrapQuantity())); + } + + @Test + public void testCompleteFeedbackFromIpqc_feedbackNotExists() { + // 调用不存在的 feedbackId,应该抛异常 + Long feedbackId = randomLongId(); + assertThrows(Exception.class, () -> + feedbackService.completeFeedbackFromIpqc(feedbackId, + BigDecimal.TEN, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO)); + } + + @Test + public void testCompleteFeedbackFromIpqc_feedbackNotUncheck() { + // 准备数据:草稿状态(不是 UNCHECK),应该报错 + MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> { + o.setStatus(MesProFeedbackStatusEnum.PREPARE.getStatus()); + }); + feedbackMapper.insert(feedback); + + // 调用,应该抛异常 + assertThrows(Exception.class, () -> + feedbackService.completeFeedbackFromIpqc(feedback.getId(), + BigDecimal.TEN, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO)); + + // 断言:不应该调用任何产出单方法 + verify(productProduceService, never()).splitPendingAndFinishProduce(anyLong(), any(), any()); + } + + @Test + public void testCompleteFeedbackFromIpqc_feedbackAlreadyFinished() { + // 准备数据:已完成状态,不能再次完成 + MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> { + o.setStatus(MesProFeedbackStatusEnum.FINISHED.getStatus()); + }); + feedbackMapper.insert(feedback); + + // 调用,应该抛异常 + assertThrows(Exception.class, () -> + feedbackService.completeFeedbackFromIpqc(feedback.getId(), + BigDecimal.TEN, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO)); + } + +} diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImplTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImplTest.java new file mode 100644 index 0000000..ceaf63d --- /dev/null +++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImplTest.java @@ -0,0 +1,214 @@ +package cn.iocoder.yudao.module.mes.service.qc.ipqc; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO; +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.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.workorder.MesProWorkOrderService; +import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService; +import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateDetailService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +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 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.*; +import static org.mockito.Mockito.*; + +/** + * {@link MesQcIpqcServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +@Import(MesQcIpqcServiceImpl.class) +public class MesQcIpqcServiceImplTest extends BaseDbUnitTest { + + @Resource + private MesQcIpqcServiceImpl ipqcService; + + @Resource + private MesQcIpqcMapper ipqcMapper; + + @MockitoBean + private MesQcTemplateDetailService templateDetailService; + @MockitoBean + private MesQcIpqcLineService ipqcLineService; + @MockitoBean + private MesProWorkOrderService workOrderService; + @MockitoBean + private MesMdWorkstationService workstationService; + @MockitoBean + private MesMdItemService itemService; + @MockitoBean + private MesQcDefectRecordService defectRecordService; + @MockitoBean + private AdminUserApi adminUserApi; + @MockitoBean + private MesProFeedbackService feedbackService; + + @Test + public void testFinishIpqc_writeBack_feedback() { + // 准备数据:插入一条草稿状态的 IPQC 单,来源为报工单(PRO_FEEDBACK=304) + Long sourceDocId = randomLongId(); + BigDecimal qualifiedQty = BigDecimal.valueOf(80); + BigDecimal unqualifiedQty = BigDecimal.valueOf(20); + BigDecimal laborScrapQty = BigDecimal.valueOf(5); + BigDecimal materialScrapQty = BigDecimal.valueOf(10); + BigDecimal otherScrapQty = BigDecimal.valueOf(5); + + MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> { + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); + o.setCheckResult(1); // 非空,满足 finishIpqc 的校验 + o.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK); + o.setSourceDocId(sourceDocId); + o.setSourceLineId(null); // 报工场景下预留不使用 + o.setQualifiedQuantity(qualifiedQty); + o.setUnqualifiedQuantity(unqualifiedQty); + o.setLaborScrapQuantity(laborScrapQty); + o.setMaterialScrapQuantity(materialScrapQty); + o.setOtherScrapQuantity(otherScrapQty); + }); + ipqcMapper.insert(ipqc); + + // 调用 + ipqcService.finishIpqc(ipqc.getId()); + + // 断言 1:状态更新为已完成 + MesQcIpqcDO updatedIpqc = ipqcMapper.selectById(ipqc.getId()); + assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIpqc.getStatus()); + + // 断言 2:调用了 feedbackService.completeFeedbackFromIpqc,传递正确参数 + // 注意:数量经过 DB 存取后 scale 可能变化(例如 80 → 80.00),所以用 any() 匹配 + verify(feedbackService).completeFeedbackFromIpqc( + eq(sourceDocId), + any(BigDecimal.class), + any(BigDecimal.class), + any(BigDecimal.class), + any(BigDecimal.class), + any(BigDecimal.class)); + } + + @Test + public void testFinishIpqc_writeBack_feedback_withNullQuantities() { + // 准备数据:合格品/不合格品/废品数量为 null 的场景,应该 defaultIfNull 为 0 + Long sourceDocId = 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.setQualifiedQuantity(null); + o.setUnqualifiedQuantity(null); + o.setLaborScrapQuantity(null); + o.setMaterialScrapQuantity(null); + o.setOtherScrapQuantity(null); + }); + ipqcMapper.insert(ipqc); + + // 调用 + ipqcService.finishIpqc(ipqc.getId()); + + // 断言:所有数量参数都是 BigDecimal.ZERO(defaultIfNull 处理) + verify(feedbackService).completeFeedbackFromIpqc( + eq(sourceDocId), + eq(BigDecimal.ZERO), + eq(BigDecimal.ZERO), + eq(BigDecimal.ZERO), + eq(BigDecimal.ZERO), + eq(BigDecimal.ZERO)); + } + + @Test + public void testFinishIpqc_writeBack_noSourceDoc() { + // 准备数据:sourceDocType 为空,不需要回写 + MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> { + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); + o.setCheckResult(1); + o.setSourceDocType(null); + o.setSourceDocId(null); + o.setSourceLineId(null); + }); + ipqcMapper.insert(ipqc); + + // 调用 + ipqcService.finishIpqc(ipqc.getId()); + + // 断言 1:状态更新成功 + MesQcIpqcDO updatedIpqc = ipqcMapper.selectById(ipqc.getId()); + assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedIpqc.getStatus()); + + // 断言 2:不应该调用 feedbackService + verify(feedbackService, never()).completeFeedbackFromIpqc( + anyLong(), any(), any(), any(), any(), any()); + } + + @Test + public void testFinishIpqc_writeBack_sourceDocTypeDirtyData() { + // 准备数据:sourceDocType 非空但 sourceDocId 为空,应该抛异常 + MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> { + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); + o.setCheckResult(1); + o.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK); + o.setSourceDocId(null); // 脏数据 + o.setSourceLineId(null); + }); + ipqcMapper.insert(ipqc); + + // 调用,并断言异常 + assertThrows(IllegalArgumentException.class, () -> ipqcService.finishIpqc(ipqc.getId())); + } + + @Test + public void testFinishIpqc_writeBack_unknownSourceDocType() { + // 准备数据:sourceDocType 无法识别 + MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> { + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); + o.setCheckResult(1); + o.setSourceDocType(99999); // 无法识别的类型 + o.setSourceDocId(randomLongId()); + o.setSourceLineId(null); + }); + ipqcMapper.insert(ipqc); + + // 调用,并断言异常 + assertThrows(IllegalArgumentException.class, () -> ipqcService.finishIpqc(ipqc.getId())); + } + + @Test + public void testFinishIpqc_checkResultEmpty() { + // 准备数据:checkResult 为空,应该报错 + MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> { + o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); + o.setCheckResult(null); // 检测结论为空 + o.setSourceDocType(null); + }); + ipqcMapper.insert(ipqc); + + // 调用,应该抛异常(检测结论必填) + assertThrows(Exception.class, () -> ipqcService.finishIpqc(ipqc.getId())); + } + + @Test + public void testFinishIpqc_notDraftStatus() { + // 准备数据:已完成状态,不能再次完成 + MesQcIpqcDO ipqc = randomPojo(MesQcIpqcDO.class, o -> { + o.setStatus(MesQcStatusEnum.FINISHED.getStatus()); + o.setCheckResult(1); + }); + ipqcMapper.insert(ipqc); + + // 调用,应该抛异常(不是草稿状态) + assertThrows(Exception.class, () -> ipqcService.finishIpqc(ipqc.getId())); + } + +} diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImplTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImplTest.java new file mode 100644 index 0000000..3ea0824 --- /dev/null +++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImplTest.java @@ -0,0 +1,255 @@ +package cn.iocoder.yudao.module.mes.service.wm.productproduce; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDetailDO; +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.dataobject.wm.warehouse.MesWmWarehouseAreaDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO; +import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceMapper; +import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductProduceStatusEnum; +import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; +import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; +import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; +import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; +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.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import java.math.BigDecimal; +import java.util.List; + +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.*; +import static org.mockito.Mockito.*; + +/** + * {@link MesWmProductProduceServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +@Import(MesWmProductProduceServiceImpl.class) +public class MesWmProductProduceServiceImplTest extends BaseDbUnitTest { + + @Resource + private MesWmProductProduceServiceImpl productProduceService; + + @Resource + private MesWmProductProduceMapper productProduceMapper; + + @MockitoBean + private MesWmProductProduceLineService productProduceLineService; + @MockitoBean + private MesWmProductProduceDetailService productProduceDetailService; + @MockitoBean + private MesProWorkOrderService workOrderService; + @MockitoBean + private MesWmBatchService batchService; + @MockitoBean + private MesWmTransactionService wmTransactionService; + @MockitoBean + private MesWmWarehouseService warehouseService; + @MockitoBean + private MesWmWarehouseLocationService locationService; + @MockitoBean + private MesWmWarehouseAreaService areaService; + + @Test + public void testSplitPendingAndFinishProduce_withUnqualified() { + // 准备数据:插入一个 PREPARE 状态的产出单 + Long feedbackId = randomLongId(); + Long itemId = randomLongId(); + Long batchId = randomLongId(); + String batchCode = "BATCH-001"; + + MesWmProductProduceDO produce = randomPojo(MesWmProductProduceDO.class, o -> { + o.setFeedbackId(feedbackId); + o.setStatus(MesWmProductProduceStatusEnum.PREPARE.getStatus()); + }); + productProduceMapper.insert(produce); + + // mock: 虚拟线边库 + Long whId = randomLongId(), locId = randomLongId(), areaId = randomLongId(); + MesWmWarehouseDO wh = new MesWmWarehouseDO(); wh.setId(whId); + MesWmWarehouseLocationDO loc = new MesWmWarehouseLocationDO(); loc.setId(locId); + MesWmWarehouseAreaDO area = new MesWmWarehouseAreaDO(); area.setId(areaId); + when(warehouseService.getWarehouseByCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE)).thenReturn(wh); + when(locationService.getWarehouseLocationByCode(MesWmWarehouseLocationDO.WIP_VIRTUAL_LOCATION)).thenReturn(loc); + when(areaService.getWarehouseAreaByCode(MesWmWarehouseAreaDO.WIP_VIRTUAL_AREA)).thenReturn(area); + + // mock: 返回一条 PENDING 的产出行 + MesWmProductProduceLineDO pendingLine = MesWmProductProduceLineDO.builder() + .id(100L).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(List.of(pendingLine)); + + // mock: finishProductProduce 内部会再查一次行和明细(用于创建库存事务) + // 由于 finishProductProduce 在拆分行后调用,此时行已经变了,但由于 line/detail 是 mock 的, + // 我们需要 mock 已拆分后的行给 finishProductProduce + MesWmProductProduceLineDO qualifiedLine = MesWmProductProduceLineDO.builder() + .id(100L).produceId(produce.getId()).itemId(itemId) + .quantity(BigDecimal.valueOf(80)) + .qualityStatus(MesWmQualityStatusEnum.PASS.getStatus()) + .build(); + MesWmProductProduceLineDO unqualifiedLine = MesWmProductProduceLineDO.builder() + .id(101L).produceId(produce.getId()).itemId(itemId) + .quantity(BigDecimal.valueOf(20)) + .qualityStatus(MesWmQualityStatusEnum.FAIL.getStatus()) + .build(); + // finishProductProduce 内部第二次调用 getProductProduceLineListByProduceId + when(productProduceLineService.getProductProduceLineListByProduceId(produce.getId())) + .thenReturn(List.of(pendingLine)) // 第 1 次:splitPendingAndFinishProduce 查待检行 + .thenReturn(List.of(qualifiedLine, unqualifiedLine)); // 第 2 次:finishProductProduce 查所有行 + + // mock: finishProductProduce 中按行查明细 + MesWmProductProduceDetailDO qualifiedDetail = MesWmProductProduceDetailDO.builder() + .lineId(100L).quantity(BigDecimal.valueOf(80)).build(); + MesWmProductProduceDetailDO unqualifiedDetail = MesWmProductProduceDetailDO.builder() + .lineId(101L).quantity(BigDecimal.valueOf(20)).build(); + when(productProduceDetailService.getProductProduceDetailListByLineId(100L)) + .thenReturn(List.of(qualifiedDetail)); + when(productProduceDetailService.getProductProduceDetailListByLineId(101L)) + .thenReturn(List.of(unqualifiedDetail)); + + // 调用 + productProduceService.splitPendingAndFinishProduce(feedbackId, BigDecimal.valueOf(80), BigDecimal.valueOf(20)); + + // 断言 1:不合格品行 - 新建了一行 + ArgumentCaptor lineCaptor = ArgumentCaptor.forClass(MesWmProductProduceLineDO.class); + verify(productProduceLineService).createProductProduceLine(lineCaptor.capture()); + MesWmProductProduceLineDO createdLine = lineCaptor.getValue(); + assertEquals(MesWmQualityStatusEnum.FAIL.getStatus(), createdLine.getQualityStatus()); + assertEquals(0, BigDecimal.valueOf(20).compareTo(createdLine.getQuantity())); + assertEquals(itemId, createdLine.getItemId()); + + // 断言 2:原待检行 - 更新为合格品 + ArgumentCaptor updateCaptor = ArgumentCaptor.forClass(MesWmProductProduceLineDO.class); + verify(productProduceLineService).updateProductProduceLine(updateCaptor.capture()); + MesWmProductProduceLineDO updatedLine = updateCaptor.getValue(); + assertEquals(MesWmQualityStatusEnum.PASS.getStatus(), updatedLine.getQualityStatus()); + assertEquals(0, BigDecimal.valueOf(80).compareTo(updatedLine.getQuantity())); + + // 断言 3:生成了 2 条明细(不合格品明细 + 合格品明细) + verify(productProduceDetailService, times(2)).createProductProduceDetail(any()); + + // 断言 4:产出单状态更新为已完成 + MesWmProductProduceDO updatedProduce = productProduceMapper.selectById(produce.getId()); + assertEquals(MesWmProductProduceStatusEnum.FINISHED.getStatus(), updatedProduce.getStatus()); + + // 断言 5:创建了库存事务 + verify(wmTransactionService, times(2)).createTransaction(any()); + } + + @Test + public void testSplitPendingAndFinishProduce_allQualified() { + // 准备数据:全部合格品场景 + Long feedbackId = randomLongId(); + Long itemId = randomLongId(); + + MesWmProductProduceDO produce = randomPojo(MesWmProductProduceDO.class, o -> { + o.setFeedbackId(feedbackId); + o.setStatus(MesWmProductProduceStatusEnum.PREPARE.getStatus()); + }); + productProduceMapper.insert(produce); + + // mock: 虚拟线边库 + Long whId = randomLongId(), locId = randomLongId(), areaId = randomLongId(); + MesWmWarehouseDO wh = new MesWmWarehouseDO(); wh.setId(whId); + MesWmWarehouseLocationDO loc = new MesWmWarehouseLocationDO(); loc.setId(locId); + MesWmWarehouseAreaDO area = new MesWmWarehouseAreaDO(); area.setId(areaId); + when(warehouseService.getWarehouseByCode(any())).thenReturn(wh); + when(locationService.getWarehouseLocationByCode(any())).thenReturn(loc); + when(areaService.getWarehouseAreaByCode(any())).thenReturn(area); + + MesWmProductProduceLineDO pendingLine = MesWmProductProduceLineDO.builder() + .id(200L).produceId(produce.getId()).feedbackId(feedbackId) + .itemId(itemId).quantity(BigDecimal.valueOf(50)) + .qualityStatus(MesWmQualityStatusEnum.PENDING.getStatus()) + .build(); + + // 第 1 次查行(拆分阶段)返回 PENDING 行;第 2 次查行(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(List.of(pendingLine)) + .thenReturn(List.of(qualifiedLine)); + + // mock: finishProductProduce 中按行查明细 + MesWmProductProduceDetailDO detail = MesWmProductProduceDetailDO.builder() + .lineId(200L).quantity(BigDecimal.valueOf(50)).build(); + when(productProduceDetailService.getProductProduceDetailListByLineId(200L)) + .thenReturn(List.of(detail)); + + // 调用:不合格品数量 = 0 + productProduceService.splitPendingAndFinishProduce(feedbackId, BigDecimal.valueOf(50), BigDecimal.ZERO); + + // 断言 1:没有新建行(不合格品数量为 0) + verify(productProduceLineService, never()).createProductProduceLine(any()); + + // 断言 2:更新了原待检行为合格 + ArgumentCaptor updateCaptor = ArgumentCaptor.forClass(MesWmProductProduceLineDO.class); + verify(productProduceLineService).updateProductProduceLine(updateCaptor.capture()); + assertEquals(MesWmQualityStatusEnum.PASS.getStatus(), updateCaptor.getValue().getQualityStatus()); + + // 断言 3:只生成了 1 条明细(全部合格品) + verify(productProduceDetailService, times(1)).createProductProduceDetail(any()); + + // 断言 4:产出单状态更新为已完成 + MesWmProductProduceDO updatedProduce = productProduceMapper.selectById(produce.getId()); + assertEquals(MesWmProductProduceStatusEnum.FINISHED.getStatus(), updatedProduce.getStatus()); + } + + @Test + public void testSplitPendingAndFinishProduce_produceNotExists() { + // 调用不存在的 feedbackId,应该抛异常 + Long feedbackId = randomLongId(); + assertThrows(Exception.class, () -> + productProduceService.splitPendingAndFinishProduce(feedbackId, BigDecimal.TEN, BigDecimal.ZERO)); + } + + @Test + public void testSplitPendingAndFinishProduce_noPendingLine() { + // 准备数据:产出单存在,但没有 PENDING 行 + Long feedbackId = randomLongId(); + MesWmProductProduceDO produce = randomPojo(MesWmProductProduceDO.class, o -> { + o.setFeedbackId(feedbackId); + o.setStatus(MesWmProductProduceStatusEnum.PREPARE.getStatus()); + }); + productProduceMapper.insert(produce); + + // mock: 虚拟线边库 + MesWmWarehouseDO wh = new MesWmWarehouseDO(); wh.setId(1L); + MesWmWarehouseLocationDO loc = new MesWmWarehouseLocationDO(); loc.setId(1L); + MesWmWarehouseAreaDO area = new MesWmWarehouseAreaDO(); area.setId(1L); + when(warehouseService.getWarehouseByCode(any())).thenReturn(wh); + 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(List.of(passLine)); + + // 调用,应该抛异常(找不到 PENDING 行) + assertThrows(Exception.class, () -> + productProduceService.splitPendingAndFinishProduce(feedbackId, BigDecimal.TEN, BigDecimal.ZERO)); + } + +} diff --git a/yudao-module-mes/src/test/resources/sql/clean.sql b/yudao-module-mes/src/test/resources/sql/clean.sql index 5ac2653..b458b3c 100644 --- a/yudao-module-mes/src/test/resources/sql/clean.sql +++ b/yudao-module-mes/src/test/resources/sql/clean.sql @@ -4,3 +4,8 @@ DELETE FROM "mes_md_auto_code_record"; DELETE FROM "mes_wm_arrival_notice"; DELETE FROM "mes_wm_outsource_receipt"; DELETE FROM "mes_qc_iqc"; +DELETE FROM "mes_qc_ipqc"; +DELETE FROM "mes_pro_feedback"; +DELETE FROM "mes_wm_product_produce"; +DELETE FROM "mes_wm_product_produce_line"; +DELETE FROM "mes_wm_product_produce_detail"; diff --git a/yudao-module-mes/src/test/resources/sql/create_tables.sql b/yudao-module-mes/src/test/resources/sql/create_tables.sql index 3e38115..1d73cd9 100644 --- a/yudao-module-mes/src/test/resources/sql/create_tables.sql +++ b/yudao-module-mes/src/test/resources/sql/create_tables.sql @@ -146,3 +146,154 @@ CREATE TABLE IF NOT EXISTS "mes_qc_iqc" ( "tenant_id" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("id") ); + +-- ---------------------------- +-- MES 过程检验单(IPQC) +-- ---------------------------- +CREATE TABLE IF NOT EXISTS "mes_qc_ipqc" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "code" varchar(64) DEFAULT NULL, + "name" varchar(500) DEFAULT NULL, + "type" tinyint DEFAULT NULL, + "template_id" bigint DEFAULT NULL, + "source_doc_type" int DEFAULT NULL, + "source_doc_id" bigint DEFAULT NULL, + "source_line_id" bigint DEFAULT NULL, + "work_order_id" bigint DEFAULT NULL, + "task_id" bigint DEFAULT NULL, + "workstation_id" bigint DEFAULT NULL, + "process_id" bigint DEFAULT NULL, + "item_id" bigint DEFAULT NULL, + "check_quantity" decimal(14,2) DEFAULT NULL, + "qualified_quantity" decimal(14,2) DEFAULT NULL, + "unqualified_quantity" decimal(14,2) DEFAULT NULL, + "labor_scrap_quantity" decimal(14,2) DEFAULT NULL, + "material_scrap_quantity" decimal(14,2) DEFAULT NULL, + "other_scrap_quantity" decimal(14,2) DEFAULT NULL, + "critical_rate" decimal(14,2) DEFAULT NULL, + "major_rate" decimal(14,2) DEFAULT NULL, + "minor_rate" decimal(14,2) DEFAULT NULL, + "critical_quantity" int DEFAULT NULL, + "major_quantity" int DEFAULT NULL, + "minor_quantity" int DEFAULT NULL, + "check_result" tinyint DEFAULT NULL, + "inspect_date" timestamp DEFAULT NULL, + "inspector_user_id" bigint 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_pro_feedback" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "code" varchar(64) DEFAULT NULL, + "type" tinyint DEFAULT NULL, + "channel" varchar(64) DEFAULT NULL, + "feedback_time" timestamp DEFAULT NULL, + "workstation_id" bigint DEFAULT NULL, + "route_id" bigint DEFAULT NULL, + "process_id" bigint DEFAULT NULL, + "work_order_id" bigint DEFAULT NULL, + "task_id" bigint DEFAULT NULL, + "item_id" bigint DEFAULT NULL, + "expire_date" timestamp DEFAULT NULL, + "lot_number" varchar(64) DEFAULT NULL, + "scheduled_quantity" decimal(14,2) DEFAULT NULL, + "feedback_quantity" decimal(14,2) DEFAULT NULL, + "qualified_quantity" decimal(14,2) DEFAULT NULL, + "unqualified_quantity" decimal(14,2) DEFAULT NULL, + "uncheck_quantity" decimal(14,2) DEFAULT NULL, + "labor_scrap_quantity" decimal(14,2) DEFAULT NULL, + "material_scrap_quantity" decimal(14,2) DEFAULT NULL, + "other_scrap_quantity" decimal(14,2) DEFAULT NULL, + "feedback_user_id" bigint DEFAULT NULL, + "approve_user_id" bigint 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_wm_product_produce" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "work_order_id" bigint DEFAULT NULL, + "feedback_id" bigint DEFAULT NULL, + "task_id" bigint DEFAULT NULL, + "workstation_id" bigint DEFAULT NULL, + "process_id" bigint DEFAULT NULL, + "produce_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_wm_product_produce_line" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "produce_id" bigint DEFAULT NULL, + "feedback_id" bigint DEFAULT NULL, + "item_id" bigint DEFAULT NULL, + "quantity" decimal(12,2) DEFAULT NULL, + "batch_id" bigint DEFAULT NULL, + "batch_code" varchar(255) DEFAULT NULL, + "expire_date" timestamp DEFAULT NULL, + "lot_number" varchar(128) DEFAULT NULL, + "quality_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_wm_product_produce_detail" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "produce_id" bigint DEFAULT NULL, + "line_id" bigint DEFAULT NULL, + "item_id" bigint DEFAULT NULL, + "quantity" decimal(12,2) 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") +); -- libgit2 0.21.4