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

0017. Учёт комиссий/логистики/штрафов из отчёта реализации WB

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

  • Статус: accepted
  • Дата: 2026-06-24
  • Связанные задачи: OP-55 (5.1 · Оцифровка продаж / финансовый учёт)

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

ТЗ 5.1: для корректного расчёта прибыли (OP-56) нужны финансовые составляющие — комиссии, логистика, штрафы. Источник — отчёт WB о реализации. Нужна загрузка, привязка к продаже/артикулу/периоду, нормализация типов начислений и агрегация по артикулу. Блокер OP-54 (импорт WB) — Done. Расширяем модуль digitization.

Рассмотренные варианты​

Источник WB​

  1. /api/v5/supplier/reportDetailByPeriod (выбран, scope statistics). Детализация отчёта о реализации: по строке — комиссия (ppvz_sales_commission), логистика (delivery_rub), штраф (penalty), эквайринг, хранение, удержания, к перечислению (ppvz_for_pay). Пагинация по rrdid. Это канонический финотчёт WB на уровне строки.
  2. /api/v1/supplier/sales (OP-54) — там только упрощённые forPay/finishedPrice, без разбивки комиссий/логистики/штрафов. Недостаточно для учёта затрат.

Модель хранения​

  1. Одна таблица wb_report_lines (выбран): строка отчёта со всеми финполями + raw. Нормализация начислений в типы — на лету чистой extractCharges(line); агрегаты считаются чистой aggregateByArticle.
    • Плюсы: повторяет источник 1:1, идемпотентность по rrd_id, raw future-proof, нет дублирования данных в отдельную charge-таблицу.
  2. Нормализованная wb_charges (строка на каждое начисление, enum type). Отвергнут для базового учёта: взрывает каждую строку отчёта в N charge-строк, усложняет идемпотентность и не нужен для агрегатов (их даёт extractCharges+aggregateByArticle над wb_report_lines). Можно ввести позже, если понадобятся per-charge запросы.

Расчёт агрегатов​

  1. Чистая aggregateByArticle(lines) (выбран): группировка по nm_id, сумма по типам — без БД, детерминированно, легко покрыть тестами на эталонных данных (AC3). Сервис читает строки за период и зовёт функцию.
  2. SQL GROUP BY nm_id, SUM(...). Отвергнут как основной: расчёт «размазан» по SQL, сложнее юнит-тестировать нормализацию начислений; SQL-агрегация — возможная оптимизация позже.

Решение​

В модуле digitization:

  • WbReportLineEntity (wb_report_lines): финполя строки отчёта, FK wb_account_id → wb_accounts (CASCADE), unique (wb_account_id, rrd_id), индексы по (wb_account_id, nm_id) и периоду, raw jsonb.
  • ChargeType (commission/logistics/fine/acquiring/storage/deduction) + чистый extractCharges(line) (ненулевые затраты) — нормализация. additional_payment (доплата/корректировка вознаграждения ВВ) — НЕ начисление-затрата (знаковая выплата продавцу), поэтому не входит в extractCharges/total; значение хранится в колонке и raw для P&L (OP-56).
  • Фильтр периода в summaryByArticle — по дню операции в московском времени ((rr_dt AT TIME ZONE 'Europe/Moscow')::date), с fallback на date_from для строк без rr_dt; иначе session-TZ (UTC) сдвинул бы день на границах и null-rr_dt строки выпадали бы из агрегата.
  • aggregateByArticle(lines) — суммы по артикулу × тип + итого.
  • WbFinancialReportService: importReport(actor, dto) (пагинация по rrdid, idempotent upsert, per-record resilience как OP-54) + summaryByArticle(actor, dto) (scope кабинета через getById(actor,id), чтение строк, агрегатор).
  • Контроллер: POST /digitization/financial-report/import, GET /digitization/financial-report/summary.
  • Общие хелперы parseWbDate(MSK)/wbMoney вынесены в wb-mapping.util.ts и переиспользованы OP-54-маппером (DRY, единый источник MSK-логики).

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

  • Положительные: появился учёт затрат — вход для расчёта прибыли (OP-56). Нормализация и агрегаты — чистые функции, полностью покрыты тестами. srid сшивает строки отчёта с продажами/заказами OP-54.
  • Границы: себестоимость (закупочные цены) — не здесь (товарный учёт 5.11); P&L — OP-56; ежедневная авто-синхронизация — OP-58. Per-charge нормализованная таблица — отложена.
  • Деньги — numeric(14,2); агрегатор оперирует числами и возвращает строки фиксированной точности.
  • MSK-обработка дат — общая с OP-54 (отчёт тоже в московском времени).