v0.1.0: CRM/ERP 系统内测版本 - 安全加固完成

- Docker bridge 网络隔离(8000 端口封死)
- Gunicorn 4 Worker 多进程
- Alembic 数据库迁移基线
- 日志轮转 20m×3
- JWT 密钥 + DB 密码 + CORS 收紧
- 3-2-1 备份链路(NAS + R740-B 冷备)
- 连接池 pool_pre_ping + pool_recycle=3600
This commit is contained in:
hankin
2026-03-16 07:31:37 +00:00
commit 423baff73b
2578 changed files with 824643 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
"""
财务票据域 Pydantic V2 Schemas
票据池 / 报销单(嵌套明细)/ 审批状态变更
"""
from __future__ import annotations
import uuid
from datetime import date, datetime
from typing import Any
from pydantic import BaseModel, Field
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 票据池
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
class InvoiceCreate(BaseModel):
file_url: str | None = Field(default=None, max_length=500)
merchant_name: str | None = Field(default=None, max_length=200)
amount: float = Field(default=0, ge=0)
invoice_date: date | None = None
type: str = Field(default="expense", pattern=r"^(expense|customer)$")
ai_extracted_data: dict[str, Any] = Field(
default_factory=dict,
description="OCR/AI 解析的结构化数据 (JSONB)",
examples=[{"merchant": "中国石化", "amount": 580.00, "date": "2026-02-20"}],
)
class InvoiceResponse(BaseModel):
id: uuid.UUID
uploader_id: uuid.UUID | None = None
uploader_name: str | None = None
file_url: str | None = None
merchant_name: str | None = None
amount: float = 0
invoice_date: date | None = None
type: str
ai_extracted_data: dict[str, Any] = Field(default_factory=dict)
is_used: bool = False
created_at: datetime
model_config = {"from_attributes": True}
class InvoiceListResponse(BaseModel):
total: int
items: list[InvoiceResponse]
page: int
size: int
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 报销单创建(嵌套明细)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
class ExpenseDetailCreate(BaseModel):
invoice_id: uuid.UUID = Field(..., description="关联发票 ID")
expense_desc: str | None = Field(default=None, max_length=500)
expense_date: date | None = Field(default=None, description="费用发生日期")
original_type: str | None = Field(
default=None, max_length=50, examples=["fuel", "entertainment", "travel", "office"]
)
offset_type: str | None = Field(default=None, max_length=50)
amount: float = Field(..., gt=0, description="本行报销金额")
class ExpenseCreate(BaseModel):
total_amount: float = Field(..., gt=0)
remark: str | None = None
items: list[ExpenseDetailCreate] = Field(..., min_length=1)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 报销明细响应
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
class ExpenseDetailResponse(BaseModel):
id: uuid.UUID
invoice_id: uuid.UUID | None = None
invoice_merchant: str | None = None
invoice_amount: float | None = None
expense_desc: str | None = None
expense_date: date | None = None
original_type: str | None = None
offset_type: str | None = None
amount: float = 0
model_config = {"from_attributes": True}
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 报销单响应
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
class ExpenseResponse(BaseModel):
id: uuid.UUID
system_no: str
applicant_id: uuid.UUID
applicant_name: str | None = None
total_amount: float
status: str
remark: str | None = None
approved_by: uuid.UUID | None = None
approver_name: str | None = None
approved_at: datetime | None = None
details: list[ExpenseDetailResponse] = Field(default_factory=list)
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
class ExpenseBriefResponse(BaseModel):
id: uuid.UUID
system_no: str
applicant_id: uuid.UUID
applicant_name: str | None = None
total_amount: float
status: str
created_at: datetime
model_config = {"from_attributes": True}
class ExpenseListResponse(BaseModel):
total: int
items: list[ExpenseBriefResponse]
page: int
size: int
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 审批状态变更
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
class ExpenseStatusUpdate(BaseModel):
action: str = Field(
..., pattern=r"^(withdraw|approve|reject)$",
description="状态变更动作: withdraw/approve/reject",
)
reason: str | None = Field(default=None, description="审批意见或驳回原因")