mirror of
https://github.com/dartdavros/chatballs.git
synced 2026-10-14 05:09:02 +03:00
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}.
119 lines
3.9 KiB
Python
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,
|
|
)
|