feat: 新增预算后端服务与差旅风险规则库

后端新增预算模型、端点和服务模块,支持预算 CRUD 和余额
查询,清理旧生成规则文件并替换为按严重等级分类的差旅风
险规则库,优化认证权限和报销单访问策略,新增财务规则目
录和演示数据构建脚本,前端预算中心增加对话框交互,完善
审计页面运行时模型和元数据展示,补充单元测试。
This commit is contained in:
caoxiaozhu
2026-05-26 17:29:35 +08:00
parent e1e515ecae
commit e7bef0883d
85 changed files with 6443 additions and 1497 deletions

View File

@@ -0,0 +1,120 @@
from __future__ import annotations
from datetime import datetime
from decimal import Decimal
from pydantic import BaseModel, ConfigDict, Field
class BudgetAllocationCreate(BaseModel):
fiscal_year: int = Field(ge=2000, le=2100)
period_type: str = Field(default="quarter", max_length=20)
period_key: str = Field(min_length=1, max_length=30)
department_id: str | None = Field(default=None, max_length=36)
department_name: str = Field(min_length=1, max_length=100)
cost_center: str | None = Field(default=None, max_length=50)
project_code: str | None = Field(default=None, max_length=50)
subject_code: str = Field(min_length=1, max_length=50)
subject_name: str = Field(min_length=1, max_length=100)
original_amount: Decimal = Field(ge=0)
warning_threshold: Decimal = Field(default=Decimal("80.00"), ge=0, le=100)
control_action: str = Field(default="block", max_length=30)
description: str | None = Field(default=None, max_length=500)
class BudgetCheckRequest(BaseModel):
fiscal_year: int | None = Field(default=None, ge=2000, le=2100)
period_key: str | None = Field(default=None, max_length=30)
department_id: str | None = Field(default=None, max_length=36)
department_name: str | None = Field(default=None, max_length=100)
cost_center: str | None = Field(default=None, max_length=50)
project_code: str | None = Field(default=None, max_length=50)
subject_code: str = Field(min_length=1, max_length=50)
amount: Decimal = Field(ge=0)
class BudgetOperationRequest(BudgetCheckRequest):
source_type: str = Field(min_length=1, max_length=40)
source_id: str = Field(min_length=1, max_length=64)
source_no: str = Field(min_length=1, max_length=80)
reason: str | None = Field(default=None, max_length=500)
class BudgetBalanceRead(BaseModel):
total_amount: Decimal
reserved_amount: Decimal
consumed_amount: Decimal
available_amount: Decimal
usage_rate: Decimal
class BudgetAllocationRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
budget_no: str
fiscal_year: int
period_type: str
period_key: str
department_id: str | None
department_name: str
cost_center: str | None
project_code: str | None
subject_code: str
subject_name: str
original_amount: Decimal
adjusted_amount: Decimal
status: str
warning_threshold: Decimal
control_action: str
description: str | None = None
balance: BudgetBalanceRead
created_at: datetime
updated_at: datetime
class BudgetTransactionRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
transaction_no: str
allocation_id: str
reservation_id: str | None
source_type: str
source_id: str
source_no: str
transaction_type: str
amount: Decimal
before_available_amount: Decimal
after_available_amount: Decimal
operator: str | None
reason: str | None
created_at: datetime
class BudgetSummaryRead(BaseModel):
fiscal_year: int | None = None
period_key: str | None = None
total_amount: Decimal
reserved_amount: Decimal
consumed_amount: Decimal
available_amount: Decimal
warning_count: int
over_budget_count: int
allocations: list[BudgetAllocationRead] = Field(default_factory=list)
class BudgetCheckRead(BaseModel):
passed: bool
blocking_reasons: list[str] = Field(default_factory=list)
flags: list[dict] = Field(default_factory=list)
allocation: BudgetAllocationRead | None = None
class BudgetOperationRead(BaseModel):
ok: bool
message: str
reservation_id: str | None = None
allocation: BudgetAllocationRead | None = None
transaction: BudgetTransactionRead | None = None
flags: list[dict] = Field(default_factory=list)

View File

@@ -23,7 +23,7 @@ class SettingsCompanyForm(BaseModel):
class SettingsAdminForm(BaseModel):
adminAccount: str = Field(min_length=1, max_length=120)
adminEmail: str = Field(min_length=1, max_length=255)
adminEmail: str = Field(default="", max_length=255)
newPassword: str = Field(default="", max_length=128)
confirmPassword: str = Field(default="", max_length=128)
sessionTimeout: int = Field(default=30, ge=5, le=240)