From 962f78c8383e7c884ddb08c951c15e5ba6eb4cb8 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期五, 23 十月 2026 15:43:33 +0800
Subject: [PATCH] fix: 完善审批流程与业务状态校验

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmServiceImpl.java |  226 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 218 insertions(+), 8 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmServiceImpl.java
index 155c10e..88c5280 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/techconfirm/ProductTechConfirmServiceImpl.java
@@ -1,56 +1,121 @@
 package cn.iocoder.yudao.module.mes.service.pd.techconfirm;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
+import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmImportRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pd.techconfirm.vo.ProductTechConfirmSaveReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pd.techconfirm.ProductTechConfirmDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.pd.techconfirm.ProductTechConfirmMapper;
+import cn.iocoder.yudao.module.mes.enums.pd.MesPdTechConfirmStatusEnum;
+import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_TECH_CONFIRM_NOT_EXISTS;
-import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_TECH_CONFIRM_NO_DUPLICATE;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
 
 /**
  * MES 浜у搧鎶�鏈‘璁ゅ嚱 Service 瀹炵幇绫�
+ *
+ * 鐘舵�佸畬鍏ㄧ敱涓氬姟娴佺▼椹卞姩锛屽墠绔笉鍙備笌鐘舵�佽祴鍊笺�傚悇鐘舵�佺殑鍐欏叆鍏ュ彛涓ユ牸鍖哄垎锛�
+ * <ul>
+ *     <li>鑽夌锛歿@link #createTechConfirm} 鍒涘缓鏃跺啓鍏ワ紱{@link #auditTechConfirm} 瀹㈡埛纭琚┏鍥炴椂鍥為��</li>
+ *     <li>寰呭鏍革細{@link #submitTechConfirmAudit} 鎻愪氦瀹℃牳鍚庡啓鍏ワ紝鍚屾椂璁板綍缂栧埗浜烘寚瀹氱殑瀹℃牳浜�</li>
+ *     <li>宸插鏍革細{@link #auditTechConfirm} 瀹℃牳閫氳繃鏃跺啓鍏ワ紝鍚屾椂璁板綍瀹℃牳鏃堕棿涓庡鏍告剰瑙�</li>
+ *     <li>瀹℃牳涓嶉�氳繃锛歿@link #auditTechConfirm} 瀹℃牳涓嶉�氳繃鏃跺啓鍏ワ紝淇濈暀涓嶉�氳繃鍘熷洜渚涚紪鍒朵汉淇敼鍚庨噸鏂版彁浜�</li>
+ *     <li>瀹㈡埛宸茬‘璁わ細{@link #confirmTechConfirmByCustomer} 瀹㈡埛纭鍚庡啓鍏�</li>
+ * </ul>
  *
  * @author 瓒呯骇绠$悊鍛�
  */
 @Service
 @Validated
