From 6fa82a1ad7c3ae061ef499f0d0806a16846e122b Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 10 十一月 2026 16:55:02 +0800
Subject: [PATCH] feat(mes): 来料检验与不合格退货闭环优化

---
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java |  199 ++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 178 insertions(+), 21 deletions(-)

diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
index 10cef64..3a95fea 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -29,7 +29,9 @@
 import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestMapper;
 import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
 import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
+import cn.iocoder.yudao.module.erp.enums.ErpPaymentMethodEnum;
 import cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderInStatusEnum;
+import cn.iocoder.yudao.module.erp.enums.ErpPurchaseTypeEnum;
 import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
 import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
 import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
@@ -117,8 +119,16 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createPurchaseOrder(ErpPurchaseOrderSaveReqVO createReqVO) {
+        return createPurchaseOrder(createReqVO, true);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createPurchaseOrder(ErpPurchaseOrderSaveReqVO createReqVO, boolean submit) {
         // 1.1 鏍¢獙璁㈠崟椤圭殑鏈夋晥鎬�
         List<ErpPurchaseOrderItemDO> purchaseOrderItems = validatePurchaseOrderItems(createReqVO.getItems());
+        // 1.1.1 鏍¢獙閲囪喘鍙拌处瀛楁
+        validateLedgerFields(createReqVO);
         // 1.2 鏍¢獙渚涘簲鍟�
         supplierService.validateSupplier(createReqVO.getSupplierId());
         // 1.3 鏍¢獙缁撶畻璐︽埛
@@ -126,15 +136,16 @@
             accountService.validateAccount(createReqVO.getAccountId());
         }
         // 1.4 鐢熸垚璁㈠崟鍙凤紝骞舵牎楠屽敮涓�鎬�
-        String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX);
-        if (purchaseOrderMapper.selectByNo(no) != null) {
-            throw exception(PURCHASE_ORDER_NO_EXISTS);
-        }
+        String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX,
+                candidate -> purchaseOrderMapper.selectByNo(candidate) != null);
 
-        // 2.1 鎻掑叆璁㈠崟锛堥噰璐鍗曞垱寤哄悗涓烘湭瀹℃牳鐘舵�侊紝闇�瑕佹彁浜ゅ鎵癸級
+        // 2.1 鎻掑叆璁㈠崟锛坰ubmit=false 鏃朵繚瀛樹负鑽夌锛岀瓑寰呬笟鍔$‘璁ゅ悗鎵嬪姩鎻愪氦瀹℃壒锛�
         ErpPurchaseOrderDO purchaseOrder = BeanUtils.toBean(createReqVO, ErpPurchaseOrderDO.class, in -> in
-                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())
+                .setNo(no).setStatus(submit ? ErpAuditStatus.PROCESS.getStatus() : ErpAuditStatus.DRAFT.getStatus())
                 .setInCount(BigDecimal.ZERO).setInStatus(ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus()));
+        if (purchaseOrder.getDepositPrice() == null) {
+            purchaseOrder.setDepositPrice(BigDecimal.ZERO);
+        }
         calculateTotalPrice(purchaseOrder, purchaseOrderItems);
         purchaseOrderMapper.insert(purchaseOrder);
         // 2.2 鎻掑叆璁㈠崟椤�
@@ -145,7 +156,9 @@
                 purchaseOrder.getId(), createReqVO.getBlobIds());
 
         // 3. 鍒涘缓鍚庤嚜鍔ㄦ彁浜ゅ鎵瑰埌鍗忓悓鍔炲叕锛堝鎵瑰湪鍗忓悓鍔炲叕瀹屾垚锛屼笉鍦ㄦ湰椤甸潰瀹℃壒锛�
-        submitPurchaseOrder(purchaseOrder.getId(), PURCHASE_ORDER_APPROVE_PROCESS_DEFINITION_KEY, getLoginUserId());
+        if (submit) {
+            submitPurchaseOrder(purchaseOrder.getId(), PURCHASE_ORDER_APPROVE_PROCESS_DEFINITION_KEY, getLoginUserId());
+        }
 
         return purchaseOrder.getId();
     }
