package com.aps.service.mp;

import com.google.ortools.Loader;
import com.google.ortools.linearsolver.MPConstraint;
import com.google.ortools.linearsolver.MPObjective;
import com.google.ortools.linearsolver.MPSolver;
import com.google.ortools.linearsolver.MPVariable;

import java.util.*;

/**
 * 多阶BOM + MRP 逻辑 MIP 排产（通用网状BOM架构）
 *
 * 架构说明：
 *   - 基于节点的网状BOM结构，支持任意层级深度
 *   - 产品可直接消耗半成品和原材料（混合BOM）
 *   - 每个产品节点可同时具备：生产、采购、外部需求、BOM子项
 *
 * 核心公式（库存平衡）：
 *   上期库存 + Σ(各产线产量) + 采购到货 + shortfall
 *   = 外部需求 + Σ(父节点产量 × BOM系数) + 期末库存
 *
 * 节点类型：
 *   - 成品(hasExternalDemand=true)：有外部需求 + shortfall
 *   - 中间品(hasExternalDemand=false)：由父节点需求推导
 *   - 采购品(isPurchased=true)：用采购变量而非生产变量
 */
public class BomMpsScheduling1 {

    // ========== 内部数据类 ==========

    /**
     * BOM子项：父节点消耗子节点的记录
     */
    static class BomChild {
        ProductNode child;      // 子节点引用
        double coefficient;     // 消耗系数（1件父产品消耗多少件子产品）

        BomChild(ProductNode child, double coefficient) {
            this.child = child;
            this.coefficient = coefficient;
        }
    }

    /**
     * BOM父引用：子节点被哪个父节点消耗
     */
    static class BomParentRef {
        ProductNode parent;     // 父节点引用
        double coefficient;     // 消耗系数

        BomParentRef(ProductNode parent, double coefficient) {
            this.parent = parent;
            this.coefficient = coefficient;
        }
    }

    /**
     * 产品节点：通用产品定义
     *
     * 变量创建规则：
     *   - 生产型：production[line][day], switchVar[line][day]
     *   - 采购型：purchase[day]
     *   - 所有节点：inventory[day]
     *   - 有外部需求：shortfall[day]
     */
    static class ProductNode {
        String name;
        double prodCost;             // 单位生产成本（或采购成本）
        double setupCost;            // 换型成本
        double holdCost;             // 库存持有成本
        double initInv;              // 初始库存
        double safetyStock;          // 安全库存（软约束，低于此值有惩罚）
        double minStock;             // 最小库存（硬约束，不得低于此值）
        double maxStock;             // 最大库存（硬约束，不得高于此值）
        double overstockPenalty;     // 超库存惩罚成本（超过maxStock时惩罚）
        double shortfallPenalty;     // 缺口惩罚（仅外部需求节点）
        double[] prodRateByLine;   // 按产线生产效率 [line]（件/小时）
        boolean[] canProduceOnLine;  // 产线兼容性
        double[] lineCapacity;       // 产线日产能
        String[] lineNames;          // 产线名称

        // 生产批量配置
        double minLotSize;           // 最小生产批量
        double lotMultiple;          // 批量步长（Lot Multiple），0表示不启用
        boolean enableLotMultiple;   // 是否启用批量步长约束

        boolean isPurchased;         // 是否为外购品
        boolean hasExternalDemand;   // 是否有外部需求
        double[] demand;             // 外部需求 [day]（仅 hasExternalDemand=true）

        // 多供应商配置（仅采购品使用）
        List<Supplier> suppliers = new ArrayList<>();

        List<BomChild> bomChildren = new ArrayList<>();     // 我消耗谁
        List<BomParentRef> bomParents = new ArrayList<>(); // 谁消耗我

        // 求解变量
        MPVariable[][] inventory;
        MPVariable[][] production;
        MPVariable[][] switchVar;
        MPVariable[][][] purchase;   // [supplier][0][day] 多供应商采购
        MPVariable[][] purchaseSwitch; // [supplier][day] 采购开关变量（用于最小采购批量）
        MPVariable[][][] lotMultipleVar; // [line][day] 批量步长整数变量
        MPVariable[][][] supplierLotMultipleVar; // [supplier][0][day] 供应商批量步长整数变量
        MPVariable[][] shortfall;
        MPVariable[][] underSafety;    // 低于安全库存的量（惩罚用）
        MPVariable[][] overMaxStock;   // 超过最大库存的量（惩罚用）

        boolean variablesCreated = false;

        ProductNode(String name) {
            this.name = name;
        }

        int lineCount() { return lineNames != null ? lineNames.length : 0; }
        boolean canProduce(int line) { return canProduceOnLine != null && canProduceOnLine[line]; }
        boolean isProduced() { return !isPurchased; }
        boolean isRoot() { return hasExternalDemand; }
        int supplierCount() { return suppliers.size(); }
    }

    /**
     * 供应商配置（用于采购品多供应商场景）
     */
    static class Supplier {
        String name;                 // 供应商名称
        double purchaseCost;         // 采购单价
        int leadTime;                // 采购提前期（天）
        double maxSupplyPerDay;      // 每日最大供应能力
        double minPurchaseLot;       // 最小采购批量
        double lotMultiple;          // 采购批量步长
        boolean enableLotMultiple;  // 是否启用批量步长

        Supplier(String name) {
            this.name = name;
        }
    }

    // ========== 通用方法 ==========

    /**
     * 递归创建所有节点的变量
     */
    static void createAllVariables(MPSolver solver, List<ProductNode> nodes, int numDays) {
        for (ProductNode node : nodes) {
            createNodeVariables(solver, node, numDays);
        }
    }