+@Slf4j
 public class ProductTechConfirmServiceImpl implements ProductTechConfirmService {
+
+    /**
+     * 鎶�鏈‘璁ゅ嚱瀹℃壒鐨勪笟鍔$被鍨嬬紪鐮�
+     *
+     * 涓� system_approval_config.biz_type 瀵瑰簲锛屽鏍镐汉鍦ㄣ�岀郴缁熺鐞� - 瀹℃壒閰嶇疆銆嶄腑缁存姢銆�
+     */
+    private static final String TECH_CONFIRM_APPROVE_BIZ_TYPE = "pd_tech_confirm_approve";
 
     @Resource
     private ProductTechConfirmMapper techConfirmMapper;
 
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @Resource
+    private ApprovalConfigApi approvalConfigApi;
+
+    @Resource
+    private CrmCustomerApi customerApi;
+
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Long createTechConfirm(ProductTechConfirmSaveReqVO createReqVO) {
         // 鏍¢獙纭鍑界紪鍙峰敮涓�
         validateConfirmNoUnique(createReqVO.getConfirmNo(), null);
-        // 鎻掑叆
+        // 鎻掑叆锛氱姸鎬佸浐瀹氫负鑽夌锛岀紪鍒朵汉 / 缂栧埗鏃堕棿鍙栧綋鍓嶇櫥褰曠敤鎴蜂笌褰撳墠鏃堕棿
+        Long userId = getLoginUserId();
         ProductTechConfirmDO techConfirm = BeanUtils.toBean(createReqVO, ProductTechConfirmDO.class);
+        techConfirm.setStatus(MesPdTechConfirmStatusEnum.DRAFT.getStatus());
+        techConfirm.setDrafterId(userId);
+        techConfirm.setDrafterName(getUserNickname(userId));
+        techConfirm.setDraftTime(LocalDateTime.now());
+        // 瀹℃牳浜虹敱鎻愪氦瀹℃牳鏃舵寚瀹氾紝鍒涘缓鏃舵竻绌猴紝閬垮厤鍓嶇浼犲弬甯﹀叆
+        techConfirm.setReviewerId(null);
+        techConfirm.setReviewerName(null);
+        techConfirm.setReviewTime(null);
+        techConfirm.setReviewRemark(null);
+        techConfirm.setCustomerConfirmUser(null);
+        techConfirm.setCustomerConfirmTime(null);
         techConfirmMapper.insert(techConfirm);
         return techConfirm.getId();
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateTechConfirm(ProductTechConfirmSaveReqVO updateReqVO) {
         // 鏍¢獙瀛樺湪
-        validateTechConfirmExists(updateReqVO.getId());
+        ProductTechConfirmDO techConfirm = validateTechConfirmExists(updateReqVO.getId());
+        // 鏍¢獙鐘舵�佷负鑽夌鎴栧鏍镐笉閫氳繃
+        validateTechConfirmEditable(techConfirm);
         // 鏍¢獙纭鍑界紪鍙峰敮涓�锛堟帓闄よ嚜韬級
         validateConfirmNoUnique(updateReqVO.getConfirmNo(), updateReqVO.getId());
-        // 鏇存柊
+        // 鏇存柊锛氱姸鎬併�佺紪鍒朵汉銆佸鏍镐汉銆佸鎴风‘璁や汉绛夋祦绋嬪瓧娈典竴寰嬩笉鎺ユ敹鍓嶇浼犲弬锛屾澶勫彧钀戒笟鍔″瓧娈�
         ProductTechConfirmDO updateObj = BeanUtils.toBean(updateReqVO, ProductTechConfirmDO.class);
         techConfirmMapper.updateById(updateObj);
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void deleteTechConfirm(Long id) {
         // 鏍¢獙瀛樺湪
-        validateTechConfirmExists(id);
+        ProductTechConfirmDO techConfirm = validateTechConfirmExists(id);
+        // 鏍¢獙鐘舵�佷负鑽夌鎴栧鏍镐笉閫氳繃
+        validateTechConfirmEditable(techConfirm);
         // 鍒犻櫎
         techConfirmMapper.deleteById(id);
     }
@@ -65,9 +130,139 @@
         return techConfirmMapper.selectPage(pageReqVO);
     }
 
-    private void validateConfirmNoUnique(String confirmNo, Long excludeId) {
-        if (confirmNo == null) {
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitTechConfirmAudit(Long id) {
+        // 1. 鏍¢獙瀛樺湪 + 鍙彁浜ょ姸鎬侊紙鑽夌 / 瀹℃牳涓嶉�氳繃锛�
+        ProductTechConfirmDO techConfirm = validateTechConfirmExists(id);
+        if (!MesPdTechConfirmStatusEnum.DRAFT.getStatus().equals(techConfirm.getStatus())
+                && !MesPdTechConfirmStatusEnum.REJECTED.getStatus().equals(techConfirm.getStatus())) {
+            throw exception(PD_TECH_CONFIRM_NOT_EDITABLE_FOR_SUBMIT);
+        }
+        // 2. 鏍¢獙瀹℃壒閰嶇疆宸插惎鐢ㄤ笖閰嶇疆浜嗘湁鏁堝鏍镐汉锛堟湭閰嶇疆鏃舵姏鍑哄甫鏄庣‘鎻愮ず鐨勪笟鍔″紓甯革級
+        approvalConfigApi.validateApprovalEnabledAndGetApprovers(TECH_CONFIRM_APPROVE_BIZ_TYPE);
+        // 3. 鏇存柊鐘舵�佷负寰呭鏍革紝骞舵竻绌轰笂涓�杞鏍哥粨鏋�
+        //    鐢� UpdateWrapper 鑰岄潪 updateById锛歁yBatis-Plus 榛樿鏇存柊绛栫暐浼氬拷鐣� null锛�
+        //    鐩存帴 updateById 鏃犳硶鎶婂鏍镐笉閫氳繃鍚庢畫鐣欑殑瀹℃牳浜� / 瀹℃牳鏃堕棿 / 瀹℃牳鎰忚鐪熸娓呯┖銆�
+        techConfirmMapper.submitAndResetAuditInfo(id, MesPdTechConfirmStatusEnum.PENDING_AUDIT.getStatus());
+    }
+
+    @Override
+    public Set<Long> getTechConfirmApproverUserIds() {
+        return approvalConfigApi.getApproverUserIds(TECH_CONFIRM_APPROVE_BIZ_TYPE);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void auditTechConfirm(Long id, Boolean pass, String reviewRemark) {
+        // 1. 鏍¢獙瀛樺湪
+        ProductTechConfirmDO techConfirm = validateTechConfirmExists(id);
+        // 2. 鏍¢獙澶勪簬寰呭鏍哥姸鎬�
+        if (!MesPdTechConfirmStatusEnum.PENDING_AUDIT.getStatus().equals(techConfirm.getStatus())) {
+            throw exception(PD_TECH_CONFIRM_NOT_PENDING_AUDIT);
+        }
+        // 3. 鏍¢獙褰撳墠鐧诲綍鐢ㄦ埛鏄涓氬姟绫诲瀷鐨勫鏍镐汉锛堟垨绛撅細浠讳竴浜哄潎鍙鏍革級
+        Long userId = getLoginUserId();
+        approvalConfigApi.validateApprover(TECH_CONFIRM_APPROVE_BIZ_TYPE, userId);
+        // 4. 瀹℃牳涓嶉�氳繃锛氬繀椤诲~鍐欏師鍥狅紝鐘舵�佺疆涓哄鏍镐笉閫氳繃锛岄��鍥炵紪鍒朵汉淇敼鍚庡彲閲嶆柊鎻愪氦
+        if (!Boolean.TRUE.equals(pass)) {
+            if (StrUtil.isBlank(reviewRemark)) {
+                throw exception(PD_TECH_CONFIRM_REJECT_REASON_REQUIRED);
+            }
+            techConfirmMapper.auditTechConfirm(id, MesPdTechConfirmStatusEnum.REJECTED.getStatus(),
+                    userId, getUserNickname(userId), reviewRemark);
             return;
+        }
+        // 5. 瀹℃牳閫氳繃锛氳褰曞鏍镐汉涓庡鏍告剰瑙侊紝鐘舵�佺疆涓哄凡瀹℃牳
+        techConfirmMapper.auditTechConfirm(id, MesPdTechConfirmStatusEnum.AUDITED.getStatus(),
+                userId, getUserNickname(userId), reviewRemark);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void confirmTechConfirmByCustomer(Long id, String customerConfirmUser) {
+        // 1. 鏍¢獙瀛樺湪 + 宸插鏍哥姸鎬�
+        ProductTechConfirmDO techConfirm = validateTechConfirmExists(id);
+        if (!MesPdTechConfirmStatusEnum.AUDITED.getStatus().equals(techConfirm.getStatus())) {
+            throw exception(PD_TECH_CONFIRM_NOT_AUDITED_FOR_CUSTOMER_CONFIRM);
+        }
+        // 2. 鏇存柊鐘舵�佷负瀹㈡埛宸茬‘璁わ紝骞惰褰曞鎴风‘璁や汉涓庣‘璁ゆ椂闂�
+        techConfirmMapper.updateById(ProductTechConfirmDO.builder()
+                .id(id)
+                .status(MesPdTechConfirmStatusEnum.CUSTOMER_CONFIRMED.getStatus())
+                .customerConfirmUser(customerConfirmUser)
+                .customerConfirmTime(LocalDateTime.now())
+                .build());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ProductTechConfirmImportRespVO importTechConfirmList(List<ProductTechConfirmImportExcelVO> importList, boolean updateSupport) {
+        if (CollUtil.isEmpty(importList)) {
+            throw exception(PD_TECH_CONFIRM_IMPORT_LIST_IS_EMPTY);
+        }
+        ProductTechConfirmImportRespVO respVO = ProductTechConfirmImportRespVO.builder()
+                .createCodes(new ArrayList<>()).updateCodes(new ArrayList<>())
+                .failureCodes(new LinkedHashMap<>()).build();
+        AtomicInteger index = new AtomicInteger(1);
+        importList.forEach(importItem -> {
+            int currentIndex = index.getAndIncrement();
+            String key = StrUtil.blankToDefault(importItem.getConfirmNo(), "绗� " + currentIndex + " 琛�");
+            if (StrUtil.isBlank(importItem.getConfirmNo())) {
+                respVO.getFailureCodes().put(key, "纭鍑界紪鍙蜂笉鑳戒负绌�");
+                return;
+            }
+            // 1. 瑙f瀽瀹㈡埛鍚嶇О
+            Long customerId = null;
+            if (StrUtil.isNotBlank(importItem.getCustomerName())) {
+                CrmCustomerRespDTO customer = customerApi.getCustomerByName(importItem.getCustomerName().trim());
+                if (customer == null) {
+                    respVO.getFailureCodes().put(key, "瀹㈡埛鍚嶇О[" + importItem.getCustomerName() + "]涓嶅瓨鍦�");
+                    return;
+                }
+                customerId = customer.getId();
+            }
+            // 2. 鏋勫缓璇锋眰骞跺垱寤�/鏇存柊锛堝鐢ㄧ幇鏈� create/update 鏈嶅姟锛岀姸鎬佺敱 create 鍥哄畾涓鸿崏绋匡級
+            ProductTechConfirmSaveReqVO reqVO = BeanUtils.toBean(importItem, ProductTechConfirmSaveReqVO.class);
+            reqVO.setCustomerId(customerId);
+            try {
+                ProductTechConfirmDO exist = techConfirmMapper.selectByConfirmNo(importItem.getConfirmNo().trim());
+                if (exist == null) {
+                    reqVO.setId(null);
+                    createTechConfirm(reqVO);
+                    respVO.getCreateCodes().add(importItem.getConfirmNo());
+                } else if (updateSupport) {
+                    reqVO.setId(exist.getId());
+                    updateTechConfirm(reqVO);
+                    respVO.getUpdateCodes().add(importItem.getConfirmNo());
+                } else {
+                    respVO.getFailureCodes().put(key, "纭鍑界紪鍙峰凡瀛樺湪");
+                }
+            } catch (ServiceException e) {
+                respVO.getFailureCodes().put(key, e.getMessage());
+            }
+        });
+        return respVO;
+    }
+
+    /**
+     * 鑾峰彇鐢ㄦ埛鏄电О
+     *
+     * @param userId 鐢ㄦ埛缂栧彿
+     * @return 鏄电О锛岀敤鎴蜂笉瀛樺湪鏃惰繑鍥� null
+     */
+    private String getUserNickname(Long userId) {
+        if (userId == null) {
+            return null;
+        }
+        AdminUserRespDTO user = adminUserApi.getUser(userId);
+        return user == null ? null : user.getNickname();
+    }
+
+    private void validateConfirmNoUnique(String confirmNo, Long excludeId) {
+        // 纭鍑界紪鍙峰繀濉細VO 涓婄殑 @NotBlank 鍙鐩栨帴鍙e叆鍙傦紝Service 鍐咃紙鍚鍏ワ級浠嶉渶鏄惧紡鏍¢獙
+        if (StrUtil.isBlank(confirmNo)) {
+            throw exception(PD_TECH_CONFIRM_NO_REQUIRED);
         }
         ProductTechConfirmDO techConfirm = techConfirmMapper.selectOne(
                 ProductTechConfirmDO::getConfirmNo, confirmNo);
@@ -84,4 +279,19 @@
         return techConfirm;
     }
 
+    /**
+     * 鏍¢獙鎶�鏈‘璁ゅ嚱澶勪簬鍙慨鏀圭姸鎬�
+     *
+     * 鍙湁鑽夌涓庡鏍镐笉閫氳繃鍏佽淇敼涓庡垹闄わ紱寰呭鏍� / 宸插鏍� / 瀹㈡埛宸茬‘璁や负閿佸畾鎴栫粓鎬併��
+     * 瀹℃牳涓嶉�氳繃鐨勫崟鎹渶閫�鍥炵粰缂栧埗浜轰慨鏀瑰悗閲嶆柊鎻愪氦锛屽洜姝ゅ悓鏍锋斁寮�銆�
+     *
+     * @param techConfirm 鎶�鏈‘璁ゅ嚱
+     */
+    private void validateTechConfirmEditable(ProductTechConfirmDO techConfirm) {
+        if (!MesPdTechConfirmStatusEnum.DRAFT.getStatus().equals(techConfirm.getStatus())
+                && !MesPdTechConfirmStatusEnum.REJECTED.getStatus().equals(techConfirm.getStatus())) {
+            throw exception(PD_TECH_CONFIRM_NOT_EDITABLE);
+        }
+    }
+
 }

--
Gitblit v1.9.3