Рефакторинг: вынос логики онбординга тенанта в сервисный слой
Создан 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:
@@ -9,9 +9,8 @@ Management команда для ручной активации заявки н
|
||||
python manage.py activate_registration mixflowers
|
||||
"""
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from tenants.models import TenantRegistration, Client, Domain, Subscription
|
||||
from django.conf import settings
|
||||
from tenants.models import TenantRegistration, Client, Domain
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -33,22 +32,20 @@ class Command(BaseCommand):
|
||||
schema_name = options['schema_name']
|
||||
force = options.get('force', False)
|
||||
|
||||
# Ищем заявку
|
||||
try:
|
||||
# Ищем заявку
|
||||
registration = TenantRegistration.objects.get(schema_name=schema_name)
|
||||
except TenantRegistration.DoesNotExist:
|
||||
raise CommandError(f'Заявка с schema_name "{schema_name}" не найдена')
|
||||
|
||||
self.stdout.write(f'Найдена заявка: {registration.shop_name} ({registration.schema_name})')
|
||||
self.stdout.write(f'Статус: {registration.get_status_display()}')
|
||||
self.stdout.write(f'Email: {registration.owner_email}')
|
||||
self.stdout.write('')
|
||||
self._print_registration_info(registration)
|
||||
|
||||
# Проверяем статус
|
||||
if registration.status == TenantRegistration.STATUS_APPROVED and not force:
|
||||
self.stdout.write(self.style.WARNING('Эта заявка уже была одобрена!'))
|
||||
if registration.tenant:
|
||||
self.stdout.write(f'Связанный тенант: {registration.tenant.name} (ID: {registration.tenant.id})')
|
||||
return
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING('Но тенант не был создан. Используйте --force для пересоздания'))
|
||||
if not self._confirm('Продолжить с --force?'):
|
||||
@@ -63,74 +60,47 @@ class Command(BaseCommand):
|
||||
'Используйте --force для пересоздания (ОПАСНО: удалит все данные!)'
|
||||
)
|
||||
|
||||
# Активируем
|
||||
# Если force - удаляем старый тенант
|
||||
if force and existing_client:
|
||||
self._delete_existing_tenant(existing_client)
|
||||
|
||||
# Активация через сервис
|
||||
self._activate(registration)
|
||||
|
||||
def _print_registration_info(self, registration):
|
||||
"""Вывод информации о заявке."""
|
||||
self.stdout.write(f'Найдена заявка: {registration.shop_name} ({registration.schema_name})')
|
||||
self.stdout.write(f'Статус: {registration.get_status_display()}')
|
||||
self.stdout.write(f'Email: {registration.owner_email}')
|
||||
self.stdout.write('')
|
||||
|
||||
def _delete_existing_tenant(self, client):
|
||||
"""Удаление существующего тенанта."""
|
||||
self.stdout.write(self.style.WARNING(f'Удаление существующего тенанта {client.id}...'))
|
||||
try:
|
||||
client.subscription.delete()
|
||||
except Exception:
|
||||
pass
|
||||
Domain.objects.filter(tenant=client).delete()
|
||||
client.delete()
|
||||
self.stdout.write(self.style.SUCCESS('Старый тенант удален'))
|
||||
|
||||
def _activate(self, registration):
|
||||
"""Активация заявки через сервис."""
|
||||
self.stdout.write('')
|
||||
self.stdout.write(self.style.WARNING('НАЧИНАЮ АКТИВАЦИЮ...'))
|
||||
self.stdout.write('')
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# Если force - удаляем старый тенант
|
||||
if force and existing_client:
|
||||
self.stdout.write(self.style.WARNING(f'Удаление существующего тенанта {existing_client.id}...'))
|
||||
# Удаляем подписку
|
||||
try:
|
||||
existing_client.subscription.delete()
|
||||
except:
|
||||
pass
|
||||
# Удаляем домены
|
||||
Domain.objects.filter(tenant=existing_client).delete()
|
||||
# Удаляем тенант (это также удалит схему из PostgreSQL)
|
||||
existing_client.delete()
|
||||
self.stdout.write(self.style.SUCCESS('Старый тенант удален'))
|
||||
from tenants.services import TenantOnboardingService
|
||||
client = TenantOnboardingService.activate_registration(registration, admin_user=None)
|
||||
|
||||
# Создаем тенант
|
||||
self.stdout.write(f'Создание тенанта: {registration.schema_name}')
|
||||
client = Client.objects.create(
|
||||
schema_name=registration.schema_name,
|
||||
name=registration.shop_name,
|
||||
owner_email=registration.owner_email,
|
||||
owner_name=registration.owner_name,
|
||||
phone=registration.phone,
|
||||
is_active=True
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(f'✓ Тенант создан (ID: {client.id})'))
|
||||
|
||||
# Создаем домен
|
||||
domain_base = getattr(settings, 'TENANT_DOMAIN_BASE', 'localhost')
|
||||
# Убираем порт из домена (django-tenants ищет по hostname без порта)
|
||||
if ':' in domain_base:
|
||||
domain_base = domain_base.split(':')[0]
|
||||
domain_name = f"{registration.schema_name}.{domain_base}"
|
||||
self.stdout.write(f'Создание домена: {domain_name}')
|
||||
domain = Domain.objects.create(
|
||||
domain=domain_name,
|
||||
tenant=client,
|
||||
is_primary=True
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(f'✓ Домен создан (ID: {domain.id})'))
|
||||
|
||||
# Создаем триальную подписку
|
||||
self.stdout.write('Создание триальной подписки на 90 дней')
|
||||
subscription = Subscription.create_trial(client)
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f'✓ Подписка создана (ID: {subscription.id}), '
|
||||
f'истекает: {subscription.expires_at.strftime("%Y-%m-%d")}'
|
||||
))
|
||||
|
||||
# Инициализация системных данных
|
||||
self.stdout.write('Инициализация системных данных...')
|
||||
from django.core.management import call_command
|
||||
call_command('init_tenant_data', schema=client.schema_name)
|
||||
self.stdout.write(self.style.SUCCESS('✓ Системные данные созданы'))
|
||||
|
||||
# Обновляем заявку
|
||||
registration.status = TenantRegistration.STATUS_APPROVED
|
||||
registration.processed_at = timezone.now()
|
||||
registration.processed_by = None # Активировано через команду
|
||||
registration.tenant = client
|
||||
registration.save()
|
||||
self.stdout.write(self.style.SUCCESS('✓ Заявка обновлена'))
|
||||
# Выводим результат
|
||||
domain = Domain.objects.filter(tenant=client, is_primary=True).first()
|
||||
domain_base = settings.TENANT_DOMAIN_BASE
|
||||
if ':' in domain_base:
|
||||
domain_base = domain_base.split(':')[0]
|
||||
domain_name = f"{registration.schema_name}.{domain_base}"
|
||||
|
||||
self.stdout.write('')
|
||||
self.stdout.write(self.style.SUCCESS('=' * 60))
|
||||
@@ -140,7 +110,7 @@ class Command(BaseCommand):
|
||||
self.stdout.write(f'Магазин: {client.name}')
|
||||
self.stdout.write(f'Schema: {client.schema_name}')
|
||||
self.stdout.write(f'Домен: http://{domain_name}:8000/')
|
||||
self.stdout.write(f'Подписка до: {subscription.expires_at.strftime("%Y-%m-%d")} ({subscription.days_left()} дней)')
|
||||
self.stdout.write(f'Подписка до: {client.subscription.expires_at.strftime("%Y-%m-%d")} ({client.subscription.days_left()} дней)')
|
||||
self.stdout.write('')
|
||||
|
||||
except Exception as e:
|
||||
@@ -150,6 +120,6 @@ class Command(BaseCommand):
|
||||
raise CommandError('Активация не удалась')
|
||||
|
||||
def _confirm(self, question):
|
||||
"""Запрос подтверждения у пользователя"""
|
||||
"""Запрос подтверждения у пользователя."""
|
||||
answer = input(f'{question} (yes/no): ')
|
||||
return answer.lower() in ['yes', 'y', 'да']
|
||||
|
||||
Reference in New Issue
Block a user