    /**
     * 创建单个节点的变量
     */
    static void createNodeVariables(MPSolver solver, ProductNode node, int numDays) {
        if (node.variablesCreated) return;
        node.variablesCreated = true;

        int numLines = node.lineCount();

        // 所有节点都有库存变量
        node.inventory = new MPVariable[1][numDays];
        for (int t = 0; t < numDays; t++) {
            node.inventory[0][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                    "inv_" + node.name + "_d" + (t + 1));
        }

        if (node.isPurchased) {
            // 采购品：多供应商采购变量 [supplier][0][day]
            int numSuppliers = node.supplierCount();
            if (numSuppliers > 0) {
                node.purchase = new MPVariable[numSuppliers][1][numDays];
                node.purchaseSwitch = new MPVariable[numSuppliers][numDays];

                // 检查是否有供应商启用了批量步长
                boolean hasLotMultipleSupplier = false;
                for (Supplier s : node.suppliers) {
                    if (s.enableLotMultiple && s.lotMultiple > 0) {
                        hasLotMultipleSupplier = true;
                        break;
                    }
                }
                if (hasLotMultipleSupplier) {
                    node.supplierLotMultipleVar = new MPVariable[numSuppliers][1][numDays];
                }

                for (int s = 0; s < numSuppliers; s++) {
                    Supplier supplier = node.suppliers.get(s);
                    boolean needSwitch = supplier.minPurchaseLot > 0;
                    boolean needLotMult = supplier.enableLotMultiple && supplier.lotMultiple > 0;
                    for (int t = 0; t < numDays; t++) {
                        node.purchase[s][0][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                                "pr_" + node.name + "_" + supplier.name + "_d" + (t + 1));
                        if (needSwitch) {
                            node.purchaseSwitch[s][t] = solver.makeBoolVar(
                                    "prSw_" + node.name + "_" + supplier.name + "_d" + (t + 1));
                        }
                        if (needLotMult && node.supplierLotMultipleVar != null) {
                            node.supplierLotMultipleVar[s][0][t] = solver.makeIntVar(0,
                                    Double.POSITIVE_INFINITY,
                                    "suppLotK_" + node.name + "_" + supplier.name + "_d" + (t + 1));
                        }
                    }
                }
            } else {
                // 无供应商配置，使用默认单一采购变量（向后兼容）
                node.purchase = new MPVariable[1][1][numDays];
                for (int t = 0; t < numDays; t++) {
                    node.purchase[0][0][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                            "pr_" + node.name + "_d" + (t + 1));
                }
            }
        } else {
            // 生产品：产量 + 开关变量
            if (numLines > 0 && node.canProduceOnLine != null) {
                node.production = new MPVariable[numLines][numDays];
                node.switchVar = new MPVariable[numLines][numDays];

                // 如果启用了批量步长约束，创建整数变量
                if (node.enableLotMultiple && node.lotMultiple > 0) {
                    node.lotMultipleVar = new MPVariable[numLines][1][numDays];
                }

                for (int l = 0; l < numLines; l++) {
                    if (node.canProduce(l)) {
                        for (int t = 0; t < numDays; t++) {
                            node.production[l][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                                    "xp_" + node.name + "_L" + (l + 1) + "_d" + (t + 1));
                            node.switchVar[l][t] = solver.makeBoolVar(
                                    "sw_" + node.name + "_L" + (l + 1) + "_d" + (t + 1));

                            // 创建批量步长整数变量 k (>= 0)
                            if (node.enableLotMultiple && node.lotMultiple > 0
                                    && node.lotMultipleVar != null) {
                                node.lotMultipleVar[l][0][t] = solver.makeIntVar(0,
                                        Double.POSITIVE_INFINITY,
                                        "lotK_" + node.name + "_L" + (l + 1) + "_d" + (t + 1));
                            }
                        }
                    }
                }
            }
        }

        // 外部需求节点：shortfall 变量
        if (node.hasExternalDemand && node.shortfallPenalty > 0) {
            node.shortfall = new MPVariable[1][numDays];
            for (int t = 0; t < numDays; t++) {
                node.shortfall[0][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                        "short_" + node.name + "_d" + (t + 1));
            }
        }

        // 低于安全库存的惩罚变量（若设置了安全库存）
        if (node.safetyStock > 0) {
            node.underSafety = new MPVariable[1][numDays];
            for (int t = 0; t < numDays; t++) {
                node.underSafety[0][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                        "underSafe_" + node.name + "_d" + (t + 1));
            }
        }

        // 超过最大库存的惩罚变量（若设置了最大库存和惩罚）
        if (node.maxStock > 0 && node.overstockPenalty > 0) {
            node.overMaxStock = new MPVariable[1][numDays];
            for (int t = 0; t < numDays; t++) {
                node.overMaxStock[0][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                        "overMax_" + node.name + "_d" + (t + 1));
            }
        }
    }

    /**
     * 添加所有节点的目标函数系数
     */
    static void addAllObjectiveTerms(MPObjective obj, List<ProductNode> nodes, int numDays) {
        for (ProductNode node : nodes) {
            addNodeObjective(obj, node, numDays);
        }
    }

    /**
     * 添加单个节点的目标函数系数
     */
    static void addNodeObjective(MPObjective obj, ProductNode node, int numDays) {
        int numLines = node.lineCount();

        if (node.isPurchased && node.purchase != null) {
            // 多供应商采购成本
            int numSuppliers = node.supplierCount();
            for (int s = 0; s < (numSuppliers > 0 ? numSuppliers : 1); s++) {
                double cost = (numSuppliers > 0 && s < numSuppliers)
                        ? node.suppliers.get(s).purchaseCost : node.prodCost;
                for (int t = 0; t < numDays; t++) {
                    obj.setCoefficient(node.purchase[s][0][t], cost);
                }
            }
        } else if (node.production != null) {
            // 生产成本 + 换型成本
            for (int l = 0; l < numLines; l++) {
                if (node.canProduce(l)) {
                    for (int t = 0; t < numDays; t++) {
                        obj.setCoefficient(node.production[l][t], node.prodCost);
                        obj.setCoefficient(node.switchVar[l][t], node.setupCost);
                    }
                }
            }
        }

        // 库存持有成本
        for (int t = 0; t < numDays; t++) {
            obj.setCoefficient(node.inventory[0][t], node.holdCost);
        }

        // shortfall 惩罚成本
        if (node.shortfall != null) {
            for (int t = 0; t < numDays; t++) {
                obj.setCoefficient(node.shortfall[0][t], node.shortfallPenalty);
            }
        }

        // 低于安全库存惩罚（按 shortfallPenalty 的 50% 计算）
        if (node.underSafety != null) {
            double safetyPenalty = node.shortfallPenalty * 0.5;
            for (int t = 0; t < numDays; t++) {
                obj.setCoefficient(node.underSafety[0][t], safetyPenalty);
            }
        }

        // 超过最大库存惩罚
        if (node.overMaxStock != null) {
            for (int t = 0; t < numDays; t++) {
                obj.setCoefficient(node.overMaxStock[0][t], node.overstockPenalty);
            }
        }
    }

    /**
     * 添加所有节点的库存平衡约束
     */
    static void addAllInventoryBalance(MPSolver solver, List<ProductNode> nodes, int numDays) {
        for (ProductNode node : nodes) {
            addNodeInventoryBalance(solver, node, numDays);
        }
    }

