huminmin
10 天以前 0268f58a18bfcf061389387ef5322bf11aece154
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -15,15 +15,18 @@
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.ProductionBomStructureVo;
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.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.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.pojo.StockInventory;
import com.ruoyi.technology.mapper.*;
import com.ruoyi.technology.pojo.*;
import lombok.RequiredArgsConstructor;
@@ -51,6 +54,7 @@
    private final ProductionOrderPickMapper productionOrderPickMapper;
    private final ProductionOrderPickRecordMapper productionOrderPickRecordMapper;
    private final ProductionPlanMapper productionPlanMapper;
    private final StockInventoryMapper stockInventoryMapper;
    private final StorageAttachmentMapper storageAttachmentMapper;
    private final StorageBlobMapper storageBlobMapper;
    private final SalesLedgerMapper salesLedgerMapper;
@@ -105,7 +109,6 @@
        if (!saved) {
            return false;
        }
        syncProductionPlanIssueStatus(oldOrder, productionOrder);
        boolean needSync = productionOrder.getTechnologyRoutingId() != null
                && (oldOrder == null
                || !Objects.equals(oldOrder.getTechnologyRoutingId(), productionOrder.getTechnologyRoutingId())
@@ -114,11 +117,7 @@
                || productionOrderRoutingMapper.selectCount(Wrappers.<ProductionOrderRouting>lambdaQuery()
                        .eq(ProductionOrderRouting::getProductionOrderId, productionOrder.getId())) == 0);
        if (needSync) {
            // 工艺、产品或数量变化后,订单快照必须和当前下单数据重新对齐。
            syncProductionOrderSnapshot(productionOrder.getId());
        } else {
            // 未重建快照时,也要确保备料主单和订单数量保持同步。
            upsertOrderPick(productionOrder);
        }
        return true;
    }
@@ -180,6 +179,16 @@
    }
    @Override
    public List<ProductionPlanVo> getSource(Long id) {
        ProductionOrder productionOrder = baseMapper.selectById(id);
        if (productionOrder != null && productionOrder.getProductionPlanIds() != null) {
            List<Long> planIds = parsePlanIds(productionOrder.getProductionPlanIds());
            return productionPlanMapper.getSource(planIds);
        }
        return null;
    }
    @Override
    public int syncProductionOrderSnapshot(Long productionOrderId) {
        ProductionOrder productionOrder = this.getById(productionOrderId);
        if (productionOrder == null) {
@@ -224,11 +233,12 @@
            ProductionOrderRoutingOperation targetOperation = new ProductionOrderRoutingOperation();
            targetOperation.setProductionOrderId(productionOrder.getId());
            targetOperation.setTechnologyRoutingOperationId(sourceOperation.getId());
            targetOperation.setTechnologyRoutingId(orderRouting.getId());
            targetOperation.setOrderRoutingId(orderRouting.getId());
            targetOperation.setProductModelId(sourceOperation.getProductModelId());
            targetOperation.setDragSort(sourceOperation.getDragSort());
            targetOperation.setIsQuality(sourceOperation.getIsQuality());
            targetOperation.setOperationName(operationNameMap.get(sourceOperation.getTechnologyOperationId()));
            targetOperation.setTechnologyOperationId(sourceOperation.getTechnologyOperationId());
            productionOrderRoutingOperationMapper.insert(targetOperation);
            ProductionOperationTask task = new ProductionOperationTask();
@@ -248,7 +258,7 @@
                // 工序执行参数同样做快照,避免工艺参数调整影响已下达订单。
                ProductionOrderRoutingOperationParam targetParam = new ProductionOrderRoutingOperationParam();
                targetParam.setProductionOrderId(productionOrder.getId());
                targetParam.setTechnologyRoutingOperationId(targetOperation.getId());
                targetParam.setProductionOrderRoutingOperationId(targetOperation.getId());
                targetParam.setTechnologyRoutingOperationParamId(sourceParam.getId());
                targetParam.setParamId(sourceParam.getParamId());
                targetParam.setTechnologyOperationId(sourceParam.getTechnologyOperationId());
@@ -265,8 +275,6 @@
                syncedParamCount++;
            }
        }
        upsertOrderPick(productionOrder);
        return syncedParamCount;
    }
@@ -289,10 +297,9 @@
        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());
        orderBom.setRemark(technologyBom.getRemark());
        orderBom.setBomNo(technologyBom.getBomNo());
        orderBom.setVersion(technologyBom.getVersion());
        productionOrderBomMapper.insert(orderBom);
        Map<Long, Long> idMap = new HashMap<>();
@@ -305,7 +312,7 @@
            target.setProductModelId(source.getProductModelId());
            target.setTechnologyOperationId(source.getOperationId());
            target.setUnitQuantity(source.getUnitQuantity());
            target.setDemandedQuantity(resolveBomDemandQuantity(source, orderQuantity));
            target.setDemandedQuantity(source.getUnitQuantity().multiply(orderQuantity));
            target.setUnit(source.getUnit());
            productionBomStructureMapper.insert(target);
            idMap.put(source.getId(), target.getId());
@@ -354,7 +361,6 @@
        ProductionOrder query = dto == null ? new ProductionOrder() : dto;
        return Wrappers.<ProductionOrder>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())
