feat: 添加数据库迁移脚本并更新Dockerfile配置 - 在Dockerfile中添加alembic配置文件和目录的复制指令 - 更新alembic/env.py注册新的模块模型:events、workflows、writebacks - 生成完整的初始数据库schema迁移脚本,包含以下表: - approval_requests, attendance_records, audit_logs, domain_events - expenses, feishu_event_receipts, fund_accounts, legacy_sync_runs - official_writeback_runs, performance_metrics, policies, procurements - projects, report_push_runs, risk_event_actions, risk_events - standards, suppliers, work_reports, work_tasks, workflow_actions - workflow_instances等21个数据表结构定义 - 在API路由器中添加新模块的路由:events、workflows、writebacks、observability ```
241 lines
6.1 KiB
Python
241 lines
6.1 KiB
Python
from sqlalchemy.orm import DeclarativeMeta
|
|
|
|
from app.modules.business import models
|
|
from app.modules.business.constants import BusinessDomain
|
|
|
|
|
|
DOMAIN_MODELS: dict[BusinessDomain, type[DeclarativeMeta]] = {
|
|
BusinessDomain.PROJECTS: models.Project,
|
|
BusinessDomain.TASKS: models.WorkTask,
|
|
BusinessDomain.PROCUREMENTS: models.Procurement,
|
|
BusinessDomain.EXPENSES: models.Expense,
|
|
BusinessDomain.FUND_ACCOUNTS: models.FundAccount,
|
|
BusinessDomain.POLICIES: models.Policy,
|
|
BusinessDomain.STANDARDS: models.Standard,
|
|
BusinessDomain.PERFORMANCE_METRICS: models.PerformanceMetric,
|
|
BusinessDomain.SUPPLIERS: models.Supplier,
|
|
BusinessDomain.ATTENDANCE_RECORDS: models.AttendanceRecord,
|
|
BusinessDomain.WORK_REPORTS: models.WorkReport,
|
|
BusinessDomain.RISK_EVENTS: models.RiskEvent,
|
|
BusinessDomain.LEGACY_SYNC_RUNS: models.LegacySyncRun,
|
|
}
|
|
|
|
DOMAIN_WRITABLE_FIELDS: dict[BusinessDomain, frozenset[str]] = {
|
|
BusinessDomain.PROJECTS: frozenset(
|
|
{
|
|
"code",
|
|
"name",
|
|
"owner",
|
|
"status",
|
|
"priority",
|
|
"progress_percent",
|
|
"risk_level",
|
|
"budget_amount",
|
|
"actual_amount",
|
|
"start_date",
|
|
"due_date",
|
|
"description",
|
|
}
|
|
),
|
|
BusinessDomain.TASKS: frozenset(
|
|
{
|
|
"code",
|
|
"title",
|
|
"project_code",
|
|
"owner",
|
|
"status",
|
|
"priority",
|
|
"due_date",
|
|
"completed_at",
|
|
"blocker",
|
|
"description",
|
|
}
|
|
),
|
|
BusinessDomain.PROCUREMENTS: frozenset(
|
|
{
|
|
"code",
|
|
"name",
|
|
"applicant",
|
|
"project_code",
|
|
"supplier_name",
|
|
"budget_subject",
|
|
"expected_amount",
|
|
"actual_amount",
|
|
"approval_status",
|
|
"delivery_status",
|
|
"payment_status",
|
|
"comparison_summary",
|
|
}
|
|
),
|
|
BusinessDomain.EXPENSES: frozenset(
|
|
{
|
|
"code",
|
|
"expense_type",
|
|
"amount",
|
|
"applicant",
|
|
"department",
|
|
"project_code",
|
|
"budget_subject",
|
|
"payment_account",
|
|
"invoice_status",
|
|
"approval_status",
|
|
"payment_status",
|
|
}
|
|
),
|
|
BusinessDomain.FUND_ACCOUNTS: frozenset(
|
|
{
|
|
"code",
|
|
"name",
|
|
"account_type",
|
|
"current_balance",
|
|
"expected_receivable",
|
|
"expected_payable",
|
|
"safety_line",
|
|
"risk_level",
|
|
"note",
|
|
}
|
|
),
|
|
BusinessDomain.POLICIES: frozenset(
|
|
{
|
|
"code",
|
|
"title",
|
|
"policy_type",
|
|
"owner_department",
|
|
"version",
|
|
"status",
|
|
"effective_date",
|
|
"feishu_doc_url",
|
|
"summary",
|
|
}
|
|
),
|
|
BusinessDomain.STANDARDS: frozenset(
|
|
{
|
|
"code",
|
|
"title",
|
|
"standard_type",
|
|
"applies_to",
|
|
"status",
|
|
"check_items",
|
|
"remediation",
|
|
"policy_code",
|
|
}
|
|
),
|
|
BusinessDomain.PERFORMANCE_METRICS: frozenset(
|
|
{
|
|
"code",
|
|
"name",
|
|
"applies_to_role",
|
|
"formula",
|
|
"weight",
|
|
"data_source",
|
|
"auto_score",
|
|
"confirmed_score",
|
|
"status",
|
|
}
|
|
),
|
|
BusinessDomain.SUPPLIERS: frozenset(
|
|
{
|
|
"code",
|
|
"name",
|
|
"category",
|
|
"contact",
|
|
"quality_score",
|
|
"delivery_score",
|
|
"price_score",
|
|
"risk_level",
|
|
"blacklist_status",
|
|
}
|
|
),
|
|
BusinessDomain.ATTENDANCE_RECORDS: frozenset(
|
|
{
|
|
"code",
|
|
"employee_name",
|
|
"employee_id",
|
|
"department",
|
|
"project_code",
|
|
"work_date",
|
|
"check_in_at",
|
|
"check_out_at",
|
|
"status",
|
|
"location",
|
|
"note",
|
|
}
|
|
),
|
|
BusinessDomain.WORK_REPORTS: frozenset(
|
|
{
|
|
"code",
|
|
"report_type",
|
|
"title",
|
|
"reporter",
|
|
"department",
|
|
"project_code",
|
|
"period_start",
|
|
"period_end",
|
|
"content",
|
|
"metrics",
|
|
"risk_summary",
|
|
"status",
|
|
}
|
|
),
|
|
BusinessDomain.RISK_EVENTS: frozenset(
|
|
{
|
|
"code",
|
|
"title",
|
|
"risk_type",
|
|
"risk_level",
|
|
"status",
|
|
"source_domain",
|
|
"source_record_id",
|
|
"project_code",
|
|
"owner",
|
|
"due_date",
|
|
"assigned_to",
|
|
"closed_reason",
|
|
"review_summary",
|
|
"description",
|
|
"mitigation",
|
|
"evidence",
|
|
}
|
|
),
|
|
BusinessDomain.LEGACY_SYNC_RUNS: frozenset(
|
|
{
|
|
"code",
|
|
"domain",
|
|
"source_table",
|
|
"status",
|
|
"created_count",
|
|
"updated_count",
|
|
"skipped_count",
|
|
"error_message",
|
|
"note",
|
|
}
|
|
),
|
|
}
|
|
|
|
HIGH_RISK_DOMAINS = frozenset(DOMAIN_MODELS)
|
|
LOW_RISK_DOMAINS = frozenset(set(DOMAIN_MODELS) - HIGH_RISK_DOMAINS)
|
|
|
|
|
|
def normalize_domain(domain: str | BusinessDomain) -> BusinessDomain:
|
|
try:
|
|
return BusinessDomain(domain)
|
|
except ValueError as exc:
|
|
supported = ", ".join(sorted(item.value for item in DOMAIN_MODELS))
|
|
raise KeyError(f"Unsupported domain '{domain}'. Supported: {supported}") from exc
|
|
|
|
|
|
def supported_domain_values() -> list[str]:
|
|
return sorted(item.value for item in DOMAIN_MODELS)
|
|
|
|
|
|
def is_high_risk_domain(domain: str | BusinessDomain) -> bool:
|
|
return normalize_domain(domain) in HIGH_RISK_DOMAINS
|
|
|
|
|
|
def get_domain_model(domain: str | BusinessDomain) -> type[DeclarativeMeta]:
|
|
return DOMAIN_MODELS[normalize_domain(domain)]
|
|
|
|
|
|
def get_writable_fields(domain: str | BusinessDomain) -> frozenset[str]:
|
|
return DOMAIN_WRITABLE_FIELDS[normalize_domain(domain)]
|