5efe624340
- 反馈:新增 GET /admin/api/feedbacks/summary(待审核/已采纳/未采纳/合计计数); 列表联表带出完整手机号 + 昵称(供 admin 点手机号查该用户全部反馈) - 低价审核:列表加 sort_by/sort_order(提交时间排序);联表带出手机号/昵称, 并按 comparison_record_id 关联比价记录带出 trace_id/trace_url + 机型/ROM/Android/app 版本 - feedback 表新增 app_version/device_model/rom_name/android_version 四列 + alembic 迁移; /api/v1/feedback 提交接口接收并落库,供 admin 反馈页展示「提交版本号」「机型OS版本」 (字段可空,旧端不传不影响提交) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: zzhyyyyy <2685922758@qq.com> Reviewed-on: #94 Co-authored-by: zhuzihao <zhuzihao@wonderable.ai> Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
176 lines
5.3 KiB
Python
176 lines
5.3 KiB
Python
"""admin 反馈工单:列表筛选 + 审核采纳/拒绝(带金币发放与审计)。"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
|
|
|
from app.admin.audit import write_audit
|
|
from app.admin.deps import AdminDb, get_client_ip, get_current_admin, require_role
|
|
from app.admin.repositories import mutations, queries
|
|
from app.admin.schemas.common import CursorPage, OkResponse
|
|
from app.admin.schemas.feedback import (
|
|
FeedbackApproveRequest,
|
|
FeedbackOut,
|
|
FeedbackRejectRequest,
|
|
FeedbackSummary,
|
|
)
|
|
from app.models.admin import AdminUser
|
|
from app.models.feedback import Feedback
|
|
from app.repositories import wallet as wallet_repo
|
|
|
|
router = APIRouter(
|
|
prefix="/admin/api/feedbacks",
|
|
tags=["admin-feedback"],
|
|
dependencies=[Depends(get_current_admin)],
|
|
)
|
|
|
|
|
|
def _ensure_pending(fb: Feedback) -> None:
|
|
if fb.status not in {"pending", "new"}:
|
|
raise HTTPException(status_code=400, detail="反馈已审核")
|
|
|
|
|
|
@router.get("", response_model=CursorPage[FeedbackOut], summary="反馈工单列表")
|
|
def list_feedbacks(
|
|
db: AdminDb,
|
|
status: Annotated[str | None, Query()] = None,
|
|
user_id: Annotated[int | None, Query()] = None,
|
|
content: Annotated[str | None, Query(max_length=100)] = None,
|
|
created_from: Annotated[datetime | None, Query()] = None,
|
|
created_to: Annotated[datetime | None, Query()] = None,
|
|
sort_by: Annotated[str, Query(pattern="^(id|created_at)$")] = "id",
|
|
sort_order: Annotated[str, Query(pattern="^(asc|desc)$")] = "desc",
|
|
limit: Annotated[int, Query(ge=1, le=100)] = 20,
|
|
cursor: Annotated[int | None, Query()] = None,
|
|
) -> CursorPage[FeedbackOut]:
|
|
items, next_cursor, total = queries.list_feedbacks(
|
|
db,
|
|
status=status,
|
|
user_id=user_id,
|
|
content=content,
|
|
created_from=created_from,
|
|
created_to=created_to,
|
|
sort_by=sort_by,
|
|
sort_order=sort_order,
|
|
limit=limit,
|
|
cursor=cursor,
|
|
)
|
|
return CursorPage(
|
|
items=[FeedbackOut.model_validate(f) for f in items],
|
|
next_cursor=next_cursor,
|
|
total=total,
|
|
)
|
|
|
|
|
|
@router.get("/summary", response_model=FeedbackSummary, summary="反馈审核统计(各状态计数)")
|
|
def feedback_summary(db: AdminDb) -> FeedbackSummary:
|
|
return FeedbackSummary.model_validate(queries.feedback_summary(db))
|
|
|
|
|
|
@router.post("/{feedback_id}/handle", response_model=OkResponse, summary="标记反馈已处理")
|
|
def handle_feedback(
|
|
feedback_id: int,
|
|
_admin: Annotated[AdminUser, Depends(require_role("operator"))],
|
|
db: AdminDb,
|
|
) -> OkResponse:
|
|
fb = db.get(Feedback, feedback_id)
|
|
if fb is None:
|
|
raise HTTPException(status_code=404, detail="反馈不存在")
|
|
raise HTTPException(status_code=400, detail="请使用采纳或拒绝接口审核反馈")
|
|
|
|
|
|
@router.post("/{feedback_id}/approve", response_model=FeedbackOut, summary="采纳反馈并发金币")
|
|
def approve_feedback(
|
|
feedback_id: int,
|
|
payload: FeedbackApproveRequest,
|
|
request: Request,
|
|
admin: Annotated[AdminUser, Depends(require_role("operator"))],
|
|
db: AdminDb,
|
|
) -> FeedbackOut:
|
|
fb = db.get(Feedback, feedback_id)
|
|
if fb is None:
|
|
raise HTTPException(status_code=404, detail="反馈不存在")
|
|
_ensure_pending(fb)
|
|
|
|
before = fb.status
|
|
mutations.review_feedback(
|
|
db,
|
|
fb,
|
|
status="adopted",
|
|
reward_coins=payload.reward_coins,
|
|
review_note=payload.note,
|
|
reviewed_by_admin_id=admin.id,
|
|
commit=False,
|
|
)
|
|
wallet_repo.grant_coins(
|
|
db,
|
|
fb.user_id,
|
|
payload.reward_coins,
|
|
biz_type="feedback_reward",
|
|
ref_id=str(fb.id),
|
|
remark="意见反馈被采纳",
|
|
)
|
|
write_audit(
|
|
db,
|
|
admin,
|
|
action="feedback.approve",
|
|
target_type="feedback",
|
|
target_id=feedback_id,
|
|
detail={
|
|
"before": before,
|
|
"after": "adopted",
|
|
"reward_coins": payload.reward_coins,
|
|
"note": payload.note,
|
|
},
|
|
ip=get_client_ip(request),
|
|
commit=False,
|
|
)
|
|
db.commit()
|
|
db.refresh(fb)
|
|
return FeedbackOut.model_validate(fb)
|
|
|
|
|
|
@router.post("/{feedback_id}/reject", response_model=FeedbackOut, summary="拒绝采纳反馈")
|
|
def reject_feedback(
|
|
feedback_id: int,
|
|
payload: FeedbackRejectRequest,
|
|
request: Request,
|
|
admin: Annotated[AdminUser, Depends(require_role("operator"))],
|
|
db: AdminDb,
|
|
) -> FeedbackOut:
|
|
fb = db.get(Feedback, feedback_id)
|
|
if fb is None:
|
|
raise HTTPException(status_code=404, detail="反馈不存在")
|
|
_ensure_pending(fb)
|
|
|
|
before = fb.status
|
|
mutations.review_feedback(
|
|
db,
|
|
fb,
|
|
status="rejected",
|
|
reject_reason=payload.reason,
|
|
review_note=payload.note,
|
|
reviewed_by_admin_id=admin.id,
|
|
commit=False,
|
|
)
|
|
write_audit(
|
|
db,
|
|
admin,
|
|
action="feedback.reject",
|
|
target_type="feedback",
|
|
target_id=feedback_id,
|
|
detail={
|
|
"before": before,
|
|
"after": "rejected",
|
|
"reason": payload.reason,
|
|
"note": payload.note,
|
|
},
|
|
ip=get_client_ip(request),
|
|
commit=False,
|
|
)
|
|
db.commit()
|
|
db.refresh(fb)
|
|
return FeedbackOut.model_validate(fb)
|