| | |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | 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.MesProWorkOrderImportExcelVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderImportRespVO; |
| | | 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.md.item.MesMdItemDO; |
| | | 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.service.pro.task.MesProTaskService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi; |
| | | import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDateTime; |
| | | import java.util.*; |
| | | import java.util.concurrent.atomic.AtomicInteger; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | |
| | | @Resource |
| | | private CrmCustomerApi customerApi; |
| | | |
| | | @Resource |
| | | @Lazy // 避免 erpSaleOrderApiImpl -> ... -> mesProWorkOrderServiceImpl 的循环依赖 |
| | | private ErpSaleOrderApi saleOrderApi; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createWorkOrder(MesProWorkOrderSaveReqVO createReqVO) { |
| | | // 1. 校验数据 |
| | | validateWorkOrderSaveData(null, createReqVO); |
| | | // 1.1 关联营销订单信息回填 |
| | | fillSaleOrderInfo(createReqVO); |
| | | |
| | | // 2.1 设置默认值 |
| | | if (createReqVO.getParentId() == null) { |
| | |
| | | workOrder.setStatus(MesProWorkOrderStatusEnum.PREPARE.getStatus()); |
| | | workOrderMapper.insert(workOrder); |
| | | |
| | | // 3. 自动生成 BOM:根据产品 BOM 生成工单 BOM |
| | | workOrderBomService.generateWorkOrderBom(workOrder.getId(), createReqVO, false); |
| | | // 3. 自动生成 BOM:根据产品 BOM 生成工单 BOM(无产品的运维/发电/检修类工单跳过) |
| | | if (createReqVO.getProductId() != null) { |
| | | workOrderBomService.generateWorkOrderBom(workOrder.getId(), createReqVO, false); |
| | | } |
| | | |
| | | // 4. 工序由前端选择产品后带入,不再自动生成 |
| | | |
| | |
| | | } |
| | | // 1.2 校验数据 |
| | | validateWorkOrderSaveData(updateReqVO.getId(), updateReqVO); |
| | | // 1.3 关联营销订单信息回填 |
| | | fillSaleOrderInfo(updateReqVO); |
| | | |
| | | // 2. 判断产品或数量是否变更,如果变更则重新生成 BOM(updated=true 会先清理旧数据) |
| | | // 2. 判断产品或数量是否变更,如果变更则重新生成 BOM(updated=true 会先清理旧数据;无产品的运维类工单跳过) |
| | | boolean productChanged = ObjUtil.notEqual(oldWorkOrder.getProductId(), updateReqVO.getProductId()); |
| | | boolean quantityChanged = oldWorkOrder.getQuantity().compareTo(updateReqVO.getQuantity()) != 0; |
| | | if (productChanged || quantityChanged) { |
| | | if ((productChanged || quantityChanged) && updateReqVO.getProductId() != null) { |
| | | workOrderBomService.generateWorkOrderBom(updateReqVO.getId(), updateReqVO, true); |
| | | } |
| | | // 2.1 工序由前端管理,产品变更时前端会重新获取工艺路线工序 |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesProWorkOrderImportRespVO importWorkOrderList(List<MesProWorkOrderImportExcelVO> importList, |
| | | boolean updateSupport) { |
| | | // 1. 校验非空 |
| | | if (CollUtil.isEmpty(importList)) { |
| | | throw exception(MES_PRO_WORK_ORDER_IMPORT_LIST_IS_EMPTY); |
| | | } |
| | | |
| | | // 2. 逐行处理 |
| | | MesProWorkOrderImportRespVO respVO = MesProWorkOrderImportRespVO.builder() |
| | | .createCodes(new ArrayList<>()).updateCodes(new ArrayList<>()) |
| | | .failureCodes(new LinkedHashMap<>()).build(); |
| | | AtomicInteger index = new AtomicInteger(1); |
| | | importList.forEach(importItem -> { |
| | | int currentIndex = index.getAndIncrement(); |
| | | String key = StrUtil.blankToDefault(importItem.getCode(), "第 " + currentIndex + " 行"); |
| | | if (StrUtil.isBlank(importItem.getCode())) { |
| | | respVO.getFailureCodes().put(key, "工单编码不能为空"); |
| | | return; |
| | | } |
| | | if (StrUtil.isBlank(importItem.getName())) { |
| | | respVO.getFailureCodes().put(key, "工单名称不能为空"); |
| | | return; |
| | | } |
| | | // 2.1 解析产品 |
| | | Long productId = null; |
| | | if (StrUtil.isNotBlank(importItem.getProductCode())) { |
| | | MesMdItemDO item = itemService.getItemByCode(importItem.getProductCode().trim()); |
| | | if (item == null) { |
| | | respVO.getFailureCodes().put(key, "产品编码[" + importItem.getProductCode() + "]不存在"); |
| | | return; |
| | | } |
| | | productId = item.getId(); |
| | | } |
| | | // 2.2 解析客户 |
| | | Long clientId = null; |
| | | if (StrUtil.isNotBlank(importItem.getClientName())) { |
| | | CrmCustomerRespDTO customer = customerApi.getCustomerByName(importItem.getClientName().trim()); |
| | | if (customer == null) { |
| | | respVO.getFailureCodes().put(key, "客户名称[" + importItem.getClientName() + "]不存在"); |
| | | return; |
| | | } |
| | | clientId = customer.getId(); |
| | | } |
| | | // 2.3 构建请求并创建/更新(复用现有 create/update 服务,保留 BOM 与条码生成逻辑) |
| | | MesProWorkOrderSaveReqVO reqVO = BeanUtils.toBean(importItem, MesProWorkOrderSaveReqVO.class); |
| | | reqVO.setProductId(productId); |
| | | reqVO.setClientId(clientId); |
| | | MesProWorkOrderDO existWorkOrder = workOrderMapper.selectByCode(importItem.getCode()); |
| | | try { |
| | | if (existWorkOrder == null) { |
| | | reqVO.setId(null); |
| | | createWorkOrder(reqVO); |
| | | respVO.getCreateCodes().add(importItem.getCode()); |
| | | } else if (updateSupport) { |
| | | reqVO.setId(existWorkOrder.getId()); |
| | | updateWorkOrder(reqVO); |
| | | respVO.getUpdateCodes().add(importItem.getCode()); |
| | | } else { |
| | | respVO.getFailureCodes().put(key, "工单编码已存在"); |
| | | } |
| | | } catch (ServiceException e) { |
| | | respVO.getFailureCodes().put(key, e.getMessage()); |
| | | } |
| | | }); |
| | | return respVO; |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<MesProWorkOrderDO> getWorkOrderPage(MesProWorkOrderPageReqVO pageReqVO) { |
| | | // 获取用户有权限的客户 ID 列表 |
| | | List<Long> permittedClientIds = customerApi.getPermittedCustomerIds(); |
| | | return workOrderMapper.selectPage(pageReqVO, permittedClientIds); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<MesProWorkOrderDO> getWorkOrderSchedulePage(MesProWorkOrderPageReqVO pageReqVO) { |
| | | // 获取用户有权限的客户 ID 列表 |
| | | List<Long> permittedClientIds = customerApi.getPermittedCustomerIds(); |
| | | return workOrderMapper.selectSchedulePage(pageReqVO, permittedClientIds); |
| | | } |
| | | |
| | | @Override |
| | |
| | | if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.CONFIRMED.getStatus())) { |
| | | throw exception(PRO_WORK_ORDER_NOT_CONFIRMED); |
| | | } |
| | | if (workOrder.getQuantityProduced() == null |
| | | || workOrder.getQuantity() == null |
| | | || workOrder.getQuantityProduced().compareTo(workOrder.getQuantity()) < 0) { |
| | | throw exception(PRO_WORK_ORDER_PRODUCTION_NOT_COMPLETE); |
| | | } |
| | | // 校验每个任务的实际产出是否已达成计划数量(任务状态只有在工单完成时由级联方法置为已完成,不能在此校验状态) |
| | | List<MesProTaskDO> tasks = taskService.getTaskListByWorkOrderIds(Collections.singletonList(id)); |
| | | boolean hasUnfinishedTask = CollUtil.isEmpty(tasks) || tasks.stream().anyMatch(task -> |
| | | task.getProducedQuantity() == null |
| | | || task.getQuantity() == null |
| | | || task.getProducedQuantity().compareTo(task.getQuantity()) < 0); |
| | | if (hasUnfinishedTask) { |
| | | throw exception(PRO_WORK_ORDER_PRODUCTION_NOT_COMPLETE); |
| | | } |
| | | |
| | | // 2. 级联完成所有关联任务 |
| | | taskService.finishTaskByOrderId(id); |
| | |
| | | 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); |
| | | // 2. 产品非空时校验产品存在(运维/发电/检修类工单可无产品) |
| | | if (reqVO.getProductId() != null) { |
| | | 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); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 关联营销订单(ERP 销售订单):校验存在性并回填单号、客户 |
| | | */ |
| | | private void fillSaleOrderInfo(MesProWorkOrderSaveReqVO reqVO) { |
| | | if (reqVO.getSaleOrderId() == null) { |
| | | return; |
| | | } |
| | | ErpSaleOrderRespDTO saleOrder = saleOrderApi.getSaleOrder(reqVO.getSaleOrderId()).getCheckedData(); |
| | | if (saleOrder == null) { |
| | | throw exception(PRO_WORK_ORDER_SALE_ORDER_NOT_EXISTS); |
| | | } |
| | | reqVO.setSaleOrderNo(saleOrder.getNo()); |
| | | if (reqVO.getClientId() == null && saleOrder.getCustomerId() != null) { |
| | | reqVO.setClientId(saleOrder.getCustomerId()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void updateProducedQuantity(Long id, BigDecimal incrQuantityProduced) { |
| | | // 校验工单存在 |