    /**
     * 统一库存平衡约束
     * 公式：上期库存 + 产量 + 采购(按提前期到货) + shortfall = 需求 + 消耗 + 期末库存
     * 移项后：上期库存 + 产量 + 采购 + shortfall - 消耗 - 期末库存 = 需求
     */
    static void addNodeInventoryBalance(MPSolver solver, ProductNode node, int numDays) {
        for (int t = 0; t < numDays; t++) {
            double rhs = 0;

            // 外部需求作为 rhs
            if (node.hasExternalDemand && node.demand != null) {
                rhs = node.demand[t];
            }

            // 创建约束，右端为 rhs
            MPConstraint c = solver.makeConstraint(rhs, rhs,
                    "inv_" + node.name + "_d" + (t + 1));

            // 1. 上期库存（正）
            if (t == 0) {
                rhs -= node.initInv;
                c.setBounds(rhs, rhs);
            } else {
                c.setCoefficient(node.inventory[0][t - 1], 1);
            }

            // 2. 本期产量（正）
            if (node.isProduced() && node.production != null) {
                for (int l = 0; l < node.lineCount(); l++) {
                    if (node.canProduce(l)) {
                        c.setCoefficient(node.production[l][t], 1);
                    }
                }
            }

            // 3. 本期采购到货（正）- 支持多供应商不同提前期
            if (node.isPurchased && node.purchase != null) {
                int numSuppliers = node.supplierCount();
                if (numSuppliers > 0) {
                    // 多供应商：按各自提前期到货
                    for (int s = 0; s < numSuppliers; s++) {
                        Supplier supplier = node.suppliers.get(s);
                        int leadTime = supplier.leadTime;
                        // 采购在 day t-leadTime 到货于 day t
                        int purchaseDay = t - leadTime;
                        if (purchaseDay >= 0 && purchaseDay < numDays) {
                            c.setCoefficient(node.purchase[s][0][purchaseDay], 1);
                        }
                    }
                } else {
                    // 单采购变量（向后兼容，提前期为0）
                    c.setCoefficient(node.purchase[0][0][t], 1);
                }
            }

            // 4. shortfall 缺口（正，外部需求节点）
            if (node.shortfall != null) {
                c.setCoefficient(node.shortfall[0][t], 1);
            }

            // 5. 本期消耗（负）：Σ(父节点产量 × BOM系数)
            for (BomParentRef ref : node.bomParents) {
                ProductNode parent = ref.parent;
                if (parent.production != null) {
                    for (int l = 0; l < parent.lineCount(); l++) {
                        if (parent.canProduce(l) && parent.production[l][t] != null) {
                            c.setCoefficient(parent.production[l][t], -ref.coefficient);
                        }
                    }
                }
            }

            // 6. 期末库存（负）
            c.setCoefficient(node.inventory[0][t], -1);
        }
    }

    /**
     * 添加所有节点的产能约束
     */
    static void addAllCapacityConstraints(MPSolver solver, List<ProductNode> nodes, int numDays) {
        // 按产线分组，每条产线每天所有可生产产品的耗时之和 ≤ 产能
        // 使用 Map<产线索引, List<节点>> 聚合
        Map<Integer, List<ProductNode>> lineNodesMap = new HashMap<>();
        for (ProductNode node : nodes) {
            if (!node.isProduced() || node.production == null) continue;
            for (int l = 0; l < node.lineCount(); l++) {
                if (node.canProduce(l)) {
                    lineNodesMap.computeIfAbsent(l, k -> new ArrayList<>()).add(node);
                }
            }
        }

        for (Map.Entry<Integer, List<ProductNode>> entry : lineNodesMap.entrySet()) {
            int lineIdx = entry.getKey();
            List<ProductNode> lineNodes = entry.getValue();
            double capacity = lineNodes.get(0).lineCapacity[lineIdx];
            String lineName = lineNodes.get(0).lineNames[lineIdx];

            for (int t = 0; t < numDays; t++) {
                MPConstraint cap = solver.makeConstraint(0, capacity,
                        "cap_" + lineName + "_d" + (t + 1));
                for (ProductNode node : lineNodes) {
                    cap.setCoefficient(node.production[lineIdx][t],
                            1.0 / node.prodRateByLine[lineIdx]);
                }
            }
        }
    }

    /**
     * 添加所有节点的生产开关约束
     */
    static void addAllSwitchConstraints(MPSolver solver, List<ProductNode> nodes,
                                        int numDays, double bigM) {
        for (ProductNode node : nodes) {
            if (node.isProduced() && node.production != null && node.switchVar != null) {
                for (int l = 0; l < node.lineCount(); l++) {
                    if (node.canProduce(l)) {
                        for (int t = 0; t < numDays; t++) {
                            MPConstraint c = solver.makeConstraint(-Double.POSITIVE_INFINITY, 0,
                                    "sw_" + node.name + "_L" + (l + 1) + "_d" + (t + 1));
                            c.setCoefficient(node.production[l][t], 1);
                            c.setCoefficient(node.switchVar[l][t], -bigM);
                        }
                    }
                }
            }
        }
    }

    /**
     * 添加所有节点的库存边界约束
     * 包括：最小库存（硬约束）、最大库存（硬约束+惩罚）、安全库存（软约束+惩罚）
     */
    static void addAllInventoryBoundsConstraints(MPSolver solver, List<ProductNode> nodes, int numDays) {
        for (ProductNode node : nodes) {
            addNodeInventoryBounds(solver, node, numDays);
        }
    }

    /**
     * 添加单个节点的库存边界约束
     */
    static void addNodeInventoryBounds(MPSolver solver, ProductNode node, int numDays) {
        for (int t = 0; t < numDays; t++) {
            MPVariable inv = node.inventory[0][t];

            // 1. 最小库存约束（硬约束）
            if (node.minStock > 0) {
                // inventory[t] >= minStock
                MPConstraint minC = solver.makeConstraint(node.minStock, Double.POSITIVE_INFINITY,
                        "minInv_" + node.name + "_d" + (t + 1));
                minC.setCoefficient(inv, 1);
            }

            // 2. 最大库存约束（硬约束 + 超库存惩罚变量）
            if (node.maxStock > 0) {
                if (node.overMaxStock != null) {
                    // inventory[t] - overMaxStock[t] <= maxStock
                    MPConstraint maxC = solver.makeConstraint(-Double.POSITIVE_INFINITY, node.maxStock,
                            "maxInv_" + node.name + "_d" + (t + 1));
                    maxC.setCoefficient(inv, 1);
                    maxC.setCoefficient(node.overMaxStock[0][t], -1);
                } else {
                    // 硬约束：inventory[t] <= maxStock
                    MPConstraint maxC = solver.makeConstraint(-Double.POSITIVE_INFINITY, node.maxStock,
                            "maxInv_" + node.name + "_d" + (t + 1));
                    maxC.setCoefficient(inv, 1);
                }
            }

            // 3. 安全库存约束（软约束 + 低于安全库存惩罚变量）
            if (node.safetyStock > 0 && node.underSafety != null) {
                // inventory[t] + underSafety[t] >= safetyStock
                MPConstraint safeC = solver.makeConstraint(node.safetyStock, Double.POSITIVE_INFINITY,
                        "safeInv_" + node.name + "_d" + (t + 1));
                safeC.setCoefficient(inv, 1);
                safeC.setCoefficient(node.underSafety[0][t], 1);
            }
        }
    }

