Corp/docs/guides/COMMERCE.md

10 KiB
Raw Permalink Blame History

Commerce, МойСклад и маркетплейсы

Сводные отчёты продаж, сотрудников и производства описаны в руководстве по аналитике компании.

Commerce-контур хранит нормализованные товары, внешние привязки, продажи, возвраты, расходы, остатки и локальные изображения товаров. Источником истины для структуры данных служат модели backend/commerce/models/, для HTTP API — backend/api/v1/commerce/, для импорта — provider-specific services в backend/commerce/services/.

Импорт

Пользовательский раздел «Товары» временно скрыт для дальнейшей разработки: пункт меню и выдача товаров в глобальном поиске отключены, адреса /products и вложенные страницы перенаправляют в ленту. Код экранов, данные и HTTP API сохранены; финансовая аналитика продолжает использовать импортированные факты.

Управление доступно в Django admin → «Подключения интеграций» (/admin/commerce/integrationconnection/). Здесь можно добавить поддерживаемый сервис, заменить реквизиты, настроить автосинхронизацию и интервал. Пустые поля реквизитов сохраняют прежние значения; сохранённые секреты не показываются. Для проверки и ручного импорта отметьте подключение в списке и выберите «Проверить выбранные подключения» или «Синхронизировать выбранные подключения». Эти действия требуют права изменения подключений. Повторный импорт во время активного запуска не создаёт новую задачу.

Прогресс и ошибки доступны в «Запусках синхронизации» (/admin/commerce/integrationsyncrun/). Проекты и выплаты по проектам также доступны в админке для просмотра; исправления импортированных документов вносятся в сервисе-источнике. Новые провайдеры требуют реализации коннектора; добавление одной записи источника не создаёт интеграцию автоматически.

Синхронизация любого подключения запускается 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 не считается локальной ошибкой изображения: запуск завершается с явным сообщением о превышении времени.

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 секунд.

Проверка

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