# -*- 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="确认时间")