15 小时以前 641206421e76861db238ecf24094b2a04e7c6371
src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java
@@ -61,9 +61,8 @@
     * 合并生产计划并下发生产订单。
     * 约束:
     * 1. 仅允许同一产品型号的计划合并;
     * 2. 已下发或部分下发的计划不允许再次合并;
     * 3. 下发数量不能大于所选计划剩余需求总量;
     * 4. 下发时统一调用 ProductionOrderService.saveProductionOrder,确保后续工艺/BOM/领料逻辑一致。
     * 2. 下发数量只受输入数量和基础数据校验约束;
     * 3. 下发时统一调用 ProductionOrderService.saveProductionOrder,确保后续工艺/BOM/领料逻辑一致。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -101,49 +100,18 @@
            throw new BaseException("合并失败,所选生产计划的产品型号不一致");
        }
        // 仅“已下发”计划不允许再次参与合并下发;
        // “待下发/部分下发”允许继续下发剩余数量。
        boolean hasFullyIssuedPlan = planLists.stream()
                .anyMatch(item -> item.getStatus() != null
                        && item.getStatus() == PLAN_STATUS_ISSUED);
        if (hasFullyIssuedPlan) {
            throw new BaseException("合并失败,所选生产计划存在已下发的数据");
        }
        // 计算本次可下发的剩余需求总量
        BigDecimal totalRequiredQuantity = planLists.stream()
                .map(this::resolveRemainingQuantity)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        if (totalRequiredQuantity.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("下发失败,所选生产计划剩余需求总量必须大于0");
        }
        // 校验下发数量
        BigDecimal assignedQuantity = productionPlanDto.getTotalAssignedQuantity();
        if (assignedQuantity == null || assignedQuantity.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("下发失败,下发数量必须大于0");
        }
        if (assignedQuantity.compareTo(totalRequiredQuantity) > 0) {
            throw new ServiceException("下发失败,下发数量不能大于计划剩余需求总量");
        }
        // 按计划顺序分摊下发数量,收集实际参与下发的计划 ID
        BigDecimal remainingForOrderBind = assignedQuantity;
        List<Long> issuedPlanIds = new ArrayList<>();
        for (ProductionPlanDto plan : planLists) {
            BigDecimal remainingQuantity = resolveRemainingQuantity(plan);
            if (remainingForOrderBind.compareTo(BigDecimal.ZERO) <= 0 || remainingQuantity.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            BigDecimal issueForThisPlan = remainingForOrderBind.min(remainingQuantity);
            remainingForOrderBind = remainingForOrderBind.subtract(issueForThisPlan);
            if (issueForThisPlan.compareTo(BigDecimal.ZERO) > 0) {
                issuedPlanIds.add(plan.getId());
            }
        }
        if (issuedPlanIds.isEmpty()) {
            throw new ServiceException("下发失败,无可下发数量");
        }
        // 本次下发数量不再按需求量限制,所有选中计划共同作为订单来源
        List<Long> issuedPlanIds = planLists.stream()
                .map(ProductionPlanDto::getId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        // 生成生产订单主单,并绑定本次下发关联的计划
        ProductionOrder productionOrder = new ProductionOrder();
@@ -155,24 +123,18 @@
            throw new ServiceException("下发失败,生产订单保存失败");
        }
        // 回写每条计划的累计下发量和状态
        // 回写每条计划的累计下发量和状态,允许累计下发量超过需求量
        BigDecimal remainingAssignedQuantity = assignedQuantity;
        List<ProductionPlan> updates = new ArrayList<>();
        for (ProductionPlanDto plan : planLists) {
            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 = BigDecimal.ZERO;
            if (remainingAssignedQuantity.compareTo(BigDecimal.ZERO) > 0 && remainingQuantity.compareTo(BigDecimal.ZERO) > 0) {
                issuedQuantity = remainingAssignedQuantity.min(remainingQuantity);
            BigDecimal historicalIssuedQuantity = Optional.ofNullable(plan.getQuantityIssued()).orElse(BigDecimal.ZERO);
            BigDecimal issuedQuantity = remainingAssignedQuantity.min(assignedQuantity);
                remainingAssignedQuantity = remainingAssignedQuantity.subtract(issuedQuantity);
            }
            BigDecimal totalIssuedQuantity = historicalIssuedQuantity.add(issuedQuantity);
            int planStatus = resolvePlanStatus(requiredQuantity, totalIssuedQuantity);
            int planStatus = plan.getStatus() != null && plan.getStatus() == PLAN_STATUS_ISSUED
                    ? PLAN_STATUS_ISSUED
                    : resolvePlanStatus(plan.getQtyRequired(), totalIssuedQuantity);
            ProductionPlan update = new ProductionPlan();
            update.setId(plan.getId());
            update.setStatus(planStatus);
@@ -455,32 +417,6 @@
            }
        }
        return sequence;
    }
    /**
     * 计算生产计划的剩余未下发数量(需求量 - 已下发量,最小为 0)。
     */
    private BigDecimal resolveRemainingQuantity(ProductionPlan plan) {
        // 空对象按 0 处理
        if (plan == null) {
            return BigDecimal.ZERO;
        }
        // 需求量为空或小于等于 0,视为无剩余
        BigDecimal requiredQuantity = Optional.ofNullable(plan.getQtyRequired()).orElse(BigDecimal.ZERO);
        if (requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        // 已下发量为空或小于等于 0,剩余即需求量
        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);
    }
    /**