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.ProductionOrderDto; import com.ruoyi.production.bean.vo.ProductionOrderVo; import com.ruoyi.production.enums.ProductOrderStatusEnum; import com.ruoyi.production.mapper.*; import com.ruoyi.production.pojo.*; import com.ruoyi.production.service.ProductionOrderService; import com.ruoyi.sales.mapper.SalesLedgerMapper; import com.ruoyi.sales.mapper.SalesLedgerProductMapper; import com.ruoyi.sales.pojo.SalesLedger; import com.ruoyi.sales.pojo.SalesLedgerProduct; 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 ProductionOrderPickMapper productionOrderPickMapper; private final ProductionOrderPickRecordMapper productionOrderPickRecordMapper; private final ProductionPlanMapper productionPlanMapper; 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 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; } syncProductionPlanIssueStatus(oldOrder, productionOrder); 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()); } else { // 未重建快照时,也要确保备料主单和订单数量保持同步。 upsertOrderPick(productionOrder); } 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 int syncProductionOrderSnapshot(Long productionOrderId) { ProductionOrder productionOrder = this.getById(productionOrderId); if (productionOrder == null) { throw new ServiceException("Production order not found"); } if (productionOrder.getTechnologyRoutingId() == null) { throw new ServiceException("technologyRoutingId is required"); } TechnologyRouting technologyRouting = technologyRoutingMapper.selectById(productionOrder.getTechnologyRoutingId()); if (technologyRouting == null) { throw new ServiceException("Technology routing not found"); } // 订单快照按“先清后建”处理,保证工艺路线、工序、参数、BOM 全部来自同一版本。 clearProductionSnapshot(productionOrderId); 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()); productionOrderRoutingMapper.insert(orderRouting); int syncedParamCount = 0; List routingOperations = technologyRoutingOperationMapper.selectList( Wrappers.lambdaQuery() .eq(TechnologyRoutingOperation::getTechnologyRoutingId, technologyRouting.getId()) .orderByAsc(TechnologyRoutingOperation::getDragSort) .orderByAsc(TechnologyRoutingOperation::getId)); for (TechnologyRoutingOperation sourceOperation : routingOperations) { // 订单工序保存的是工艺工序快照,后续报工只依赖快照,不再直接引用工艺主数据。 ProductionOrderRoutingOperation targetOperation = new ProductionOrderRoutingOperation(); targetOperation.setProductionOrderId(productionOrder.getId()); targetOperation.setTechnologyRoutingOperationId(sourceOperation.getId()); targetOperation.setTechnologyRoutingId(orderRouting.getId()); targetOperation.setProductModelId(sourceOperation.getProductModelId()); targetOperation.setDragSort(sourceOperation.getDragSort()); targetOperation.setIsQuality(sourceOperation.getIsQuality()); productionOrderRoutingOperationMapper.insert(targetOperation); ProductionOperationTask task = new ProductionOperationTask(); task.setTechnologyRoutingOperationId(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.setTechnologyRoutingOperationId(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++; } } syncProductionOrderBomSnapshot(productionOrder, technologyRouting); upsertOrderPick(productionOrder); return syncedParamCount; } private void syncProductionOrderBomSnapshot(ProductionOrder productionOrder, TechnologyRouting technologyRouting) { if (technologyRouting.getBomId() == null) { return; } TechnologyBom technologyBom = technologyBomMapper.selectById(technologyRouting.getBomId()); if (technologyBom == null) { throw new ServiceException("Technology BOM not found"); } 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.setTechnologyOperationId(root == null ? null : root.getOperationId()); orderBom.setUnitQuantity(root != null && root.getUnitQuantity() != null ? root.getUnitQuantity() : BigDecimal.ONE); orderBom.setDemandedQuantity(orderQuantity); orderBom.setUnit(root == null ? null : root.getUnit()); 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(resolveBomDemandQuantity(source, orderQuantity)); target.setUnit(source.getUnit()); productionBomStructureMapper.insert(target); idMap.put(source.getId(), target.getId()); } } private void clearProductionSnapshot(Long productionOrderId) { // 已产生领料记录后禁止重建,避免备料/投料依据与订单快照脱节。 boolean hasPickRecord = productionOrderPickRecordMapper.selectCount( Wrappers.lambdaQuery() .eq(ProductionOrderPickRecord::getProductionOrderId, productionOrderId)) > 0; if (hasPickRecord) { throw new ServiceException("Production order pick records already exist, snapshot cannot be regenerated"); } 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("Production order already started, snapshot cannot be regenerated"); } 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.getSalesLedgerId() != null, ProductionOrder::getSalesLedgerId, query.getSalesLedgerId()) .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("Production order is required"); } fillFromSalesLedgerProduct(productionOrder); fillFromProductionPlans(productionOrder); if (productionOrder.getProductModelId() == null) { throw new ServiceException("productModelId is required"); } if (defaultDecimal(productionOrder.getQuantity()).compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException("quantity must be greater than 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) { // throw new ServiceException("No technology routing found for the product model"); // } // 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("Started production orders cannot modify product, routing or quantity"); } } } private void fillFromSalesLedgerProduct(ProductionOrder productionOrder) { if (productionOrder.getSaleLedgerProductId() == null) { return; } // 销售明细是订单来源时,以销售明细为准回填销售台账、产品规格和默认数量。 SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(productionOrder.getSaleLedgerProductId().longValue()); if (salesLedgerProduct == null) { throw new ServiceException("Sales ledger product not found"); } if (productionOrder.getSalesLedgerId() == null) { productionOrder.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId()); } else if (!Objects.equals(productionOrder.getSalesLedgerId(), salesLedgerProduct.getSalesLedgerId())) { throw new ServiceException("salesLedgerId does not match the sales ledger product"); } if (productionOrder.getProductModelId() == null) { productionOrder.setProductModelId(salesLedgerProduct.getProductModelId()); } else if (!Objects.equals(productionOrder.getProductModelId(), salesLedgerProduct.getProductModelId())) { throw new ServiceException("productModelId does not match the sales ledger product"); } if (productionOrder.getQuantity() == null || productionOrder.getQuantity().compareTo(BigDecimal.ZERO) <= 0) { productionOrder.setQuantity(salesLedgerProduct.getQuantity()); } if (productionOrder.getPlanCompleteTime() == null && productionOrder.getSalesLedgerId() != null) { SalesLedger salesLedger = salesLedgerMapper.selectById(productionOrder.getSalesLedgerId()); if (salesLedger != null && salesLedger.getDeliveryDate() != null) { productionOrder.setPlanCompleteTime(salesLedger.getDeliveryDate()); } } } 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("Some production plans do not exist"); } Set productModelIds = productionPlans.stream() .map(ProductionPlan::getProductModelId) .collect(Collectors.toSet()); if (productModelIds.size() > 1) { throw new ServiceException("Selected production plans must belong to the same product model"); } if (Boolean.TRUE.equals(productionPlans.stream().anyMatch(item -> Boolean.TRUE.equals(item.getIssued())))) { throw new ServiceException("Selected production plans already issued"); } ProductionPlan firstPlan = productionPlans.get(0); if (productionOrder.getProductModelId() == null) { productionOrder.setProductModelId(firstPlan.getProductModelId()); } else if (!Objects.equals(productionOrder.getProductModelId(), firstPlan.getProductModelId())) { throw new ServiceException("productModelId does not match the production plans"); } 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 syncProductionPlanIssueStatus(ProductionOrder oldOrder, ProductionOrder newOrder) { // 只处理本次增量变化,避免无关计划被重复写状态。 Set oldIds = new LinkedHashSet<>(parsePlanIds(oldOrder == null ? null : oldOrder.getProductionPlanIds())); Set newIds = new LinkedHashSet<>(parsePlanIds(newOrder == null ? null : newOrder.getProductionPlanIds())); Set toRelease = new LinkedHashSet<>(oldIds); toRelease.removeAll(newIds); Set toIssue = new LinkedHashSet<>(newIds); toIssue.removeAll(oldIds); if (!toRelease.isEmpty()) { updatePlanIssuedFlag(new ArrayList<>(toRelease), false); } if (!toIssue.isEmpty()) { updatePlanIssuedFlag(new ArrayList<>(toIssue), true); } } private void releaseProductionPlanIssueStatus(ProductionOrder productionOrder) { if (productionOrder == null) { return; } List planIds = parsePlanIds(productionOrder.getProductionPlanIds()); if (!planIds.isEmpty()) { updatePlanIssuedFlag(planIds, false); } } private void updatePlanIssuedFlag(List planIds, boolean issued) { if (planIds == null || planIds.isEmpty()) { return; } List plans = productionPlanMapper.selectBatchIds(planIds); for (ProductionPlan plan : plans) { ProductionPlan update = new ProductionPlan(); update.setId(plan.getId()); update.setIssued(issued); productionPlanMapper.updateById(update); } } private void upsertOrderPick(ProductionOrder productionOrder) { if (productionOrder == null || productionOrder.getId() == null) { return; } // 订单下达后自动生成一张备料主单,后续领料记录都挂在这张单上。 ProductionOrderPick orderPick = productionOrderPickMapper.selectOne( Wrappers.lambdaQuery() .eq(ProductionOrderPick::getProductionOrderId, productionOrder.getId()) .last("limit 1")); if (orderPick == null) { orderPick = new ProductionOrderPick(); orderPick.setProductionOrderId(productionOrder.getId()); } orderPick.setProductModelId(productionOrder.getProductModelId() == null ? null : Math.toIntExact(productionOrder.getProductModelId())); orderPick.setQuantity(defaultDecimal(productionOrder.getQuantity())); orderPick.setRemark("下单自动生成"); if (orderPick.getId() == null) { productionOrderPickMapper.insert(orderPick); } else { productionOrderPickMapper.updateById(orderPick); } } private BigDecimal resolveBomDemandQuantity(TechnologyBomStructure source, BigDecimal orderQuantity) { // 工艺 BOM 中的需求量按“单件需求 * 订单数量”展开成订单级需求。 BigDecimal baseQuantity = source.getDemandedQuantity() != null ? source.getDemandedQuantity() : source.getUnitQuantity(); baseQuantity = baseQuantity == null ? BigDecimal.ZERO : baseQuantity; if (baseQuantity.compareTo(BigDecimal.ZERO) <= 0) { return BigDecimal.ZERO; } return baseQuantity.multiply(orderQuantity); } 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; } }