feat(woocommerce): реализовать проверку соединения с WooCommerce API
- Добавлена реализация метода test_connection() с обработкой различных HTTP статусов - Реализованы вспомогательные методы _get_api_url() и _get_auth() для работы с API - Добавлена интеграция WooCommerceService в get_integration_service() - Настроены поля формы для WooCommerceIntegration в get_form_fields_meta() fix(inventory): исправить расчет цены продажи в базовых единицах - Исправлен расчет sale_price в SaleProcessor с учетом conversion_factor_snapshot - Обновлен расчет цены в сигнале create_sale_on_order_completion для корректной работы с sales_unit
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import requests
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from .base import MarketplaceService
|
from .base import MarketplaceService
|
||||||
|
|
||||||
@@ -5,16 +6,58 @@ from .base import MarketplaceService
|
|||||||
class WooCommerceService(MarketplaceService):
|
class WooCommerceService(MarketplaceService):
|
||||||
"""Сервис для работы с WooCommerce API"""
|
"""Сервис для работы с WooCommerce API"""
|
||||||
|
|
||||||
|
def _get_api_url(self) -> str:
|
||||||
|
"""Получить базовый URL для WooCommerce REST API"""
|
||||||
|
base = self.config.store_url.rstrip('/')
|
||||||
|
# WooCommerce REST API v3 endpoint
|
||||||
|
return f"{base}/wp-json/wc/v3/"
|
||||||
|
|
||||||
|
def _get_auth(self) -> tuple:
|
||||||
|
"""Получить кортеж для Basic Auth (consumer_key, consumer_secret)"""
|
||||||
|
return (self.config.consumer_key or '', self.config.consumer_secret or '')
|
||||||
|
|
||||||
def test_connection(self) -> Tuple[bool, str]:
|
def test_connection(self) -> Tuple[bool, str]:
|
||||||
"""Проверить соединение с WooCommerce API"""
|
"""
|
||||||
|
Проверить соединение с WooCommerce API.
|
||||||
|
|
||||||
|
Использует endpoint /wp-json/wc/v3/ для проверки.
|
||||||
|
Аутентификация через HTTP Basic Auth.
|
||||||
|
"""
|
||||||
if not self.config.store_url:
|
if not self.config.store_url:
|
||||||
return False, 'Не указан URL магазина'
|
return False, 'Не указан URL магазина'
|
||||||
|
|
||||||
if not self.config.consumer_key or not self.config.consumer_secret:
|
if not self.config.consumer_key or not self.config.consumer_secret:
|
||||||
return False, 'Не указаны ключи API'
|
return False, 'Не указаны ключи API'
|
||||||
|
|
||||||
# TODO: реализовать проверку соединения с WooCommerce API
|
url = self._get_api_url()
|
||||||
return True, 'Соединение успешно (заглушка)'
|
|
||||||
|
try:
|
||||||
|
# Пытаемся получить список товаров (limit=1) для проверки авторизации
|
||||||
|
# Это более надёжный способ проверки, чем просто обращение к корню API
|
||||||
|
response = requests.get(
|
||||||
|
f"{url}products",
|
||||||
|
params={'per_page': 1},
|
||||||
|
auth=self._get_auth(),
|
||||||
|
timeout=15
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return True, 'Соединение установлено успешно'
|
||||||
|
elif response.status_code == 401:
|
||||||
|
return False, 'Неверные ключи API (Consumer Key/Secret)'
|
||||||
|
elif response.status_code == 403:
|
||||||
|
return False, 'Доступ запрещён. Проверьте права API ключа'
|
||||||
|
elif response.status_code == 404:
|
||||||
|
return False, 'WooCommerce REST API не найден. Проверьте, что WooCommerce установлен и активирован'
|
||||||
|
else:
|
||||||
|
return False, f'Ошибка соединения: HTTP {response.status_code}'
|
||||||
|
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
return False, 'Таймаут соединения (15 сек)'
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
return False, 'Не удалось подключиться к серверу. Проверьте URL магазина'
|
||||||
|
except Exception as e:
|
||||||
|
return False, f'Ошибка: {str(e)}'
|
||||||
|
|
||||||
def sync(self) -> Tuple[bool, str]:
|
def sync(self) -> Tuple[bool, str]:
|
||||||
"""Выполнить синхронизацию с WooCommerce"""
|
"""Выполнить синхронизацию с WooCommerce"""
|
||||||
|
|||||||
@@ -170,8 +170,8 @@ def get_integration_service(integration_id: str, instance):
|
|||||||
from .services.marketplaces.recommerce import RecommerceService
|
from .services.marketplaces.recommerce import RecommerceService
|
||||||
return RecommerceService(instance)
|
return RecommerceService(instance)
|
||||||
elif integration_id == 'woocommerce':
|
elif integration_id == 'woocommerce':
|
||||||
# TODO: WooCommerceService
|
from .services.marketplaces.woocommerce import WooCommerceService
|
||||||
return None
|
return WooCommerceService(instance)
|
||||||
elif integration_id == 'glm':
|
elif integration_id == 'glm':
|
||||||
from .services.ai_services.glm_service import GLMIntegrationService
|
from .services.ai_services.glm_service import GLMIntegrationService
|
||||||
return GLMIntegrationService(instance)
|
return GLMIntegrationService(instance)
|
||||||
@@ -386,6 +386,29 @@ def get_form_fields_meta(model):
|
|||||||
'choices': getattr(field, 'choices', [])
|
'choices': getattr(field, 'choices', [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fields.append(field_info)
|
||||||
|
# Для WooCommerce показываем только базовые поля для подключения
|
||||||
|
elif model.__name__ == 'WooCommerceIntegration':
|
||||||
|
basic_fields = ['store_url', 'consumer_key', 'consumer_secret']
|
||||||
|
for field_name in editable_fields:
|
||||||
|
if field_name in basic_fields:
|
||||||
|
field = model._meta.get_field(field_name)
|
||||||
|
field_info = {
|
||||||
|
'name': field_name,
|
||||||
|
'label': getattr(field, 'verbose_name', field_name),
|
||||||
|
'help_text': getattr(field, 'help_text', ''),
|
||||||
|
'required': not getattr(field, 'blank', True),
|
||||||
|
'type': 'text',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Определить тип поля
|
||||||
|
if 'BooleanField' in field.__class__.__name__:
|
||||||
|
field_info['type'] = 'checkbox'
|
||||||
|
elif 'URLField' in field.__class__.__name__:
|
||||||
|
field_info['type'] = 'url'
|
||||||
|
elif 'secret' in field_name.lower() or 'key' in field_name.lower():
|
||||||
|
field_info['type'] = 'password'
|
||||||
|
|
||||||
fields.append(field_info)
|
fields.append(field_info)
|
||||||
else:
|
else:
|
||||||
# Для других интеграций - все редактируемые поля
|
# Для других интеграций - все редактируемые поля
|
||||||
|
|||||||
@@ -35,8 +35,12 @@ class SaleProcessor:
|
|||||||
"""
|
"""
|
||||||
# Определяем цену продажи из заказа или из товара
|
# Определяем цену продажи из заказа или из товара
|
||||||
if order and reservation.order_item:
|
if order and reservation.order_item:
|
||||||
# Цена из OrderItem
|
item = reservation.order_item
|
||||||
sale_price = reservation.order_item.price
|
# Пересчитываем цену в базовые единицы
|
||||||
|
if item.sales_unit and item.conversion_factor_snapshot:
|
||||||
|
sale_price = Decimal(str(item.price)) * item.conversion_factor_snapshot
|
||||||
|
else:
|
||||||
|
sale_price = item.price
|
||||||
else:
|
else:
|
||||||
# Цена из товара
|
# Цена из товара
|
||||||
sale_price = reservation.product.actual_price or Decimal('0')
|
sale_price = reservation.product.actual_price or Decimal('0')
|
||||||
|
|||||||
@@ -480,12 +480,18 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs):
|
|||||||
f"Используем quantity_in_base_units: {sale_quantity}"
|
f"Используем quantity_in_base_units: {sale_quantity}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Пересчитываем цену в базовые единицы
|
||||||
|
if item.sales_unit and item.conversion_factor_snapshot:
|
||||||
|
base_price = Decimal(str(item.price)) * item.conversion_factor_snapshot
|
||||||
|
else:
|
||||||
|
base_price = Decimal(str(item.price))
|
||||||
|
|
||||||
# Создаем Sale (с автоматическим FIFO-списанием)
|
# Создаем Sale (с автоматическим FIFO-списанием)
|
||||||
sale = SaleProcessor.create_sale(
|
sale = SaleProcessor.create_sale(
|
||||||
product=product,
|
product=product,
|
||||||
warehouse=warehouse,
|
warehouse=warehouse,
|
||||||
quantity=sale_quantity,
|
quantity=sale_quantity,
|
||||||
sale_price=Decimal(str(item.price)),
|
sale_price=base_price,
|
||||||
order=instance,
|
order=instance,
|
||||||
document_number=instance.order_number,
|
document_number=instance.order_number,
|
||||||
sales_unit=item.sales_unit # Передаем sales_unit в Sale
|
sales_unit=item.sales_unit # Передаем sales_unit в Sale
|
||||||
|
|||||||
Reference in New Issue
Block a user