| | |
| | | import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProTaskStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductBomService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceService; |
| | | import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemBatchConfigApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemBatchConfigRespDTO; |
| | | import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi; |
| | | import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsRespDTO; |
| | | import cn.iocoder.yudao.module.mes.api.event.ProductionFinishedEvent; |
| | |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.ArrayList; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | |
| | | /** |
| | | * MES 生产报工 Service 实现类 |
| | | * |
| | | * @author 芋道源码 |
| | | * @author 超级管理员 |
| | | */ |
| | | @Service |
| | | @Validated |
| | |
| | | @Resource |
| | | private MesProRouteProcessService routeProcessService; |
| | | @Resource |
| | | private MesProRouteProductBomService routeProductBomService; |
| | | private MesProWorkOrderBomService workOrderBomService; |
| | | @Resource |
| | | private MesProWorkOrderProcessService workOrderProcessService; |
| | | @Resource |
| | | @Lazy // 避免循环依赖 |
| | | private MesProTaskService taskService; |
| | |
| | | private MesMdAutoCodeRecordService autoCodeRecordService; |
| | | @Resource |
| | | private ApplicationEventPublisher eventPublisher; |
| | | @Resource |
| | | private MesMdmItemService mdmItemService; |
| | | @Resource |
| | | private MdmItemBatchConfigApi itemBatchConfigApi; |
| | | |
| | | @Override |
| | | public Long createFeedback(MesProFeedbackSaveReqVO createReqVO) { |
| | |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean approveFeedback(Long id) { |
| | | public boolean approveFeedback(MesProFeedbackApproveReqVO reqVO) { |
| | | // 1.1 校验存在 + 审批中状态 |
| | | MesProFeedbackDO feedback = validateFeedbackStatusApproving(id); |
| | | MesProFeedbackDO feedback = validateFeedbackStatusApproving(reqVO.getId()); |
| | | // 1.2 校验报工数量 > 0 |
| | | if (feedback.getFeedbackQuantity() == null |
| | | || feedback.getFeedbackQuantity().compareTo(BigDecimal.ZERO) <= 0) { |
| | |
| | | boolean backflushFlag = routeProcess != null && Boolean.TRUE.equals(routeProcess.getBackflushFlag()); |
| | | if (backflushFlag) { |
| | | // 3.1 校验倒冲工序必须配置 BOM 消耗,避免静默不扣料 |
| | | List<MesProRouteProductBomDO> boms = routeProductBomService.getRouteProductBomList( |
| | | feedback.getRouteId(), feedback.getProcessId(), feedback.getItemId()); |
| | | List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderId( |
| | | feedback.getWorkOrderId()); |
| | | if (CollUtil.isEmpty(boms)) { |
| | | throw exception(PRO_FEEDBACK_BACKFLUSH_BOM_REQUIRED); |
| | | } |
| | |
| | | } |
| | | |
| | | // 5. 先更新报工状态为已完成(确保 checkAndFinishMps 检查时当前报工已标记完成) |
| | | feedbackMapper.updateById(new MesProFeedbackDO().setId(id) |
| | | feedbackMapper.updateById(new MesProFeedbackDO().setId(reqVO.getId()) |
| | | .setStatus(MesProFeedbackStatusEnum.FINISHED.getStatus()) |
| | | .setUncheckQuantity(BigDecimal.ZERO)); |
| | | |
| | | // 6. 根据是否需要检验,决定产出和入库方式 |
| | | // 6.2 调整 只要审批成功直接更新 |
| | | // 关键工序:更新任务 + 工单的已生产数量;非关键工序:只更新任务的已生产数量(工单只看关键工序产出) |
| | | if (keyFlag) { |
| | | updateTaskAndWorkOrderByFeedback(feedback); |
| | | } else { |
| | | taskService.updateProducedQuantity(feedback.getTaskId(), |
| | | feedback.getFeedbackQuantity(), |
| | | ObjectUtil.defaultIfNull(feedback.getQualifiedQuantity(), BigDecimal.ZERO), |
| | | ObjectUtil.defaultIfNull(feedback.getUnqualifiedQuantity(), BigDecimal.ZERO)); |
| | | checkAndPublishProductionFinished(feedback.getWorkOrderId()); |
| | | |
| | | } |
| | | if (checkFlag) { |
| | | // 6.1 需要检验:生成产出单(质量状态=待检验),更新报工状态为待检验,等质检完成回调后再入库 |
| | | // 完成时回调见:MesQcIpqcServiceImpl#finishIpqc → splitPendingAndFinishProduce + updateProFeedbackWhenIpqcFinish |
| | | productProduceService.generateProductProduce(feedback, true); |
| | | feedbackMapper.updateById(new MesProFeedbackDO().setId(id) |
| | | productProduceService.generateProductProduce(feedback, true, reqVO); |
| | | feedbackMapper.updateById(new MesProFeedbackDO().setId(reqVO.getId()) |
| | | .setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus())); |
| | | return false; |
| | | } else { |
| | | // 6.2 无需检验:生成产出单(按合格/不合格拆行),直接完成入库 |
| | | MesWmProductProduceDO produce = productProduceService.generateProductProduce(feedback, false); |
| | | MesWmProductProduceDO produce = productProduceService.generateProductProduce(feedback, false, reqVO); |
| | | productProduceService.finishProductProduce(produce.getId()); |
| | | // 关键工序:更新任务 + 工单的已生产数量;非关键工序:只更新任务的已生产数量(工单只看关键工序产出) |
| | | if (keyFlag) { |
| | | updateTaskAndWorkOrderByFeedback(feedback); |
| | | } else { |
| | | taskService.updateProducedQuantity(feedback.getTaskId(), |
| | | feedback.getFeedbackQuantity(), |
| | | ObjectUtil.defaultIfNull(feedback.getQualifiedQuantity(), BigDecimal.ZERO), |
| | | ObjectUtil.defaultIfNull(feedback.getUnqualifiedQuantity(), BigDecimal.ZERO)); |
| | | checkAndPublishProductionFinished(feedback.getWorkOrderId()); |
| | | } |
| | | |
| | | } |
| | | |
| | | return true; // 已完成 |
| | |
| | | * <li>使用报工数量(feedbackQuantity),累加任务和工单的已生产数量</li> |
| | | * <li>使用产出单行按 qualityStatus 聚合的合格品/不合格品数量,累加任务的合格品/不合格品数量 |
| | | * (不直接用 feedback 上的数量,确保质检回调场景下数量来源正确)</li> |
| | | * isPush 是否推送生产完成事件 |
| | | * </ul> |
| | | * |
| | | * @param feedback 报工单 |
| | |
| | | } |
| | | } |
| | | |
| | | // 2. 更新任务的已生产/合格/不合格数量 |
| | | // 2. 更新任务的已生产/合格/不合格数量 |
| | | taskService.updateProducedQuantity(feedback.getTaskId(), |
| | | feedback.getFeedbackQuantity(), qualifiedQty, unqualifiedQty); |
| | | // 3. 更新工单的已生产数量 |
| | | workOrderService.updateProducedQuantity(feedback.getWorkOrderId(), |
| | | feedback.getFeedbackQuantity()); |
| | | |
| | | // 4. 更新 MPS 的已生产数量(如果是销售订单来源) |
| | | MesProMpsRespDTO mps = mpsApi.getMpsByWorkOrderId(feedback.getWorkOrderId()); |
| | | if (mps != null) { |
| | | mpsApi.updateMpsProducedQuantity(mps.getId(), feedback.getFeedbackQuantity()); |
| | | // 3. 更新订单的已生产数量 |
| | | // 新增逻辑:只有在该笔订单最后一个工序才更新生产订单的已生产数量 |
| | | MesProWorkOrderProcessDO lastProcess = workOrderProcessService.getLastWorkOrderProcessByWorkOrderId(feedback.getWorkOrderId()); |
| | | if (lastProcess != null && ObjUtil.equal(lastProcess.getProcessId(), feedback.getProcessId())) { |
| | | workOrderService.updateProducedQuantity(feedback.getWorkOrderId(), |
| | | feedback.getFeedbackQuantity()); |
| | | // 4. 更新 MPS 的已生产数量(如果是销售订单来源) |
| | | MesProMpsRespDTO mps = mpsApi.getMpsByWorkOrderId(feedback.getWorkOrderId()); |
| | | if (mps != null) { |
| | | mpsApi.updateMpsProducedQuantity(mps.getId(), feedback.getFeedbackQuantity()); |
| | | } |
| | | } |
| | | |
| | | |
| | | // 5. 检查并发布生产完成事件 |
| | | checkAndPublishProductionFinished(feedback.getWorkOrderId()); |
| | |
| | | // 1. 校验任务存在且未终态 |
| | | MesProTaskDO task = taskService.validateTaskNotFinished(reqVO.getTaskId()); |
| | | |
| | | // 校验停工状态不能报工 |
| | | if (!MesProTaskStatusEnum.canFeedback(task.getStatus())) { |
| | | throw exception(PRO_FEEDBACK_CANNOT_CREATE_WHEN_PAUSED); |
| | | } |
| | | |
| | | // 2. 从任务自动填充关联字段(前端无需传入,后端统一从任务获取) |
| | | reqVO.setWorkOrderId(task.getWorkOrderId()); |
| | | reqVO.setRouteId(task.getRouteId()); |
| | |
| | | public MesProFeedbackQuickCreateRespVO quickCreateFeedback(MesProFeedbackQuickCreateReqVO reqVO) { |
| | | // 1. 校验任务存在且非终态 |
| | | MesProTaskDO task = taskService.validateTaskNotFinished(reqVO.getTaskId()); |
| | | |
| | | // 校验停工状态不能报工 |
| | | if (!MesProTaskStatusEnum.canFeedback(task.getStatus())) { |
| | | throw exception(PRO_FEEDBACK_CANNOT_CREATE_WHEN_PAUSED); |
| | | } |
| | | |
| | | // 2. 校验工艺路线工序配置 |
| | | MesProRouteProcessDO routeProcess = routeProcessService.getRouteProcessByRouteIdAndProcessId( |
| | | task.getRouteId(), task.getProcessId()); |
| | |
| | | |
| | | // 4. 更新任务/工单的已生产数量 |
| | | feedback.setQualifiedQuantity(qualifiedQty).setUnqualifiedQuantity(unqualifiedQty); |
| | | updateTaskAndWorkOrderByFeedback(feedback); |
| | | //5.检查并发布生产完成事件 |
| | | checkAndPublishProductionFinished(feedback.getWorkOrderId()); |
| | | } |
| | | |
| | | @Override |
| | | public MesProFeedbackBatchCheckRespVO checkBatchRequirements(Long feedbackId) { |
| | | MesProFeedbackDO feedback = validateFeedbackExists(feedbackId); |
| | | MesProFeedbackBatchCheckRespVO respVO = new MesProFeedbackBatchCheckRespVO(); |
| | | |
| | | // 1. 检查物料是否启用批次管理 |
| | | MesMdmItemDO item = mdmItemService.getItem(feedback.getItemId()); |
| | | if (item == null || Boolean.FALSE.equals(item.getIsBatchManaged())) { |
| | | respVO.setNeedBatchInput(false); |
| | | respVO.setIsBatchManaged(false); |
| | | return respVO; |
| | | } |
| | | respVO.setIsBatchManaged(true); |
| | | |
| | | // 2. 获取批次配置 |
| | | MdmItemBatchConfigRespDTO config = itemBatchConfigApi.getItemBatchConfig(feedback.getItemId()); |
| | | if (config == null) { |
| | | respVO.setNeedBatchInput(false); |
| | | return respVO; |
| | | } |
| | | |
| | | // 3. 填充配置标志 |
| | | respVO.setProduceDateFlag(config.getProduceDateFlag()); |
| | | respVO.setExpireDateFlag(config.getExpireDateFlag()); |
| | | respVO.setReceiptDateFlag(config.getReceiptDateFlag()); |
| | | respVO.setVendorFlag(config.getVendorFlag()); |
| | | respVO.setClientFlag(config.getClientFlag()); |
| | | respVO.setSalesOrderCodeFlag(config.getSalesOrderCodeFlag()); |
| | | respVO.setPurchaseOrderCodeFlag(config.getPurchaseOrderCodeFlag()); |
| | | respVO.setWorkOrderFlag(config.getWorkOrderFlag()); |
| | | respVO.setTaskFlag(config.getTaskFlag()); |
| | | respVO.setWorkstationFlag(config.getWorkstationFlag()); |
| | | respVO.setToolFlag(config.getToolFlag()); |
| | | respVO.setMoldFlag(config.getMoldFlag()); |
| | | respVO.setLotNumberFlag(config.getLotNumberFlag()); |
| | | respVO.setQualityStatusFlag(config.getQualityStatusFlag()); |
| | | |
| | | // 4. 判断哪些字段已自动填充(报工和生产过程中必然有值的字段) |
| | | List<String> autoFilled = new ArrayList<>(); |
| | | // 生产日期:审批时自动取当天 |
| | | autoFilled.add("produceDate"); |
| | | // 生产工单:从报工记录获取 |
| | | autoFilled.add("workOrderId"); |
| | | // 生产任务:从报工记录获取 |
| | | autoFilled.add("taskId"); |
| | | // 工作站:从报工记录获取 |
| | | autoFilled.add("workstationId"); |
| | | respVO.setAutoFilledFields(autoFilled); |
| | | |
| | | // 5. 判断是否需要弹窗:存在必填但无法自动填充的字段 |
| | | boolean needBatchInput = false; |
| | | if (Boolean.TRUE.equals(config.getExpireDateFlag())) needBatchInput = true; |
| | | if (Boolean.TRUE.equals(config.getReceiptDateFlag())) needBatchInput = true; |
| | | if (Boolean.TRUE.equals(config.getVendorFlag())) needBatchInput = true; |
| | | if (Boolean.TRUE.equals(config.getClientFlag())) needBatchInput = true; |
| | | if (Boolean.TRUE.equals(config.getSalesOrderCodeFlag())) needBatchInput = true; |
| | | if (Boolean.TRUE.equals(config.getPurchaseOrderCodeFlag())) needBatchInput = true; |
| | | if (Boolean.TRUE.equals(config.getToolFlag())) needBatchInput = true; |
| | | if (Boolean.TRUE.equals(config.getMoldFlag())) needBatchInput = true; |
| | | if (Boolean.TRUE.equals(config.getLotNumberFlag())) needBatchInput = true; |
| | | if (Boolean.TRUE.equals(config.getQualityStatusFlag())) needBatchInput = true; |
| | | respVO.setNeedBatchInput(needBatchInput); |
| | | |
| | | return respVO; |
| | | } |
| | | |
| | | } |