feat: кнопка «Обновить цены» на странице варианта проекта
Добавлена кнопка «Обновить цены» в панель действий страницы варианта. При нажатии синхронизирует прайс-листы с сервером (если онлайн), затем последовательно вызывает POST /api/configs/:uuid/refresh-prices для каждой активной конфигурации — тот же механизм, что и кнопка внутри конфигурации. Цены и итоговая сумма обновляются в таблице без перезагрузки страницы. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,9 @@
|
|||||||
<button onclick="openCreateModal()" class="py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium">
|
<button onclick="openCreateModal()" class="py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium">
|
||||||
+ Конфигурация
|
+ Конфигурация
|
||||||
</button>
|
</button>
|
||||||
|
<button id="refresh-all-prices-btn" onclick="refreshAllPrices()" class="py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium">
|
||||||
|
Обновить цены
|
||||||
|
</button>
|
||||||
<button onclick="openVendorImportModal()" class="py-2 bg-amber-600 text-white rounded-lg hover:bg-amber-700 font-medium">
|
<button onclick="openVendorImportModal()" class="py-2 bg-amber-600 text-white rounded-lg hover:bg-amber-700 font-medium">
|
||||||
Импорт
|
Импорт
|
||||||
</button>
|
</button>
|
||||||
@@ -1569,6 +1572,85 @@ async function exportProject() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function refreshAllPrices() {
|
||||||
|
const configs = (allConfigs || []).filter(c => c.is_active !== false);
|
||||||
|
if (!configs.length) {
|
||||||
|
if (typeof showToast === 'function') showToast('Нет активных конфигураций', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btn = document.getElementById('refresh-all-prices-btn');
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Обновление...';
|
||||||
|
btn.className = 'py-2 bg-gray-300 text-gray-500 rounded-lg cursor-not-allowed font-medium';
|
||||||
|
}
|
||||||
|
|
||||||
|
let serverSyncSkipped = false;
|
||||||
|
try {
|
||||||
|
const statusResp = await fetch('/api/sync/status');
|
||||||
|
const statusData = statusResp.ok ? await statusResp.json() : null;
|
||||||
|
if (statusData && statusData.is_online) {
|
||||||
|
const componentSyncResp = await fetch('/api/sync/components', { method: 'POST' });
|
||||||
|
if (!componentSyncResp.ok) throw new Error('component sync failed');
|
||||||
|
const pricelistSyncResp = await fetch('/api/sync/pricelists', { method: 'POST' });
|
||||||
|
if (!pricelistSyncResp.ok) throw new Error('pricelist sync failed');
|
||||||
|
} else {
|
||||||
|
serverSyncSkipped = true;
|
||||||
|
}
|
||||||
|
} catch (syncErr) {
|
||||||
|
if (syncErr.message === 'component sync failed' || syncErr.message === 'pricelist sync failed') {
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = 'Обновить цены';
|
||||||
|
btn.className = 'py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium';
|
||||||
|
}
|
||||||
|
if (typeof showToast === 'function') showToast('Ошибка синхронизации прайс-листов', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
serverSyncSkipped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let failed = 0;
|
||||||
|
let newTotalSum = 0;
|
||||||
|
for (const cfg of configs) {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('/api/configs/' + cfg.uuid + '/refresh-prices', { method: 'POST' });
|
||||||
|
if (!resp.ok) { failed++; continue; }
|
||||||
|
const updated = await resp.json();
|
||||||
|
if (updated && updated.total_price != null) {
|
||||||
|
cfg.total_price = updated.total_price;
|
||||||
|
const totalCell = document.querySelector('[data-total-uuid="' + cfg.uuid + '"]');
|
||||||
|
if (totalCell) totalCell.textContent = formatMoneyNoDecimals(updated.total_price);
|
||||||
|
const serverCount = cfg.server_count || 1;
|
||||||
|
const unitPrice = serverCount > 0 ? (updated.total_price / serverCount) : 0;
|
||||||
|
const row = totalCell && totalCell.closest('tr');
|
||||||
|
if (row) {
|
||||||
|
const cells = row.querySelectorAll('td');
|
||||||
|
if (cells[4]) cells[4].textContent = formatMoneyNoDecimals(unitPrice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newTotalSum += cfg.total_price || 0;
|
||||||
|
} catch { failed++; }
|
||||||
|
}
|
||||||
|
|
||||||
|
const footerTotal = document.querySelector('[data-footer-total="1"]');
|
||||||
|
if (footerTotal) footerTotal.textContent = formatMoneyNoDecimals(newTotalSum);
|
||||||
|
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = 'Обновить цены';
|
||||||
|
btn.className = 'py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed > 0) {
|
||||||
|
if (typeof showToast === 'function') showToast('Часть конфигураций не обновилась (' + failed + ')', 'error');
|
||||||
|
} else {
|
||||||
|
const msg = serverSyncSkipped ? 'Цены обновлены (без связи с сервером)' : 'Цены обновлены';
|
||||||
|
if (typeof showToast === 'function') showToast(msg, 'success');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async function() {
|
document.addEventListener('DOMContentLoaded', async function() {
|
||||||
applyStatusModeUI();
|
applyStatusModeUI();
|
||||||
const ok = await loadProject();
|
const ok = await loadProject();
|
||||||
|
|||||||
Reference in New Issue
Block a user