diff --git a/web/templates/index.html b/web/templates/index.html
index 7d1d645..6938929 100644
--- a/web/templates/index.html
+++ b/web/templates/index.html
@@ -351,6 +351,8 @@ let priceLevelsRefreshTimer = null;
let warehouseStockLotsByPricelist = new Map();
let warehouseStockLoadSeq = 0;
let warehouseStockLoadsByPricelist = new Map();
+let componentPricesCache = {}; // { lot_name: price } - caches prices loaded via API
+let componentPricesCacheLoading = new Map(); // { category: Promise } - tracks ongoing price loads
// Autocomplete state
let autocompleteInput = null;
@@ -1201,12 +1203,54 @@ function renderMultiSelectTabWithSections(sections) {
document.getElementById('tab-content').innerHTML = html;
}
+// Load prices for components in a category/tab via API
+async function ensurePricesLoaded(components) {
+ if (!components || components.length === 0) return;
+
+ // Filter out components that already have prices cached
+ const toLoad = components.filter(c => !(c.lot_name in componentPricesCache));
+ if (toLoad.length === 0) return;
+
+ try {
+ // Use quote/price-levels API to get prices for these components
+ const resp = await fetch('/api/quote/price-levels', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ items: toLoad.map(c => ({ lot_name: c.lot_name, quantity: 1 })),
+ pricelist_ids: Object.fromEntries(
+ Object.entries(selectedPricelistIds)
+ .filter(([, id]) => typeof id === 'number' && id > 0)
+ )
+ })
+ });
+
+ if (resp.ok) {
+ const data = await resp.json();
+ if (data.items) {
+ data.items.forEach(item => {
+ // Cache the estimate price (or 0 if not found)
+ componentPricesCache[item.lot_name] = item.estimate_price || 0;
+ });
+ }
+ }
+ } catch (e) {
+ console.error('Failed to load component prices', e);
+ }
+}
+
+function hasComponentPrice(lotName) {
+ return lotName in componentPricesCache && componentPricesCache[lotName] > 0;
+}
+
// Autocomplete for single select (Base tab)
-function showAutocomplete(category, input) {
+async function showAutocomplete(category, input) {
autocompleteInput = input;
autocompleteCategory = category;
autocompleteMode = 'single';
autocompleteIndex = -1;
+ const components = getComponentsForCategory(category);
+ await ensurePricesLoaded(components);
filterAutocomplete(category, input.value);
}
@@ -1215,7 +1259,7 @@ function filterAutocomplete(category, search) {
const searchLower = search.toLowerCase();
autocompleteFiltered = components.filter(c => {
- if (!c.current_price) return false;
+ if (!hasComponentPrice(c.lot_name)) return false;
if (!isComponentAllowedByStockFilter(c)) return false;
const text = (c.lot_name + ' ' + (c.description || '')).toLowerCase();
return text.includes(searchLower);
@@ -1298,12 +1342,13 @@ function selectAutocompleteItem(index) {
const qtyInput = document.getElementById('qty-' + autocompleteCategory);
const qty = parseInt(qtyInput?.value) || 1;
+ const price = componentPricesCache[comp.lot_name] || 0;
cart.push({
lot_name: comp.lot_name,
quantity: qty,
- unit_price: comp.current_price,
- estimate_price: comp.current_price,
+ unit_price: price,
+ estimate_price: price,
warehouse_price: null,
competitor_price: null,
delta_wh_estimate_abs: null,
@@ -1333,11 +1378,13 @@ function hideAutocomplete() {
}
// Autocomplete for multi select tabs
-function showAutocompleteMulti(input) {
+async function showAutocompleteMulti(input) {
autocompleteInput = input;
autocompleteCategory = null;
autocompleteMode = 'multi';
autocompleteIndex = -1;
+ const components = getComponentsForTab(currentTab);
+ await ensurePricesLoaded(components);
filterAutocompleteMulti(input.value);
}
@@ -1349,7 +1396,7 @@ function filterAutocompleteMulti(search) {
const addedLots = new Set(cart.map(i => i.lot_name));
autocompleteFiltered = components.filter(c => {
- if (!c.current_price) return false;
+ if (!hasComponentPrice(c.lot_name)) return false;
if (addedLots.has(c.lot_name)) return false;
if (!isComponentAllowedByStockFilter(c)) return false;
const text = (c.lot_name + ' ' + (c.description || '')).toLowerCase();
@@ -1390,12 +1437,13 @@ function selectAutocompleteItemMulti(index) {
const qtyInput = document.getElementById('new-qty');
const qty = parseInt(qtyInput?.value) || 1;
+ const price = componentPricesCache[comp.lot_name] || 0;
cart.push({
lot_name: comp.lot_name,
quantity: qty,
- unit_price: comp.current_price,
- estimate_price: comp.current_price,
+ unit_price: price,
+ estimate_price: price,
warehouse_price: null,
competitor_price: null,
delta_wh_estimate_abs: null,
@@ -1417,11 +1465,16 @@ function selectAutocompleteItemMulti(index) {
}
// Autocomplete for sectioned tabs (like storage with RAID and Disks sections)
-function showAutocompleteSection(sectionId, input) {
+async function showAutocompleteSection(sectionId, input) {
autocompleteInput = input;
autocompleteCategory = sectionId; // Store section ID
autocompleteMode = 'section';
autocompleteIndex = -1;
+
+ // Load prices for tab components
+ const components = getComponentsForTab(currentTab);
+ await ensurePricesLoaded(components);
+
filterAutocompleteSection(sectionId, input.value, input);
}
@@ -1448,7 +1501,7 @@ function filterAutocompleteSection(sectionId, search, inputElement) {
const addedLots = new Set(cart.map(i => i.lot_name));
autocompleteFiltered = sectionComponents.filter(c => {
- if (!c.current_price) return false;
+ if (!hasComponentPrice(c.lot_name)) return false;
if (addedLots.has(c.lot_name)) return false;
if (!isComponentAllowedByStockFilter(c)) return false;
const text = (c.lot_name + ' ' + (c.description || '')).toLowerCase();
@@ -1489,12 +1542,13 @@ function selectAutocompleteItemSection(index, sectionId) {
const qtyInput = document.getElementById('new-qty-' + sectionId);
const qty = parseInt(qtyInput?.value) || 1;
+ const price = componentPricesCache[comp.lot_name] || 0;
cart.push({
lot_name: comp.lot_name,
quantity: qty,
- unit_price: comp.current_price,
- estimate_price: comp.current_price,
+ unit_price: price,
+ estimate_price: price,
warehouse_price: null,
competitor_price: null,
delta_wh_estimate_abs: null,