@@ -167,9 +180,14 @@
         }
         // 1.4 鏍¢獙璁㈠崟椤圭殑鏈夋晥鎬�
         List<ErpPurchaseOrderItemDO> purchaseOrderItems = validatePurchaseOrderItems(updateReqVO.getItems());
+        // 1.5 鏍¢獙閲囪喘鍙拌处瀛楁
+        validateLedgerFields(updateReqVO);
 
         // 2.1 鏇存柊璁㈠崟
         ErpPurchaseOrderDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseOrderDO.class);
+        if (updateObj.getDepositPrice() == null) {
+            updateObj.setDepositPrice(BigDecimal.ZERO);
+        }
         calculateTotalPrice(updateObj, purchaseOrderItems);
         purchaseOrderMapper.updateById(updateObj);
         // 2.2 鏇存柊璁㈠崟椤�
@@ -276,20 +294,27 @@
             }
             ErpPurchaseOrderSaveReqVO.Item itemVO = new ErpPurchaseOrderSaveReqVO.Item();
             itemVO.setProductId(item.getId());
+            itemVO.setProductCode(item.getCode());
             itemVO.setProductUnitId(item.getUnitMeasureId());
             itemVO.setProductPrice(row.getProductPrice() == null ? BigDecimal.ZERO : row.getProductPrice());
             itemVO.setCount(row.getCount());
             itemVO.setTaxPercent(row.getTaxPercent());
             itemVO.setRemark(row.getRemark());
-            itemVO.setQcCheckFlag("鏄�".equals(StrUtil.trim(row.getQcCheckFlag())));
+            // 閲囪喘鍙拌处涓嶅啀鎻愪緵銆屾槸鍚﹁川妫�銆嶉�夋嫨锛氬鍏ョ殑鏄庣粏涓�寰嬮渶瑕佹潵鏂欐楠�
+            itemVO.setQcCheckFlag(true);
             items.add(itemVO);
         }
 
         // 3. 鍗曞彿锛氬~浜嗗氨鐢紙鏍¢獙鍞竴锛夛紝娌″~鑷姩鐢熸垚
-        String no = StrUtil.isNotBlank(head.getNo()) ? head.getNo().trim()
-                : noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX);
-        if (purchaseOrderMapper.selectByNo(no) != null) {
-            throw new IllegalArgumentException("閲囪喘鍗曠紪鍙峰凡瀛樺湪锛�" + no);
+        String no;
+        if (StrUtil.isNotBlank(head.getNo())) {
+            no = head.getNo().trim();
+            if (purchaseOrderMapper.selectByNo(no) != null) {
+                throw new IllegalArgumentException("閲囪喘鍗曠紪鍙峰凡瀛樺湪锛�" + no);
+            }
+        } else {
+            no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX,
+                    candidate -> purchaseOrderMapper.selectByNo(candidate) != null);
         }
 
         // 4. 缁勮涓昏〃锛氬嬀閫夆�滄槸鍚﹀鏍糕�� => 宸插鏍�(20)锛屽惁鍒欐湭瀹℃牳(10)
@@ -297,6 +322,11 @@
         purchaseOrder.setNo(no);
         purchaseOrder.setSupplierId(supplierId);
         purchaseOrder.setOrderTime(parseImportOrderTime(head.getOrderTime()));
+        purchaseOrder.setPurchaseType(parsePurchaseType(head.getPurchaseType()));
+        purchaseOrder.setContractNo(StrUtil.trimToNull(head.getContractNo()));
+        purchaseOrder.setProjectName(StrUtil.trimToNull(head.getProjectName()));
+        purchaseOrder.setPaymentDate(parseImportDateTime(head.getPaymentDate(), "浠樻鏃ユ湡"));
+        purchaseOrder.setPaymentMethod(parsePaymentMethod(head.getPaymentMethod()));
         purchaseOrder.setRemark(head.getRemark());
         purchaseOrder.setStatus(Boolean.TRUE.equals(directApprove)
                 ? ErpAuditStatus.APPROVE.getStatus() : ErpAuditStatus.PROCESS.getStatus());
