feat: batch update - gift card breakdown spec, backend APIs, miniprogram pages, ETL finance recharge, docs & migrations

This commit is contained in:
Neo
2026-03-20 01:43:48 +08:00
parent 075caf067f
commit 79f9a0e1da
437 changed files with 118603 additions and 976 deletions

View File

@@ -0,0 +1,17 @@
from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel
class CamelModel(BaseModel):
"""所有小程序 API 响应 schema 的基类。
- alias_generator=to_camelJSON 输出字段名自动转 camelCase
- populate_by_name=True同时接受 snake_case 和 camelCase 输入
- from_attributes=True支持从 ORM 对象/dict 构造
"""
model_config = ConfigDict(
alias_generator=to_camel,
populate_by_name=True,
from_attributes=True,
)

View File

@@ -6,17 +6,19 @@
from __future__ import annotations
from pydantic import BaseModel, Field
from pydantic import Field
from app.schemas.base import CamelModel
# ── 微信登录 ──────────────────────────────────────────────
class WxLoginRequest(BaseModel):
class WxLoginRequest(CamelModel):
"""微信登录请求。"""
code: str = Field(..., min_length=1, description="微信临时登录凭证")
class WxLoginResponse(BaseModel):
class WxLoginResponse(CamelModel):
"""微信登录响应。"""
access_token: str
refresh_token: str
@@ -25,7 +27,7 @@ class WxLoginResponse(BaseModel):
user_id: int
class DevLoginRequest(BaseModel):
class DevLoginRequest(CamelModel):
"""开发模式 mock 登录请求(仅 WX_DEV_MODE=true 时可用)。"""
openid: str = Field(..., min_length=1, description="模拟的微信 openid")
status: str | None = Field(None, description="模拟的用户状态;为空时保留已有用户的当前状态,新用户默认 new")
@@ -33,24 +35,24 @@ class DevLoginRequest(BaseModel):
# ── 开发调试端点(仅 WX_DEV_MODE=true ─────────────────
class DevSwitchRoleRequest(BaseModel):
class DevSwitchRoleRequest(CamelModel):
"""切换角色请求。替换当前用户在当前门店下的所有角色为指定角色。"""
role_code: str = Field(..., description="目标角色 codecoach/staff/site_admin/tenant_admin")
class DevSwitchStatusRequest(BaseModel):
class DevSwitchStatusRequest(CamelModel):
"""切换用户状态请求。"""
status: str = Field(..., description="目标状态new/pending/approved/rejected/disabled")
class DevSwitchBindingRequest(BaseModel):
class DevSwitchBindingRequest(CamelModel):
"""切换人员绑定请求。"""
binding_type: str = Field(..., description="绑定类型assistant/staff/manager")
assistant_id: int | None = Field(None, description="助教 IDbinding_type=assistant 时必填)")
staff_id: int | None = Field(None, description="员工 IDbinding_type=staff/manager 时必填)")
class DevContextResponse(BaseModel):
class DevContextResponse(CamelModel):
"""开发调试上下文信息。"""
user_id: int
openid: str | None = None
@@ -67,7 +69,7 @@ class DevLoginRequest(BaseModel):
# ── 用户申请 ──────────────────────────────────────────────
class ApplicationRequest(BaseModel):
class ApplicationRequest(CamelModel):
"""用户申请提交请求。"""
site_code: str = Field(..., pattern=r"^[A-Za-z]{2}\d{3}$", description="球房ID")
applied_role_text: str = Field(..., min_length=1, max_length=100, description="申请身份")
@@ -76,7 +78,7 @@ class ApplicationRequest(BaseModel):
nickname: str | None = Field(None, max_length=50, description="昵称")
class ApplicationResponse(BaseModel):
class ApplicationResponse(CamelModel):
"""申请记录响应。"""
id: int
site_code: str
@@ -89,7 +91,7 @@ class ApplicationResponse(BaseModel):
# ── 用户状态 ──────────────────────────────────────────────
class UserStatusResponse(BaseModel):
class UserStatusResponse(CamelModel):
"""用户状态查询响应。"""
user_id: int
status: str
@@ -99,28 +101,28 @@ class UserStatusResponse(BaseModel):
# ── 店铺 ──────────────────────────────────────────────────
class SiteInfo(BaseModel):
class SiteInfo(CamelModel):
"""店铺信息。"""
site_id: int
site_name: str
roles: list[dict] = []
class SwitchSiteRequest(BaseModel):
class SwitchSiteRequest(CamelModel):
"""切换店铺请求。"""
site_id: int
# ── 刷新令牌 ──────────────────────────────────────────────
class RefreshTokenRequest(BaseModel):
class RefreshTokenRequest(CamelModel):
"""刷新令牌请求。"""
refresh_token: str = Field(..., min_length=1, description="刷新令牌")
# ── 人员匹配 ──────────────────────────────────────────────
class MatchCandidate(BaseModel):
class MatchCandidate(CamelModel):
"""匹配候选人。"""
source_type: str # assistant / staff
id: int
@@ -131,38 +133,38 @@ class MatchCandidate(BaseModel):
# ── 管理端审核 ────────────────────────────────────────────
class ApproveRequest(BaseModel):
class ApproveRequest(CamelModel):
"""批准申请请求。"""
role_id: int
binding: dict | None = None # {"assistant_id": ..., "staff_id": ..., "binding_type": ...}
review_note: str | None = None
class RejectRequest(BaseModel):
class RejectRequest(CamelModel):
"""拒绝申请请求。"""
review_note: str = Field(..., min_length=1, description="拒绝原因")
# ── 开发调试端点(仅 WX_DEV_MODE=true ─────────────────
class DevSwitchRoleRequest(BaseModel):
class DevSwitchRoleRequest(CamelModel):
"""切换角色请求。替换当前用户在当前门店下的所有角色为指定角色。"""
role_code: str = Field(..., description="目标角色 codecoach/staff/site_admin/tenant_admin")
class DevSwitchStatusRequest(BaseModel):
class DevSwitchStatusRequest(CamelModel):
"""切换用户状态请求。"""
status: str = Field(..., description="目标状态new/pending/approved/rejected/disabled")
class DevSwitchBindingRequest(BaseModel):
class DevSwitchBindingRequest(CamelModel):
"""切换人员绑定请求。"""
binding_type: str = Field(..., description="绑定类型assistant/staff/manager")
assistant_id: int | None = Field(None, description="助教 IDbinding_type=assistant 时必填)")
staff_id: int | None = Field(None, description="员工 IDbinding_type=staff/manager 时必填)")
class DevContextResponse(BaseModel):
class DevContextResponse(CamelModel):
"""开发调试上下文信息。"""
user_id: int
openid: str | None = None
@@ -178,24 +180,24 @@ class DevContextResponse(BaseModel):
# ── 开发调试端点(仅 WX_DEV_MODE=true ─────────────────
class DevSwitchRoleRequest(BaseModel):
class DevSwitchRoleRequest(CamelModel):
"""切换角色请求。替换当前用户在当前门店下的所有角色为指定角色。"""
role_code: str = Field(..., description="目标角色 codecoach/staff/site_admin/tenant_admin")
class DevSwitchStatusRequest(BaseModel):
class DevSwitchStatusRequest(CamelModel):
"""切换用户状态请求。"""
status: str = Field(..., description="目标状态new/pending/approved/rejected/disabled")
class DevSwitchBindingRequest(BaseModel):
class DevSwitchBindingRequest(CamelModel):
"""切换人员绑定请求。"""
binding_type: str = Field(..., description="绑定类型assistant/staff/manager")
assistant_id: int | None = Field(None, description="助教 IDbinding_type=assistant 时必填)")
staff_id: int | None = Field(None, description="员工 IDbinding_type=staff/manager 时必填)")
class DevContextResponse(BaseModel):
class DevContextResponse(CamelModel):
"""开发调试上下文信息。"""
user_id: int
openid: str | None = None
@@ -207,4 +209,3 @@ class DevContextResponse(BaseModel):
permissions: list[str] = []
binding: dict | None = None
all_sites: list[dict] = []