    /**
     * 添加所有节点的生产批量约束（最小生产批量 + 批量步长）
     */
    static void addAllLotSizeConstraints(MPSolver solver, List<ProductNode> nodes, int numDays) {
        for (ProductNode node : nodes) {
            addNodeLotSizeConstraints(solver, node, numDays);
        }
    }

    /**
     * 添加单个节点的生产批量约束
     * 1. 最小生产批量：production >= switchVar * minLotSize
     * 2. 批量步长：production = lotMultiple * kVar (启用时)
     *    kVar 为整数变量，表示批量倍数
     */
    static void addNodeLotSizeConstraints(MPSolver solver, ProductNode node, int numDays) {
        if (node.isPurchased || node.production == null) return;
        if (node.minLotSize <= 0 && node.lotMultiple <= 0) return;

        boolean hasMinLot = node.minLotSize > 0;
        boolean hasLotMultiple = node.enableLotMultiple && node.lotMultiple > 0 && node.lotMultipleVar != null;

        for (int l = 0; l < node.lineCount(); l++) {
            if (!node.canProduce(l)) continue;

            for (int t = 0; t < numDays; t++) {
                // 1. 最小生产批量约束
                if (hasMinLot) {
                    MPConstraint minLotC = solver.makeConstraint(-Double.POSITIVE_INFINITY, 0,
                            "minLot_" + node.name + "_L" + (l + 1) + "_d" + (t + 1));
                    minLotC.setCoefficient(node.production[l][t], 1);
                    minLotC.setCoefficient(node.switchVar[l][t], -node.minLotSize);
                }

                // 2. 批量步长约束：production = lotMultiple * kVar
                // 即：production - lotMultiple * kVar = 0
                if (hasLotMultiple && node.lotMultipleVar[l][0][t] != null) {
                    MPConstraint lotMultC = solver.makeConstraint(0, 0,
                            "lotMult_" + node.name + "_L" + (l + 1) + "_d" + (t + 1));
                    lotMultC.setCoefficient(node.production[l][t], 1);
                    lotMultC.setCoefficient(node.lotMultipleVar[l][0][t], -node.lotMultiple);
                }
            }
        }
    }

    /**
     * 添加供应商约束（最大供应能力 + 最小采购批量）
     */
    static void addAllSupplierConstraints(MPSolver solver, List<ProductNode> nodes, int numDays) {
        for (ProductNode node : nodes) {
            if (!node.isPurchased || node.supplierCount() == 0) continue;
            addSupplierCapacityConstraints(solver, node, numDays);
        }
    }

    /**
     * 添加单个节点的供应商约束
     * 1. 供应商最大供应能力约束
     * 2. 供应商最小采购批量约束
     * 3. 供应商批量步长约束
     */
    static void addSupplierCapacityConstraints(MPSolver solver, ProductNode node, int numDays) {
        int numSuppliers = node.supplierCount();
        if (node.purchase == null) return;

        for (int s = 0; s < numSuppliers; s++) {
            Supplier supplier = node.suppliers.get(s);
            boolean hasMinLot = supplier.minPurchaseLot > 0
                    && node.purchaseSwitch != null && node.purchaseSwitch[s][0] != null;

            for (int t = 0; t < numDays; t++) {
                if (hasMinLot) {
                    // 1. 最小采购批量约束：purchase >= purchaseSwitch * minPurchaseLot
                    MPConstraint minLotC = solver.makeConstraint(-Double.POSITIVE_INFINITY, 0,
                            "suppMinLot_" + node.name + "_" + supplier.name + "_d" + (t + 1));
                    minLotC.setCoefficient(node.purchase[s][0][t], 1);
                    minLotC.setCoefficient(node.purchaseSwitch[s][t], -supplier.minPurchaseLot);

                    // 2. 与开关联动的上限约束：purchase <= purchaseSwitch * maxSupplyPerDay
                    //    此约束同时保证：switch=0时purchase=0，switch=1时purchase<=max
                    if (supplier.maxSupplyPerDay > 0) {
                        MPConstraint maxLotC = solver.makeConstraint(-Double.POSITIVE_INFINITY, 0,
                                "suppMaxLot_" + node.name + "_" + supplier.name + "_d" + (t + 1));
                        maxLotC.setCoefficient(node.purchase[s][0][t], 1);
                        maxLotC.setCoefficient(node.purchaseSwitch[s][t], -supplier.maxSupplyPerDay);
                    }
                } else {
                    // 无最小批量约束，使用简单上限约束
                    if (supplier.maxSupplyPerDay > 0) {
                        MPConstraint capC = solver.makeConstraint(0, supplier.maxSupplyPerDay,
                                "suppCap_" + node.name + "_" + supplier.name + "_d" + (t + 1));
                        capC.setCoefficient(node.purchase[s][0][t], 1);
                    }
                }

                // 3. 供应商批量步长约束：purchase = lotMultiple * kVar
                if (supplier.enableLotMultiple && supplier.lotMultiple > 0
                        && node.supplierLotMultipleVar != null
                        && node.supplierLotMultipleVar[s][0][t] != null) {
                    MPConstraint lotMultC = solver.makeConstraint(0, 0,
                            "suppLotMult_" + node.name + "_" + supplier.name + "_d" + (t + 1));
                    lotMultC.setCoefficient(node.purchase[s][0][t], 1);
                    lotMultC.setCoefficient(node.supplierLotMultipleVar[s][0][t], -supplier.lotMultiple);
                }
            }
        }
    }

    /**
     * 构建BOM父子引用关系
     */
    static void buildParentReferences(List<ProductNode> nodes) {
        for (ProductNode node : nodes) {
            for (BomChild child : node.bomChildren) {
                BomParentRef ref = new BomParentRef(node, child.coefficient);
                child.child.bomParents.add(ref);
            }
        }
    }

