Зазначимо: коли два покупці одночасно кладуть у кошик останній екземпляр товару, перемагає той, хто швидше натиснув «Оформити замовлення». Без системи управління залишками другий отримає повідомлення про відсутність після оплати — це overselling. Ми будуємо механізми, які виключають такі ситуації: атомарне резервування, синхронізацію з обліковими системами та сповіщення про дефіцит. Нижче — технічні деталі, які дозволять вашій платформі працювати з реальними запасами, а не з цифрами в адмінці.
Проблеми, які вирішує система
Перша — конкурентне резервування. Якщо в момент оформлення замовлення не заблокувати одиницю, два користувачі можуть купити один товар. Друга — завислі резерви. Покинуті кошики — не рідкість, і якщо не звільняти резерви, через тиждень половина залишків буде недоступна. Третя — розсинхронізація із зовнішніми обліковими системами. Коли 1С або МійСклад оновлюють кількість, а магазин продовжує продавати за старими даними, виникають заборгованості.
Схема даних
Базова структура для PostgreSQL:
CREATE TABLE product_variants ( id BIGSERIAL PRIMARY KEY, product_id BIGINT NOT NULL REFERENCES products(id), sku VARCHAR(100) NOT NULL UNIQUE, attributes JSONB NOT NULL DEFAULT '{}', stock_qty INTEGER NOT NULL DEFAULT 0, reserved_qty INTEGER NOT NULL DEFAULT 0, low_stock_threshold INTEGER NOT NULL DEFAULT 5, CHECK (stock_qty >= 0), CHECK (reserved_qty >= 0), CHECK (stock_qty >= reserved_qty) ); CREATE TABLE stock_movements ( id BIGSERIAL PRIMARY KEY, variant_id BIGINT NOT NULL REFERENCES product_variants(id), delta INTEGER NOT NULL, type VARCHAR(50) NOT NULL, reference VARCHAR(255), created_at TIMESTAMP NOT NULL DEFAULT NOW() ); available_qty = stock_qty - reserved_qty обчислюється на льоту. Лог stock_movements фіксує кожну операцію для аудиту. Індекси на (product_id, sku) та (variant_id, created_at) прискорюють пошук і звітність.
Атомарне резервування без гонок
Атомарність забезпечується через UPDATE ... RETURNING:
UPDATE product_variants SET reserved_qty = reserved_qty + :qty WHERE id = :variant_id AND (stock_qty - reserved_qty) >= :qty RETURNING id, stock_qty, reserved_qty; Якщо рядок не повернувся — товару недостатньо. В Laravel це обгортається в транзакцію з lockForUpdate(). Для високонавантажених сценаріїв використовуйте песимістичне блокування рядка, щоб уникнути взаємоблокувань.
Чому важливо звільняти резерви?
Покинутий кошик — втрачений залишок. Два підходи: TTL через чергу (точність до хвилини) та scheduled cleanup (простіше, але ±5 хвилин). Для магазинів до 1000 замовлень на день достатньо cron-завдання кожні 5 хвилин. Порівняння методів:
| Характеристика | TTL через чергу | Scheduled cleanup |
|---|---|---|
| Точність звільнення | ±1 хвилина | ±5 хвилин |
| Залежність від інфраструктури | Потрібна черга (Redis/Beanstalkd) | Тільки cron |
| Навантаження при 1000 замовлень/день | Низька | Низька |
Як налаштувати TTL в Laravel?
// В сервіс-провайдері реєструємо job use App\Jobs\ReleaseReservation; $ttl = Carbon::now()->addMinutes(30); ReleaseReservation::dispatch($variantId, $qty)->delay($ttl); Якщо замовлення оформлено, job видаляється. Якщо ні — резерв автоматично зменшується.
Інтеграція з 1С та МійСклад
Залишки надходять із зовнішніх систем двома способами:
| Режим | Опис | Коли вибрати |
|---|---|---|
| Webhook | Склад викликає ваш endpoint при зміні | Висока частота оновлень |
| Pull | Магазин опитує систему раз на N хвилин | Обмеження по безпеці |
Важливе правило: при імпорті оновлюйте тільки stock_qty, не чіпаючи reserved_qty. Інакше активні резерви злетять.
Як відображати статус на вітрині?
Для покупця важливо бачити реальну наявність. Ми обчислюємо доступну кількість та відображаємо:
- Якщо
available_qty > low_stock_threshold— «В наявності». - Якщо
0 < available_qty ≤ low_stock_threshold— «Залишилось N шт.». - Якщо
available_qty = 0— «Немає в наявності». - Якщо налаштовано передзамовлення — «Під замовлення, 5–7 днів».
Для високонавантажених сторінок статус кешується в Redis з TTL 60 секунд.
Як налаштувати сповіщення про низький залишок?
Оповіщення менеджера закупівель тригериться при оновленні stock_qty нижче порогу. В Laravel це observer:
class ProductVariantObserver { public function updated(ProductVariant $variant): void { if ($variant->wasChanged('stock_qty') && $variant->isLowStock()) { LowStockAlert::dispatch($variant); } } } Алерт надсилається в email, Telegram або Slack. Додатково можна налаштувати SMS для критичних позицій.
Як оптимізувати запити для відображення залишків?
При виведенні списку товарів типова помилка — N+1 запит: для кожного варіанту вибираємо залишок окремо. Використовуйте withCount або віконні функції. Наприклад, для PostgreSQL:
SELECT p.*, SUM(sm.delta) OVER (PARTITION BY sm.variant_id) AS current_stock FROM products p LEFT JOIN stock_movements sm ON sm.variant_id = p.id WHERE ... Цей запит виконується за один прохід. Для великих каталогів агрегуйте дані в Redis через scheduled job.
Процес впровадження
- Проектування схеми даних та вибір методів резервування.
- Реалізація атомарних операцій з тестуванням на конкурентність.
- Налаштування звільнення резервів (TTL або cron).
- Інтеграція із зовнішніми обліковими системами (1С, МійСклад).
- Розробка панелі адміністрування з фільтрами та експортом.
- Навантажувальне тестування та деплой.
До складу робіт входить: підготовка документації по схемі даних та API, передача вихідного коду та доступів до репозиторію, навчання адміністраторів роботі з панеллю, гарантійна підтримка протягом 30 днів після деплою.
Строки реалізації
| Етап | Час |
|---|---|
| Базове резервування + лог | 3–5 днів |
| Інтеграція з обліковою системою | +3–5 днів |
| Кешування та відображення на вітрині | +2 дні |
| Панель залишків в адмінці | +2–3 дні |
| Разом | 1–2 тижні |
Правильно спроектована система залишків знижує втрати від overselling на 15–20% та виключає ручний контроль. Наприклад, для магазину з 5000 замовлень на місяць усунення overselling зберігає до 150 000 гривень виручки щомісячно. Додатково автоматизація закупівель економить до 100 000 гривень на місяць на зарплаті логіста.
Зв'яжіться з нами для аудиту поточної схеми. Отримайте консультацію з архітектури вашого складу — замовте її просто зараз.







