Перейти к основному содержимому

0031. Метод учёта товарных остатков и интеграция со складом селлера

Копия ADR из репозитория: backend/docs/adr/0031-inventory-stock-accounting.md. Канонический текст — в репозитории; портал обновляется командой npm run sync:adr.

  • Статус: accepted (рекомендация спайка согласована 2026-07-07 — журнал операций + сверка с WB; «свой склад» через адаптер импорта)
  • Дата: 2026-07-07
  • Связанные задачи: OP-133 (Spike · 5.11 · Товарный учёт); потребители — OP-128 (модель номенклатуры/остатков), OP-129 (синхронизация с WB), OP-130 (модель движения), OP-131 (учёт операций), OP-132 (API остатков/истории)
  • Связанные ADR: ADR-0008 (хранение временных рядов и истории), ADR-0026 (стратегия доступа к ЛК — вся WB-часть по официальному API), ADR-0006 (rate-limit/retry для WB)

Контекст и проблема​

ТЗ 5.11 помечает «Метод учёта остатков — уточняется», а интеграцию со складом селлера/фулфилментом — «API + доступ к веб-порталу для менеджера». Нужно решить не «откуда брать данные» (WB их отдаёт), а как считать остаток и движение по цепочке из ТЗ (поставщик → склад селлера/фулфилмент → доставка до WB → склад WB → в пути до/от клиента → продажа → возврат → иные статусы).

Что закрывает официальный WB API (проверено по локальному снимку OpenAPI)​

Чтение остатков WB покрыто полностью — ЛК не нужен:

ДанныеЭндпоинтДомен
Остатки FBW (склады WB), полный снимокGET /api/v1/supplier/stocksstatistics
Детальные остатки по складам/размерам (async: task → status → download)GET /api/v1/warehouse_remainsanalytics
Остатки на складах селлера (FBS)POST /api/v3/stocks/{warehouseId}marketplace
Агрегаты остатков по группам товаровPOST /api/v2/stocks-report/products/groupsanalytics
Список складов селлера / складов WBGET /api/v3/warehouses, GET /api/v3/officesmarketplace

Пробел ровно один: участок цепочки «поставщик → свой склад селлера / фулфилмент» у WB API нет — эти движения существуют вне WB (в WMS селлера, в 1С, в Excel менеджера).

Рассмотренные варианты (метод учёта)​

Вар.СутьПлюсыМинусы
A. Зеркало WBТолько читаем снимок остатков WB и показываемТривиально, всегда сходится с WBНет «своей» цепочки движения и истории из ТЗ (учёт поступлений/списаний/перемещений)
B. Журнал операций (event-sourced) + сверка с WBКаждое движение — событие (поступление/списание/перемещение/возврат/продажа); остаток = проекция; периодическая сверка проекции с WB-снимкомПолная цепочка, история движения, аудитируемость (ТЗ)Нужен источник событий для «своей» части (ручной ввод/импорт)
C. Периодические снимки без событийХраним снимки остатков во времениПроще BТеряется причинность движения; «история» = ряд снимков, а не операции

Решение​

Принят вариант B — журнал операций + сверка с WB:

  1. Движения — append-only fact-таблица (поверх ADR-0008, партиции помесячно): тип операции, nm_id, склад, статус-звено цепочки, количество, дельта, источник, время. Остаток — проекция per (wb_account_id, nm_id, warehouse, status), пересобираемая идемпотентно.
  2. WB-часть — только официальный API (согласуется с ADR-0026): синк остатков 1/час (OP-129) пишет WB-снимок в отдельную таблицу; сверка сопоставляет проекцию с WB-снимком и помечает расхождения (не «чинит молча»).
  3. «Свой склад / фулфилмент» — адаптер источника событий: ручной ввод + CSV-импорт в MVP; опциональный коннектор к WMS селлера — за интерфейсом, вне MVP. Веб-портал менеджера из ТЗ — на стороне склада, мы принимаем от него события импортом/API, а не ходим в его UI.
  4. Оценка себестоимости: для остатков в штуках метод оценки (FIFO/средневзвешенная) не требуется. Если понадобится денежная оценка — средневзвешенная (детерминированно, проще), отдельным решением; в scope 5.11 не входит.

Последствия​

  • Положительные: реализуется цепочка движения и история из ТЗ; WB-данные — целиком по официальному API (никакого ЛК/парсинга, согласуется с ADR-0026); переиспользуются ADR-0008 (партиции/история) и ADR-0006 (rate-limit). Сверка делает расхождения видимыми, а не скрытыми.
  • Границы (осознанно): участок «свой склад/фулфилмент» без готового API в MVP — ручной ввод/CSV-импорт; денежная оценка остатков — вне scope до отдельного ADR; диаризация статусов «в пути до/от клиента» ограничена тем, что отдаёт WB (order/sale события).
  • Разблокирует: OP-131 (учёт операций финализируется как запись событий в журнал), OP-130 (модель движения = звенья цепочки как статусы), OP-132 (API остатков/истории читает проекцию и журнал), OP-129 (синк пишет WB-снимок для сверки).