feat: batch update - gift card breakdown spec, backend APIs, miniprogram pages, ETL finance recharge, docs & migrations
This commit is contained in:
17
apps/backend/app/schemas/base.py
Normal file
17
apps/backend/app/schemas/base.py
Normal 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_camel:JSON 输出字段名自动转 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,
|
||||
)
|
||||
@@ -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="目标角色 code(coach/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="助教 ID(binding_type=assistant 时必填)")
|
||||
staff_id: int | None = Field(None, description="员工 ID(binding_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="目标角色 code(coach/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="助教 ID(binding_type=assistant 时必填)")
|
||||
staff_id: int | None = Field(None, description="员工 ID(binding_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="目标角色 code(coach/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="助教 ID(binding_type=assistant 时必填)")
|
||||
staff_id: int | None = Field(None, description="员工 ID(binding_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] = []
|
||||
|
||||
|
||||
433
apps/backend/app/schemas/xcx_board.py
Normal file
433
apps/backend/app/schemas/xcx_board.py
Normal file
@@ -0,0 +1,433 @@
|
||||
"""三看板接口 Pydantic Schema(BOARD-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 响应 Schema(Task 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 响应 Schema(Task 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 响应 Schema(Task 2.4)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class OverviewPanel(CamelModel):
|
||||
"""经营一览:8 项核心指标 + 各 3 个环比字段(Optional,compare=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
|
||||
116
apps/backend/app/schemas/xcx_coaches.py
Normal file
116
apps/backend/app/schemas/xcx_coaches.py
Normal 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] = []
|
||||
12
apps/backend/app/schemas/xcx_config.py
Normal file
12
apps/backend/app/schemas/xcx_config.py
Normal 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 # 前端样式类
|
||||
124
apps/backend/app/schemas/xcx_customers.py
Normal file
124
apps/backend/app/schemas/xcx_customers.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
122
apps/backend/app/schemas/xcx_performance.py
Normal file
122
apps/backend/app/schemas/xcx_performance.py
Normal 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
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user