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 BomMpsScheduling2 {

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

    /**
     * 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 safetyPenalty;        // 安全库存惩罚成本（低于safetyStock时惩罚）
        double minStock;             // 最小库存（软约束，低于此值有惩罚）
        double minStockPenalty;      // 低于最小库存的惩罚成本
        double maxStock;             // 最大库存（硬约束，不得高于此值）
        double overstockPenalty;     // 超库存惩罚成本（超过maxStock时惩罚）
        double shortfallPenalty;     // 缺口惩罚（仅外部需求节点）
        double endMinStock;          // 末周期库存下限（硬约束，规划期末最低库存）
        double[] prodRateByLine;   // 按产线生产效率 [line]（件/小时）
        boolean[] canProduceOnLine;  // 产线兼容性
        double[] lineCapacity;       // 产线日产能
        String[] lineNames;          // 产线名称

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

        // 多工序配置
        List<ProcessStep> processSteps = new ArrayList<>();  // 产品的工序列表
        double initWipInventory;      // 初始在制品库存（第一个工序前的输入库存）

        boolean useMultiProcess;     // 是否使用多工序模式

        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;   // 超过最大库存的量（惩罚用）
        MPVariable[][] minInvUnder;    // 低于最小库存的量（惩罚用，软约束松弛）

        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(); }
        int stepCount() { return processSteps.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 class Equipment {
        String name;                 // 设备名称
        double dailyCapacity;        // 日产能（小时）
        double setupCost;            // 换型成本
        double setupTime;            // 换型时间（小时）

        Equipment(String name, double dailyCapacity) {
            this.name = name;
            this.dailyCapacity = dailyCapacity;
        }
    }

    /**
     * 工序配置（产品生产步骤）
     */
    static class ProcessStep {
        String stepName;             // 工序名称
        int stepOrder;               // 工序顺序（0, 1, 2...）
        String equipmentName;        // 使用的设备名称
        double processTimePerUnit;   // 单件加工时间（小时/件）
        double setupCost;            // 该工序换型成本
        double setupTime;            // 该工序换型时间（小时）
        double minBatchSize;         // 该工序最小批量

        // 求解变量
        MPVariable[][] production;    // [day] 该工序产量
        MPVariable[][] switchVar;     // [day] 该工序开关变量
        MPVariable[][][] lotKVar;    // [0][day] 批量步长整数变量

        // WIP在制品库存
        MPVariable[][] wipInventory;  // [day] 该工序产出的WIP库存

        ProcessStep(String stepName, int stepOrder, String equipmentName,
                    double processTimePerUnit) {
            this.stepName = stepName;
            this.stepOrder = stepOrder;
            this.equipmentName = equipmentName;
            this.processTimePerUnit = processTimePerUnit;
        }
    }

    // ========== 产线级产能超载松弛变量存储 ==========
    // key: 产线名称, value: 该产线每天的超载松弛变量 [day]
    // 在 addAllCapacityConstraints 中创建，在 addLineCapacityOverloadObjective 中使用
    static Map<String, MPVariable[]> lineCapacityOverloadMap = new HashMap<>();
    static double capacityOverloadPenalty = 500; // 产能超载默认惩罚（元/小时）

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

    /**
     * 递归创建所有节点的变量
     */
    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 变量：所有节点都可以有 shortfall（包括中间品）
        // 成品的 shortfall: 外部需求未满足（损失销售），惩罚 = shortfallPenalty
        // 中间品的 shortfall: 用于保证模型有解（松弛硬约束），惩罚 = 极高值
        //
        // 关键机制：中间品 shortfall 惩罚 >> 成品 shortfall 惩罚
        // 这样模型会优先选择"减少成品产量"，而不是"创造虚拟中间品"
        // 只有当 minStock 硬约束无法满足时，中间品 shortfall 才会 > 0
        if (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));
            }
        }

        // 低于最小库存的松弛变量（软约束，对应文档 MinInvQtyUnder）
        // 当物理限制导致库存无法满足 minStock 时，通过松弛避免模型无解
        if (node.minStock > 0) {
            node.minInvUnder = new MPVariable[1][numDays];
            for (int t = 0; t < numDays; t++) {
                node.minInvUnder[0][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                        "minUnder_" + node.name + "_d" + (t + 1));
            }
        }

        // 多工序变量创建
        if (node.useMultiProcess && !node.processSteps.isEmpty()) {
            createProcessStepVariables(solver, node, numDays);
        }
    }

    /**
     * 创建多工序变量（每个工序的产量、开关、WIP库存）
     */
    static void createProcessStepVariables(MPSolver solver, ProductNode node, int numDays) {
        String baseName = node.name;

        for (ProcessStep step : node.processSteps) {
            step.production = new MPVariable[1][numDays];
            step.switchVar = new MPVariable[1][numDays];
            step.wipInventory = new MPVariable[1][numDays];

            // 检查是否需要批量步长变量
            boolean needLotMult = step.minBatchSize > 0 && node.enableLotMultiple && node.lotMultiple > 0;
            if (needLotMult) {
                step.lotKVar = new MPVariable[1][1][numDays];
            }

            for (int t = 0; t < numDays; t++) {
                step.production[0][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                        "step_" + baseName + "_" + step.stepName + "_d" + (t + 1));
                step.switchVar[0][t] = solver.makeBoolVar(
                        "stepSw_" + baseName + "_" + step.stepName + "_d" + (t + 1));
                step.wipInventory[0][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                        "stepWIP_" + baseName + "_" + step.stepName + "_d" + (t + 1));

                if (needLotMult && step.lotKVar != null) {
                    step.lotKVar[0][0][t] = solver.makeIntVar(0, Double.POSITIVE_INFINITY,
                            "stepLotK_" + baseName + "_" + step.stepName + "_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);
                    }
                }
            }
        } else if (node.useMultiProcess && !node.processSteps.isEmpty()) {
            // 多工序模式：每个工序的生产成本 + 换型成本
            for (ProcessStep step : node.processSteps) {
                if (step.production != null) {
                    for (int t = 0; t < numDays; t++) {
                        obj.setCoefficient(step.production[0][t], node.prodCost);
                        if (step.switchVar != null) {
                            obj.setCoefficient(step.switchVar[0][t], step.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);
            }
        }

        // 低于安全库存惩罚
        if (node.underSafety != null) {
            double penalty = node.safetyPenalty > 0 ? node.safetyPenalty : node.shortfallPenalty * 0.5;
            for (int t = 0; t < numDays; t++) {
                obj.setCoefficient(node.underSafety[0][t], penalty);
            }
        }

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

        // 低于最小库存惩罚（软约束松弛，对应文档 MinimumInventoryLevel KPI）
        // minInvUnder 惩罚应高于 shortfallPenalty，因为 minStock 是比外部需求更硬的约束
        if (node.minInvUnder != null) {
            double penalty = node.minStockPenalty > 0 ? node.minStockPenalty : node.shortfallPenalty * 2;
            for (int t = 0; t < numDays; t++) {
                obj.setCoefficient(node.minInvUnder[0][t], penalty);
            }
        }
    }

    /**
     * 添加所有节点的库存平衡约束
     */
    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()) {
                if (node.production != null) {
                    // 传统产线模式
                    for (int l = 0; l < node.lineCount(); l++) {
                        if (node.canProduce(l)) {
                            c.setCoefficient(node.production[l][t], 1);
                        }
                    }
                } else if (node.useMultiProcess && !node.processSteps.isEmpty()) {
                    // 多工序模式：使用最后一道工序的产量作为产品产量
                    ProcessStep lastStep = node.processSteps.get(node.processSteps.size() - 1);
                    if (lastStep.production != null && lastStep.production[0][t] != null) {
                        c.setCoefficient(lastStep.production[0][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 缺口（正）
            // 成品：shortfall 在平衡等式中，承担外部需求缺口（损失销售）
            // 中间品/原材料：shortfall 不在平衡等式中，避免创造虚拟物料被父节点消耗
            //   （中间品/原材料的 shortfall 只用于松弛 minStock 硬约束，见 addNodeInventoryBounds）
            //   物理传导：采购不足 → 平衡等式迫使父节点消耗减少 → 父节点产量减少 → 成品 shortfall 增加
            if (node.shortfall != null && node.hasExternalDemand) {
                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);
                        }
                    }
                }
                // 多工序模式
                else if (parent.useMultiProcess && !parent.processSteps.isEmpty()) {
                    ProcessStep lastStep = parent.processSteps.get(parent.processSteps.size() - 1);
                    if (lastStep.production != null && lastStep.production[0][t] != null) {
                        c.setCoefficient(lastStep.production[0][t], -ref.coefficient);
                    }
                }
            }

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

    /**
     * 添加所有节点的产能约束
     * 对应文档 MaxUnitCapacityQty: 产能使用 ≤ 最大产能 + 超载松弛
     */
    static void addAllCapacityConstraints(MPSolver solver, List<ProductNode> nodes, int numDays) {
        // 清空之前的产线级超载松弛变量
        lineCapacityOverloadMap.clear();

        // 按产线分组，每条产线每天所有可生产产品的耗时之和 ≤ 产能 + 超载松弛
        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];

            // 创建产线级超载松弛变量 [day]
            MPVariable[] overloadVars = new MPVariable[numDays];
            for (int t = 0; t < numDays; t++) {
                overloadVars[t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                        "capOver_" + lineName + "_d" + (t + 1));
            }
            lineCapacityOverloadMap.put(lineName, overloadVars);

            for (int t = 0; t < numDays; t++) {
                // 软约束：Σ(产量/效率) - capOver <= capacity
                // 即：Σ(产量/效率) <= capacity + capOver（允许超载，但有惩罚）
                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]);
                }
                // 超载松弛变量系数为 -1（移到左侧，相当于右侧 +capOver）
                cap.setCoefficient(overloadVars[t], -1);
            }
        }

        // 添加多工序设备产能约束
        addAllProcessEquipmentConstraints(solver, nodes, numDays);
    }

    /**
     * 添加产线级产能超载松弛的惩罚到目标函数
     * 对应文档 UnitCapacity KPI: Σ UnitCapacityOverloaded × penalty
     */
    static void addLineCapacityOverloadObjective(MPObjective obj, int numDays) {
        for (Map.Entry<String, MPVariable[]> entry : lineCapacityOverloadMap.entrySet()) {
            MPVariable[] overloadVars = entry.getValue();
            for (int t = 0; t < numDays; t++) {
                obj.setCoefficient(overloadVars[t], capacityOverloadPenalty);
            }
        }
    }

    /**
     * 添加多工序设备产能约束
     * 按设备分组，每天所有使用该设备的工序耗时之和 ≤ 设备产能
     */
    static void addAllProcessEquipmentConstraints(MPSolver solver, List<ProductNode> nodes, int numDays) {
        // 收集所有使用多工序模式的产品的工序
        // 按设备名称分组
        Map<String, List<Object[]>> equipmentStepsMap = new HashMap<>();
        // Object[] = {ProductNode, ProcessStep}

        for (ProductNode node : nodes) {
            if (!node.useMultiProcess || node.processSteps.isEmpty()) continue;
            for (ProcessStep step : node.processSteps) {
                equipmentStepsMap.computeIfAbsent(step.equipmentName, k -> new ArrayList<>())
                        .add(new Object[]{node, step});
            }
        }

        if (equipmentStepsMap.isEmpty()) return;

        for (Map.Entry<String, List<Object[]>> entry : equipmentStepsMap.entrySet()) {
            String equipName = entry.getKey();
            List<Object[]> steps = entry.getValue();

            // 从第一个工序获取设备产能配置（简化处理）
            // 实际应用中应传入设备配置Map
            double equipCapacity = 16; // 默认16小时/天

            for (int t = 0; t < numDays; t++) {
                MPConstraint cap = solver.makeConstraint(0, equipCapacity,
                        "equipCap_" + equipName + "_d" + (t + 1));

                for (Object[] obj : steps) {
                    ProcessStep step = (ProcessStep) obj[1];
                    if (step.production != null) {
                        cap.setCoefficient(step.production[0][t], step.processTimePerUnit);
                    }
                }
            }
        }
    }

    /**
     * 添加多工序WIP库存平衡约束
     * 工序间衔接：上道工序产出的WIP = 下道工序可使用的输入
     */
    static void addAllProcessWipConstraints(MPSolver solver, List<ProductNode> nodes, int numDays) {
        for (ProductNode node : nodes) {
            if (!node.useMultiProcess || node.processSteps.isEmpty()) continue;
            if (node.processSteps.size() < 2) continue;

            List<ProcessStep> steps = node.processSteps;

            for (int t = 0; t < numDays; t++) {
                // 第1个工序的WIP约束：
                // WIP_step1[t] = production_step1[t] + initWipInventory
                // 但WIP就是上道工序产出的库存，供下道工序使用

                // 中间工序的WIP平衡：
                // WIP_prev[t] + production_current[t] = WIP_current[t] + 消耗到下道的量
                // 简化：WIP[t] = production[t]（当日产出），下道工序直接使用

                for (int i = 0; i < steps.size() - 1; i++) {
                    ProcessStep currentStep = steps.get(i);
                    ProcessStep nextStep = steps.get(i + 1);

                    // WIP平衡约束：
                    // 当前工序产出 + 期初WIP = 期末WIP + 被下道工序消耗
                    // 简化模型：前道工序的WIP[t] = 前道工序的产量[t]（不考虑跨期）
                    // 下道工序的产量[t] <= 前道工序的WIP[t]（当日可用量）

                    if (currentStep.wipInventory != null && nextStep.production != null) {
                        // 约束：nextStep.production[t] <= currentStep.wipInventory[t] + currentStep.production[t]
                        // 简化：nextStep.production[t] <= currentStep.production[t]（当日产出直接流转）
                        MPConstraint wipBal = solver.makeConstraint(-Double.POSITIVE_INFINITY, 0,
                                "wipBal_" + node.name + "_" + currentStep.stepName + "_to_"
                                        + nextStep.stepName + "_d" + (t + 1));
                        wipBal.setCoefficient(nextStep.production[0][t], 1);
                        wipBal.setCoefficient(currentStep.production[0][t], -1);
                    }
                }
            }
        }
    }

    /**
     * 添加所有节点的生产开关约束
     */
    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);
                        }
                    }
                }
            } else if (node.useMultiProcess && !node.processSteps.isEmpty()) {
                // 多工序模式：每个工序都有自己的开关变量
                for (ProcessStep step : node.processSteps) {
                    if (step.production != null && step.switchVar != null) {
                        for (int t = 0; t < numDays; t++) {
                            MPConstraint c = solver.makeConstraint(-Double.POSITIVE_INFINITY, 0,
                                    "sw_step_" + node.name + "_" + step.stepName + "_d" + (t + 1));
                            c.setCoefficient(step.production[0][t], 1);
                            c.setCoefficient(step.switchVar[0][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. 最小库存约束（软约束，对应文档 MinInventoryLevel: InvQty + MinInvQtyUnder ≥ MinLevel）
            // 当物理限制导致库存无法满足 minStock 时，通过 minInvUnder 松弛避免模型无解
            // minInvUnder 惩罚成本高，模型会优先满足 minStock，只在物理上确实无法满足时才用松弛
            if (node.minStock > 0) {
                if (node.minInvUnder != null) {
                    // 软约束：inventory[t] + minInvUnder[t] >= minStock
                    MPConstraint minC = solver.makeConstraint(node.minStock, Double.POSITIVE_INFINITY,
                            "minInv_" + node.name + "_d" + (t + 1));
                    minC.setCoefficient(inv, 1);
                    minC.setCoefficient(node.minInvUnder[0][t], 1);
                } else {
                    // 无松弛变量时退化为硬约束
                    MPConstraint minC = solver.makeConstraint(node.minStock, Double.POSITIVE_INFINITY,
                            "minInv_" + node.name + "_d" + (t + 1));
                    minC.setCoefficient(inv, 1);
                }
            }

            // 1.1 末周期库存下限约束（对应文档 LowerBoundLastPeriodInventory）
            // 保证规划期末有足够库存应对未来需求
            if (t == numDays - 1 && node.endMinStock > 0) {
                MPConstraint endC = solver.makeConstraint(node.endMinStock, Double.POSITIVE_INFINITY,
                        "endMinInv_" + node.name + "_d" + (t + 1));
                endC.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) return;

        // 多工序产品的批量约束
        if (node.useMultiProcess && !node.processSteps.isEmpty()) {
            for (ProcessStep step : node.processSteps) {
                if (step.production == null) continue;
                boolean hasMinLot = step.minBatchSize > 0;
                boolean hasLotMultiple = node.enableLotMultiple && node.lotMultiple > 0
                        && step.lotKVar != null;

                for (int t = 0; t < numDays; t++) {
                    // 工序级最小批量约束: production >= switchVar * minBatchSize
                    if (hasMinLot && step.switchVar != null) {
                        MPConstraint minLotC = solver.makeConstraint(0, Double.POSITIVE_INFINITY,
                                "stepMinLot_" + node.name + "_" + step.stepName + "_d" + (t + 1));
                        minLotC.setCoefficient(step.production[0][t], 1);
                        minLotC.setCoefficient(step.switchVar[0][t], -step.minBatchSize);
                    }

                    // 工序级批量步长约束
                    if (hasLotMultiple && step.lotKVar[0][0][t] != null) {
                        MPConstraint lotMultC = solver.makeConstraint(0, 0,
                                "stepLotMult_" + node.name + "_" + step.stepName + "_d" + (t + 1));
                        lotMultC.setCoefficient(step.production[0][t], 1);
                        lotMultC.setCoefficient(step.lotKVar[0][0][t], -node.lotMultiple);
                    }
                }
            }
            return;
        }

        // 传统产线模式
        if (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. 最小生产批量约束: production >= switchVar * minLotSize
                // 即: production - switchVar * minLotSize >= 0
                if (hasMinLot) {
                    MPConstraint minLotC = solver.makeConstraint(0, Double.POSITIVE_INFINITY,
                            "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
                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
                    //    即: purchase - purchaseSwitch * minPurchaseLot >= 0
                    MPConstraint minLotC = solver.makeConstraint(0, Double.POSITIVE_INFINITY,
                            "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);
            }
        }

        // 调试：打印BOM关系
        System.out.println("📋 BOM关系验证:");
        for (ProductNode node : nodes) {
            StringBuilder sb = new StringBuilder();
            sb.append(node.name).append(" → 消耗: [");
            for (BomChild child : node.bomChildren) {
                sb.append(child.child.name).append("×").append(child.coefficient).append(", ");
            }
            if (node.bomChildren.size() > 0) sb.setLength(sb.length() - 2);
            sb.append("]");

            sb.append(", 被消耗于: [");
            for (BomParentRef parent : node.bomParents) {
                sb.append(parent.parent.name).append("×").append(parent.coefficient).append(", ");
            }
            if (node.bomParents.size() > 0) sb.setLength(sb.length() - 2);
            sb.append("]");

            System.out.println("  " + sb);
        }
        System.out.println();
    }

    /**
     * 按产线打印生产计划（产线视图）
     */
    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> orderDetails = new ArrayList<>();
                    List<String> arrivalDetails = new ArrayList<>();
                    double totalOrdered = 0;
                    double totalArrived = 0;

                    for (int s = 0; s < numSuppliers; s++) {
                        Supplier supplier = node.suppliers.get(s);
                        double orderQty = node.purchase[s][0][day].solutionValue();
                        if (orderQty > 0.001) {
                            totalOrdered += orderQty;
                            double cost = orderQty * supplier.purchaseCost;
                            orderDetails.add(String.format("%s(下单%.0f件,%.1f元)",
                                    supplier.name, orderQty, cost));
                        }
                        // 计算到货量：当天到货 = 提前期之前那天的下单量
                        int leadTime = supplier.leadTime;
                        int orderDay = day - leadTime;
                        double arrivalQty = 0;
                        if (orderDay >= 0) {
                            arrivalQty = node.purchase[s][0][orderDay].solutionValue();
                        }
                        if (arrivalQty > 0.001) {
                            totalArrived += arrivalQty;
                            arrivalDetails.add(String.format("%s(到货%.0f件)",
                                    supplier.name, arrivalQty));
                        }
                    }

                    StringBuilder purchaseInfo = new StringBuilder();
                    if (totalOrdered > 0.001) {
                        purchaseInfo.append(String.format("下单共%.0f件 %s",
                                totalOrdered, "[" + String.join(", ", orderDetails) + "]"));
                    } else {
                        purchaseInfo.append("无下单");
                    }
                    if (totalArrived > 0.001) {
                        purchaseInfo.append(String.format(" | 到货共%.0f件 %s",
                                totalArrived, "[" + String.join(", ", arrivalDetails) + "]"));
                    } else {
                        purchaseInfo.append(" | 无到货");
                    }

                    System.out.printf("    %s: 库存%.0f件%s%s | %s%n",
                            node.name, inv, constraintStr, status, purchaseInfo.toString());
                } 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);
                }
            } else if (node.useMultiProcess && !node.processSteps.isEmpty()) {
                // 多工序产品：使用最后一道工序的产量
                ProcessStep lastStep = node.processSteps.get(node.processSteps.size() - 1);
                double totalQty = lastStep.production[0][day].solutionValue();

                // 汇总所有工序信息
                List<String> stepDetails = new ArrayList<>();
                double totalHours = 0;
                for (ProcessStep step : node.processSteps) {
                    double qty = step.production[0][day].solutionValue();
                    double hours = qty * step.processTimePerUnit;
                    totalHours += hours;
                    if (qty > 0.001) {
                        stepDetails.add(String.format("%s→%s@%s(%.0f件,%.1fh)",
                                node.name, step.stepName, step.equipmentName, qty, hours));
                    }
                }

                if (totalQty > 0.001) {
                    System.out.printf("    %s: 库存%.0f件%s%s | 多工序生产%.0f件 (总耗时%.1fh) %n",
                            node.name, inv, constraintStr, status, totalQty, totalHours);
                    if (!stepDetails.isEmpty()) {
                        System.out.printf("      工序详情: %s%n", String.join(" → ", stepDetails));
                    }
                } 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) {
                // 根据节点类型显示不同的提示
                String typeLabel;
                if (node.hasExternalDemand) {
                    typeLabel = "未满足外部需求";
                } else if (node.isPurchased) {
                    typeLabel = "供应不足（采购缺口）";
                } else {
                    typeLabel = "供应不足（产能缺口）";
                }
                System.out.printf("    ⚠ %s %s：%.0f 件%n", node.name, typeLabel, sht);
                List<String> reasons = analyzeShortfallReasons(node, day, allNodes, sht);
                for (String reason : reasons) {
                    System.out.printf("      └ %s%n", reason);
                }
            }
        }
    }

    /**
     * 物料需求展开视图
     * 根据成品的实际生产数量，计算出半成品和原材料的需求
     */
    static void printMaterialRequirementsView(List<ProductNode> nodes, int day) {
        System.out.println("  ▶ 物料需求展开（按BOM逐级展开）");

        // 计算每种物料的需求汇总
        Map<ProductNode, Double> materialDemandMap = new HashMap<>();
        Map<ProductNode, StringBuilder> demandDetailMap = new HashMap<>();

        // 从成品开始逐级展开BOM
        for (ProductNode rootNode : nodes) {
            if (!rootNode.hasExternalDemand) continue;

            // 获取成品的实际生产量
            double rootProduction = getNodeProduction(rootNode, day);
            if (rootProduction <= 0.001) continue;

            StringBuilder detail = new StringBuilder();
            detail.append(String.format("%s生产%.0f件 → ", rootNode.name, rootProduction));

            // 递归展开BOM需求
            expandBomDemand(rootNode, rootProduction, 1.0, materialDemandMap, demandDetailMap, day);

            System.out.println("    " + detail.toString().trim());
        }

        // 输出物料需求汇总
        if (!materialDemandMap.isEmpty()) {
            System.out.println("    ─── 物料需求汇总 ───");
            for (Map.Entry<ProductNode, Double> entry : materialDemandMap.entrySet()) {
                ProductNode material = entry.getKey();
                double totalDemand = entry.getValue();
                double initInv = day == 0 ? material.initInv : material.inventory[0][day - 1].solutionValue();
                double currentInv = material.inventory[0][day].solutionValue();

                // 物料来源
                String source;
                if (material.isPurchased) {
                    source = "采购";
                } else {
                    source = "生产";
                }

                // 计算当日到货量/生产量
                double todaySupply = 0;
                if (material.isPurchased && material.purchase != null && material.supplierCount() > 0) {
                    for (int s = 0; s < material.supplierCount(); s++) {
                        Supplier supplier = material.suppliers.get(s);
                        int orderDay = day - supplier.leadTime;
                        if (orderDay >= 0) {
                            todaySupply += material.purchase[s][0][orderDay].solutionValue();
                        }
                    }
                    System.out.printf("    %s(%s): 期初库存%.0f件, 今日到货%.0f件, 今日需求%.0f件, 期末库存%.0f件%n",
                            material.name, source, initInv, todaySupply, totalDemand, currentInv);
                } else if (material.production != null) {
                    for (int l = 0; l < material.lineCount(); l++) {
                        if (material.canProduce(l) && material.production[l] != null
                                && material.production[l][day] != null) {
                            todaySupply += material.production[l][day].solutionValue();
                        }
                    }
                    System.out.printf("    %s(%s): 期初库存%.0f件, 今日生产%.0f件, 今日需求%.0f件, 期末库存%.0f件%n",
                            material.name, source, initInv, todaySupply, totalDemand, currentInv);
                } else {
                    System.out.printf("    %s(%s): 期初库存%.0f件, 今日需求%.0f件, 期末库存%.0f件%n",
                            material.name, source, initInv, totalDemand, currentInv);
                }

                // 显示消耗明细
                StringBuilder detail = demandDetailMap.get(material);
                if (detail != null && detail.length() > 0) {
                    System.out.printf("      └ %s%n", detail.toString());
                }
            }
        }
    }

    /**
     * 获取节点的实际产量
     */
    static double getNodeProduction(ProductNode node, int day) {
        if (node.production != null) {
            double total = 0;
            for (int l = 0; l < node.lineCount(); l++) {
                if (node.canProduce(l) && node.production[l] != null
                        && node.production[l][day] != null) {
                    total += node.production[l][day].solutionValue();
                }
            }
            return total;
        } else if (node.useMultiProcess && !node.processSteps.isEmpty()) {
            ProcessStep lastStep = node.processSteps.get(node.processSteps.size() - 1);
            if (lastStep.production != null && lastStep.production[0][day] != null) {
                return lastStep.production[0][day].solutionValue();
            }
        }
        return 0;
    }

    /**
     * 递归展开BOM需求
     */
    static void expandBomDemand(ProductNode node, double quantity, double parentCoef,
                                Map<ProductNode, Double> demandMap,
                                Map<ProductNode, StringBuilder> detailMap,
                                int day) {
        for (BomChild child : node.bomChildren) {
            ProductNode childNode = child.child;
            double childDemand = quantity * child.coefficient;

            // 累计需求
            demandMap.merge(childNode, childDemand, Double::sum);

            // 添加明细
            StringBuilder detail = detailMap.computeIfAbsent(childNode, k -> new StringBuilder());
            if (detail.length() > 0) detail.append(", ");
            detail.append(String.format("%s需求%.0f件(由%s×%.1f系数)",
                    childNode.name, childDemand, node.name, child.coefficient));

            // 递归展开子节点的BOM（如果子节点也有BOM消耗）
            if (!childNode.bomChildren.isEmpty() && childDemand > 0) {
                expandBomDemand(childNode, childDemand, child.coefficient,
                        demandMap, detailMap, day);
            }
        }
    }

    /**
     * 分析需求未满足的原因
     * @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 {
                    // 需求充足但未生产，检查是否因物料瓶颈导致实际可生产量低于minLotSize
                    double netDemand = shortfall + totalProduction;
                    if (netDemand >= node.minLotSize) {
                        // 需求 >= 批量，不是批量约束问题，不添加此原因
                        // 真正原因会在后面的物料瓶颈分析中体现
                    } else {
                        reasons.add(String.format("最小生产批量约束: 净需求%.0f件<批量%.0f件，模型选择不生产",
                                netDemand, node.minLotSize));
                    }
                }
            }

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

        // 原因4: 中间品供应不足分析（有bomParents的节点）
        if (!node.isPurchased && !node.bomParents.isEmpty()) {
            List<String> downstreamDemands = analyzeDownstreamDemands(node, day, shortfall);
            reasons.addAll(downstreamDemands);
        }

        // 原因5: 供应商供应能力限制（采购品）
        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();

            // 计算该物料的总可用量
            // 根据库存平衡：期初 + 供应 = 消耗 + 期末
            // 所以：总可用 = 期末 + 消耗 = childInv + currentNeed
            // 这比分别计算生产量/到货量更准确（避免了提前期到货 vs 下单的混淆）
            double childAvailable = childInv + currentNeed;

            // 补充显示信息：对于采购品，显示到货量（考虑提前期）
            String supplyInfo = "";
            if (child.isPurchased && child.purchase != null && child.supplierCount() > 0) {
                double totalArrival = 0;
                double totalOrder = 0;
                for (int s = 0; s < child.supplierCount(); s++) {
                    Supplier supplier = child.suppliers.get(s);
                    totalOrder += child.purchase[s][0][day].solutionValue();
                    int orderDay = day - supplier.leadTime;
                    if (orderDay >= 0) {
                        totalArrival += child.purchase[s][0][orderDay].solutionValue();
                    }
                }
                double initInv = day == 0 ? child.initInv : child.inventory[0][day - 1].solutionValue();
                supplyInfo = String.format("期初%.0f+到货%.0f", initInv, totalArrival);
            } else if (!child.isPurchased && child.production != null) {
                double childProduced = 0;
                for (int l = 0; l < child.lineCount(); l++) {
                    if (child.canProduce(l) && child.production[l] != null
                            && child.production[l][day] != null) {
                        childProduced += child.production[l][day].solutionValue();
                    }
                }
                double initInv = day == 0 ? child.initInv : child.inventory[0][day - 1].solutionValue();
                supplyInfo = String.format("期初%.0f+生产%.0f", initInv, childProduced);
            }

            // 检查物料是否成为瓶颈
            double gap = totalNeed - childAvailable;
            if (gap > 0.001) {
                // 物料不足，是瓶颈
                reasons.add(String.format("物料瓶颈: %s(可用%.0f件[%s],需求%.0f件,缺%.0f件)",
                        child.name, childAvailable, supplyInfo, totalNeed, gap));
            }
        }

        return reasons;
    }

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

        // 计算当天的到货量（考虑提前期）
        double totalArrival = 0;
        double totalOrderToday = 0;
        double totalSupplyCapacity = 0;
        boolean allSuppliersLeadTimeBlocked = true;

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

            // 计算当天到货量
            int leadTime = supplier.leadTime;
            int orderDay = day - leadTime;
            double arrivalQty = 0;
            if (orderDay >= 0) {
                arrivalQty = node.purchase[s][0][orderDay].solutionValue();
            }
            totalArrival += arrivalQty;

            // 检查这个供应商是否能在当天到货（即 day >= leadTime 或之前有下单）
            if (orderDay >= 0) {
                allSuppliersLeadTimeBlocked = false;
            }

            // 检查是否达到供应上限
            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) {
                        for (int l = 0; l < parent.parent.lineCount(); l++) {
                            if (parent.parent.canProduce(l) && parent.parent.production[l] != null
                                    && parent.parent.production[l][day] != null) {
                                needed += parent.parent.production[l][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));
                }
            }
        }

        // 关键：检查是否因提前期导致无法到货
        if (allSuppliersLeadTimeBlocked && totalArrival < 0.001) {
            // 所有供应商的提前期都 > 当天，无法在当天到货
            StringBuilder leadTimeInfo = new StringBuilder();
            for (int s = 0; s < node.supplierCount(); s++) {
                Supplier supplier = node.suppliers.get(s);
                if (s > 0) leadTimeInfo.append(", ");
                leadTimeInfo.append(String.format("%s提前期%d天", supplier.name, supplier.leadTime));
            }
            reasons.add(String.format("采购提前期限制: 当天无到货(第%d天, %s), 仅能依赖期初库存",
                    day + 1, leadTimeInfo.toString()));
        }

        return reasons;
    }

    /**
     * 分析中间品的下游需求情况
     * 用于诊断半成品（如S1）的供应缺口原因
     */
    static List<String> analyzeDownstreamDemands(ProductNode node, int day, double shortfall) {
        List<String> reasons = new ArrayList<>();

        // 计算下游（父节点）的总需求
        double totalDownstreamDemand = 0;
        StringBuilder demandDetail = new StringBuilder();

        for (BomParentRef parentRef : node.bomParents) {
            ProductNode parent = parentRef.parent;
            double coefficient = parentRef.coefficient;

            // 获取父节点的产量
            double parentProduction = 0;
            if (parent.production != null) {
                for (int l = 0; l < parent.lineCount(); l++) {
                    if (parent.canProduce(l) && parent.production[l] != null
                            && parent.production[l][day] != null) {
                        parentProduction += parent.production[l][day].solutionValue();
                    }
                }
            }

            // 如果有shortfall，也计入需求
            double parentShortfall = 0;
            if (parent.shortfall != null) {
                parentShortfall = parent.shortfall[0][day].solutionValue();
            }

            double parentTotalDemand = parentProduction + parentShortfall;
            double materialNeed = parentTotalDemand * coefficient;
            totalDownstreamDemand += materialNeed;

            if (materialNeed > 0.001) {
                demandDetail.append(String.format("%s(需求%.0f件×%.0f系数=%.0f件) ",
                        parent.name, parentTotalDemand, coefficient, materialNeed));
            }
        }

        if (totalDownstreamDemand > 0.001) {
            // 计算当前供应
            double currentSupply = 0;
            if (node.inventory != null) {
                currentSupply += node.inventory[0][day].solutionValue();
            }
            if (node.production != null) {
                for (int l = 0; l < node.lineCount(); l++) {
                    if (node.canProduce(l) && node.production[l] != null
                            && node.production[l][day] != null) {
                        currentSupply += node.production[l][day].solutionValue();
                    }
                }
            }

            double gap = totalDownstreamDemand - currentSupply;
            if (gap > 0.001) {
                reasons.add(String.format("下游需求超过供应: 总需求%.0f件(由%s), 供应%.0f件, 缺%.0f件",
                        totalDownstreamDemand, demandDetail.toString().trim(), currentSupply, gap));
            }
        }

        // 检查是否因为上游产能限制
        if (node.production != null) {
            for (int l = 0; l < node.lineCount(); l++) {
                if (!node.canProduce(l) || node.production[l] == null
                        || node.production[l][day] == null) continue;
                double capacity = node.lineCapacity[l];
                double prod = node.production[l][day].solutionValue();
                double usedHours = prod > 0.001 ? prod / node.prodRateByLine[l] : 0;
                double utilization = (usedHours / capacity) * 100;

                if (utilization >= 95) {
                    reasons.add(String.format("L%d产线产能已满: 利用率%.0f%%, 生产%.0f件",
                            l + 1, utilization, prod));
                } else if (prod > 0.001 && utilization < 30) {
                    // 低利用率但仍有shortfall，可能是其他约束限制
                    reasons.add(String.format("L%d产线利用率低(%.0f%%), 但仍有供应缺口%.0f件",
                            l + 1, utilization, shortfall));
                }
            }
        }

        return reasons;
    }

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

        // 如果有多工序，输出WIP库存
        if (node.useMultiProcess && !node.processSteps.isEmpty()) {
            for (ProcessStep step : node.processSteps) {
                if (step.wipInventory != null) {
                    double wip = step.wipInventory[0][day].solutionValue();
                    System.out.printf("WIP_%s=%.0f  ", step.stepName, wip);
                }
            }
        }
    }

    /**
     * 按设备视图打印多工序生产计划
     */
    static void printByEquipmentView(List<ProductNode> nodes, int day) {
        // 按设备分组聚合
        Map<String, List<Object[]>> equipStepsMap = new HashMap<>();

        for (ProductNode node : nodes) {
            if (!node.useMultiProcess || node.processSteps.isEmpty()) continue;
            for (ProcessStep step : node.processSteps) {
                equipStepsMap.computeIfAbsent(step.equipmentName, k -> new ArrayList<>())
                        .add(new Object[]{node, step});
            }
        }

        if (equipStepsMap.isEmpty()) return;

        // 按设备排序输出
        List<String> sortedEquips = new ArrayList<>(equipStepsMap.keySet());
        Collections.sort(sortedEquips);

        for (String equipName : sortedEquips) {
            List<Object[]> steps = equipStepsMap.get(equipName);
            // 计算设备总耗时
            double totalHours = 0;
            System.out.printf("    设备【%s】%n", equipName);

            for (Object[] obj : steps) {
                ProductNode node = (ProductNode) obj[0];
                ProcessStep step = (ProcessStep) obj[1];

                double qty = step.production[0][day].solutionValue();
                if (qty > 0.001) {
                    double hours = qty * step.processTimePerUnit;
                    double setupCost = step.switchVar[0][day].solutionValue() * step.setupCost;
                    totalHours += hours;

                    // 检查WIP库存
                    String wipInfo = "";
                    if (step.wipInventory != null) {
                        double wip = step.wipInventory[0][day].solutionValue();
                        if (wip > 0.001) {
                            wipInfo = String.format(" [WIP:%.0f]", wip);
                        }
                    }

                    System.out.printf("      %s→%s: %.0f件 (耗时%.1fh,换型%.0f元)%s%n",
                            node.name, step.stepName, qty, hours, setupCost, wipInfo);
                }
            }

            if (totalHours > 0) {
                System.out.printf("      耗时: %.1fh%n", totalHours);
            } else {
                System.out.println("      （无生产）");
            }
            System.out.println();
        }
    }

    /**
     * 按产品工序视图打印多工序详情
     */
    static void printByProcessView(List<ProductNode> nodes, int day) {
        boolean hasMultiProcess = false;

        for (ProductNode node : nodes) {
            if (!node.useMultiProcess || node.processSteps.isEmpty()) continue;
            hasMultiProcess = true;

            System.out.printf("    产品【%s】多工序：%n", node.name);

            double totalProduced = 0;
            for (ProcessStep step : node.processSteps) {
                double qty = step.production[0][day].solutionValue();
                double wip = step.wipInventory != null ? step.wipInventory[0][day].solutionValue() : 0;
                double hours = qty * step.processTimePerUnit;
                totalProduced = qty;

                String prefix = "      └";
                System.out.printf("%s %s(%s@%s): %.0f件, 耗时%.1fh, WIP=%.0f%n",
                        prefix, step.stepName,
                        qty > 0 ? "✓" : "✗",
                        step.equipmentName,
                        qty, hours, wip);
            }

            // 最终产出（最后工序的产量）
            if (!node.processSteps.isEmpty()) {
                ProcessStep lastStep = node.processSteps.get(node.processSteps.size() - 1);
                double finalQty = lastStep.production[0][day].solutionValue();
                System.out.printf("      ★ 最终产出: %.0f件%n", finalQty);
            }
            System.out.println();
        }

        if (!hasMultiProcess) {
            System.out.println("    （无多工序产品）");
        }
    }

    /**
     * 计算节点成本
     */
    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 = 100;           // 降低换型成本，提高生产意愿
        p1.holdCost = 1.0;
        p1.initInv = 20;
        p1.shortfallPenalty = 5000;   // 提高缺口惩罚，倒逼生产
        p1.safetyStock = 20;          // 安全库存：20件
        p1.safetyPenalty = 300;       // 安全库存惩罚：300元/件
        p1.minStock = 5;              // 最小库存：5件
        p1.minStockPenalty = 2000;    // 低于最小库存惩罚：2000元/件
        p1.endMinStock = 20;          // 末周期库存下限：20件
        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 = 5000;   // 提高缺口惩罚，与P1一致
        p2.safetyStock = 15;          // 安全库存：15件
        p2.safetyPenalty = 300;       // 安全库存惩罚：300元/件
        p2.minStock = 3;              // 最小库存：3件
        p2.minStockPenalty = 2000;    // 低于最小库存惩罚：2000元/件
        p2.endMinStock = 15;          // 末周期库存下限：15件
        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 = 10;           // 降低最小生产批量（从15→10），提高生产灵活性
        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 不设置 shortfallPenalty（不创建 shortfall 变量）
        // minStock 改为软约束（minInvUnder 松弛），避免物理限制导致模型无解
        // 当 S1 库存无法满足 minStock 时，通过 minInvUnder 松弛 + 平衡等式物理传导
        s1.shortfallPenalty = 0;
        s1.safetyStock = 30;          // 安全库存：30件
        s1.safetyPenalty = 100;       // 降低安全库存惩罚（从500→100），减少对生产决策的干扰
        s1.minStock = 10;             // 最小库存：10件
        s1.minStockPenalty = 3000;    // 低于最小库存惩罚：3000元/件（中间品库存不足影响大）
        s1.endMinStock = 30;          // 末周期库存下限：30件
        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 不设置 shortfallPenalty（不创建 shortfall 变量）
        // minStock 改为软约束（minInvUnder 松弛），避免采购提前期导致模型无解
        r1.shortfallPenalty = 0;
        r1.safetyStock = 80;          // 降低安全库存（从150→80），减少提前期约束
        r1.safetyPenalty = 50;        // 降低安全库存惩罚（从200→50），减少对生产决策的干扰
        r1.minStock = 50;             // 最小库存：50件
        r1.minStockPenalty = 500;     // 低于最小库存惩罚：500元/件（原材料库存不足可采购补货）
        r1.endMinStock = 100;         // 末周期库存下限：100件（保留采购周期用量）
        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 不设置 shortfallPenalty（不创建 shortfall 变量）
        // minStock 改为软约束（minInvUnder 松弛），避免采购提前期导致模型无解
        r2.shortfallPenalty = 0;
        r2.safetyStock = 40;          // 降低安全库存（从80→40），考虑提前期约束
        r2.safetyPenalty = 50;        // 降低安全库存惩罚（从200→50），减少对生产决策的干扰
        r2.minStock = 20;             // 最小库存：20件
        r2.minStockPenalty = 500;     // 低于最小库存惩罚：500元/件（原材料库存不足可采购补货）
        r2.endMinStock = 50;          // 末周期库存下限：50件
        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);

        // ========== 多工序示例：P3（经过3道工序：切割→装配→包装） ==========
        ProductNode p3 = new ProductNode("P3");
        p3.prodCost = 20;
        p3.setupCost = 150;
        p3.holdCost = 2.0;
        p3.initInv = 0;
        p3.shortfallPenalty = 1200;
        p3.safetyStock = 10;
        p3.minStock = 0;
        p3.endMinStock = 10;          // 末周期库存下限：10件
        p3.maxStock = 100;
        p3.overstockPenalty = 40;
        p3.isPurchased = false;
        p3.hasExternalDemand = true;
        p3.demand = new double[]{20, 25, 30};
        p3.useMultiProcess = true;  // 启用多工序模式
        p3.initWipInventory = 5;    // 初始WIP库存

        // 工序1：切割（设备A）
        ProcessStep cutStep = new ProcessStep("切割", 0, "切割机", 0.1); // 0.1小时/件 = 6分钟/件
        cutStep.setupCost = 50;
        cutStep.setupTime = 0.5;
        cutStep.minBatchSize = 10;

        // 工序2：装配（设备B）
        ProcessStep asmStep = new ProcessStep("装配", 1, "装配机", 0.2); // 0.2小时/件 = 12分钟/件
        asmStep.setupCost = 80;
        asmStep.setupTime = 0.8;
        asmStep.minBatchSize = 5;

        // 工序3：包装（设备C）
        ProcessStep packStep = new ProcessStep("包装", 2, "包装机", 0.05); // 0.05小时/件 = 3分钟/件
        packStep.setupCost = 20;
        packStep.setupTime = 0.2;
        packStep.minBatchSize = 10;

        p3.processSteps.add(cutStep);
        p3.processSteps.add(asmStep);
        p3.processSteps.add(packStep);

        // P3 消耗原材料 R1（每件P3消耗2件R1）
        p3.bomChildren.add(new BomChild(r1, 2));

      //  nodes.add(p3);

        // 构建父子引用
        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 = new MPSolver("demo",
                MPSolver.OptimizationProblemType.SCIP_MIXED_INTEGER_PROGRAMMING);
        solver.enableOutput();

        // ========== 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);
        addAllProcessWipConstraints(solver, nodes, numDays);  // 多工序WIP约束

        // 产能超载松弛惩罚（在产能约束创建后调用，因为松弛变量在 addAllCapacityConstraints 中创建）
        addLineCapacityOverloadObjective(obj, 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);

        // 导出LP模型用于调试
        try {
            String lpContent = solver.exportModelAsLpFormat(false);
            String lpPath = "F:\\aps\\hyh.apsj\\log\\model.lp";
            java.nio.file.Files.write(java.nio.file.Paths.get(lpPath), lpContent.getBytes("UTF-8"));
            System.out.println("📁 LP模型已导出到: " + lpPath);
            System.out.println("📊 LP模型大小: " + lpContent.length() + " 字符");
        } catch (Exception e) {
            System.out.println("导出LP模型失败: " + e.getMessage());
        }

        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);

                // 物料需求展开视图
                printMaterialRequirementsView(nodes, t);

                // 多工序视图
                System.out.println("  ▶ 多工序视图（按设备/工序）");
                printByEquipmentView(nodes, t);
                printByProcessView(nodes, t);

                // 输出成品的 shortfall（含原因分析）
                // 只有成品（有外部需求的节点）才有 shortfall
                // 原材料/半成品的不足会通过成品 shortfall 体现
                for (ProductNode node : nodes) {
                    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);
        }
    }
}