Склад и синхронизация с ERP
Склады, движения остатков, резервы, заказы поставщикам и синхронизация магазина с бухгалтерией.
За 30 секунд
Учёт остатков отвечает на простой вопрос — сколько мы можем продать и где это лежит? — который становится сложным, как только появляются несколько складов, поставщики, возвраты и учётная система, которая тоже думает, что знает ответ. Надёжный подход — записывать каждое изменение остатка как движение, резервировать товар под заказы и синхронизироваться с ERP через наблюдаемые задачи по расписанию с понятными правилами, какая система какими данными владеет.
Почему я это использую
- Я использую учёт на основе движений и синхронизацию задачами по расписанию, потому что строил складской учёт для ритейлера и его синхронизацию с учётной/ERP-системой и фискальными чеками.
- Когда остатки неверны, страдают все: покупатели покупают товары, которых нет, а склады держат товар, который никто не видит онлайн.
- Учётные системы — это юридическая запись цен, клиентов и продаж. Магазин должен с ними совпадать, даже когда их API медленные или недоступны.
- Фискальные чеки во многих странах обязательны по закону; сбои в них — это не просто баги, а проблемы с соответствием законодательству.
Как это работает
01Склады, уровни остатков и движения
Склад — это любое место, где хранится товар: центральный склад, филиал магазина, остатки поставщика для дропшиппинга. Уровень остатка — текущее количество одного варианта на одном складе. Ключевая идея в том, что это число никогда нельзя просто перезаписывать. Каждое изменение — это движение остатка: строка «+20 получено по заказу поставщику 118», «−2 продано в заказе 5531», «−1 брак», «+3 корректировка по инвентаризации». Уровень остатка — это сумма движений, закэшированная для скорости. Как банковская выписка, история движений объясняет каждое число, позволяет пересобрать уровни после бага и даёт отчёты: стоимость запасов, неликвиды, оборачиваемость. Инвентаризации сравнивают физический подсчёт с системой и создают корректирующие движения на разницу.
CREATE TABLE stock_movements (
id BIGSERIAL PRIMARY KEY,
variant_id BIGINT NOT NULL,
warehouse_id BIGINT NOT NULL,
quantity INTEGER NOT NULL, -- positive in, negative out
reason TEXT NOT NULL, -- sale, receipt, transfer_out, count, waste
reference TEXT, -- order or purchase order id
created_by BIGINT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Rebuild or verify cached levels from history
SELECT variant_id, warehouse_id, SUM(quantity) AS on_hand
FROM stock_movements
GROUP BY variant_id, warehouse_id;
02Доступный остаток и резервы
«В наличии» (on hand) — это то, что физически лежит на складе. «Доступно» (available) — то, что вы ещё можете пообещать: наличие минус резерв. Резерв создаётся, когда заказ оформлен и оплачен (или пока оплата ожидается, со сроком истечения), и превращается в реальное исходящее движение, когда заказ собран и отправлен. Если заказ отменён или оплата не прошла, резерв освобождается. Витрина должна показывать доступный остаток, просуммированный по складам, которые могут обслужить покупателя. Правила исполнения выбирают источник: ближайший филиал, основной склад, поставщика, который отправляет напрямую, или backorder (продать сейчас, доставить после пополнения). Сделайте эти правила явными, потому что вопрос «какой склад это отправляет?» влияет на сроки доставки, стоимость и бухгалтерию.
03Перемещения, заказы поставщикам и поставщики
Перемещение переносит товар между складами в два шага: исходящее движение, когда товар уезжает, и входящее, когда он прибывает. В промежутке товар находится «в пути» и не принадлежит ни одной полке — смоделируйте это состояние, иначе остатки будто бы исчезают. Заказы поставщикам (purchase orders, PO) — это то, как товар попадает на склад: вы заказываете у поставщика, у PO есть ожидаемые количества и стоимость, а приёмки фиксируют, что реально пришло, — это может быть частичная поставка или отличаться от заказа. Каждая приёмка создаёт входящие движения и обновляет себестоимость, которая питает отчёты о марже и стоимости запасов. С хорошими данными можно генерировать рекомендации по дозаказу: средние дневные продажи, умноженные на срок поставки поставщика, плюс страховой запас — в сравнении с доступным и ожидаемым остатком.
- Статусы PO: черновик → отправлен → частично получен → получен → закрыт.
- Записывайте себестоимость единицы при приёмке; от неё зависит маржа.
- Отслеживайте качество работы поставщиков: опоздания и расхождения в количествах.
04Синхронизация с бухгалтерией и ERP
ERP (enterprise resource planning) или учётная система — это бэк-офис бизнеса: товары, цены, клиенты, счета и остатки для налоговых целей. Сначала определите владельца для каждого типа данных. Типичное разделение: ERP владеет кодами товаров, ценами и официальными остатками; магазин — описаниями, изображениями, SEO и онлайн-заказами. Затем синхронизируйте в направлении владения. Большинство API ERP медленные или ориентированы на пакетную обработку, поэтому синхронизируйте задачами по расписанию: забирайте изменённые цены и остатки каждые несколько минут, отправляйте новые заказы и возвраты по мере появления через очередь, а ночью запускайте полное сравнение. Делайте каждую задачу идемпотентной, сопоставляя записи по стабильным внешним ID, обрабатывайте данные пакетами и ведите лог синхронизации с количествами и ошибками. Дайте администраторам возможность включать и выключать отдельные задачи в настройках — это бесценно во время обслуживания ERP.
// Laravel scheduler: each sync is a separate, non-overlapping job
use Illuminate\Support\Facades\Schedule;
Schedule::job(new SyncPricesFromErp)->everyFiveMinutes()
->withoutOverlapping()->when(fn () => settings('erp.sync_prices'));
Schedule::job(new SyncStockFromErp)->everyTenMinutes()
->withoutOverlapping()->when(fn () => settings('erp.sync_stock'));
Schedule::job(new CompareCatalogueWithErp)->dailyAt('03:00')
->withoutOverlapping();
Schedule::job(new CheckErpHealth)->everyFiveMinutes(); // alerts admins on failures
05Конфликты, health checks и страховочные сетки
Синхронизации ломаются тихо: ERP недоступна, код товара переименовали, импорт цен содержит ноль. Защищайтесь. Health checks отслеживают время последнего успешного запуска каждой задачи и поднимают алерт, если оно слишком давнее или резко вырос процент ошибок. Правила здравого смысла отклоняют подозрительные изменения — цена упала на 90%, все остатки стали нулевыми — и откладывают их на проверку. Сохраняйте предыдущие значения, чтобы администратор мог откатить неудачный импорт цен одним кликом. В конфликтах побеждает владелец: если поле изменили обе стороны, берите значение из системы-владельца и логируйте перезапись. Ночной отчёт сравнения перечисляет товары, которые есть только на одной стороне или отличаются, чтобы люди исправляли данные в источнике, а не латали магазин.
06Фискальные чеки
Во многих странах каждая продажа должна регистрироваться в налоговой через фискальное устройство или облачный сервис, который возвращает номер фискального чека (часто его печатают или показывают как QR-код). Для возвратов нужны собственные фискальные чеки возврата, связанные с исходным. Относитесь к этому как к платёжной интеграции: создайте запись чека со статусом, отправляйте её через очередь с повторами, храните фискальный номер в заказе и поднимайте алерты при сбоях. Повторяйте только тогда, когда это безопасно: если сервис мог зарегистрировать чек до таймаута, сначала проверьте, чтобы избежать дублей. Филиалам или кассам, которые могут работать офлайн, нужен офлайн-режим, который ставит чеки в очередь и отправляет их, когда связь вернётся, в пределах срока, разрешённого законом.
Когда применять
- Товар хранится больше чем в одном месте, или онлайн- и офлайн-продажи делят общие остатки.
- В бизнесе уже работает учётная или ERP-система, которая должна оставаться источником истины для цен и остатков.
- Продажа товара, которого нет, обходится дорого: скоропортящиеся товары, ограниченные тиражи, дропшиппинг от поставщика.
- Местный закон требует фискальной регистрации каждой продажи.
Когда не стоит
- Товары под заказ или цифровые товары без остатков, которые нужно отслеживать.
- Один небольшой склад, где хватает простой колонки количества с атомарными обновлениями.
- Когда у ERP есть готовый поддерживаемый коннектор для вашей платформы, — используйте его, прежде чем писать свой.
Альтернативы
- Учёт остатков в платформе магазина
Shopify и WooCommerce отслеживают остатки по локациям; хватает для простых случаев, но слабо для заказов поставщикам и учёта себестоимости.
- Специализированные инструменты учёта / WMS
Системы управления складом обрабатывают сборку, ячейки и перемещения; магазин синхронизируется с ними, а не владеет остатками сам.
- ERP как единственный источник остатков
Магазин читает остатки из ERP почти в реальном времени; самое простое владение, но каждый сбой блокирует продажи.
Лучшие практики
- Записывайте каждое изменение остатка как движение с причиной и ссылкой.
- Разделяйте остатки в наличии, в резерве и доступные.
- Определите владельца каждого поля до того, как писать код синхронизации.
- Запускайте синхронизации как идемпотентные, непересекающиеся задачи по расписанию с привязкой к внешним ID.
- Отслеживайте время последнего успеха каждой задачи и поднимайте алерт при устаревших или падающих синхронизациях.
- Отклоняйте подозрительные массовые изменения и храните предыдущие значения для отката.
- Ставьте фискальные чеки в очередь с безопасными повторами и храните их номера в заказе.
Частые ошибки
- Напрямую перезаписывать количества остатков, теряя историю, почему они изменились.
- Двусторонняя синхронизация без правил владения, из-за которой системы по кругу перезаписывают друг друга.
- Задачи синхронизации, которые пересекаются и обрабатывают один и тот же пакет дважды.
- Тихие сбои синхронизации, которые замечают, только когда покупатели жалуются на цены.
- Игнорирование товара в пути при перемещениях.
Глубже
07Согласованность движений и закэшированных остатков
Движения — это история, а остатки — это кэш, поэтому они никогда не должны расходиться. Записывайте и то и другое в одной транзакции: вставляйте движение и обновляйте остаток условным запросом, который не может уйти в минус, если только не разрешён backorder. Порядок блокировок важен, когда одна операция затрагивает несколько строк, например перемещение или заказ из нескольких строк: всегда блокируйте строки в одном и том же порядке (например, по variant_id, затем по warehouse_id), чтобы избежать deadlock-ов. Держите ночную задачу проверки, которая суммирует движения по варианту и складу и сравнивает их с закэшированным остатком; любое расхождение — это баг, который нужно расследовать, а не молча исправлять. При большом объёме записей движения можно партиционировать по месяцам, а старые месяцы сворачивать во входящие остатки.
BEGIN;
-- 1. Decrease the cached level only if enough is available
UPDATE stock_levels
SET on_hand = on_hand - 3
WHERE variant_id = 42 AND warehouse_id = 1 AND on_hand - reserved >= 3;
-- 0 rows → ROLLBACK and report out of stock
-- 2. Record why it changed
INSERT INTO stock_movements (variant_id, warehouse_id, quantity, reason, reference)
VALUES (42, 1, -3, 'sale', 'order:5531');
COMMIT;
08Оценка запасов и методы расчёта себестоимости
Запасы — это деньги на полках, поэтому финансисты спрашивают, сколько они стоят и какова себестоимость каждой продажи. Это зависит от метода расчёта себестоимости. Средневзвешенная себестоимость пересчитывает стоимость единицы при каждом поступлении: (старое количество × старая стоимость + поступившее количество × новая стоимость) / общее количество. Это просто и распространено в ERP. FIFO (first in, first out) предполагает, что первыми продаются самые старые единицы, поэтому каждая продажа расходует слои себестоимости начиная с самого старого поступления; нужны слои по каждому поступлению, зато растущие цены отражаются точно. Что бы вы ни выбрали, метод должен совпадать с ERP или учётной системой, иначе маржа в магазине и в бухгалтерии будет расходиться. Записывайте себестоимость в движение продажи в момент продажи — так же, как снимки цен в заказах, — чтобы последующие поступления не переписывали историческую маржу.
- Средневзвешенная: одна себестоимость на вариант и склад, обновляется при поступлении.
- FIFO: слои себестоимости по каждому поступлению, расходуются начиная со старейших.
- Возвраты приходуются по той себестоимости, с которой ушли, а не по сегодняшней.
09Паттерны интеграции: polling, события и outbox
Узнавать об изменениях в другой системе можно двумя способами: polling (по расписанию спрашивать «что изменилось с момента T?») и события (другая система присылает webhook-и или сообщения). Многие ERP поддерживают только polling. Опрашивайте с watermark — последним обработанным timestamp или ID изменения, — который хранится для каждой задачи, и слегка перекрывайте окно, чтобы пережить рассинхрон часов, полагаясь на идемпотентные upsert-ы, которые делают повторное чтение безвредным. Для данных, которые вы отправляете, используйте паттерн transactional outbox: в той же транзакции базы, что и заказ, вставляйте строку в таблицу outbox; воркер читает outbox и отправляет данные в ERP, повторяя попытки до подтверждения. Это гарантирует, что закоммиченный заказ рано или поздно будет выгружен, а откатившийся — никогда, и всё без распределённых транзакций.
CREATE TABLE outbox (
id BIGSERIAL PRIMARY KEY,
topic TEXT NOT NULL, -- erp.order_created, erp.return_created
payload JSONB NOT NULL,
attempts INT NOT NULL DEFAULT 0,
sent_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Worker: claim a batch without blocking other workers
SELECT id, topic, payload FROM outbox
WHERE sent_at IS NULL
ORDER BY id
LIMIT 50
FOR UPDATE SKIP LOCKED;
10Распределение по нескольким складам и раздельные отгрузки
При нескольких складах каждый заказ — это маленькая задача оптимизации: какие локации должны выполнить какие строки? Типичные правила по приоритету: если возможно, отгружать всё из одной локации (одна посылка дешевле), предпочитать локацию, ближайшую к покупателю, не опустошать выставочный запас филиала и использовать dropship от поставщика или backorder только в крайнем случае. Опишите правила как функцию оценки и записывайте решение в заказ, чтобы сотрудники склада и поддержка видели, почему выбрана именно эта локация. Раздельным отгрузкам нужны свои записи: один заказ, несколько отгрузок, у каждой — строки, трекинг и статус, а заказ становится доставленным только тогда, когда доставлены все отгрузки. Резервировать нужно по складам на этапе оформления, а не позже, иначе распределение может провалиться уже после того, как покупатель заплатил.
Реальные ситуации
Импорт цен выставил половине каталога цену 0.01
Проблема
Команда ERP за ночь меняет формат прайс-листа. Синхронизация цен каждые 5 минут импортирует тысячи товаров по 0.01, и на рассвете покупатели начинают их заказывать.
Как решить
Остановите потери: выключите синхронизацию цен в настройках админки, откатите цены из сохранённых предыдущих значений и вместе с бизнесом решите, как поступить с заказами по неверным ценам согласно условиям. Затем добавьте защиту, чтобы это не повторилось: отклоняйте пакет, если больше заданной доли товаров меняется, скажем, более чем на 50%, отказывайтесь принимать нулевые или почти нулевые цены без явного разрешения и отправляйте отклонённые пакеты на ручное утверждение с отчётом о различиях. Валидируйте схему payload, чтобы изменение формата громко падало, а не парсилось в ерунду. Поднимайте алерт дежурному, когда пакет задержан.
Онлайн-остаток показывает 5, а полка пуста
Проблема
Сотрудники филиалов регулярно отменяют онлайн-заказы на самовывоз, потому что товаров, показанных как доступные, физически нет. Покупатели перестают доверять отображаемому остатку.
Как решить
Найдите, где реальность и система расходятся. Обычные причины: продажи в магазине записываются в ERP, но синхронизируются только раз в час; повреждённые или украденные товары нигде не фиксируются; выставочный запас считается доступным к продаже. Сократите интервал синхронизации остатков или отправляйте продажи в магазине событиями, добавьте тип движения «списание», который сотрудники могут записать с телефона, и введите циклическую инвентаризацию — пересчёт нескольких товаров каждый день вместо годовой инвентаризации. Показывайте онлайн-доступность со страховым буфером по филиалу (доступно минус одна-две единицы для ходовых товаров). Отслеживайте причины отмен по филиалам; отчёт быстро покажет, каким локациям нужно менять процессы, а не код.
API ERP ограничен 60 запросами в минуту
Проблема
Полное сравнение каталога из 40 000 товаров занимает часы и блокирует выгрузку заказов, которая использует ту же квоту API.
Как решить
Используйте квоту осознанно. Выясните, есть ли у ERP bulk- или delta-эндпоинты, например файлы выгрузки или запросы «изменено с», которые стоят гораздо меньше запросов, чем вызовы по каждому товару. Пропускайте все вызовы ERP через один клиент с общим rate limiter-ом на основе token bucket и давайте выгрузкам заказов и возвратов приоритет над фоновым сравнением, чтобы критичные для выручки данные проходили всегда. Запускайте полное сравнение ночью, постранично, с возможностью продолжить с последней обработанной страницы. Кэшируйте неизменные справочные данные. Измеряйте расход квоты по задачам, и если бюджета всё равно не хватает, это конкретный аргумент для переговоров с поставщиком ERP о повышении лимита.
Шпаргалка
- Пересобрать остатки из движений
SELECT variant_id, warehouse_id, SUM(quantity) FROM stock_movements GROUP BY 1, 2;- Безопасно забрать строки из outbox
SELECT ... FROM outbox WHERE sent_at IS NULL ORDER BY id LIMIT 50 FOR UPDATE SKIP LOCKED;- Идемпотентный upsert по внешнему ID
INSERT INTO products (erp_id, price) VALUES (:id, :price) ON CONFLICT (erp_id) DO UPDATE SET price = EXCLUDED.price;- Задача без наложений на одном сервере (Laravel)
Schedule::job(new SyncStockFromErp)->everyTenMinutes()->withoutOverlapping()->onOneServer();- Средневзвешенная себестоимость при поступлении
new_cost = (qty_old * cost_old + qty_in * cost_in) / (qty_old + qty_in)- Точка перезаказа
reorder_point = avg_daily_sales * lead_time_days + safety_stock- Проверка зависшей синхронизации
SELECT job FROM sync_runs GROUP BY job HAVING MAX(finished_at) < now() - interval '30 minutes';- Обработка больших таблиц пачками (Laravel)
Product::query()->chunkById(500, fn ($products) => $this->compare($products));
В моих реальных проектах
- На платформе товаров для животных я построил складской учёт из 13 моделей: склады, уровни и движения остатков, перемещения, инвентаризации, поставщики, заказы поставщикам и приёмки, очередь исполнения через поставщиков — с отчётами по рекомендациям дозаказа, стоимости запасов, неликвидам и работе поставщиков.
- Та же платформа синхронизируется с учётной/ERP-системой через 12 сервисных классов — каталог, цены, клиенты, лояльность, фискальные чеки, экспорт заказов и возвратов, сравнение каталога и откат цен, алерты о состоянии — в виде задач по расписанию, которые включаются в настройках админки.
- В CRM для сети спортзалов я интегрировал облачный сервис фискальных чеков с привязкой устройств по филиалам и упорядоченными, непересекающимися задачами по расписанию; POS SaaS обрабатывает чеки продажи и возврата, офлайн-варианты и повторы.
Вопросы и ответы
Q1Почему движения остатков лучше, чем просто колонка количества?
Колонка количества говорит только текущее число и не может его объяснить. С движениями каждая приёмка, продажа, возврат, перемещение и корректировка — это строка с причиной, ссылкой и автором, а количество — их сумма. Можно расследовать расхождения, пересобрать уровни после бага, строить отчёты по стоимости запасов и неликвидам и точно видеть, когда товар закончился. На практике я храню закэшированный уровень остатка для быстрого чтения и обновляю его в той же транзакции, что и движение.
Q2В чём разница между остатком в наличии и доступным остатком?
В наличии — то, что физически лежит на складе. Доступно — то, что вы ещё можете продать: наличие минус товар, зарезервированный под оформленные, но ещё не отправленные заказы. Витрина должна показывать доступный остаток, иначе два покупателя могут купить один и тот же последний товар. Резервы создаются при оформлении заказа, превращаются в исходящие движения при отправке и освобождаются при отмене или неудачной оплате. Ожидаемое поступление по заказам поставщикам можно показывать отдельно для backorder.
Q3Как надёжно синхронизировать магазин с ERP?
Начните с владения: для каждого поля решите, какая система — источник истины. Затем синхронизируйте в этом направлении идемпотентными задачами по расписанию, привязанными к стабильным внешним ID, с обработкой пакетами и защитой от пересекающихся запусков. События вроде новых заказов отправляйте через очередь с повторами. Логируйте каждый запуск, отслеживайте последний успех каждой задачи и поднимайте алерт, если он устарел. Добавьте проверки на подозрительные массовые изменения, храните предыдущие значения для отката и запускайте ночной отчёт сравнения, чтобы ловить расхождения.
Q4Что происходит, когда ERP недоступна?
Магазин должен продолжать продавать. Цены и остатки уже закэшированы локально с последней успешной синхронизации, поэтому витрина работает. Исходящие данные, такие как заказы и возвраты, ждут в очереди и повторяются с backoff, пока ERP не вернётся. Health checks оповещают администраторов, а администратор может поставить задачу на паузу на время планового обслуживания. Если точность остатков критична, можно добавить страховой буфер — показывать чуть меньше доступного товара, — пока синхронизация устарела.
Q5Как избежать дублирования фискальных чеков?
Каждый заказ получает одну запись фискального чека со статусом и уникальным идентификатором. Отправка происходит в задаче из очереди. Если вызов явно упал до того, как дошёл до сервиса, повторять безопасно. Если он завершился по таймауту, чек мог уже зарегистрироваться, поэтому задача сначала спрашивает у сервиса, существует ли чек с этим идентификатором. Только после этого она повторяет отправку. Фискальный номер хранится в заказе, а возвраты создают отдельные чеки возврата, связанные с исходным.
Вопросы уровня senior
S1Кто должен быть источником истины по остаткам — магазин или ERP?
Зависит от того, где происходят изменения остатков и насколько надёжна ERP. Если большинство движений происходит в ERP — поступления на склад, продажи в магазинах, перемещения, — то остатками должна владеть ERP, а магазин держит кэш почти в реальном времени с резервированиями поверх. Если основную часть продаж делает магазин, а ERP медленная или работает пакетами, магазин может владеть операционными остатками и отправлять движения в ERP для учёта. Худший вариант — совместное владение без правил. Какая бы сторона ни владела, другая должна переносить устаревание данных: показывать страховой буфер, перепроверять на оформлении и сверяться ночью. Я принимаю решение вместе с клиентом, перечисляя каждое событие, которое меняет остаток, и место, где оно возникает.
S2Как гарантировать, что каждый заказ попадает в ERP ровно один раз?
Доставку ровно один раз через сеть в общем случае обеспечить нельзя, поэтому я строю доставку как минимум один раз плюс идемпотентную обработку, что даёт эффект ровно одного раза. Заказ и строка outbox коммитятся в одной транзакции, так что ничего не теряется. Воркер отправляет строки outbox и помечает их отправленными только после подтверждения от ERP. Если подтверждение потерялось, строка отправляется снова, а сторона ERP дедуплицирует по нашему номеру заказа — через уникальное поле внешнего ID или запрос по этому номеру перед созданием. Если ERP не умеет дедуплицировать, воркер должен сначала делать запрос при любом повторе после таймаута. Мониторинг поднимает алерт по возрасту записей в outbox, так что зависшие выгрузки видны в течение нескольких минут.
S3Как масштабировать запись остатков для распродажи с большим объёмом?
Узкое место — конкуренция за несколько горячих строк: один и тот же популярный вариант на одном и том же складе. Варианты по возрастанию сложности: держать транзакции очень короткими и блокировать строки в одном порядке; вынести некритичную работу, например движения для аналитики и выгрузку в ERP, из горячей транзакции в outbox; заранее разделить остаток горячих товаров на несколько бакетов, которые уменьшаются независимо и суммируются для отображения; или поставить перед горячими товарами атомарный счётчик в Redis, который выдаёт резервирования, с периодической сверкой с базой. Каждый шаг меняет простоту на пропускную способность, а подход с Redis добавляет второй источник истины, требующий аккуратного восстановления. Сначала я измеряю ожидание блокировок, потому что большинству магазинов никогда не нужно больше первых двух шагов.
Глоссарий
- On hand (фактический остаток)
- Единицы, которые прямо сейчас физически находятся на складе.
- Available (доступно)
- Единицы, которые ещё можно продать: фактический остаток минус зарезервированное.
- Движение запаса
- Запись об одном изменении остатка с количеством, причиной, ссылкой на документ и автором.
- Циклическая инвентаризация
- Регулярный пересчёт небольшого меняющегося набора товаров вместо пересчёта всего склада сразу.
- Средневзвешенная себестоимость
- Стоимость единицы, пересчитываемая при каждом поступлении как среднее между стоимостью старого и нового запаса.
- FIFO
- First in, first out: первыми расходуются самые старые запасы и их себестоимость.
- Transactional outbox
- Запись исходящих сообщений в таблицу в той же транзакции, что и данные, с последующей асинхронной отправкой.
- Watermark
- Последний обработанный timestamp или ID изменения, чтобы следующий опрос забирал только более новые изменения.
- Страховой запас
- Дополнительный запас на случай всплесков спроса и задержек у поставщиков.
Что изучить дальше
Есть идея продукта?Давайте создадим его.
Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.