feat: price_quality индикатор для прайслиста; убраны неиспользуемые price_method/period/coefficient/manual_price/meta_prices

Прайслисты: добавлено поле price_quality (0-9, выставляется внешней
утилитой ценообразования на основе количества/свежести котировок за
период — QF только отображает, не считает сам). В карточке прайслиста
рядом с ценой показывается предупреждение (жёлтый/красный «!») при
quality < 7.

price_method/price_period_days/price_coefficient/manual_price/meta_prices
убраны из контракта QF (models.PricelistItem, local_pricelist_items,
API, колонка «Настройки» на странице прайслиста) — эти поля принадлежат
внешнему движку ценообразования и нигде не использовались в клиенте.
Сами колонки в qt_pricelist_items не трогаем — оставлены для обратной
совместимости с внешней системой.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-07-22 18:09:01 +03:00
co-authored by Claude Sonnet 5
parent 39e6fd5642
commit bea8d9453e
8 changed files with 90 additions and 128 deletions
+8 -51
View File
@@ -59,7 +59,6 @@
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Категория</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Описание</th>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">Цена, $</th>
<th id="th-settings" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Настройки</th>
</tr>
</thead>
<tbody id="items-body" class="bg-white divide-y divide-gray-200">
@@ -90,7 +89,6 @@
const pl = await resp.json();
currentSource = pl.source || '';
toggleWarehouseColumns();
document.getElementById('page-title').textContent = `Прайслист ${pl.version}`;
document.getElementById('pl-version').textContent = pl.version;
@@ -133,7 +131,6 @@
const resp = await fetch(url);
const data = await resp.json();
currentSource = data.source || currentSource;
toggleWarehouseColumns();
renderItems(data.items || []);
renderItemsPagination(data.total_count, data.page, data.per_page);
@@ -158,12 +155,14 @@
}
function itemsColspan() {
return isStockSource() ? 4 : 5;
return 4;
}
function toggleWarehouseColumns() {
const stock = isStockSource();
document.getElementById('th-settings').classList.toggle('hidden', stock);
function formatPriceQualityWarning(quality) {
if (typeof quality !== 'number') return '';
if (quality >= 7) return '';
const color = quality >= 4 ? 'text-amber-500' : 'text-red-600';
return ` <span class="${color} font-bold" title="Качество цены: ${quality}/9">&#33;</span>`;
}
function escapeHtml(text) {
@@ -176,48 +175,6 @@
.replace(/'/g, '&#039;');
}
function formatPriceSettings(item) {
// Format price settings to match admin pricing interface style
let settings = [];
const hasManualPrice = item.manual_price && item.manual_price > 0;
const hasMeta = item.meta_prices && item.meta_prices.trim() !== '';
const method = (item.price_method || '').toLowerCase();
// Method indicator
if (hasManualPrice) {
settings.push('<span class="text-orange-600 font-medium">РУЧН</span>');
} else if (method === 'average') {
settings.push('Сред');
} else if (method === 'weighted_median') {
settings.push('Взвеш. мед');
} else {
settings.push('Мед');
}
// Period (only if not manual price)
if (!hasManualPrice) {
const period = item.price_period_days !== undefined && item.price_period_days !== null ? item.price_period_days : 90;
if (period === 7) settings.push('1н');
else if (period === 30) settings.push('1м');
else if (period === 90) settings.push('3м');
else if (period === 365) settings.push('1г');
else if (period === 0) settings.push('все');
else settings.push(period + 'д');
}
// Coefficient
if (item.price_coefficient && item.price_coefficient !== 0) {
settings.push((item.price_coefficient > 0 ? '+' : '') + item.price_coefficient + '%');
}
// Meta article indicator
if (hasMeta) {
settings.push('<span class="text-purple-600 font-medium">МЕТА</span>');
}
return settings.join(' | ') || '-';
}
function renderItems(items) {
if (items.length === 0) {
document.getElementById('items-body').innerHTML = `
@@ -241,11 +198,12 @@
// Price cell — add spread badge for competitor
// Price cell — add spread badge for competitor, plus a price-quality warning
let priceHtml = price;
if (!isWarehouseSource() && item.price_spread_pct > 0) {
priceHtml += ` <span class="text-xs text-amber-600 font-medium" title="Разброс цен конкурентов">±${item.price_spread_pct.toFixed(0)}%</span>`;
}
priceHtml += formatPriceQualityWarning(item.price_quality);
return `
<tr class="hover:bg-gray-50">
@@ -257,7 +215,6 @@
</td>
<td class="${p} text-sm text-gray-500" title="${escapeHtml(description)}">${escapeHtml(truncatedDesc)}</td>
<td class="${p} whitespace-nowrap text-right font-mono">${priceHtml}</td>
${!stock ? `<td class="${p} whitespace-nowrap text-sm"><span class="text-xs bg-gray-100 px-2 py-1 rounded">${formatPriceSettings(item)}</span></td>` : ''}
</tr>
`;
}).join('');