Files
octopus/myproject/integrations/recommerce/mappers.py
Andrey Smakotin a5ab216934 feat(integrations): добавлена полная интеграция с Recommerce
Реализован клиент для работы с API Recommerce, включая:
- Клиент с методами для работы с товарами и заказами
- Сервисный слой для высокоуровневых операций
- Мапперы данных между форматами
- Обработку исключений

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 17:56:53 +03:00

125 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)