let canWrite = false;
let currentPage = 1;
function escapeHtml(value) {
return String(value ?? '')
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
async function checkPricelistWritePermission() {
try {
const resp = await fetch('/api/pricelists/can-write');
const data = await resp.json();
canWrite = data.can_write;
if (canWrite) {
document.getElementById('create-btn-container').innerHTML = `
`;
}
} catch (e) {
console.error('Failed to check pricelist write permission:', e);
}
}
async function loadPricelists(page = 1) {
currentPage = page;
try {
const resp = await fetch(`/api/pricelists?page=${page}&per_page=20`);
const data = await resp.json();
renderPricelists(data.pricelists || []);
renderPagination(data.total, data.page, data.per_page);
} catch (e) {
document.getElementById('pricelists-body').innerHTML = `
|
Ошибка загрузки: ${escapeHtml(e.message)}
|
`;
}
}
function renderPricelists(pricelists) {
if (pricelists.length === 0) {
document.getElementById('pricelists-body').innerHTML = `
|
Прайслисты не найдены. ${canWrite ? 'Создайте первый прайслист.' : ''}
|
`;
return;
}
const html = pricelists.map(pl => {
const date = new Date(pl.created_at).toLocaleDateString('ru-RU');
const statusClass = pl.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800';
const statusText = pl.is_active ? 'Активен' : 'Неактивен';
const id = Number(pl.id);
const safeID = Number.isFinite(id) ? id : 0;
const safeVersion = escapeHtml(pl.version || '');
const safeDate = escapeHtml(date);
const safeCreatedBy = escapeHtml(pl.created_by || '-');
const safeItemCount = Number.isFinite(Number(pl.item_count)) ? Number(pl.item_count) : 0;
const safeUsageCount = Number.isFinite(Number(pl.usage_count)) ? Number(pl.usage_count) : 0;
let actions = `Просмотр`;
if (canWrite && safeUsageCount === 0 && safeID > 0) {
actions += ` `;
}
return `
|
${safeVersion}
|
${safeDate} |
${safeCreatedBy} |
${safeItemCount} |
${safeUsageCount} |
${statusText}
|
${actions} |
`;
}).join('');
document.getElementById('pricelists-body').innerHTML = html;
}
function renderPagination(total, page, perPage) {
const totalPages = Math.ceil(total / perPage);
if (totalPages <= 1) {
document.getElementById('pagination').innerHTML = '';
return;
}
let html = '';
for (let i = 1; i <= totalPages; i++) {
const activeClass = i === page ? 'bg-orange-600 text-white' : 'bg-white text-gray-700 hover:bg-gray-50';
html += ``;
}
document.getElementById('pagination').innerHTML = html;
}
async function loadDbUsername() {
try {
const resp = await fetch('/api/current-user');
const data = await resp.json();
document.getElementById('db-username').textContent = data.username || 'неизвестно';
} catch (e) {
document.getElementById('db-username').textContent = 'неизвестно';
}
}
function openCreateModal() {
document.getElementById('create-modal').classList.remove('hidden');
document.getElementById('create-modal').classList.add('flex');
loadDbUsername();
}
function closeCreateModal() {
document.getElementById('create-modal').classList.add('hidden');
document.getElementById('create-modal').classList.remove('flex');
}
function closePriceChangesModal() {
document.getElementById('price-changes-modal').classList.add('hidden');
document.getElementById('price-changes-modal').classList.remove('flex');
}
function formatPrice(value) {
if (value === null || value === undefined || Number.isNaN(Number(value))) return '-';
return Number(value).toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function renderPriceChangesTable(items, { showOldPrice = true, showNewPrice = true, showDiff = true } = {}) {
if (!items || items.length === 0) return 'Нет данных
';
const rows = items.map(item => {
const changeType = item.change_type || '';
const rowClass = changeType === 'increased'
? 'bg-red-50'
: (changeType === 'decreased' ? 'bg-green-50' : (changeType === 'added' ? 'bg-blue-50' : ''));
const diffValue = Number(item.diff || 0);
const diffText = showDiff && item.diff !== null && item.diff !== undefined
? `${diffValue > 0 ? '+' : ''}${formatPrice(diffValue)}`
: '-';
const diffClass = diffValue > 0 ? 'text-red-700' : (diffValue < 0 ? 'text-green-700' : 'text-gray-700');
return `
| ${escapeHtml(item.lot_name || '')} |
${showOldPrice ? `${formatPrice(item.old_price)} | ` : ''}
${showNewPrice ? `${item.new_price == null ? '-' : formatPrice(item.new_price)} | ` : ''}
${showDiff ? `${diffText} | ` : ''}
`;
}).join('');
return `
| Позиция |
${showOldPrice ? 'Было | ' : ''}
${showNewPrice ? 'Стало | ' : ''}
${showDiff ? 'Изм. | ' : ''}
${rows}
`;
}
function openPriceChangesModal(pl) {
const changes = pl?.price_changes || {};
const changed = Array.isArray(changes.changed) ? changes.changed : [];
const missing = Array.isArray(changes.missing) ? changes.missing : [];
const added = Array.isArray(changes.added) ? changes.added : [];
const prevVersion = changes.previous_pricelist_version || null;
document.getElementById('price-changes-summary').innerHTML = prevVersion
? `Сравнение с прайслистом ${escapeHtml(prevVersion)}. Изменилось: ${changed.length}, пропало: ${missing.length}, добавлено: ${added.length}.`
: 'Предыдущий прайслист для сравнения не найден.';
let html = '';
html += `Изменившиеся цены
${renderPriceChangesTable(changed)}`;
html += `Пропали котировки (цена была раньше)
${renderPriceChangesTable(missing, { showNewPrice: true, showDiff: false })}`;
html += `Новые позиции
${renderPriceChangesTable(added, { showOldPrice: false, showNewPrice: true, showDiff: false })}`;
document.getElementById('price-changes-content').innerHTML = html;
document.getElementById('price-changes-modal').classList.remove('hidden');
document.getElementById('price-changes-modal').classList.add('flex');
}
async function createPricelist() {
const resp = await fetch('/api/pricelists', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
});
if (!resp.ok) {
const data = await resp.json();
throw new Error(data.error || 'Failed to create pricelist');
}
return await resp.json();
}
async function deletePricelist(id) {
if (!confirm('Удалить этот прайслист?')) return;
try {
const resp = await fetch(`/api/pricelists/${id}`, {
method: 'DELETE'
});
if (!resp.ok) {
const data = await resp.json();
throw new Error(data.error || 'Failed to delete');
}
showToast('Прайслист удален', 'success');
loadPricelists(currentPage);
} catch (e) {
showToast('Ошибка: ' + e.message, 'error');
}
}
document.getElementById('create-form').addEventListener('submit', async function(e) {
e.preventDefault();
try {
const pl = await createPricelist();
closeCreateModal();
showToast(`Прайслист ${pl.version} создан (${pl.item_count} позиций)`, 'success');
openPriceChangesModal(pl);
loadPricelists(1);
} catch (e) {
showToast('Ошибка: ' + e.message, 'error');
}
});
document.addEventListener('DOMContentLoaded', function() {
checkPricelistWritePermission();
loadPricelists(1);
});