Files
company-ai-platform/app/modules/business/registry.py
JiuContinent 19e59e83cc ```
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
```
2026-07-08 14:08:03 +08:00

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)]