```
feat: 添加生命周期报告和AI规则管理功能 - 在Dockerfile中添加pillow依赖包用于图像处理 - 实现生命周期报告调度任务,支持日报和周报两种类型 - 新增TASK_RUN_LIFECYCLE任务常量和相关配置选项 - 扩展AI Agent服务以支持用户规则,并在分析时应用规则 - 添加AI用户规则创建、更新和查询接口 - 增加项目生命周期和财务需求分析技能 - 扩展现有模型以支持更完整的业务数据字段 - 实现飞书图片上传功能用于报告展示 ```
This commit is contained in:
358
alembic/versions/202607120001_intasect_lifecycle_foundation.py
Normal file
358
alembic/versions/202607120001_intasect_lifecycle_foundation.py
Normal file
@@ -0,0 +1,358 @@
|
||||
"""Add Intasect project and personnel lifecycle foundation.
|
||||
|
||||
Revision ID: 202607120001
|
||||
Revises: 202607090001
|
||||
Create Date: 2026-07-12
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = "202607120001"
|
||||
down_revision = "202607090001"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _indexes(table_name: str, columns: list[tuple[str, bool]]) -> None:
|
||||
for column_name, unique in columns:
|
||||
op.create_index(
|
||||
op.f(f"ix_{table_name}_{column_name}"),
|
||||
table_name,
|
||||
[column_name],
|
||||
unique=unique,
|
||||
)
|
||||
|
||||
|
||||
def _add_columns(table_name: str, columns: list[sa.Column]) -> None:
|
||||
for column in columns:
|
||||
op.add_column(table_name, column)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"employees",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("code", sa.String(length=64), nullable=False),
|
||||
sa.Column("name", sa.String(length=128), nullable=False),
|
||||
sa.Column("department_code", sa.String(length=64), nullable=True),
|
||||
sa.Column("department_name", sa.String(length=128), nullable=True),
|
||||
sa.Column("title", sa.String(length=128), nullable=True),
|
||||
sa.Column("employment_status", sa.String(length=32), nullable=False),
|
||||
sa.Column("hired_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("ding_user_id", sa.String(length=64), nullable=True),
|
||||
sa.Column("source_system", sa.String(length=64), nullable=False),
|
||||
sa.Column("external_id", sa.String(length=128), nullable=False),
|
||||
sa.Column("source_created_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("source_updated_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("last_seen_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
_indexes(
|
||||
"employees",
|
||||
[
|
||||
("code", True),
|
||||
("name", False),
|
||||
("department_code", False),
|
||||
("department_name", False),
|
||||
("employment_status", False),
|
||||
("ding_user_id", False),
|
||||
("external_id", False),
|
||||
("source_updated_at", False),
|
||||
("last_seen_at", False),
|
||||
("is_active", False),
|
||||
],
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"project_members",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("code", sa.String(length=64), nullable=False),
|
||||
sa.Column("project_code", sa.String(length=64), nullable=False),
|
||||
sa.Column("employee_code", sa.String(length=64), nullable=False),
|
||||
sa.Column("role_name", sa.String(length=128), nullable=True),
|
||||
sa.Column("is_leader", sa.Boolean(), nullable=False),
|
||||
sa.Column("is_resident", sa.Boolean(), nullable=False),
|
||||
sa.Column("work_mode", sa.Text(), nullable=True),
|
||||
sa.Column("planned_days", sa.Integer(), nullable=True),
|
||||
sa.Column("workload_percent", sa.Integer(), nullable=True),
|
||||
sa.Column("source_system", sa.String(length=64), nullable=False),
|
||||
sa.Column("external_id", sa.String(length=128), nullable=False),
|
||||
sa.Column("source_created_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("last_seen_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
_indexes(
|
||||
"project_members",
|
||||
[
|
||||
("code", True),
|
||||
("project_code", False),
|
||||
("employee_code", False),
|
||||
("role_name", False),
|
||||
("is_leader", False),
|
||||
("external_id", False),
|
||||
("last_seen_at", False),
|
||||
("is_active", False),
|
||||
],
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"project_milestones",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("code", sa.String(length=128), nullable=False),
|
||||
sa.Column("project_code", sa.String(length=64), nullable=False),
|
||||
sa.Column("source_stage_id", sa.String(length=64), nullable=False),
|
||||
sa.Column("stage_name", sa.String(length=128), nullable=False),
|
||||
sa.Column("plan_start", sa.Date(), nullable=True),
|
||||
sa.Column("plan_end", sa.Date(), nullable=True),
|
||||
sa.Column("status", sa.String(length=32), nullable=False),
|
||||
sa.Column("is_overdue", sa.Boolean(), nullable=False),
|
||||
sa.Column("activity_total", sa.Integer(), nullable=False),
|
||||
sa.Column("activity_completed", sa.Integer(), nullable=False),
|
||||
sa.Column("source_system", sa.String(length=64), nullable=False),
|
||||
sa.Column("external_id", sa.String(length=128), nullable=False),
|
||||
sa.Column("source_updated_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("last_seen_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
_indexes(
|
||||
"project_milestones",
|
||||
[
|
||||
("code", True),
|
||||
("project_code", False),
|
||||
("source_stage_id", False),
|
||||
("stage_name", False),
|
||||
("plan_end", False),
|
||||
("status", False),
|
||||
("is_overdue", False),
|
||||
("external_id", False),
|
||||
("source_updated_at", False),
|
||||
("last_seen_at", False),
|
||||
("is_active", False),
|
||||
],
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"source_sync_cursors",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("dataset", sa.String(length=64), nullable=False),
|
||||
sa.Column("status", sa.String(length=32), nullable=False),
|
||||
sa.Column("watermark_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("watermark_key", sa.String(length=128), nullable=True),
|
||||
sa.Column("last_run_code", sa.String(length=64), nullable=True),
|
||||
sa.Column("last_success_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("processed_count", sa.Integer(), nullable=False),
|
||||
sa.Column("error_message", sa.Text(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
_indexes(
|
||||
"source_sync_cursors",
|
||||
[
|
||||
("dataset", True),
|
||||
("status", False),
|
||||
("last_run_code", False),
|
||||
("last_success_at", False),
|
||||
],
|
||||
)
|
||||
|
||||
_add_columns(
|
||||
"projects",
|
||||
[
|
||||
sa.Column("display_code", sa.String(length=64), nullable=True),
|
||||
sa.Column("department_code", sa.String(length=64), nullable=True),
|
||||
sa.Column("department_name", sa.String(length=128), nullable=True),
|
||||
sa.Column("owner_employee_code", sa.String(length=64), nullable=True),
|
||||
sa.Column("source_stage", sa.String(length=32), nullable=True),
|
||||
sa.Column("source_stage_label", sa.String(length=128), nullable=True),
|
||||
sa.Column("source_archived", sa.Boolean(), nullable=False, server_default=sa.false()),
|
||||
sa.Column("source_created_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("source_updated_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("last_seen_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
|
||||
],
|
||||
)
|
||||
_indexes(
|
||||
"projects",
|
||||
[
|
||||
("display_code", False),
|
||||
("department_code", False),
|
||||
("department_name", False),
|
||||
("owner_employee_code", False),
|
||||
("source_stage", False),
|
||||
("source_archived", False),
|
||||
("source_updated_at", False),
|
||||
("last_seen_at", False),
|
||||
("is_active", False),
|
||||
],
|
||||
)
|
||||
|
||||
_add_columns(
|
||||
"work_tasks",
|
||||
[
|
||||
sa.Column("employee_code", sa.String(length=64), nullable=True),
|
||||
sa.Column("source_created_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("source_updated_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("last_seen_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
|
||||
],
|
||||
)
|
||||
_indexes(
|
||||
"work_tasks",
|
||||
[
|
||||
("employee_code", False),
|
||||
("source_updated_at", False),
|
||||
("last_seen_at", False),
|
||||
("is_active", False),
|
||||
],
|
||||
)
|
||||
|
||||
_add_columns(
|
||||
"attendance_records",
|
||||
[
|
||||
sa.Column(
|
||||
"attendance_scope", sa.String(length=32), nullable=False, server_default="company"
|
||||
),
|
||||
sa.Column("source_status", sa.String(length=64), nullable=True),
|
||||
sa.Column("source_location_status", sa.String(length=64), nullable=True),
|
||||
sa.Column("source_updated_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("last_seen_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
|
||||
],
|
||||
)
|
||||
_indexes(
|
||||
"attendance_records",
|
||||
[
|
||||
("attendance_scope", False),
|
||||
("source_status", False),
|
||||
("source_updated_at", False),
|
||||
("last_seen_at", False),
|
||||
("is_active", False),
|
||||
],
|
||||
)
|
||||
|
||||
_add_columns(
|
||||
"work_reports",
|
||||
[
|
||||
sa.Column("employee_code", sa.String(length=64), nullable=True),
|
||||
sa.Column("external_id", sa.String(length=128), nullable=True),
|
||||
sa.Column("is_late", sa.Boolean(), nullable=False, server_default=sa.false()),
|
||||
sa.Column("is_draft", sa.Boolean(), nullable=False, server_default=sa.false()),
|
||||
sa.Column("source_updated_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("last_seen_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
|
||||
],
|
||||
)
|
||||
_indexes(
|
||||
"work_reports",
|
||||
[
|
||||
("employee_code", False),
|
||||
("external_id", False),
|
||||
("is_late", False),
|
||||
("is_draft", False),
|
||||
("source_updated_at", False),
|
||||
("last_seen_at", False),
|
||||
("is_active", False),
|
||||
],
|
||||
)
|
||||
|
||||
op.add_column(
|
||||
"report_push_runs", sa.Column("idempotency_key", sa.String(length=128), nullable=True)
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_report_push_runs_idempotency_key"),
|
||||
"report_push_runs",
|
||||
["idempotency_key"],
|
||||
unique=True,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f("ix_report_push_runs_idempotency_key"), table_name="report_push_runs")
|
||||
op.drop_column("report_push_runs", "idempotency_key")
|
||||
index_columns = {
|
||||
"work_reports": {
|
||||
"employee_code",
|
||||
"external_id",
|
||||
"is_late",
|
||||
"is_draft",
|
||||
"source_updated_at",
|
||||
"last_seen_at",
|
||||
"is_active",
|
||||
},
|
||||
"attendance_records": {
|
||||
"attendance_scope",
|
||||
"source_status",
|
||||
"source_updated_at",
|
||||
"last_seen_at",
|
||||
"is_active",
|
||||
},
|
||||
"work_tasks": {"employee_code", "source_updated_at", "last_seen_at", "is_active"},
|
||||
"projects": {
|
||||
"display_code",
|
||||
"department_code",
|
||||
"department_name",
|
||||
"owner_employee_code",
|
||||
"source_stage",
|
||||
"source_archived",
|
||||
"source_updated_at",
|
||||
"last_seen_at",
|
||||
"is_active",
|
||||
},
|
||||
}
|
||||
for table_name, columns in {
|
||||
"work_reports": [
|
||||
"employee_code",
|
||||
"external_id",
|
||||
"is_late",
|
||||
"is_draft",
|
||||
"source_updated_at",
|
||||
"last_seen_at",
|
||||
"is_active",
|
||||
],
|
||||
"attendance_records": [
|
||||
"attendance_scope",
|
||||
"source_status",
|
||||
"source_location_status",
|
||||
"source_updated_at",
|
||||
"last_seen_at",
|
||||
"is_active",
|
||||
],
|
||||
"work_tasks": [
|
||||
"employee_code",
|
||||
"source_created_at",
|
||||
"source_updated_at",
|
||||
"last_seen_at",
|
||||
"is_active",
|
||||
],
|
||||
"projects": [
|
||||
"display_code",
|
||||
"department_code",
|
||||
"department_name",
|
||||
"owner_employee_code",
|
||||
"source_stage",
|
||||
"source_stage_label",
|
||||
"source_archived",
|
||||
"source_created_at",
|
||||
"source_updated_at",
|
||||
"last_seen_at",
|
||||
"is_active",
|
||||
],
|
||||
}.items():
|
||||
for column_name in reversed(columns):
|
||||
if column_name in index_columns[table_name]:
|
||||
op.drop_index(op.f(f"ix_{table_name}_{column_name}"), table_name=table_name)
|
||||
op.drop_column(table_name, column_name)
|
||||
for table_name in ["source_sync_cursors", "project_milestones", "project_members", "employees"]:
|
||||
op.drop_table(table_name)
|
||||
118
alembic/versions/202607120002_project_finance_needs.py
Normal file
118
alembic/versions/202607120002_project_finance_needs.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""Add project contracts and normalized cash flows.
|
||||
|
||||
Revision ID: 202607120002
|
||||
Revises: 202607120001
|
||||
Create Date: 2026-07-12
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = "202607120002"
|
||||
down_revision = "202607120001"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _index(table: str, column: str, unique: bool = False) -> None:
|
||||
op.create_index(op.f(f"ix_{table}_{column}"), table, [column], unique=unique)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"projects", sa.Column("source_contract_amount", sa.Numeric(16, 2), nullable=True)
|
||||
)
|
||||
op.add_column(
|
||||
"projects",
|
||||
sa.Column("source_project_investment_amount", sa.Numeric(16, 2), nullable=True),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"project_contracts",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("code", sa.String(64), nullable=False),
|
||||
sa.Column("project_code", sa.String(64), nullable=True),
|
||||
sa.Column("contract_type_code", sa.String(32), nullable=True),
|
||||
sa.Column("contract_type_label", sa.String(128), nullable=True),
|
||||
sa.Column("amount", sa.Numeric(16, 2), nullable=False),
|
||||
sa.Column("signed_date", sa.Date(), nullable=True),
|
||||
sa.Column("start_date", sa.Date(), nullable=True),
|
||||
sa.Column("end_date", sa.Date(), nullable=True),
|
||||
sa.Column("invoice_type_code", sa.String(32), nullable=True),
|
||||
sa.Column("data_quality_status", sa.String(32), nullable=False),
|
||||
sa.Column("source_system", sa.String(64), nullable=False),
|
||||
sa.Column("external_id", sa.String(128), nullable=False),
|
||||
sa.Column("source_created_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("last_seen_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
for column, unique in (
|
||||
("code", True),
|
||||
("project_code", False),
|
||||
("contract_type_code", False),
|
||||
("signed_date", False),
|
||||
("data_quality_status", False),
|
||||
("external_id", False),
|
||||
("last_seen_at", False),
|
||||
("is_active", False),
|
||||
):
|
||||
_index("project_contracts", column, unique)
|
||||
|
||||
op.create_table(
|
||||
"project_cash_flows",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("code", sa.String(64), nullable=False),
|
||||
sa.Column("project_code", sa.String(64), nullable=True),
|
||||
sa.Column("contract_code", sa.String(64), nullable=True),
|
||||
sa.Column("flow_type", sa.String(32), nullable=False),
|
||||
sa.Column("direction", sa.String(16), nullable=False),
|
||||
sa.Column("category_code", sa.String(32), nullable=True),
|
||||
sa.Column("category_label", sa.String(128), nullable=True),
|
||||
sa.Column("planned_amount", sa.Numeric(16, 2), nullable=False),
|
||||
sa.Column("actual_amount", sa.Numeric(16, 2), nullable=True),
|
||||
sa.Column("planned_date", sa.Date(), nullable=True),
|
||||
sa.Column("actual_date", sa.Date(), nullable=True),
|
||||
sa.Column("payment_status", sa.String(32), nullable=True),
|
||||
sa.Column("invoice_status", sa.String(32), nullable=True),
|
||||
sa.Column("approval_status", sa.String(32), nullable=True),
|
||||
sa.Column("confirmation_status", sa.String(32), nullable=True),
|
||||
sa.Column("data_quality_status", sa.String(32), nullable=False),
|
||||
sa.Column("source_system", sa.String(64), nullable=False),
|
||||
sa.Column("external_id", sa.String(128), nullable=False),
|
||||
sa.Column("source_created_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("source_updated_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("last_seen_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
for column, unique in (
|
||||
("code", True),
|
||||
("project_code", False),
|
||||
("contract_code", False),
|
||||
("flow_type", False),
|
||||
("direction", False),
|
||||
("category_code", False),
|
||||
("planned_date", False),
|
||||
("actual_date", False),
|
||||
("payment_status", False),
|
||||
("approval_status", False),
|
||||
("data_quality_status", False),
|
||||
("external_id", False),
|
||||
("source_updated_at", False),
|
||||
("last_seen_at", False),
|
||||
("is_active", False),
|
||||
):
|
||||
_index("project_cash_flows", column, unique)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("project_cash_flows")
|
||||
op.drop_table("project_contracts")
|
||||
op.drop_column("projects", "source_project_investment_amount")
|
||||
op.drop_column("projects", "source_contract_amount")
|
||||
Reference in New Issue
Block a user