fix: load component prices via API instead of removed current_price field

After the recent refactor that removed CurrentPrice from local_components,
the configurator's autocomplete was filtering out all components because
it checked for the now-removed current_price field.

Instead, now load prices from the API when the user starts typing in a
component search field:
- Added ensurePricesLoaded() to fetch prices via /api/quote/price-levels
- Added componentPricesCache to store loaded prices
- Updated all 3 autocomplete modes (single, multi, section) to load prices
- Changed price checks from c.current_price to hasComponentPrice()
- Updated cart item creation to use cached prices

Components without prices are still filtered out as required, but the check
now uses API data rather than a removed database field.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-02-09 15:31:53 +03:00
parent 5984a57a8b
commit acf7c8a4da

View File

@@ -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,