@@ -406,7 +412,6 @@
        if (productionOrder == null) {
            throw new ServiceException("生产订单不能为空");
        }
        fillFromSalesLedgerProduct(productionOrder);
        fillFromProductionPlans(productionOrder);
        if (productionOrder.getProductModelId() == null) {
            throw new ServiceException("产品规格ID不能为空");
@@ -414,54 +419,23 @@
        if (defaultDecimal(productionOrder.getQuantity()).compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("下单数量必须大于0");
        }
//        if (productionOrder.getTechnologyRoutingId() == null) {
//            // 未显式指定工艺路线时,按产品规格选最新一条工艺作为默认路线。
//            TechnologyRouting technologyRouting = technologyRoutingMapper.selectOne(
//                    Wrappers.<TechnologyRouting>lambdaQuery()
//                            .eq(TechnologyRouting::getProductModelId, productionOrder.getProductModelId())
//                            .orderByDesc(TechnologyRouting::getId)
//                            .last("limit 1"));
//            if (technologyRouting == null) {
//                throw new ServiceException("未找到该产品规格对应的工艺路线");
//            }
//            productionOrder.setTechnologyRoutingId(technologyRouting.getId());
//        }
        if (productionOrder.getTechnologyRoutingId() == null) {
            // 未显式指定工艺路线时,按产品规格选最新一条工艺作为默认路线。
            TechnologyRouting technologyRouting = technologyRoutingMapper.selectOne(
                    Wrappers.<TechnologyRouting>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 fillFromSalesLedgerProduct(ProductionOrder productionOrder) {
        if (productionOrder.getSalesLedgerProductId() == null) {
            return;
        }
        // 销售明细是订单来源时,以销售明细为准回填销售台账、产品规格和默认数量。
        SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(productionOrder.getSalesLedgerProductId().longValue());
        if (salesLedgerProduct == null) {
            throw new ServiceException("销售台账产品不存在");
        }
        if (productionOrder.getSalesLedgerId() == null) {
            productionOrder.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId());
        } else if (!Objects.equals(productionOrder.getSalesLedgerId(), salesLedgerProduct.getSalesLedgerId())) {
            throw new ServiceException("销售台账ID与销售台账产品不一致");
        }
        if (productionOrder.getProductModelId() == null) {
            productionOrder.setProductModelId(salesLedgerProduct.getProductModelId());
        } else if (!Objects.equals(productionOrder.getProductModelId(), salesLedgerProduct.getProductModelId())) {
            throw new ServiceException("产品规格ID与销售台账产品不一致");
        }
        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());
            }
        }
    }
@@ -507,76 +481,73 @@
        productionOrder.setProductionPlanIds(formatPlanIds(planIds));
    }
    private void syncProductionPlanIssueStatus(ProductionOrder oldOrder, ProductionOrder newOrder) {
        // 只处理本次增量变化,避免无关计划被重复写状态。
        Set<Long> oldIds = new LinkedHashSet<>(parsePlanIds(oldOrder == null ? null : oldOrder.getProductionPlanIds()));
        Set<Long> newIds = new LinkedHashSet<>(parsePlanIds(newOrder == null ? null : newOrder.getProductionPlanIds()));
        Set<Long> toRelease = new LinkedHashSet<>(oldIds);
        toRelease.removeAll(newIds);
        Set<Long> 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<Long> planIds = parsePlanIds(productionOrder.getProductionPlanIds());
        if (!planIds.isEmpty()) {
            updatePlanIssuedFlag(planIds, false);
            // 生产订单删除--对应的生产计划的已下发数量要减去
            updatePlanIssuedFlag(planIds, productionOrder.getQuantity());
        }
    }
    private void updatePlanIssuedFlag(List<Long> planIds, boolean issued) {
    //生产订单删除,生产计划的已下发数量对应变更
    private void updatePlanIssuedFlag(List<Long> planIds, BigDecimal remainingAssignedQuantity) {
        if (planIds == null || planIds.isEmpty()) {
            return;
        }
        List<ProductionPlan> plans = productionPlanMapper.selectBatchIds(planIds);
        //下发数量减去
        List<ProductionPlan> 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.setIssued(issued);
            productionPlanMapper.updateById(update);
            update.setStatus(planStatus);
            update.setQuantityIssued(totalIssuedQuantity);
            updates.add(update);
        }
        if (!updates.isEmpty()) {
            productionPlanMapper.updateById(updates);
        }
    }
    private void upsertOrderPick(ProductionOrder productionOrder) {
        if (productionOrder == null || productionOrder.getId() == null) {
            return;
        }
        // 订单下达后自动生成一张备料主单,后续领料记录都挂在这张单上。
        ProductionOrderPick orderPick = productionOrderPickMapper.selectOne(
                Wrappers.<ProductionOrderPick>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) {
    private BigDecimal resolveRemainingQuantity(ProductionPlan plan) {
        if (plan == null) {
            return BigDecimal.ZERO;
        }
        return baseQuantity.multiply(orderQuantity);
        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<Long> parsePlanIds(String productionPlanIds) {
@@ -682,4 +653,73 @@
        vo.setDownloadURL(fileUtil.buildSignedDownloadUrl(vo));
        return vo;
    }
    @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);
                }
            }
        }
        List<ProductionOrderPickVo> result = new ArrayList<>(bomStructureList.size());
        for (ProductionBomStructureVo structure : bomStructureList) {
            if (structure == null || structure.getProductModelId() == null) {
                continue;
            }
            Long productModelId = structure.getProductModelId();
            ProductionOrderPickVo vo = new ProductionOrderPickVo();
            vo.setProductModelId(productModelId);
            vo.setOperationName(structure.getOperationName());
            vo.setTechnologyOperationId(structure.getTechnologyOperationId());
            vo.setProductName(structure.getProductName());
            vo.setModel(structure.getModel());
            vo.setDemandedQuantity(defaultDecimal(structure.getDemandedQuantity()));
            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);
            result.add(vo);
        }
        return result;
    }
}