feat: складские цены и «только наличие» — настройки уровня проекта
Добавлен чекбокс «Складские цены» (по аналогии с «Аренда»), управляющий показом складских цен во всех ценовых интерфейсах конфигурации, включая CSV-экспорт. «Только наличие» перенесён из настроек цен конфигурации в настройки проекта и активен только при включённых складских ценах. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
18282452a1
commit
9629521495
+33
-22
@@ -227,7 +227,7 @@
|
||||
<th class="px-2 py-2 text-left border-b">LOT</th>
|
||||
<th class="px-2 py-2 text-right border-b">Кол-во</th>
|
||||
<th class="px-2 py-2 text-right border-b">Estimate</th>
|
||||
<th class="px-2 py-2 text-right border-b">Склад</th>
|
||||
<th class="px-2 py-2 text-right border-b stock-price-col">Склад</th>
|
||||
<th class="px-2 py-2 text-right border-b">Конкуренты</th>
|
||||
<th class="px-2 py-2 text-right border-b">Ручная цена</th>
|
||||
</tr>
|
||||
@@ -239,7 +239,7 @@
|
||||
<tr>
|
||||
<td colspan="4" class="px-2 py-2 text-right">Итого:</td>
|
||||
<td class="px-2 py-2 text-right" id="pricing-total-buy-estimate">—</td>
|
||||
<td class="px-2 py-2 text-right" id="pricing-total-buy-warehouse">—</td>
|
||||
<td class="px-2 py-2 text-right stock-price-col" id="pricing-total-buy-warehouse">—</td>
|
||||
<td class="px-2 py-2 text-right" id="pricing-total-buy-competitor">—</td>
|
||||
<td class="px-2 py-2 text-right font-bold" id="pricing-total-buy-vendor">—</td>
|
||||
</tr>
|
||||
@@ -276,7 +276,7 @@
|
||||
<th class="px-2 py-2 text-left border-b">LOT</th>
|
||||
<th class="px-2 py-2 text-right border-b">Кол-во</th>
|
||||
<th class="px-2 py-2 text-right border-b">Estimate</th>
|
||||
<th class="px-2 py-2 text-right border-b">Склад</th>
|
||||
<th class="px-2 py-2 text-right border-b stock-price-col">Склад</th>
|
||||
<th class="px-2 py-2 text-right border-b">Конкуренты</th>
|
||||
<th class="px-2 py-2 text-right border-b">Ручная цена</th>
|
||||
</tr>
|
||||
@@ -288,7 +288,7 @@
|
||||
<tr>
|
||||
<td colspan="4" class="px-2 py-2 text-right">Итого:</td>
|
||||
<td class="px-2 py-2 text-right" id="pricing-total-sale-estimate">—</td>
|
||||
<td class="px-2 py-2 text-right" id="pricing-total-sale-warehouse">—</td>
|
||||
<td class="px-2 py-2 text-right stock-price-col" id="pricing-total-sale-warehouse">—</td>
|
||||
<td class="px-2 py-2 text-right" id="pricing-total-sale-competitor">—</td>
|
||||
<td class="px-2 py-2 text-right font-bold" id="pricing-total-sale-vendor">—</td>
|
||||
</tr>
|
||||
@@ -395,10 +395,6 @@
|
||||
<input id="settings-disable-price-refresh" type="checkbox" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||
<span>Не обновлять цены</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700">
|
||||
<input id="settings-only-in-stock" type="checkbox" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||
<span>Только наличие</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="px-5 py-4 border-t flex justify-end gap-2">
|
||||
<button type="button" onclick="closePriceSettingsModal()" class="px-4 py-2 bg-gray-100 text-gray-700 rounded hover:bg-gray-200">Отмена</button>
|
||||
@@ -411,6 +407,9 @@
|
||||
<div id="autocomplete-dropdown" class="hidden absolute z-50 bg-white border rounded-lg shadow-lg max-h-96 overflow-y-auto w-96"></div>
|
||||
|
||||
<style>
|
||||
#top-section-pricing.hide-stock-prices .stock-price-col {
|
||||
display: none;
|
||||
}
|
||||
.autocomplete-item {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
@@ -594,6 +593,7 @@ let resolvedAutoPricelistIds = {
|
||||
};
|
||||
let disablePriceRefresh = false;
|
||||
let onlyInStock = false;
|
||||
let showStockPrices = false;
|
||||
let activePricelistsBySource = {
|
||||
estimate: [],
|
||||
warehouse: [],
|
||||
@@ -992,6 +992,7 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
await loadProjectIndex();
|
||||
updateConfigBreadcrumbs();
|
||||
applyRentalTabVisibility();
|
||||
applyStockPriceSettings();
|
||||
|
||||
rentalConditions = {};
|
||||
(config.rental_items || []).forEach(item => {
|
||||
@@ -1009,7 +1010,6 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
selectedPricelistIds.warehouse = config.warehouse_pricelist_id || null;
|
||||
selectedPricelistIds.competitor = config.competitor_pricelist_id || null;
|
||||
disablePriceRefresh = Boolean(config.disable_price_refresh);
|
||||
onlyInStock = Boolean(config.only_in_stock);
|
||||
|
||||
if (config.items && config.items.length > 0) {
|
||||
cart = config.items.map(item => ({
|
||||
@@ -1203,10 +1203,6 @@ function syncPriceSettingsControls() {
|
||||
if (disableCheckbox) {
|
||||
disableCheckbox.checked = disablePriceRefresh;
|
||||
}
|
||||
const inStockCheckbox = document.getElementById('settings-only-in-stock');
|
||||
if (inStockCheckbox) {
|
||||
inStockCheckbox.checked = onlyInStock;
|
||||
}
|
||||
}
|
||||
|
||||
function getPricelistVersionById(source, id) {
|
||||
@@ -1223,7 +1219,8 @@ function renderPricelistSettingsSummary() {
|
||||
const competitor = selectedPricelistIds.competitor ? getPricelistVersionById('competitor', selectedPricelistIds.competitor) || `ID ${selectedPricelistIds.competitor}` : '—';
|
||||
const refreshState = disablePriceRefresh ? ' | Обновление цен: выкл' : '';
|
||||
const stockFilterState = onlyInStock ? ' | Только наличие: вкл' : '';
|
||||
summary.textContent = `Estimate: ${estimate}, Склад: ${warehouse}, Конкуренты: ${competitor}${refreshState}${stockFilterState}`;
|
||||
const stockPricesState = showStockPrices ? ' | Складские цены: вкл' : '';
|
||||
summary.textContent = `Estimate: ${estimate}, Склад: ${warehouse}, Конкуренты: ${competitor}${refreshState}${stockFilterState}${stockPricesState}`;
|
||||
}
|
||||
|
||||
function updateRefreshPricesButtonState() {
|
||||
@@ -1293,7 +1290,6 @@ function applyPriceSettings() {
|
||||
const warehouseVal = parseInt(document.getElementById('settings-pricelist-warehouse')?.value || '');
|
||||
const competitorVal = parseInt(document.getElementById('settings-pricelist-competitor')?.value || '');
|
||||
const disableVal = Boolean(document.getElementById('settings-disable-price-refresh')?.checked);
|
||||
const inStockVal = Boolean(document.getElementById('settings-only-in-stock')?.checked);
|
||||
|
||||
const prevWarehouseID = currentWarehousePricelistID();
|
||||
if (Number.isFinite(estimateVal) && estimateVal > 0) {
|
||||
@@ -1309,7 +1305,6 @@ function applyPriceSettings() {
|
||||
resolvedAutoPricelistIds.competitor = null;
|
||||
}
|
||||
disablePriceRefresh = disableVal;
|
||||
onlyInStock = inStockVal;
|
||||
|
||||
const nextWarehouseID = currentWarehousePricelistID();
|
||||
if (Number.isFinite(prevWarehouseID) && prevWarehouseID > 0 && prevWarehouseID !== nextWarehouseID) {
|
||||
@@ -2631,8 +2626,7 @@ function buildSavePayload() {
|
||||
pricelist_id: selectedPricelistIds.estimate,
|
||||
warehouse_pricelist_id: selectedPricelistIds.warehouse,
|
||||
competitor_pricelist_id: selectedPricelistIds.competitor,
|
||||
disable_price_refresh: disablePriceRefresh,
|
||||
only_in_stock: onlyInStock
|
||||
disable_price_refresh: disablePriceRefresh
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2693,7 +2687,6 @@ function restoreAutosaveDraftIfAny() {
|
||||
supportCode = payload.support_code || supportCode;
|
||||
currentArticle = payload.article || currentArticle;
|
||||
selectedPricelistIds.estimate = payload.pricelist_id || selectedPricelistIds.estimate;
|
||||
onlyInStock = Boolean(payload.only_in_stock);
|
||||
|
||||
const customPriceInput = document.getElementById('custom-price-input');
|
||||
if (customPriceInput) {
|
||||
@@ -3262,6 +3255,24 @@ function applyRentalTabVisibility() {
|
||||
}
|
||||
}
|
||||
|
||||
function applyStockPriceSettings() {
|
||||
const proj = projectUUID ? projectByUUID[projectUUID] : null;
|
||||
showStockPrices = !!(proj && proj.show_stock_prices);
|
||||
onlyInStock = !!(proj && proj.show_stock_prices && proj.only_in_stock);
|
||||
|
||||
const pricingSection = document.getElementById('top-section-pricing');
|
||||
if (pricingSection) {
|
||||
pricingSection.classList.toggle('hide-stock-prices', !showStockPrices);
|
||||
}
|
||||
|
||||
if (onlyInStock) {
|
||||
ensureWarehouseStockFilterLoaded().then(() => {
|
||||
renderTab();
|
||||
updateCartUI();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleRentalRecalc() {
|
||||
clearTimeout(rentalRecalcTimer);
|
||||
rentalRecalcTimer = setTimeout(renderRentalTab, 300);
|
||||
@@ -4670,7 +4681,7 @@ async function renderPricingTab() {
|
||||
<td class="px-2 py-1.5 text-xs ${borderTop}">${r.lotCell}</td>
|
||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop}">${r.qty}</td>
|
||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.estWorld ? WORLD_CLS : ''}">${r.estUnit > 0 ? formatCurrency(r.estUnit) : '—'}</td>
|
||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.whWorld ? WORLD_CLS : ''}">${r.warehouseUnit != null ? formatCurrency(r.warehouseUnit) : '—'}</td>
|
||||
<td class="px-2 py-1.5 text-right text-xs stock-price-col ${borderTop} ${r.whWorld ? WORLD_CLS : ''}">${r.warehouseUnit != null ? formatCurrency(r.warehouseUnit) : '—'}</td>
|
||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.compWorld ? WORLD_CLS : ''}">${r.competitorUnit != null ? formatCurrency(r.competitorUnit) : '—'}</td>
|
||||
<td class="px-2 py-1.5 text-right text-xs pricing-vendor-price-buy ${borderTop} ${r.vendorOrigUnit == null ? 'text-gray-400' : ''}">${r.vendorOrigUnit != null ? formatCurrency(r.vendorOrigUnit) : '—'}</td>
|
||||
`;
|
||||
@@ -4727,7 +4738,7 @@ async function renderPricingTab() {
|
||||
<td class="px-2 py-1.5 text-xs ${borderTop}">${r.lotCell}</td>
|
||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop}">${r.qty}</td>
|
||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.estWorld ? WORLD_CLS : ''}">${saleEstUnit > 0 ? formatCurrency(saleEstUnit) : '—'}</td>
|
||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.whWorld ? WORLD_CLS : ''}">${saleWhUnit != null ? formatCurrency(saleWhUnit) : '—'}</td>
|
||||
<td class="px-2 py-1.5 text-right text-xs stock-price-col ${borderTop} ${r.whWorld ? WORLD_CLS : ''}">${saleWhUnit != null ? formatCurrency(saleWhUnit) : '—'}</td>
|
||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.compWorld ? WORLD_CLS : ''}">${saleCompUnit != null ? formatCurrency(saleCompUnit) : '—'}</td>
|
||||
<td class="px-2 py-1.5 text-right text-xs pricing-vendor-price-sale ${borderTop} text-gray-400">—</td>
|
||||
`;
|
||||
@@ -4945,7 +4956,7 @@ async function exportPricingCSV(table) {
|
||||
include_lot: true,
|
||||
include_bom: true,
|
||||
include_estimate: true,
|
||||
include_stock: true,
|
||||
include_stock: !!showStockPrices,
|
||||
include_competitor: true,
|
||||
basis: basis,
|
||||
sale_markup: saleUplift > 0 ? saleUplift : null,
|
||||
|
||||
@@ -321,6 +321,20 @@
|
||||
</label>
|
||||
<div class="text-xs text-gray-500 mt-1">Добавляет вкладку «Аренда» (расчёт стоимости платного тестирования/аренды) на конфигурациях этого проекта.</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="flex items-center space-x-2">
|
||||
<input type="checkbox" id="project-settings-show-stock-prices" class="rounded border-gray-300" onchange="applyStockSettingsGating()">
|
||||
<span class="text-sm font-medium text-gray-700">Складские цены</span>
|
||||
</label>
|
||||
<div class="text-xs text-gray-500 mt-1">Показывает складские (Stock) цены во всех ценовых интерфейсах конфигураций этого проекта, включая экспорты CSV.</div>
|
||||
</div>
|
||||
<div id="project-settings-only-in-stock-wrap">
|
||||
<label class="flex items-center space-x-2">
|
||||
<input type="checkbox" id="project-settings-only-in-stock" class="rounded border-gray-300">
|
||||
<span class="text-sm font-medium text-gray-700">Только наличие</span>
|
||||
</label>
|
||||
<div class="text-xs text-gray-500 mt-1">При добавлении компонентов предлагает только позиции, доступные на складе. Активно только вместе со «Складские цены».</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end space-x-3 mt-6">
|
||||
<button onclick="closeProjectSettingsModal()" class="px-4 py-2 text-gray-600 hover:text-gray-800">Отмена</button>
|
||||
@@ -1261,10 +1275,24 @@ function openProjectSettingsModal() {
|
||||
document.getElementById('project-settings-name').value = project.name || '';
|
||||
document.getElementById('project-settings-tracker-url').value = (project.tracker_url || '').trim();
|
||||
document.getElementById('project-settings-rental-enabled').checked = !!project.rental_enabled;
|
||||
document.getElementById('project-settings-show-stock-prices').checked = !!project.show_stock_prices;
|
||||
document.getElementById('project-settings-only-in-stock').checked = !!project.only_in_stock;
|
||||
applyStockSettingsGating();
|
||||
document.getElementById('project-settings-modal').classList.remove('hidden');
|
||||
document.getElementById('project-settings-modal').classList.add('flex');
|
||||
}
|
||||
|
||||
function applyStockSettingsGating() {
|
||||
const showStockPrices = document.getElementById('project-settings-show-stock-prices').checked;
|
||||
const onlyInStock = document.getElementById('project-settings-only-in-stock');
|
||||
const wrap = document.getElementById('project-settings-only-in-stock-wrap');
|
||||
onlyInStock.disabled = !showStockPrices;
|
||||
if (!showStockPrices) {
|
||||
onlyInStock.checked = false;
|
||||
}
|
||||
wrap.classList.toggle('opacity-50', !showStockPrices);
|
||||
}
|
||||
|
||||
function closeProjectSettingsModal() {
|
||||
document.getElementById('project-settings-modal').classList.add('hidden');
|
||||
document.getElementById('project-settings-modal').classList.remove('flex');
|
||||
@@ -1277,6 +1305,8 @@ async function saveProjectSettings() {
|
||||
const name = document.getElementById('project-settings-name').value.trim();
|
||||
const trackerURL = document.getElementById('project-settings-tracker-url').value.trim();
|
||||
const rentalEnabled = document.getElementById('project-settings-rental-enabled').checked;
|
||||
const showStockPrices = document.getElementById('project-settings-show-stock-prices').checked;
|
||||
const onlyInStock = document.getElementById('project-settings-only-in-stock').checked;
|
||||
if (!code) {
|
||||
alert('Введите код проекта');
|
||||
return;
|
||||
@@ -1284,7 +1314,7 @@ async function saveProjectSettings() {
|
||||
const resp = await fetch('/api/projects/' + projectUUID, {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({code: code, variant: variant, name: name, tracker_url: trackerURL, rental_enabled: rentalEnabled})
|
||||
body: JSON.stringify({code: code, variant: variant, name: name, tracker_url: trackerURL, rental_enabled: rentalEnabled, show_stock_prices: showStockPrices, only_in_stock: onlyInStock})
|
||||
});
|
||||
if (!resp.ok) {
|
||||
if (resp.status === 409) {
|
||||
@@ -1521,6 +1551,12 @@ function openExportModal() {
|
||||
const modal = document.getElementById('project-export-modal');
|
||||
if (!modal) return;
|
||||
setProjectExportStatus('', '');
|
||||
const stockCheckbox = document.getElementById('export-col-stock');
|
||||
const showStockPrices = !!(project && project.show_stock_prices);
|
||||
stockCheckbox.disabled = !showStockPrices;
|
||||
if (!showStockPrices) {
|
||||
stockCheckbox.checked = false;
|
||||
}
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user