8.2 KiB
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.
Импорт МойСклад выполняет следующие этапы:
catalog— загружает и идемпотентно нормализует ассортимент.images— синхронизирует локальные изображения.variant_links— восстанавливает связи модификаций с родителями.stocks— пакетно обновляет остатки по складам.salesиreturns— импортирует отгрузки и возвраты с позициями.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