From 7bd10d34e8a8dc2437ec8551eb511e37634d7306 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 02 十一月 2026 16:51:52 +0800
Subject: [PATCH] feat(mes): 生产计划审批改走协同办公,修复工单领料与产成品入库校验
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java | 208 +++++++++++++++++++++++++++++++--------------------
1 files changed, 127 insertions(+), 81 deletions(-)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
index 1326d89..8e0381f 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
@@ -3,14 +3,17 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
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.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.bpm.enums.task.BpmProcessInstanceStatusEnum;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseRequestApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseRequestCreateReqDTO;
-import cn.iocoder.yudao.module.mes.controller.admin.pro.mps.vo.MesProMpsAuditReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.mps.vo.MesProMpsPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.mps.vo.MesProMpsSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderItemRespVO;
@@ -77,6 +80,11 @@
*/
private static final String MPS_CODE_RULE = "MPS";
+ /**
+ * MPS 瀹℃壒娴佺▼瀹氫箟 Key锛圔PM 娴佺▼妯″瀷涓厤缃級
+ */
+ private static final String MPS_APPROVE_PROCESS_DEFINITION_KEY = "mes-pro-mps-audit";
+
@Resource
private MesProMpsMapper mpsMapper;
@Resource
@@ -105,6 +113,8 @@
private ErpPurchaseOrderApi purchaseOrderApi;
@Resource
private ErpPurchaseRequestApi purchaseRequestApi;
+ @Resource
+ private BpmProcessInstanceApi processInstanceApi;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -194,11 +204,8 @@
// 鏍¢獙缂栫爜鍞竴
validateMpsCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
- // 鏇存柊锛涘鏍镐腑鐨勮鍒掕淇敼鍚庨渶瑕侀噸鏂版彁浜ゅ鏍�
+ // 鏇存柊锛堜粎鑽夌鍙紪杈戯級
MesProMpsDO updateObj = BeanUtils.toBean(updateReqVO, MesProMpsDO.class);
- if (MesProMpsStatusEnum.AUDITING.getStatus().equals(mps.getStatus())) {
- updateObj.setStatus(MesProMpsStatusEnum.DRAFT.getStatus());
- }
mpsMapper.updateById(updateObj);
}
@@ -221,35 +228,71 @@
@Transactional(rollbackFor = Exception.class)
public void submitMps(Long id) {
MesProMpsDO mps = validateMpsExists(id);
- if (!MesProMpsStatusEnum.DRAFT.getStatus().equals(mps.getStatus())
- && !MesProMpsStatusEnum.AUDITING.getStatus().equals(mps.getStatus())) {
+ // 鍙湁鑽夌鍙互鎻愪氦瀹℃牳锛涘鏍镐腑鐨勮鍒掗渶绛夊緟鍗忓悓鍔炲叕瀹℃壒缁撴灉
+ if (!MesProMpsStatusEnum.DRAFT.getStatus().equals(mps.getStatus())) {
throw exception(PRO_MPS_NOT_DRAFT);
+ }
+ // 鍏ㄥ眬鍏嶅鎵癸細涓嶅仛 BPM 娴佺▼锛岀洿鎺ユ寜瀹℃壒閫氳繃澶勭悊
+ if (processInstanceApi.isGlobalAutoApprove()) {
+ mpsMapper.updateById(new MesProMpsDO()
+ .setId(id)
+ .setStatus(MesProMpsStatusEnum.AUDITING.getStatus())
+ .setProcessInstanceId(""));
+ handleBpmProcessResult(id, BpmProcessInstanceStatusEnum.APPROVE.getStatus(), "鍏ㄥ眬鍏嶅鎵硅嚜鍔ㄩ�氳繃", null);
+ return;
+ }
+ // 鍚姩 BPM 娴佺▼锛屽鎵瑰湪鍗忓悓鍔炲叕瀹屾垚锛岀粨鏋滅敱鐩戝惉鍣ㄥ洖鍐�
+ String processInstanceId;
+ try {
+ processInstanceId = processInstanceApi.createProcessInstance(SecurityFrameworkUtils.getLoginUserId(),
+ new BpmProcessInstanceCreateReqDTO()
+ .setProcessDefinitionKey(MPS_APPROVE_PROCESS_DEFINITION_KEY)
+ .setBusinessKey(String.valueOf(id)));
+ } catch (ServiceException ex) {
+ // 娴佺▼鏈儴缃叉椂 BPM 鍙姏銆屾祦绋嬪畾涔変笉瀛樺湪銆嶏紝琛ヤ笂鍙墽琛岀殑鎻愮ず
+ if (StrUtil.contains(ex.getMessage(), "娴佺▼瀹氫箟涓嶅瓨鍦�")) {
+ log.warn("[submitMps] 鐢熶骇璁″垝瀹℃壒娴佺▼({}) 鏈儴缃诧紝mpsId={}", MPS_APPROVE_PROCESS_DEFINITION_KEY, id);
+ throw exception(PRO_MPS_PROCESS_NOT_DEPLOYED);
+ }
+ throw ex;
}
mpsMapper.updateById(new MesProMpsDO()
.setId(id)
- .setStatus(MesProMpsStatusEnum.AUDITING.getStatus()));
+ .setStatus(MesProMpsStatusEnum.AUDITING.getStatus())
+ .setProcessInstanceId(processInstanceId));
}
@Override
@Transactional(rollbackFor = Exception.class)
- public void auditMps(MesProMpsAuditReqVO reqVO) {
- MesProMpsDO mps = validateMpsExists(reqVO.getId());
+ public void handleBpmProcessResult(Long id, Integer bpmResult, String reason, Long auditUserId) {
+ // 1. 鏌旀�ф牎楠岋細璁″垝瀛樺湪涓斿浜庡鏍镐腑
+ MesProMpsDO mps = mpsMapper.selectById(id);
+ if (mps == null) {
+ log.warn("[handleBpmProcessResult] 鐢熶骇璁″垝({}) 涓嶅瓨鍦�", id);
+ return;
+ }
if (!MesProMpsStatusEnum.AUDITING.getStatus().equals(mps.getStatus())) {
- throw exception(PRO_MPS_NOT_AUDITING);
+ log.warn("[handleBpmProcessResult] 鐢熶骇璁″垝({}) 涓嶅浜庡鏍镐腑鐘舵�侊紝褰撳墠鐘舵�侊細{}", id, mps.getStatus());
+ return;
}
- boolean approved = Boolean.TRUE.equals(reqVO.getApproved());
- if (!approved && StrUtil.isBlank(reqVO.getAuditRemark())) {
- throw exception(PRO_MPS_AUDIT_REMARK_REQUIRED);
+
+ // 2. 鏍规嵁瀹℃壒缁撴灉鏇存柊鐘舵��
+ if (BpmProcessInstanceStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
+ mpsMapper.updateById(new MesProMpsDO()
+ .setId(id)
+ .setStatus(MesProMpsStatusEnum.APPROVED.getStatus())
+ .setAuditUserId(auditUserId)
+ .setAuditTime(LocalDateTime.now())
+ .setAuditRemark(StrUtil.blankToDefault(reason, "瀹℃牳閫氳繃")));
+ } else if (BpmProcessInstanceStatusEnum.REJECT.getStatus().equals(bpmResult)
+ || BpmProcessInstanceStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
+ mpsMapper.updateById(new MesProMpsDO()
+ .setId(id)
+ .setStatus(MesProMpsStatusEnum.DRAFT.getStatus())
+ .setAuditUserId(auditUserId)
+ .setAuditTime(LocalDateTime.now())
+ .setAuditRemark(StrUtil.blankToDefault(reason, "瀹℃壒椹冲洖")));
}
- mpsMapper.updateById(new MesProMpsDO()
- .setId(reqVO.getId())
- .setStatus(approved ? MesProMpsStatusEnum.APPROVED.getStatus()
- : MesProMpsStatusEnum.DRAFT.getStatus())
- .setAuditUserId(SecurityFrameworkUtils.getLoginUserId())
- .setAuditTime(LocalDateTime.now())
- .setAuditRemark(approved
- ? StrUtil.blankToDefault(reqVO.getAuditRemark(), "瀹℃牳閫氳繃")
- : reqVO.getAuditRemark()));
}
@Override
@@ -267,9 +310,16 @@
if (!MesProMpsStatusEnum.APPROVED.getStatus().equals(mps.getStatus())) {
throw exception(PRO_MPS_NOT_APPROVED);
}
- // 1. 鍙缂哄彛鐗╂枡鐢熸垚閲囪喘鏄庣粏锛氱己鍙� = 闇�姹� - 鍙敤搴撳瓨 - 鍦ㄩ�旈噰璐�
+ // 1. 鐗╂枡闇�姹備负绌鸿鏄庝骇鍝佹湭缁存姢浠讳綍 BOM锛岀洿鎺ユ彁绀猴紝閬垮厤璇姤鈥滃簱瀛樺厖瓒斥��
+ List<MesProWorkOrderItemRespVO> materialItems = getMpsItemList(id);
+ if (CollUtil.isEmpty(materialItems)) {
+ MesMdItemDO product = itemService.getItem(mps.getProductId());
+ throw exception(PRO_MPS_NO_MATERIAL_BOM, mps.getCode(),
+ product != null ? product.getName() : String.valueOf(mps.getProductId()));
+ }
+ // 2. 鍙缂哄彛鐗╂枡鐢熸垚閲囪喘鏄庣粏锛氱己鍙� = 闇�姹� - 鍙敤搴撳瓨 - 鍦ㄩ�旈噰璐�
List<ErpPurchaseRequestCreateReqDTO.Item> shortageItems = new ArrayList<>();
- for (MesProWorkOrderItemRespVO item : getMpsItemList(id)) {
+ for (MesProWorkOrderItemRespVO item : materialItems) {
if (Boolean.TRUE.equals(item.getStockSufficient())) {
continue;
}
@@ -293,14 +343,14 @@
if (CollUtil.isEmpty(shortageItems)) {
throw exception(PRO_MPS_NO_MATERIAL_SHORTAGE);
}
- // 2. 鐢熸垚閲囪喘鐢宠鍗曪紙鑽夌锛屾棤渚涘簲鍟嗭紝鍚庣画鐢遍噰璐汉鍛樿ˉ鍏呭苟鎸夋祦绋嬫彁浜わ級
+ // 3. 鐢熸垚閲囪喘鐢宠鍗曪紙鑽夌锛屾棤渚涘簲鍟嗭紝鍚庣画鐢遍噰璐汉鍛樿ˉ鍏呭苟鎸夋祦绋嬫彁浜わ級
ErpPurchaseRequestCreateReqDTO createReqDTO = new ErpPurchaseRequestCreateReqDTO()
.setRequestTime(LocalDateTime.now())
.setRequestReason("鐢熶骇璁″垝 " + mps.getCode() + " 杈呮枡閲囪喘")
.setRemark("鐢辩敓浜ц鍒掕嚜鍔ㄧ敓鎴愶紝鍏� " + shortageItems.size() + " 椤圭墿鏂欏簱瀛樹笉瓒�")
.setItems(shortageItems);
Long purchaseRequestId = purchaseRequestApi.createPurchaseRequest(createReqDTO).getCheckedData();
- // 3. 鍥炲啓閲囪喘鐢宠鍗曠紪鍙凤紝渚夸簬浠庤鍒掕拷婧�
+ // 4. 鍥炲啓閲囪喘鐢宠鍗曠紪鍙凤紝渚夸簬浠庤鍒掕拷婧�
mpsMapper.updateById(new MesProMpsDO().setId(id).setPurchaseRequestId(purchaseRequestId));
log.info("[generatePurchaseRequest] 璁″垝({}) 鐢熸垚閲囪喘鐢宠鍗�({})锛屾槑缁� {} 椤�", mps.getCode(), purchaseRequestId, shortageItems.size());
return purchaseRequestId;
@@ -638,12 +688,11 @@
}
/**
- * 鏍¢獙 MPS 瀛樺湪 + 鍙紪杈戠姸鎬侊紙鏈紑濮嬨�佸鏍镐腑锛�
+ * 鏍¢獙 MPS 瀛樺湪 + 鍙紪杈戠姸鎬侊紙浠呰崏绋匡紱瀹℃牳涓殑璁″垝闇�绛夊緟鍗忓悓鍔炲叕瀹℃壒锛�
*/
private MesProMpsDO validateMpsExistsAndEditable(Long id) {
MesProMpsDO mps = validateMpsExists(id);
- if (!MesProMpsStatusEnum.DRAFT.getStatus().equals(mps.getStatus())
- && !MesProMpsStatusEnum.AUDITING.getStatus().equals(mps.getStatus())) {
+ if (!MesProMpsStatusEnum.DRAFT.getStatus().equals(mps.getStatus())) {
throw exception(PRO_MPS_NOT_DRAFT);
}
return mps;
@@ -676,19 +725,12 @@
* 鏍规嵁 MPS 鍒涘缓鐢熶骇宸ュ崟
*/
private Long createWorkOrderFromMps(MesProMpsDO mps, BigDecimal quantity) {
- // 1. 鏍¢獙浜у搧鏄惁瀛樺湪宸ヨ壓璺嚎
+ // 1. 鏌ユ壘浜у搧宸ヨ壓璺嚎锛涙湭閰嶇疆鏃朵笉闃绘柇锛屾寜鍗曞伐搴忕敓浜у鐞�
MesProRouteProductDO routeProduct = routeProductService.getRouteProductByItemId(mps.getProductId());
- if (routeProduct == null) {
- throw exception(PRO_ROUTE_NOT_EXISTS);
- }
- Long routeId = routeProduct.getRouteId();
-
- // 2. 鏍¢獙宸ヨ壓璺嚎鏄惁鏈夊伐搴忥紙鎸変骇鍝佽繃婊ゅ叾瀹冧骇鍝佺殑浜у嚭鐗╂枡锛�
- List<MesProRouteProcessDO> routeProcesses = routeProcessService
- .getRouteProcessListByProductId(mps.getProductId());
- if (CollUtil.isEmpty(routeProcesses)) {
- throw exception(PRO_ROUTE_ENABLE_NO_PROCESS);
- }
+ Long routeId = routeProduct != null ? routeProduct.getRouteId() : null;
+ List<MesProRouteProcessDO> routeProcesses = routeProduct != null
+ ? routeProcessService.getRouteProcessListByProductId(mps.getProductId())
+ : Collections.emptyList();
// 3. 鍒涘缓鐢熶骇宸ュ崟
MesProWorkOrderSaveReqVO workOrderReq = new MesProWorkOrderSaveReqVO();
@@ -705,46 +747,50 @@
Long workOrderId = workOrderService.createWorkOrder(workOrderReq);
- // 4. 鏇存柊宸ュ崟宸ヨ壓璺嚎
- workOrderMapper.updateById(new MesProWorkOrderDO().setId(workOrderId).setRouteId(routeId));
-
- // 5. 淇濆瓨宸ュ崟宸ュ簭锛堜粠宸ヨ壓璺嚎宸ュ簭甯﹀叆锛屽惈鎶曟枡BOM锛�
- Map<Long, List<MesProRouteProductBomDO>> bomMap = routeProcessService.getRouteProcessBomMapByProductId(mps.getProductId());
- List<MesProWorkOrderProcessSaveReqVO> processList = new ArrayList<>();
- for (MesProRouteProcessDO rp : routeProcesses) {
- MesProWorkOrderProcessSaveReqVO process = new MesProWorkOrderProcessSaveReqVO();
- process.setWorkOrderId(workOrderId);
- process.setProcessId(rp.getProcessId());
- process.setSort(rp.getSort());
- process.setKeyFlag(rp.getKeyFlag());
- process.setCheckFlag(rp.getCheckFlag());
- process.setBackflushFlag(rp.getBackflushFlag());
- if (rp.getOutputItemId() != null && !rp.getOutputItemId().isBlank()) {
- process.setOutputItemIds(Arrays.stream(rp.getOutputItemId().split(","))
- .map(String::trim).filter(s -> !s.isEmpty())
- .map(Long::valueOf).collect(Collectors.toList()));
- }
- process.setColorCode(rp.getColorCode());
- // 甯﹀叆宸ヨ壓璺嚎浜у搧閰嶇疆鐨勬姤宸ヤ粨搴�
- process.setWarehouseId(routeProduct.getWarehouseId());
- process.setLocationId(routeProduct.getLocationId());
- process.setAreaId(routeProduct.getAreaId());
- // 甯﹀叆宸ュ簭鎶曟枡BOM
- List<MesProRouteProductBomDO> routeBomItems = bomMap != null ? bomMap.get(rp.getProcessId()) : null;
- if (CollUtil.isNotEmpty(routeBomItems)) {
- List<MesProWorkOrderProcessSaveReqVO.BomItem> bomItems = new ArrayList<>();
- for (MesProRouteProductBomDO bomDO : routeBomItems) {
- MesProWorkOrderProcessSaveReqVO.BomItem bomItem = new MesProWorkOrderProcessSaveReqVO.BomItem();
- bomItem.setItemId(bomDO.getItemId());
- bomItem.setQuantity(bomDO.getQuantity());
- bomItem.setRemark(bomDO.getRemark());
- bomItems.add(bomItem);
- }
- process.setBomItems(bomItems);
- }
- processList.add(process);
+ // 4. 鏇存柊宸ュ崟宸ヨ壓璺嚎锛堟湭閰嶇疆璺嚎鏃朵繚鎸佷负绌猴紝鎸夊崟宸ュ簭鐢熶骇锛�
+ if (routeId != null) {
+ workOrderMapper.updateById(new MesProWorkOrderDO().setId(workOrderId).setRouteId(routeId));
}
- workOrderProcessService.batchSaveWorkOrderProcess(processList);
+
+ // 5. 淇濆瓨宸ュ崟宸ュ簭锛堜粎閰嶇疆浜嗗伐鑹鸿矾绾垮伐搴忔椂甯﹀叆锛屽惈鎶曟枡BOM锛�
+ if (CollUtil.isNotEmpty(routeProcesses)) {
+ Map<Long, List<MesProRouteProductBomDO>> bomMap = routeProcessService.getRouteProcessBomMapByProductId(mps.getProductId());
+ List<MesProWorkOrderProcessSaveReqVO> processList = new ArrayList<>();
+ for (MesProRouteProcessDO rp : routeProcesses) {
+ MesProWorkOrderProcessSaveReqVO process = new MesProWorkOrderProcessSaveReqVO();
+ process.setWorkOrderId(workOrderId);
+ process.setProcessId(rp.getProcessId());
+ process.setSort(rp.getSort());
+ process.setKeyFlag(rp.getKeyFlag());
+ process.setCheckFlag(rp.getCheckFlag());
+ process.setBackflushFlag(rp.getBackflushFlag());
+ if (rp.getOutputItemId() != null && !rp.getOutputItemId().isBlank()) {
+ process.setOutputItemIds(Arrays.stream(rp.getOutputItemId().split(","))
+ .map(String::trim).filter(s -> !s.isEmpty())
+ .map(Long::valueOf).collect(Collectors.toList()));
+ }
+ process.setColorCode(rp.getColorCode());
+ // 甯﹀叆宸ヨ壓璺嚎浜у搧閰嶇疆鐨勬姤宸ヤ粨搴�
+ process.setWarehouseId(routeProduct.getWarehouseId());
+ process.setLocationId(routeProduct.getLocationId());
+ process.setAreaId(routeProduct.getAreaId());
+ // 甯﹀叆宸ュ簭鎶曟枡BOM
+ List<MesProRouteProductBomDO> routeBomItems = bomMap != null ? bomMap.get(rp.getProcessId()) : null;
+ if (CollUtil.isNotEmpty(routeBomItems)) {
+ List<MesProWorkOrderProcessSaveReqVO.BomItem> bomItems = new ArrayList<>();
+ for (MesProRouteProductBomDO bomDO : routeBomItems) {
+ MesProWorkOrderProcessSaveReqVO.BomItem bomItem = new MesProWorkOrderProcessSaveReqVO.BomItem();
+ bomItem.setItemId(bomDO.getItemId());
+ bomItem.setQuantity(bomDO.getQuantity());
+ bomItem.setRemark(bomDO.getRemark());
+ bomItems.add(bomItem);
+ }
+ process.setBomItems(bomItems);
+ }
+ processList.add(process);
+ }
+ workOrderProcessService.batchSaveWorkOrderProcess(processList);
+ }
// 6. 宸ュ崟鐩存帴纭涓哄凡纭鐘舵��
workOrderService.confirmWorkOrder(workOrderId);
--
Gitblit v1.9.3