first commit
CI / typecheck (push) Successful in 1m8s
CI / format (push) Failing after 1m6s
CI / lint (push) Failing after 49s
CI / test (push) Failing after 1m8s

This commit is contained in:
SDI
2026-07-15 18:05:12 +09:00
commit 12e4f17b62
4633 changed files with 817125 additions and 0 deletions
@@ -0,0 +1,374 @@
"""initial support schema
Revision ID: 0001_initial_support_schema
Revises:
Create Date: 2026-07-08 00:00:00
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
revision = "0001_initial_support_schema"
down_revision = None
branch_labels = None
depends_on = None
support_status_codes = sa.table(
"support_status_codes",
sa.column("code", sa.String(length=20)),
sa.column("name", sa.String(length=50)),
sa.column("sort_order", sa.Integer()),
)
support_category_codes = sa.table(
"support_category_codes",
sa.column("code", sa.String(length=20)),
sa.column("name", sa.String(length=50)),
sa.column("sort_order", sa.Integer()),
)
service_types = sa.table(
"service_types",
sa.column("id", sa.BigInteger()),
sa.column("service_code", sa.String(length=50)),
sa.column("service_name", sa.String(length=100)),
sa.column("description", sa.Text()),
sa.column("is_active", sa.Boolean()),
)
workspaces = sa.table(
"workspaces",
sa.column("workspace_type", sa.String(length=20)),
sa.column("software_app_id", sa.BigInteger()),
sa.column("service_type_id", sa.BigInteger()),
sa.column("workspace_code", sa.String(length=50)),
sa.column("workspace_name", sa.String(length=100)),
sa.column("is_active", sa.Boolean()),
)
def upgrade() -> None:
op.create_table(
"software_apps",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("app_code", sa.String(length=50), nullable=False),
sa.Column("app_name", sa.String(length=100), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("app_code", name="uq_software_apps_app_code"),
sa.UniqueConstraint("app_name", name="uq_software_apps_app_name"),
mysql_engine="InnoDB",
)
op.create_table(
"service_types",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("service_code", sa.String(length=50), nullable=False),
sa.Column("service_name", sa.String(length=100), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("service_code", name="uq_service_types_service_code"),
sa.UniqueConstraint("service_name", name="uq_service_types_service_name"),
mysql_engine="InnoDB",
)
op.create_table(
"workspaces",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("workspace_type", sa.String(length=20), nullable=False),
sa.Column("software_app_id", sa.BigInteger(), nullable=True),
sa.Column("service_type_id", sa.BigInteger(), nullable=True),
sa.Column("workspace_code", sa.String(length=50), nullable=False),
sa.Column("workspace_name", sa.String(length=100), nullable=False),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.CheckConstraint(
"(workspace_type = 'SOFTWARE_APP' AND software_app_id IS NOT NULL AND service_type_id IS NULL) OR "
"(workspace_type = 'INTRANET_SERVICE' AND service_type_id IS NOT NULL AND software_app_id IS NULL)",
name="chk_workspaces_scope",
),
sa.ForeignKeyConstraint(["service_type_id"], ["service_types.id"], name="fk_workspaces_service_type"),
sa.ForeignKeyConstraint(["software_app_id"], ["software_apps.id"], name="fk_workspaces_software_app"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("workspace_code", name="uq_workspaces_workspace_code"),
mysql_engine="InnoDB",
)
op.create_table(
"user_workspace_access",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("user_id", sa.String(length=100), nullable=False),
sa.Column("tenant_id", sa.String(length=100), nullable=False),
sa.Column("workspace_id", sa.BigInteger(), nullable=False),
sa.Column("workspace_role", sa.String(length=30), nullable=False, server_default="USER"),
sa.Column("can_read", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("can_write", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("can_manage", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("can_approve", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("page_scope", sa.String(length=50), nullable=True),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.ForeignKeyConstraint(["workspace_id"], ["workspaces.id"], name="fk_user_workspace_access_workspace"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"user_id",
"tenant_id",
"workspace_id",
name="uq_user_workspace_access_user_tenant_workspace",
),
mysql_engine="InnoDB",
)
op.create_table(
"workspace_channel_mappings",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("workspace_id", sa.BigInteger(), nullable=False),
sa.Column("abc_channel_id", sa.String(length=100), nullable=False),
sa.Column("abc_channel_key", sa.String(length=100), nullable=True),
sa.Column("form_template_version", sa.String(length=30), nullable=True),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.ForeignKeyConstraint(["workspace_id"], ["workspaces.id"], name="fk_workspace_channel_mappings_workspace"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("workspace_id", "abc_channel_id", name="uq_workspace_channel_mappings_workspace_channel"),
mysql_engine="InnoDB",
)
op.create_table(
"workspace_field_mappings",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("workspace_id", sa.BigInteger(), nullable=False),
sa.Column("abc_channel_id", sa.String(length=100), nullable=False),
sa.Column("abc_field_key", sa.String(length=100), nullable=False),
sa.Column("local_field_code", sa.String(length=100), nullable=False),
sa.Column("field_label", sa.String(length=100), nullable=False),
sa.Column("field_type", sa.String(length=30), nullable=False),
sa.Column("is_required", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("sort_order", sa.Integer(), nullable=False, server_default="0"),
sa.Column("validation_rule", sa.JSON(), nullable=True),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.ForeignKeyConstraint(["workspace_id"], ["workspaces.id"], name="fk_workspace_field_mappings_workspace"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"workspace_id",
"abc_channel_id",
"abc_field_key",
name="uq_workspace_field_mappings_workspace_channel_field",
),
mysql_engine="InnoDB",
)
op.create_table(
"support_status_codes",
sa.Column("code", sa.String(length=20), nullable=False),
sa.Column("name", sa.String(length=50), nullable=False),
sa.Column("sort_order", sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint("code"),
mysql_engine="InnoDB",
)
op.create_table(
"support_category_codes",
sa.Column("code", sa.String(length=20), nullable=False),
sa.Column("name", sa.String(length=50), nullable=False),
sa.Column("sort_order", sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint("code"),
mysql_engine="InnoDB",
)
op.create_table(
"support_tickets",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("workspace_id", sa.BigInteger(), nullable=False),
sa.Column("requester_id", sa.String(length=100), nullable=False),
sa.Column("requester_tenant_id", sa.String(length=100), nullable=False),
sa.Column("ticket_type", sa.String(length=30), nullable=False),
sa.Column("source_system", sa.String(length=30), nullable=False, server_default="ABC"),
sa.Column("title", sa.String(length=255), nullable=False),
sa.Column("category_code", sa.String(length=20), nullable=True),
sa.Column("status_code", sa.String(length=20), nullable=False),
sa.Column("is_secret", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("requires_approval", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("current_assignee_id", sa.String(length=100), nullable=True),
sa.Column("current_assignee_tenant_id", sa.String(length=100), nullable=True),
sa.Column("requested_start_at", sa.TIMESTAMP(), nullable=True),
sa.Column("requested_end_at", sa.TIMESTAMP(), nullable=True),
sa.Column("priority", sa.String(length=20), nullable=False, server_default="NORMAL"),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.Column(
"updated_at",
sa.TIMESTAMP(),
nullable=False,
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
),
sa.ForeignKeyConstraint(["category_code"], ["support_category_codes.code"], name="fk_support_tickets_category_code"),
sa.ForeignKeyConstraint(["status_code"], ["support_status_codes.code"], name="fk_support_tickets_status_code"),
sa.ForeignKeyConstraint(["workspace_id"], ["workspaces.id"], name="fk_support_tickets_workspace"),
sa.PrimaryKeyConstraint("id"),
mysql_engine="InnoDB",
)
op.create_table(
"abc_feedback_mappings",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("ticket_id", sa.BigInteger(), nullable=False),
sa.Column("workspace_id", sa.BigInteger(), nullable=False),
sa.Column("abc_channel_id", sa.String(length=100), nullable=False),
sa.Column("abc_feedback_id", sa.String(length=100), nullable=False),
sa.Column("abc_feedback_url", sa.Text(), nullable=True),
sa.Column("sync_status", sa.String(length=20), nullable=False, server_default="SYNCED"),
sa.Column("last_synced_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.ForeignKeyConstraint(["ticket_id"], ["support_tickets.id"], name="fk_abc_feedback_mappings_ticket"),
sa.ForeignKeyConstraint(["workspace_id"], ["workspaces.id"], name="fk_abc_feedback_mappings_workspace"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("abc_feedback_id", name="uq_abc_feedback_mappings_feedback_id"),
sa.UniqueConstraint("ticket_id", name="uq_abc_feedback_mappings_ticket_id"),
mysql_engine="InnoDB",
)
op.create_table(
"request_approvals",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("ticket_id", sa.BigInteger(), nullable=False),
sa.Column("approver_id", sa.String(length=100), nullable=False),
sa.Column("approver_tenant_id", sa.String(length=100), nullable=False),
sa.Column("approval_status", sa.String(length=20), nullable=False),
sa.Column("comment", sa.Text(), nullable=True),
sa.Column("approved_at", sa.TIMESTAMP(), nullable=True),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.ForeignKeyConstraint(["ticket_id"], ["support_tickets.id"], name="fk_request_approvals_ticket"),
sa.PrimaryKeyConstraint("id"),
mysql_engine="InnoDB",
)
op.bulk_insert(
support_status_codes,
[
{"code": "RECEIVED", "name": "접수", "sort_order": 10},
{"code": "PENDING_APPROVAL", "name": "승인 대기", "sort_order": 20},
{"code": "APPROVED", "name": "승인 완료", "sort_order": 30},
{"code": "REJECTED", "name": "반려", "sort_order": 40},
{"code": "IN_PROGRESS", "name": "처리 중", "sort_order": 50},
{"code": "RESOLVED", "name": "처리 완료", "sort_order": 60},
{"code": "CLOSED", "name": "종료", "sort_order": 70},
],
)
op.bulk_insert(
support_category_codes,
[
{"code": "SUPPLIES", "name": "물품 신청", "sort_order": 10},
{"code": "BOOK", "name": "도서 신청", "sort_order": 20},
{"code": "VEHICLE", "name": "차량 신청", "sort_order": 30},
{"code": "EQUIPMENT", "name": "비품 대여", "sort_order": 40},
{"code": "GENERAL_QNA", "name": "일반 문의", "sort_order": 50},
],
)
op.bulk_insert(
service_types,
[
{
"id": 1,
"service_code": "SUPPLIES_REQUEST",
"service_name": "물품 신청",
"description": "인트라넷 물품 신청 서비스",
"is_active": True,
},
{
"id": 2,
"service_code": "BOOK_REQUEST",
"service_name": "도서 신청",
"description": "인트라넷 도서 신청 서비스",
"is_active": True,
},
{
"id": 3,
"service_code": "VEHICLE_REQUEST",
"service_name": "출장 차량 신청",
"description": "인트라넷 차량 배차 신청 서비스",
"is_active": True,
},
{
"id": 4,
"service_code": "EQUIPMENT_RENTAL",
"service_name": "비품 대여",
"description": "인트라넷 비품 대여 서비스",
"is_active": True,
},
{
"id": 5,
"service_code": "GENERAL_QNA",
"service_name": "사내 일반 문의",
"description": "인트라넷 일반 문의 서비스",
"is_active": True,
},
],
)
op.bulk_insert(
workspaces,
[
{
"workspace_type": "INTRANET_SERVICE",
"software_app_id": None,
"service_type_id": 1,
"workspace_code": "INTRA_SUPPLIES_REQUEST",
"workspace_name": "인트라넷 물품 신청",
"is_active": True,
},
{
"workspace_type": "INTRANET_SERVICE",
"software_app_id": None,
"service_type_id": 2,
"workspace_code": "INTRA_BOOK_REQUEST",
"workspace_name": "인트라넷 도서 신청",
"is_active": True,
},
{
"workspace_type": "INTRANET_SERVICE",
"software_app_id": None,
"service_type_id": 3,
"workspace_code": "INTRA_VEHICLE_REQUEST",
"workspace_name": "인트라넷 차량 신청",
"is_active": True,
},
{
"workspace_type": "INTRANET_SERVICE",
"software_app_id": None,
"service_type_id": 4,
"workspace_code": "INTRA_EQUIPMENT_RENTAL",
"workspace_name": "인트라넷 비품 대여",
"is_active": True,
},
{
"workspace_type": "INTRANET_SERVICE",
"software_app_id": None,
"service_type_id": 5,
"workspace_code": "INTRA_GENERAL_QNA",
"workspace_name": "인트라넷 일반 문의",
"is_active": True,
},
],
)
def downgrade() -> None:
op.drop_table("request_approvals")
op.drop_table("abc_feedback_mappings")
op.drop_table("support_tickets")
op.drop_table("support_category_codes")
op.drop_table("support_status_codes")
op.drop_table("workspace_field_mappings")
op.drop_table("workspace_channel_mappings")
op.drop_table("user_workspace_access")
op.drop_table("workspaces")
op.drop_table("service_types")
op.drop_table("software_apps")
@@ -0,0 +1,62 @@
"""add support ticket content fields
Revision ID: 0002_support_fields
Revises: 0001_initial_support_schema
Create Date: 2026-07-10 00:00:00
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
revision = "0002_support_fields"
down_revision = "0001_initial_support_schema"
branch_labels = None
depends_on = None
def upgrade() -> None:
bind = op.get_bind()
inspector = inspect(bind)
existing_columns = {column["name"] for column in inspector.get_columns("support_tickets")}
if "requester_contact" not in existing_columns:
op.add_column("support_tickets", sa.Column("requester_contact", sa.String(length=100), nullable=True))
if "description" not in existing_columns:
op.add_column("support_tickets", sa.Column("description", sa.Text(), nullable=True))
if "approval_status" not in existing_columns:
op.add_column(
"support_tickets",
sa.Column("approval_status", sa.String(length=20), nullable=False, server_default="NOT_REQUIRED"),
)
if "sync_status" not in existing_columns:
op.add_column(
"support_tickets",
sa.Column("sync_status", sa.String(length=20), nullable=False, server_default="PENDING"),
)
if "issue_link_status" not in existing_columns:
op.add_column(
"support_tickets",
sa.Column("issue_link_status", sa.String(length=20), nullable=False, server_default="NOT_REQUIRED"),
)
if "extra_fields" not in existing_columns:
op.add_column("support_tickets", sa.Column("extra_fields", sa.JSON(), nullable=True))
op.execute("UPDATE support_tickets SET description = '' WHERE description IS NULL")
op.alter_column("support_tickets", "description", existing_type=sa.Text(), nullable=False)
def downgrade() -> None:
op.drop_column("support_tickets", "extra_fields")
op.drop_column("support_tickets", "issue_link_status")
op.drop_column("support_tickets", "sync_status")
op.drop_column("support_tickets", "approval_status")
op.drop_column("support_tickets", "description")
op.drop_column("support_tickets", "requester_contact")
@@ -0,0 +1,204 @@
"""add comment, attachment, and approval workflow tables
Revision ID: 0003_support_comment_flow
Revises: 0002_support_fields
Create Date: 2026-07-13 00:00:00
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
revision = "0003_support_comment_flow"
down_revision = "0002_support_fields"
branch_labels = None
depends_on = None
def upgrade() -> None:
bind = op.get_bind()
inspector = inspect(bind)
existing_tables = set(inspector.get_table_names())
if "workspace_approval_policies" not in existing_tables:
op.create_table(
"workspace_approval_policies",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("workspace_id", sa.BigInteger(), nullable=False),
sa.Column("request_category_code", sa.String(length=50), nullable=True),
sa.Column("ticket_type", sa.String(length=30), nullable=True),
sa.Column("rule_name", sa.String(length=100), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("conditions", sa.JSON(), nullable=True),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("version", sa.Integer(), nullable=False, server_default="1"),
sa.Column("created_by", sa.String(length=100), nullable=True),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.Column(
"updated_at",
sa.TIMESTAMP(),
nullable=False,
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
),
sa.ForeignKeyConstraint(["workspace_id"], ["workspaces.id"], name="fk_workspace_approval_policies_workspace"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"workspace_id",
"rule_name",
"version",
name="uq_workspace_approval_policies_workspace_rule_version",
),
mysql_engine="InnoDB",
)
if "workspace_approval_steps" not in existing_tables:
op.create_table(
"workspace_approval_steps",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("policy_id", sa.BigInteger(), nullable=False),
sa.Column("step_order", sa.Integer(), nullable=False),
sa.Column("approver_type", sa.String(length=30), nullable=False),
sa.Column("approver_key", sa.String(length=100), nullable=False),
sa.Column("approver_name", sa.String(length=100), nullable=True),
sa.Column("approval_mode", sa.String(length=20), nullable=False, server_default="ALL"),
sa.Column("is_required", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("sla_hours", sa.Integer(), nullable=True),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.ForeignKeyConstraint(["policy_id"], ["workspace_approval_policies.id"], name="fk_workspace_approval_steps_policy"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("policy_id", "step_order", name="uq_workspace_approval_steps_policy_order"),
mysql_engine="InnoDB",
)
if "ticket_comments" not in existing_tables:
op.create_table(
"ticket_comments",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("ticket_id", sa.BigInteger(), nullable=False),
sa.Column("parent_comment_id", sa.BigInteger(), nullable=True),
sa.Column("author_id", sa.String(length=100), nullable=False),
sa.Column("author_tenant_id", sa.String(length=100), nullable=False),
sa.Column("author_name", sa.String(length=100), nullable=True),
sa.Column("comment_type", sa.String(length=20), nullable=False, server_default="COMMENT"),
sa.Column("content", sa.Text(), nullable=False),
sa.Column("is_internal", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("abc_comment_id", sa.String(length=100), nullable=True),
sa.Column("sync_status", sa.String(length=20), nullable=False, server_default="PENDING"),
sa.Column("edited_at", sa.TIMESTAMP(), nullable=True),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.Column(
"updated_at",
sa.TIMESTAMP(),
nullable=False,
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
),
sa.Column("deleted_at", sa.TIMESTAMP(), nullable=True),
sa.ForeignKeyConstraint(["ticket_id"], ["support_tickets.id"], name="fk_ticket_comments_ticket"),
sa.ForeignKeyConstraint(["parent_comment_id"], ["ticket_comments.id"], name="fk_ticket_comments_parent_comment"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("abc_comment_id", name="uq_ticket_comments_abc_comment_id"),
mysql_engine="InnoDB",
)
op.create_index("idx_ticket_comments_ticket_created", "ticket_comments", ["ticket_id", "created_at"])
if "attachments" not in existing_tables:
op.create_table(
"attachments",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("ticket_id", sa.BigInteger(), nullable=False),
sa.Column("comment_id", sa.BigInteger(), nullable=True),
sa.Column("workspace_id", sa.BigInteger(), nullable=False),
sa.Column("uploader_id", sa.String(length=100), nullable=False),
sa.Column("uploader_tenant_id", sa.String(length=100), nullable=False),
sa.Column("original_file_name", sa.String(length=255), nullable=False),
sa.Column("stored_file_name", sa.String(length=255), nullable=True),
sa.Column("storage_provider", sa.String(length=30), nullable=False, server_default="LOCAL"),
sa.Column("storage_bucket", sa.String(length=255), nullable=True),
sa.Column("storage_key", sa.String(length=500), nullable=False),
sa.Column("mime_type", sa.String(length=100), nullable=True),
sa.Column("file_extension", sa.String(length=20), nullable=True),
sa.Column("file_size", sa.BigInteger(), nullable=True),
sa.Column("checksum_sha256", sa.String(length=64), nullable=True),
sa.Column("abc_attachment_id", sa.String(length=100), nullable=True),
sa.Column("attachment_status", sa.String(length=20), nullable=False, server_default="ACTIVE"),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.Column(
"updated_at",
sa.TIMESTAMP(),
nullable=False,
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
),
sa.Column("deleted_at", sa.TIMESTAMP(), nullable=True),
sa.CheckConstraint(
"comment_id IS NULL OR ticket_id IS NOT NULL",
name="chk_attachments_comment_requires_ticket",
),
sa.ForeignKeyConstraint(["comment_id"], ["ticket_comments.id"], name="fk_attachments_comment"),
sa.ForeignKeyConstraint(["ticket_id"], ["support_tickets.id"], name="fk_attachments_ticket"),
sa.ForeignKeyConstraint(["workspace_id"], ["workspaces.id"], name="fk_attachments_workspace"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("storage_provider", "storage_key", name="uq_attachments_storage_provider_key"),
sa.UniqueConstraint("abc_attachment_id", name="uq_attachments_abc_attachment_id"),
mysql_engine="InnoDB",
)
op.create_index("idx_attachments_ticket_created", "attachments", ["ticket_id", "created_at"])
op.create_index("idx_attachments_comment_created", "attachments", ["comment_id", "created_at"])
approval_columns = {column["name"] for column in inspector.get_columns("request_approvals")}
if "workflow_policy_id" not in approval_columns:
op.add_column("request_approvals", sa.Column("workflow_policy_id", sa.BigInteger(), nullable=True))
op.create_foreign_key(
"fk_request_approvals_workflow_policy",
"request_approvals",
"workspace_approval_policies",
["workflow_policy_id"],
["id"],
)
if "workflow_step_id" not in approval_columns:
op.add_column("request_approvals", sa.Column("workflow_step_id", sa.BigInteger(), nullable=True))
op.create_foreign_key(
"fk_request_approvals_workflow_step",
"request_approvals",
"workspace_approval_steps",
["workflow_step_id"],
["id"],
)
if "step_order" not in approval_columns:
op.add_column("request_approvals", sa.Column("step_order", sa.Integer(), nullable=True))
def downgrade() -> None:
approval_columns = {column["name"] for column in inspect(op.get_bind()).get_columns("request_approvals")}
if "step_order" in approval_columns:
op.drop_column("request_approvals", "step_order")
if "workflow_step_id" in approval_columns:
op.drop_constraint("fk_request_approvals_workflow_step", "request_approvals", type_="foreignkey")
op.drop_column("request_approvals", "workflow_step_id")
if "workflow_policy_id" in approval_columns:
op.drop_constraint("fk_request_approvals_workflow_policy", "request_approvals", type_="foreignkey")
op.drop_column("request_approvals", "workflow_policy_id")
existing_tables = set(inspect(op.get_bind()).get_table_names())
if "attachments" in existing_tables:
op.drop_index("idx_attachments_comment_created", table_name="attachments")
op.drop_index("idx_attachments_ticket_created", table_name="attachments")
op.drop_table("attachments")
if "ticket_comments" in existing_tables:
op.drop_index("idx_ticket_comments_ticket_created", table_name="ticket_comments")
op.drop_table("ticket_comments")
if "workspace_approval_steps" in existing_tables:
op.drop_table("workspace_approval_steps")
if "workspace_approval_policies" in existing_tables:
op.drop_table("workspace_approval_policies")