Law/app/api/admin/requests_modules/service_requests.py
2026-02-27 18:46:07 +03:00

186 lines
7.1 KiB
Python

from __future__ import annotations
from datetime import datetime, timezone
from uuid import UUID
from fastapi import HTTPException
from sqlalchemy.orm import Session
from app.models.audit_log import AuditLog
from app.models.request import Request
from app.models.request_service_request import RequestServiceRequest
from app.schemas.admin import RequestServiceRequestPatch
from .permissions import ensure_lawyer_can_view_request_or_403, request_uuid_or_400
SERVICE_REQUEST_TYPES = {"CURATOR_CONTACT", "LAWYER_CHANGE_REQUEST"}
SERVICE_REQUEST_STATUSES = {"NEW", "IN_PROGRESS", "RESOLVED", "REJECTED"}
def _parse_service_request_uuid_or_400(service_request_id: str) -> UUID:
try:
return UUID(str(service_request_id))
except ValueError as exc:
raise HTTPException(status_code=400, detail="Некорректный идентификатор запроса") from exc
def _service_request_for_id_or_404(db: Session, service_request_id: str) -> RequestServiceRequest:
row = db.get(RequestServiceRequest, _parse_service_request_uuid_or_400(service_request_id))
if row is None:
raise HTTPException(status_code=404, detail="Запрос не найден")
return row
def _resolve_responsible(admin: dict) -> str:
return str(admin.get("email") or "").strip() or "Администратор системы"
def _actor_id_or_none(admin: dict) -> str | None:
raw = str(admin.get("sub") or "").strip()
if not raw:
return None
try:
UUID(raw)
return raw
except ValueError:
return None
def _actor_uuid_or_none(admin: dict) -> UUID | None:
raw = str(admin.get("sub") or "").strip()
if not raw:
return None
try:
return UUID(raw)
except ValueError:
return None
def _ensure_lawyer_can_view_service_request_or_403(admin: dict, row: RequestServiceRequest) -> None:
role = str(admin.get("role") or "").upper()
if role != "LAWYER":
return
actor = str(admin.get("sub") or "").strip()
row_type = str(row.type or "").strip().upper()
assigned = str(row.assigned_lawyer_id or "").strip()
if row_type != "CURATOR_CONTACT" or not actor or not assigned or assigned != actor:
raise HTTPException(status_code=403, detail="Недостаточно прав")
def _serialize_service_request(row: RequestServiceRequest) -> dict:
return {
"id": str(row.id),
"request_id": str(row.request_id),
"client_id": str(row.client_id) if row.client_id else None,
"assigned_lawyer_id": str(row.assigned_lawyer_id) if row.assigned_lawyer_id else None,
"resolved_by_admin_id": str(row.resolved_by_admin_id) if row.resolved_by_admin_id else None,
"type": str(row.type or ""),
"status": str(row.status or "NEW"),
"body": str(row.body or ""),
"created_by_client": bool(row.created_by_client),
"admin_unread": bool(row.admin_unread),
"lawyer_unread": bool(row.lawyer_unread),
"admin_read_at": row.admin_read_at.isoformat() if row.admin_read_at else None,
"lawyer_read_at": row.lawyer_read_at.isoformat() if row.lawyer_read_at else None,
"resolved_at": row.resolved_at.isoformat() if row.resolved_at else None,
"created_at": row.created_at.isoformat() if row.created_at else None,
"updated_at": row.updated_at.isoformat() if row.updated_at else None,
}
def list_request_service_requests_service(request_id: str, db: Session, admin: dict) -> dict:
request_uuid = request_uuid_or_400(request_id)
req = db.get(Request, request_uuid)
if req is None:
raise HTTPException(status_code=404, detail="Заявка не найдена")
ensure_lawyer_can_view_request_or_403(admin, req)
role = str(admin.get("role") or "").upper()
query = db.query(RequestServiceRequest).filter(RequestServiceRequest.request_id == str(req.id))
if role == "LAWYER":
actor_id = _actor_id_or_none(admin)
if actor_id is None:
raise HTTPException(status_code=401, detail="Некорректный токен")
query = query.filter(
RequestServiceRequest.type == "CURATOR_CONTACT",
RequestServiceRequest.assigned_lawyer_id == actor_id,
)
rows = query.order_by(RequestServiceRequest.created_at.desc(), RequestServiceRequest.id.desc()).all()
return {"rows": [_serialize_service_request(row) for row in rows], "total": len(rows)}
def mark_service_request_read_service(service_request_id: str, db: Session, admin: dict) -> dict:
row = _service_request_for_id_or_404(db, service_request_id)
role = str(admin.get("role") or "").upper()
_ensure_lawyer_can_view_service_request_or_403(admin, row)
now = datetime.now(timezone.utc)
changed = False
responsible = _resolve_responsible(admin)
actor_uuid = _actor_uuid_or_none(admin)
action = None
if role == "LAWYER":
if row.lawyer_unread:
row.lawyer_unread = False
row.lawyer_read_at = now
action = "READ_MARK_LAWYER"
changed = True
else:
if row.admin_unread:
row.admin_unread = False
row.admin_read_at = now
action = "READ_MARK_ADMIN"
changed = True
if changed:
row.responsible = responsible
db.add(row)
db.add(
AuditLog(
actor_admin_id=actor_uuid,
entity="request_service_requests",
entity_id=str(row.id),
action=str(action or "READ_MARK"),
diff={"status": str(row.status or "NEW")},
responsible=responsible,
)
)
db.commit()
db.refresh(row)
return {"status": "ok", "changed": int(changed), "row": _serialize_service_request(row)}
def update_service_request_status_service(service_request_id: str, payload: RequestServiceRequestPatch, db: Session, admin: dict) -> dict:
row = _service_request_for_id_or_404(db, service_request_id)
next_status = str(payload.status or "").strip().upper()
if next_status not in SERVICE_REQUEST_STATUSES:
raise HTTPException(status_code=400, detail="Некорректный статус запроса")
previous_status = str(row.status or "NEW")
if next_status == previous_status:
return {"status": "ok", "changed": 0, "row": _serialize_service_request(row)}
now = datetime.now(timezone.utc)
responsible = _resolve_responsible(admin)
actor_id = _actor_id_or_none(admin)
actor_uuid = _actor_uuid_or_none(admin)
row.status = next_status
if next_status in {"RESOLVED", "REJECTED"}:
row.resolved_at = now
row.resolved_by_admin_id = actor_id
row.responsible = responsible
db.add(row)
db.add(
AuditLog(
actor_admin_id=actor_uuid,
entity="request_service_requests",
entity_id=str(row.id),
action="STATUS_UPDATE",
diff={"before": {"status": previous_status}, "after": {"status": next_status}},
responsible=responsible,
)
)
db.commit()
db.refresh(row)
return {"status": "ok", "changed": 1, "row": _serialize_service_request(row)}