From 8c65f898ed6e5b0c7a94c792c4562f6ec831dccd Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期六, 07 十一月 2026 08:50:53 +0800
Subject: [PATCH] feat: 完善质检、生产、销售、财务与人事模块并接入审批流程
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ncr/MesQcNcrServiceImpl.java | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 150 insertions(+), 8 deletions(-)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ncr/MesQcNcrServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ncr/MesQcNcrServiceImpl.java
index 56ed686..1b9f291 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ncr/MesQcNcrServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ncr/MesQcNcrServiceImpl.java
@@ -1,11 +1,16 @@
package cn.iocoder.yudao.module.mes.service.qc.ncr;
+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.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcNcrMapper;
+import cn.iocoder.yudao.module.mes.enums.qc.MesNcrDispositionEnum;
import cn.iocoder.yudao.module.mes.enums.qc.MesNcrStatusEnum;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
@@ -23,6 +28,7 @@
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
@@ -38,11 +44,31 @@
@Validated
public class MesQcNcrServiceImpl implements MesQcNcrService {
+ /**
+ * NCR 鍗忓悓瀹℃壒娴佺▼鍒嗙被/娴佺▼瀹氫箟 key
+ */
+ private static final String NCR_APPROVAL_PROCESS_KEY = "mes_ncr_approve";
+
+ /**
+ * 鏈�缁堝缃厑璁稿�硷細璁╂鏀捐銆佽繑淇�佽繑宸ャ�佹姤搴�
+ * <p>
+ * 閫�璐�(1)銆侀檷绾т娇鐢�(5) 浠嶄繚鐣欏湪 {@link MesNcrDispositionEnum} 涓庡瓧鍏镐腑锛岀敤浜庡巻鍙叉暟鎹樉绀猴紝
+ * 浣嗕笉鍐嶄綔涓鸿川閲忛儴鏈�缁堝缃殑鍙�夐」銆�
+ */
+ private static final Set<Integer> DISPOSITION_ALLOWED_TYPES = Set.of(
+ MesNcrDispositionEnum.SCRAP.getType(),
+ MesNcrDispositionEnum.REWORK.getType(),
+ MesNcrDispositionEnum.CONCESSION.getType(),
+ MesNcrDispositionEnum.REPAIR.getType());
+
@Resource
private MesQcNcrMapper ncrMapper;
@Resource
private MdmItemApi mdmItemApi;
+
+ @Resource
+ private BpmProcessInstanceApi processInstanceApi;
@Resource
@Lazy
@@ -105,6 +131,7 @@
Integer defectLevel, String reviewOpinion) {
// 1. 鏍¢獙瀛樺湪 + 寰呰瘎瀹$姸鎬�
validateNcrStatusPendingReview(id);
+ validateDisposition(disposition);
// 2. 鏇存柊璇勫淇℃伅
MesQcNcrDO updateObj = new MesQcNcrDO()
@@ -123,17 +150,20 @@
@Override
@Transactional(rollbackFor = Exception.class)
- public void disposeNcr(Long id, Long handlerUserId, String handleResult) {
- // 1. 鏍¢獙瀛樺湪 + 宸茶瘎瀹$姸鎬�
+ public void disposeNcr(Long id, Integer disposition, Long handlerUserId, String handleResult) {
+ // 1. 鏍¢獙瀛樺湪 + 宸茶瘎瀹★紙瀹℃壒閫氳繃锛夌姸鎬�
MesQcNcrDO ncr = validateNcrExists(id);
if (!MesNcrStatusEnum.REVIEWED.getStatus().equals(ncr.getStatus())) {
- throw exception(QC_NCR_NOT_REVIEWED);
+ throw exception(QC_NCR_DISPOSE_STATUS_INVALID, ncr.getCode(), resolveStatusName(ncr.getStatus()));
}
+ // 2. 澶勭疆鏂瑰紡蹇呭~锛屼笖蹇呴』鏄悎娉曟灇涓惧��
+ validateDisposition(disposition);
- // 2. 鏇存柊澶勭疆淇℃伅
+ // 3. 鏇存柊澶勭疆淇℃伅锛堝缃柟寮忎互鏈澶勭疆閫夋嫨涓哄噯骞惰惤搴擄級
// TODO: WM 妯″潡闆嗘垚 鈥� 澶勭疆涓洪��璐ф椂瑙﹀彂渚涘簲鍟嗛��璐э紝澶勭疆涓烘姤搴熸椂瑙﹀彂鏉傞」鍑哄簱
ncrMapper.updateById(new MesQcNcrDO()
.setId(id)
+ .setDisposition(disposition)
.setHandlerUserId(handlerUserId)
.setHandleDate(LocalDateTime.now())
.setHandleResult(handleResult)
@@ -142,11 +172,99 @@
@Override
@Transactional(rollbackFor = Exception.class)
+ public void submitNcrApproval(Long id) {
+ // 璇存槑锛欱PM 鐨� createProcessInstance 鍐呴儴閫氳繃 FlowableUtils.executeAuthenticatedUserId 鎵ц锛�
+ // 璇ユ柟娉曚細鎶婃墍鏈夊紓甯哥粺涓�鍖呮垚 RuntimeException锛屽洜姝よ繖閲屼笉鑳界洿鎺� catch ServiceException锛�
+ // 蹇呴』娌� cause 閾惧垽鏂槸鍚︿负銆屾祦绋嬪畾涔変笉瀛樺湪銆嶃��
+ // 1. 鏍¢獙瀛樺湪 + 寰呰瘎瀹★紙鑽夌/瀹℃壒椹冲洖鍚庯級鐘舵��
+ MesQcNcrDO ncr = validateNcrExists(id);
+ if (!MesNcrStatusEnum.PENDING_REVIEW.getStatus().equals(ncr.getStatus())) {
+ throw exception(QC_NCR_SUBMIT_STATUS_INVALID, ncr.getCode(), resolveStatusName(ncr.getStatus()));
+ }
+
+ // 2. 鍏ㄥ眬鍏嶅鎵癸細涓氬姟鏍¢獙閫氳繃鍚庣洿鎺ヨ涓哄鎵归�氳繃锛堝凡璇勫/寰呭缃級锛屾棤闇�娴佺▼妯″瀷
+ if (processInstanceApi.isGlobalAutoApprove()) {
+ ncrMapper.updateById(new MesQcNcrDO()
+ .setId(id)
+ .setStatus(MesNcrStatusEnum.REVIEWED.getStatus())
+ .setProcessInstanceId(""));
+ return;
+ }
+
+ // 3. 鍒涘缓 BPM 娴佺▼瀹炰緥锛涘彇涓嶅埌娴佺▼瀹氫箟鏃舵寜绾﹀畾璧拌嚜鍔ㄩ�氳繃鍏滃簳
+ String processInstanceId;
+ try {
+ processInstanceId = processInstanceApi.createProcessInstance(getLoginUserId(),
+ new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(NCR_APPROVAL_PROCESS_KEY)
+ .setBusinessKey(String.valueOf(id)));
+ } catch (RuntimeException ex) {
+ if (isProcessDefinitionNotExists(ex)) {
+ ncrMapper.updateById(new MesQcNcrDO()
+ .setId(id)
+ .setStatus(MesNcrStatusEnum.REVIEWED.getStatus())
+ .setProcessInstanceId(""));
+ return;
+ }
+ throw ex;
+ }
+
+ // 4. 鏇存柊 NCR 鐘舵�佷负瀹℃壒涓紝骞跺洖鍐欐祦绋嬪疄渚嬬紪鍙�
+ ncrMapper.updateById(new MesQcNcrDO()
+ .setId(id)
+ .setStatus(MesNcrStatusEnum.APPROVING.getStatus())
+ .setProcessInstanceId(processInstanceId));
+ }
+
+ /**
+ * 鍒ゆ柇寮傚父閾句腑鏄惁鍖呭惈銆屾祦绋嬪畾涔変笉瀛樺湪銆嶏紙BPM 瀵瑰浼氬寘涓�灞� RuntimeException锛�
+ */
+ private static boolean isProcessDefinitionNotExists(Throwable ex) {
+ Throwable current = ex;
+ while (current != null) {
+ if (current instanceof ServiceException serviceException
+ && cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_NOT_EXISTS
+ .getCode().equals(serviceException.getCode())) {
+ return true;
+ }
+ current = current.getCause();
+ }
+ return false;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateNcrAuditStatus(Long id, Integer bpmResult) {
+ // 1. 鏍¢獙瀛樺湪 + 瀹℃壒涓紙閬垮厤骞跺彂鎴栭噸澶嶅洖璋冭鐩栧凡澶勭疆/宸插叧闂姸鎬侊級
+ MesQcNcrDO ncr = validateNcrExists(id);
+ if (!MesNcrStatusEnum.APPROVING.getStatus().equals(ncr.getStatus())) {
+ return;
+ }
+
+ // 2. 瀹℃壒閫氳繃 -> 宸茶瘎瀹★紙寰呭缃級锛涢┏鍥�/鍙栨秷 -> 寰呰瘎瀹★紙鍙噸鏂颁慨鏀瑰苟鎻愪氦锛�
+ if (BpmProcessInstanceStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
+ ncrMapper.updateById(new MesQcNcrDO()
+ .setId(id)
+ .setStatus(MesNcrStatusEnum.REVIEWED.getStatus()));
+ return;
+ }
+ if (BpmProcessInstanceStatusEnum.REJECT.getStatus().equals(bpmResult)
+ || BpmProcessInstanceStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
+ // 淇濈暀 processInstanceId锛氶┏鍥�/鍙栨秷鍚庝粛鍙湪銆屾煡鐪嬪鎵广�嶄腑鏌ョ湅瀹℃壒鎰忚锛�
+ // 閲嶆柊鎻愪氦鏃朵細鐢ㄦ柊娴佺▼瀹炰緥缂栧彿瑕嗙洊銆�
+ ncrMapper.updateById(new MesQcNcrDO()
+ .setId(id)
+ .setStatus(MesNcrStatusEnum.PENDING_REVIEW.getStatus()));
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
public void closeNcr(Long id, String closeOpinion) {
// 1. 鏍¢獙瀛樺湪 + 宸插缃姸鎬�
MesQcNcrDO ncr = validateNcrExists(id);
if (!MesNcrStatusEnum.DISPOSED.getStatus().equals(ncr.getStatus())) {
- throw exception(QC_NCR_NOT_DISPOSED);
+ throw exception(QC_NCR_CLOSE_STATUS_INVALID, ncr.getCode(), resolveStatusName(ncr.getStatus()));
}
// 2. 鏇存柊鍏抽棴淇℃伅
@@ -164,7 +282,7 @@
public MesQcNcrDO validateNcrExists(Long id) {
MesQcNcrDO ncr = ncrMapper.selectById(id);
if (ncr == null) {
- throw exception(QC_NCR_NOT_EXISTS);
+ throw exception(QC_NCR_NOT_EXISTS, id);
}
return ncr;
}
@@ -175,9 +293,33 @@
private MesQcNcrDO validateNcrStatusPendingReview(Long id) {
MesQcNcrDO ncr = validateNcrExists(id);
if (!MesNcrStatusEnum.PENDING_REVIEW.getStatus().equals(ncr.getStatus())) {
- throw exception(QC_NCR_NOT_PENDING_REVIEW);
+ throw exception(QC_NCR_STATUS_NOT_PENDING, ncr.getCode(), resolveStatusName(ncr.getStatus()));
}
return ncr;
+ }
+
+ /**
+ * 鏍¢獙澶勭疆鏂瑰紡锛氬繀濉� + 蹇呴』鏄悎娉曟灇涓惧��
+ */
+ private void validateDisposition(Integer disposition) {
+ if (disposition == null) {
+ throw exception(QC_NCR_DISPOSITION_REQUIRED);
+ }
+ if (!DISPOSITION_ALLOWED_TYPES.contains(disposition)) {
+ throw exception(QC_NCR_DISPOSITION_INVALID, disposition);
+ }
+ }
+
+ /**
+ * 鐘舵�佸�艰浆鍚嶇О锛岃В鏋愬け璐ュ洖閫�涓� ID 瀛楃涓�
+ */
+ private String resolveStatusName(Integer status) {
+ for (MesNcrStatusEnum value : MesNcrStatusEnum.values()) {
+ if (value.getStatus().equals(status)) {
+ return value.getName();
+ }
+ }
+ return "ID=" + status;
}
/**
@@ -186,7 +328,7 @@
private void validateNcrCodeUnique(Long id, String code) {
MesQcNcrDO existing = ncrMapper.selectByCode(code);
if (existing != null && !existing.getId().equals(id)) {
- throw exception(QC_NCR_CODE_DUPLICATE);
+ throw exception(QC_NCR_CODE_DUPLICATE, code);
}
}
--
Gitblit v1.9.3