| | |
| | | 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.bpm.api.task.BpmProcessInstanceApi; |
| | | import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; |
| | | 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.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 lombok.extern.slf4j.Slf4j; |
| | | 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.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.*; |
| | |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class MesProWorkOrderServiceImpl implements MesProWorkOrderService { |
| | | |
| | | /** |
| | | * BPM 生产工单审批流程标识 |
| | | */ |
| | | public static final String BPM_PROCESS_DEFINITION_KEY = "mes-pro-work-order-audit"; |
| | | |
| | | @Resource |
| | | private MesProWorkOrderMapper workOrderMapper; |
| | | |
| | | @Resource |
| | | private MesProWorkOrderBomService workOrderBomService; |
| | | @Resource |
| | | private MesProWorkOrderProcessService workOrderProcessService; |
| | | @Resource |
| | | private MesMdItemService itemService; |
| | | @Resource |
| | |
| | | private MesWmBarcodeService barcodeService; |
| | | @Resource |
| | | private MesProTaskService taskService; |
| | | |
| | | @Resource |
| | | private BpmProcessInstanceApi bpmProcessInstanceApi; |
| | | private MesProProcessService processService; |
| | | @Resource |
| | | private MesMdWorkstationService workstationService; |
| | | @Resource |
| | | private CrmCustomerApi customerApi; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | // 3. 自动生成 BOM:根据产品 BOM 生成工单 BOM |
| | | workOrderBomService.generateWorkOrderBom(workOrder.getId(), createReqVO, false); |
| | | |
| | | // 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); |
| | |
| | | throw exception(PRO_WORK_ORDER_HAS_CHILDREN); |
| | | } |
| | | |
| | | // 2. 删除工单 + BOM |
| | | // 2. 删除工单 + BOM + 工序 |
| | | workOrderMapper.deleteById(id); |
| | | workOrderBomService.deleteWorkOrderBomByWorkOrderId(id); |
| | | workOrderProcessService.deleteWorkOrderProcessByWorkOrderId(id); |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | @Override |
| | | public PageResult<MesProWorkOrderDO> getWorkOrderPage(MesProWorkOrderPageReqVO pageReqVO) { |
| | | return workOrderMapper.selectPage(pageReqVO); |
| | | // 获取用户有权限的客户 ID 列表 |
| | | List<Long> permittedClientIds = customerApi.getPermittedCustomerIds(); |
| | | return workOrderMapper.selectPage(pageReqVO, permittedClientIds); |
| | | } |
| | | |
| | | @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); |
| | | } |
| | | |
| | | // ==================== BPM 流程审批 ==================== |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void submitWorkOrder(Long id, Long userId) { |
| | | // 1. 校验工单处于草稿状态 |
| | | MesProWorkOrderDO workOrder = validateWorkOrderExists(id); |
| | | if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.PREPARE.getStatus())) { |
| | | throw exception(PRO_WORK_ORDER_SUBMIT_FAIL_NOT_PREPARE); |
| | | } |
| | | |
| | | // 2. 创建生产工单审批流程实例 |
| | | String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO() |
| | | .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY) |
| | | .setBusinessKey(String.valueOf(id))); |
| | | |
| | | // 3. 更新工单工作流编号、状态为审批中 |
| | | workOrderMapper.updateById(new MesProWorkOrderDO() |
| | | .setId(id) |
| | | .setProcessInstanceId(processInstanceId) |
| | | .setStatus(MesProWorkOrderStatusEnum.AUDITING.getStatus())); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateWorkOrderAuditStatus(Long id, Integer bpmResult) { |
| | | // 1. 校验工单存在 |
| | | MesProWorkOrderDO workOrder = validateWorkOrderExists(id); |
| | | |
| | | // 2. 校验工单处于审批中 |
| | | if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.AUDITING.getStatus())) { |
| | | log.error("[updateWorkOrderAuditStatus][workOrder({}) 不处于审批中,无法更新审批结果({})]", |
| | | workOrder.getId(), bpmResult); |
| | | throw exception(PRO_WORK_ORDER_UPDATE_AUDIT_STATUS_FAIL_NOT_AUDITING); |
| | | } |
| | | |
| | | // 3. 更新工单审批结果 |
| | | Integer auditStatus = convertBpmResultToWorkOrderStatus(bpmResult); |
| | | workOrderMapper.updateById(new MesProWorkOrderDO() |
| | | .setId(id) |
| | | .setStatus(auditStatus)); |
| | | } |
| | | |
| | | /** |
| | | * 将 BPM 审批结果转换为 MES 工单状态 |
| | | * |
| | | * @param bpmResult BPM 审批结果 |
| | | * @return MES 工单状态 |
| | | */ |
| | | private Integer convertBpmResultToWorkOrderStatus(Integer bpmResult) { |
| | | // BPM 审批结果:2=通过,3=不通过,4=已取消 |
| | | if (bpmResult == 2) { |
| | | return MesProWorkOrderStatusEnum.CONFIRMED.getStatus(); |
| | | } else if (bpmResult == 3) { |
| | | return MesProWorkOrderStatusEnum.REJECTED.getStatus(); |
| | | } else if (bpmResult == 4) { |
| | | return MesProWorkOrderStatusEnum.PREPARE.getStatus(); |
| | | } else { |
| | | log.warn("[convertBpmResultToWorkOrderStatus][未知的 BPM 审批结果({})]", bpmResult); |
| | | return MesProWorkOrderStatusEnum.AUDITING.getStatus(); |
| | | } |
| | | } |
| | | |
| | | } |