Рефакторинг: вынос логики онбординга тенанта в сервисный слой

Создан TenantOnboardingService как единый источник истины для:
- Активации заявки на регистрацию тенанта
- Создания Client, Domain, Subscription
- Инициализации системных данных (Customer, статусы, способы оплаты, склад, витрина)

Новые сервисы:
- TenantOnboardingService (tenants/services/onboarding.py)
- WarehouseService (inventory/services/warehouse_service.py)
- ShowcaseService (inventory/services/showcase_service.py)
- PaymentMethodService (orders/services/payment_method_service.py)

Рефакторинг:
- admin.py: 220 строк → 5 строк (делегирование сервису)
- init_tenant_data.py: 259 строк → 68 строк
- activate_registration.py: использует сервис
- Тесты обновлены для вызова сервиса напрямую

При создании тенанта автоматически создаются склад и витрина по умолчанию.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 14:52:55 +03:00
parent 658cd59511
commit b59ad725cb
13 changed files with 679 additions and 477 deletions

View File

@@ -6,10 +6,14 @@ from .batch_manager import StockBatchManager
from .sale_processor import SaleProcessor
from .inventory_processor import InventoryProcessor
from .showcase_manager import ShowcaseManager
from .warehouse_service import WarehouseService
from .showcase_service import ShowcaseService
__all__ = [
'StockBatchManager',
'SaleProcessor',
'InventoryProcessor',
'ShowcaseManager',
'WarehouseService',
'ShowcaseService',
]

View File

@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
"""
Сервис для работы с витринами.
"""
from inventory.models import Showcase, Warehouse
from inventory.services.warehouse_service import WarehouseService
class ShowcaseService:
"""Сервис управления витринами"""
# Константы для витрины по умолчанию
DEFAULT_NAME = 'Основная витрина'
DEFAULT_DESCRIPTION = 'Витрина по умолчанию, созданная автоматически при регистрации'
@classmethod
def get_default(cls) -> Showcase | None:
"""
Получить витрину по умолчанию.
Returns:
Showcase или None если не найдена
"""
return Showcase.objects.filter(is_default=True, is_active=True).first()
@classmethod
def get_or_create_default(cls, warehouse: Warehouse = None) -> tuple[Showcase, bool]:
"""
Получить или создать витрину по умолчанию.
Args:
warehouse: Склад для витрины. Если не указан, используется склад по умолчанию.
Returns:
tuple (Showcase, created: bool)
Raises:
ValueError: если склад не найден и не может быть создан
"""
if warehouse is None:
warehouse, _ = WarehouseService.get_or_create_default()
return Showcase.objects.get_or_create(
warehouse=warehouse,
is_default=True,
defaults={
'name': cls.DEFAULT_NAME,
'description': cls.DEFAULT_DESCRIPTION,
'is_active': True,
}
)
@classmethod
def reset_default(cls, warehouse: Warehouse = None) -> Showcase:
"""
Удалить и пересоздать витрину по умолчанию.
Используется при --reset в init_tenant_data.
Args:
warehouse: Склад для витрины
Returns:
Новый Showcase
"""
if warehouse is None:
warehouse, _ = WarehouseService.get_or_create_default()
Showcase.objects.filter(warehouse=warehouse, is_default=True).delete()
showcase, _ = cls.get_or_create_default(warehouse)
return showcase

View File

@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
"""
Сервис для работы со складами.
"""
from inventory.models import Warehouse
class WarehouseService:
"""Сервис управления складами"""
# Константы для склада по умолчанию
DEFAULT_NAME = 'Основной склад'
DEFAULT_DESCRIPTION = 'Склад по умолчанию, созданный автоматически при регистрации'
@classmethod
def get_default(cls) -> Warehouse | None:
"""
Получить склад по умолчанию.
Returns:
Warehouse или None если не найден
"""
return Warehouse.objects.filter(is_default=True, is_active=True).first()
@classmethod
def get_or_create_default(cls) -> tuple[Warehouse, bool]:
"""
Получить или создать склад по умолчанию.
Returns:
tuple (Warehouse, created: bool)
"""
return Warehouse.objects.get_or_create(
is_default=True,
defaults={
'name': cls.DEFAULT_NAME,
'description': cls.DEFAULT_DESCRIPTION,
'is_active': True,
'is_pickup_point': True,
}
)
@classmethod
def reset_default(cls) -> Warehouse:
"""
Удалить и пересоздать склад по умолчанию.
Используется при --reset в init_tenant_data.
Returns:
Новый Warehouse
"""
Warehouse.objects.filter(is_default=True).delete()
warehouse, _ = cls.get_or_create_default()
return warehouse