package cn.iocoder.yudao.module.mes.service.pro.workorder; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemBatchConfigDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper; import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum; import cn.iocoder.yudao.module.mes.enums.wm.BarcodeBizTypeEnum; import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemBatchConfigService; import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderProgressRespVO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO; import cn.iocoder.yudao.module.mes.enums.pro.MesProTaskStatusEnum; 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.service.pro.task.MesProTaskService; import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService; import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; /** * MES 生产工单 Service 实现类 * * @author 芋道源码 */ @Service @Validated public class MesProWorkOrderServiceImpl implements MesProWorkOrderService { @Resource private MesProWorkOrderMapper workOrderMapper; @Resource private MesProWorkOrderBomService workOrderBomService; @Resource private MesProWorkOrderProcessService workOrderProcessService; @Resource private MesMdItemService itemService; @Resource private MesMdItemBatchConfigService itemBatchConfigService; @Resource private MesWmBarcodeService barcodeService; @Resource private MesProTaskService taskService; @Resource private MesProProcessService processService; @Resource private MesMdWorkstationService workstationService; @Resource private CrmCustomerApi customerApi; @Override @Transactional(rollbackFor = Exception.class) public Long createWorkOrder(MesProWorkOrderSaveReqVO createReqVO) { // 1. 校验数据 validateWorkOrderSaveData(null, createReqVO); // 2.1 设置默认值 if (createReqVO.getParentId() == null) { createReqVO.setParentId(MesProWorkOrderDO.PARENT_ID_NULL); } // 2.2 插入工单 MesProWorkOrderDO workOrder = BeanUtils.toBean(createReqVO, MesProWorkOrderDO.class); workOrder.setStatus(MesProWorkOrderStatusEnum.PREPARE.getStatus()); workOrderMapper.insert(workOrder); // 3. 自动生成 BOM:根据产品 BOM 生成工单 BOM workOrderBomService.generateWorkOrderBom(workOrder.getId(), createReqVO, false); // 4. 工序由前端选择产品后带入,不再自动生成 // 5. 自动生成条码 barcodeService.autoGenerateBarcode(BarcodeBizTypeEnum.WORKORDER.getValue(), workOrder.getId(), workOrder.getCode(), workOrder.getName()); return workOrder.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void updateWorkOrder(MesProWorkOrderSaveReqVO updateReqVO) { // 1.1 校验存在 + 只有草稿状态才能编辑 MesProWorkOrderDO oldWorkOrder = validateWorkOrderExists(updateReqVO.getId()); if (ObjUtil.notEqual(oldWorkOrder.getStatus(), MesProWorkOrderStatusEnum.PREPARE.getStatus())) { throw exception(PRO_WORK_ORDER_NOT_PREPARE); } // 1.2 校验数据 validateWorkOrderSaveData(updateReqVO.getId(), updateReqVO); // 2. 判断产品或数量是否变更,如果变更则重新生成 BOM(updated=true 会先清理旧数据) boolean productChanged = ObjUtil.notEqual(oldWorkOrder.getProductId(), updateReqVO.getProductId()); boolean quantityChanged = oldWorkOrder.getQuantity().compareTo(updateReqVO.getQuantity()) != 0; if (productChanged || quantityChanged) { workOrderBomService.generateWorkOrderBom(updateReqVO.getId(), updateReqVO, true); } // 2.1 工序由前端管理,产品变更时前端会重新获取工艺路线工序 // 3. 更新 MesProWorkOrderDO updateObj = BeanUtils.toBean(updateReqVO, MesProWorkOrderDO.class); workOrderMapper.updateById(updateObj); } @Override @Transactional(rollbackFor = Exception.class) public void deleteWorkOrder(Long id) { // 1.1 校验存在 MesProWorkOrderDO workOrder = validateWorkOrderExists(id); // 1.2 只能删除草稿状态的工单 if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.PREPARE.getStatus())) { throw exception(PRO_WORK_ORDER_NOT_PREPARE); } // 1.3 校验是否有子工单 Long childCount = workOrderMapper.selectCount(MesProWorkOrderDO::getParentId, id); if (childCount > 0) { throw exception(PRO_WORK_ORDER_HAS_CHILDREN); } // 2. 删除工单 + BOM + 工序 workOrderMapper.deleteById(id); workOrderBomService.deleteWorkOrderBomByWorkOrderId(id); workOrderProcessService.deleteWorkOrderProcessByWorkOrderId(id); } @Override public MesProWorkOrderDO validateWorkOrderExists(Long id) { MesProWorkOrderDO workOrder = workOrderMapper.selectById(id); if (workOrder == null) { throw exception(PRO_WORK_ORDER_NOT_EXISTS); } return workOrder; } @Override public MesProWorkOrderDO getWorkOrder(Long id) { return workOrderMapper.selectById(id); } @Override public MesProWorkOrderDO getWorkOrder(String code) { return workOrderMapper.selectByCode(code); } @Override public PageResult getWorkOrderPage(MesProWorkOrderPageReqVO pageReqVO) { // 获取用户有权限的客户 ID 列表 List permittedClientIds = customerApi.getPermittedCustomerIds(); return workOrderMapper.selectPage(pageReqVO, permittedClientIds); } @Override public List getWorkOrderList(Collection ids) { if (CollUtil.isEmpty(ids)) { return Collections.emptyList(); } return workOrderMapper.selectByIds(ids); } @Override public MesProWorkOrderDO validateWorkOrderConfirmed(Long id) { MesProWorkOrderDO workOrder = validateWorkOrderExists(id); if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.CONFIRMED.getStatus())) { throw exception(PRO_WORK_ORDER_NOT_CONFIRMED); } return workOrder; } @Override public void confirmWorkOrder(Long id) { // 1.1 校验存在 MesProWorkOrderDO workOrder = validateWorkOrderExists(id); // 1.2 只有草稿状态才能确认 if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.PREPARE.getStatus())) { throw exception(PRO_WORK_ORDER_NOT_PREPARE); } // 2. 更新状态为已确认 workOrderMapper.updateById(new MesProWorkOrderDO().setId(id) .setStatus(MesProWorkOrderStatusEnum.CONFIRMED.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void finishWorkOrder(Long id) { // 1. 校验存在 + 只有已确认状态才能完成 MesProWorkOrderDO workOrder = validateWorkOrderExists(id); if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.CONFIRMED.getStatus())) { throw exception(PRO_WORK_ORDER_NOT_CONFIRMED); } // 2. 级联完成所有关联任务 taskService.finishTaskByOrderId(id); // 3. 更新工单状态为已完成 workOrderMapper.updateById(new MesProWorkOrderDO().setId(id) .setStatus(MesProWorkOrderStatusEnum.FINISHED.getStatus()) .setFinishDate(LocalDateTime.now())); } @Override @Transactional(rollbackFor = Exception.class) public void cancelWorkOrder(Long id) { // 1. 校验存在 + 只有已确认状态才能取消 MesProWorkOrderDO workOrder = validateWorkOrderExists(id); if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.CONFIRMED.getStatus())) { throw exception(PRO_WORK_ORDER_NOT_CONFIRMED); } // 2. 级联取消所有关联任务 taskService.cancelTaskByOrderId(id); // 3. 更新工单状态为已取消 workOrderMapper.updateById(new MesProWorkOrderDO().setId(id) .setStatus(MesProWorkOrderStatusEnum.CANCELED.getStatus()) .setCancelDate(LocalDateTime.now())); } // ==================== 校验方法 ==================== private void validateWorkOrderSaveData(Long id, MesProWorkOrderSaveReqVO reqVO) { // 1. 校验编码唯一 validateWorkOrderCodeUnique(id, reqVO.getCode()); // 2. 校验产品存在 itemService.validateItemExists(reqVO.getProductId()); // 3. 校验批次配置(如果产品有 clientFlag=true,则 clientId 必填) MesMdItemBatchConfigDO batchConfig = itemBatchConfigService.getItemBatchConfigByItemId(reqVO.getProductId()); if (batchConfig != null && Boolean.TRUE.equals(batchConfig.getClientFlag()) && reqVO.getClientId() == null) { throw exception(MD_CLIENT_NOT_EXISTS); } } private void validateWorkOrderCodeUnique(Long id, String code) { if (code == null) { return; } MesProWorkOrderDO workOrder = workOrderMapper.selectByCode(code); if (workOrder == null) { return; } if (ObjUtil.notEqual(workOrder.getId(), id)) { throw exception(PRO_WORK_ORDER_CODE_DUPLICATE); } } @Override public void updateProducedQuantity(Long id, BigDecimal incrQuantityProduced) { // 校验工单存在 validateWorkOrderExists(id); // 更新数量 workOrderMapper.updateProducedQuantity(id, incrQuantityProduced); } @Override public void updateScheduledQuantity(Long id, BigDecimal incrQuantityScheduled) { // 校验工单存在 validateWorkOrderExists(id); // 更新数量 workOrderMapper.updateScheduledQuantity(id, incrQuantityScheduled); } @Override public void validateScheduleQuantity(Long id, BigDecimal scheduleQuantity) { MesProWorkOrderDO workOrder = validateWorkOrderExists(id); BigDecimal currentScheduled = workOrder.getQuantityScheduled() != null ? workOrder.getQuantityScheduled() : BigDecimal.ZERO; BigDecimal totalScheduled = currentScheduled.add(scheduleQuantity); if (totalScheduled.compareTo(workOrder.getQuantity()) > 0) { throw exception(PRO_WORK_ORDER_SCHEDULE_QUANTITY_EXCEED, workOrder.getCode(), workOrder.getQuantity(), currentScheduled, scheduleQuantity); } } @Override @Transactional(rollbackFor = Exception.class) public boolean checkAndAutoFinishWorkOrder(Long id) { MesProWorkOrderDO workOrder = getWorkOrder(id); if (workOrder == null) { return false; } // 未开启自动完成 if (!Boolean.TRUE.equals(workOrder.getAutoFinishFlag())) { return false; } // 状态不是已确认 if (!ObjUtil.equal(workOrder.getStatus(), MesProWorkOrderStatusEnum.CONFIRMED.getStatus())) { return false; } // 已生产数量 >= 生产数量 BigDecimal quantityProduced = workOrder.getQuantityProduced() != null ? workOrder.getQuantityProduced() : BigDecimal.ZERO; if (quantityProduced.compareTo(workOrder.getQuantity()) >= 0) { finishWorkOrder(id); return true; } return false; } @Override public MesProWorkOrderProgressRespVO getWorkOrderProgress(Long id) { MesProWorkOrderDO workOrder = getWorkOrder(id); if (workOrder == null) { return null; } // 1. 获取工单工序列表 List processes = workOrderProcessService.getWorkOrderProcessListByWorkOrderId(id); // 2. 获取工单下所有任务,按工序分组 List allTasks = taskService.getTaskListByWorkOrderIds(Collections.singletonList(id)); Map> taskMap = allTasks.stream() .collect(Collectors.groupingBy(t -> t.getProcessId() != null ? t.getProcessId() : 0L)); // 3. 批量查询工序名称和工作站名称 Set processIds = processes.stream().map(MesProWorkOrderProcessDO::getProcessId).collect(Collectors.toSet()); Map processMap = processService.getProcessMap(processIds); Set workstationIds = allTasks.stream().map(MesProTaskDO::getWorkstationId).filter(Objects::nonNull).collect(Collectors.toSet()); Map workstationMap = workstationService.getWorkstationMap(workstationIds); // 4. 计算进度 BigDecimal quantity = workOrder.getQuantity() != null ? workOrder.getQuantity() : BigDecimal.ZERO; BigDecimal quantityProduced = workOrder.getQuantityProduced() != null ? workOrder.getQuantityProduced() : BigDecimal.ZERO; BigDecimal quantityScheduled = workOrder.getQuantityScheduled() != null ? workOrder.getQuantityScheduled() : BigDecimal.ZERO; MesProWorkOrderProgressRespVO vo = new MesProWorkOrderProgressRespVO() .setWorkOrderId(workOrder.getId()) .setWorkOrderCode(workOrder.getCode()) .setQuantity(quantity) .setQuantityScheduled(quantityScheduled) .setQuantityProduced(quantityProduced) .setProgressPercent(calcPercent(quantityProduced, quantity)); // 5. 组装工序进度 List processList = new ArrayList<>(); for (MesProWorkOrderProcessDO wp : processes) { MesProProcessDO proc = processMap.get(wp.getProcessId()); List procTasks = taskMap.getOrDefault(wp.getProcessId(), Collections.emptyList()); BigDecimal procProduced = procTasks.stream() .map(t -> t.getProducedQuantity() != null ? t.getProducedQuantity() : BigDecimal.ZERO) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal plannedQty = Boolean.TRUE.equals(wp.getKeyFlag()) ? quantity : null; int procStatus = calcProcessStatus(procTasks); List taskInfoList = procTasks.stream().map(t -> { MesMdWorkstationDO ws = workstationMap.get(t.getWorkstationId()); BigDecimal tQty = t.getQuantity() != null ? t.getQuantity() : BigDecimal.ZERO; BigDecimal tProduced = t.getProducedQuantity() != null ? t.getProducedQuantity() : BigDecimal.ZERO; return new MesProWorkOrderProgressRespVO.TaskInfo() .setTaskId(t.getId()) .setTaskCode(t.getCode()) .setWorkstationName(ws != null ? ws.getName() : null) .setQuantity(tQty) .setProducedQuantity(tProduced) .setProgressPercent(calcPercent(tProduced, tQty)) .setStatus(t.getStatus()); }).collect(Collectors.toList()); processList.add(new MesProWorkOrderProgressRespVO.ProcessProgress() .setProcessId(wp.getProcessId()) .setProcessCode(proc != null ? proc.getCode() : null) .setProcessName(proc != null ? proc.getName() : null) .setKeyFlag(wp.getKeyFlag()) .setPlannedQuantity(plannedQty) .setProducedQuantity(procProduced) .setProgressPercent(calcPercent(procProduced, plannedQty)) .setStatus(procStatus) .setTaskList(taskInfoList)); } vo.setProcessList(processList); return vo; } private BigDecimal calcPercent(BigDecimal produced, BigDecimal total) { if (total == null || total.compareTo(BigDecimal.ZERO) <= 0 || produced == null) { return BigDecimal.ZERO; } return produced.multiply(new BigDecimal("100")).divide(total, 2, RoundingMode.HALF_UP); } private int calcProcessStatus(List tasks) { if (tasks.isEmpty()) { return 0; // 未开始 } boolean allFinished = tasks.stream().allMatch(t -> MesProTaskStatusEnum.isEndStatus(t.getStatus())); if (allFinished) { return 2; // 已完成 } boolean anyStarted = tasks.stream().anyMatch(t -> !MesProTaskStatusEnum.PREPARE.getStatus().equals(t.getStatus())); return anyStarted ? 1 : 0; // 进行中 : 未开始 } @Override public Long getWorkOrderCountByVendorId(Long vendorId) { return workOrderMapper.selectCountByVendorId(vendorId); } }