119 lines
5.8 KiB
Python
119 lines
5.8 KiB
Python
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 |