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 16 @Schema(description = "出库单ID", example = "1")
17 17 private Long salesId;
18 18  
  19 + @Schema(description = "发货通知单行ID", example = "1")
  20 + private Long noticeLineId;
  21 +
19 22 @Schema(description = "物料ID", example = "1")
20 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 17 @NotNull(message = "出库单ID不能为空")
18 18 private Long salesId;
19 19  
  20 + @Schema(description = "发货通知单行ID", example = "1")
  21 + private Long noticeLineId;
  22 +
20 23 @Schema(description = "物料ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
21 24 @NotNull(message = "物料不能为空")
22 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 5 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO;
6 6 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
7 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 9 import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
9 10 import com.baomidou.mybatisplus.annotation.KeySequence;
10 11 import com.baomidou.mybatisplus.annotation.TableId;
... ... @@ -40,6 +41,12 @@ public class MesWmProductSalesLineDO extends BaseDO {
40 41 */
41 42 private Long salesId;
42 43 /**
  44 + * 发货通知单行ID
  45 + *
  46 + * 关联 {@link MesWmSalesNoticeLineDO#getId()}
  47 + */
  48 + private Long noticeLineId;
  49 + /**
43 50 * 物料ID
44 51 *
45 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 619 ErrorCode WM_PRODUCT_SALES_LINE_QUANTITY_INVALID = new ErrorCode(1_040_714_014, "出库数量必须大于 0");
620 620 ErrorCode WM_PRODUCT_SALES_DETAIL_LINE_NOT_MATCH = new ErrorCode(1_040_714_015, "拣货明细不属于指定的销售出库单");
621 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 635 // ========== MES 仓库管理-杂项出库单(1-040-715-000) ==========
624 636 ErrorCode WM_MISC_ISSUE_NOT_EXISTS = new ErrorCode(1_040_715_000, "杂项出库单不存在");
... ... @@ -651,6 +663,7 @@ public interface ErrorCodeConstants {
651 663 ErrorCode WM_SALES_NOTICE_CLIENT_MISMATCH = new ErrorCode(1_040_720_005, "发货通知单的客户与当前单据不一致");
652 664 ErrorCode WM_SALES_NOTICE_LINE_NOT_EXISTS = new ErrorCode(1_040_720_010, "发货通知单行不存在");
653 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 668 // ========== MES 仓库管理-条码配置(1-040-730-000) ==========
656 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 3 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
4 4 import cn.hutool.core.collection.CollUtil;
5 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.core.util.ObjUtil;
6 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 8 import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLinePageReqVO;
8 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 15 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
15 16 import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
16 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 20 import jakarta.annotation.Resource;
18 21 import lombok.extern.slf4j.Slf4j;
19 22 import org.springframework.context.annotation.Lazy;
... ... @@ -26,7 +29,7 @@ import java.util.Objects;
26 29  
27 30 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
28 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 35 * MES 销售出库单行 Service 实现类
... ... @@ -49,6 +52,9 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe
49 52 @Resource
50 53 @Lazy
51 54 private MesWmProductSalesService productSalesService;
  55 + @Resource
  56 + @Lazy
  57 + private MesWmSalesNoticeLineService salesNoticeLineService;
52 58  
53 59 @Override
54 60 public Long createProductSalesLine(MesWmProductSalesLineSaveReqVO createReqVO) {
... ... @@ -143,13 +149,55 @@ public class MesWmProductSalesLineServiceImpl implements MesWmProductSalesLineSe
143 149 */
144 150 private void validateLineSaveData(MesWmProductSalesLineSaveReqVO reqVO) {
145 151 // 校验主单存在且为草稿状态
146   - productSalesService.validateProductSalesExistsAndDraft(reqVO.getSalesId());
  152 + MesWmProductSalesDO sales = productSalesService.validateProductSalesExistsAndDraft(reqVO.getSalesId());
147 153 // 校验物料存在
148 154 itemService.validateItemExistsAndEnable(reqVO.getItemId());
  155 + // 校验关联发货通知单行(存在性 + 归属 + 字段一致性)
  156 + validateSalesNoticeLine(sales, reqVO);
149 157 // 根据 batchCode 解析 batchId
150 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 201 @Override
154 202 @Transactional(rollbackFor = Exception.class)
155 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 66 */
67 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 100 salesNoticeLineMapper.deleteByNoticeId(noticeId);
101 101 }
102 102  
103   - private MesWmSalesNoticeLineDO validateSalesNoticeLineExists(Long id) {
  103 + @Override
  104 + public MesWmSalesNoticeLineDO validateSalesNoticeLineExists(Long id) {
104 105 MesWmSalesNoticeLineDO line = salesNoticeLineMapper.selectById(id);
105 106 if (line == null) {
106 107 throw exception(WM_SALES_NOTICE_LINE_NOT_EXISTS);
... ... @@ -108,6 +109,16 @@ public class MesWmSalesNoticeLineServiceImpl implements MesWmSalesNoticeLineServ
108 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 122 private void validateLineSaveData(MesWmSalesNoticeLineSaveReqVO reqVO) {
112 123 // 校验父单据存在且为草稿状态
113 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 387 CREATE TABLE IF NOT EXISTS "mes_wm_product_sales_line" (
388 388 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
389 389 "sales_id" bigint NOT NULL,
  390 + "notice_line_id" bigint DEFAULT NULL,
390 391 "item_id" bigint NOT NULL,
391 392 "quantity" decimal(20,6) NOT NULL,
392 393 "batch_id" bigint DEFAULT NULL,
... ...