From cccbfadcb6e96c23a33c40c0687a27c2f6efa1db Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 09 十一月 2026 13:16:42 +0800
Subject: [PATCH] feat(seal): 添加印章详情及图片预览功能

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java |  123 +++++++++++++++++++++++++++++++++-------
 1 files changed, 100 insertions(+), 23 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
index 63cd140..387146e 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
@@ -8,6 +8,7 @@
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.MesWmReturnVendorPageReqVO;
@@ -32,8 +33,7 @@
 import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
 import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import jakarta.annotation.Resource;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -55,12 +55,14 @@
  */
 @Service
 @Validated
+@Slf4j
 public class MesWmReturnVendorServiceImpl implements MesWmReturnVendorService {
 
     /**
      * 闄勪欢涓氬姟绫诲瀷锛歁ES 渚涘簲鍟嗛��璐у崟
      */
     public static final String RETURN_VENDOR_RECORD_TYPE = "mes_wm_return_vendor";
+    private static final String RETURN_VENDOR_APPROVAL_KEY = "purchase_return_vendor_approve";
 
     @Resource
     private MesWmReturnVendorMapper returnVendorMapper;
@@ -184,26 +186,29 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void submitReturnVendor(Long id) {
-        // 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
-        validateReturnVendorExistsAndPrepare(id);
-        // 鏍¢獙鑷冲皯鏈変竴鏉¤
-        List<MesWmReturnVendorLineDO> lines = returnVendorLineService.getReturnVendorLineListByReturnId(id);
-        if (CollUtil.isEmpty(lines)) {
-            throw exception(WM_RETURN_VENDOR_NO_LINE);
-        }
-
-        // 鎻愪氦锛堣崏绋� -> 寰呮嫞璐э級
-        returnVendorMapper.updateById(new MesWmReturnVendorDO()
-                .setId(id).setStatus(MesWmReturnVendorStatusEnum.APPROVING.getStatus()));
+        submitReturnVendorApproval(id, RETURN_VENDOR_APPROVAL_KEY);
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void submitReturnVendorApproval(Long id, String processDefinitionKey) {
+        if (!RETURN_VENDOR_APPROVAL_KEY.equals(processDefinitionKey)) {
+            throw exception(WM_RETURN_VENDOR_APPROVAL_PROCESS_INVALID, RETURN_VENDOR_APPROVAL_KEY);
+        }
         // 1. 鏍¢獙瀛樺湪 + 鑽夌鐘舵�� + 鑷冲皯涓�鏉¤
-        validateReturnVendorExistsAndPrepare(id);
+        MesWmReturnVendorDO returnVendor = validateReturnVendorExistsAndPrepare(id);
         if (CollUtil.isEmpty(returnVendorLineService.getReturnVendorLineListByReturnId(id))) {
             throw exception(WM_RETURN_VENDOR_NO_LINE);
+        }
+        validateReturnVendorOrderQuantity(returnVendor);
+        // 鎷h揣鍙湪瀹℃壒閫氳繃鍚庤繘琛岋紱濡傚凡鎻愬墠褰曞叆鏄庣粏锛屽垯鎻愪氦鍓嶅繀椤绘暟閲忓畧鎭掋��
+        if (CollUtil.isNotEmpty(returnVendorDetailService.getReturnVendorDetailListByReturnId(id))
+                && !checkReturnVendorQuantity(id)) {
+            throw exception(WM_RETURN_VENDOR_DETAIL_QUANTITY_MISMATCH);
+        }
+        // 1.1. 璐ㄩ噺閮ㄥ彂璧烽��璐у繀椤讳笂浼犻檮浠讹紙璐ㄦ鎶ュ憡銆佷笉鍚堟牸鐓х墖绛夛級锛屽惁鍒欏鎵逛汉鏃犳硶鍒ゆ柇閫�璐т緷鎹�
+        if (CollUtil.isEmpty(storageAttachmentApi.listAttachments(RETURN_VENDOR_RECORD_TYPE, id))) {
+            throw exception(WM_RETURN_VENDOR_ATTACHMENT_REQUIRED, returnVendor.getCode());
         }
 
         // 2. 鍏ㄥ眬鍏嶅鎵癸細鐩存帴缃负銆屽緟鎷h揣銆嶏紝鏃犻渶娴佺▼妯″瀷
@@ -279,6 +284,11 @@
         if (ObjUtil.notEqual(MesWmReturnVendorStatusEnum.APPROVING.getStatus(), returnVendor.getStatus())) {
             throw exception(WM_RETURN_VENDOR_STATUS_INVALID);
         }
+        validateReturnVendorApproval(returnVendor);
+        if (!checkReturnVendorQuantity(id)) {
+            throw exception(WM_RETURN_VENDOR_DETAIL_QUANTITY_MISMATCH);
+        }
+        validateReturnVendorOrderQuantity(returnVendor);
         // 鎵ц鎷h揣锛堝緟鎷h揣 -> 寰呮墽琛岄��璐э級
         returnVendorMapper.updateById(new MesWmReturnVendorDO()
                 .setId(id).setStatus(MesWmReturnVendorStatusEnum.APPROVED.getStatus()));
@@ -292,11 +302,16 @@
         if (ObjUtil.notEqual(MesWmReturnVendorStatusEnum.APPROVED.getStatus(), returnVendor.getStatus())) {
             throw exception(WM_RETURN_VENDOR_STATUS_INVALID);
         }
+        validateReturnVendorApproval(returnVendor);
         // 1.2 鏍¢獙鏄庣粏涓嶄负绌�
         List<MesWmReturnVendorDetailDO> details = returnVendorDetailService.getReturnVendorDetailListByReturnId(id);
         if (CollUtil.isEmpty(details)) {
             throw exception(WM_RETURN_VENDOR_NO_DETAIL);
         }
+        if (!checkReturnVendorQuantity(id)) {
+            throw exception(WM_RETURN_VENDOR_DETAIL_QUANTITY_MISMATCH);
+        }
+        validateReturnVendorOrderQuantity(returnVendor);
 
         // 1.3 鏍¢獙澶勭悊鏂瑰紡锛堥��娆� / 鎹㈣揣锛�
         if (!MesWmReturnVendorHandleTypeEnum.isValid(returnVendor.getHandleType())) {
@@ -321,6 +336,13 @@
         }
     }
 
+    private void validateReturnVendorApproval(MesWmReturnVendorDO returnVendor) {
+        if (!RETURN_VENDOR_APPROVAL_KEY.equals(returnVendor.getProcessDefinitionKey())
+                || returnVendor.getProcessInstanceId() == null) {
+            throw exception(WM_RETURN_VENDOR_APPROVAL_REQUIRED, returnVendor.getCode());
+        }
+    }
+
     /**
      * 鎶婇��璐ф暟閲忓閲忓洖鍐欏埌鍏宠仈閲囪喘璁㈠崟鐨勬槑缁嗚锛屽苟鍚屾璁㈠崟绾ч��璐ф暟閲�
      */
@@ -338,15 +360,58 @@
             return;
         }
         Map<Long, Long> purchaseOrderItemIdMap = new HashMap<>();
+        Set<Long> ambiguousProductIds = new HashSet<>();
         orderItems.stream()
                 .filter(item -> item.getProductId() != null)
-                .forEach(item -> purchaseOrderItemIdMap.putIfAbsent(item.getProductId(), item.getId()));
+                .forEach(item -> {
+                    if (purchaseOrderItemIdMap.putIfAbsent(item.getProductId(), item.getId()) != null) {
+                        ambiguousProductIds.add(item.getProductId());
+                    }
+                });
         for (MesWmReturnVendorLineDO line : lines) {
+            if (ambiguousProductIds.contains(line.getItemId())) {
+                throw exception(WM_RETURN_VENDOR_PURCHASE_ITEM_AMBIGUOUS, line.getItemId());
+            }
             Long purchaseOrderItemId = purchaseOrderItemIdMap.get(line.getItemId());
             if (purchaseOrderItemId == null || line.getQuantity() == null) {
                 continue;
             }
-            purchaseOrderApi.updateOrderItemReturnCount(purchaseOrderItemId, line.getQuantity());
+            if (!Boolean.TRUE.equals(purchaseOrderApi.updateOrderItemReturnCount(purchaseOrderItemId, line.getQuantity()).getCheckedData())) {
+                throw exception(WM_RETURN_VENDOR_ITEM_NOT_IN_PURCHASE_ORDER);
+            }
+        }
+    }
+
+    private void validateReturnVendorOrderQuantity(MesWmReturnVendorDO returnVendor) {
+        if (returnVendor.getPurchaseOrderId() == null) {
+            return;
+        }
+        List<ErpPurchaseOrderItemRespDTO> items = purchaseOrderApi
+                .getPurchaseOrderItemList(returnVendor.getPurchaseOrderId()).getCheckedData();
+        Map<Long, ErpPurchaseOrderItemRespDTO> byProduct = new HashMap<>();
+        Set<Long> ambiguousProductIds = new HashSet<>();
+        for (ErpPurchaseOrderItemRespDTO item : items) {
+            if (item.getProductId() != null && byProduct.putIfAbsent(item.getProductId(), item) != null) {
+                ambiguousProductIds.add(item.getProductId());
+            }
+        }
+        Map<Long, BigDecimal> requested = new HashMap<>();
+        for (MesWmReturnVendorLineDO line : returnVendorLineService.getReturnVendorLineListByReturnId(returnVendor.getId())) {
+            requested.merge(line.getItemId(), line.getQuantity(), BigDecimal::add);
+        }
+        for (Map.Entry<Long, BigDecimal> entry : requested.entrySet()) {
+            if (ambiguousProductIds.contains(entry.getKey())) {
+                throw exception(WM_RETURN_VENDOR_PURCHASE_ITEM_AMBIGUOUS, entry.getKey());
+            }
+            ErpPurchaseOrderItemRespDTO item = byProduct.get(entry.getKey());
+            if (item == null) {
+                throw exception(WM_RETURN_VENDOR_ITEM_NOT_IN_PURCHASE_ORDER);
+            }
+            BigDecimal available = (item.getInCount() == null ? BigDecimal.ZERO : item.getInCount())
+                    .subtract(item.getReturnCount() == null ? BigDecimal.ZERO : item.getReturnCount());
+            if (entry.getValue().compareTo(available) > 0) {
+                throw exception(WM_RETURN_VENDOR_QUANTITY_EXCEED, entry.getKey(), entry.getValue(), available);
+            }
         }
     }
 