View File

@@ -0,0 +1,433 @@
"""三看板接口 Pydantic SchemaBOARD-1/2/3 请求参数枚举 + 响应模型)。"""
from __future__ import annotations
from enum import Enum
from app.schemas.base import CamelModel
# ---------------------------------------------------------------------------
# 请求参数枚举Task 2.1
# ---------------------------------------------------------------------------
class CoachSortEnum(str, Enum):
"""BOARD-1 排序维度。"""
perf_desc = "perf_desc"
perf_asc = "perf_asc"
salary_desc = "salary_desc"
salary_asc = "salary_asc"
sv_desc = "sv_desc"
task_desc = "task_desc"
class SkillFilterEnum(str, Enum):
"""BOARD-1 技能筛选。"""
all = "all"
chinese = "chinese"
snooker = "snooker"
mahjong = "mahjong"
karaoke = "karaoke"
class BoardTimeEnum(str, Enum):
"""BOARD-1 时间范围。"""
month = "month"
quarter = "quarter"
last_month = "last_month"
last_3m = "last_3m"
last_quarter = "last_quarter"
last_6m = "last_6m"
class CustomerDimensionEnum(str, Enum):
"""BOARD-2 客户维度。"""
recall = "recall"
potential = "potential"
balance = "balance"
recharge = "recharge"
recent = "recent"
spend60 = "spend60"
freq60 = "freq60"
loyal = "loyal"
class ProjectFilterEnum(str, Enum):
"""BOARD-2 项目筛选。"""
all = "all"
chinese = "chinese"
snooker = "snooker"
mahjong = "mahjong"
karaoke = "karaoke"
class FinanceTimeEnum(str, Enum):
"""BOARD-3 时间范围。"""
month = "month"
lastMonth = "lastMonth"
week = "week"
lastWeek = "lastWeek"
quarter3 = "quarter3"
quarter = "quarter"
lastQuarter = "lastQuarter"
half6 = "half6"
class AreaFilterEnum(str, Enum):
"""BOARD-3 区域筛选。"""
all = "all"
hall = "hall"
hallA = "hallA"
hallB = "hallB"
hallC = "hallC"
mahjong = "mahjong"
teamBuilding = "teamBuilding"
# ---------------------------------------------------------------------------
# BOARD-1 响应 SchemaTask 2.2
# ---------------------------------------------------------------------------
class CoachSkillItem(CamelModel):
text: str
cls: str
class CoachBoardItem(CamelModel):
"""助教看板单条记录(扁平结构,包含所有维度字段)。"""
# 基础字段(所有维度共享)
id: int
name: str
initial: str
avatar_gradient: str
level: str # star/senior/middle/junior
skills: list[CoachSkillItem]
top_customers: list[str] # ["💖 王先生", "💛 李女士"]
# perf 维度
perf_hours: float = 0.0
perf_hours_before: float | None = None
perf_gap: str | None = None # "距升档 13.8h" 或 None
perf_reached: bool = False
# salary 维度
salary: float = 0.0
salary_perf_hours: float = 0.0
salary_perf_before: float | None = None
# sv 维度
sv_amount: float = 0.0
sv_customer_count: int = 0
sv_consume: float = 0.0
# task 维度
task_recall: int = 0
task_callback: int = 0
class CoachBoardResponse(CamelModel):
items: list[CoachBoardItem]
dim_type: str # perf/salary/sv/task
# ---------------------------------------------------------------------------
# BOARD-2 响应 SchemaTask 2.3
# ---------------------------------------------------------------------------
class CustomerAssistant(CamelModel):
name: str
cls: str
heart_score: float
badge: str | None = None
badge_cls: str | None = None
class CustomerBoardItemBase(CamelModel):
"""客户看板基础字段(所有维度共享)。"""
id: int
name: str
initial: str
avatar_cls: str
assistants: list[CustomerAssistant]
class RecallItem(CustomerBoardItemBase):
ideal_days: int
elapsed_days: int
overdue_days: int
visits_30d: int
balance: str
recall_index: float
class PotentialTag(CamelModel):
text: str
theme: str
class PotentialItem(CustomerBoardItemBase):
potential_tags: list[PotentialTag]
spend_30d: float
avg_visits: float
avg_spend: float
class BalanceItem(CustomerBoardItemBase):
balance: str
last_visit: str # "3天前"
monthly_consume: float
available_months: str # "约0.8个月"
class RechargeItem(CustomerBoardItemBase):
last_recharge: str
recharge_amount: float
recharges_60d: int
current_balance: str
class RecentItem(CustomerBoardItemBase):
days_ago: int
visit_freq: str # "6.2次/月"
ideal_days: int
visits_30d: int
avg_spend: float
class Spend60Item(CustomerBoardItemBase):
spend_60d: float
visits_60d: int
high_spend_tag: bool
avg_spend: float
class WeeklyVisit(CamelModel):
val: int
pct: int # 0-100
class Freq60Item(CustomerBoardItemBase):
visits_60d: int
avg_interval: str # "5.0天"
weekly_visits: list[WeeklyVisit] # 固定长度 8
spend_60d: float
class CoachDetail(CamelModel):
name: str
cls: str
heart_score: float
badge: str | None = None
avg_duration: str
service_count: int
coach_spend: float
relation_idx: float
class LoyalItem(CustomerBoardItemBase):
intimacy: float
top_coach_name: str
top_coach_heart: float
top_coach_score: float
coach_name: str
coach_ratio: str # "78%"
coach_details: list[CoachDetail]
class CustomerBoardResponse(CamelModel):
items: list[dict] # 实际类型取决于 dimension
total: int
page: int
page_size: int
# ---------------------------------------------------------------------------
# BOARD-3 响应 SchemaTask 2.4
# ---------------------------------------------------------------------------
class OverviewPanel(CamelModel):
"""经营一览8 项核心指标 + 各 3 个环比字段Optionalcompare=0 时为 None"""
occurrence: float
discount: float # 负值
discount_rate: float
confirmed_revenue: float
cash_in: float
cash_out: float
cash_balance: float
balance_rate: float
# occurrence 环比
occurrence_compare: str | None = None
occurrence_down: bool | None = None
occurrence_flat: bool | None = None
# discount 环比
discount_compare: str | None = None
discount_down: bool | None = None
discount_flat: bool | None = None
# discount_rate 环比
discount_rate_compare: str | None = None
discount_rate_down: bool | None = None
discount_rate_flat: bool | None = None
# confirmed_revenue 环比
confirmed_revenue_compare: str | None = None
confirmed_revenue_down: bool | None = None
confirmed_revenue_flat: bool | None = None
# cash_in 环比
cash_in_compare: str | None = None
cash_in_down: bool | None = None
cash_in_flat: bool | None = None
# cash_out 环比
cash_out_compare: str | None = None
cash_out_down: bool | None = None
cash_out_flat: bool | None = None
# cash_balance 环比
cash_balance_compare: str | None = None
cash_balance_down: bool | None = None
cash_balance_flat: bool | None = None
# balance_rate 环比
balance_rate_compare: str | None = None
balance_rate_down: bool | None = None
balance_rate_flat: bool | None = None
class GiftCell(CamelModel):
value: float
compare: str | None = None
down: bool | None = None
flat: bool | None = None
class GiftRow(CamelModel):
"""赠送卡矩阵一行:合计 / 酒水卡 / 台费卡 / 抵用券。"""
label: str # "新增" / "消费" / "余额"
total: GiftCell
liquor: GiftCell
table_fee: GiftCell
voucher: GiftCell
class RechargePanel(CamelModel):
"""预收资产板块:储值卡 5 指标 + 赠送卡 3×4 矩阵 + 全卡余额。"""
actual_income: float
first_charge: float
renew_charge: float
consumed: float
card_balance: float
gift_rows: list[GiftRow] # 3 行
all_card_balance: float
# 储值卡各项环比字段
actual_income_compare: str | None = None
actual_income_down: bool | None = None
actual_income_flat: bool | None = None
first_charge_compare: str | None = None
first_charge_down: bool | None = None
first_charge_flat: bool | None = None
renew_charge_compare: str | None = None
renew_charge_down: bool | None = None
renew_charge_flat: bool | None = None
consumed_compare: str | None = None
consumed_down: bool | None = None
consumed_flat: bool | None = None
card_balance_compare: str | None = None
card_balance_down: bool | None = None
card_balance_flat: bool | None = None
class RevenueStructureRow(CamelModel):
id: str
name: str
desc: str | None = None
is_sub: bool = False
amount: float
discount: float
booked: float
booked_compare: str | None = None
class RevenueItem(CamelModel):
label: str
amount: float
class ChannelItem(CamelModel):
label: str
amount: float
class RevenuePanel(CamelModel):
structure_rows: list[RevenueStructureRow]
price_items: list[RevenueItem] # 4 项
total_occurrence: float
discount_items: list[RevenueItem] # 4 项
confirmed_total: float
channel_items: list[ChannelItem] # 3 项
class CashflowItem(CamelModel):
label: str
amount: float
class CashflowPanel(CamelModel):
consume_items: list[CashflowItem] # 3 项
recharge_items: list[CashflowItem] # 1 项
total: float
class ExpenseItem(CamelModel):
label: str
amount: float
compare: str | None = None
down: bool | None = None
flat: bool | None = None
class ExpensePanel(CamelModel):
operation_items: list[ExpenseItem] # 3 项
fixed_items: list[ExpenseItem] # 4 项
coach_items: list[ExpenseItem] # 4 项
platform_items: list[ExpenseItem] # 3 项
total: float
total_compare: str | None = None
total_down: bool | None = None
total_flat: bool | None = None
class CoachAnalysisRow(CamelModel):
level: str
pay: float
share: float
hourly: float
pay_compare: str | None = None
pay_down: bool | None = None
share_compare: str | None = None
share_down: bool | None = None
hourly_compare: str | None = None
hourly_flat: bool | None = None
class CoachAnalysisTable(CamelModel):
total_pay: float
total_share: float
avg_hourly: float
total_pay_compare: str | None = None
total_pay_down: bool | None = None
total_share_compare: str | None = None
total_share_down: bool | None = None
avg_hourly_compare: str | None = None
avg_hourly_flat: bool | None = None
rows: list[CoachAnalysisRow] # 4 行:初级/中级/高级/星级
class CoachAnalysisPanel(CamelModel):
basic: CoachAnalysisTable # 基础课/陪打
incentive: CoachAnalysisTable # 激励课/超休
class FinanceBoardResponse(CamelModel):
overview: OverviewPanel
recharge: RechargePanel | None # area≠all 时为 null
revenue: RevenuePanel
cashflow: CashflowPanel
expense: ExpensePanel
coach_analysis: CoachAnalysisPanel

