Commit 0c3c271c54189712d22fe10de8efed869c47f4c6

Authored by YunaiV
1 parent 3cb4adf5

✨ feat(sales): 新增发货通知单行ID字段及相关校验逻辑

在销售出库单行中新增发货通知单行ID字段,并实现相关的校验逻辑,以确保出库单与发货通知单行之间的关联性和一致性。此变更提升了系统的完整性和数据一致性。
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 { @@ -16,6 +16,9 @@ public class MesWmProductSalesLineRespVO {
16 @Schema(description = "出库单ID", example = "1") 16 @Schema(description = "出库单ID", example = "1")
17 private Long salesId; 17 private Long salesId;
18 18
  19 + @Schema(description = "发货通知单行ID", example = "1")
  20 + private Long noticeLineId;
  21 +
19 @Schema(description = "物料ID", example = "1") 22 @Schema(description = "物料ID", example = "1")
20 private Long itemId; 23 private Long itemId;
21 24
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 { @@ -17,6 +17,9 @@ public class MesWmProductSalesLineSaveReqVO {
17 @NotNull(message = "出库单ID不能为空") 17 @NotNull(message = "出库单ID不能为空")
18 private Long salesId; 18 private Long salesId;
19 19
  20 + @Schema(description = "发货通知单行ID", example = "1")
  21 + private Long noticeLineId;
  22 +
20 @Schema(description = "物料ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 23 @Schema(description = "物料ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
21 @NotNull(message = "物料不能为空") 24 @NotNull(message = "物料不能为空")
22 private Long itemId; 25 private Long itemId;
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; @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
5 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; 5 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO;
6 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
7 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; 7 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
  8 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeLineDO;
8 import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; 9 import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
9 import com.baomidou.mybatisplus.annotation.KeySequence; 10 import com.baomidou.mybatisplus.annotation.KeySequence;
10 import com.baomidou.mybatisplus.annotation.TableId; 11 import com.baomidou.mybatisplus.annotation.TableId;
@@ -40,6 +41,12 @@ public class MesWmProductSalesLineDO extends BaseDO { @@ -40,6 +41,12 @@ public class MesWmProductSalesLineDO extends BaseDO {
40 */ 41 */
41 private Long salesId; 42 private Long salesId;
42 /** 43 /**
  44 + * 发货通知单行ID
  45 + *
  46 + * 关联 {@link MesWmSalesNoticeLineDO#getId()}
  47 + */
  48 + private Long noticeLineId;
  49 + /**
43 * 物料ID 50 * 物料ID
44 * 51 *
45 * 关联 {@link MesMdItemDO#getId()} 52 * 关联 {@link MesMdItemDO#getId()}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -619,6 +619,18 @@ public interface ErrorCodeConstants { @@ -619,6 +619,18 @@ public interface ErrorCodeConstants {
619 ErrorCode WM_PRODUCT_SALES_LINE_QUANTITY_INVALID = new ErrorCode(1_040_714_014, "出库数量必须大于 0"); 619 ErrorCode WM_PRODUCT_SALES_LINE_QUANTITY_INVALID = new ErrorCode(1_040_714_014, "出库数量必须大于 0");
620 ErrorCode WM_PRODUCT_SALES_DETAIL_LINE_NOT_MATCH = new ErrorCode(1_040_714_015, "拣货明细不属于指定的销售出库单"); 620 ErrorCode WM_PRODUCT_SALES_DETAIL_LINE_NOT_MATCH = new ErrorCode(1_040_714_015, "拣货明细不属于指定的销售出库单");
621 ErrorCode WM_PRODUCT_SALES_DETAIL_ITEM_MISMATCH = new ErrorCode(1_040_714_016, "拣货明细的物料与销售出库单行的物料不一致"); 621 ErrorCode WM_PRODUCT_SALES_DETAIL_ITEM_MISMATCH = new ErrorCode(1_040_714_016, "拣货明细的物料与销售出库单行的物料不一致");
  622 + ErrorCode WM_PRODUCT_SALES_LINE_SALES_NOTICE_LINE_REQUIRED = new ErrorCode(1_040_714_017,
  623 + "出库单关联了发货通知单,必须选择发货通知单行");
  624 + ErrorCode WM_PRODUCT_SALES_LINE_SALES_NOTICE_LINE_NOT_ALLOWED = new ErrorCode(1_040_714_018,
  625 + "出库单未关联发货通知单,不能选择发货通知单行");
  626 + ErrorCode WM_PRODUCT_SALES_LINE_NOTICE_LINE_ITEM_MISMATCH = new ErrorCode(1_040_714_030,
  627 + "出库行物料与发货通知单行物料不一致");
  628 + ErrorCode WM_PRODUCT_SALES_LINE_NOTICE_LINE_QUANTITY_MISMATCH = new ErrorCode(1_040_714_031,
  629 + "出库行数量与发货通知单行数量不一致");
  630 + ErrorCode WM_PRODUCT_SALES_LINE_NOTICE_LINE_BATCH_MISMATCH = new ErrorCode(1_040_714_032,
  631 + "出库行批次号与发货通知单行批次号不一致");
  632 + ErrorCode WM_PRODUCT_SALES_LINE_NOTICE_LINE_OQC_MISMATCH = new ErrorCode(1_040_714_033,
  633 + "出库行 OQC 检验标识与发货通知单行不一致");
622 634
623 // ========== MES 仓库管理-杂项出库单(1-040-715-000) ========== 635 // ========== MES 仓库管理-杂项出库单(1-040-715-000) ==========
624 ErrorCode WM_MISC_ISSUE_NOT_EXISTS = new ErrorCode(1_040_715_000, "杂项出库单不存在"); 636 ErrorCode WM_MISC_ISSUE_NOT_EXISTS = new ErrorCode(1_040_715_000, "杂项出库单不存在");
@@ -651,6 +663,7 @@ public interface ErrorCodeConstants { @@ -651,6 +663,7 @@ public interface ErrorCodeConstants {
651 ErrorCode WM_SALES_NOTICE_CLIENT_MISMATCH = new ErrorCode(1_040_720_005, "发货通知单的客户与当前单据不一致"); 663 ErrorCode WM_SALES_NOTICE_CLIENT_MISMATCH = new ErrorCode(1_040_720_005, "发货通知单的客户与当前单据不一致");
652 ErrorCode WM_SALES_NOTICE_LINE_NOT_EXISTS = new ErrorCode(1_040_720_010, "发货通知单行不存在"); 664 ErrorCode WM_SALES_NOTICE_LINE_NOT_EXISTS = new ErrorCode(1_040_720_010, "发货通知单行不存在");
653 ErrorCode WM_SALES_NOTICE_LINE_EMPTY = new ErrorCode(1_040_720_011, "发货通知单行为空,不能提交"); 665 ErrorCode WM_SALES_NOTICE_LINE_EMPTY = new ErrorCode(1_040_720_011, "发货通知单行为空,不能提交");
  666 + ErrorCode WM_SALES_NOTICE_LINE_NOT_MATCH = new ErrorCode(1_040_720_012, "发货通知单行不属于指定的发货通知单");
654 667
655 // ========== MES 仓库管理-条码配置(1-040-730-000) ========== 668 // ========== MES 仓库管理-条码配置(1-040-730-000) ==========
656 ErrorCode WM_BARCODE_CONFIG_NOT_EXISTS = new ErrorCode(1_040_730_000, "条码配置不存在"); 669 ErrorCode WM_BARCODE_CONFIG_NOT_EXISTS = new ErrorCode(1_040_730_000, "条码配置不存在");
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; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.mes.service.wm.productsales;
3 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 3 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
4 import cn.hutool.core.collection.CollUtil; 4 import cn.hutool.core.collection.CollUtil;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.core.util.ObjUtil;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLinePageReqVO; 8 import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLinePageReqVO;
8 import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLineSaveReqVO; 9 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; @@ -14,6 +15,8 @@ import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
14 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; 15 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
15 import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; 16 import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
16 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; 17 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
  18 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeLineDO;
  19 +import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeLineService;
17 import jakarta.annotation.Resource; 20 import jakarta.annotation.Resource;
18 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
19 import org.springframework.context.annotation.Lazy; 22 import org.springframework.context.annotation.Lazy;
@@ -26,7 +29,7 @@ import java.util.Objects; @@ -26,7 +29,7 @@ import java.util.Objects;
26 29
27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 30 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
28 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 31 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
29 -import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_PRODUCT_SALES_LINE_NOT_EXISTS; 32 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
30 33
31 /** 34 /**
32 * MES 销售出库单行 Service 实现类 35 * MES 销售出库单行 Service 实现类
@@ -49,6 +52,9 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe @@ -49,6 +52,9 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe
49 @Resource 52 @Resource
50 @Lazy 53 @Lazy
51 private MesWmProductSalesService productSalesService; 54 private MesWmProductSalesService productSalesService;
  55 + @Resource
  56 + @Lazy
  57 + private MesWmSalesNoticeLineService salesNoticeLineService;
52 58
53 @Override 59 @Override
54 public Long createProductSalesLine(MesWmProductSalesLineSaveReqVO createReqVO) { 60 public Long createProductSalesLine(MesWmProductSalesLineSaveReqVO createReqVO) {
@@ -143,13 +149,55 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe @@ -143,13 +149,55 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe
143 */ 149 */
144 private void validateLineSaveData(MesWmProductSalesLineSaveReqVO reqVO) { 150 private void validateLineSaveData(MesWmProductSalesLineSaveReqVO reqVO) {
145 // 校验主单存在且为草稿状态 151 // 校验主单存在且为草稿状态
146 - productSalesService.validateProductSalesExistsAndDraft(reqVO.getSalesId()); 152 + MesWmProductSalesDO sales = productSalesService.validateProductSalesExistsAndDraft(reqVO.getSalesId());
147 // 校验物料存在 153 // 校验物料存在
148 itemService.validateItemExistsAndEnable(reqVO.getItemId()); 154 itemService.validateItemExistsAndEnable(reqVO.getItemId());
  155 + // 校验关联发货通知单行(存在性 + 归属 + 字段一致性)
  156 + validateSalesNoticeLine(sales, reqVO);
149 // 根据 batchCode 解析 batchId 157 // 根据 batchCode 解析 batchId
150 fillBatchId(reqVO); 158 fillBatchId(reqVO);
151 } 159 }
152 160
  161 + /**
  162 + * 校验发货通知单行
  163 + *
  164 + * @param sales 出库单
  165 + * @param reqVO 出库行请求
  166 + */
  167 + private void validateSalesNoticeLine(MesWmProductSalesDO sales, MesWmProductSalesLineSaveReqVO reqVO) {
  168 + Long noticeLineId = reqVO.getNoticeLineId();
  169 + // 情况一:如果出库单关联了发货通知单,则必须关联发货通知单行
  170 + if (sales.getNoticeId() != null) {
  171 + if (noticeLineId == null) {
  172 + throw exception(WM_PRODUCT_SALES_LINE_SALES_NOTICE_LINE_REQUIRED);
  173 + }
  174 + MesWmSalesNoticeLineDO noticeLine = salesNoticeLineService.validateSalesNoticeLineExists(
  175 + noticeLineId, sales.getNoticeId());
  176 + // 校验关键字段一致性:物料、数量、批次号、OQC 检验标识
  177 + if (ObjUtil.notEqual(reqVO.getItemId(), noticeLine.getItemId())) {
  178 + throw exception(WM_PRODUCT_SALES_LINE_NOTICE_LINE_ITEM_MISMATCH);
  179 + }
  180 + if (noticeLine.getQuantity() != null && reqVO.getQuantity() != null
  181 + && reqVO.getQuantity().compareTo(noticeLine.getQuantity()) != 0) {
  182 + throw exception(WM_PRODUCT_SALES_LINE_NOTICE_LINE_QUANTITY_MISMATCH);
  183 + }
  184 + if (StrUtil.isNotBlank(noticeLine.getBatchCode())
  185 + && ObjUtil.notEqual(reqVO.getBatchCode(), noticeLine.getBatchCode())) {
  186 + throw exception(WM_PRODUCT_SALES_LINE_NOTICE_LINE_BATCH_MISMATCH);
  187 + }
  188 + if (noticeLine.getOqcCheckFlag() != null
  189 + && ObjUtil.notEqual(reqVO.getOqcCheckFlag(), noticeLine.getOqcCheckFlag())) {
  190 + throw exception(WM_PRODUCT_SALES_LINE_NOTICE_LINE_OQC_MISMATCH);
  191 + }
  192 + return;
  193 + }
  194 +
  195 + // 情况二:如果出库单没有关联发货通知单,则不允许关联发货通知单行
  196 + if (noticeLineId != null) {
  197 + throw exception(WM_PRODUCT_SALES_LINE_SALES_NOTICE_LINE_NOT_ALLOWED);
  198 + }
  199 + }
  200 +
153 @Override 201 @Override
154 @Transactional(rollbackFor = Exception.class) 202 @Transactional(rollbackFor = Exception.class)
155 public void updateProductSalesLineWhenOqcFinish(Long id, Long oqcId, Integer checkResult) { 203 public void updateProductSalesLineWhenOqcFinish(Long id, Long oqcId, Integer checkResult) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineService.java
@@ -66,4 +66,21 @@ public interface MesWmSalesNoticeLineService { @@ -66,4 +66,21 @@ public interface MesWmSalesNoticeLineService {
66 */ 66 */
67 void deleteSalesNoticeLineByNoticeId(Long noticeId); 67 void deleteSalesNoticeLineByNoticeId(Long noticeId);
68 68
  69 + /**
  70 + * 校验发货通知单行存在
  71 + *
  72 + * @param id 行编号
  73 + * @return 发货通知单行
  74 + */
  75 + MesWmSalesNoticeLineDO validateSalesNoticeLineExists(Long id);
  76 +
  77 + /**
  78 + * 校验发货通知单行存在,并且属于指定的通知单
  79 + *
  80 + * @param lineId 行编号
  81 + * @param noticeId 通知单编号
  82 + * @return 发货通知单行
  83 + */
  84 + MesWmSalesNoticeLineDO validateSalesNoticeLineExists(Long lineId, Long noticeId);
  85 +
69 } 86 }
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 @@ -100,7 +100,8 @@ public class MesWmSalesNoticeLineServiceImpl implements MesWmSalesNoticeLineServ
100 salesNoticeLineMapper.deleteByNoticeId(noticeId); 100 salesNoticeLineMapper.deleteByNoticeId(noticeId);
101 } 101 }
102 102
103 - private MesWmSalesNoticeLineDO validateSalesNoticeLineExists(Long id) { 103 + @Override
  104 + public MesWmSalesNoticeLineDO validateSalesNoticeLineExists(Long id) {
104 MesWmSalesNoticeLineDO line = salesNoticeLineMapper.selectById(id); 105 MesWmSalesNoticeLineDO line = salesNoticeLineMapper.selectById(id);
105 if (line == null) { 106 if (line == null) {
106 throw exception(WM_SALES_NOTICE_LINE_NOT_EXISTS); 107 throw exception(WM_SALES_NOTICE_LINE_NOT_EXISTS);
@@ -108,6 +109,16 @@ public class MesWmSalesNoticeLineServiceImpl implements MesWmSalesNoticeLineServ @@ -108,6 +109,16 @@ public class MesWmSalesNoticeLineServiceImpl implements MesWmSalesNoticeLineServ
108 return line; 109 return line;
109 } 110 }
110 111
  112 + @Override
  113 + public MesWmSalesNoticeLineDO validateSalesNoticeLineExists(Long lineId, Long noticeId) {
  114 + MesWmSalesNoticeLineDO line = validateSalesNoticeLineExists(lineId);
  115 + // 进一步校验行的 noticeId 与传入的 noticeId 是否匹配
  116 + if (noticeId != null && ObjUtil.notEqual(line.getNoticeId(), noticeId)) {
  117 + throw exception(WM_SALES_NOTICE_LINE_NOT_MATCH);
  118 + }
  119 + return line;
  120 + }
  121 +
111 private void validateLineSaveData(MesWmSalesNoticeLineSaveReqVO reqVO) { 122 private void validateLineSaveData(MesWmSalesNoticeLineSaveReqVO reqVO) {
112 // 校验父单据存在且为草稿状态 123 // 校验父单据存在且为草稿状态
113 validateNoticeStatusDraft(reqVO.getNoticeId()); 124 validateNoticeStatusDraft(reqVO.getNoticeId());
yudao-module-mes/src/test/resources/sql/create_tables.sql
@@ -387,6 +387,7 @@ CREATE TABLE IF NOT EXISTS "mes_qc_rqc" ( @@ -387,6 +387,7 @@ CREATE TABLE IF NOT EXISTS "mes_qc_rqc" (
387 CREATE TABLE IF NOT EXISTS "mes_wm_product_sales_line" ( 387 CREATE TABLE IF NOT EXISTS "mes_wm_product_sales_line" (
388 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, 388 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
389 "sales_id" bigint NOT NULL, 389 "sales_id" bigint NOT NULL,
  390 + "notice_line_id" bigint DEFAULT NULL,
390 "item_id" bigint NOT NULL, 391 "item_id" bigint NOT NULL,
391 "quantity" decimal(20,6) NOT NULL, 392 "quantity" decimal(20,6) NOT NULL,
392 "batch_id" bigint DEFAULT NULL, 393 "batch_id" bigint DEFAULT NULL,