feat(integrations): добавлена полная интеграция с Recommerce
Реализован клиент для работы с API Recommerce, включая: - Клиент с методами для работы с товарами и заказами - Сервисный слой для высокоуровневых операций - Мапперы данных между форматами - Обработку исключений Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
125
myproject/integrations/recommerce/mappers.py
Normal file
125
myproject/integrations/recommerce/mappers.py
Normal file
@@ -0,0 +1,125 @@
|
||||
from typing import Dict, Any, List, Optional
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
def to_api_product(product: Any, stock_count: Optional[int] = None, fields: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Преобразование внутреннего товара в формат Recommerce API.
|
||||
|
||||
Args:
|
||||
product: Экземпляр модели Product
|
||||
stock_count: Остаток товара (вычисляется отдельно, т.к. зависит от склада)
|
||||
fields: Список полей для экспорта. Если None - экспортируются все базовые поля.
|
||||
Поддерживаемые поля: 'sku', 'name', 'price', 'description', 'count', 'images'.
|
||||
|
||||
Returns:
|
||||
Dict: JSON-совместимый словарь для API
|
||||
"""
|
||||
data = {}
|
||||
|
||||
# Если поля не указаны, берем базовый набор (без тяжелых полей типа картинок)
|
||||
if fields is None:
|
||||
fields = ['sku', 'name', 'price']
|
||||
|
||||
# SKU (Артикул) - обязательное поле для идентификации
|
||||
# Если product.sku нет, используем id (как fallback, но лучше sku)
|
||||
sku = getattr(product, 'sku', str(product.id))
|
||||
|
||||
# Всегда добавляем SKU если это не обновление, но для update метода API SKU идет в URL.
|
||||
# Но маппер просто готовит данные.
|
||||
|
||||
if 'sku' in fields:
|
||||
data['sku'] = sku
|
||||
|
||||
if 'name' in fields:
|
||||
data['name'] = product.name
|
||||
|
||||
if 'price' in fields:
|
||||
# Recommerce ожидает price[amount] и price[currency]
|
||||
# Предполагаем, что валюта магазина совпадает с валютой Recommerce (BYN)
|
||||
data['price'] = {
|
||||
'amount': float(product.sale_price or 0),
|
||||
'currency': 'BYN' # Можно вынести в настройки
|
||||
}
|
||||
|
||||
if 'description' in fields:
|
||||
data['description'] = product.description or ''
|
||||
|
||||
if 'count' in fields and stock_count is not None:
|
||||
data['count'] = stock_count
|
||||
|
||||
if 'images' in fields:
|
||||
# Примерная логика для картинок
|
||||
# Recommerce ожидает массив URL или объектов.
|
||||
# Документация: images[] - Картинки товара
|
||||
images = []
|
||||
if hasattr(product, 'images'):
|
||||
for img in product.images.all():
|
||||
if img.image:
|
||||
images.append(img.image.url)
|
||||
if images:
|
||||
data['images'] = images
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def from_api_order(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Преобразование заказа из Recommerce в DTO (словарь) для создания внутреннего заказа.
|
||||
|
||||
Args:
|
||||
data: JSON заказа от Recommerce
|
||||
|
||||
Returns:
|
||||
Dict: Структурированные данные для создания Order
|
||||
"""
|
||||
# Извлекаем основные поля
|
||||
recommerce_id = data.get('id')
|
||||
customer_data = data.get('customer', {})
|
||||
delivery_data = data.get('delivery', {})
|
||||
items_data = data.get('products', [])
|
||||
|
||||
# Формируем DTO
|
||||
return {
|
||||
'external_id': str(recommerce_id),
|
||||
'source': 'recommerce',
|
||||
'status_external': data.get('state'),
|
||||
'date_created': data.get('date'), # Unix timestamp
|
||||
'customer': {
|
||||
'name': customer_data.get('name'),
|
||||
'phone': customer_data.get('phone'),
|
||||
'email': customer_data.get('email'),
|
||||
'address': _extract_address(data),
|
||||
},
|
||||
'items': [
|
||||
{
|
||||
'sku': item.get('sku'),
|
||||
'quantity': item.get('count', 1),
|
||||
'price': item.get('price', {}).get('amount', 0),
|
||||
'name': item.get('title'),
|
||||
}
|
||||
for item in items_data
|
||||
],
|
||||
'total_amount': data.get('total_amount', {}).get('amount'),
|
||||
'notes': data.get('note'),
|
||||
}
|
||||
|
||||
|
||||
def _extract_address(data: Dict[str, Any]) -> str:
|
||||
"""Вспомогательная функция для сборки адреса"""
|
||||
delivery = data.get('delivery', {})
|
||||
fields = delivery.get('fields', [])
|
||||
|
||||
# Recommerce может хранить адрес в полях доставки
|
||||
address_parts = []
|
||||
|
||||
# Пробуем найти стандартные поля
|
||||
for field in fields:
|
||||
if field.get('value'):
|
||||
address_parts.append(f"{field.get('name')}: {field.get('value')}")
|
||||
|
||||
if not address_parts:
|
||||
# Fallback если адрес в другом месте или не задан
|
||||
return "Адрес не указан в стандартных полях"
|
||||
|
||||
return ", ".join(address_parts)
|
||||
Reference in New Issue
Block a user