View File

@@ -0,0 +1,116 @@
from __future__ import annotations
from app.schemas.base import CamelModel
class PerformanceMetrics(CamelModel):
monthly_hours: float
monthly_salary: float
customer_balance: float
tasks_completed: int
perf_current: float
perf_target: float
class IncomeItem(CamelModel):
label: str
amount: str
color: str
class IncomeSection(CamelModel):
this_month: list[IncomeItem] = []
last_month: list[IncomeItem] = []
class CoachTaskItem(CamelModel):
type_label: str
type_class: str
customer_name: str
customer_id: int | None = None
note_count: int = 0
pinned: bool = False
notes: list[dict] | None = None
class AbandonedTask(CamelModel):
customer_name: str
reason: str
class TopCustomer(CamelModel):
id: int
name: str
initial: str
avatar_gradient: str
heart_emoji: str # ❤️ / 💛 / 🤍
score: str
score_color: str
service_count: int
balance: str
consume: str
class CoachServiceRecord(CamelModel):
customer_id: int | None = None
customer_name: str
initial: str
avatar_gradient: str
type: str
type_class: str
table: str | None = None
duration: str
income: str
date: str
perf_hours: str | None = None
class HistoryMonth(CamelModel):
month: str
estimated: bool
customers: str
hours: str
salary: str
callback_done: int
recall_done: int
class CoachNoteItem(CamelModel):
id: int
content: str
timestamp: str
score: int | None = None
customer_name: str
tag_label: str
created_at: str
class CoachDetailResponse(CamelModel):
"""COACH-1 响应。"""
# 基础信息
id: int
name: str
avatar: str
level: str
skills: list[str] = []
work_years: float = 0
customer_count: int = 0
hire_date: str | None = None
# 绩效
performance: PerformanceMetrics
# 收入
income: IncomeSection
# 档位
tier_nodes: list[float] = []
# 任务分组
visible_tasks: list[CoachTaskItem] = []
hidden_tasks: list[CoachTaskItem] = []
abandoned_tasks: list[AbandonedTask] = []
# TOP 客户
top_customers: list[TopCustomer] = []
# 近期服务记录
service_records: list[CoachServiceRecord] = []
# 历史月份
history_months: list[HistoryMonth] = []
# 备注
notes: list[CoachNoteItem] = []

