diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/line/MesWmProductSalesLineRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/line/MesWmProductSalesLineRespVO.java index 19eff65..67e244b 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/line/MesWmProductSalesLineRespVO.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/line/MesWmProductSalesLineRespVO.java @@ -16,6 +16,9 @@ public class MesWmProductSalesLineRespVO { @Schema(description = "出库单ID", example = "1") private Long salesId; + @Schema(description = "发货通知单行ID", example = "1") + private Long noticeLineId; + @Schema(description = "物料ID", example = "1") private Long itemId; diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/line/MesWmProductSalesLineSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/line/MesWmProductSalesLineSaveReqVO.java index 444c2b8..67e7c3b 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/line/MesWmProductSalesLineSaveReqVO.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/line/MesWmProductSalesLineSaveReqVO.java @@ -17,6 +17,9 @@ public class MesWmProductSalesLineSaveReqVO { @NotNull(message = "出库单ID不能为空") private Long salesId; + @Schema(description = "发货通知单行ID", example = "1") + private Long noticeLineId; + @Schema(description = "物料ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "物料不能为空") private Long itemId; diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesLineDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesLineDO.java index cf390f7..f241e04 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesLineDO.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesLineDO.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeLineDO; import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; @@ -40,6 +41,12 @@ public class MesWmProductSalesLineDO extends BaseDO { */ private Long salesId; /** + * 发货通知单行ID + * + * 关联 {@link MesWmSalesNoticeLineDO#getId()} + */ + private Long noticeLineId; + /** * 物料ID * * 关联 {@link MesMdItemDO#getId()} diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java index a0881d9..14a221b 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java @@ -619,6 +619,18 @@ public interface ErrorCodeConstants { ErrorCode WM_PRODUCT_SALES_LINE_QUANTITY_INVALID = new ErrorCode(1_040_714_014, "出库数量必须大于 0"); ErrorCode WM_PRODUCT_SALES_DETAIL_LINE_NOT_MATCH = new ErrorCode(1_040_714_015, "拣货明细不属于指定的销售出库单"); ErrorCode WM_PRODUCT_SALES_DETAIL_ITEM_MISMATCH = new ErrorCode(1_040_714_016, "拣货明细的物料与销售出库单行的物料不一致"); + ErrorCode WM_PRODUCT_SALES_LINE_SALES_NOTICE_LINE_REQUIRED = new ErrorCode(1_040_714_017, + "出库单关联了发货通知单,必须选择发货通知单行"); + ErrorCode WM_PRODUCT_SALES_LINE_SALES_NOTICE_LINE_NOT_ALLOWED = new ErrorCode(1_040_714_018, + "出库单未关联发货通知单,不能选择发货通知单行"); + ErrorCode WM_PRODUCT_SALES_LINE_NOTICE_LINE_ITEM_MISMATCH = new ErrorCode(1_040_714_030, + "出库行物料与发货通知单行物料不一致"); + ErrorCode WM_PRODUCT_SALES_LINE_NOTICE_LINE_QUANTITY_MISMATCH = new ErrorCode(1_040_714_031, + "出库行数量与发货通知单行数量不一致"); + ErrorCode WM_PRODUCT_SALES_LINE_NOTICE_LINE_BATCH_MISMATCH = new ErrorCode(1_040_714_032, + "出库行批次号与发货通知单行批次号不一致"); + ErrorCode WM_PRODUCT_SALES_LINE_NOTICE_LINE_OQC_MISMATCH = new ErrorCode(1_040_714_033, + "出库行 OQC 检验标识与发货通知单行不一致"); // ========== MES 仓库管理-杂项出库单(1-040-715-000) ========== ErrorCode WM_MISC_ISSUE_NOT_EXISTS = new ErrorCode(1_040_715_000, "杂项出库单不存在"); @@ -651,6 +663,7 @@ public interface ErrorCodeConstants { ErrorCode WM_SALES_NOTICE_CLIENT_MISMATCH = new ErrorCode(1_040_720_005, "发货通知单的客户与当前单据不一致"); ErrorCode WM_SALES_NOTICE_LINE_NOT_EXISTS = new ErrorCode(1_040_720_010, "发货通知单行不存在"); ErrorCode WM_SALES_NOTICE_LINE_EMPTY = new ErrorCode(1_040_720_011, "发货通知单行为空,不能提交"); + ErrorCode WM_SALES_NOTICE_LINE_NOT_MATCH = new ErrorCode(1_040_720_012, "发货通知单行不属于指定的发货通知单"); // ========== MES 仓库管理-条码配置(1-040-730-000) ========== ErrorCode WM_BARCODE_CONFIG_NOT_EXISTS = new ErrorCode(1_040_730_000, "条码配置不存在"); diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java index 5741b3d..7ee9711 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java @@ -3,6 +3,7 @@ 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.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; @@ -14,6 +15,8 @@ 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; @@ -26,7 +29,7 @@ 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 实现类 @@ -49,6 +52,9 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe @Resource @Lazy private MesWmProductSalesService productSalesService; + @Resource + @Lazy + private MesWmSalesNoticeLineService salesNoticeLineService; @Override public Long createProductSalesLine(MesWmProductSalesLineSaveReqVO createReqVO) { @@ -143,13 +149,55 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe */ private void validateLineSaveData(MesWmProductSalesLineSaveReqVO reqVO) { // 校验主单存在且为草稿状态 - productSalesService.validateProductSalesExistsAndDraft(reqVO.getSalesId()); + 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/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineService.java index 4fc969d..70aec1d 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineService.java +++ b/yudao-module-mes/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/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineServiceImpl.java index 2a522ce..18fb88d 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineServiceImpl.java @@ -100,7 +100,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,6 +109,16 @@ 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()); 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 2d4deef..4565b76 100644 --- a/yudao-module-mes/src/test/resources/sql/create_tables.sql +++ b/yudao-module-mes/src/test/resources/sql/create_tables.sql @@ -387,6 +387,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,