Files
chatballs/apps/backend/hub_platform/identity/bootstrap.py
T
Andrey e2c9295295 feat(support): seed support department, channels and identity contract
SPEC-HUB-0010 §4.1/§4.2, SPEC-HUB-0011 §5.2.

- bootstrap_edevs_owner: создаёт отдел support (BootstrapResult.support_department).
- seed_hub_initial_data: support-каналы foxray-support/firepage-support с политикой
  requires_auth=True, anonymous/sales/checkout=False; CHANNEL_SPECS вынесены в
  _seed_specs.py (NO GOD FILES: команда 226 строк).
- support/seed_support.py: контракт foxray.support.v1 (ACTIVE, пример из SPEC §5.2),
  привязка к каналу через allowed_channels; генерация support_token_secret для
  продуктов (EncryptedCharField, не печатается/не логируется).
- Обновлены существующие тесты: departments теперь {sales, support}.
2026-07-09 00:45:14 +03:00

119 lines
3.9 KiB
Python

from dataclasses import dataclass
from django.db import transaction
from hub_platform.identity.audit import record_audit_event
from hub_platform.identity.models import (
Department,
EmployeeProfile,
EmployeeRole,
HumanUser,
Organization,
)
from hub_platform.products.models import Product, ProductDepartment
@dataclass(frozen=True)
class BootstrapResult:
organization: Organization
sales_department: Department
support_department: Department
owner: HumanUser
created_owner: bool
@transaction.atomic
def bootstrap_edevs_owner(*, email: str, password: str, full_name: str = "") -> BootstrapResult:
organization, _ = Organization.objects.get_or_create(
slug="edevs",
defaults={
"name": "Edevs",
"timezone": "Europe/Moscow",
"currency": "RUB",
},
)
sales_department, _ = Department.objects.get_or_create(
organization=organization,
code="sales",
defaults={"name": "Продажи"},
)
# Отдел поддержки (ADR-HUB-0022, SPEC-HUB-0010 §4.1): authenticated in-product
# чат существующих клиентов продуктов. Сосуществует с sales, identity разделены.
support_department, _ = Department.objects.get_or_create(
organization=organization,
code="support",
defaults={"name": "Поддержка"},
)
for code, name in (("firepage", "FirePage"), ("foxray", "Foxray")):
product, _ = Product.objects.get_or_create(organization=organization, code=code, defaults={"name": name})
ProductDepartment.objects.get_or_create(product=product, department=sales_department)
# AI-агенты теперь на уровне канала обработки (ADR-HUB-0019) — см. seed_channels.
owner, created_owner = HumanUser.objects.get_or_create(
email=HumanUser.objects.normalize_email(email),
defaults={
"full_name": full_name,
"is_staff": True,
"is_superuser": True,
},
)
if created_owner:
owner.set_password(password)
owner.save(update_fields=["password"])
elif not owner.is_staff or not owner.is_superuser:
owner.is_staff = True
owner.is_superuser = True
owner.save(update_fields=["is_staff", "is_superuser"])
EmployeeProfile.objects.get_or_create(
user=owner,
defaults={
"organization": organization,
"role": EmployeeRole.OWNER,
"department": sales_department,
"totp_required": False,
},
)
operator, created_operator = HumanUser.objects.get_or_create(
email=HumanUser.objects.normalize_email("a.kotova@edevs.tech"),
defaults={
"full_name": "Анна Котова",
"is_staff": False,
"is_superuser": False,
},
)
if created_operator:
operator.set_password("Operator-Local-2026")
operator.save(update_fields=["password"])
operator_profile, _ = EmployeeProfile.objects.get_or_create(
user=operator,
defaults={
"organization": organization,
"role": EmployeeRole.OPERATOR,
"phone": "+7 916 245 14 02",
"department": sales_department,
},
)
if not operator_profile.phone:
operator_profile.phone = "+7 916 245 14 02"
operator_profile.save(update_fields=["phone"])
record_audit_event(
organization=organization,
actor=owner,
action="identity.owner_bootstrapped",
object_type="HumanUser",
object_id=str(owner.id),
payload={"created_owner": created_owner},
)
return BootstrapResult(
organization=organization,
sales_department=sales_department,
support_department=support_department,
owner=owner,
created_owner=created_owner,
)