    /**
     * 按产线打印生产计划（产线视图）
     */
    static void printByLineView(List<ProductNode> nodes, int day) {
        // 按产线分组聚合
        Map<Integer, List<ProductNode>> lineNodesMap = new HashMap<>();
        for (ProductNode node : nodes) {
            if (node.isPurchased || node.production == null) continue;
            for (int l = 0; l < node.lineCount(); l++) {
                if (node.canProduce(l)) {
                    lineNodesMap.computeIfAbsent(l, k -> new ArrayList<>()).add(node);
                }
            }
        }

        for (Map.Entry<Integer, List<ProductNode>> entry : lineNodesMap.entrySet()) {
            int lineIdx = entry.getKey();
            List<ProductNode> lineNodes = entry.getValue();
            String lineName = lineNodes.get(0).lineNames[lineIdx];
            double capacity = lineNodes.get(0).lineCapacity[lineIdx];

            System.out.printf("    【%s】产能%.0fh %n", lineName, capacity);
            double totalHours = 0;
            boolean hasProduction = false;

            for (ProductNode node : lineNodes) {
                double qty = node.production[lineIdx][day].solutionValue();
                if (qty > 0.001) {
                    hasProduction = true;
                    double hours = qty / node.prodRateByLine[lineIdx];
                    double setupCost = node.switchVar[lineIdx][day].solutionValue() * node.setupCost;
                    totalHours += hours;
                    System.out.printf("      %s: %.0f件 (耗时%.1fh, 换型%.0f元)%n",
                            node.name, qty, hours, setupCost);
                }
            }

            if (!hasProduction) {
                System.out.println("      （休息）");
            } else {
                System.out.printf("      利用率: %.0f/%.0f h (%.0f%%)%n",
                        totalHours, capacity, (totalHours / capacity) * 100);
            }
        }
    }

    /**
     * 按产品打印生产计划（产品视图）
     */
    static void printByProductView(List<ProductNode> nodes, int day) {
        for (ProductNode node : nodes) {
            // 库存信息
            double inv = node.inventory[0][day].solutionValue();
            List<String> constraints = new ArrayList<>();
            if (node.safetyStock > 0) constraints.add(String.format("安全%.0f", node.safetyStock));
            if (node.minStock > 0) constraints.add(String.format("最小%.0f", node.minStock));
            if (node.maxStock > 0) constraints.add(String.format("最大%.0f", node.maxStock));
            if (node.minLotSize > 0) constraints.add(String.format("最小批量%.0f", node.minLotSize));
            String constraintStr = constraints.isEmpty() ? "" : " [" + String.join("/", constraints) + "]";

            // 库存状态标识
            String status = "";
            if (node.minStock > 0 && inv < node.minStock) {
                status = " ⚠低于最小库存";
            } else if (node.safetyStock > 0 && inv < node.safetyStock) {
                status = " ⚠低于安全库存";
            } else if (node.maxStock > 0 && inv > node.maxStock) {
                status = " ⚠超过最大库存";
            }

            if (node.isPurchased && node.purchase != null) {
                // 多供应商采购
                int numSuppliers = node.supplierCount();
                if (numSuppliers > 0) {
                    List<String> supplierDetails = new ArrayList<>();
                    double totalQty = 0;
                    for (int s = 0; s < numSuppliers; s++) {
                        Supplier supplier = node.suppliers.get(s);
                        double qty = node.purchase[s][0][day].solutionValue();
                        if (qty > 0.001) {
                            totalQty += qty;
                            double cost = qty * supplier.purchaseCost;
                            supplierDetails.add(String.format("%s(%.0f件,%.1f元)",
                                    supplier.name, qty, cost));
                        }
                    }
                    if (totalQty > 0.001) {
                        System.out.printf("    %s: 库存%.0f件%s%s | 采购共%.0f件 %s%n",
                                node.name, inv, constraintStr, status, totalQty,
                                "[" + String.join(", ", supplierDetails) + "]");
                    } else {
                        System.out.printf("    %s: 库存%.0f件%s%s | 无采购%n",
                                node.name, inv, constraintStr, status);
                    }
                } else {
                    // 向后兼容：单一采购变量
                    double qty = node.purchase[0][0][day].solutionValue();
                    if (qty > 0.001) {
                        System.out.printf("    %s: 库存%.0f件%s%s | 采购到货 %.0f 件（成本 %.0f元）%n",
                                node.name, inv, constraintStr, status, qty, qty * node.prodCost);
                    } else {
                        System.out.printf("    %s: 库存%.0f件%s%s | 无采购%n",
                                node.name, inv, constraintStr, status);
                    }
                }
            } else if (node.production != null) {
                double totalQty = 0;
                List<String> lineDetails = new ArrayList<>();

                for (int l = 0; l < node.lineCount(); l++) {
                    if (node.canProduce(l)) {
                        double qty = node.production[l][day].solutionValue();
                        if (qty > 0.001) {
                            totalQty += qty;
                            double hours = qty / node.prodRateByLine[l];
                            lineDetails.add(String.format("L%d(%.0f件,%.1fh)", l + 1, qty, hours));
                        }
                    }
                }

                if (totalQty > 0.001) {
                    System.out.printf("    %s: 库存%.0f件%s%s | 生产共%.0f件 [",
                            node.name, inv, constraintStr, status, totalQty);
                    System.out.println(String.join(", ", lineDetails) + "]");
                } else {
                    System.out.printf("    %s: 库存%.0f件%s%s | 无生产%n",
                            node.name, inv, constraintStr, status);
                }
            }
        }
    }

    /**
     * 打印节点的shortfall信息（含原因分析）
     */
    static void printNodeShortfall(ProductNode node, int day, List<ProductNode> allNodes) {
        if (node.shortfall != null) {
            double sht = node.shortfall[0][day].solutionValue();
            if (sht > 0.001) {
                System.out.printf("    ⚠ %s 未满足需求：%.0f 件%n", node.name, sht);
                List<String> reasons = analyzeShortfallReasons(node, day, allNodes, sht);
                for (String reason : reasons) {
                    System.out.printf("      └ %s%n", reason);
                }
            }
        }
    }