View File

@@ -0,0 +1,12 @@
"""CONFIG-1 技能类型响应 Schema。"""
from __future__ import annotations
from app.schemas.base import CamelModel
class SkillTypeItem(CamelModel):
key: str # chinese/snooker/mahjong/karaoke
label: str # 中文标签
emoji: str # 表情符号
cls: str # 前端样式类

View File

@@ -0,0 +1,124 @@
from __future__ import annotations
from app.schemas.base import CamelModel
class AiStrategy(CamelModel):
color: str
text: str
class AiInsight(CamelModel):
summary: str = ""
strategies: list[AiStrategy] = []
class MetricItem(CamelModel):
label: str
value: str
color: str | None = None
class CoachTask(CamelModel):
name: str
level: str # star / senior / middle / junior
level_color: str
task_type: str
task_color: str
bg_class: str
status: str
last_service: str | None = None
metrics: list[MetricItem] = []
class FavoriteCoach(CamelModel):
emoji: str # 💖 / 💛
name: str
relation_index: str
index_color: str
bg_class: str
stats: list[MetricItem] = []
class CoachServiceItem(CamelModel):
name: str
level: str
level_color: str
course_type: str # "基础课" / "激励课"
hours: float
perf_hours: float | None = None
fee: float
class ConsumptionRecord(CamelModel):
id: str
type: str # table / shop / recharge
date: str
table_name: str | None = None
start_time: str | None = None
end_time: str | None = None
duration: int | None = None
table_fee: float | None = None
table_orig_price: float | None = None
coaches: list[CoachServiceItem] = []
food_amount: float | None = None
food_orig_price: float | None = None
total_amount: float
total_orig_price: float
pay_method: str
recharge_amount: float | None = None
class RetentionClue(CamelModel):
type: str
text: str
class CustomerNote(CamelModel):
id: int
tag_label: str
created_at: str
content: str
class CustomerDetailResponse(CamelModel):
"""CUST-1 响应。"""
# 基础信息
id: int
name: str
phone: str
phone_full: str
avatar: str
member_level: str
relation_index: str
tags: list[str] = []
# Banner 概览
balance: float | None = None
consumption_60d: float | None = None
ideal_interval: int | None = None
days_since_visit: int | None = None
# 扩展模块
ai_insight: AiInsight = AiInsight()
coach_tasks: list[CoachTask] = []
favorite_coaches: list[FavoriteCoach] = []
retention_clues: list[RetentionClue] = []
consumption_records: list[ConsumptionRecord] = []
notes: list[CustomerNote] = []
class ServiceRecordItem(CamelModel):
id: str
date: str
time_range: str | None = None
table: str | None = None
type: str
type_class: str
record_type: str | None = None # course / recharge
duration: float
duration_raw: float | None = None
income: float
is_estimate: bool = False
drinks: str | None = None
class CustomerRecordsResponse(CamelModel):
"""CUST-2 响应。"""
customer_name: str
customer_phone: str
customer_phone_full: str
relation_index: str
tables: list[dict] = []
total_service_count: int
month_count: int
month_hours: float
records: list[ServiceRecordItem] = []
has_more: bool = False

