PMFlow/backend/app/api/ai_steward_api/routes_feedback_center.py

158 lines
5.5 KiB
Python

from __future__ import annotations
from enum import Enum
from typing import Literal
from fastapi import Depends, HTTPException, Query
from sqlalchemy import and_, select
from sqlalchemy.ext.asyncio import AsyncSession
from ...core.response import success_response
from ...dependencies.permissions import require_manager_user
from ...models import User, UserProject
from ...schemas.feedback_center import FeedbackCenterReviewRequest
from ...services.auth import parse_role_values
from ...services.feedback_center import FeedbackCenterService, list_reason_catalog
from .shared import *
MANAGER_ROLES = {"ADMIN", "BOSS"}
def _is_manager_user(current_user: User) -> bool:
role_value = getattr(current_user, "role", "")
if isinstance(role_value, Enum):
role_value = role_value.value
roles = set(parse_role_values(str(role_value or "")))
return bool(roles.intersection(MANAGER_ROLES))
async def _ensure_summary_scope(
*,
current_user: User,
db: AsyncSession,
project_id: int | None,
) -> None:
if _is_manager_user(current_user):
return
if project_id is None:
raise HTTPException(status_code=403, detail="Manager permission required for global feedback center")
membership = await db.execute(
select(UserProject.id).where(
and_(
UserProject.tenant_id == int(current_user.tenant_id),
UserProject.project_id == int(project_id),
UserProject.user_id == int(current_user.id),
)
)
)
if membership.scalar_one_or_none() is None:
raise HTTPException(status_code=403, detail="Project membership required")
@router.get("/feedback-center/summary")
async def get_feedback_center_summary(
project_id: int | None = Query(default=None),
window_days: int | None = Query(default=None),
source_group: str | None = Query(default=None),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
await _ensure_summary_scope(current_user=current_user, db=db, project_id=project_id)
try:
data = await FeedbackCenterService(db).summary(
tenant_id=effective_tenant_id,
project_id=project_id,
window_days=window_days,
source_group=source_group,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return success_response(data)
@router.get("/feedback-center/items")
async def list_feedback_center_items(
project_id: int | None = Query(default=None),
source_type: str | None = Query(default=None),
review_status: str | None = Query(default=None),
field_name: str | None = Query(default=None),
reason_code: str | None = Query(default=None),
search: str | None = Query(default=None),
limit: int = Query(default=20, ge=1, le=100),
offset: int = Query(default=0, ge=0),
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
data = await FeedbackCenterService(db).list_records(
tenant_id=effective_tenant_id,
project_id=project_id,
source_type=source_type,
review_status=review_status,
field_name=field_name,
reason_code=reason_code,
search=search,
limit=limit,
offset=offset,
)
return success_response(data)
@router.get("/feedback-center/items/{record_id}")
async def get_feedback_center_item_detail(
record_id: int,
project_id: int | None = Query(default=None),
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
data = await FeedbackCenterService(db).get_detail(
tenant_id=effective_tenant_id,
record_id=record_id,
project_id=project_id,
)
if data is None:
raise HTTPException(status_code=404, detail="feedback record not found")
return success_response(data)
@router.post("/feedback-center/items/{record_id}/review")
async def review_feedback_center_item(
record_id: int,
request: FeedbackCenterReviewRequest,
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id)
service = FeedbackCenterService(db)
reviewed = await service.review(
tenant_id=effective_tenant_id,
record_id=record_id,
reviewer_id=int(current_user.id),
review_status=request.review_status,
reason_code=request.reason_code,
reason_text=request.reason_text,
)
return success_response(reviewed)
@router.get("/feedback-center/reason-catalog")
async def get_feedback_center_reason_catalog(
source_type: str | None = Query(default=None),
field_name: str | None = Query(default=None),
current_user: User = Depends(require_manager_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
_ = db, tenant_id, current_user
return success_response(
{
"items": list_reason_catalog(source_type=source_type, field_name=field_name),
}
)