feat: 新增预算后端服务与差旅风险规则库
后端新增预算模型、端点和服务模块,支持预算 CRUD 和余额 查询,清理旧生成规则文件并替换为按严重等级分类的差旅风 险规则库,优化认证权限和报销单访问策略,新增财务规则目 录和演示数据构建脚本,前端预算中心增加对话框交互,完善 审计页面运行时模型和元数据展示,补充单元测试。
This commit is contained in:
@@ -3,6 +3,7 @@ from app.models.agent_asset import AgentAsset, AgentAssetReview, AgentAssetVersi
|
||||
from app.models.agent_run import AgentRun, AgentToolCall, SemanticParseLog
|
||||
from app.models.approval import ApprovalRecord
|
||||
from app.models.audit_log import AuditLog
|
||||
from app.models.budget import BudgetAllocation, BudgetReservation, BudgetTransaction
|
||||
from app.models.employee_change_log import EmployeeChangeLog
|
||||
from app.models.employee import Employee
|
||||
from app.models.financial_record import (
|
||||
@@ -32,6 +33,9 @@ __all__ = [
|
||||
"AgentToolCall",
|
||||
"ApprovalRecord",
|
||||
"AuditLog",
|
||||
"BudgetAllocation",
|
||||
"BudgetReservation",
|
||||
"BudgetTransaction",
|
||||
"Employee",
|
||||
"EmployeeChangeLog",
|
||||
"ExpenseClaim",
|
||||
|
||||
115
server/src/app/models/budget.py
Normal file
115
server/src/app/models/budget.py
Normal file
@@ -0,0 +1,115 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Index, Numeric, String, Text, UniqueConstraint, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.types import JSON
|
||||
|
||||
from app.db.base_class import Base
|
||||
|
||||
|
||||
class BudgetAllocation(Base):
|
||||
__tablename__ = "budget_allocations"
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"fiscal_year",
|
||||
"period_key",
|
||||
"department_id",
|
||||
"cost_center",
|
||||
"project_code",
|
||||
"subject_code",
|
||||
name="uq_budget_allocation_dimension",
|
||||
),
|
||||
Index("ix_budget_allocations_dimension", "fiscal_year", "period_key", "subject_code"),
|
||||
)
|
||||
|
||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
budget_no: Mapped[str] = mapped_column(String(50), unique=True, index=True)
|
||||
fiscal_year: Mapped[int] = mapped_column(index=True)
|
||||
period_type: Mapped[str] = mapped_column(String(20), default="quarter", index=True)
|
||||
period_key: Mapped[str] = mapped_column(String(30), index=True)
|
||||
department_id: Mapped[str | None] = mapped_column(
|
||||
ForeignKey("organization_units.id"), nullable=True, index=True
|
||||
)
|
||||
department_name: Mapped[str] = mapped_column(String(100), index=True)
|
||||
cost_center: Mapped[str | None] = mapped_column(String(50), nullable=True, index=True)
|
||||
project_code: Mapped[str | None] = mapped_column(String(50), nullable=True, index=True)
|
||||
subject_code: Mapped[str] = mapped_column(String(50), index=True)
|
||||
subject_name: Mapped[str] = mapped_column(String(100))
|
||||
original_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), default=Decimal("0.00"))
|
||||
adjusted_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), default=Decimal("0.00"))
|
||||
status: Mapped[str] = mapped_column(String(30), default="active", index=True)
|
||||
warning_threshold: Mapped[Decimal] = mapped_column(Numeric(5, 2), default=Decimal("80.00"))
|
||||
control_action: Mapped[str] = mapped_column(String(30), default="block")
|
||||
description: Mapped[str | None] = mapped_column(Text(), nullable=True)
|
||||
created_by: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
updated_by: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
|
||||
transactions = relationship("BudgetTransaction", back_populates="allocation")
|
||||
reservations = relationship("BudgetReservation", back_populates="allocation")
|
||||
|
||||
|
||||
class BudgetReservation(Base):
|
||||
__tablename__ = "budget_reservations"
|
||||
__table_args__ = (
|
||||
Index("ix_budget_reservations_source", "source_type", "source_id"),
|
||||
Index("ix_budget_reservations_status", "allocation_id", "source_status"),
|
||||
)
|
||||
|
||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
reservation_no: Mapped[str] = mapped_column(String(50), unique=True, index=True)
|
||||
allocation_id: Mapped[str] = mapped_column(ForeignKey("budget_allocations.id"), index=True)
|
||||
source_type: Mapped[str] = mapped_column(String(40), index=True)
|
||||
source_id: Mapped[str] = mapped_column(String(64), index=True)
|
||||
source_no: Mapped[str] = mapped_column(String(80), index=True)
|
||||
source_status: Mapped[str] = mapped_column(String(30), default="active", index=True)
|
||||
amount: Mapped[Decimal] = mapped_column(Numeric(14, 2))
|
||||
consumed_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), default=Decimal("0.00"))
|
||||
released_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), default=Decimal("0.00"))
|
||||
context_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
released_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
consumed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
|
||||
allocation = relationship("BudgetAllocation", back_populates="reservations")
|
||||
transactions = relationship("BudgetTransaction", back_populates="reservation")
|
||||
|
||||
|
||||
class BudgetTransaction(Base):
|
||||
__tablename__ = "budget_transactions"
|
||||
__table_args__ = (
|
||||
Index("ix_budget_transactions_allocation_created", "allocation_id", "created_at"),
|
||||
Index("ix_budget_transactions_source", "source_type", "source_id"),
|
||||
)
|
||||
|
||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
transaction_no: Mapped[str] = mapped_column(String(50), unique=True, index=True)
|
||||
allocation_id: Mapped[str] = mapped_column(ForeignKey("budget_allocations.id"), index=True)
|
||||
reservation_id: Mapped[str | None] = mapped_column(
|
||||
ForeignKey("budget_reservations.id"), nullable=True, index=True
|
||||
)
|
||||
source_type: Mapped[str] = mapped_column(String(40), index=True)
|
||||
source_id: Mapped[str] = mapped_column(String(64), index=True)
|
||||
source_no: Mapped[str] = mapped_column(String(80), index=True)
|
||||
transaction_type: Mapped[str] = mapped_column(String(30), index=True)
|
||||
amount: Mapped[Decimal] = mapped_column(Numeric(14, 2))
|
||||
before_available_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2))
|
||||
after_available_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2))
|
||||
operator: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
reason: Mapped[str | None] = mapped_column(Text(), nullable=True)
|
||||
context_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
allocation = relationship("BudgetAllocation", back_populates="transactions")
|
||||
reservation = relationship("BudgetReservation", back_populates="transactions")
|
||||
Reference in New Issue
Block a user