View File

@@ -6,21 +6,23 @@
from __future__ import annotations
from pydantic import BaseModel, Field
from pydantic import Field
from app.schemas.base import CamelModel
class NoteCreateRequest(BaseModel):
"""创建备注请求(含星星评分,评分 1-5 范围约束)。"""
class NoteCreateRequest(CamelModel):
"""创建备注请求(含手动评分:再次服务意愿 + 再来店可能性,各 1-5)。"""
target_type: str = Field(default="member")
target_id: int
content: str = Field(..., min_length=1)
task_id: int | None = None
rating_service_willingness: int | None = Field(None, ge=1, le=5)
rating_revisit_likelihood: int | None = Field(None, ge=1, le=5)
rating_service_willingness: int | None = Field(None, ge=1, le=5, description="再次服务意愿1-5")
rating_revisit_likelihood: int | None = Field(None, ge=1, le=5, description="再来店可能性1-5")
class NoteOut(BaseModel):
class NoteOut(CamelModel):
"""备注输出模型(含评分 + AI 评分)。"""
id: int

View File

@@ -0,0 +1,122 @@
"""
小程序绩效相关 Pydantic 模型。
覆盖绩效概览PERF-1、绩效明细PERF-2响应结构。
"""
from __future__ import annotations
from app.schemas.base import CamelModel
# ---------------------------------------------------------------------------
# 绩效通用模型
# ---------------------------------------------------------------------------
class DateGroupRecord(CamelModel):
"""按日期分组的单条服务记录。"""
customer_name: str
avatar_char: str | None = None # PERF-1 返回PERF-2 不返回
avatar_color: str | None = None # PERF-1 返回PERF-2 不返回
time_range: str
hours: str
course_type: str
course_type_class: str # 'basic' | 'vip' | 'tip'
location: str
income: str
class DateGroup(CamelModel):
"""按日期分组的服务记录组。"""
date: str
total_hours: str
total_income: str
records: list[DateGroupRecord]
# ---------------------------------------------------------------------------
# PERF-1 绩效概览
# ---------------------------------------------------------------------------
class TierInfo(CamelModel):
"""档位信息(含基础/激励费率)。"""
basic_rate: float
incentive_rate: float
class IncomeItem(CamelModel):
"""收入明细项。"""
icon: str
label: str
desc: str
value: str
class CustomerSummary(CamelModel):
"""客户摘要(新客/常客基类)。"""
name: str
avatar_char: str
avatar_color: str
class NewCustomer(CustomerSummary):
"""新客户。"""
last_service: str
count: int
class RegularCustomer(CustomerSummary):
"""常客。"""
hours: float
income: str
count: int
class PerformanceOverviewResponse(CamelModel):
"""PERF-1 响应。"""
coach_name: str
coach_role: str
store_name: str
monthly_income: str
last_month_income: str
current_tier: TierInfo
next_tier: TierInfo
upgrade_hours_needed: float
upgrade_bonus: float
income_items: list[IncomeItem]
monthly_total: str
this_month_records: list[DateGroup]
new_customers: list[NewCustomer]
regular_customers: list[RegularCustomer]
# ---------------------------------------------------------------------------
# PERF-2 绩效明细
# ---------------------------------------------------------------------------
class RecordsSummary(CamelModel):
"""月度汇总。"""
total_count: int
total_hours: float
total_hours_raw: float
total_income: float
class PerformanceRecordsResponse(CamelModel):
"""PERF-2 响应。"""
summary: RecordsSummary
date_groups: list[DateGroup]
has_more: bool

