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