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,134 @@
# -*- coding: utf-8 -*-
"""
租户管理后台 — Excel 上传 Pydantic Schema。
覆盖4 种模板行数据模型、校验结果、冲突 diff、确认请求、上传记录。
需求: 5.2, 7.2, 8.4
"""
from __future__ import annotations
from typing import Literal
from pydantic import Field
from app.schemas.base import CamelModel
# ── 4 种模板行数据模型 ────────────────────────────────────
class ExpenseRow(CamelModel):
"""财务支出行数据。"""
row_index: int = Field(..., description="行号(从 1 开始)")
expense_month: str = Field(..., description="月份 YYYY-MM")
category: str = Field(..., description="支出类别8 值枚举)")
amount: float = Field(..., description="金额(> 0精度 2 位小数)")
remark: str | None = Field(None, description="备注(可选,最长 500 字符)")
class PlatformIncomeRow(CamelModel):
"""团购收入行数据。"""
row_index: int = Field(..., description="行号")
income_month: str = Field(..., description="月份 YYYY-MM")
platform_name: str = Field(..., description="平台名称")
amount: float = Field(..., description="收入金额(> 0")
remark: str | None = Field(None, description="备注(可选,最长 500 字符)")
class SalaryAdjRow(CamelModel):
"""助教奖罚行数据。"""
row_index: int = Field(..., description="行号")
salary_month: str = Field(..., description="月份 YYYY-MM")
assistant_name: str = Field(..., description="助教姓名")
assistant_number: str = Field(..., description="助教编号")
adjustment_type: str = Field(..., description="类型(扣款/奖金)")
amount: float = Field(..., description="金额(> 0")
reason: str = Field(..., description="原因(非空,最长 200 字符)")
assistant_id: int | None = Field(None, description="匹配到的助教 ID")
class RechargeCommissionRow(CamelModel):
"""充值业绩归属行数据。"""
row_index: int = Field(..., description="行号")
recharge_date: str = Field(..., description="充值日期 YYYY-MM-DD")
member_name: str = Field(..., description="会员名称")
recharge_amount: float = Field(..., description="充值金额(> 0")
assigned_assistant: str = Field(..., description="归属助教")
reward_amount: float = Field(..., description="奖励金额(≥ 0")
assistant_id: int | None = Field(None, description="匹配到的助教 ID")
# ── 校验错误/警告 ─────────────────────────────────────────
class ValidationError(CamelModel):
"""单行校验错误。"""
row_index: int = Field(..., description="行号")
column: str = Field(..., description="列名")
message: str = Field(..., description="错误描述")
class ValidationWarning(CamelModel):
"""单行校验警告(如人员匹配失败)。"""
row_index: int = Field(..., description="行号")
column: str = Field(..., description="列名")
message: str = Field(..., description="警告描述")
class ValidationResult(CamelModel):
"""校验结果。"""
errors: list[ValidationError] = Field(default_factory=list, description="错误列表")
warnings: list[ValidationWarning] = Field(default_factory=list, description="警告列表")
passed_rows: list[dict] = Field(default_factory=list, description="通过校验的行数据")
upload_id: int | None = Field(None, description="上传批次 ID校验全部通过时创建")
# ── 冲突 diff ─────────────────────────────────────────────
class FieldDiff(CamelModel):
"""单字段差异。"""
field: str = Field(..., description="字段名")
old_value: str | None = Field(None, description="旧值")
new_value: str | None = Field(None, description="新值")
class ConflictDiff(CamelModel):
"""冲突行 diff。"""
row_index: int = Field(..., description="行号")
field_diffs: list[FieldDiff] = Field(default_factory=list, description="逐字段差异")
# ── 确认请求 ──────────────────────────────────────────────
class Resolution(CamelModel):
"""单行冲突解决方案。"""
row_index: int = Field(..., description="行号")
action: Literal["replace", "keep"] = Field(..., description="操作replace=替换/keep=保留")
class ConfirmRequest(CamelModel):
"""确认写入请求。"""
upload_id: int = Field(..., description="上传批次 ID")
resolutions: list[Resolution] = Field(default_factory=list, description="冲突解决方案列表")
# ── 上传记录 ──────────────────────────────────────────────
class UploadLogItem(CamelModel):
"""上传记录列表项。"""
id: int
site_id: int
upload_type: str = Field(..., description="模板类型")
file_name: str = Field(..., description="原始文件名")
uploaded_by: int = Field(..., description="上传人 ID")
row_count: int = Field(0, description="数据行数")
conflict_count: int = Field(0, description="冲突行数")
resolved_count: int = Field(0, description="已解决冲突数")
status: str = Field(..., description="状态pending/confirmed/failed")
created_at: str | None = Field(None, description="上传时间")
confirmed_at: str | None = Field(None, description="确认时间")