    /**
     * 分析需求未满足的原因
     * @param node 产品节点
     * @param day 天数
     * @param allNodes 所有节点
     * @param shortfall 缺口数量
     * @return 原因列表
     */
    static List<String> analyzeShortfallReasons(ProductNode node, int day,
                                                List<ProductNode> allNodes, double shortfall) {
        List<String> reasons = new ArrayList<>();

        // 计算当前产量和可达到的最大产量
        double totalProduction = 0;
        double maxPossibleProduction = 0;
        StringBuilder lineAnalysis = new StringBuilder();

        if (!node.isPurchased && node.production != null) {
            for (int l = 0; l < node.lineCount(); l++) {
                if (!node.canProduce(l)) continue;
                double prod = node.production[l][day].solutionValue();
                totalProduction += prod;

                // 计算该产线的剩余产能
                double capacity = node.lineCapacity[l];
                double usedHours = prod > 0.001 ? prod / node.prodRateByLine[l] : 0;
                double remainingHours = capacity - usedHours;
                double maxAdditional = remainingHours * node.prodRateByLine[l];
                maxPossibleProduction += prod + maxAdditional;

                // 检查产线利用率
                double utilization = (usedHours / capacity) * 100;
                if (prod > 0.001 && utilization >= 95) {
                    lineAnalysis.append(String.format("L%d(%.0f%%利用) ", l + 1));
                }
            }

            if (lineAnalysis.length() > 0) {
                reasons.add("产线产能已满: " + lineAnalysis.toString().trim()
                        + String.format("，仅生产%.0f件", totalProduction));
            }

            // 原因2: 最小生产批量约束导致无法生产
            if (totalProduction < 0.001 && node.minLotSize > 0) {
                List<String> blockedLines = new ArrayList<>();
                for (int l = 0; l < node.lineCount(); l++) {
                    if (!node.canProduce(l)) continue;
                    double remainingCap = node.lineCapacity[l];
                    double canProduce = remainingCap * node.prodRateByLine[l];
                    if (canProduce < node.minLotSize) {
                        blockedLines.add(String.format("L%d(产能仅能生产%.0f件<批量%.0f件)",
                                l + 1, canProduce, node.minLotSize));
                    }
                }
                if (!blockedLines.isEmpty()) {
                    reasons.add("最小生产批量约束: " + String.join(", ", blockedLines));
                } else {
                    reasons.add(String.format("最小生产批量约束: 需求%.0f件<批量%.0f件，模型选择不生产",
                            shortfall + totalProduction, node.minLotSize));
                }
            }

            // 原因3: 原材料/半成品瓶颈分析
            if (!node.bomChildren.isEmpty()) {
                List<String> materialBottlenecks = analyzeMaterialBottlenecks(node, day, shortfall, totalProduction);
                reasons.addAll(materialBottlenecks);
            }
        }

        // 原因4: 供应商供应能力限制（采购品）
        if (node.isPurchased && node.purchase != null && node.supplierCount() > 0) {
            List<String> supplierLimits = analyzeSupplierLimits(node, day);
            reasons.addAll(supplierLimits);
        }

        // 如果没有具体原因，给出总体判断
        if (reasons.isEmpty()) {
            if (maxPossibleProduction < shortfall + totalProduction) {
                reasons.add(String.format("综合产能不足: 最大可生产%.0f件，需求%.0f件",
                        maxPossibleProduction, shortfall + totalProduction));
            } else {
                reasons.add("多因素综合影响（产能/物料/成本优化）");
            }
        }

        return reasons;
    }

    /**
     * 分析原材料/半成品瓶颈
     */
    static List<String> analyzeMaterialBottlenecks(ProductNode node, int day,
                                                   double shortfall, double currentProduction) {
        List<String> reasons = new ArrayList<>();

        for (BomChild bomChild : node.bomChildren) {
            ProductNode child = bomChild.child;
            double needPerUnit = bomChild.coefficient;

            // 计算当前产量对该物料的消耗
            double currentNeed = currentProduction * needPerUnit;
            // 满足需求的总消耗
            double totalNeed = (shortfall + currentProduction) * needPerUnit;

            if (child.inventory == null) continue;
            double childInv = child.inventory[0][day].solutionValue();

            // 计算该物料的当前可用量（库存+当日产量/采购）
            double childAvailable = childInv;
            double childProduced = 0;
            if (!child.isPurchased && child.production != null) {
                for (int l = 0; l < child.lineCount(); l++) {
                    if (child.canProduce(l)) {
                        childProduced += child.production[l][day].solutionValue();
                    }
                }
                childAvailable += childProduced;
            } else if (child.isPurchased && child.purchase != null) {
                for (int s = 0; s < child.supplierCount(); s++) {
                    childAvailable += child.purchase[s][0][day].solutionValue();
                }
                if (child.supplierCount() == 0) {
                    childAvailable += child.purchase[0][0][day].solutionValue();
                }
            }

            // 检查物料是否成为瓶颈
            double gap = totalNeed - childAvailable;
            if (gap > 0.001 && childInv < currentNeed) {
                // 库存不足以覆盖当前消耗
                reasons.add(String.format("物料瓶颈: %s(库存%.0f件,消耗%.0f件,需求%.0f件,缺%.0f件)",
                        child.name, childInv, currentNeed, totalNeed, gap));
            } else if (gap > 0.001 && child.isPurchased && child.supplierCount() > 0) {
                // 采购品供应不足
                double totalPurchased = 0;
                for (int s = 0; s < child.supplierCount(); s++) {
                    totalPurchased += child.purchase[s][0][day].solutionValue();
                }
                reasons.add(String.format("采购品供应不足: %s(库存%.0f件,采购%.0f件,总供%.0f件,缺%.0f件)",
                        child.name, childInv, totalPurchased, childAvailable, gap));
            }
        }

        return reasons;
    }

    /**
     * 分析供应商供应能力限制
     */
    static List<String> analyzeSupplierLimits(ProductNode node, int day) {
        List<String> reasons = new ArrayList<>();
        if (node.suppliers.isEmpty()) return reasons;

        for (int s = 0; s < node.supplierCount(); s++) {
            Supplier supplier = node.suppliers.get(s);
            double purchased = node.purchase[s][0][day].solutionValue();

            // 检查是否达到供应上限
            if (supplier.maxSupplyPerDay > 0 && purchased >= supplier.maxSupplyPerDay * 0.98) {
                reasons.add(String.format("%s供应已满: %s(供%.0f件/日,已购%.0f件)",
                        node.name, supplier.name, supplier.maxSupplyPerDay, purchased));
            }

            // 检查是否因最小批量未采购
            if (purchased < 0.001 && supplier.minPurchaseLot > 0) {
                // 需求不足以达到最小批量
                double needed = 0;
                for (BomParentRef parent : node.bomParents) {
                    if (parent.parent.production != null) {
                        needed += parent.parent.production[0][day].solutionValue() * parent.coefficient;
                    }
                }
                if (needed > 0 && needed < supplier.minPurchaseLot) {
                    reasons.add(String.format("采购批量约束: %s需求%.0f件<%s最小批量%.0f件",
                            node.name, needed, supplier.name, supplier.minPurchaseLot));
                }
            }
        }

        return reasons;
    }

    /**
     * 打印节点库存
     */
    static void printNodeInventory(ProductNode node, int day) {
        double inv = node.inventory[0][day].solutionValue();
        System.out.printf("%s=%.0f  ", node.name, inv);
    }

