feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本

包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Neo
2026-04-06 00:03:48 +08:00
parent 70324d8542
commit 6f8f12314f
515 changed files with 76604 additions and 7456 deletions

View File

@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
"""
租户管理后台 — 用户审核 + 用户管理 Pydantic Schema。
覆盖:申请列表、关联建议、审核通过/拒绝、用户列表、编辑、绑定、角色列表、人员候选。
需求: 3.2, 4.1
AI_CHANGELOG
- 2026-03-23 17:00:00 | Prompt: P20260323-164500审核弹窗改造| Direct cause新增 roles + site-staff 端点需要响应 Schema | Summary新增 RoleItem角色列表项+ StaffCandidate人员候选项source 区分 assistant/staff| Verify后端 /roles 和 /site-staff 返回正确 JSON 结构
- 2026-03-24 | Prompt: 用户管理绑定功能改造 | Direct causeUserEditRequest 需要同时携带角色+绑定字段 | SummaryUserEditRequest 扩展 assistant_id/staff_id 字段,支持角色+绑定合并提交 | VerifyPATCH /users/{id} 接受 assistantId/staffId 参数
- 2026-03-24 | Prompt: 审核弹窗头像昵称+排版优化 | Direct causeApplicationListItem 缺少 avatar_url | Summary新增 avatar_url 字段 | VerifyGET /applications 返回 avatarUrl
"""
from __future__ import annotations
from pydantic import Field
from app.schemas.base import CamelModel
# ── 用户申请审核 ──────────────────────────────────────────
class ApplicationListItem(CamelModel):
"""申请列表项。"""
id: int
user_id: int
nickname: str | None = None
avatar_url: str | None = None
phone: str | None = None
site_code: str | None = None
applied_role_text: str | None = None
employee_number: str | None = None
created_at: str | None = None
status: str # pending / approved / rejected
class MatchSuggestion(CamelModel):
"""关联匹配建议。"""
assistant_id: int | None = None
staff_id: int | None = None
name: str
number: str | None = None
source_table: str # v_dim_assistant / v_dim_staff
class ApproveRequest(CamelModel):
"""审核通过请求。"""
role: str = Field(..., min_length=1, description="分配角色coach/staff/head_coach/manager")
assistant_id: int | None = Field(None, description="关联助教 ID")
staff_id: int | None = Field(None, description="关联员工 ID")
class RejectRequest(CamelModel):
"""审核拒绝请求。"""
reason: str = Field(..., min_length=1, description="拒绝原因")
# ── 用户管理 ──────────────────────────────────────────────
class UserListItem(CamelModel):
"""用户列表项。"""
id: int
nickname: str | None = None
role: str | None = None # 角色中文名(显示用)
role_code: str | None = None # 角色 code提交用
assistant_id: int | None = None # 当前绑定的助教 ID
staff_id: int | None = None # 当前绑定的员工 ID
assistant_name: str | None = None
site_name: str | None = None
site_id: int | None = None
status: str # approved / disabled
class UserEditRequest(CamelModel):
"""用户编辑请求(合并角色+绑定)。
角色与绑定互斥coach 只能绑 assistant_id其他角色只能绑 staff_id。
换角色时后端自动清除旧绑定。staffBinding="none" 表示解绑。
"""
role: str | None = Field(None, description="新角色 codecoach/staff/head_coach/manager")
site_id: int | None = Field(None, description="新门店 ID")
assistant_id: int | None = Field(None, description="关联助教 ID仅 coach 角色)")
staff_id: int | None = Field(None, description="关联员工 ID仅非 coach 角色)")
# CHANGE 2026-03-23 | 移除 status 字段:租户不能禁用用户,只能移除店铺关系
# CHANGE 2026-03-24 | 合并绑定字段:角色+绑定同一请求提交,换角色自动清除旧绑定
class UserBindingRequest(CamelModel):
"""用户绑定修改请求。"""
assistant_id: int | None = Field(None, description="关联助教 ID")
staff_id: int | None = Field(None, description="关联员工 ID")
# ── 角色 + 人员候选 ──────────────────────────────────────
# [CHANGE P20260323-164500] intent: 审核弹窗角色动态化 + 人员联动所需的响应 Schema
# assumptions: StaffCandidate.source 区分 assistant/staff前端据此构造 staffBinding 值
class RoleItem(CamelModel):
"""角色列表项(从 auth.roles 动态读取)。"""
id: int
code: str
name: str
description: str | None = None
class StaffCandidate(CamelModel):
"""人员候选项(审核弹窗关联下拉用)。"""
id: int = Field(..., description="assistant_id 或 staff_id")
identity_label: str | None = Field(None, description="身份角色level / staff_identity 的原始值)")
name: str = Field(..., description="姓名")
mobile: str | None = Field(None, description="手机号")
entry_time: str | None = Field(None, description="入职时间")
source: str = Field(..., description="assistant / staff")