From 97f048ce14db07ee41ca05f9c7010dfe132150e4 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 03 十一月 2026 11:06:59 +0800
Subject: [PATCH] feat(mes): 补生产准备检查与仓储收发存汇总接口,附 CFWJ 演示数据种子脚本
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java | 482 +++++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 412 insertions(+), 70 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 e380ac7..4584c96 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,11 +3,24 @@
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.MesProMpsPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.mps.vo.MesProMpsPreparationCheckRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.mps.vo.MesProMpsSaveReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderItemRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.mps.MesProMpsDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.mps.MesProMpsMergeItemDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMapper;
@@ -18,11 +31,18 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
+import cn.iocoder.yudao.module.mes.enums.dv.MesDvMachineryStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProMpsStatusEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderSourceTypeEnum;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum;
+import cn.iocoder.yudao.module.mes.service.dv.machinery.MesDvMachineryService;
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
+import cn.iocoder.yudao.module.mes.service.pro.material.MesProMaterialRequirementService;
+import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
@@ -45,6 +65,8 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -53,7 +75,7 @@
/**
* MES 鐢熶骇涓昏鍒掞紙MPS锛塖ervice 瀹炵幇绫�
*
- * @author 鑺嬮亾婧愮爜
+ * @author 瓒呯骇绠$悊鍛�
*/
@Service
@Validated
@@ -64,6 +86,11 @@
* MPS 缂栫爜瑙勫垯
*/
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;
@@ -87,6 +114,18 @@
private MesProRouteProcessService routeProcessService;
@Resource
private MesProWorkOrderProcessService workOrderProcessService;
+ @Resource
+ private MesProMaterialRequirementService materialRequirementService;
+ @Resource
+ private MesProProcessService processService;
+ @Resource
+ private MesDvMachineryService machineryService;
+ @Resource
+ private ErpPurchaseOrderApi purchaseOrderApi;
+ @Resource
+ private ErpPurchaseRequestApi purchaseRequestApi;
+ @Resource
+ private BpmProcessInstanceApi processInstanceApi;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -140,6 +179,13 @@
public Long createMps(MesProMpsSaveReqVO createReqVO) {
// 鏍¢獙浜у搧瀛樺湪
itemService.validateItemExists(createReqVO.getProductId());
+ // 璁¢噺鍗曚綅鏈~鍐欐椂锛岄粯璁ゅ彇浜у搧鍗曚綅
+ if (createReqVO.getUnitMeasureId() == null) {
+ MesMdItemDO product = itemService.getItem(createReqVO.getProductId());
+ if (product != null) {
+ createReqVO.setUnitMeasureId(product.getUnitMeasureId());
+ }
+ }
// 濡傛灉鏈~鍐欑紪鐮侊紝鑷姩鐢熸垚
if (StrUtil.isBlank(createReqVO.getCode())) {
@@ -163,13 +209,13 @@
@Override
@Transactional(rollbackFor = Exception.class)
public void updateMps(MesProMpsSaveReqVO updateReqVO) {
- // 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
- validateMpsExistsAndDraft(updateReqVO.getId());
+ // 鏍¢獙瀛樺湪 + 鍙紪杈戠姸鎬侊紙鏈紑濮嬨�佸鏍镐腑锛�
+ MesProMpsDO mps = validateMpsExistsAndEditable(updateReqVO.getId());
// 鏍¢獙缂栫爜鍞竴
validateMpsCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
- // 鏇存柊
+ // 鏇存柊锛堜粎鑽夌鍙紪杈戯級
MesProMpsDO updateObj = BeanUtils.toBean(updateReqVO, MesProMpsDO.class);
mpsMapper.updateById(updateObj);
}
@@ -177,8 +223,8 @@
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteMps(Long id) {
- // 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
- MesProMpsDO mps = validateMpsExistsAndDraft(id);
+ // 鏍¢獙瀛樺湪 + 鍙紪杈戠姸鎬侊紙鏈紑濮嬨�佸鏍镐腑锛�
+ MesProMpsDO mps = validateMpsExistsAndEditable(id);
// 鏍¢獙鏄惁宸插叧鑱斿伐鍗�
if (mps.getWorkOrderId() != null) {
@@ -191,9 +237,255 @@
@Override
@Transactional(rollbackFor = Exception.class)
+ public void submitMps(Long id) {
+ MesProMpsDO mps = validateMpsExists(id);
+ // 鍙湁鑽夌鍙互鎻愪氦瀹℃牳锛涘鏍镐腑鐨勮鍒掗渶绛夊緟鍗忓悓鍔炲叕瀹℃壒缁撴灉
+ 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())
+ .setProcessInstanceId(processInstanceId));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ 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())) {
+ log.warn("[handleBpmProcessResult] 鐢熶骇璁″垝({}) 涓嶅浜庡鏍镐腑鐘舵�侊紝褰撳墠鐘舵�侊細{}", id, mps.getStatus());
+ return;
+ }
+
+ // 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, "瀹℃壒椹冲洖")));
+ }
+ }
+
+ @Override
+ public List<MesProWorkOrderItemRespVO> getMpsItemList(Long id) {
+ MesProMpsDO mps = validateMpsExists(id);
+ // 鎸変骇鍝� BOM锛堜紭鍏堝伐鑹鸿矾绾� BOM锛夊睍寮�鍙跺瓙鐗╂枡锛屽苟鎸夎鍒掓暟閲忔姌绠楅渶姹傦紱
+ // 璁″垝灏氭湭鐢熸垚宸ュ崟锛屾棤鏉ユ簮涓烘湰璁″垝鐨勫湪閫旈噰璐紝鐩存帴鎸夊彲鐢ㄥ簱瀛樺垽鏂槸鍚﹀厖瓒�
+ return materialRequirementService.getItemListByProduct(mps.getProductId(), mps.getQuantity());
+ }
+
+ @Override
+ public MesProMpsPreparationCheckRespVO getPreparationCheck(Long id) {
+ MesProMpsDO mps = validateMpsExists(id);
+ MesProMpsPreparationCheckRespVO result = new MesProMpsPreparationCheckRespVO();
+ result.setMpsId(mps.getId());
+ result.setMpsCode(mps.getCode());
+ result.setProductId(mps.getProductId());
+ result.setQuantity(mps.getQuantity());
+ MesMdItemDO product = itemService.getItem(mps.getProductId());
+ result.setProductName(product != null ? product.getName() : String.valueOf(mps.getProductId()));
+
+ // 1. 鐗╂枡妫�鏌ワ細闇�姹� - 鍙敤搴撳瓨 - 鍦ㄩ�旈噰璐�
+ List<MesProWorkOrderItemRespVO> materialItems = getMpsItemList(id);
+ int materialShortageCount = 0;
+ for (MesProWorkOrderItemRespVO item : materialItems) {
+ MesProMpsPreparationCheckRespVO.MaterialCheck check = new MesProMpsPreparationCheckRespVO.MaterialCheck();
+ check.setItemId(item.getItemId());
+ check.setItemCode(item.getItemCode());
+ check.setItemName(item.getItemName());
+ check.setSpecification(item.getItemSpecification());
+ check.setUnitMeasureName(item.getUnitMeasureName());
+ check.setRequiredQuantity(nullToZero(item.getQuantity()));
+ check.setStockQuantity(nullToZero(item.getStockQuantity()));
+ check.setPurchaseOrderQuantity(nullToZero(item.getPurchaseOrderQuantity()));
+ BigDecimal supply = nullToZero(item.getStockQuantity()).add(nullToZero(item.getPurchaseOrderQuantity()));
+ BigDecimal shortage = nullToZero(item.getQuantity()).subtract(supply);
+ if (shortage.compareTo(BigDecimal.ZERO) < 0) {
+ shortage = BigDecimal.ZERO;
+ }
+ check.setShortageQuantity(shortage);
+ check.setSufficient(shortage.compareTo(BigDecimal.ZERO) <= 0);
+ if (!Boolean.TRUE.equals(check.getSufficient())) {
+ materialShortageCount++;
+ }
+ result.getMaterials().add(check);
+ }
+ result.setMaterialReady(materialShortageCount == 0);
+
+ // 2. 璁惧妫�鏌ワ細鎸変骇鍝佺殑宸ヨ壓璺嚎閫愬伐搴忔煡鎵剧粦瀹氳澶�
+ List<MesProRouteProcessDO> routeProcesses = routeProcessService.getRouteProcessListByProductId(mps.getProductId());
+ int equipmentUnavailableCount = 0;
+ if (CollUtil.isEmpty(routeProcesses)) {
+ MesProMpsPreparationCheckRespVO.EquipmentCheck check = new MesProMpsPreparationCheckRespVO.EquipmentCheck();
+ check.setProcessName("宸ヨ壓璺嚎");
+ check.setAvailable(false);
+ check.setReason("浜у搧鏈厤缃伐鑹鸿矾绾匡紝鏃犳硶纭畾鎵�闇�璁惧锛涜鍏堝湪銆屽伐鑹鸿矾绾裤�嶄腑缁存姢璇ヤ骇鍝佺殑宸ヨ壓璺嚎鍚庡啀妫�鏌�");
+ result.getEquipments().add(check);
+ equipmentUnavailableCount++;
+ } else {
+ Map<Long, MesProRouteProcessDO> processIdMap = routeProcesses.stream()
+ .filter(rp -> rp.getProcessId() != null)
+ .collect(Collectors.toMap(MesProRouteProcessDO::getProcessId, rp -> rp, (a, b) -> a));
+ Map<Long, MesProProcessDO> processMap = processService.getProcessMap(processIdMap.keySet());
+ // 璁惧涓庡伐搴�/宸ヤ綅鐨勭粦瀹氾細浼樺厛鍙栬澶囦笂鐨勫伐搴� ID锛屽叾娆″洖閫�宸ヤ綅 ID锛堝巻鍙叉暟鎹腑涓よ�呬竴鑷达級
+ List<MesDvMachineryDO> machineries = machineryService.getMachineryList();
+ Map<Long, List<MesDvMachineryDO>> machineryMap = machineries.stream()
+ .filter(m -> m.getProcessId() != null || m.getWorkstationId() != null)
+ .collect(Collectors.groupingBy(m -> m.getProcessId() != null ? m.getProcessId() : m.getWorkstationId()));
+ for (MesProRouteProcessDO rp : routeProcesses) {
+ MesProProcessDO process = processMap.get(rp.getProcessId());
+ String processName = process != null ? process.getName() : String.valueOf(rp.getProcessId());
+ List<MesDvMachineryDO> boundMachineries = machineryMap.get(rp.getProcessId());
+ if (CollUtil.isEmpty(boundMachineries)) {
+ MesProMpsPreparationCheckRespVO.EquipmentCheck check = new MesProMpsPreparationCheckRespVO.EquipmentCheck();
+ check.setProcessId(rp.getProcessId());
+ check.setProcessName(processName);
+ check.setAvailable(false);
+ check.setReason("璇ュ伐搴忔湭缁戝畾璁惧锛岃鍒般�岃澶囧熀纭�淇℃伅绠$悊銆嶄负璇ュ伐搴忕粦瀹氳澶�");
+ result.getEquipments().add(check);
+ equipmentUnavailableCount++;
+ continue;
+ }
+ for (MesDvMachineryDO machinery : boundMachineries) {
+ MesProMpsPreparationCheckRespVO.EquipmentCheck check = new MesProMpsPreparationCheckRespVO.EquipmentCheck();
+ check.setProcessId(rp.getProcessId());
+ check.setProcessName(processName);
+ check.setMachineryId(machinery.getId());
+ check.setMachineryCode(machinery.getCode());
+ check.setMachineryName(machinery.getName());
+ check.setStatus(machinery.getStatus());
+ MesDvMachineryStatusEnum statusEnum = MesDvMachineryStatusEnum.valueOfStatus(machinery.getStatus());
+ check.setStatusName(statusEnum != null ? statusEnum.getName() : "鏈煡");
+ boolean available = statusEnum != MesDvMachineryStatusEnum.MAINTENANCE;
+ check.setAvailable(available);
+ if (available) {
+ check.setReason(statusEnum == MesDvMachineryStatusEnum.PRODUCING ? "璁惧鍗犵敤涓紝鎺掍骇鏃堕渶纭鍙敤鏃舵" : null);
+ } else {
+ check.setReason("璁惧澶勪簬缁存姢涓紝璇风瓑寰呯淮鎶ゅ畬鎴愭垨鏀规淳鍏朵粬璁惧");
+ equipmentUnavailableCount++;
+ }
+ result.getEquipments().add(check);
+ }
+ }
+ }
+ result.setEquipmentReady(equipmentUnavailableCount == 0);
+ boolean ready = Boolean.TRUE.equals(result.getMaterialReady()) && Boolean.TRUE.equals(result.getEquipmentReady());
+ result.setReady(ready);
+ StringBuilder summary = new StringBuilder();
+ if (materialShortageCount > 0) {
+ summary.append(materialShortageCount).append(" 椤圭墿鏂欑煭缂�");
+ }
+ if (equipmentUnavailableCount > 0) {
+ if (summary.length() > 0) {
+ summary.append("锛�");
+ }
+ summary.append(equipmentUnavailableCount).append(" 椤硅澶囦笉鍙敤");
+ }
+ if (summary.length() == 0) {
+ summary.append("鐗╂枡銆佽澶囧潎宸插榻愶紝鍙互瀹夋帓鐢熶骇");
+ }
+ result.setSummary(summary.toString());
+ return result;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long generatePurchaseRequest(Long id) {
+ MesProMpsDO mps = validateMpsExists(id);
+ if (!MesProMpsStatusEnum.APPROVED.getStatus().equals(mps.getStatus())) {
+ throw exception(PRO_MPS_NOT_APPROVED);
+ }
+ // 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 : materialItems) {
+ if (Boolean.TRUE.equals(item.getStockSufficient())) {
+ continue;
+ }
+ BigDecimal supply = nullToZero(item.getStockQuantity())
+ .add(nullToZero(item.getPurchaseOrderQuantity()));
+ BigDecimal shortage = nullToZero(item.getQuantity()).subtract(supply);
+ if (shortage.compareTo(BigDecimal.ZERO) <= 0) {
+ continue;
+ }
+ MesMdItemDO itemDO = itemService.getItem(item.getItemId());
+ if (itemDO == null) {
+ continue;
+ }
+ shortageItems.add(new ErpPurchaseRequestCreateReqDTO.Item()
+ .setProductId(item.getItemId())
+ .setProductUnitId(itemDO.getUnitMeasureId())
+ .setCount(shortage)
+ .setDemandTime(mps.getRequestDate() != null ? mps.getRequestDate().toLocalDate() : null)
+ .setRemark("鐢熶骇璁″垝 " + mps.getCode() + " 搴撳瓨缂哄彛 " + shortage.stripTrailingZeros().toPlainString()));
+ }
+ if (CollUtil.isEmpty(shortageItems)) {
+ throw exception(PRO_MPS_NO_MATERIAL_SHORTAGE);
+ }
+ // 3. 鐢熸垚閲囪喘鐢宠鍗曪紙鑽夌锛屾棤渚涘簲鍟嗭紝鍚庣画鐢遍噰璐汉鍛樿ˉ鍏呭苟鎸夋祦绋嬫彁浜わ級
+ ErpPurchaseRequestCreateReqDTO createReqDTO = new ErpPurchaseRequestCreateReqDTO()
+ .setRequestTime(LocalDateTime.now())
+ .setRequestReason("鐢熶骇璁″垝 " + mps.getCode() + " 杈呮枡閲囪喘")
+ .setRemark("鐢辩敓浜ц鍒掕嚜鍔ㄧ敓鎴愶紝鍏� " + shortageItems.size() + " 椤圭墿鏂欏簱瀛樹笉瓒�")
+ .setItems(shortageItems);
+ Long purchaseRequestId = purchaseRequestApi.createPurchaseRequest(createReqDTO).getCheckedData();
+ // 4. 鍥炲啓閲囪喘鐢宠鍗曠紪鍙凤紝渚夸簬浠庤鍒掕拷婧�
+ mpsMapper.updateById(new MesProMpsDO().setId(id).setPurchaseRequestId(purchaseRequestId));
+ log.info("[generatePurchaseRequest] 璁″垝({}) 鐢熸垚閲囪喘鐢宠鍗�({})锛屾槑缁� {} 椤�", mps.getCode(), purchaseRequestId, shortageItems.size());
+ return purchaseRequestId;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
public Long issueMps(Long id) {
- // 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
- MesProMpsDO mps = validateMpsExistsAndDraft(id);
+ // 鏍¢獙瀛樺湪 + 宸插鏍哥姸鎬�
+ MesProMpsDO mps = validateMpsExistsAndApproved(id);
// 鍒涘缓鐢熶骇宸ュ崟
Long workOrderId = createWorkOrderFromMps(mps, mps.getQuantity());
@@ -222,11 +514,11 @@
throw exception(PRO_MPS_NOT_EXISTS);
}
- // 鏍¢獙锛氬繀椤绘槸鐩稿悓浜у搧銆佽崏绋跨姸鎬�
+ // 鏍¢獙锛氬繀椤绘槸鐩稿悓浜у搧銆佸凡瀹℃牳鐘舵��
Long productId = null;
for (MesProMpsDO mps : mpsList) {
- if (!MesProMpsStatusEnum.DRAFT.getStatus().equals(mps.getStatus())) {
- throw exception(PRO_MPS_NOT_DRAFT);
+ if (!MesProMpsStatusEnum.APPROVED.getStatus().equals(mps.getStatus())) {
+ throw exception(PRO_MPS_NOT_APPROVED);
}
if (productId == null) {
productId = mps.getProductId();
@@ -282,14 +574,32 @@
throw exception(PRO_MPS_NOT_ISSUED);
}
- // 绾ц仈鍙栨秷鍏宠仈鐨勭敓浜у伐鍗�
+ // 绾ц仈鍙栨秷鍏宠仈鐨勭敓浜у伐鍗曪紙璺熼殢宸ュ崟褰撳墠鐘舵�侊紝涓斾笉璇激鍚堝苟涓嬪彂鐨勫叡浜伐鍗曪級
if (mps.getWorkOrderId() != null) {
- try {
- workOrderService.cancelWorkOrder(mps.getWorkOrderId());
- log.info("[cancelMps] 绾ц仈鍙栨秷鐢熶骇宸ュ崟锛寃orkOrderId={}", mps.getWorkOrderId());
- } catch (Exception e) {
- // 宸ュ崟鍙兘宸插畬鎴愭垨宸插彇娑堬紝璁板綍鏃ュ織浣嗕笉闃绘 MPS 鍙栨秷
- log.warn("[cancelMps] 鍙栨秷鐢熶骇宸ュ崟澶辫触锛寃orkOrderId={}锛屽師鍥狅細{}", mps.getWorkOrderId(), e.getMessage());
+ // 鏂板閫昏緫锛氳嫢璇ュ伐鍗曟湁鎶ュ伐璁板綍锛屽垯涓嶅厑璁稿彇娑� MPS
+ Long feedbackCount = feedbackMapper.selectCount(MesProFeedbackDO::getWorkOrderId, mps.getWorkOrderId());
+ if (feedbackCount != null && feedbackCount > 0) {
+ throw exception(PRO_MPS_WORK_ORDER_HAS_FEEDBACK);
+ }
+
+ // 1. 缁熻璇ュ伐鍗曡澶氬皯鏉� MPS 寮曠敤锛氬悎骞朵笅鍙戠殑澶氭潯 MPS 鍏变韩鍚屼竴涓伐鍗�
+ Long refCount = mpsMapper.selectCount(MesProMpsDO::getWorkOrderId, mps.getWorkOrderId());
+ if (refCount != null && refCount > 1) {
+ // 鍏变韩宸ュ崟锛氬彧鍙栨秷褰撳墠 MPS锛屼笉鍔ㄥ伐鍗�
+ log.info("[cancelMps] 鐢熶骇宸ュ崟({}) 琚� {} 鏉� MPS 鍏辩敤锛岃烦杩囩骇鑱斿彇娑堝伐鍗�",
+ mps.getWorkOrderId(), refCount);
+ } else {
+ MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(mps.getWorkOrderId());
+ if (workOrder != null
+ && MesProWorkOrderStatusEnum.CONFIRMED.getStatus().equals(workOrder.getStatus())) {
+ // 鍙湁"宸茬‘璁�"鐘舵�佺殑宸ュ崟鎵嶉渶瑕佺骇鑱斿彇娑�
+ workOrderService.cancelWorkOrder(mps.getWorkOrderId());
+ log.info("[cancelMps] 绾ц仈鍙栨秷鐢熶骇宸ュ崟锛寃orkOrderId={}", mps.getWorkOrderId());
+ } else {
+ // 宸ュ崟宸叉槸鑽夌/宸插畬鎴�/宸插彇娑堬紝鏃犻渶绾ц仈鍙栨秷锛岀洿鎺ュ彇娑� MPS
+ log.info("[cancelMps] 鐢熶骇宸ュ崟({}) 鐘舵�佷负 {}锛屾棤闇�绾ц仈鍙栨秷",
+ mps.getWorkOrderId(), workOrder != null ? workOrder.getStatus() : "null");
+ }
}
}
@@ -329,9 +639,7 @@
@Override
public PageResult<MesProMpsDO> getMpsPage(MesProMpsPageReqVO pageReqVO) {
- // 鑾峰彇鐢ㄦ埛鏈夋潈闄愮殑瀹㈡埛 ID 鍒楄〃
- List<Long> permittedClientIds = customerApi.getPermittedCustomerIds();
- return mpsMapper.selectPage(pageReqVO, permittedClientIds);
+ return mpsMapper.selectPage(pageReqVO, (List<Long>) null);
}
@Override
@@ -424,6 +732,10 @@
* 鍒ゆ柇鍗曚釜 MPS 鏄惁瀹屾垚
*/
private boolean isMpsFinished(MesProMpsDO mps) {
+ // 宸插彇娑堢殑 MPS 瑙嗕负缁堟�侊紝涓嶅啀闃诲鐢熶骇瀹屾垚鍒ゆ柇锛堜笌 checkCanCreateSalesNotice 鐨勫彛寰勪繚鎸佷竴鑷达級
+ if (MesProMpsStatusEnum.CANCELED.getStatus().equals(mps.getStatus())) {
+ return true;
+ }
// 1. 宸茬敓浜ф暟閲� >= 璁″垝鏁伴噺
BigDecimal quantityProduced = ObjUtil.defaultIfNull(mps.getQuantityProduced(), BigDecimal.ZERO);
if (quantityProduced.compareTo(mps.getQuantity()) < 0) {
@@ -490,6 +802,32 @@
}
/**
+ * 鏍¢獙 MPS 瀛樺湪 + 宸插鏍哥姸鎬�
+ */
+ private MesProMpsDO validateMpsExistsAndApproved(Long id) {
+ MesProMpsDO mps = validateMpsExists(id);
+ if (!MesProMpsStatusEnum.APPROVED.getStatus().equals(mps.getStatus())) {
+ throw exception(PRO_MPS_NOT_APPROVED);
+ }
+ return mps;
+ }
+
+ /**
+ * 鏍¢獙 MPS 瀛樺湪 + 鍙紪杈戠姸鎬侊紙浠呰崏绋匡紱瀹℃牳涓殑璁″垝闇�绛夊緟鍗忓悓鍔炲叕瀹℃壒锛�
+ */
+ private MesProMpsDO validateMpsExistsAndEditable(Long id) {
+ MesProMpsDO mps = validateMpsExists(id);
+ if (!MesProMpsStatusEnum.DRAFT.getStatus().equals(mps.getStatus())) {
+ throw exception(PRO_MPS_NOT_DRAFT);
+ }
+ return mps;
+ }
+
+ private static BigDecimal nullToZero(BigDecimal value) {
+ return value != null ? value : BigDecimal.ZERO;
+ }
+
+ /**
* 鏍¢獙 MPS 缂栫爜鍞竴
*/
private void validateMpsCodeUnique(Long id, String code) {
@@ -512,18 +850,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.getRouteProcessListByRouteId(routeId);
- 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();
@@ -540,42 +872,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());
- // 甯﹀叆宸ュ簭鎶曟枡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);
@@ -588,11 +928,13 @@
*/
private String generateWorkOrderCode() {
try {
- return autoCodeRecordService.generateAutoCode("WO");
+ return autoCodeRecordService.generateAutoCode(
+ MesMdAutoCodeRuleCodeEnum.PRO_WORK_ORDER_CODE.getCode());
} catch (Exception e) {
- log.warn("[generateWorkOrderCode] 缂栫爜瑙勫垯 WO 涓嶅瓨鍦紝浣跨敤鏃堕棿鎴崇敓鎴�");
+ log.warn("[generateWorkOrderCode] 缂栫爜瑙勫垯 {} 涓嶅瓨鍦紝浣跨敤鏃堕棿鎴崇敓鎴�",
+ MesMdAutoCodeRuleCodeEnum.PRO_WORK_ORDER_CODE.getCode());
return "WO" + System.currentTimeMillis();
}
}
-}
\ No newline at end of file
+}
--
Gitblit v1.9.3