"""admin 反馈工单 schemas。""" from __future__ import annotations from datetime import datetime from pydantic import BaseModel, ConfigDict, Field, field_validator from app.core.rewards import FEEDBACK_REWARD_MAX_COINS class FeedbackOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int user_id: int content: str contact: str # 反馈来源入口:profile(「我的」页)/ comparison(比价结果页)——admin 展示/筛选「反馈类型」 source: str = "profile" # 比价反馈的问题场景(找错商品/优惠不对…);普通反馈为 None scene: str | None = None images: list[str] | None = None status: str reject_reason: str | None = None reward_coins: int | None = None review_note: str | None = None # 运营给用户的回复留言(用户端可见,采纳/未采纳都可填) admin_reply: str | None = None reviewed_by_admin_id: int | None = None reviewed_at: datetime | None = None created_at: datetime # 提交端环境快照(feedback 表列):提交版本号 / 机型OS版本;改版前的历史反馈为 None app_version: str | None = None device_model: str | None = None rom_name: str | None = None android_version: str | None = None # 联表瞬态字段(queries._attach_user_info 挂):列表展示完整手机号,点手机号查该用户全部反馈 phone: str | None = None nickname: str | None = None class FeedbackApproveRequest(BaseModel): reward_coins: int = Field( ge=1, le=FEEDBACK_REWARD_MAX_COINS, description="采纳后发放金币数", ) note: str | None = Field(default=None, max_length=256, description="采纳要点/审核备注(内部)") reply: str | None = Field(default=None, max_length=256, description="给用户的回复留言,用户端可见") class FeedbackRejectRequest(BaseModel): reason: str = Field(min_length=1, max_length=256, description="未采纳原因,用户端可见") note: str | None = Field(default=None, max_length=256, description="运营内部审核备注") reply: str | None = Field(default=None, max_length=256, description="给用户的回复留言,用户端可见") class FeedbackBulkRequest(BaseModel): ids: list[int] = Field(min_length=1, max_length=50, description="待审核反馈 ID 列表") @field_validator("ids") @classmethod def _ids_must_be_unique(cls, ids: list[int]) -> list[int]: if len(ids) != len(set(ids)): raise ValueError("反馈 ID 不能重复") return ids class FeedbackBulkApproveRequest(FeedbackBulkRequest): reward_coins: int = Field( ge=1, le=FEEDBACK_REWARD_MAX_COINS, description="每条采纳反馈发放的金币数", ) note: str | None = Field(default=None, max_length=256, description="采纳要点/审核备注(内部)") reply: str | None = Field(default=None, max_length=256, description="给用户的回复留言,用户端可见") class FeedbackBulkRejectRequest(FeedbackBulkRequest): reason: str = Field(min_length=1, max_length=256, description="批量未采纳原因,用户端可见") note: str | None = Field(default=None, max_length=256, description="运营内部审核备注") reply: str | None = Field(default=None, max_length=256, description="给用户的回复留言,用户端可见") @field_validator("reason") @classmethod def _reason_not_blank(cls, value: str) -> str: if not value.strip(): raise ValueError("未采纳原因不能为空") return value.strip() class FeedbackBulkItemResult(BaseModel): id: int ok: bool status: str | None = None error: str | None = None class FeedbackBulkResult(BaseModel): total: int success: int failed: int items: list[FeedbackBulkItemResult] class FeedbackSummary(BaseModel): """审核台顶部各状态计数(pending 含历史 new 态)。""" pending: int adopted: int rejected: int total: int