Довідник товарів — подвійне клацання по рядку відкриває картку
Артикул
Назва
Група
Од.
Ціна
Залишок
Мін.
Завантаження…
—
Покупці
Назва
Телефон
Знижка %
Ліміт боргу
Нотатка
Завантаження…
Групи товарів
№
Назва
Товарів
Завантаження…
Продажі — подвійне клацання показує склад чека
Дата
№
Покупець
Сума
Готівка
Картка
Борг
Завантаження…
—
Статті витрат
№
Назва
Витрачено
Завантаження…
—
Закупівлі / Дашборд
Що треба закупити
Спершу те, чого немає й що купують. Далі — за швидкістю продажів.
продажі за
Товар
Залишок
Мінімум
Продано за 30 дн.
Вистачить на
Закупівельна
Завантаження…
Ручний список закупок
Працівники позначають тут товари, яких фізично немає на поличці чи у вітрині — навіть якщо в системі залишок ще показує наявність.
Товар
Додав
Коли
Завантаження…
ABC / XYZ
А·В·С — скільки товар приносить грошей. X·Y·Z — наскільки рівно його беруть. Разом підказують, під що тримати запас, а де гроші лежать без діла.
за
Товар
ABC
XYZ
Виручка
Завантаження…
Борг постачальникам
Прихід збільшує борг, оплата зменшує
Завантаження…
Останні приходи
Завантаження…
Що замовити у постачальників
Оберіть постачальника — покажемо, які товари, що закінчились або закінчуються, ви раніше купували саме в нього.
Оберіть постачальника ліворуч
Аналітика
Оберіть період
—
Списання — подвійне клацання показує склад
Дата
№
Документ
Причина
Сума втрат
Завантаження…
—
Каса
Залишок:0,00
Дата
Операція
Надходження
Видаток
Завантаження…
—
Повернення — подвійне клацання показує склад
Дата
№
Покупець
Сума
Видано готівкою
Пішло на борг
Завантаження…
Закупівлі — подвійне клацання показує склад накладної
Дата
№ накладної
Постачальник
Сума
Оплачено
Борг
Завантаження…
Постачальники
Назва
Телефон
Нотатка
Наш борг
Завантаження…
Оберіть товар зі списку справа, щоб додати його в чек
Оплата✕
Вкажіть, скільки платить покупець. Якщо сума менша за суму чека — різниця
запишеться як борг, і тоді обов'язково оберіть покупця. Якщо більша — буде
порахована решта, і в касу піде лише сума чека.
Картка товару або послуги✕
Вкажіть найменування товару і групу, до якої він належить:
Обидві ціни рахуються самі при проведенні закупівлі. Середня — зважена
за залишком: якщо лишалось 2 шт по 10 грн і прийшло 10 шт по 15, вона
стане (2×10 + 10×15) ÷ 12 = 14,17.
На цій сторінці властивостей товару ви вказуєте залишок товару на складі
магазину, кількість зарезервованого за покупцями товару та мінімальний
залишок, нижче якого товар вважається таким, що потребує закупівлі.
Облік серійних номерів ще не реалізовано. У базі для нього немає ні таблиці, ні полів.
Комплекти ще не реалізовано. Потрібна окрема таблиця складу комплекту.
Фото товару ще не реалізовано. Потрібне сховище файлів на сервері.
Довільні атрибути ще не реалізовано.
Вивантаження в інтернет-магазин ще не реалізовано. У базі є лише поле export_to_prom, самої інтеграції немає.
Опис товару ще не реалізовано. Потрібне текстове поле в таблиці products.
Порожня вкладка з макета.
Порожня вкладка з макета.
Коригування залишку✕
Новий покупець✕
Нова група✕
Немає на полиці✕
Товар потрапить у ручний список закупок. Це не змінює залишок у системі — просто позначка, що фізично товару немає.
Товар знімається із залишку. Каса не змінюється — гроші тут не рухаються,
це саме втрата товару. Собівартість підставляється із середньої ціни
закупки і потрібна лише щоб оцінити розмір збитку.
Списання✕
Товар
К-сть
Собівартість
Сума
Прихід✕
Повернення товару✕
Товар
К-сть
Ціна продажу
Сума
Товар повертається на склад. Якщо за покупцем є борг, сума спершу гасить
його, починаючи з найдавніших чеків, і лише залишок видається готівкою з
каси. Для роздрібного покупця вся сума йде готівкою.
Повернення✕
Товар
К-сть
Ціна
Сума
Оплата боргу⋮✕
Хто винен
борг
Неоплачені чеки✕
Покупець✕
Що бере найчастіше
«Разів» — у скількох чеках траплявся товар. Це показовіше за кількість:
мішок цементу можуть узяти раз возом, а саморізи — щотижня потроху.
Товар
Разів
Кількість
Виручка
Прибуток
Маржа %
Чек✕
Товар
К-сть
Ціна
Знижка %
Сума
Нова закупівля✕
Товар
К-сть
Ціна закупки
Нова ціна продажу
Сума
Товар одразу стає на облік, а сплачена сума списується з каси. Якщо
оплатили не все — різниця запишеться як наш борг постачальнику.
«Нову ціну продажу» заповнюйте лише коли вона змінюється; інакше
лишиться попередня.
Закупівля✕
Товар
К-сть
Ціна закупки
Сума
Новий постачальник✕
Вхід в систему
Хто заходить
// ------------------------------------------- залишки: зведення й динаміка
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 = `
Минулі залишки відновлені з журналу руху товару й оцінені за сьогоднішніми
цінами закупки — окремої історії собівартості база не веде. Тому при різкій
зміні закупівельних цін минуле оцінюється приблизно.
`;
}
$('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(); }
});