package com.ruoyi.production.service.impl; import cn.hutool.core.bean.BeanUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.basic.dto.StorageBlobVO; import com.ruoyi.basic.mapper.StorageAttachmentMapper; import com.ruoyi.basic.mapper.StorageBlobMapper; import com.ruoyi.basic.pojo.StorageAttachment; import com.ruoyi.basic.pojo.StorageBlob; import com.ruoyi.basic.utils.FileUtil; import com.ruoyi.common.constant.StorageAttachmentConstants; 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.enums.ProductOrderStatusEnum; import com.ruoyi.production.mapper.*; import com.ruoyi.production.pojo.*; import com.ruoyi.quality.mapper.QualityInspectFileMapper; import com.ruoyi.quality.mapper.QualityInspectMapper; import com.ruoyi.quality.mapper.QualityInspectParamMapper; import com.ruoyi.quality.pojo.QualityInspect; import com.ruoyi.quality.pojo.QualityInspectFile; import com.ruoyi.quality.pojo.QualityInspectParam; 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 org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; @Service @Transactional(rollbackFor = Exception.class) @RequiredArgsConstructor public class ProductionOrderServiceImpl extends ServiceImpl implements ProductionOrderService { private final ProductionOrderRoutingMapper productionOrderRoutingMapper; private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper; private final ProductionOrderRoutingOperationParamMapper productionOrderRoutingOperationParamMapper; private final ProductionOperationTaskMapper productionOperationTaskMapper; private final ProductionOrderBomMapper productionOrderBomMapper; private final ProductionBomStructureMapper productionBomStructureMapper; 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 SalesLedgerProductMapper salesLedgerProductMapper; private final TechnologyRoutingMapper technologyRoutingMapper; 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 public IPage pageProductionOrder(Page page, ProductionOrderDto dto) { Page result = (Page) baseMapper.pageProductionOrder(page, dto); fillProductImages(result.getRecords()); return result; } @Override public List listProductionOrder(ProductionOrderDto dto) { List records = baseMapper.listProductionOrder(dto); fillProductImages(records); return records; } @Override public ProductionOrderVo getProductionOrderInfo(Long id) { ProductionOrderVo item = baseMapper.getProductionOrderInfo(id); if (item == null) { return null; } fillProductImages(java.util.Collections.singletonList(item)); return item; } @Override public boolean saveProductionOrder(ProductionOrder productionOrder) { ProductionOrder oldOrder = productionOrder.getId() == null ? null : this.getById(productionOrder.getId()); // 下单入口统一补齐来源单据、计划和工艺信息,避免前端分别传多套字段。 validateAndFillOrder(productionOrder, oldOrder); if (productionOrder.getNpsNo() == null || productionOrder.getNpsNo().trim().isEmpty()) { productionOrder.setNpsNo(generateNextOrderNo()); } if (productionOrder.getCompleteQuantity() == null) { productionOrder.setCompleteQuantity(BigDecimal.ZERO); } if (productionOrder.getStatus() == null) { productionOrder.setStatus(ProductOrderStatusEnum.WAIT.getCode()); } boolean saved = this.saveOrUpdate(productionOrder); if (!saved) { return false; } boolean needSync = productionOrder.getTechnologyRoutingId() != null && (oldOrder == null || !Objects.equals(oldOrder.getTechnologyRoutingId(), productionOrder.getTechnologyRoutingId()) || !Objects.equals(oldOrder.getProductModelId(), productionOrder.getProductModelId()) || compareDecimal(oldOrder.getQuantity(), productionOrder.getQuantity()) != 0 || productionOrderRoutingMapper.selectCount(Wrappers.lambdaQuery() .eq(ProductionOrderRouting::getProductionOrderId, productionOrder.getId())) == 0); if (needSync) { syncProductionOrderSnapshot(productionOrder.getId()); } return true; } @Override public boolean removeProductionOrder(List ids) { if (ids == null || ids.isEmpty()) { return false; } for (Long id : ids) { ProductionOrder productionOrder = this.getById(id); clearProductionSnapshot(id); releaseProductionPlanIssueStatus(productionOrder); } return this.removeByIds(ids); } @Override public Integer bindingRoute(ProductionOrderDto productionOrderDto) { if (productionOrderDto == null || productionOrderDto.getId() == null) { throw new ServiceException("生产订单ID不能为空"); } ProductionOrder productionOrder = this.getById(productionOrderDto.getId()); if (productionOrder == null) { throw new ServiceException("生产订单不存在"); } Long targetRoutingId = productionOrderDto.getTechnologyRoutingId() == null ? productionOrder.getTechnologyRoutingId() : productionOrderDto.getTechnologyRoutingId(); if (targetRoutingId == null) { throw new ServiceException("工艺路线ID不能为空"); } TechnologyRouting targetRouting = technologyRoutingMapper.selectById(targetRoutingId); if (targetRouting == null) { throw new ServiceException("工艺路线不存在"); } if (productionOrder.getProductModelId() != null && !Objects.equals(productionOrder.getProductModelId(), targetRouting.getProductModelId())) { throw new ServiceException("工艺路线与生产订单产品规格不匹配"); } if (ProductOrderStatusEnum.isStarted(productionOrder.getStatus()) && !Objects.equals(productionOrder.getTechnologyRoutingId(), targetRoutingId)) { throw new ServiceException("生产订单已开工,不能修改工艺路线"); } if (!Objects.equals(productionOrder.getTechnologyRoutingId(), targetRoutingId)) { ProductionOrder update = new ProductionOrder(); update.setId(productionOrder.getId()); update.setTechnologyRoutingId(targetRoutingId); if (!this.updateById(update)) { throw new ServiceException("绑定工艺路线失败"); } } // 绑定路线仅重建订单侧快照数据 return syncProductionOrderSnapshot(productionOrder.getId()); } @Override public List getSource(Long id) { ProductionOrder productionOrder = baseMapper.selectById(id); if (productionOrder != null && productionOrder.getProductionPlanIds() != null) { List planIds = parsePlanIds(productionOrder.getProductionPlanIds()); return productionPlanMapper.getSource(planIds); } return null; } @Override public int syncProductionOrderSnapshot(Long productionOrderId) { ProductionOrder productionOrder = this.getById(productionOrderId); if (productionOrder == null) { throw new ServiceException("生产订单不存在"); } if (productionOrder.getTechnologyRoutingId() == null) { throw new ServiceException("工艺路线ID不能为空"); } TechnologyRouting technologyRouting = technologyRoutingMapper.selectById(productionOrder.getTechnologyRoutingId()); 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 routingOperations = technologyRoutingOperationMapper.selectList( Wrappers.lambdaQuery() .eq(TechnologyRoutingOperation::getTechnologyRoutingId, technologyRouting.getId()) .orderByDesc(TechnologyRoutingOperation::getDragSort) .orderByDesc(TechnologyRoutingOperation::getId)); Map operationNameMap = technologyOperationMapper.selectBatchIds( routingOperations.stream() .map(TechnologyRoutingOperation::getTechnologyOperationId) .filter(Objects::nonNull) .collect(Collectors.toSet())) .stream() .collect(Collectors.toMap(TechnologyOperation::getId, TechnologyOperation::getName, (a, b) -> a)); Integer lastDragSort = routingOperations.stream() .map(TechnologyRoutingOperation::getDragSort) .filter(Objects::nonNull) .max(Integer::compareTo) .orElse(null); for (TechnologyRoutingOperation sourceOperation : routingOperations) { // 订单工序保存的是工艺工序快照,后续报工只依赖快照,不再直接引用工艺主数据。 ProductionOrderRoutingOperation targetOperation = new ProductionOrderRoutingOperation(); targetOperation.setProductionOrderId(productionOrder.getId()); targetOperation.setTechnologyRoutingOperationId(sourceOperation.getId()); targetOperation.setOrderRoutingId(orderRouting.getId()); targetOperation.setProductModelId(sourceOperation.getProductModelId()); targetOperation.setDragSort(sourceOperation.getDragSort()); targetOperation.setIsProduction(sourceOperation.getIsProduction()); targetOperation.setIsQuality(sourceOperation.getIsQuality()); targetOperation.setOperationName(operationNameMap.get(sourceOperation.getTechnologyOperationId())); targetOperation.setTechnologyOperationId(sourceOperation.getTechnologyOperationId()); productionOrderRoutingOperationMapper.insert(targetOperation); boolean isLastOperation = lastDragSort != null && Objects.equals(sourceOperation.getDragSort(), lastDragSort); if (isLastOperation || Boolean.TRUE.equals(targetOperation.getIsProduction())) { ProductionOperationTask task = new ProductionOperationTask(); task.setProductionOrderRoutingOperationId(targetOperation.getId()); task.setProductionOrderId(productionOrder.getId()); task.setPlanQuantity(defaultDecimal(productionOrder.getQuantity())); task.setCompleteQuantity(BigDecimal.ZERO); task.setWorkOrderNo(generateNextTaskNo()); task.setStatus(2); productionOperationTaskMapper.insert(task); } List sourceParams = technologyRoutingOperationParamMapper.selectList( Wrappers.lambdaQuery() .eq(TechnologyRoutingOperationParam::getTechnologyRoutingOperationId, sourceOperation.getId()) .orderByAsc(TechnologyRoutingOperationParam::getId)); for (TechnologyRoutingOperationParam sourceParam : sourceParams) { // 工序执行参数同样做快照,避免工艺参数调整影响已下达订单。 ProductionOrderRoutingOperationParam targetParam = new ProductionOrderRoutingOperationParam(); targetParam.setProductionOrderId(productionOrder.getId()); targetParam.setProductionOrderRoutingOperationId(targetOperation.getId()); targetParam.setTechnologyRoutingOperationParamId(sourceParam.getId()); targetParam.setParamId(sourceParam.getParamId()); targetParam.setTechnologyOperationId(sourceParam.getTechnologyOperationId()); targetParam.setTechnologyOperationParamId(sourceParam.getTechnologyOperationParamId()); targetParam.setParamCode(sourceParam.getParamCode()); targetParam.setParamName(sourceParam.getParamName()); targetParam.setParamType(sourceParam.getParamType()); targetParam.setParamFormat(sourceParam.getParamFormat()); targetParam.setUnit(sourceParam.getUnit()); targetParam.setIsRequired(sourceParam.getIsRequired()); targetParam.setRemark(sourceParam.getRemark()); targetParam.setStandardValue(sourceParam.getStandardValue()); productionOrderRoutingOperationParamMapper.insert(targetParam); syncedParamCount++; } } return syncedParamCount; } private ProductionOrderBom syncProductionOrderBomSnapshot(ProductionOrder productionOrder, TechnologyRouting technologyRouting) { if (technologyRouting.getBomId() == null) { return null; } TechnologyBom technologyBom = technologyBomMapper.selectById(technologyRouting.getBomId()); if (technologyBom == null) { throw new ServiceException("工艺BOM不存在"); } List structureList = technologyBomStructureMapper.selectList( Wrappers.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 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) { // 已产生领料记录后禁止重建,避免备料/投料依据与订单快照脱节。 boolean hasPickRecord = productionOrderPickRecordMapper.selectCount( Wrappers.lambdaQuery() .eq(ProductionOrderPickRecord::getProductionOrderId, productionOrderId)) > 0; if (hasPickRecord) { throw new ServiceException("生产订单已存在领料记录,不能重新生成快照"); } List taskIds = productionOperationTaskMapper.selectList( Wrappers.lambdaQuery() .eq(ProductionOperationTask::getProductionOrderId, productionOrderId)) .stream().map(ProductionOperationTask::getId).collect(Collectors.toList()); if (!taskIds.isEmpty()) { // 已有报工记录说明订单已开工,此时不允许再重建快照。 boolean started = productionProductMainMapper.selectCount( Wrappers.lambdaQuery() .in(ProductionProductMain::getProductionOperationTaskId, taskIds)) > 0; if (started) { throw new ServiceException("生产订单已开工,不能重新生成快照"); } productionOperationTaskMapper.delete(Wrappers.lambdaQuery() .eq(ProductionOperationTask::getProductionOrderId, productionOrderId)); } productionOrderRoutingOperationParamMapper.delete(Wrappers.lambdaQuery() .eq(ProductionOrderRoutingOperationParam::getProductionOrderId, productionOrderId)); productionOrderRoutingOperationMapper.delete(Wrappers.lambdaQuery() .eq(ProductionOrderRoutingOperation::getProductionOrderId, productionOrderId)); productionOrderRoutingMapper.delete(Wrappers.lambdaQuery() .eq(ProductionOrderRouting::getProductionOrderId, productionOrderId)); productionBomStructureMapper.delete(Wrappers.lambdaQuery() .eq(ProductionBomStructure::getProductionOrderId, productionOrderId)); productionOrderBomMapper.delete(Wrappers.lambdaQuery() .eq(ProductionOrderBom::getProductionOrderId, productionOrderId)); productionOrderPickMapper.delete(Wrappers.lambdaQuery() .eq(ProductionOrderPick::getProductionOrderId, productionOrderId)); } private LambdaQueryWrapper buildQueryWrapper(ProductionOrderDto dto) { ProductionOrder query = dto == null ? new ProductionOrder() : dto; return Wrappers.lambdaQuery() .eq(query.getId() != null, ProductionOrder::getId, query.getId()) .eq(query.getProductModelId() != null, ProductionOrder::getProductModelId, query.getProductModelId()) .eq(query.getTechnologyRoutingId() != null, ProductionOrder::getTechnologyRoutingId, query.getTechnologyRoutingId()) .like(query.getNpsNo() != null && !query.getNpsNo().trim().isEmpty(), ProductionOrder::getNpsNo, query.getNpsNo()) .orderByDesc(ProductionOrder::getId); } private String generateNextOrderNo() { String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); String prefix = "SC" + datePrefix; ProductionOrder latestOrder = this.getOne(Wrappers.lambdaQuery() .likeRight(ProductionOrder::getNpsNo, prefix) .orderByDesc(ProductionOrder::getNpsNo) .last("limit 1")); int sequence = 1; if (latestOrder != null && latestOrder.getNpsNo() != null && latestOrder.getNpsNo().startsWith(prefix)) { try { sequence = Integer.parseInt(latestOrder.getNpsNo().substring(prefix.length())) + 1; } catch (NumberFormatException ignored) { sequence = 1; } } return prefix + String.format("%04d", sequence); } private String generateNextTaskNo() { String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); String prefix = "GD" + datePrefix; ProductionOperationTask lastTask = productionOperationTaskMapper.selectOne( Wrappers.lambdaQuery() .likeRight(ProductionOperationTask::getWorkOrderNo, prefix) .orderByDesc(ProductionOperationTask::getWorkOrderNo) .last("limit 1")); int sequence = 1; if (lastTask != null && lastTask.getWorkOrderNo() != null && lastTask.getWorkOrderNo().startsWith(prefix)) { try { sequence = Integer.parseInt(lastTask.getWorkOrderNo().substring(prefix.length())) + 1; } catch (NumberFormatException ignored) { sequence = 1; } } return prefix + String.format("%03d", sequence); } private BigDecimal defaultDecimal(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; } private void validateAndFillOrder(ProductionOrder productionOrder, ProductionOrder oldOrder) { if (productionOrder == null) { throw new ServiceException("生产订单不能为空"); } fillFromProductionPlans(productionOrder); if (productionOrder.getProductModelId() == null) { throw new ServiceException("产品规格ID不能为空"); } if (defaultDecimal(productionOrder.getQuantity()).compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException("下单数量必须大于0"); } if (productionOrder.getTechnologyRoutingId() == null) { // 未显式指定工艺路线时,按产品规格选最新一条工艺作为默认路线。 TechnologyRouting technologyRouting = technologyRoutingMapper.selectOne( Wrappers.lambdaQuery() .eq(TechnologyRouting::getProductModelId, productionOrder.getProductModelId()) .orderByDesc(TechnologyRouting::getId) .last("limit 1")); if (technologyRouting != null) { productionOrder.setTechnologyRoutingId(technologyRouting.getId()); } } if (oldOrder != null && ProductOrderStatusEnum.isStarted(oldOrder.getStatus())) { // 开工后只允许修正非核心字段,核心生产依据锁定。 if (!Objects.equals(oldOrder.getProductModelId(), productionOrder.getProductModelId()) || !Objects.equals(oldOrder.getTechnologyRoutingId(), productionOrder.getTechnologyRoutingId()) || compareDecimal(oldOrder.getQuantity(), productionOrder.getQuantity()) != 0) { throw new ServiceException("生产订单已开工,不能修改产品、工艺路线或数量"); } } } private void fillFromProductionPlans(ProductionOrder productionOrder) { List planIds = parsePlanIds(productionOrder.getProductionPlanIds()); if (planIds.isEmpty()) { return; } // 多计划合并转单时,所有计划必须属于同一规格,且只能下发一次。 List productionPlans = productionPlanMapper.selectBatchIds(planIds); if (productionPlans.size() != planIds.size()) { throw new ServiceException("部分生产计划不存在"); } Map planMap = productionPlans.stream() .collect(Collectors.toMap(ProductionPlan::getId, item -> item, (left, right) -> left)); ProductionPlan mainPlan = planMap.get(planIds.get(0)); if (mainPlan == null) { throw new ServiceException("主生产计划不存在"); } Set productModelIds = productionPlans.stream() .map(ProductionPlan::getProductModelId) .collect(Collectors.toSet()); if (productModelIds.size() > 1) { throw new ServiceException("所选生产计划必须属于同一产品规格"); } if (productionPlans.stream().anyMatch(item -> item.getStatus() != null && item.getStatus() == 2)) { throw new ServiceException("所选生产计划已下发"); } ProductionPlan firstPlan = mainPlan; if (productionOrder.getProductModelId() == null) { productionOrder.setProductModelId(firstPlan.getProductModelId()); } else if (!Objects.equals(productionOrder.getProductModelId(), firstPlan.getProductModelId())) { throw new ServiceException("产品规格ID与生产计划不一致"); } if (productionOrder.getQuantity() == null || productionOrder.getQuantity().compareTo(BigDecimal.ZERO) <= 0) { productionOrder.setQuantity(productionPlans.stream() .map(ProductionPlan::getQtyRequired) .reduce(BigDecimal.ZERO, BigDecimal::add)); } if (productionOrder.getPlanCompleteTime() == null) { LocalDate planCompleteTime = productionPlans.stream() .map(this::resolvePlanCompleteDate) .filter(Objects::nonNull) .min(Comparator.naturalOrder()) .orElse(null); productionOrder.setPlanCompleteTime(planCompleteTime); } productionOrder.setProductionPlanIds(formatPlanIds(planIds)); } private void releaseProductionPlanIssueStatus(ProductionOrder productionOrder) { if (productionOrder == null) { return; } List planIds = parsePlanIds(productionOrder.getProductionPlanIds()); if (!planIds.isEmpty()) { // 生产订单删除--对应的生产计划的已下发数量要减去 updatePlanIssuedFlag(planIds, productionOrder.getQuantity()); } } //生产订单删除,生产计划的已下发数量对应变更 private void updatePlanIssuedFlag(List planIds, BigDecimal remainingAssignedQuantity) { if (planIds == null || planIds.isEmpty()) { return; } List plans = productionPlanMapper.selectBatchIds(planIds); //下发数量减去 List updates = new ArrayList<>(); for (ProductionPlan plan : plans) { BigDecimal requiredQuantity = Optional.ofNullable(plan.getQtyRequired()).orElse(BigDecimal.ZERO); if (requiredQuantity.compareTo(BigDecimal.ZERO) < 0) { requiredQuantity = BigDecimal.ZERO; } BigDecimal remainingQuantity = resolveRemainingQuantity(plan); BigDecimal historicalIssuedQuantity = requiredQuantity.subtract(remainingQuantity); BigDecimal issuedQuantity = remainingAssignedQuantity.min(historicalIssuedQuantity); remainingAssignedQuantity = remainingAssignedQuantity.subtract(issuedQuantity); BigDecimal totalIssuedQuantity = historicalIssuedQuantity.subtract(issuedQuantity); int planStatus = resolvePlanStatus(requiredQuantity, totalIssuedQuantity); ProductionPlan update = new ProductionPlan(); update.setId(plan.getId()); update.setStatus(planStatus); update.setQuantityIssued(totalIssuedQuantity); updates.add(update); } if (!updates.isEmpty()) { productionPlanMapper.updateById(updates); } } private BigDecimal resolveRemainingQuantity(ProductionPlan plan) { if (plan == null) { return BigDecimal.ZERO; } BigDecimal requiredQuantity = Optional.ofNullable(plan.getQtyRequired()).orElse(BigDecimal.ZERO); if (requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) { return BigDecimal.ZERO; } BigDecimal issuedQuantity = Optional.ofNullable(plan.getQuantityIssued()).orElse(BigDecimal.ZERO); if (issuedQuantity.compareTo(BigDecimal.ZERO) <= 0) { return requiredQuantity; } if (issuedQuantity.compareTo(requiredQuantity) >= 0) { return BigDecimal.ZERO; } return requiredQuantity.subtract(issuedQuantity); } private int resolvePlanStatus(BigDecimal requiredQuantity, BigDecimal issuedQuantity) { if (requiredQuantity == null || requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) { return 0; } if (issuedQuantity == null || issuedQuantity.compareTo(BigDecimal.ZERO) <= 0) { return 0; } return issuedQuantity.compareTo(requiredQuantity) < 0 ? 1 : 2; } private List parsePlanIds(String productionPlanIds) { if (productionPlanIds == null || productionPlanIds.trim().isEmpty()) { return new ArrayList<>(); } String normalized = productionPlanIds.replace("[", "").replace("]", "").trim(); if (normalized.isEmpty()) { return new ArrayList<>(); } return java.util.Arrays.stream(normalized.split(",")) .map(String::trim) .filter(item -> !item.isEmpty()) .map(Long::valueOf) .distinct() .collect(Collectors.toList()); } private String formatPlanIds(List planIds) { if (planIds == null || planIds.isEmpty()) { return null; } return planIds.stream() .distinct() .map(String::valueOf) .collect(Collectors.joining(",", "[", "]")); } private LocalDate resolvePlanCompleteDate(ProductionPlan productionPlan) { if (productionPlan == null) { return null; } if (productionPlan.getPromisedDeliveryDate() != null) { return productionPlan.getPromisedDeliveryDate(); } if (productionPlan.getRequiredDate() != null) { return productionPlan.getRequiredDate(); } return null; } private int compareDecimal(BigDecimal left, BigDecimal right) { return defaultDecimal(left).compareTo(defaultDecimal(right)); } private void fillProductImages(List records) { if (records == null || records.isEmpty()) { return; } List productModelIds = records.stream() .map(ProductionOrderVo::getProductModelId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); if (productModelIds.isEmpty()) { return; } List attachments = storageAttachmentMapper.selectList( Wrappers.lambdaQuery() .in(StorageAttachment::getRecordId, productModelIds) .eq(StorageAttachment::getApplication, StorageAttachmentConstants.StorageAttachmentImage) .eq(StorageAttachment::getDeleted, 0L) .orderByAsc(StorageAttachment::getId)); if (attachments == null || attachments.isEmpty()) { return; } Map> attachmentMap = attachments.stream() .collect(Collectors.groupingBy(StorageAttachment::getRecordId, java.util.LinkedHashMap::new, Collectors.toList())); List blobIds = attachments.stream() .map(StorageAttachment::getStorageBlobId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); if (blobIds.isEmpty()) { return; } Map blobMap = storageBlobMapper.selectBatchIds(blobIds).stream() .filter(Objects::nonNull) .collect(Collectors.toMap(StorageBlob::getId, item -> item)); for (ProductionOrderVo record : records) { List modelAttachments = attachmentMap.get(record.getProductModelId()); if (modelAttachments == null || modelAttachments.isEmpty()) { continue; } List images = modelAttachments.stream() .map(StorageAttachment::getStorageBlobId) .map(blobMap::get) .filter(Objects::nonNull) .map(this::toStorageBlobVO) .collect(Collectors.toList()); if (!images.isEmpty()) { record.setProductImages(images); } } } private StorageBlobVO toStorageBlobVO(StorageBlob blob) { StorageBlobVO vo = BeanUtil.copyProperties(blob, StorageBlobVO.class); vo.setPreviewURL(fileUtil.buildSignedPreviewUrl(vo)); vo.setDownloadURL(fileUtil.buildSignedDownloadUrl(vo)); return vo; } @Override public ProductionOrderWorkOrderDetailVo getWorkOrderReportInspectDetail(ProductionOrderDto dto) { Long productionOrderId = resolveProductionOrderId(dto); ProductionOrderVo orderInfo = getProductionOrderInfo(productionOrderId); if (orderInfo == null) { throw new ServiceException("生产订单不存在"); } ProductionOrderWorkOrderDetailVo detailVo = new ProductionOrderWorkOrderDetailVo(); detailVo.setProductionOrder(orderInfo); ProductionOperationTaskDto taskQuery = new ProductionOperationTaskDto(); taskQuery.setProductionOrderId(productionOrderId); IPage workOrderPage = productionOperationTaskMapper.pageProductionOperationTask( new Page(1, -1), taskQuery); List workOrderList = workOrderPage == null || workOrderPage.getRecords() == null ? Collections.emptyList() : workOrderPage.getRecords().stream() .filter(Objects::nonNull) .sorted(Comparator.comparing(ProductionOperationTaskVo::getId, Comparator.nullsLast(Comparator.naturalOrder()))) .collect(Collectors.toList()); if (workOrderList == null || workOrderList.isEmpty()) { detailVo.setWorkOrderList(Collections.emptyList()); return detailVo; } List workOrderIdList = workOrderList.stream() .map(ProductionOperationTaskVo::getId) .filter(Objects::nonNull) .collect(Collectors.toList()); List reportMainList = workOrderIdList.isEmpty() ? Collections.emptyList() : productionProductMainMapper.selectList( Wrappers.lambdaQuery() .in(ProductionProductMain::getProductionOperationTaskId, workOrderIdList) .orderByAsc(ProductionProductMain::getId)); Map> reportMainByWorkOrderMap = new LinkedHashMap<>(); for (ProductionProductMain reportMain : reportMainList) { if (reportMain == null || reportMain.getProductionOperationTaskId() == null) { continue; } reportMainByWorkOrderMap.computeIfAbsent(reportMain.getProductionOperationTaskId(), key -> new ArrayList<>()).add(reportMain); } List reportMainIdList = reportMainList.stream() .map(ProductionProductMain::getId) .filter(Objects::nonNull) .collect(Collectors.toList()); Map> reportOutputMap = new LinkedHashMap<>(); Map> reportParamMap = new LinkedHashMap<>(); Map> inspectMap = new LinkedHashMap<>(); Map> inspectParamMap = new LinkedHashMap<>(); Map> inspectFileMap = new LinkedHashMap<>(); if (!reportMainIdList.isEmpty()) { List reportOutputList = productionProductOutputMapper.selectList( Wrappers.lambdaQuery() .in(ProductionProductOutput::getProductionProductMainId, reportMainIdList) .orderByAsc(ProductionProductOutput::getId)); for (ProductionProductOutput reportOutput : reportOutputList) { if (reportOutput == null) { continue; } Long reportMainId = reportOutput.getProductionProductMainId() != null ? reportOutput.getProductionProductMainId() : reportOutput.getProductMainId(); if (reportMainId == null) { continue; } reportOutputMap.computeIfAbsent(reportMainId, k -> new ArrayList<>()).add(reportOutput); } List reportParamList = productionOrderRoutingOperationParamMapper.selectList( Wrappers.lambdaQuery() .in(ProductionOrderRoutingOperationParam::getProductionProductMainId, reportMainIdList) .orderByAsc(ProductionOrderRoutingOperationParam::getId)); for (ProductionOrderRoutingOperationParam reportParam : reportParamList) { if (reportParam == null || reportParam.getProductionProductMainId() == null) { continue; } reportParamMap.computeIfAbsent(reportParam.getProductionProductMainId(), k -> new ArrayList<>()).add(reportParam); } List inspectList = qualityInspectMapper.selectList( Wrappers.lambdaQuery() .in(QualityInspect::getProductMainId, reportMainIdList) .orderByAsc(QualityInspect::getId)); for (QualityInspect inspect : inspectList) { if (inspect == null || inspect.getProductMainId() == null) { continue; } inspectMap.computeIfAbsent(inspect.getProductMainId(), key -> new ArrayList<>()).add(inspect); } List inspectIdList = inspectList.stream() .map(QualityInspect::getId) .filter(Objects::nonNull) .collect(Collectors.toList()); if (!inspectIdList.isEmpty()) { List inspectParamList = qualityInspectParamMapper.selectList( Wrappers.lambdaQuery() .in(QualityInspectParam::getInspectId, inspectIdList) .orderByAsc(QualityInspectParam::getId)); for (QualityInspectParam inspectParam : inspectParamList) { if (inspectParam == null || inspectParam.getInspectId() == null) { continue; } inspectParamMap.computeIfAbsent(inspectParam.getInspectId(), k -> new ArrayList<>()).add(inspectParam); } List inspectFileList = qualityInspectFileMapper.selectList( Wrappers.lambdaQuery() .in(QualityInspectFile::getInspectId, inspectIdList) .orderByAsc(QualityInspectFile::getId)); for (QualityInspectFile inspectFile : inspectFileList) { if (inspectFile == null || inspectFile.getInspectId() == null) { continue; } inspectFileMap.computeIfAbsent(inspectFile.getInspectId(), k -> new ArrayList<>()).add(inspectFile); } } } List workOrderDetailList = new ArrayList<>(); for (ProductionOperationTaskVo workOrder : workOrderList) { ProductionOrderWorkOrderDetailVo.WorkOrderDetail workOrderDetail = new ProductionOrderWorkOrderDetailVo.WorkOrderDetail(); workOrderDetail.setWorkOrder(workOrder); List workOrderReportMainList = reportMainByWorkOrderMap.getOrDefault(workOrder.getId(), Collections.emptyList()); if (workOrderReportMainList.isEmpty()) { workOrderDetail.setReportList(Collections.emptyList()); workOrderDetail.setInspectList(Collections.emptyList()); workOrderDetailList.add(workOrderDetail); continue; } List reportDetailList = new ArrayList<>(); List inspectDetailList = new ArrayList<>(); for (ProductionProductMain reportMain : workOrderReportMainList) { Long reportMainId = reportMain.getId(); ProductionOrderWorkOrderDetailVo.ReportDetail reportDetail = new ProductionOrderWorkOrderDetailVo.ReportDetail(); reportDetail.setReportMain(reportMain); reportDetail.setReportOutputList(reportOutputMap.getOrDefault(reportMainId, Collections.emptyList())); reportDetail.setReportParamList(reportParamMap.getOrDefault(reportMainId, Collections.emptyList())); reportDetailList.add(reportDetail); List reportInspectList = inspectMap.getOrDefault(reportMainId, Collections.emptyList()); for (QualityInspect inspect : reportInspectList) { ProductionOrderWorkOrderDetailVo.InspectDetail inspectDetail = new ProductionOrderWorkOrderDetailVo.InspectDetail(); inspectDetail.setReportId(reportMainId); inspectDetail.setReportNo(reportMain.getProductNo()); inspectDetail.setReportMain(reportMain); inspectDetail.setInspect(inspect); inspectDetail.setInspectParamList(inspectParamMap.getOrDefault(inspect.getId(), Collections.emptyList())); inspectDetail.setInspectFileList(inspectFileMap.getOrDefault(inspect.getId(), Collections.emptyList())); inspectDetailList.add(inspectDetail); } } workOrderDetail.setReportList(reportDetailList); workOrderDetail.setInspectList(inspectDetailList); workOrderDetailList.add(workOrderDetail); } detailVo.setWorkOrderList(workOrderDetailList); return detailVo; } private Long resolveProductionOrderId(ProductionOrderDto dto) { if (dto == null) { throw new ServiceException("请传入生产订单ID或生产订单号"); } if (dto.getId() != null) { return dto.getId(); } if (dto.getNpsNo() == null || dto.getNpsNo().trim().isEmpty()) { throw new ServiceException("请传入生产订单ID或生产订单号"); } ProductionOrder productionOrder = baseMapper.selectOne( Wrappers.lambdaQuery() .eq(ProductionOrder::getNpsNo, dto.getNpsNo().trim()) .last("limit 1")); if (productionOrder == null || productionOrder.getId() == null) { throw new ServiceException("生产订单不存在"); } return productionOrder.getId(); } @Override public List pick(Long productionOrderId) { if (productionOrderId == null) { return Collections.emptyList(); } ProductionOrderBom orderBom = productionOrderBomMapper.selectOne( Wrappers.lambdaQuery() .eq(ProductionOrderBom::getProductionOrderId, productionOrderId) .orderByDesc(ProductionOrderBom::getId) .last("limit 1")); if (orderBom == null || orderBom.getId() == null) { return Collections.emptyList(); } List bomStructureList = productionBomStructureMapper.pickByBomId(orderBom.getId()); if (bomStructureList == null || bomStructureList.isEmpty()) { return Collections.emptyList(); } List productModelIds = bomStructureList.stream() .map(ProductionBomStructureVo::getProductModelId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); Map stockQuantityMap = new HashMap<>(); Map> stockBatchNoMap = new HashMap<>(); if (!productModelIds.isEmpty()) { List stockList = stockInventoryMapper.selectList( Wrappers.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 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 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 public int updateOrder(ProductionOrderDto productionOrderDto) { productionOrderDto.setStatus(5); return baseMapper.updateById(productionOrderDto); } }