View File

@@ -1,15 +1,17 @@
"""
小程序任务相关 Pydantic 模型。
覆盖:任务列表项、放弃请求等场景。
覆盖:任务列表项、任务详情、绩效概览、放弃请求等场景。
"""
from __future__ import annotations
from pydantic import BaseModel, Field
from pydantic import Field
from app.schemas.base import CamelModel
class TaskListItem(BaseModel):
class TaskListItem(CamelModel):
"""任务列表项(含客户信息 + RS 指数 + 爱心 icon"""
id: int
@@ -30,7 +32,138 @@ class TaskListItem(BaseModel):
abandon_reason: str | None = None
class AbandonRequest(BaseModel):
class AbandonRequest(CamelModel):
"""放弃任务请求reason 必填)。"""
reason: str = Field(..., min_length=1, description="放弃原因(必填)")
# ---------------------------------------------------------------------------
# RNS1.1 扩展模型
# ---------------------------------------------------------------------------
class PerformanceSummary(CamelModel):
"""绩效概览(附带在任务列表响应中)。"""
total_hours: float
total_income: float
total_customers: int
month_label: str
tier_nodes: list[float]
basic_hours: float
bonus_hours: float
current_tier: int
next_tier_hours: float
tier_completed: bool
bonus_money: float
income_trend: str
income_trend_dir: str # 'up' | 'down'
prev_month: str
current_tier_label: str
class TaskItem(CamelModel):
"""任务列表项(扩展版)。"""
id: int
customer_name: str
customer_avatar: str
task_type: str
task_type_label: str
deadline: str | None
heart_score: float
hobbies: list[str]
is_pinned: bool
has_note: bool
status: str
last_visit_days: int | None = None
balance: float | None = None
ai_suggestion: str | None = None
class TaskListResponse(CamelModel):
"""TASK-1 响应。"""
items: list[TaskItem]
total: int
page: int
page_size: int
performance: PerformanceSummary
class RetentionClue(CamelModel):
"""维客线索。"""
tag: str
tag_color: str
emoji: str
text: str
source: str # 'manual' | 'ai_consumption' | 'ai_note'
desc: str | None = None
class ServiceRecord(CamelModel):
"""服务记录。"""
table: str | None = None
type: str
type_class: str # 'basic' | 'vip' | 'tip' | 'recharge' | 'incentive'
record_type: str | None = None # 'course' | 'recharge'
duration: float
duration_raw: float | None = None
income: float
is_estimate: bool | None = None
drinks: str | None = None
date: str
class AiAnalysis(CamelModel):
"""AI 分析结果。"""
summary: str
suggestions: list[str]
class NoteItem(CamelModel):
"""备注项。"""
id: int
content: str
tag_type: str
tag_label: str
created_at: str
score: int | None = None
class ServiceSummary(CamelModel):
"""服务记录摘要。"""
total_hours: float
total_income: float
avg_income: float
class TaskDetailResponse(CamelModel):
"""TASK-2 响应。"""
# 基础信息
id: int
customer_name: str
customer_avatar: str
task_type: str
task_type_label: str
deadline: str | None
heart_score: float
hobbies: list[str]
is_pinned: bool
has_note: bool
status: str
customer_id: int
# 扩展模块
retention_clues: list[RetentionClue]
talking_points: list[str]
service_summary: ServiceSummary
service_records: list[ServiceRecord]
ai_analysis: AiAnalysis
notes: list[NoteItem]