Complete CarPass product flows
This commit is contained in:
151
app/api/admin.py
151
app/api/admin.py
@@ -1,14 +1,22 @@
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_current_telegram_user, log_audit, require_platform_role
|
||||
from app.core.config import settings
|
||||
from app.db.session import get_session
|
||||
from app.models.car import AuditLog, ServiceCenter, ServiceCenterVerification, ServiceVisit
|
||||
from app.models.car import (
|
||||
AuditLog,
|
||||
ServiceCenter,
|
||||
ServiceCenterVerification,
|
||||
ServiceEmployee,
|
||||
ServiceVisit,
|
||||
)
|
||||
from app.models.user import User
|
||||
from app.schemas.service_center import ServiceCenterRead, ServiceVisitRead
|
||||
from app.schemas.service_center import AdminModerationDecision, ServiceCenterRead, ServiceVisitRead
|
||||
|
||||
router = APIRouter(prefix="/admin", tags=["admin"])
|
||||
|
||||
@@ -31,9 +39,23 @@ async def pending_service_centers(
|
||||
return list(result.scalars())
|
||||
|
||||
|
||||
@router.get("/service-centers/{service_center_id}", response_model=ServiceCenterRead)
|
||||
async def admin_service_center_detail(
|
||||
service_center_id: int,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
current_user: User = Depends(get_current_telegram_user),
|
||||
) -> ServiceCenter:
|
||||
require_admin_or_verifier(current_user)
|
||||
center = await session.get(ServiceCenter, service_center_id)
|
||||
if center is None:
|
||||
raise HTTPException(status_code=404, detail="Service center not found")
|
||||
return center
|
||||
|
||||
|
||||
@router.post("/service-centers/{service_center_id}/verify", response_model=ServiceCenterRead)
|
||||
async def verify_service_center(
|
||||
service_center_id: int,
|
||||
payload: AdminModerationDecision | None = None,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
current_user: User = Depends(get_current_telegram_user),
|
||||
) -> ServiceCenter:
|
||||
@@ -43,8 +65,21 @@ async def verify_service_center(
|
||||
raise HTTPException(status_code=404, detail="Service center not found")
|
||||
center.verification_status = "approved"
|
||||
center.verified_at = datetime.now(UTC)
|
||||
await mark_latest_verification(session, center.id, "approved", current_user.id)
|
||||
await log_audit(session, actor=current_user, action="service_center.verify", target_type="service_center", target_id=center.id)
|
||||
if center.owner_user_id:
|
||||
owner = await session.get(User, center.owner_user_id)
|
||||
if owner:
|
||||
owner.platform_role = "service_owner"
|
||||
await ensure_owner_employee(session, center.id, owner.id)
|
||||
await notify_user(owner, f"Заявка СТО «{center.display_name or center.name}» одобрена. Панель СТО доступна в CarPass.")
|
||||
await mark_latest_verification(session, center.id, "approved", current_user.id, payload)
|
||||
await log_audit(
|
||||
session,
|
||||
actor=current_user,
|
||||
action="service_center.verify",
|
||||
target_type="service_center",
|
||||
target_id=center.id,
|
||||
metadata={"comment": payload.comment if payload else None},
|
||||
)
|
||||
await session.commit()
|
||||
await session.refresh(center)
|
||||
return center
|
||||
@@ -53,6 +88,7 @@ async def verify_service_center(
|
||||
@router.post("/service-centers/{service_center_id}/reject", response_model=ServiceCenterRead)
|
||||
async def reject_service_center(
|
||||
service_center_id: int,
|
||||
payload: AdminModerationDecision | None = None,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
current_user: User = Depends(get_current_telegram_user),
|
||||
) -> ServiceCenter:
|
||||
@@ -61,8 +97,20 @@ async def reject_service_center(
|
||||
if center is None:
|
||||
raise HTTPException(status_code=404, detail="Service center not found")
|
||||
center.verification_status = "rejected"
|
||||
await mark_latest_verification(session, center.id, "rejected", current_user.id)
|
||||
await log_audit(session, actor=current_user, action="service_center.reject", target_type="service_center", target_id=center.id)
|
||||
if center.owner_user_id:
|
||||
owner = await session.get(User, center.owner_user_id)
|
||||
if owner:
|
||||
reason = payload.reason or payload.comment if payload else None
|
||||
await notify_user(owner, f"Заявка СТО «{center.display_name or center.name}» отклонена.{f' Причина: {reason}' if reason else ''}")
|
||||
await mark_latest_verification(session, center.id, "rejected", current_user.id, payload)
|
||||
await log_audit(
|
||||
session,
|
||||
actor=current_user,
|
||||
action="service_center.reject",
|
||||
target_type="service_center",
|
||||
target_id=center.id,
|
||||
metadata={"reason": payload.reason if payload else None, "comment": payload.comment if payload else None},
|
||||
)
|
||||
await session.commit()
|
||||
await session.refresh(center)
|
||||
return center
|
||||
@@ -71,6 +119,7 @@ async def reject_service_center(
|
||||
@router.post("/service-centers/{service_center_id}/suspend", response_model=ServiceCenterRead)
|
||||
async def suspend_service_center(
|
||||
service_center_id: int,
|
||||
payload: AdminModerationDecision | None = None,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
current_user: User = Depends(get_current_telegram_user),
|
||||
) -> ServiceCenter:
|
||||
@@ -80,7 +129,50 @@ async def suspend_service_center(
|
||||
raise HTTPException(status_code=404, detail="Service center not found")
|
||||
center.verification_status = "suspended"
|
||||
center.suspended_at = datetime.now(UTC)
|
||||
await log_audit(session, actor=current_user, action="service_center.suspend", target_type="service_center", target_id=center.id)
|
||||
if center.owner_user_id:
|
||||
owner = await session.get(User, center.owner_user_id)
|
||||
if owner:
|
||||
reason = payload.reason or payload.comment if payload else None
|
||||
await notify_user(owner, f"СТО «{center.display_name or center.name}» временно заблокировано.{f' Причина: {reason}' if reason else ''}")
|
||||
await log_audit(
|
||||
session,
|
||||
actor=current_user,
|
||||
action="service_center.suspend",
|
||||
target_type="service_center",
|
||||
target_id=center.id,
|
||||
metadata={"reason": payload.reason if payload else None, "comment": payload.comment if payload else None},
|
||||
)
|
||||
await session.commit()
|
||||
await session.refresh(center)
|
||||
return center
|
||||
|
||||
|
||||
@router.post("/service-centers/{service_center_id}/request-changes", response_model=ServiceCenterRead)
|
||||
async def request_service_center_changes(
|
||||
service_center_id: int,
|
||||
payload: AdminModerationDecision,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
current_user: User = Depends(get_current_telegram_user),
|
||||
) -> ServiceCenter:
|
||||
require_admin_or_verifier(current_user)
|
||||
center = await session.get(ServiceCenter, service_center_id)
|
||||
if center is None:
|
||||
raise HTTPException(status_code=404, detail="Service center not found")
|
||||
center.verification_status = "needs_changes"
|
||||
if center.owner_user_id:
|
||||
owner = await session.get(User, center.owner_user_id)
|
||||
if owner:
|
||||
reason = payload.reason or payload.comment or "Администратор попросил уточнить данные заявки."
|
||||
await notify_user(owner, f"По заявке СТО «{center.display_name or center.name}» нужны правки: {reason}")
|
||||
await mark_latest_verification(session, center.id, "needs_changes", current_user.id, payload)
|
||||
await log_audit(
|
||||
session,
|
||||
actor=current_user,
|
||||
action="service_center.request_changes",
|
||||
target_type="service_center",
|
||||
target_id=center.id,
|
||||
metadata={"reason": payload.reason, "comment": payload.comment},
|
||||
)
|
||||
await session.commit()
|
||||
await session.refresh(center)
|
||||
return center
|
||||
@@ -126,7 +218,11 @@ async def disputes(
|
||||
|
||||
|
||||
async def mark_latest_verification(
|
||||
session: AsyncSession, service_center_id: int, status: str, reviewed_by: int
|
||||
session: AsyncSession,
|
||||
service_center_id: int,
|
||||
status: str,
|
||||
reviewed_by: int,
|
||||
payload: AdminModerationDecision | None = None,
|
||||
) -> None:
|
||||
result = await session.execute(
|
||||
select(ServiceCenterVerification)
|
||||
@@ -139,3 +235,42 @@ async def mark_latest_verification(
|
||||
verification.status = status
|
||||
verification.reviewed_by = reviewed_by
|
||||
verification.reviewed_at = datetime.now(UTC)
|
||||
if payload and (payload.reason or payload.comment):
|
||||
verification.comment = "\n".join(
|
||||
item for item in [payload.reason, payload.comment] if item
|
||||
)
|
||||
|
||||
|
||||
async def ensure_owner_employee(session: AsyncSession, service_center_id: int, owner_user_id: int) -> None:
|
||||
result = await session.execute(
|
||||
select(ServiceEmployee).where(
|
||||
ServiceEmployee.service_center_id == service_center_id,
|
||||
ServiceEmployee.user_id == owner_user_id,
|
||||
)
|
||||
)
|
||||
employee = result.scalar_one_or_none()
|
||||
if employee is None:
|
||||
session.add(
|
||||
ServiceEmployee(
|
||||
service_center_id=service_center_id,
|
||||
user_id=owner_user_id,
|
||||
role="owner",
|
||||
status="active",
|
||||
)
|
||||
)
|
||||
else:
|
||||
employee.role = "owner"
|
||||
employee.status = "active"
|
||||
|
||||
|
||||
async def notify_user(user: User, text: str) -> None:
|
||||
if not settings.bot_token:
|
||||
return
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5) as client:
|
||||
await client.post(
|
||||
f"https://api.telegram.org/bot{settings.bot_token}/sendMessage",
|
||||
data={"chat_id": str(user.telegram_id), "text": text},
|
||||
)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user