包含多个会话的累积代码变更: - 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>
135 lines
5.6 KiB
Python
135 lines
5.6 KiB
Python
# -*- 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="确认时间")
|