@@ -471,16 +536,16 @@
      * 鑾峰彇褰撳墠鐢ㄦ埛缂栧彿锛圔PM 娴佺▼鍙戣捣浜恒�佽储鍔′汉鍛橈級
      */
     private Long getCurrentUserId() {
-        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-        if (authentication != null && authentication.getPrincipal() instanceof Long) {
-            return (Long) authentication.getPrincipal();
-        }
-        return null;
+        // 缁熶竴浠庡畨鍏ㄦ鏋跺彇鐧诲綍鐢ㄦ埛锛汼pring Security 鐨� principal 鏄� LoginUser 鑰岄潪 Long锛�
+        // 鐩存帴寮鸿浆浼氬彇鍒� null锛屽鑷� BPM 娴佺▼瀹炰緥鐨勫彂璧蜂汉涓哄瓧绗︿覆 "null"
+        return SecurityFrameworkUtils.getLoginUserId();
     }
 
     private void createTransactionList(MesWmReturnVendorDO returnVendor, List<MesWmReturnVendorDetailDO> details) {
         wmTransactionService.createTransactionList(convertList(details, detail -> new MesWmTransactionSaveReqDTO()
                 .setType(MesWmTransactionTypeEnum.OUT.getType()).setItemId(detail.getItemId())
+                // 鎸夋嫞璐ф椂閫変腑鐨勫簱瀛樿褰曟墸鍑忥細鐢熶骇鎵规鍙兘娌℃湁 vendor_id锛屼粎闈� 鐗╂枡+搴撲綅+鎵规+渚涘簲鍟� 缁勫悎閿細鎵句笉鍒板簱瀛�
+                .setMaterialStockId(detail.getMaterialStockId())
                 .setQuantity(detail.getQuantity().negate()) // 鍑哄簱鏁伴噺涓鸿礋鏁�
                 .setBatchId(detail.getBatchId()).setBatchCode(detail.getBatchCode())
                 .setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId())
@@ -499,6 +564,15 @@
                 MesWmReturnVendorStatusEnum.FINISHED.getStatus(),
                 MesWmReturnVendorStatusEnum.CANCELED.getStatus())) {
             throw exception(WM_RETURN_VENDOR_CANCEL_NOT_ALLOWED);
+        }
+
+        // 瀹℃壒涓厛鎾ら攢 BPM 瀹炰緥锛屾挙閿�澶辫触鏃朵繚鐣欏崟鎹師鐘舵�佸拰寰呭姙銆�
+        if (MesWmReturnVendorStatusEnum.BPM_APPROVING.getStatus().equals(returnVendor.getStatus())) {
+            if (StrUtil.isBlank(returnVendor.getProcessInstanceId())) {
+                throw exception(WM_RETURN_VENDOR_APPROVAL_REQUIRED, returnVendor.getCode());
+            }
+            processInstanceApi.cancelProcessInstanceByStartUser(getCurrentUserId(),
+                    returnVendor.getProcessInstanceId(), "渚涘簲鍟嗛��璐у崟鍙栨秷");
         }
 
         // 鍙栨秷
