Corp/docs/guides/COMMERCE.md

111 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Commerce, МойСклад и маркетплейсы
Commerce-контур хранит нормализованные товары, внешние привязки, продажи,
возвраты, расходы, остатки и локальные изображения товаров. Источником истины
для структуры данных служат модели `backend/commerce/models/`, для HTTP API —
`backend/api/v1/commerce/`, для импорта — provider-specific services в
`backend/commerce/services/`.
## Импорт
Синхронизация любого подключения запускается Celery-задачей
`commerce.sync_connection_run`; сервис выбирается по `connection.provider`.
Задача публикуется только после commit транзакции, а заранее сформированный
`task_id` до публикации сохраняется и в подключении, и в истории запуска.
Долгие импорты направляются в очередь `commerce`: worker должен слушать её
отдельно либо вместе с очередью `celery`. Для работы ручного импорта нужен
Celery worker, для расписания и восстановления зависших запусков — также Celery
Beat.
Импорт МойСклад выполняет следующие этапы:
1. `catalog` — загружает и идемпотентно нормализует ассортимент.
2. `images` — синхронизирует локальные изображения.
3. `variant_links` — восстанавливает связи модификаций с родителями.
4. `stocks` — пакетно обновляет остатки по складам.
5. `sales` и `returns` — импортирует отгрузки и возвраты с позициями.
6. `finalizing` — сохраняет итоговую статистику и состояние подключения.
На каждом длительном этапе сохраняются `stage`, `stage_processed`,
`stage_total` и `last_progress_at`; обновление отправляется frontend через
общий WebSocket. Ошибка отдельного изображения учитывается в `image_errors`,
но не отменяет импорт товарных и финансовых данных.
Celery Beat раз в минуту выполняет reconciliation. Запуски, слишком долго
оставшиеся в `queued`, и `running` без свежего heartbeat переводятся в `error`
без удаления истории и без автоматического повторного выполнения. Таймауты
задаются `COMMERCE_QUEUED_STALE_SECONDS` и
`COMMERCE_RUNNING_STALE_SECONDS`. Ручная безопасная проверка:
Долгие импорты имеют отдельные лимиты Celery:
`COMMERCE_TASK_SOFT_TIME_LIMIT` (по умолчанию 90 минут) и
`COMMERCE_TASK_TIME_LIMIT` (по умолчанию 120 минут). Мягкий лимит всегда должен
быть меньше жёсткого. `SoftTimeLimitExceeded` не считается локальной ошибкой
изображения: запуск завершается с явным сообщением о превышении времени.
```bash
python manage.py reconcile_commerce_sync_runs --dry-run
python manage.py reconcile_commerce_sync_runs
```
## Wildberries
Подключение принимает API-токен с категорией «Финансы». Проверка токена читает
`GET https://common-api.wildberries.ru/api/v1/seller-info`, сохраняет ID,
название магазина и ИНН без сохранения токена в открытом виде, а затем вызывает
`GET https://finance-api.wildberries.ru/ping`, чтобы подтвердить доступ именно
к финансовой категории.
Финансовая синхронизация использует актуальный
`POST https://finance-api.wildberries.ru/api/finance/v1/sales-reports/detailed`.
Отключённый 15 июля 2026 года legacy-метод `reportDetailByPeriod` не используется.
Каждая строка отчёта сохраняется как `SourceRecord`, затем идемпотентно создаёт
или обновляет `Sale`, `SaleLine` и связанные `Cost`. Поддерживаются продажи,
возвраты, комиссия, эквайринг, доставка, хранение, приёмка, удержания, штрафы и
расходы программы лояльности.
Товар сопоставляется сначала по существующей WB-привязке, затем по артикулу и
штрихкоду. Неоднозначные строки сохраняются, но не привязываются к случайному
товару; их количество видно в `unmatched_products`. Повторный импорт после
ручной привязки дополняет аналитику.
Один ответ ограничен 100 000 строками. После каждой непустой страницы `rrdId` и
границы периода сохраняются в metadata источника, а следующий запуск продолжает
импорт. Завершённым отчёт считается только после ответа WB `204`; длина страницы
не используется как признак конца. После завершения первичной истории каждый
запуск повторно проверяет последние 30 дней, чтобы корректировки финансового
отчёта обновили существующие факты.
## Изображения
`ProductImage` хранит оригинал и миниатюру в `MEDIA_ROOT/commerce/products/`.
Временные внешние ссылки МойСклад не возвращаются клиенту и не используются как
публичные URL. Повторная синхронизация сравнивает внешний размер и время
изменения, поэтому неизменённое изображение не скачивается повторно. Удалённые
в источнике изображения удаляются локально после успешного чтения списка.
Изображения сверх безопасного лимита декодирования уменьшаются изолированным
процессом ImageMagick до допустимого размера, не увеличивая лимит Pillow в
процессе worker. Ошибка одного файла оставляет остальные изображения товара и
весь импорт работоспособными.
REST `ProductSerializer` возвращает `images[]` с локальными `url` и
`thumbnail_url`. Каталог использует миниатюру, карточка товара — оригинал для
полноэкранного просмотра.
## Realtime и fallback
Событие `commerce_connection_updated` содержит состояние подключения и
последнего запуска, включая этап, heartbeat, прогресс и счётчики. При рабочем
WebSocket frontend не опрашивает connections endpoint. Если WebSocket
недоступен во время активного импорта, включается REST fallback раз в 30 секунд.
## Проверка
```bash
cd backend
pytest tests/commerce -q
python manage.py makemigrations --check --dry-run
python manage.py check
ruff check commerce api/v1/commerce tests/commerce
```