| | |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.production.bean.dto.ProductionOperationTaskDto; |
| | | import com.ruoyi.production.bean.dto.ProductionOrderDto; |
| | | import com.ruoyi.production.bean.vo.ProductionBomStructureVo; |
| | | import com.ruoyi.production.bean.vo.ProductionOperationTaskVo; |
| | | import com.ruoyi.production.bean.vo.ProductionOrderPickVo; |
| | | import com.ruoyi.production.bean.vo.ProductionOrderVo; |
| | | import com.ruoyi.production.bean.vo.ProductionPlanVo; |
| | | import com.ruoyi.production.bean.vo.ProductionOrderWorkOrderDetailVo; |
| | |
| | | import com.ruoyi.production.service.ProductionOrderService; |
| | | import com.ruoyi.sales.mapper.SalesLedgerMapper; |
| | | import com.ruoyi.sales.mapper.SalesLedgerProductMapper; |
| | | import com.ruoyi.stock.mapper.StockInventoryMapper; |
| | | import com.ruoyi.stock.pojo.StockInventory; |
| | | import com.ruoyi.technology.mapper.*; |
| | | import com.ruoyi.technology.pojo.*; |
| | | import lombok.RequiredArgsConstructor; |
| | |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | |
| | | private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper; |
| | | private final ProductionOrderRoutingOperationParamMapper productionOrderRoutingOperationParamMapper; |
| | | private final ProductionOperationTaskMapper productionOperationTaskMapper; |
| | | private final ProductionOrderBomMapper productionOrderBomMapper; |
| | | private final ProductionBomStructureMapper productionBomStructureMapper; |
| | | private final ProductionOrderMapper productionOrderMapper; |
| | | private final ProductionProductMainMapper productionProductMainMapper; |
| | | private final ProductionProductOutputMapper productionProductOutputMapper; |
| | | private final ProductionOrderPickMapper productionOrderPickMapper; |
| | | private final ProductionOrderPickRecordMapper productionOrderPickRecordMapper; |
| | | private final QualityInspectMapper qualityInspectMapper; |
| | | private final QualityInspectParamMapper qualityInspectParamMapper; |
| | | private final QualityInspectFileMapper qualityInspectFileMapper; |
| | | private final ProductionPlanMapper productionPlanMapper; |
| | | private final StockInventoryMapper stockInventoryMapper; |
| | | private final StorageAttachmentMapper storageAttachmentMapper; |
| | | private final StorageBlobMapper storageBlobMapper; |
| | | private final SalesLedgerMapper salesLedgerMapper; |
| | |
| | | private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper; |
| | | private final TechnologyRoutingOperationParamMapper technologyRoutingOperationParamMapper; |
| | | private final TechnologyOperationMapper technologyOperationMapper; |
| | | private final TechnologyBomMapper technologyBomMapper; |
| | | private final TechnologyBomStructureMapper technologyBomStructureMapper; |
| | | private final FileUtil fileUtil; |
| | | |
| | | @Override |
| | |
| | | // 下单入口统一补齐来源单据、计划和工艺信息,避免前端分别传多套字段。 |
| | | validateAndFillOrder(productionOrder, oldOrder); |
| | | if (productionOrder.getNpsNo() == null || productionOrder.getNpsNo().trim().isEmpty()) { |
| | | productionOrder.setNpsNo(generateNextOrderNo()); |
| | | productionOrder.setNpsNo(generateNextOrderNo(productionOrder.getCreateTime() != null ? productionOrder.getCreateTime() : LocalDateTime.now())); |
| | | } |
| | | if (productionOrder.getCompleteQuantity() == null) { |
| | | productionOrder.setCompleteQuantity(BigDecimal.ZERO); |
| | |
| | | |
| | | @Override |
| | | public int syncProductionOrderSnapshot(Long productionOrderId) { |
| | | // 同步订单工艺、工序、参数和BOM快照 |
| | | ProductionOrder productionOrder = this.getById(productionOrderId); |
| | | // 参数与前置条件校验 |
| | | if (productionOrder == null) { |
| | | throw new ServiceException("生产订单不存在"); |
| | | } |
| | |
| | | if (technologyRouting == null) { |
| | | throw new ServiceException("工艺路线不存在"); |
| | | } |
| | | // 订单快照按“先清后建”处理,保证工艺路线、工序、参数、BOM 全部来自同一版本。 |
| | | clearProductionSnapshot(productionOrderId); |
| | | ProductionOrderBom orderBom = syncProductionOrderBomSnapshot(productionOrder, technologyRouting); |
| | | |
| | | //生产订单工艺路线表 |
| | | ProductionOrderRouting orderRouting = new ProductionOrderRouting(); |
| | | orderRouting.setProductionOrderId(productionOrder.getId()); |
| | | orderRouting.setTechnologyRoutingId(technologyRouting.getId()); |
| | | orderRouting.setProductModelId(technologyRouting.getProductModelId()); |
| | | orderRouting.setProcessRouteCode(technologyRouting.getProcessRouteCode()); |
| | | orderRouting.setDescription(technologyRouting.getDescription()); |
| | | orderRouting.setBomId(technologyRouting.getBomId()); |
| | | orderRouting.setOrderBomId(orderBom == null ? null : orderBom.getId()); |
| | | productionOrderRoutingMapper.insert(orderRouting); |
| | | |
| | | int syncedParamCount = 0; |
| | | // 查询并准备业务数据 |
| | | List<TechnologyRoutingOperation> routingOperations = technologyRoutingOperationMapper.selectList( |
| | | Wrappers.<TechnologyRoutingOperation>lambdaQuery() |
| | | .eq(TechnologyRoutingOperation::getTechnologyRoutingId, technologyRouting.getId()) |
| | | .orderByDesc(TechnologyRoutingOperation::getDragSort) |
| | | .orderByDesc(TechnologyRoutingOperation::getId)); |
| | | Map<String, BigDecimal> operationDemandedQuantityMap = buildOperationDemandedQuantityMap(technologyRouting, productionOrder); |
| | | BigDecimal orderQuantity = defaultDecimal(productionOrder.getQuantity()); |
| | | Map<Long, String> operationNameMap = technologyOperationMapper.selectBatchIds( |
| | | // 遍历处理数据并组装结果 |
| | | routingOperations.stream() |
| | | .map(TechnologyRoutingOperation::getTechnologyOperationId) |
| | | .filter(Objects::nonNull) |
| | |
| | | .max(Integer::compareTo) |
| | | .orElse(null); |
| | | for (TechnologyRoutingOperation sourceOperation : routingOperations) { |
| | | // 订单工序保存的是工艺工序快照,后续报工只依赖快照,不再直接引用工艺主数据。 |
| | | ProductionOrderRoutingOperation targetOperation = new ProductionOrderRoutingOperation(); |
| | | targetOperation.setProductionOrderId(productionOrder.getId()); |
| | | targetOperation.setTechnologyRoutingOperationId(sourceOperation.getId()); |
| | |
| | | ProductionOperationTask task = new ProductionOperationTask(); |
| | | task.setProductionOrderRoutingOperationId(targetOperation.getId()); |
| | | task.setProductionOrderId(productionOrder.getId()); |
| | | task.setPlanQuantity(resolveTaskPlanQuantity(sourceOperation, operationDemandedQuantityMap, productionOrder)); |
| | | task.setPlanQuantity(orderQuantity); |
| | | task.setCompleteQuantity(BigDecimal.ZERO); |
| | | task.setWorkOrderNo(generateNextTaskNo()); |
| | | task.setStatus(2); |
| | |
| | | .eq(TechnologyRoutingOperationParam::getTechnologyRoutingOperationId, sourceOperation.getId()) |
| | | .orderByAsc(TechnologyRoutingOperationParam::getId)); |
| | | for (TechnologyRoutingOperationParam sourceParam : sourceParams) { |
| | | // 工序执行参数同样做快照,避免工艺参数调整影响已下达订单。 |
| | | ProductionOrderRoutingOperationParam targetParam = new ProductionOrderRoutingOperationParam(); |
| | | targetParam.setProductionOrderId(productionOrder.getId()); |
| | | targetParam.setProductionOrderRoutingOperationId(targetOperation.getId()); |
| | |
| | | return syncedParamCount; |
| | | } |
| | | |
| | | private Map<String, BigDecimal> buildOperationDemandedQuantityMap(TechnologyRouting technologyRouting, |
| | | ProductionOrder productionOrder) { |
| | | if (technologyRouting == null || technologyRouting.getBomId() == null) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | BigDecimal orderQuantity = defaultDecimal(productionOrder == null ? null : productionOrder.getQuantity()); |
| | | List<TechnologyBomStructure> bomStructures = technologyBomStructureMapper.selectList( |
| | | Wrappers.<TechnologyBomStructure>lambdaQuery() |
| | | .eq(TechnologyBomStructure::getBomId, technologyRouting.getBomId()) |
| | | .isNotNull(TechnologyBomStructure::getOperationId) |
| | | .orderByAsc(TechnologyBomStructure::getId)); |
| | | if (bomStructures.isEmpty()) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | |
| | | Map<Long, TechnologyBomStructure> structureById = bomStructures.stream() |
| | | .filter(item -> item != null && item.getId() != null) |
| | | .collect(Collectors.toMap(TechnologyBomStructure::getId, item -> item, (left, right) -> left)); |
| | | Map<String, BigDecimal> demandedQuantityMap = new HashMap<>(); |
| | | for (TechnologyBomStructure bomStructure : bomStructures) { |
| | | if (bomStructure == null || bomStructure.getOperationId() == null) { |
| | | continue; |
| | | } |
| | | BigDecimal unitQuantity = bomStructure.getUnitQuantity(); |
| | | if (unitQuantity == null) { |
| | | continue; |
| | | } |
| | | Long outputProductModelId = resolveOutputProductModelId(bomStructure, structureById, technologyRouting.getProductModelId()); |
| | | String key = buildOperationDemandedQuantityKey(bomStructure.getOperationId(), outputProductModelId); |
| | | demandedQuantityMap.merge(key, unitQuantity.multiply(orderQuantity), BigDecimal::add); |
| | | } |
| | | return demandedQuantityMap; |
| | | } |
| | | |
| | | private BigDecimal resolveTaskPlanQuantity(TechnologyRoutingOperation sourceOperation, |
| | | Map<String, BigDecimal> operationDemandedQuantityMap, |
| | | ProductionOrder productionOrder) { |
| | | if (sourceOperation == null || operationDemandedQuantityMap == null || operationDemandedQuantityMap.isEmpty()) { |
| | | return defaultDecimal(productionOrder == null ? null : productionOrder.getQuantity()); |
| | | } |
| | | String key = buildOperationDemandedQuantityKey(sourceOperation.getTechnologyOperationId(), sourceOperation.getProductModelId()); |
| | | BigDecimal planQuantity = operationDemandedQuantityMap.get(key); |
| | | return planQuantity != null ? planQuantity : defaultDecimal(productionOrder == null ? null : productionOrder.getQuantity()); |
| | | } |
| | | |
| | | private String buildOperationDemandedQuantityKey(Long operationId, Long outputProductModelId) { |
| | | return String.valueOf(operationId) + "#" + String.valueOf(outputProductModelId); |
| | | } |
| | | |
| | | private Long resolveOutputProductModelId(TechnologyBomStructure bomStructure, |
| | | Map<Long, TechnologyBomStructure> structureById, |
| | | Long routingProductModelId) { |
| | | if (bomStructure == null) { |
| | | return routingProductModelId; |
| | | } |
| | | Long parentId = bomStructure.getParentId(); |
| | | if (parentId == null) { |
| | | return routingProductModelId != null ? routingProductModelId : bomStructure.getProductModelId(); |
| | | } |
| | | TechnologyBomStructure parent = structureById.get(parentId); |
| | | if (parent != null && parent.getProductModelId() != null) { |
| | | return parent.getProductModelId(); |
| | | } |
| | | return routingProductModelId != null ? routingProductModelId : bomStructure.getProductModelId(); |
| | | } |
| | | |
| | | private ProductionOrderBom syncProductionOrderBomSnapshot(ProductionOrder productionOrder, TechnologyRouting technologyRouting) { |
| | | // 同步订单BOM快照结构 |
| | | if (technologyRouting.getBomId() == null) { |
| | | return null; |
| | | } |
| | | TechnologyBom technologyBom = technologyBomMapper.selectById(technologyRouting.getBomId()); |
| | | if (technologyBom == null) { |
| | | throw new ServiceException("工艺BOM不存在"); |
| | | } |
| | | // 查询并准备业务数据 |
| | | List<TechnologyBomStructure> structureList = technologyBomStructureMapper.selectList( |
| | | Wrappers.<TechnologyBomStructure>lambdaQuery() |
| | | .eq(TechnologyBomStructure::getBomId, technologyBom.getId()) |
| | | .orderByAsc(TechnologyBomStructure::getId)); |
| | | // 遍历处理数据并组装结果 |
| | | TechnologyBomStructure root = structureList.stream().filter(item -> item.getParentId() == null).findFirst().orElse(null); |
| | | BigDecimal orderQuantity = defaultDecimal(productionOrder.getQuantity()); |
| | | |
| | | ProductionOrderBom orderBom = new ProductionOrderBom(); |
| | | orderBom.setProductionOrderId(productionOrder.getId()); |
| | | orderBom.setBomId(Long.valueOf(technologyBom.getId())); |
| | | orderBom.setProductModelId(root != null ? root.getProductModelId() : productionOrder.getProductModelId()); |
| | | orderBom.setRemark(technologyBom.getRemark()); |
| | | orderBom.setBomNo(technologyBom.getBomNo()); |
| | | orderBom.setVersion(technologyBom.getVersion()); |
| | | // 持久化或输出处理结果 |
| | | productionOrderBomMapper.insert(orderBom); |
| | | |
| | | Map<Long, Long> idMap = new HashMap<>(); |
| | | for (TechnologyBomStructure source : structureList) { |
| | | // 子节点 parentId 需要映射成新快照节点 id,才能保留原始 BOM 层级。 |
| | | ProductionBomStructure target = new ProductionBomStructure(); |
| | | target.setProductionOrderId(productionOrder.getId()); |
| | | target.setProductionOrderBomId(orderBom.getId()); |
| | | target.setParentId(source.getParentId() == null ? null : idMap.get(source.getParentId())); |
| | | target.setProductModelId(source.getProductModelId()); |
| | | target.setTechnologyOperationId(source.getOperationId()); |
| | | target.setUnitQuantity(source.getUnitQuantity()); |
| | | target.setDemandedQuantity(source.getUnitQuantity().multiply(orderQuantity)); |
| | | target.setUnit(source.getUnit()); |
| | | productionBomStructureMapper.insert(target); |
| | | idMap.put(source.getId(), target.getId()); |
| | | } |
| | | return orderBom; |
| | | } |
| | | |
| | | private void clearProductionSnapshot(Long productionOrderId) { |
| | | // 清理订单已生成的工艺与BOM快照数据 |
| | | boolean hasPickRecord = productionOrderPickRecordMapper.selectCount( |
| | | // 查询并准备业务数据 |
| | | Wrappers.<ProductionOrderPickRecord>lambdaQuery() |
| | | .eq(ProductionOrderPickRecord::getProductionOrderId, productionOrderId)) > 0; |
| | | // 参数与前置条件校验 |
| | | if (hasPickRecord) { |
| | | throw new ServiceException("生产订单已存在领料记录,不能重新生成快照"); |
| | | } |
| | | List<Long> taskIds = productionOperationTaskMapper.selectList( |
| | | Wrappers.<ProductionOperationTask>lambdaQuery() |
| | | .eq(ProductionOperationTask::getProductionOrderId, productionOrderId)) |
| | | // 遍历处理数据并组装结果 |
| | | .stream().map(ProductionOperationTask::getId).collect(Collectors.toList()); |
| | | if (!taskIds.isEmpty()) { |
| | | // 已有报工记录说明订单已开工,此时不允许再重建快照。 |
| | | boolean started = productionProductMainMapper.selectCount( |
| | | Wrappers.<ProductionProductMain>lambdaQuery() |
| | | .in(ProductionProductMain::getProductionOperationTaskId, taskIds)) > 0; |
| | |
| | | .eq(ProductionOrderRoutingOperation::getProductionOrderId, productionOrderId)); |
| | | productionOrderRoutingMapper.delete(Wrappers.<ProductionOrderRouting>lambdaQuery() |
| | | .eq(ProductionOrderRouting::getProductionOrderId, productionOrderId)); |
| | | productionBomStructureMapper.delete(Wrappers.<ProductionBomStructure>lambdaQuery() |
| | | .eq(ProductionBomStructure::getProductionOrderId, productionOrderId)); |
| | | productionOrderBomMapper.delete(Wrappers.<ProductionOrderBom>lambdaQuery() |
| | | .eq(ProductionOrderBom::getProductionOrderId, productionOrderId)); |
| | | productionOrderPickMapper.delete(Wrappers.<ProductionOrderPick>lambdaQuery() |
| | | .eq(ProductionOrderPick::getProductionOrderId, productionOrderId)); |
| | | } |
| | | |
| | | private LambdaQueryWrapper<ProductionOrder> buildQueryWrapper(ProductionOrderDto dto) { |
| | |
| | | .orderByDesc(ProductionOrder::getId); |
| | | } |
| | | |
| | | private String generateNextOrderNo() { |
| | | private String generateNextOrderNo(LocalDateTime createTime) { |
| | | // 生成下一个生产订单号 |
| | | String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); |
| | | LocalDate localDate = createTime.toLocalDate(); |
| | | String datePrefix = localDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")); |
| | | String prefix = "SC" + datePrefix; |
| | | ProductionOrder latestOrder = this.getOne(Wrappers.<ProductionOrder>lambdaQuery() |
| | | .likeRight(ProductionOrder::getNpsNo, prefix) |
| | |
| | | if (reportOutput == null) { |
| | | continue; |
| | | } |
| | | Long reportMainId = reportOutput.getProductionProductMainId() != null |
| | | ? reportOutput.getProductionProductMainId() |
| | | : reportOutput.getProductMainId(); |
| | | if (reportMainId == null) { |
| | | if (reportOutput.getProductionProductMainId() == null) { |
| | | continue; |
| | | } |
| | | Long reportMainId = reportOutput.getProductionProductMainId(); |
| | | reportOutputMap.computeIfAbsent(reportMainId, k -> new ArrayList<>()).add(reportOutput); |
| | | } |
| | | |
| | |
| | | throw new ServiceException("生产订单不存在"); |
| | | } |
| | | return productionOrder.getId(); |
| | | } |
| | | |
| | | @Override |
| | | public List<ProductionOrderPickVo> pick(Long productionOrderId) { |
| | | // 查询订单领料、投料与退料明细 |
| | | if (productionOrderId == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | // 查询并准备业务数据 |
| | | ProductionOrderBom orderBom = productionOrderBomMapper.selectOne( |
| | | Wrappers.<ProductionOrderBom>lambdaQuery() |
| | | .eq(ProductionOrderBom::getProductionOrderId, productionOrderId) |
| | | .orderByDesc(ProductionOrderBom::getId) |
| | | .last("limit 1")); |
| | | if (orderBom == null || orderBom.getId() == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | List<ProductionBomStructureVo> bomStructureList = productionBomStructureMapper.pickByBomId(orderBom.getId()); |
| | | if (bomStructureList == null || bomStructureList.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | // 遍历处理数据并组装结果 |
| | | List<Long> productModelIds = bomStructureList.stream() |
| | | .map(ProductionBomStructureVo::getProductModelId) |
| | | .filter(Objects::nonNull) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | Map<Long, BigDecimal> stockQuantityMap = new HashMap<>(); |
| | | Map<Long, LinkedHashSet<String>> stockBatchNoMap = new HashMap<>(); |
| | | if (!productModelIds.isEmpty()) { |
| | | List<StockInventory> stockList = stockInventoryMapper.selectList( |
| | | Wrappers.<StockInventory>lambdaQuery() |
| | | .in(StockInventory::getProductModelId, productModelIds)); |
| | | for (StockInventory stockItem : stockList) { |
| | | if (stockItem == null || stockItem.getProductModelId() == null) { |
| | | continue; |
| | | } |
| | | Long productModelId = stockItem.getProductModelId(); |
| | | stockQuantityMap.merge(productModelId, defaultDecimal(stockItem.getQualitity()), BigDecimal::add); |
| | | String batchNo = stockItem.getBatchNo(); |
| | | if (batchNo != null && !batchNo.trim().isEmpty()) { |
| | | stockBatchNoMap.computeIfAbsent(productModelId, key -> new LinkedHashSet<>()).add(batchNo); |
| | | } |
| | | } |
| | | } |
| | | |
| | | Map<String, ProductionOrderPickVo> mergedPickMap = new LinkedHashMap<>(); |
| | | for (ProductionBomStructureVo structure : bomStructureList) { |
| | | if (structure == null || structure.getProductModelId() == null) { |
| | | continue; |
| | | } |
| | | Long productModelId = structure.getProductModelId(); |
| | | String mergeKey = String.valueOf(structure.getTechnologyOperationId()) + "#" + productModelId; |
| | | ProductionOrderPickVo vo = mergedPickMap.get(mergeKey); |
| | | if (vo == null) { |
| | | vo = new ProductionOrderPickVo(); |
| | | vo.setProductModelId(productModelId); |
| | | vo.setOperationName(structure.getOperationName()); |
| | | vo.setTechnologyOperationId(structure.getTechnologyOperationId()); |
| | | vo.setProductName(structure.getProductName()); |
| | | vo.setModel(structure.getModel()); |
| | | vo.setDemandedQuantity(BigDecimal.ZERO); |
| | | vo.setUnit(structure.getUnit()); |
| | | List<String> batchNoList = stockBatchNoMap.get(productModelId) == null |
| | | ? Collections.emptyList() |
| | | : new ArrayList<>(stockBatchNoMap.get(productModelId)); |
| | | vo.setBatchNoList(batchNoList); |
| | | vo.setStockQuantity(stockQuantityMap.getOrDefault(productModelId, BigDecimal.ZERO)); |
| | | vo.setBom(true); |
| | | mergedPickMap.put(mergeKey, vo); |
| | | } |
| | | vo.setDemandedQuantity(defaultDecimal(vo.getDemandedQuantity()).add(defaultDecimal(structure.getDemandedQuantity()))); |
| | | } |
| | | return new ArrayList<>(mergedPickMap.values()); |
| | | } |
| | | |
| | | @Override |