| | |
| | | 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.mps.MesProMpsDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.retentionsample.MesQcRetentionSampleMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProMpsStatusEnum; |
| | | 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.service.common.bpm.MesBpmProcessService; |
| | | 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 org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDateTime; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | 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 芋道源码 |
| | | * @author 超级管理员 |
| | | */ |
| | | @Service |
| | | @Validated |
| | | public class MesProWorkOrderServiceImpl implements MesProWorkOrderService { |
| | | |
| | | @Resource |
| | | private MesBpmProcessService mesBpmProcessService; |
| | | |
| | | private static final String BPM_PROCESS_DEFINITION_CATEGORY_CODE = "mes_pro_feedback_audit"; |
| | | |
| | | @Resource |
| | | private MesProWorkOrderMapper workOrderMapper; |
| | | @Resource |
| | | private MesProMpsMapper mpsMapper; |
| | | @Resource |
| | | private MesQcRetentionSampleMapper retentionSampleMapper; |
| | | |
| | | @Resource |
| | | private MesProWorkOrderBomService workOrderBomService; |
| | | @Resource |
| | | private MesProWorkOrderProcessService workOrderProcessService; |
| | | @Resource |
| | | private MesMdItemService itemService; |
| | | @Resource |
| | |
| | | @Resource |
| | | private MesProTaskService taskService; |
| | | @Resource |
| | | private MesProProcessService processService; |
| | | @Resource |
| | | private MesMdWorkstationService workstationService; |
| | | @Resource |
| | | private CrmCustomerApi customerApi; |
| | | @Resource |
| | | private cn.iocoder.yudao.module.mes.service.pro.cost.MesProWorkOrderCostService workOrderCostService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | |
| | | // 3. 自动生成 BOM:根据产品 BOM 生成工单 BOM |
| | | workOrderBomService.generateWorkOrderBom(workOrder.getId(), createReqVO, false); |
| | | // 3.1 计算标准物料成本 |
| | | workOrderCostService.recalculateWorkOrderCost(workOrder.getId()); |
| | | |
| | | // 4. 自动生成条码 |
| | | // 4. 工序由前端选择产品后带入,不再自动生成 |
| | | |
| | | // 5. 自动生成条码 |
| | | barcodeService.autoGenerateBarcode(BarcodeBizTypeEnum.WORKORDER.getValue(), |
| | | workOrder.getId(), workOrder.getCode(), workOrder.getName()); |
| | | return workOrder.getId(); |
| | |
| | | if (productChanged || quantityChanged) { |
| | | workOrderBomService.generateWorkOrderBom(updateReqVO.getId(), updateReqVO, true); |
| | | } |
| | | // 2.1 工序由前端管理,产品变更时前端会重新获取工艺路线工序 |
| | | |
| | | // 3. 更新 |
| | | MesProWorkOrderDO updateObj = BeanUtils.toBean(updateReqVO, MesProWorkOrderDO.class); |
| | | workOrderMapper.updateById(updateObj); |
| | | |
| | | // 4. 产品或数量变化后重算标准物料成本 |
| | | if (productChanged || quantityChanged) { |
| | | workOrderCostService.recalculateWorkOrderCost(updateReqVO.getId()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | throw exception(PRO_WORK_ORDER_HAS_CHILDREN); |
| | | } |
| | | |
| | | // 2. 删除工单 + BOM |
| | | // 2. 删除工单 + BOM + 工序 |
| | | workOrderMapper.deleteById(id); |
| | | workOrderBomService.deleteWorkOrderBomByWorkOrderId(id); |
| | | workOrderProcessService.deleteWorkOrderProcessByWorkOrderId(id); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | return workOrder; |
| | | } |
| | | |
| | | @Override |
| | | public List<Map<String, Object>> getWorkOrderApproveProcessDefinitionList() { |
| | | return mesBpmProcessService.getApproveProcessDefinitionList(BPM_PROCESS_DEFINITION_CATEGORY_CODE); |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | public MesProWorkOrderDO getWorkOrder(Long id) { |
| | |
| | | |
| | | @Override |
| | | public PageResult<MesProWorkOrderDO> getWorkOrderPage(MesProWorkOrderPageReqVO pageReqVO) { |
| | | // 获取用户有权限的客户 ID 列表 |
| | | List<Long> permittedClientIds = customerApi.getPermittedCustomerIds(); |
| | | return workOrderMapper.selectPage(pageReqVO, permittedClientIds); |
| | | return workOrderMapper.selectPage(pageReqVO, (List<Long>) null); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<MesProWorkOrderDO> getWorkOrderSchedulePage(MesProWorkOrderPageReqVO pageReqVO) { |
| | | return workOrderMapper.selectSchedulePage(pageReqVO, (List<Long>) null); |
| | | } |
| | | |
| | | @Override |
| | |
| | | if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.CONFIRMED.getStatus())) { |
| | | throw exception(PRO_WORK_ORDER_NOT_CONFIRMED); |
| | | } |
| | | // 2. 校验已生产数量必须大于 0(没有产出不允许完成) |
| | | BigDecimal quantityProduced = ObjUtil.defaultIfNull(workOrder.getQuantityProduced(), BigDecimal.ZERO); |
| | | if (quantityProduced.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw exception(PRO_WORK_ORDER_NO_PRODUCED_QUANTITY, workOrder.getCode()); |
| | | } |
| | | // 3. 校验留样记录(所有工单完成前必须登记留样) |
| | | if (retentionSampleMapper.selectByWorkOrderId(id) == null) { |
| | | throw exception(PRO_WORK_ORDER_RETENTION_SAMPLE_REQUIRED, workOrder.getCode()); |
| | | } |
| | | |
| | | // 2. 级联完成所有关联任务 |
| | | // 4. 级联完成所有关联任务 |
| | | taskService.finishTaskByOrderId(id); |
| | | |
| | | // 3. 更新工单状态为已完成 |
| | | // 5. 更新工单状态为已完成 |
| | | workOrderMapper.updateById(new MesProWorkOrderDO().setId(id) |
| | | .setStatus(MesProWorkOrderStatusEnum.FINISHED.getStatus()) |
| | | .setFinishDate(LocalDateTime.now())); |
| | | |
| | | // 6. 同步关联 MPS 状态 |
| | | finishRelatedMps(workOrder, quantityProduced); |
| | | } |
| | | |
| | | /** |
| | | * 工单完成时同步 MPS:已下发 → 已完成,并回填完成时间。 |
| | | * 合并下发时一个工单对应多条 MPS,已生产数量由各 MPS 自身维护,这里不回填,避免重复累计。 |
| | | */ |
| | | private void finishRelatedMps(MesProWorkOrderDO workOrder, BigDecimal quantityProduced) { |
| | | List<MesProMpsDO> mpsList = mpsMapper.selectListByWorkOrderId(workOrder.getId()); |
| | | if (CollUtil.isEmpty(mpsList)) { |
| | | return; |
| | | } |
| | | boolean singleMps = mpsList.size() == 1; |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | for (MesProMpsDO mps : mpsList) { |
| | | // 已取消/已完成的 MPS 不做状态回写 |
| | | if (!ObjUtil.equal(mps.getStatus(), MesProMpsStatusEnum.ISSUED.getStatus())) { |
| | | continue; |
| | | } |
| | | MesProMpsDO updateObj = new MesProMpsDO().setId(mps.getId()) |
| | | .setStatus(MesProMpsStatusEnum.FINISHED.getStatus()) |
| | | .setFinishDate(now); |
| | | if (singleMps) { |
| | | BigDecimal currentProduced = ObjUtil.defaultIfNull(mps.getQuantityProduced(), BigDecimal.ZERO); |
| | | updateObj.setQuantityProduced(quantityProduced.max(currentProduced)); |
| | | } |
| | | mpsMapper.updateById(updateObj); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | |
| | | @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<MesProWorkOrderProcessDO> processes = workOrderProcessService.getWorkOrderProcessListByWorkOrderId(id); |
| | | // 2. 获取工单下所有任务,按工序分组 |
| | | List<MesProTaskDO> allTasks = taskService.getTaskListByWorkOrderIds(Collections.singletonList(id)); |
| | | Map<Long, List<MesProTaskDO>> taskMap = allTasks.stream() |
| | | .collect(Collectors.groupingBy(t -> t.getProcessId() != null ? t.getProcessId() : 0L)); |
| | | // 3. 批量查询工序名称和工作站名称 |
| | | Set<Long> processIds = processes.stream().map(MesProWorkOrderProcessDO::getProcessId).collect(Collectors.toSet()); |
| | | Map<Long, MesProProcessDO> processMap = processService.getProcessMap(processIds); |
| | | Set<Long> workstationIds = allTasks.stream().map(MesProTaskDO::getWorkstationId).filter(Objects::nonNull).collect(Collectors.toSet()); |
| | | Map<Long, MesMdWorkstationDO> 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<MesProWorkOrderProgressRespVO.ProcessProgress> processList = new ArrayList<>(); |
| | | for (MesProWorkOrderProcessDO wp : processes) { |
| | | MesProProcessDO proc = processMap.get(wp.getProcessId()); |
| | | List<MesProTaskDO> 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<MesProWorkOrderProgressRespVO.TaskInfo> 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<MesProTaskDO> 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); |
| | | } |