Law/tests/admin/test_metrics_templates.py
2026-03-17 00:49:39 +03:00

518 lines
21 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from tests.admin.base import * # noqa: F401,F403
class AdminMetricsTemplatesTests(AdminUniversalCrudBase):
def test_dashboard_metrics_returns_lawyer_loads(self):
headers = self._auth_headers("ADMIN", email="root@example.com")
with self.SessionLocal() as db:
db.add_all(
[
Status(code="NEW", name="Новая", enabled=True, sort_order=0, is_terminal=False),
Status(code="IN_PROGRESS", name="В работе", enabled=True, sort_order=1, is_terminal=False),
Status(code="CLOSED", name="Закрыта", enabled=True, sort_order=2, is_terminal=True),
]
)
lawyer_busy = AdminUser(
role="LAWYER",
name="Юрист Загруженный",
email="busy@example.com",
password_hash="hash",
avatar_url="https://cdn.example.com/a.png",
primary_topic_code="civil-law",
is_active=True,
)
lawyer_free = AdminUser(
role="LAWYER",
name="Юрист Свободный",
email="free@example.com",
password_hash="hash",
avatar_url=None,
primary_topic_code="family-law",
is_active=True,
)
db.add_all([lawyer_busy, lawyer_free])
db.flush()
db.add_all(
[
Request(
track_number="TRK-METRICS-1",
client_name="Клиент 1",
client_phone="+79990000001",
topic_code="civil-law",
status_code="NEW",
assigned_lawyer_id=str(lawyer_busy.id),
extra_fields={},
),
Request(
track_number="TRK-METRICS-2",
client_name="Клиент 2",
client_phone="+79990000002",
topic_code="civil-law",
status_code="CLOSED",
assigned_lawyer_id=str(lawyer_busy.id),
extra_fields={},
),
]
)
db.commit()
response = self.client.get("/api/admin/metrics/overview", headers=headers)
self.assertEqual(response.status_code, 200)
body = response.json()
self.assertIn("lawyer_loads", body)
self.assertEqual(len(body["lawyer_loads"]), 2)
by_email = {row["email"]: row for row in body["lawyer_loads"]}
self.assertEqual(by_email["busy@example.com"]["active_load"], 1)
self.assertEqual(by_email["busy@example.com"]["total_assigned"], 2)
self.assertEqual(by_email["busy@example.com"]["avatar_url"], "https://cdn.example.com/a.png")
self.assertEqual(by_email["free@example.com"]["active_load"], 0)
self.assertEqual(by_email["free@example.com"]["total_assigned"], 0)
def test_dashboard_metrics_returns_service_request_unread_totals(self):
admin_headers = self._auth_headers("ADMIN", email="root@example.com")
lawyer_id = str(uuid4())
lawyer_headers = self._auth_headers("LAWYER", sub=lawyer_id, email="lawyer@example.com")
with self.SessionLocal() as db:
client = Client(full_name="Клиент по запросам", phone="+79990000012", responsible="seed")
db.add(client)
db.flush()
req = Request(
track_number="TRK-METRICS-SR-1",
client_id=client.id,
client_name=client.full_name,
client_phone=client.phone,
topic_code="consulting",
status_code="IN_PROGRESS",
assigned_lawyer_id=lawyer_id,
extra_fields={},
responsible="seed",
)
db.add(req)
db.flush()
db.add_all(
[
RequestServiceRequest(
request_id=str(req.id),
client_id=str(client.id),
assigned_lawyer_id=lawyer_id,
type="CURATOR_CONTACT",
status="NEW",
body="Нужна консультация администратора",
created_by_client=True,
admin_unread=True,
lawyer_unread=True,
responsible="Клиент",
),
RequestServiceRequest(
request_id=str(req.id),
client_id=str(client.id),
assigned_lawyer_id=lawyer_id,
type="LAWYER_CHANGE_REQUEST",
status="NEW",
body="Прошу сменить юриста",
created_by_client=True,
admin_unread=True,
lawyer_unread=False,
responsible="Клиент",
),
]
)
db.commit()
admin_response = self.client.get("/api/admin/metrics/overview", headers=admin_headers)
self.assertEqual(admin_response.status_code, 200)
self.assertEqual(int(admin_response.json().get("service_request_unread_total") or 0), 2)
lawyer_response = self.client.get("/api/admin/metrics/overview", headers=lawyer_headers)
self.assertEqual(lawyer_response.status_code, 200)
self.assertEqual(int(lawyer_response.json().get("service_request_unread_total") or 0), 1)
def test_dashboard_metrics_returns_dynamic_sla_and_frt(self):
headers = self._auth_headers("ADMIN", email="root@example.com")
now = datetime.now(timezone.utc)
with self.SessionLocal() as db:
db.add_all(
[
Status(code="NEW", name="Новая", enabled=True, sort_order=0, is_terminal=False),
Status(code="CLOSED", name="Закрыта", enabled=True, sort_order=1, is_terminal=True),
]
)
req = Request(
track_number="TRK-SLA-M-1",
client_name="Клиент SLA",
client_phone="+79990000003",
topic_code="civil-law",
status_code="NEW",
extra_fields={},
created_at=now - timedelta(hours=30),
updated_at=now - timedelta(hours=30),
)
db.add(req)
db.flush()
db.add(
Message(
request_id=req.id,
author_type="LAWYER",
author_name="Юрист",
body="Ответ",
created_at=req.created_at + timedelta(minutes=20),
updated_at=req.created_at + timedelta(minutes=20),
)
)
db.add(
StatusHistory(
request_id=req.id,
from_status=None,
to_status="NEW",
changed_by_admin_id=None,
created_at=now - timedelta(hours=30),
updated_at=now - timedelta(hours=30),
)
)
db.commit()
response = self.client.get("/api/admin/metrics/overview", headers=headers)
self.assertEqual(response.status_code, 200)
body = response.json()
self.assertGreaterEqual(int(body.get("sla_overdue") or 0), 1)
self.assertIsNotNone(body.get("frt_avg_minutes"))
self.assertAlmostEqual(float(body["frt_avg_minutes"]), 20.0, places=1)
self.assertIn("NEW", body.get("avg_time_in_status_hours") or {})
def test_dashboard_metrics_can_skip_sla_for_fast_overview(self):
headers = self._auth_headers("ADMIN", email="root@example.com")
now = datetime.now(timezone.utc)
with self.SessionLocal() as db:
db.add_all(
[
Status(code="NEW", name="Новая", enabled=True, sort_order=0, is_terminal=False),
Status(code="CLOSED", name="Закрыта", enabled=True, sort_order=1, is_terminal=True),
]
)
req = Request(
track_number="TRK-SLA-M-FAST-1",
client_name="Клиент SLA Fast",
client_phone="+79990000013",
topic_code="civil-law",
status_code="NEW",
extra_fields={},
created_at=now - timedelta(hours=30),
updated_at=now - timedelta(hours=30),
)
db.add(req)
db.flush()
db.add(
Message(
request_id=req.id,
author_type="LAWYER",
author_name="Юрист",
body="Ответ",
created_at=req.created_at + timedelta(minutes=20),
updated_at=req.created_at + timedelta(minutes=20),
)
)
db.add(
StatusHistory(
request_id=req.id,
from_status=None,
to_status="NEW",
changed_by_admin_id=None,
created_at=now - timedelta(hours=30),
updated_at=now - timedelta(hours=30),
)
)
db.commit()
response = self.client.get("/api/admin/metrics/overview?include_sla=false", headers=headers)
self.assertEqual(response.status_code, 200)
body = response.json()
self.assertEqual(int(body.get("sla_overdue") or 0), 0)
self.assertIsNone(body.get("frt_avg_minutes"))
self.assertEqual(body.get("avg_time_in_status_hours"), {})
sla_response = self.client.get("/api/admin/metrics/overview-sla", headers=headers)
self.assertEqual(sla_response.status_code, 200)
sla_body = sla_response.json()
self.assertGreaterEqual(int(sla_body.get("sla_overdue") or 0), 1)
self.assertIsNotNone(sla_body.get("frt_avg_minutes"))
def test_admin_can_manage_admin_user_topics_only_for_lawyers(self):
headers = self._auth_headers("ADMIN", email="root@example.com")
with self.SessionLocal() as db:
db.add_all(
[
Topic(code="civil-law", name="Гражданское право", enabled=True, sort_order=1),
Topic(code="tax-law", name="Налоговое право", enabled=True, sort_order=2),
]
)
lawyer = AdminUser(
role="LAWYER",
name="Юрист Профильный",
email="lawyer.topics@example.com",
password_hash="hash",
is_active=True,
)
admin = AdminUser(
role="ADMIN",
name="Администратор",
email="admin.topics@example.com",
password_hash="hash",
is_active=True,
)
db.add_all([lawyer, admin])
db.commit()
lawyer_id = str(lawyer.id)
admin_id = str(admin.id)
created = self.client.post(
"/api/admin/crud/admin_user_topics",
headers=headers,
json={"admin_user_id": lawyer_id, "topic_code": "civil-law"},
)
self.assertEqual(created.status_code, 201)
body = created.json()
self.assertEqual(body["admin_user_id"], lawyer_id)
self.assertEqual(body["topic_code"], "civil-law")
self.assertEqual(body["responsible"], "root@example.com")
relation_id = body["id"]
UUID(relation_id)
queried = self.client.post(
"/api/admin/crud/admin_user_topics/query",
headers=headers,
json={
"filters": [{"field": "admin_user_id", "op": "=", "value": lawyer_id}],
"sort": [{"field": "created_at", "dir": "desc"}],
"page": {"limit": 50, "offset": 0},
},
)
self.assertEqual(queried.status_code, 200)
self.assertEqual(queried.json()["total"], 1)
updated = self.client.patch(
f"/api/admin/crud/admin_user_topics/{relation_id}",
headers=headers,
json={"topic_code": "tax-law"},
)
self.assertEqual(updated.status_code, 200)
self.assertEqual(updated.json()["topic_code"], "tax-law")
forbidden_for_non_lawyer = self.client.post(
"/api/admin/crud/admin_user_topics",
headers=headers,
json={"admin_user_id": admin_id, "topic_code": "civil-law"},
)
self.assertEqual(forbidden_for_non_lawyer.status_code, 400)
deleted = self.client.delete(f"/api/admin/crud/admin_user_topics/{relation_id}", headers=headers)
self.assertEqual(deleted.status_code, 200)
def test_topic_templates_crud_and_request_required_fields_validation(self):
headers = self._auth_headers("ADMIN", email="root@example.com")
with self.SessionLocal() as db:
db.add(Topic(code="civil-law", name="Гражданское право", enabled=True, sort_order=1))
db.add(
FormField(
key="passport_series",
label="Серия паспорта",
type="string",
required=False,
enabled=True,
sort_order=1,
)
)
db.commit()
required_created = self.client.post(
"/api/admin/crud/topic_required_fields",
headers=headers,
json={
"topic_code": "civil-law",
"field_key": "passport_series",
"required": True,
"enabled": True,
"sort_order": 10,
},
)
self.assertEqual(required_created.status_code, 201)
self.assertEqual(required_created.json()["responsible"], "root@example.com")
invalid_required = self.client.post(
"/api/admin/crud/topic_required_fields",
headers=headers,
json={
"topic_code": "civil-law",
"field_key": "missing_field",
"required": True,
"enabled": True,
"sort_order": 11,
},
)
self.assertEqual(invalid_required.status_code, 400)
template_created = self.client.post(
"/api/admin/crud/topic_data_templates",
headers=headers,
json={
"topic_code": "civil-law",
"key": "court_file",
"label": "Судебный файл",
"description": "PDF с материалами",
"required": True,
"enabled": True,
"sort_order": 1,
},
)
self.assertEqual(template_created.status_code, 201)
self.assertEqual(template_created.json()["topic_code"], "civil-law")
blocked = self.client.post(
"/api/admin/crud/requests",
headers=headers,
json={
"client_name": "ООО Проверка",
"client_phone": "+79995550001",
"topic_code": "civil-law",
"status_code": "NEW",
"description": "missing required extra field",
"extra_fields": {},
},
)
self.assertEqual(blocked.status_code, 400)
self.assertIn("passport_series", blocked.json().get("detail", ""))
created = self.client.post(
"/api/admin/crud/requests",
headers=headers,
json={
"client_name": "ООО Проверка",
"client_phone": "+79995550001",
"topic_code": "civil-law",
"status_code": "NEW",
"description": "required extra field provided",
"extra_fields": {"passport_series": "1234"},
},
)
self.assertEqual(created.status_code, 201)
request_id = created.json()["id"]
with self.SessionLocal() as db:
row = db.get(Request, UUID(request_id))
self.assertIsNotNone(row)
self.assertEqual(row.extra_fields, {"passport_series": "1234"})
def test_request_data_template_endpoints_for_assigned_lawyer(self):
headers_admin = self._auth_headers("ADMIN", email="root@example.com")
with self.SessionLocal() as db:
db.add(Topic(code="civil-law", name="Гражданское право", enabled=True, sort_order=1))
lawyer = AdminUser(
role="LAWYER",
name="Юрист Шаблон",
email="lawyer.template@example.com",
password_hash="hash",
is_active=True,
)
outsider = AdminUser(
role="LAWYER",
name="Юрист Чужой",
email="lawyer.outside@example.com",
password_hash="hash",
is_active=True,
)
db.add_all([lawyer, outsider])
db.flush()
req = Request(
track_number="TRK-TEMPLATE-1",
client_name="Клиент",
client_phone="+79997770013",
topic_code="civil-law",
status_code="IN_PROGRESS",
assigned_lawyer_id=str(lawyer.id),
description="template flow",
extra_fields={},
)
db.add(req)
db.flush()
db.add_all(
[
TopicDataTemplate(
topic_code="civil-law",
key="power_of_attorney",
label="Доверенность",
description="Скан доверенности",
required=True,
enabled=True,
sort_order=1,
),
TopicDataTemplate(
topic_code="civil-law",
key="claim_copy",
label="Копия иска",
description="Копия заявления",
required=False,
enabled=True,
sort_order=2,
),
]
)
db.commit()
request_id = str(req.id)
lawyer_id = str(lawyer.id)
outsider_id = str(outsider.id)
headers_lawyer = self._auth_headers("LAWYER", email="lawyer.template@example.com", sub=lawyer_id)
headers_outsider = self._auth_headers("LAWYER", email="lawyer.outside@example.com", sub=outsider_id)
pre = self.client.get(f"/api/admin/requests/{request_id}/data-template", headers=headers_lawyer)
self.assertEqual(pre.status_code, 200)
self.assertEqual(len(pre.json()["topic_items"]), 2)
self.assertEqual(len(pre.json()["request_items"]), 0)
sync = self.client.post(f"/api/admin/requests/{request_id}/data-template/sync", headers=headers_lawyer)
self.assertEqual(sync.status_code, 200)
self.assertEqual(sync.json()["created"], 2)
sync_repeat = self.client.post(f"/api/admin/requests/{request_id}/data-template/sync", headers=headers_lawyer)
self.assertEqual(sync_repeat.status_code, 200)
self.assertEqual(sync_repeat.json()["created"], 0)
created_custom = self.client.post(
f"/api/admin/requests/{request_id}/data-template/items",
headers=headers_lawyer,
json={
"key": "additional_scan",
"label": "Дополнительный скан",
"description": "Любой дополнительный файл",
"required": False,
},
)
self.assertEqual(created_custom.status_code, 201)
custom_item_id = created_custom.json()["id"]
updated_custom = self.client.patch(
f"/api/admin/requests/{request_id}/data-template/items/{custom_item_id}",
headers=headers_lawyer,
json={"label": "Дополнительный скан (обновлено)", "required": True},
)
self.assertEqual(updated_custom.status_code, 200)
self.assertEqual(updated_custom.json()["label"], "Дополнительный скан (обновлено)")
self.assertTrue(updated_custom.json()["required"])
outsider_forbidden = self.client.get(f"/api/admin/requests/{request_id}/data-template", headers=headers_outsider)
self.assertEqual(outsider_forbidden.status_code, 403)
admin_access = self.client.get(f"/api/admin/requests/{request_id}/data-template", headers=headers_admin)
self.assertEqual(admin_access.status_code, 200)
self.assertEqual(len(admin_access.json()["request_items"]), 3)
deleted_custom = self.client.delete(
f"/api/admin/requests/{request_id}/data-template/items/{custom_item_id}",
headers=headers_lawyer,
)
self.assertEqual(deleted_custom.status_code, 200)
with self.SessionLocal() as db:
count = db.query(RequestDataRequirement).filter(RequestDataRequirement.request_id == UUID(request_id)).count()
self.assertEqual(count, 2)