@@ -581,8 +655,11 @@
         }
         try {
             importPurchaseOrderItems(id);
-        } catch (ServiceException ignore) {
-            // 閲囪喘璁㈠崟鏃犳槑缁� / 鏃犲彲閫�鐗╂枡绛変笟鍔″紓甯革細鍗曟嵁涓讳綋浠嶆甯镐繚瀛橈紝鍙湪鐗╂枡鍖哄彟琛屽鐞�
+        } catch (ServiceException ex) {
+            // 閲囪喘璁㈠崟鏃犳槑缁� / 鏃犲彲閫�鐗╂枡绛変笟鍔″紓甯革細鍗曟嵁涓讳綋浠嶆甯镐繚瀛橈紝鍙湪鐗╂枡鍖虹偣銆岄噸鏂板甫鍏ヨ鍗曠墿鏂欍�嶈ˉ甯�
+            // 鎵撴棩蹇椾究浜庢帓鏌ワ紙渚嬪锛氫繚瀛樺悗鎵嶅叆搴擄紝姝ゆ椂璁㈠崟杩樻病鏈夊彲閫�鏁伴噺锛�
+            log.warn("[autoImportLinesFromLinkedOrder][閫�璐у崟({}) 鍏宠仈閲囪喘璁㈠崟({}) 鑷姩甯﹀叆鐗╂枡澶辫触锛歿}]",
+                    id, returnVendor.getPurchaseOrderId(), ex.getMessage());
         }
     }
 

--
Gitblit v1.9.3