    /**
     * 计算节点成本
     */
    static double[] calcNodeCost(ProductNode node, int numDays) {
        double prodCost = 0, holdCost = 0, setupCost = 0, shortfallCost = 0, purchaseCost = 0;

        for (int t = 0; t < numDays; t++) {
            if (node.isPurchased && node.purchase != null) {
                // 多供应商采购成本
                int numSuppliers = node.supplierCount();
                if (numSuppliers > 0) {
                    for (int s = 0; s < numSuppliers; s++) {
                        Supplier supplier = node.suppliers.get(s);
                        purchaseCost += node.purchase[s][0][t].solutionValue() * supplier.purchaseCost;
                    }
                } else {
                    purchaseCost += node.purchase[0][0][t].solutionValue() * node.prodCost;
                }
            } else if (node.production != null) {
                for (int l = 0; l < node.lineCount(); l++) {
                    if (node.canProduce(l)) {
                        prodCost += node.production[l][t].solutionValue() * node.prodCost;
                        setupCost += node.switchVar[l][t].solutionValue() * node.setupCost;
                    }
                }
            }
            holdCost += node.inventory[0][t].solutionValue() * node.holdCost;

            if (node.shortfall != null) {
                shortfallCost += node.shortfall[0][t].solutionValue() * node.shortfallPenalty;
            }
        }

        return new double[]{prodCost, holdCost, setupCost, purchaseCost, shortfallCost};
    }

    // ========== 构建BOM树的辅助方法 ==========

    /**
     * 构建一个简单的3层BOM示例
     * P1 → S1,R1 → R2,R3
     */
    static List<ProductNode> buildExampleBom(int numDays, int purchaseLeadTime) {
        List<ProductNode> nodes = new ArrayList<>();

        // 公共产线配置（3条产线）
        String[] lineNames = {"L1", "L2", "L3"};
        double[] lineCapacity = {10, 10, 10};

        // ========== 成品 P1（外部需求，可在L1或L3生产） ==========
        ProductNode p1 = new ProductNode("P1");
        p1.prodCost = 10;
        p1.setupCost = 200;
        p1.holdCost = 1.0;
        p1.initInv = 20;
        p1.shortfallPenalty = 1000;
        p1.safetyStock = 20;          // 安全库存：20件
        p1.minStock = 5;              // 最小库存：5件
        p1.maxStock = 150;            // 最大库存：150件
        p1.overstockPenalty = 50;     // 超库存惩罚：50元/件
        p1.isPurchased = false;
        p1.hasExternalDemand = true;
        p1.lineNames = lineNames;
        p1.lineCapacity = lineCapacity;
        p1.canProduceOnLine = new boolean[]{true, false, true};
        p1.prodRateByLine = new double[]{10, 0, 8};
        p1.demand = new double[]{50, 60, 40};
        p1.minLotSize = 20;           // 最小生产批量：20件
        p1.lotMultiple = 10;           // 批量步长：10件（需启用）
        p1.enableLotMultiple = false; // 是否启用批量步长约束

        // ========== 成品 P2（外部需求，可在L1或L3生产） ==========
        ProductNode p2 = new ProductNode("P2");
        p2.prodCost = 15;
        p2.setupCost = 300;
        p2.holdCost = 1.5;
        p2.initInv = 10;
        p2.shortfallPenalty = 1000;
        p2.safetyStock = 15;          // 安全库存：15件
        p2.minStock = 3;              // 最小库存：3件
        p2.maxStock = 120;            // 最大库存：120件
        p2.overstockPenalty = 60;     // 超库存惩罚：60元/件
        p2.isPurchased = false;
        p2.hasExternalDemand = true;
        p2.lineNames = lineNames;
        p2.lineCapacity = lineCapacity;
        p2.canProduceOnLine = new boolean[]{true, false, true};
        p2.prodRateByLine = new double[]{8, 0, 6};
        p2.demand = new double[]{30, 40, 50};
        p2.minLotSize = 15;           // 最小生产批量：15件
        p2.lotMultiple = 5;            // 批量步长：5件（需启用）
        p2.enableLotMultiple = false;  // 是否启用批量步长约束

        // ========== 半成品 S1（生产品，在L2生产） ==========
        ProductNode s1 = new ProductNode("S1");
        s1.prodCost = 3;
        s1.setupCost = 80;
        s1.holdCost = 0.3;
        s1.initInv = 50;
        s1.safetyStock = 30;          // 安全库存：30件
        s1.minStock = 10;             // 最小库存：10件
        s1.maxStock = 200;            // 最大库存：200件
        s1.overstockPenalty = 20;     // 超库存惩罚：20元/件
        s1.isPurchased = false;
        s1.hasExternalDemand = false;
        s1.lineNames = lineNames;
        s1.lineCapacity = lineCapacity;
        s1.canProduceOnLine = new boolean[]{false, true, false};
        s1.prodRateByLine = new double[]{0, 30, 0};
        s1.minLotSize = 30;           // 最小生产批量：30件
        s1.lotMultiple = 0;            // 不启用批量步长

        // ========== 原材料 R1（采购品，多供应商） ==========
        ProductNode r1 = new ProductNode("R1");
        r1.prodCost = 1;
        r1.holdCost = 0.1;
        r1.initInv = 200;
        r1.safetyStock = 100;         // 安全库存：100件
        r1.minStock = 50;             // 最小库存：50件
        r1.maxStock = 500;            // 最大库存：500件
        r1.overstockPenalty = 10;     // 超库存惩罚：10元/件
        r1.isPurchased = true;
        r1.hasExternalDemand = false;

        // 多供应商配置
        Supplier r1Sup1 = new Supplier("供应商A");
        r1Sup1.purchaseCost = 1.0;     // 单价1元/件
        r1Sup1.leadTime = 1;           // 提前期1天
        r1Sup1.maxSupplyPerDay = 200;  // 日最大供应200件
        r1Sup1.minPurchaseLot = 50;    // 最小采购批量50件
        r1Sup1.lotMultiple = 0;        // 不启用批量步长

        Supplier r1Sup2 = new Supplier("供应商B");
        r1Sup2.purchaseCost = 0.9;     // 单价0.9元/件（更便宜）
        r1Sup2.leadTime = 2;           // 提前期2天（更长）
        r1Sup2.maxSupplyPerDay = 100;  // 日最大供应100件
        r1Sup2.minPurchaseLot = 30;    // 最小采购批量30件
        r1Sup2.lotMultiple = 10;       // 批量步长10件
        r1Sup2.enableLotMultiple = false;

        r1.suppliers.add(r1Sup1);
        r1.suppliers.add(r1Sup2);

        // ========== 原材料 R2（采购品，多供应商） ==========
        ProductNode r2 = new ProductNode("R2");
        r2.prodCost = 1.5;
        r2.holdCost = 0.15;
        r2.initInv = 100;
        r2.safetyStock = 50;          // 安全库存：50件
        r2.minStock = 20;             // 最小库存：20件
        r2.maxStock = 400;            // 最大库存：400件
        r2.overstockPenalty = 12;     // 超库存惩罚：12元/件
        r2.isPurchased = true;
        r2.hasExternalDemand = false;

        // 多供应商配置
        Supplier r2Sup1 = new Supplier("供应商甲");
        r2Sup1.purchaseCost = 1.5;     // 单价1.5元/件
        r2Sup1.leadTime = 1;           // 提前期1天
        r2Sup1.maxSupplyPerDay = 150;  // 日最大供应150件
        r2Sup1.minPurchaseLot = 40;    // 最小采购批量40件

        Supplier r2Sup2 = new Supplier("供应商乙");
        r2Sup2.purchaseCost = 1.3;     // 单价1.3元/件（更便宜）
        r2Sup2.leadTime = 3;           // 提前期3天（更长）
        r2Sup2.maxSupplyPerDay = 80;   // 日最大供应80件
        r2Sup2.minPurchaseLot = 20;    // 最小采购批量20件

        r2.suppliers.add(r2Sup1);
        r2.suppliers.add(r2Sup2);

        // ========== 构建BOM关系 ==========
        // P1 → S1(2件), R1(3件)  — 成品同时消耗半成品和原材料
        p1.bomChildren.add(new BomChild(s1, 2));
        p1.bomChildren.add(new BomChild(r1, 3));

        // P2 → S1(1件), S2(1件)
        p2.bomChildren.add(new BomChild(s1, 1));

        // S1 → R2(2件), R3(1件)
        s1.bomChildren.add(new BomChild(r2, 2));

        // ========== 添加所有节点 ==========
        nodes.add(p1);
        nodes.add(p2);
        nodes.add(s1);
        nodes.add(r1);
        nodes.add(r2);

        // 构建父子引用
        buildParentReferences(nodes);

        return nodes;
    }

