| | |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| | | import cn.iocoder.yudao.module.mes.service.pd.processparamtemplate.MesPdProcessParamTemplateService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.feedback.dto.MesTaskParamSnapshot; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskAutoGeneratePreviewRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskAutoGenerateReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskSaveReqVO; |
| | | 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.md.unitmeasure.MesMdUnitMeasureDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationMachineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProTaskStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.service.dv.machinery.MesDvMachineryService; |
| | | import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamService; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO; |
| | | import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; |
| | | import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationMachineService; |
| | | import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO; |
| | |
| | | import java.time.LocalDateTime; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | 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.convertList; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_ALREADY_FINISHED; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_CANNOT_PAUSE; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_HAS_FEEDBACK; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_NOT_EXISTS; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_NOT_PAUSED; |
| | | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskPauseRecordDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskPauseRecordMapper; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPauseReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskResumeReqVO; |
| | | |
| | | /** |
| | | * MES 生产任务 Service 实现类 |
| | | * |
| | | * @author 芋道源码 |
| | | * @author 超级管理员 |
| | | */ |
| | | @Service |
| | | @Validated |
| | |
| | | |
| | | @Resource |
| | | private MesProTaskMapper taskMapper; |
| | | @Resource |
| | | private MesPdProcessParamTemplateService paramTemplateService; |
| | | |
| | | @Resource |
| | | private MesProFeedbackMapper feedbackMapper; |
| | | |
| | | @Resource |
| | | private MesProTaskPauseRecordMapper taskPauseRecordMapper; |
| | | |
| | | @Resource |
| | | @Lazy |
| | |
| | | private MesProWorkOrderProcessService workOrderProcessService; |
| | | @Resource |
| | | private MesMdAutoCodeRecordService autoCodeRecordService; |
| | | @Resource |
| | | private MesMdWorkstationMachineService workstationMachineService; |
| | | @Resource |
| | | @Lazy |
| | | private MesDvMachineryService machineryService; |
| | | @Resource |
| | | private MesCalTeamService teamService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createTask(MesProTaskSaveReqVO createReqVO) { |
| | | // 1. 校验关联数据存在 |
| | | MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId()); |
| | | workstationService.validateWorkstationExists(createReqVO.getWorkstationId()); |
| | | routeService.validateRouteExists(createReqVO.getRouteId()); |
| | | processService.validateProcessExistsAndEnable(createReqVO.getProcessId()); |
| | | fillTeamInfo(createReqVO); |
| | | |
| | | // 2. 自动确定 itemId:优先前端传入 → 工艺路线工序的产出产品 → 工单成品 |
| | | Long itemId = createReqVO.getItemId(); |
| | | if (itemId == null) { |
| | | MesProRouteProcessDO routeProcess = routeProcessService |
| | | .getRouteProcessByRouteIdAndProcessId(createReqVO.getRouteId(), createReqVO.getProcessId()); |
| | | if (routeProcess != null && routeProcess.getOutputItemId() != null) { |
| | | itemId = routeProcess.getOutputItemId(); |
| | | } else { |
| | | itemId = workOrder.getProductId(); |
| | | } |
| | | itemId = resolveTaskItemId(routeProcess, workOrder.getProductId()); |
| | | } |
| | | MesMdItemDO item = itemService.validateItemExists(itemId); |
| | | |
| | |
| | | task.setClientId(workOrder.getClientId()); |
| | | } |
| | | // 2.2 插入 |
| | | freezeTaskParamConfig(task); |
| | | taskMapper.insert(task); |
| | | return task.getId(); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateTask(MesProTaskSaveReqVO updateReqVO) { |
| | | // 1.1 校验存在 |
| | | MesProTaskDO task = validateTaskExists(updateReqVO.getId()); |
| | | MesProTaskDO task = taskMapper.selectOne(new LambdaQueryWrapperX<MesProTaskDO>() |
| | | .eq(MesProTaskDO::getId, updateReqVO.getId()).last("FOR UPDATE")); |
| | | if (task == null) throw exception(PRO_TASK_NOT_EXISTS); |
| | | // 1.2 校验关联数据存在 |
| | | if (updateReqVO.getWorkOrderId() != null) { |
| | | workOrderService.validateWorkOrderExists(updateReqVO.getWorkOrderId()); |
| | |
| | | if (updateReqVO.getProcessId() != null) { |
| | | processService.validateProcessExistsAndEnable(updateReqVO.getProcessId()); |
| | | } |
| | | fillTeamInfo(updateReqVO); |
| | | MesMdItemDO item; |
| | | if (updateReqVO.getItemId() != null) { |
| | | item = itemService.validateItemExists(updateReqVO.getItemId()); |
| | |
| | | // 2. 更新 |
| | | MesProTaskDO updateObj = BeanUtils.toBean(updateReqVO, MesProTaskDO.class) |
| | | .setName(buildTaskName(item, quantity)); |
| | | if (updateReqVO.getProcessId() != null && !updateReqVO.getProcessId().equals(task.getProcessId())) { |
| | | if (feedbackMapper.selectCountByTaskId(task.getId()) > 0) throw exception(PRO_TASK_HAS_FEEDBACK); |
| | | freezeTaskParamConfig(updateObj); |
| | | } |
| | | taskMapper.updateById(updateObj); |
| | | } |
| | | |
| | | /** 从工序和模板复制定义,后续模板维护不改变已排产任务。 */ |
| | | private void freezeTaskParamConfig(MesProTaskDO task) { |
| | | var process = processService.getProcess(task.getProcessId()); |
| | | if (process == null) throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_PROCESS_NOT_EXISTS); |
| | | int mode = process.getParamCollectMode() == null ? 0 : process.getParamCollectMode(); |
| | | task.setParamCollectMode(mode); |
| | | task.setParamTemplateSnapshot(""); |
| | | task.setParamSharedSnapshot(""); |
| | | if (mode == 0) return; |
| | | if ((mode != 1 && mode != 2) || process.getParamTemplateId() == null) |
| | | throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_PROCESS_PARAM_CONFIG_INVALID); |
| | | var template = paramTemplateService.validateProcessParamTemplateExists(process.getParamTemplateId()); |
| | | var details = paramTemplateService.getProcessParamTemplateDetailListByTemplateId(template.getId()); |
| | | if (CollUtil.isEmpty(details)) throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_WORK_ORDER_PARAM_RECORD_TEMPLATE_NO_DETAIL); |
| | | MesTaskParamSnapshot snapshot = new MesTaskParamSnapshot(); |
| | | snapshot.setTemplateId(template.getId()); |
| | | snapshot.setTemplateCode(template.getTemplateCode()); |
| | | snapshot.setTemplateName(template.getTemplateName()); |
| | | List<MesTaskParamSnapshot.Detail> frozenDetails = BeanUtils.toBean(details, MesTaskParamSnapshot.Detail.class); |
| | | for (MesTaskParamSnapshot.Detail detail : frozenDetails) { |
| | | if (detail.getUnitMeasureId() != null) { |
| | | var unit = unitMeasureService.getUnitMeasure(detail.getUnitMeasureId()); |
| | | if (unit != null) detail.setUnitMeasureName(unit.getName()); |
| | | } |
| | | } |
| | | snapshot.setDetails(frozenDetails); |
| | | task.setParamTemplateSnapshot(JsonUtils.toJsonString(snapshot)); |
| | | } |
| | | |
| | | /** |
| | | * 校验班组存在并回填班组名称 |
| | | */ |
| | | private void fillTeamInfo(MesProTaskSaveReqVO reqVO) { |
| | | if (reqVO.getTeamId() == null) { |
| | | return; |
| | | } |
| | | MesCalTeamDO team = teamService.validateTeamExists(reqVO.getTeamId()); |
| | | if (team != null) { |
| | | reqVO.setTeamName(team.getName()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | return routeProcesses; |
| | | } |
| | | |
| | | /** |
| | | * 任务产出物料:优先取工序在当前产品下的产出(按归属过滤),没有产出时取工单成品 |
| | | */ |
| | | private Long resolveTaskItemId(MesProRouteProcessDO routeProcess, Long productId) { |
| | | if (routeProcess == null) { |
| | | return productId; |
| | | } |
| | | if (routeProcess.getId() != null) { |
| | | // 工艺路线模板:按产出归属过滤出当前产品的产出 |
| | | List<Long> outputIds = routeProcessService |
| | | .getOutputItemIdsByRouteProcessId(routeProcess.getId(), productId); |
| | | if (outputIds != null) { |
| | | // 有归属配置:该产品无产出时取工单成品;null 表示存量无归属数据,继续走聚合兜底 |
| | | return CollUtil.isNotEmpty(outputIds) ? outputIds.get(0) : productId; |
| | | } |
| | | } |
| | | // 工单工序快照 / 存量无归属数据:产出已按产品过滤,取第一个 |
| | | String outputItemId = routeProcess.getOutputItemId(); |
| | | if (outputItemId != null && !outputItemId.isBlank()) { |
| | | return Long.valueOf(outputItemId.split(",")[0].trim()); |
| | | } |
| | | return productId; |
| | | } |
| | | |
| | | @Override |
| | | public void deleteTask(Long id) { |
| | | // 1. 校验存在且非终态 |
| | |
| | | } |
| | | |
| | | @Override |
| | | public MesProTaskDO getTaskByCode(String code) { |
| | | return taskMapper.selectByCode(code); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<MesProTaskDO> getTaskPage(MesProTaskPageReqVO pageReqVO) { |
| | | return taskMapper.selectPage(pageReqVO); |
| | | } |
| | |
| | | @Override |
| | | public PageResult<MesProTaskDO> getPendingFeedbackPage(MesProTaskPageReqVO pageReqVO) { |
| | | return taskMapper.selectPendingFeedbackPage(pageReqVO); |
| | | } |
| | | |
| | | @Override |
| | | public Map<Long, Long> getPendingFeedbackCountByWorkstation() { |
| | | List<Map<String, Object>> rows = taskMapper.selectPendingFeedbackCountByWorkstation(); |
| | | Map<Long, Long> result = new HashMap<>(rows.size()); |
| | | for (Map<String, Object> row : rows) { |
| | | Object workstationId = row.get("workstation_id"); |
| | | Object count = row.get("pending_count"); |
| | | if (workstationId instanceof Number && count instanceof Number) { |
| | | result.put(((Number) workstationId).longValue(), ((Number) count).longValue()); |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Integer calculateDuration(Long workstationId, BigDecimal quantity) { |
| | | if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return 0; |
| | | } |
| | | // 1. 查询工作站绑定的设备 |
| | | List<MesMdWorkstationMachineDO> machines = workstationMachineService |
| | | .getWorkstationMachineListByWorkstationId(workstationId); |
| | | if (CollUtil.isEmpty(machines)) { |
| | | return 0; |
| | | } |
| | | // 2. 获取设备详情,计算总效率 |
| | | Set<Long> machineryIds = convertSet(machines, MesMdWorkstationMachineDO::getMachineryId); |
| | | List<MesDvMachineryDO> machineryList = machineryService.getMachineryList(machineryIds); |
| | | BigDecimal totalEfficiency = BigDecimal.ZERO; |
| | | for (MesDvMachineryDO m : machineryList) { |
| | | BigDecimal efficiency = m.getProductionEfficiency() != null ? m.getProductionEfficiency() : BigDecimal.ZERO; |
| | | Integer belt = m.getConveyorBelt() != null && m.getConveyorBelt() > 0 ? m.getConveyorBelt() : 1; |
| | | totalEfficiency = totalEfficiency.add(efficiency.multiply(BigDecimal.valueOf(belt))); |
| | | } |
| | | if (totalEfficiency.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return 0; |
| | | } |
| | | // 3. 生产时长(小时) = ceil(排产数量 / 总效率),效率单位为每小时产量 |
| | | return quantity.divide(totalEfficiency, 0, java.math.RoundingMode.CEILING).intValue(); |
| | | } |
| | | |
| | | @Override |
| | | public MesProTaskAutoGeneratePreviewRespVO previewAutoGenerate(MesProTaskAutoGenerateReqVO reqVO) { |
| | | // 1. 校验工单已确认 |
| | | MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId()); |
| | |
| | | List<Long> taskIds = new java.util.ArrayList<>(); |
| | | for (MesProRouteProcessDO rp : routeProcesses) { |
| | | cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO proc = procMap.get(rp.getProcessId()); |
| | | Long itemId = rp.getOutputItemId() != null ? rp.getOutputItemId() : workOrder.getProductId(); |
| | | Long itemId = resolveTaskItemId(rp, workOrder.getProductId()); |
| | | cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO item = itemService.validateItemExists(itemId); |
| | | MesProTaskDO task = new MesProTaskDO() |
| | | .setWorkOrderId(workOrder.getId()) |
| | |
| | | .setStatus(MesProTaskStatusEnum.PREPARE.getStatus()) |
| | | .setClientId(workOrder.getClientId()) |
| | | .setColorCode(rp.getColorCode()); |
| | | freezeTaskParamConfig(task); |
| | | taskMapper.insert(task); |
| | | taskIds.add(task.getId()); |
| | | } |
| | |
| | | return taskIds; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void pauseTask(MesProTaskPauseReqVO pauseReqVO) { |
| | | // 1. 校验任务存在 |
| | | MesProTaskDO task = validateTaskExists(pauseReqVO.getId()); |
| | | // 2. 校验状态是否允许停工(如必须是 生产中 或 部分完成) |
| | | if (!ObjectUtils.equalsAny(task.getStatus(), MesProTaskStatusEnum.PRODUCING.getStatus(), |
| | | MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus())) { |
| | | throw exception(PRO_TASK_CANNOT_PAUSE); |
| | | } |
| | | |
| | | // 3. 修改任务状态为已停工 |
| | | taskMapper.updateById(new MesProTaskDO().setId(task.getId()).setStatus(MesProTaskStatusEnum.PAUSED.getStatus())); |
| | | |
| | | // 4. 创建停工记录 |
| | | MesProTaskPauseRecordDO pauseRecord = new MesProTaskPauseRecordDO() |
| | | .setTaskId(task.getId()) |
| | | .setWorkOrderId(task.getWorkOrderId()) |
| | | .setWorkstationId(task.getWorkstationId()) |
| | | .setReason(pauseReqVO.getReason()) |
| | | .setRemark(pauseReqVO.getRemark()) |
| | | .setPauseTime(LocalDateTime.now()) |
| | | .setPauseUserId(SecurityFrameworkUtils.getLoginUserId()); |
| | | taskPauseRecordMapper.insert(pauseRecord); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void resumeTask(MesProTaskResumeReqVO resumeReqVO) { |
| | | // 1. 校验任务存在 |
| | | MesProTaskDO task = validateTaskExists(resumeReqVO.getId()); |
| | | // 2. 校验状态必须是已停工 |
| | | if (!ObjectUtils.equalsAny(task.getStatus(), MesProTaskStatusEnum.PAUSED.getStatus())) { |
| | | throw exception(PRO_TASK_NOT_PAUSED); |
| | | } |
| | | |
| | | // 3. 恢复任务状态 |
| | | Integer resumeStatus = task.getProducedQuantity() != null |
| | | && task.getProducedQuantity().signum() > 0 |
| | | ? MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus() |
| | | : MesProTaskStatusEnum.PRODUCING.getStatus(); |
| | | taskMapper.updateById(new MesProTaskDO().setId(task.getId()).setStatus(resumeStatus)); |
| | | |
| | | // 4. 更新最新的一条停工记录 |
| | | MesProTaskPauseRecordDO pauseRecord = taskPauseRecordMapper.selectLastUnresumedByTaskId(task.getId()); |
| | | if (pauseRecord != null) { |
| | | pauseRecord.setResumeTime(LocalDateTime.now()); |
| | | pauseRecord.setResumeUserId(SecurityFrameworkUtils.getLoginUserId()); |
| | | taskPauseRecordMapper.updateById(pauseRecord); |
| | | } |
| | | } |
| | | |
| | | } |