@@ -316,8 +346,16 @@
      * 瑙f瀽瀵煎叆鐨勯噰璐椂闂达紝鍏煎澶氱甯歌鏍煎紡锛涗负绌哄彇褰撳墠鏃堕棿
      */
     private LocalDateTime parseImportOrderTime(String text) {
+        LocalDateTime value = parseImportDateTime(text, "閲囪喘鏃堕棿");
+        return value != null ? value : LocalDateTime.now();
+    }
+
+    /**
+     * 瑙f瀽瀵煎叆鐨勬棩鏈熸椂闂达紝鍏煎澶氱甯歌鏍煎紡锛涗负绌鸿繑鍥� null
+     */
+    private LocalDateTime parseImportDateTime(String text, String fieldName) {
         if (StrUtil.isBlank(text)) {
-            return LocalDateTime.now();
+            return null;
         }
         String value = text.trim();
         for (String pattern : new String[]{"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy/MM/dd HH:mm:ss"}) {
@@ -334,7 +372,33 @@
                 // 缁х画灏濊瘯涓嬩竴涓牸寮�
             }
         }
-        throw new IllegalArgumentException("閲囪喘鏃堕棿鏍煎紡涓嶆纭細" + text + "锛岀ず渚� 2026-01-01 10:00:00");
+        throw new IllegalArgumentException(fieldName + "鏍煎紡涓嶆纭細" + text + "锛岀ず渚� 2026-01-01 10:00:00");
+    }
+
+    /**
+     * 瑙f瀽瀵煎叆鐨勯噰璐被鍨嬪悕绉帮紱涓虹┖鎴栨棤娉曡瘑鍒繑鍥� null
+     */
+    private Integer parsePurchaseType(String text) {
+        if (StrUtil.isBlank(text)) {
+            return null;
+        }
+        String value = text.trim();
+        return Arrays.stream(ErpPurchaseTypeEnum.values())
+                .filter(item -> item.getName().equals(value))
+                .map(ErpPurchaseTypeEnum::getType).findFirst().orElse(null);
+    }
+
+    /**
+     * 瑙f瀽瀵煎叆鐨勪粯娆炬柟寮忓悕绉帮紱涓虹┖鎴栨棤娉曡瘑鍒繑鍥� null
+     */
+    private Integer parsePaymentMethod(String text) {
+        if (StrUtil.isBlank(text)) {
+            return null;
+        }
+        String value = text.trim();
+        return Arrays.stream(ErpPaymentMethodEnum.values())
+                .filter(item -> item.getName().equals(value))
+                .map(ErpPaymentMethodEnum::getType).findFirst().orElse(null);
     }
 
     /**
@@ -351,7 +415,8 @@
         purchaseOrder.setTotalCount(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getCount, BigDecimal::add));
         purchaseOrder.setTotalProductPrice(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
         purchaseOrder.setTotalTaxPrice(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO));
-        purchaseOrder.setTotalPrice(purchaseOrder.getTotalProductPrice().add(purchaseOrder.getTotalTaxPrice()));
+        // 鍗曚环涓哄惈绋庡崟浠凤細浠风◣鍚堣 = 鍗曚环 * 鏁伴噺锛岀◣棰濆凡鍖呭惈鍦ㄥ崟浠蜂腑锛屼笉鍐嶉澶栧姞绋�
+        purchaseOrder.setTotalPrice(purchaseOrder.getTotalProductPrice());
         // 璁$畻浼樻儬浠锋牸
         if (purchaseOrder.getDiscountPercent() == null) {
             purchaseOrder.setDiscountPercent(BigDecimal.ZERO);
@@ -389,16 +454,68 @@
         }
     }
 
+    /**
+     * 鎻愪氦瀹℃壒鍓嶆牎楠岄噰璐彴璐︽暟鎹槸鍚﹀凡琛ラ綈
+     *
+     * <p>閲囪喘鐢宠鐩存帴鐢熸垚鐨勫彴璐﹀彧鏈夌墿鏂欎笌鏁伴噺锛堥浂閲戦銆佹棤渚涘簲鍟嗭級锛�
+     * 閲囪喘鍛橀渶瑕佸湪鍙拌处閲岃ˉ褰曚緵搴斿晢銆佸崟浠枫�佷粯娆炬棩鏈熷悗鎵嶈兘鎻愪氦瀹℃壒銆傞�愰」鏍¢獙锛屽懡涓涓�澶勫嵆鎻愮ず鍏蜂綋鎬庝箞鏀广��</p>
+     *
+     * @param purchaseOrder 閲囪喘鍙拌处
+     */
+    private void validatePurchaseOrderReadyForSubmit(ErpPurchaseOrderDO purchaseOrder) {
+        // 2.1 渚涘簲鍟嗗繀閫�
+        if (purchaseOrder.getSupplierId() == null) {
+            throw exception(PURCHASE_ORDER_SUBMIT_SUPPLIER_REQUIRED, purchaseOrder.getNo());
+        }
+        // 2.2 浠樻鏃ユ湡蹇呴�夛細閲囪喘鐢宠鑷姩鐢熸垚鐨勮崏绋胯鍗曟病鏈変粯娆炬棩鏈�
+        if (purchaseOrder.getPaymentDate() == null) {
+            throw exception(PURCHASE_ORDER_PAYMENT_DATE_REQUIRED);
+        }
+        // 2.3 鏄庣粏鍗曚环蹇呴』琛ュ綍锛堝ぇ浜� 0锛�
+        List<ErpPurchaseOrderItemDO> items = purchaseOrderItemMapper.selectListByOrderId(purchaseOrder.getId());
+        if (CollUtil.isEmpty(items)) {
+            throw exception(PURCHASE_ORDER_SUBMIT_ITEM_REQUIRED, purchaseOrder.getNo());
+        }
+        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
+                convertSet(items, ErpPurchaseOrderItemDO::getProductId));
+        for (ErpPurchaseOrderItemDO item : items) {
+            BigDecimal productPrice = item.getProductPrice();
+            if (productPrice != null && productPrice.compareTo(BigDecimal.ZERO) > 0) {
+                continue;
+            }
+            MdmItemRespDTO itemDTO = itemMap.get(item.getProductId());
+            String itemName = itemDTO != null && StrUtil.isNotBlank(itemDTO.getName())
+                    ? itemDTO.getName() : "ID=" + item.getProductId();
+            throw exception(PURCHASE_ORDER_SUBMIT_PRICE_REQUIRED, itemName,
+                    productPrice == null ? "鏈~鍐�" : productPrice.toPlainString());
+        }
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void submitPurchaseOrder(Long id, String processDefinitionKey, Long userId) {
-        // 1. 鏍¢獙瀛樺湪 + 鏈鏍哥姸鎬�
+        // 1. 鏍¢獙瀛樺湪 + 鏈鏍哥姸鎬侊紙鑽夌鍜屾湭瀹℃牳鍧囧彲鎻愪氦锛�
         ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
-        if (!ErpAuditStatus.PROCESS.getStatus().equals(purchaseOrder.getStatus())) {
+        boolean submitable = ErpAuditStatus.DRAFT.getStatus().equals(purchaseOrder.getStatus())
+                || ErpAuditStatus.PROCESS.getStatus().equals(purchaseOrder.getStatus());
+        if (!submitable) {
             throw exception(PURCHASE_ORDER_SUBMIT_FAIL_STATUS);
         }
+        // 2. 鎻愪氦鍓嶆牎楠屽彴璐︽暟鎹凡琛ラ綈锛氶噰璐敵璇风洿鎺ョ敓鎴愮殑鑽夌鍙湁鐗╂枡涓庢暟閲忥紝
+        //    渚涘簲鍟嗐�佸崟浠风瓑闇�瑕侀噰璐憳鍦ㄥ彴璐︿腑琛ュ綍鍚庢墠鑳芥彁浜ゅ鎵�
+        validatePurchaseOrderReadyForSubmit(purchaseOrder);
 
-        // 2. 鍒涘缓 BPM 娴佺▼瀹炰緥
+        // 3. 鍒涘缓 BPM 娴佺▼瀹炰緥
+        // 鍏ㄥ眬鍏嶅鎵癸細涓氬姟鏍¢獙閫氳繃鍚庣洿鎺ユ墽琛屽鎵归�氳繃閫昏緫锛屾棤闇�娴佺▼妯″瀷銆�
+        if (processInstanceApi.isGlobalAutoApprove()) {
+            purchaseOrderMapper.updateById(new ErpPurchaseOrderDO()
+                    .setId(id)
+                    .setStatus(ErpAuditStatus.APPROVING.getStatus())
+                    .setProcessInstanceId(""));
+            updatePurchaseOrderAuditStatus(id, cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum.APPROVE.getStatus());
+            return;
+        }
+
         String processInstanceId = processInstanceApi.createProcessInstance(userId,
                 new BpmProcessInstanceCreateReqDTO()
                         .setProcessDefinitionKey(processDefinitionKey)
@@ -494,15 +611,55 @@
 
     private List<ErpPurchaseOrderItemDO> validatePurchaseOrderItems(List<ErpPurchaseOrderSaveReqVO.Item> list) {
         // 杞寲涓� ErpPurchaseOrderItemDO 鍒楄〃锛堜骇鍝侀獙璇佸凡绉婚櫎锛�
-        return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseOrderItemDO.class, item -> {
+        List<ErpPurchaseOrderItemDO> items = convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseOrderItemDO.class, item -> {
+            // 閲囪喘鍙拌处涓嶅啀鎻愪緵銆屾槸鍚﹁川妫�銆嶉�夋嫨锛氶噰璐槑缁嗕竴寰嬮渶瑕佹潵鏂欐楠�
+            item.setQcCheckFlag(true);
             item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount()));
             if (item.getTotalPrice() == null) {
                 return;
             }
             if (item.getTaxPercent() != null) {
-                item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent()));
+                // 鍗曚环涓哄惈绋庡崟浠凤細绋庨浠庝环绋庡悎璁′腑鍙嶇畻锛岃�屼笉鏄湪鍚堣涓婂啀鍔犵◣
+                item.setTaxPrice(MoneyUtils.taxPriceIncluded(item.getTotalPrice(), item.getTaxPercent()));
             }
         }));
