| | |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackTypeEnum; |
| | | 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.process.MesProProcessDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO; |
| | | 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.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.process.MesProProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService; |
| | | 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.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.wm.productreceipt.MesWmProductReceiptService; |
| | | 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 org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.Duration; |
| | | import java.time.LocalDateTime; |
| | | import java.util.ArrayList; |
| | | import java.util.Collection; |
| | |
| | | @Resource |
| | | private MesProRouteProcessService routeProcessService; |
| | | @Resource |
| | | private MesProProcessService processService; |
| | | @Resource |
| | | private MesProWorkOrderBomService workOrderBomService; |
| | | @Resource |
| | | private MesProWorkOrderProcessService workOrderProcessService; |
| | |
| | | private MesWmItemConsumeService itemConsumeService; |
| | | @Resource |
| | | private MesWmProductProduceService productProduceService; |
| | | @Resource |
| | | @Lazy // 避免循环依赖:产品收货单自动生成逻辑依赖报工 Service |
| | | private MesWmProductReceiptService productReceiptService; |
| | | @Resource |
| | | private MesWmProductProduceLineService produceLineService; |
| | | @Resource |
| | |
| | | public boolean approveFeedback(MesProFeedbackApproveReqVO reqVO) { |
| | | // 1.1 校验存在 + 审批中状态 |
| | | MesProFeedbackDO feedback = validateFeedbackStatusApproving(reqVO.getId()); |
| | | // 1.2 校验报工数量 > 0 |
| | | if (feedback.getFeedbackQuantity() == null |
| | | || feedback.getFeedbackQuantity().compareTo(BigDecimal.ZERO) <= 0) { |
| | | // 1.2 校验任务未完成 + 等待时长倒计时 |
| | | MesProTaskDO task = taskService.validateTaskNotFinished(feedback.getTaskId()); |
| | | MesProProcessDO process = validateWaitDuration(task); |
| | | BigDecimal fbQty = feedback.getFeedbackQuantity() == null |
| | | ? BigDecimal.ZERO : feedback.getFeedbackQuantity(); |
| | | // 1.3 校验报工数量:默认必须 > 0;工序配置"不需要填写报工数量"时允许为空 |
| | | if (process == null || !Boolean.FALSE.equals(process.getNeedReportQuantity())) { |
| | | if (fbQty.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw exception(PRO_FEEDBACK_QUANTITY_MUST_POSITIVE); |
| | | } |
| | | // 1.3 校验任务未完成 |
| | | taskService.validateTaskNotFinished(feedback.getTaskId()); |
| | | } |
| | | |
| | | // 2.1 查询工序的关键工序标识 + 检验标识(需在 uncheckQuantity 校验之前,因为质检工序允许 uncheckQuantity > 0) |
| | | MesProRouteProcessDO routeProcess = routeProcessService.getRouteProcessByRouteIdAndProcessId( |
| | |
| | | boolean keyFlag = routeProcess != null && Boolean.TRUE.equals(routeProcess.getKeyFlag()); |
| | | boolean checkFlag = routeProcess != null && Boolean.TRUE.equals(routeProcess.getCheckFlag()); |
| | | // 2.2 非质检工序:合格品/不良品数量由质检确定;本工序不经过质检,报工未填写时默认全部为合格品 |
| | | // (数量为空/0 时按 0 处理,避免写入 null) |
| | | if (!checkFlag |
| | | && feedback.getQualifiedQuantity() == null |
| | | && feedback.getUnqualifiedQuantity() == null) { |
| | | feedback.setQualifiedQuantity(feedback.getFeedbackQuantity()); |
| | | feedback.setQualifiedQuantity(fbQty); |
| | | feedback.setUnqualifiedQuantity(BigDecimal.ZERO); |
| | | feedbackMapper.updateById(new MesProFeedbackDO().setId(feedback.getId()) |
| | | .setQualifiedQuantity(feedback.getFeedbackQuantity()) |
| | | .setQualifiedQuantity(fbQty) |
| | | .setUnqualifiedQuantity(BigDecimal.ZERO)); |
| | | } |
| | | // 2.3 非质检工序:仍有待检数量时不能审批(质检工序的 uncheckQuantity > 0 是正常状态,不做拦截) |
| | |
| | | } |
| | | |
| | | // 3. 物料消耗:倒冲工序按 BOM 自动扣料;非倒冲工序跳过(物料走手工领料/投料) |
| | | // 报工数量为空/0 时跳过倒冲扣料,避免空数量误扣线边库 |
| | | boolean backflushFlag = routeProcess != null && Boolean.TRUE.equals(routeProcess.getBackflushFlag()); |
| | | if (backflushFlag) { |
| | | if (backflushFlag && fbQty.compareTo(BigDecimal.ZERO) > 0) { |
| | | // 3.1 校验倒冲工序必须配置 BOM 消耗,避免静默不扣料 |
| | | List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderId( |
| | | feedback.getWorkOrderId()); |
| | |
| | | feedbackMapper.updateById(new MesProFeedbackDO().setId(reqVO.getId()) |
| | | .setStatus(MesProFeedbackStatusEnum.FINISHED.getStatus()) |
| | | .setUncheckQuantity(BigDecimal.ZERO)); |
| | | |
| | | // 5.1 报工数量为空/0("不需要填写报工数量"的工序):无产出可入库,仅更新任务进度并结束 |
| | | if (fbQty.compareTo(BigDecimal.ZERO) <= 0) { |
| | | taskService.updateProducedQuantity(feedback.getTaskId(), |
| | | BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO); |
| | | checkAndPublishProductionFinished(feedback.getWorkOrderId()); |
| | | return true; |
| | | } |
| | | |
| | | // 6. 根据是否需要检验,决定产出和入库方式 |
| | | // 6.2 调整 只要审批成功直接更新 |
| | |
| | | // 6.2 无需检验:生成产出单(按合格/不合格拆行),直接完成入库 |
| | | MesWmProductProduceDO produce = productProduceService.generateProductProduce(feedback, false, reqVO); |
| | | productProduceService.finishProductProduce(produce.getId()); |
| | | // 6.3 报工产出定稿后自动生成产品入库单(一次报工=一条,幂等) |
| | | productReceiptService.createProductReceiptByFeedback(feedback.getId()); |
| | | |
| | | } |
| | | |
| | |
| | | if (routeProcess == null) { |
| | | throw exception(PRO_FEEDBACK_ROUTE_PROCESS_INVALID); |
| | | } |
| | | // 4.1 校验数量:无论是否需要检验,报工只需填报工数量,且必须 > 0 |
| | | // 4. 校验等待时长倒计时:工序配置了等待时长(waitDuration > 0)时,需先开始倒计时并等待结束 |
| | | MesProProcessDO process = validateWaitDuration(task); |
| | | // 4.1 校验数量:默认必须 > 0;工序配置"不需要填写报工数量"时允许为空 |
| | | // 合格品/不良品数量由质检确定(非质检工序在审批时默认全部为合格品) |
| | | if (process == null || !Boolean.FALSE.equals(process.getNeedReportQuantity())) { |
| | | if (reqVO.getFeedbackQuantity() == null |
| | | || reqVO.getFeedbackQuantity().compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw exception(PRO_FEEDBACK_QUANTITY_MUST_POSITIVE); |
| | | } |
| | | } |
| | | |
| | | return task; |
| | | } |
| | | |
| | | /** |
| | | * 校验工序等待时长倒计时(报工前置条件) |
| | | * |
| | | * <p>读取任务关联的工序主数据 waitDuration(小时):若未配置或为 0 则直接返回;配置了则需要 |
| | | * 任务已点击"开始倒计时"且倒计时已结束,否则抛出对应异常。返回工序主数据,供调用方复用 |
| | | * needReportQuantity 等字段。 |
| | | * |
| | | * @param task 生产任务 |
| | | * @return 工序主数据 |
| | | */ |
| | | private MesProProcessDO validateWaitDuration(MesProTaskDO task) { |
| | | MesProProcessDO process = processService.getProcess(task.getProcessId()); |
| | | if (process == null || process.getWaitDuration() == null |
| | | || process.getWaitDuration().compareTo(BigDecimal.ZERO) <= 0) { |
| | | return process; // 未配置等待时长,无需倒计时 |
| | | } |
| | | if (task.getWaitStartTime() == null) { |
| | | throw exception(PRO_TASK_WAIT_NOT_STARTED); // 请先点击"开始倒计时" |
| | | } |
| | | LocalDateTime readyTime = task.getWaitStartTime() |
| | | .plusMinutes((long) (process.getWaitDuration().doubleValue() * 60)); |
| | | if (LocalDateTime.now().isBefore(readyTime)) { |
| | | Duration remaining = Duration.between(LocalDateTime.now(), readyTime); |
| | | throw exception(PRO_TASK_WAIT_NOT_ELAPSED, |
| | | remaining.toHours() + "小时" + remaining.toMinutesPart() + "分钟"); |
| | | } |
| | | return process; |
| | | } |
| | | |
| | | @Override |
| | |
| | | if (routeProcess == null) { |
| | | throw exception(PRO_FEEDBACK_ROUTE_PROCESS_INVALID); |
| | | } |
| | | // 3. 校验数量:只填报工数量,且必须 > 0(合格品/不良品数量由质检确定) |
| | | // 3. 校验等待时长倒计时:工序配置了等待时长(waitDuration > 0)时,需先开始倒计时并等待结束 |
| | | MesProProcessDO process = validateWaitDuration(task); |
| | | // 4. 校验数量:默认必须 > 0;工序配置"不需要填写报工数量"时允许为空(合格品/不良品数量由质检确定) |
| | | if (process == null || !Boolean.FALSE.equals(process.getNeedReportQuantity())) { |
| | | if (reqVO.getFeedbackQuantity() == null |
| | | || reqVO.getFeedbackQuantity().compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw exception(PRO_FEEDBACK_QUANTITY_MUST_POSITIVE); |
| | | } |
| | | // 4. 构建报工DO |
| | | } |
| | | // 5. 构建报工DO |
| | | MesProFeedbackDO feedback = new MesProFeedbackDO() |
| | | .setCode(autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PRO_FEEDBACK_CODE.getCode())) |
| | | .setType(MesProFeedbackTypeEnum.SELF.getType()) |