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:
co-authored by
Claude Sonnet 5
parent
39e6fd5642
commit
bea8d9453e
@@ -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">!</span>`;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
@@ -176,48 +175,6 @@
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
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('');
|
||||
|
||||
Reference in New Issue
Block a user