feat: добавлена интеграция синхронизации с Recommerce

This commit is contained in:
2026-01-12 21:45:31 +03:00
parent a5ab216934
commit 707b45b16d
13 changed files with 475 additions and 104 deletions

View File

@@ -0,0 +1,119 @@
import logging
from celery import shared_task
from django.db import transaction
from django_tenants.utils import schema_context
from integrations.models import RecommerceIntegration
from integrations.recommerce.services import RecommerceService
from products.models import Product
from integrations.recommerce.exceptions import RecommerceError, RecommerceAPIError
logger = logging.getLogger(__name__)
@shared_task(bind=True)
def sync_products_batch_task(self, product_ids, options=None, schema_name=None):
"""
Celery задача для массовой синхронизации товаров с Recommerce.
Args:
product_ids (list): Список ID товаров (PK) для синхронизации
options (dict): Настройки синхронизации
- fields (list): Список полей для обновления ['price', 'count', 'content', 'images']
- create_if_missing (bool): Создавать товар, если он не найден (404)
schema_name (str): Имя схемы тенанта для выполнения запросов
"""
if options is None:
options = {}
fields = options.get('fields', [])
create_if_missing = options.get('create_if_missing', False)
# Используем schema_context для выполнения запросов в правильной tenant схеме
if schema_name:
with schema_context(schema_name):
return _do_sync(product_ids, fields, create_if_missing)
else:
return _do_sync(product_ids, fields, create_if_missing)
def _do_sync(product_ids, fields, create_if_missing):
"""
Внутренняя функция для выполнения синхронизации в контексте схемы.
Args:
product_ids (list): Список ID товаров (PK) для синхронизации
fields (list): Список полей для обновления ['price', 'count', 'content', 'images']
create_if_missing (bool): Создавать товар, если он не найден (404)
"""
# 1. Получаем интеграцию
integration = RecommerceIntegration.objects.filter(is_active=True).first()
if not integration or not integration.is_configured:
msg = "Recommerce integration is not active or configured."
logger.error(msg)
return {"success": False, "error": msg}
service = RecommerceService(integration)
# 2. Получаем товары
products = Product.objects.filter(pk__in=product_ids)
results = {
"total": len(product_ids),
"success": 0,
"failed": 0,
"created": 0,
"updated": 0,
"errors": []
}
logger.info(f"Starting Recommerce sync for {len(product_ids)} products. Fields: {fields}, create_if_missing: {create_if_missing}")
for product in products:
try:
# Если fields пустой или содержит 'all' - обновляем всё (передаем None в сервис)
# Иначе передаем список полей.
# В сервисе update_product(fields=None) обновляет всё.
# Маппинг опций фронтенда на логику сервиса
# Фронт: ['price', 'count', 'content', 'images']
# Сервис: ожидает список полей или None (всё).
# Если выбраны не все галочки, передаем конкретные поля.
# Но 'content' и 'images' в сервисе могут не поддерживаться напрямую как ключи,
# нужно смотреть реализацию to_api_product.
# Пока передаем как есть, предполагая, что сервис или маппер разберется,
# либо если выбрано "все", передаем None.
# Упрощение: если выбраны все основные группы, считаем это полным обновлением
is_full_update = False
if 'content' in fields and 'images' in fields and 'price' in fields and 'count' in fields:
is_full_update = True
service_fields = None if is_full_update else fields
# Если список полей пуст (ничего не выбрано), но задача запущена - странно, но пропустим
if not is_full_update and not service_fields:
logger.warning(f"Product {product.id}: No fields selected for update.")
continue
try:
service.update_product(product, fields=service_fields)
results["updated"] += 1
results["success"] += 1
except RecommerceAPIError as e:
# Если товар не найден (404) и разрешено создание
if e.status_code == 404 and create_if_missing:
logger.info(f"Product {product.sku} not found. Creating...")
service.create_product(product)
results["created"] += 1
results["success"] += 1
else:
raise e
except Exception as e:
results["failed"] += 1
error_msg = f"Product {product.sku} ({product.id}): {str(e)}"
logger.error(error_msg)
results["errors"].append(error_msg)
logger.info(f"Recommerce sync completed. Results: {results}")
return results