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); });