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)