+        // 閲囪喘鍙拌处锛氫骇鍝佺紪鍙峰彇 MDM 鐗╂枡缂栫爜蹇収锛屼繚璇佸垪琛�/瀵煎嚭鍙洿鎺ュ睍绀�
+        fillProductCode(items);
+        return items;
+    }
+
+    /**
+     * 鍥炲~鏄庣粏琛岀殑浜у搧缂栧彿锛圡DM 鐗╂枡缂栫爜锛�
+     */
+    private void fillProductCode(List<ErpPurchaseOrderItemDO> items) {
+        if (CollUtil.isEmpty(items)) {
+            return;
+        }
+        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
+                convertSet(items, ErpPurchaseOrderItemDO::getProductId));
+        items.forEach(item -> {
+            MdmItemRespDTO mdmItem = itemMap.get(item.getProductId());
+            if (mdmItem == null) {
+                return;
+            }
+            if (StrUtil.isBlank(mdmItem.getCode())) {
+                throw exception(PURCHASE_ORDER_ITEM_PRODUCT_CODE_MISSING,
+                        StrUtil.blankToDefault(mdmItem.getName(), "ID=" + mdmItem.getId()));
+            }
+            item.setProductCode(mdmItem.getCode());
+        });
+    }
+
+    /**
+     * 鏍¢獙閲囪喘鍙拌处瀛楁锛堥噰璐被鍨嬨�佷粯娆炬柟寮忥級
+     */
+    private void validateLedgerFields(ErpPurchaseOrderSaveReqVO reqVO) {
+        if (!ErpPurchaseTypeEnum.isValid(reqVO.getPurchaseType())) {
+            throw exception(PURCHASE_ORDER_PURCHASE_TYPE_INVALID, reqVO.getPurchaseType());
+        }
+        if (!ErpPaymentMethodEnum.isValid(reqVO.getPaymentMethod())) {
+            throw exception(PURCHASE_ORDER_PAYMENT_METHOD_INVALID, reqVO.getPaymentMethod());
+        }
     }
 
     private void updatePurchaseOrderItemList(Long id, List<ErpPurchaseOrderItemDO> newList) {
@@ -784,4 +941,4 @@
         return arrivalNoticeId;
     }
 
-}
\ No newline at end of file
+}

--
Gitblit v1.9.3