calculateBudget method

Future<List<BudgetItem>> calculateBudget(
  1. ElectricalNode root, {
  2. BudgetConfig? config,
})

Implementation

Future<List<BudgetItem>> calculateBudget(ElectricalNode root,
    {BudgetConfig? config}) async {
  // 1. Aggregate Items (raw without price)
  final rawItems = await aggregator.aggregate(root);
  final effectiveConfig = config ?? const BudgetConfig();

  // 2. Fetch all prices in parallel for better performance
  // Note: rawItems should have unique IDs by design (one entry per component type)
  final priceIds = rawItems.map((item) => item.id).toList();
  final priceFutures = priceIds.map((id) => repository.getPrice(id));
  final fetchedPrices = await Future.wait(priceFutures);

  // Create price map for O(1) lookup
  final priceMap = Map<String, double>.fromIterables(
    priceIds,
    fetchedPrices.map((p) => p ?? 0.0),
  );

  // 3. Apply Prices
  List<BudgetItem> pricedItems = [];
  double materialSubtotal = 0;

  for (var item in rawItems) {
    double price = priceMap[item.id] ?? 0.0;

    // Use price from aggregator (Library Resolution) if repository didn't return one
    if (price == 0.0 && item.unitPrice > 0) {
      price = item.unitPrice;
    }

    // Fallback/Smart Pricing if still 0.0
    if (price == 0.0) {
      price = _estimatePrice(item);
    }

    final budgetItem = BudgetItem(
      id: item.id,
      name: item.name,
      description: item.description,
      quantity: item.quantity,
      unitPrice: price,
      category: item.category,
    );

    pricedItems.add(budgetItem);
    materialSubtotal += budgetItem.total;
  }

  // 4. Add Extras (Small Material)
  if (effectiveConfig.smallMaterialPercent > 0) {
    final smallMatCost =
        materialSubtotal * (effectiveConfig.smallMaterialPercent / 100);
    pricedItems.add(BudgetItem(
      id: 'SMALL-MAT',
      name: 'Peque\u00F1o Material',
      description:
          '${effectiveConfig.smallMaterialPercent}% sobre materiales (Torniller\u00EDa, etc.)',
      quantity: 1,
      unitPrice: smallMatCost,
      category: BudgetCategory.smallMaterial,
    ));
  }

  // 4. Add Labor
  double laborCost = 0;
  String laborDesc = "";
  if (effectiveConfig.laborCostType == LaborCostType.hourly) {
    laborCost = effectiveConfig.laborRate * effectiveConfig.laborTime;
    laborDesc =
        "${effectiveConfig.laborTime}h x ${effectiveConfig.laborRate} \u20AC/h";
  } else {
    laborCost = effectiveConfig.fixedLaborCost;
    laborDesc = "Coste fijo acordado";
  }

  if (laborCost > 0) {
    pricedItems.add(BudgetItem(
      id: 'LABOR',
      name: 'Mano de Obra y Servicios',
      description: laborDesc,
      quantity: 1,
      unitPrice: laborCost,
      category: BudgetCategory.labor,
    ));
  }

  return pricedItems;
}