    // ========== 主方法 ==========

    public static void main(String[] args) {
        Loader.loadNativeLibraries();

        // ========== 1. 维度定义 ==========
        int numDays = 3;
        double bigM = 10000;

        // ========== 2. 构建BOM树 ==========
        List<ProductNode> nodes = buildExampleBom(numDays, 1);

        // 统计信息
        List<ProductNode> rootNodes = new ArrayList<>();
        List<ProductNode> producedNodes = new ArrayList<>();
        List<ProductNode> purchasedNodes = new ArrayList<>();
        for (ProductNode node : nodes) {
            if (node.isRoot()) rootNodes.add(node);
            if (node.isPurchased) purchasedNodes.add(node);
            else producedNodes.add(node);
        }

        // ========== 3. 创建求解器 ==========
        MPSolver solver = MPSolver.createSolver("CBC");

        // ========== 4. 创建变量 ==========
        createAllVariables(solver, nodes, numDays);

        // ========== 5. 目标函数 ==========
        MPObjective obj = solver.objective();
        addAllObjectiveTerms(obj, nodes, numDays);
        obj.setMinimization();

        // ========== 6. 约束条件 ==========
        addAllInventoryBalance(solver, nodes, numDays);
        addAllCapacityConstraints(solver, nodes, numDays);
        addAllSwitchConstraints(solver, nodes, numDays, bigM);
        addAllInventoryBoundsConstraints(solver, nodes, numDays);
        addAllLotSizeConstraints(solver, nodes, numDays);
        addAllSupplierConstraints(solver, nodes, numDays);

        // ========== 7. 求解 ==========
        System.out.println("========== 网状BOM + MRP 排产求解 ==========");
        System.out.printf("成品(根节点) %d 种：", rootNodes.size());
        for (ProductNode n : rootNodes) System.out.print(n.name + " ");
        System.out.printf("%n生产品 %d 种：", producedNodes.size());
        for (ProductNode n : producedNodes) System.out.print(n.name + " ");
        System.out.printf("%n采购品 %d 种：", purchasedNodes.size());
        for (ProductNode n : purchasedNodes) System.out.print(n.name + " ");
        System.out.printf("%n周期 %d 天%n%n", numDays);

        MPSolver.ResultStatus status = solver.solve();

        // ========== 8. 结果输出 ==========
        if (status == MPSolver.ResultStatus.OPTIMAL) {
            System.out.println("✅ 求解成功！全局最优解");
            System.out.printf("最小总成本：%.2f 元%n%n", obj.value());

            boolean hasShortfall = false;

            for (int t = 0; t < numDays; t++) {
                System.out.println("═══════════════════ 第 " + (t + 1) + " 天 ═══════════════════");

                // 产线视图
                System.out.println("  ▶ 产线视图（按产线分组）");
                printByLineView(nodes, t);

                // 产品视图
                System.out.println("  ▶ 产品视图（汇总各产线产量）");
                printByProductView(nodes, t);

                // 输出shortfall（含原因分析）
                for (ProductNode node : rootNodes) {
                    if (node.shortfall != null && node.shortfall[0][t].solutionValue() > 0.001) {
                        printNodeShortfall(node, t, nodes);
                        hasShortfall = true;
                    }
                }

                // 库存
                System.out.println("  ▶ 期末库存");
                System.out.print("    ");
                for (ProductNode node : nodes) {
                    printNodeInventory(node, t);
                }
                System.out.println();
                System.out.println();
            }

            // ========== 9. 成本明细 ==========
            double totalProdCost = 0, totalHoldCost = 0, totalSetupCost = 0;
            double totalPurchaseCost = 0, totalShortfallCost = 0;

            for (ProductNode node : nodes) {
                double[] cost = calcNodeCost(node, numDays);
                totalProdCost += cost[0];
                totalHoldCost += cost[1];
                totalSetupCost += cost[2];
                totalPurchaseCost += cost[3];
                totalShortfallCost += cost[4];
            }

            System.out.println("═══════════════════ 成本明细 ═══════════════════");
            System.out.printf("生产成本：%10.2f%n", totalProdCost);
            System.out.printf("换型成本：%10.2f%n", totalSetupCost);
            System.out.printf("采购成本：%10.2f%n", totalPurchaseCost);
            System.out.printf("库存成本：%10.2f%n", totalHoldCost);
            if (totalShortfallCost > 0.001) {
                System.out.printf("⚠ 缺口惩罚：%10.2f%n", totalShortfallCost);
                hasShortfall = true;
            }
            System.out.printf("──────────────────────────────%n");
            System.out.printf("总  成 本：%10.2f 元%n", obj.value());

            if (hasShortfall) {
                System.out.println();
                System.out.println("⚠ 注意：存在需求缺口，部分订单未满足。");
            }

            System.out.println();
            System.out.println("═══════════════════ 求解统计 ═══════════════════");
            System.out.println("变量数：" + solver.numVariables());
            System.out.println("约束数：" + solver.numConstraints());
            System.out.printf("耗时：%.3f 秒%n", solver.wallTime() / 1000.0);

        } else if (status == MPSolver.ResultStatus.INFEASIBLE) {
            System.out.println("❌ 无解");
        } else {
            System.out.println("求解状态：" + status);
        }
    }
}