fix: регистронезависимый поиск lot_name и удаление мёртвого кода

- SQLite-запросы по lot_name теперь используют UPPER(lot_name) IN/= для
  совместимости с легаси-данными, синхронизированными до нормализации регистра
- Удалена таблица local_components и весь связанный код синхронизации;
  источник данных для компонентов — local_pricelist_items
- Удалена функция getCategoryFromLotName из JS: категория берётся только
  из прайслиста, без инференса из имени лота
- Регистронезависимые сравнения lot_name в JS (warehouse stock set,
  addedLots, cartLots, allComponents.find, _bomLotValid)
- В support bundle добавлены: latest_pricelist_items.json, local.db,
  autocomplete_lots.json для диагностики

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 08:52:22 +03:00
parent 8b2dc6652a
commit 7d190cc7a8
19 changed files with 500 additions and 585 deletions

View File

@@ -713,7 +713,7 @@ async function loadWarehouseInStockLots() {
const lotNames = Array.isArray(data.lot_names) ? data.lot_names : [];
lotNames.forEach(lot => {
if (typeof lot === 'string' && lot.trim() !== '') {
result.add(lot);
result.add(lot.toUpperCase());
}
});
@@ -748,7 +748,7 @@ function isComponentAllowedByStockFilter(comp) {
const availableLots = warehouseStockLotsByPricelist.get(pricelistID);
// Don't block UI while stock set is being loaded.
if (!availableLots) return true;
return availableLots.has(comp.lot_name);
return availableLots.has((comp.lot_name || '').toUpperCase());
}
// Load categories from API and update tab configuration
@@ -853,7 +853,7 @@ function updateRequiredCategoryBadges() {
// Build set of categories that have at least one cart item
const filledCategories = new Set(
cart.map(item => (item.category || getCategoryFromLotName(item.lot_name) || '').toUpperCase())
cart.map(item => (item.category || '').toUpperCase())
);
// For each tab, check if it contains any required-but-unfilled category
@@ -925,8 +925,7 @@ document.addEventListener('DOMContentLoaded', async function() {
warehouse_price: null,
competitor_price: null,
description: item.description || '',
category: item.category || getCategoryFromLotName(item.lot_name)
}));
category: item.category }));
}
serverModelForQuote = config.server_model || '';
supportCode = config.support_code || '';
@@ -1003,7 +1002,7 @@ const BOM_LOT_DATALIST_DIVIDER = '────────';
function _bomLotValid(v) {
const lot = (v || '').trim();
if (!lot || lot === BOM_LOT_DATALIST_DIVIDER) return false;
return (window._bomAllComponents || allComponents).some(c => c.lot_name === lot);
return (window._bomAllComponents || allComponents).some(c => c.lot_name.toUpperCase() === lot.toUpperCase());
}
function updateServerCount() {
@@ -1219,13 +1218,8 @@ function applyPriceSettings() {
schedulePriceLevelsRefresh({ delay: 0, rerender: true, autosave: true });
}
function getCategoryFromLotName(lotName) {
const parts = lotName.split('_');
return parts[0] || '';
}
function getComponentCategory(comp) {
return (comp.category || getCategoryFromLotName(comp.lot_name)).toUpperCase();
return (comp.category || '').toUpperCase();
}
function getTabForCategory(category) {
@@ -1323,7 +1317,7 @@ function updateTabVisibility() {
if (!btn) continue;
const hasComponents = getComponentsForTab(tabId).length > 0;
const hasCartItems = cart.some(item => {
const cat = (item.category || getCategoryFromLotName(item.lot_name) || '').toUpperCase();
const cat = (item.category || '').toUpperCase();
return getTabForCategory(cat) === tabId;
});
const visible = hasComponents || hasCartItems;
@@ -1410,10 +1404,10 @@ function renderSingleSelectTab(categories) {
categories.forEach(cat => {
const catLabel = cat === 'MB' ? 'MB' : cat === 'CPU' ? 'CPU' : cat === 'MEM' ? 'MEM' : cat;
const selectedItem = cart.find(item =>
(item.category || getCategoryFromLotName(item.lot_name)).toUpperCase() === cat.toUpperCase()
(item.category).toUpperCase() === cat.toUpperCase()
);
const comp = selectedItem ? allComponents.find(c => c.lot_name === selectedItem.lot_name) : null;
const comp = selectedItem ? allComponents.find(c => c.lot_name.toUpperCase() === (selectedItem.lot_name || '').toUpperCase()) : null;
const price = comp?.current_price || 0;
const estimate = selectedItem?.estimate_price ?? price;
const qty = selectedItem?.quantity || 1;
@@ -1463,7 +1457,7 @@ function renderSingleSelectTab(categories) {
function renderMultiSelectTab(components) {
// Get cart items for this tab
const tabItems = cart.filter(item => {
const cat = (item.category || getCategoryFromLotName(item.lot_name)).toUpperCase();
const cat = (item.category).toUpperCase();
const tab = getTabForCategory(cat);
return tab === currentTab;
});
@@ -1485,7 +1479,7 @@ function renderMultiSelectTab(components) {
// Render existing cart items for this tab
tabItems.forEach((item, idx) => {
const comp = allComponents.find(c => c.lot_name === item.lot_name);
const comp = allComponents.find(c => c.lot_name.toUpperCase() === (item.lot_name || '').toUpperCase());
const total = getDisplayPrice(item) * item.quantity;
html += `
@@ -1552,7 +1546,7 @@ function renderMultiSelectTab(components) {
function renderMultiSelectTabWithSections(sections) {
// Get cart items for this tab
const tabItems = cart.filter(item => {
const cat = (item.category || getCategoryFromLotName(item.lot_name)).toUpperCase();
const cat = (item.category).toUpperCase();
const tab = getTabForCategory(cat);
return tab === currentTab;
});
@@ -1571,7 +1565,7 @@ function renderMultiSelectTabWithSections(sections) {
// Get cart items for this section
const sectionItems = tabItems.filter(item => {
const cat = (item.category || getCategoryFromLotName(item.lot_name)).toUpperCase();
const cat = (item.category).toUpperCase();
return sectionCategories.includes(cat);
});
@@ -1599,7 +1593,7 @@ function renderMultiSelectTabWithSections(sections) {
// Render existing cart items for this section
sectionItems.forEach((item) => {
const comp = allComponents.find(c => c.lot_name === item.lot_name);
const comp = allComponents.find(c => c.lot_name.toUpperCase() === (item.lot_name || '').toUpperCase());
const total = getDisplayPrice(item) * item.quantity;
html += `
@@ -1812,7 +1806,7 @@ function selectAutocompleteItem(index) {
// Remove existing item of this category
cart = cart.filter(item =>
(item.category || getCategoryFromLotName(item.lot_name)).toUpperCase() !== autocompleteCategory.toUpperCase()
(item.category).toUpperCase() !== autocompleteCategory.toUpperCase()
);
const qtyInput = document.getElementById('qty-' + autocompleteCategory);
@@ -1868,11 +1862,11 @@ function filterAutocompleteMulti(search) {
const searchLower = search.toLowerCase();
// Filter out already added items
const addedLots = new Set(cart.map(i => i.lot_name));
const addedLots = new Set(cart.map(i => (i.lot_name || '').toUpperCase()));
autocompleteFiltered = components.filter(c => {
if (!hasComponentPrice(c.lot_name)) return false;
if (addedLots.has(c.lot_name)) return false;
if (addedLots.has((c.lot_name || '').toUpperCase())) return false;
if (!isComponentAllowedByStockFilter(c)) return false;
const text = (c.lot_name + ' ' + (c.description || '')).toLowerCase();
return text.includes(searchLower);
@@ -1973,11 +1967,11 @@ function filterAutocompleteSection(sectionId, search, inputElement) {
});
// Filter out already added items
const addedLots = new Set(cart.map(i => i.lot_name));
const addedLots = new Set(cart.map(i => (i.lot_name || '').toUpperCase()));
autocompleteFiltered = sectionComponents.filter(c => {
if (!hasComponentPrice(c.lot_name)) return false;
if (addedLots.has(c.lot_name)) return false;
if (addedLots.has((c.lot_name || '').toUpperCase())) return false;
if (!isComponentAllowedByStockFilter(c)) return false;
const text = (c.lot_name + ' ' + (c.description || '')).toLowerCase();
return text.includes(searchLower);
@@ -2143,14 +2137,14 @@ function showAutocompleteBOM(rowIdx, input) {
function filterAutocompleteBOM(rowIdx, search) {
const searchLower = (search || '').toLowerCase();
const cartLots = new Set(cart.map(i => i.lot_name));
const cartLots = new Set(cart.map(i => (i.lot_name || '').toUpperCase()));
const all = (window._bomAllComponents || allComponents).filter(c => {
const text = (c.lot_name + ' ' + (c.description || '')).toLowerCase();
return text.includes(searchLower);
});
const inCart = all.filter(c => cartLots.has(c.lot_name))
const inCart = all.filter(c => cartLots.has((c.lot_name || '').toUpperCase()))
.sort((a, b) => a.lot_name.localeCompare(b.lot_name));
const notInCart = all.filter(c => !cartLots.has(c.lot_name))
const notInCart = all.filter(c => !cartLots.has((c.lot_name || '').toUpperCase()))
.sort((a, b) => {
const popDiff = (b.popularity_score || 0) - (a.popularity_score || 0);
if (popDiff !== 0) return popDiff;
@@ -2195,7 +2189,7 @@ function selectAutocompleteItemBOM(index, rowIdx) {
function clearSingleSelect(category) {
cart = cart.filter(item =>
(item.category || getCategoryFromLotName(item.lot_name)).toUpperCase() !== category.toUpperCase()
(item.category).toUpperCase() !== category.toUpperCase()
);
renderTab();
updateCartUI();
@@ -2205,7 +2199,7 @@ function clearSingleSelect(category) {
function updateSingleQuantity(category, value) {
const qty = parseInt(value) || 1;
const item = cart.find(i =>
(i.category || getCategoryFromLotName(i.lot_name)).toUpperCase() === category.toUpperCase()
(i.category).toUpperCase() === category.toUpperCase()
);
if (item) {
@@ -2264,8 +2258,8 @@ function updateCartUI() {
// Sort cart items by category display order
const sortedCart = [...cart].sort((a, b) => {
const catA = (a.category || getCategoryFromLotName(a.lot_name)).toUpperCase();
const catB = (b.category || getCategoryFromLotName(b.lot_name)).toUpperCase();
const catA = (a.category).toUpperCase();
const catB = (b.category).toUpperCase();
const orderA = categoryOrderMap[catA] || 9999;
const orderB = categoryOrderMap[catB] || 9999;
return orderA - orderB;
@@ -2273,7 +2267,7 @@ function updateCartUI() {
const grouped = {};
sortedCart.forEach(item => {
const cat = item.category || getCategoryFromLotName(item.lot_name);
const cat = item.category;
const tab = getTabForCategory(cat);
if (!grouped[tab]) grouped[tab] = [];
grouped[tab].push(item);
@@ -2282,11 +2276,11 @@ function updateCartUI() {
// Sort tabs by minimum display order of their categories
const sortedTabs = Object.entries(grouped).sort((a, b) => {
const minOrderA = Math.min(...a[1].map(item => {
const cat = (item.category || getCategoryFromLotName(item.lot_name)).toUpperCase();
const cat = (item.category).toUpperCase();
return categoryOrderMap[cat] || 9999;
}));
const minOrderB = Math.min(...b[1].map(item => {
const cat = (item.category || getCategoryFromLotName(item.lot_name)).toUpperCase();
const cat = (item.category).toUpperCase();
return categoryOrderMap[cat] || 9999;
}));
return minOrderA - minOrderB;
@@ -2517,8 +2511,7 @@ function restoreAutosaveDraftIfAny() {
warehouse_price: null,
competitor_price: null,
description: item.description || '',
category: item.category || getCategoryFromLotName(item.lot_name)
}));
category: item.category }));
}
if (typeof payload.server_count === 'number' && payload.server_count > 0) {
serverCount = payload.server_count;
@@ -2738,8 +2731,8 @@ function renderSalePriceTable() {
}
const sortedCart = [...cart].sort((a, b) => {
const catA = (a.category || getCategoryFromLotName(a.lot_name)).toUpperCase();
const catB = (b.category || getCategoryFromLotName(b.lot_name)).toUpperCase();
const catA = (a.category).toUpperCase();
const catB = (b.category).toUpperCase();
const orderA = categoryOrderMap[catA] || 9999;
const orderB = categoryOrderMap[catB] || 9999;
return orderA - orderB;
@@ -2842,8 +2835,8 @@ function calculateCustomPrice() {
// Build adjusted prices table
// Sort cart items by category display order
const sortedCart = [...cart].sort((a, b) => {
const catA = (a.category || getCategoryFromLotName(a.lot_name)).toUpperCase();
const catB = (b.category || getCategoryFromLotName(b.lot_name)).toUpperCase();
const catA = (a.category).toUpperCase();
const catB = (b.category).toUpperCase();
const orderA = categoryOrderMap[catA] || 9999;
const orderB = categoryOrderMap[catB] || 9999;
return orderA - orderB;
@@ -4153,8 +4146,8 @@ async function renderPricingTab() {
if (!bomRows.length) {
const sortedByCategory = [...cart].sort((a, b) => {
const catA = (a.category || getCategoryFromLotName(a.lot_name)).toUpperCase();
const catB = (b.category || getCategoryFromLotName(b.lot_name)).toUpperCase();
const catA = (a.category).toUpperCase();
const catB = (b.category).toUpperCase();
return (categoryOrderMap[catA] || 9999) - (categoryOrderMap[catB] || 9999);
});
sortedByCategory.forEach(item => { _pushCartRow(item, false); coveredLots.add(item.lot_name); });