Каса — продаж
Онлайн
Найменування К-во Знижка % Сума
Чек порожній — оберіть товар справа
Позицій: 0
Без знижки: 0.00
0.00

Довідник товарів — подвійне клацання по рядку відкриває картку

Артикул Назва Група Од. Ціна Залишок Мін.
Завантаження…

Покупці

Назва Телефон Знижка % Ліміт боргу Нотатка
Завантаження…

Групи товарів

НазваТоварів
Завантаження…

Продажі — подвійне клацання показує склад чека

Дата Покупець Сума Готівка Картка Борг
Завантаження…

Статті витрат

Назва Витрачено
Завантаження…
Закупівлі / Дашборд

Що треба закупити

Спершу те, чого немає й що купують. Далі — за швидкістю продажів.

продажі за
Товар Залишок Мінімум Продано за 30 дн. Вистачить на Закупівельна
Завантаження…

Ручний список закупок

Працівники позначають тут товари, яких фізично немає на поличці чи у вітрині — навіть якщо в системі залишок ще показує наявність.

ТоварДодавКоли
Завантаження…

ABC / XYZ

А·В·С — скільки товар приносить грошей. X·Y·Z — наскільки рівно його беруть. Разом підказують, під що тримати запас, а де гроші лежать без діла.

за
ТоварABCXYZВиручка
Завантаження…

Борг постачальникам

Прихід збільшує борг, оплата зменшує

Завантаження…

Останні приходи

Завантаження…

Що замовити у постачальників

Оберіть постачальника — покажемо, які товари, що закінчились або закінчуються, ви раніше купували саме в нього.

Оберіть постачальника ліворуч

Аналітика

Оберіть період

Списання — подвійне клацання показує склад

Дата Документ Причина Сума втрат
Завантаження…

Каса

Залишок: 0,00
Дата Операція Надходження Видаток
Завантаження…

Повернення — подвійне клацання показує склад

Дата Покупець Сума Видано готівкою Пішло на борг
Завантаження…

Закупівлі — подвійне клацання показує склад накладної

Дата № накладної Постачальник Сума Оплачено Борг
Завантаження…

Постачальники

Назва Телефон Нотатка Наш борг
Завантаження…
Оберіть товар зі списку справа, щоб додати його в чек
// ------------------------------------------- залишки: зведення й динаміка let stockSub = 'groups'; // groups | history let stockDays = 30; async function renderStockExtra() { const res = await apiFetch('/api/analytics/stock-total'); const t = res.ok ? await res.json() : null; const cards = t ? `
Склад у цінах закупки
${fmt(t.buy_value)}
стільки грошей лежить на полицях
У цінах продажу
${fmt(t.sale_value)}
потенційний прибуток ${fmt(t.potential_profit)}
Позицій
${t.items}
${t.out_of_stock > 0 ? `${t.out_of_stock} із нульовим залишком` : 'усі в наявності'}
Одиниць товару
${t.qty}
сумарно по всіх позиціях
` : ''; $('an-extra').innerHTML = `${cards}
${stockSub === 'history' ? ` інтервал ` : ''}
${stockSub === 'history' ? '
Завантаження…
' : ''}`; $('an-table-wrap').style.display = stockSub === 'groups' ? '' : 'none'; if (stockSub === 'history') { $('an-summary').textContent = '—'; await renderStockChart(); } } // Простий лінійний графік без сторонніх бібліотек: на такій кількості точок // повноцінна бібліотека не окупається async function renderStockChart() { const res = await apiFetch(`/api/analytics/stock-history?days=${stockDays}`); if (!res.ok) { $('stock-chart').textContent = 'Не вдалося завантажити'; return; } const d = await res.json(); const pts = d.series; if (!pts.length) { $('stock-chart').textContent = 'Немає даних'; return; } const W = 1000, H = 190, PAD_L = 8, PAD_R = 8, PAD_T = 14, PAD_B = 26; const vals = pts.map((p) => p.value); const min = Math.min(...vals, 0); const max = Math.max(...vals, 1); const x = (i) => PAD_L + (i * (W - PAD_L - PAD_R)) / Math.max(1, pts.length - 1); const y = (v) => PAD_T + (1 - (v - min) / (max - min || 1)) * (H - PAD_T - PAD_B); const line = pts.map((p, i) => `${i ? 'L' : 'M'}${x(i).toFixed(1)},${y(p.value).toFixed(1)}`).join(' '); const area = `${line} L${x(pts.length - 1).toFixed(1)},${H - PAD_B} L${x(0).toFixed(1)},${H - PAD_B} Z`; // підписи дат: приблизно шість штук, щоб не злипались const step = Math.max(1, Math.floor(pts.length / 6)); const ticks = pts .map((p, i) => ({ p, i })) .filter(({ i }) => i % step === 0 || i === pts.length - 1) .map(({ p, i }) => `${p.date.slice(8)}.${p.date.slice(5, 7)}`) .join(''); const grow = d.change >= 0; $('stock-chart').innerHTML = ` ${ticks}
На початку: ${fmt(d.from_value)} Зараз: ${fmt(d.to_value)} Зміна: ${grow ? '+' : ''}${fmt(d.change)}${d.change_pct === null ? '' : ` (${grow ? '+' : ''}${d.change_pct.toFixed(1)}%)`}

Минулі залишки відновлені з журналу руху товару й оцінені за сьогоднішніми цінами закупки — окремої історії собівартості база не веде. Тому при різкій зміні закупівельних цін минуле оцінюється приблизно.

`; } $('an-extra').addEventListener('click', (e) => { const sub = e.target.closest('[data-ssub]'); if (sub) { stockSub = sub.dataset.ssub; renderStockExtra(); return; } const days = e.target.closest('[data-sdays]'); if (days) { stockDays = Number(days.dataset.sdays); renderStockExtra(); } });