Initial deployment setup
Deploy staging / deploy (push) Failing after 6s

This commit is contained in:
root
2026-08-31 16:45:24 +09:00
commit 33453ecc55
3475 changed files with 850363 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")
@@ -0,0 +1,52 @@
"""seed role_access software workspaces
Revision ID: 0004_role_access_workspaces
Revises: 0003_support_comment_flow
"""
from alembic import op
import sqlalchemy as sa
revision = "0004_role_access_workspaces"
down_revision = "0003_support_comment_flow"
branch_labels = None
depends_on = None
def upgrade() -> None:
software_apps = sa.table(
"software_apps",
sa.column("app_code", sa.String(50)),
sa.column("app_name", sa.String(100)),
sa.column("description", sa.Text()),
sa.column("is_active", sa.Boolean()),
)
op.execute(sa.text("""
INSERT IGNORE INTO software_apps
(app_code, app_name, description, is_active)
VALUES
('EGBIM', 'EGBIM', 'EGBIM Q&A', 1),
('TOVA', 'TOVA', 'TOVA Q&A', 1),
('GAIA', 'GAIA', 'GAIA Q&A', 1),
('KNGIL', 'KNGIL', 'KNGIL Q&A', 1),
('INTRANET_QNA', 'INTRANET_QNA', '인트라넷 공통 Q&A', 1)
"""))
op.execute(sa.text("""
INSERT IGNORE INTO workspaces
(workspace_type, software_app_id, service_type_id, workspace_code, workspace_name, is_active)
SELECT 'SOFTWARE_APP', id, NULL, app_code, app_name, 1
FROM software_apps
WHERE app_code IN ('EGBIM', 'TOVA', 'GAIA', 'KNGIL', 'INTRANET_QNA')
"""))
def downgrade() -> None:
op.execute(sa.text("""
DELETE FROM workspaces
WHERE workspace_code IN ('EGBIM', 'TOVA', 'GAIA', 'KNGIL', 'INTRANET_QNA')
"""))
op.execute(sa.text("""
DELETE FROM software_apps
WHERE app_code IN ('EGBIM', 'TOVA', 'GAIA', 'KNGIL', 'INTRANET_QNA')
"""))
@@ -0,0 +1,53 @@
"""fix test workspace display name
Revision ID: 0005_qna_workspace_code
Revises: 0004_role_access_workspaces
"""
from alembic import op
import sqlalchemy as sa
revision = "0005_qna_workspace_code"
down_revision = "0004_role_access_workspaces"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.execute(
sa.text(
"UPDATE software_apps SET app_code = :new_code, app_name = :name, description = :description "
"WHERE app_code = :old_code"
).bindparams(
new_code="Q&A_Platform",
name="Q&A_Platform",
description="Q&A_Platform feedback",
old_code="INTRANET_QNA",
)
)
op.execute(
sa.text(
"UPDATE workspaces SET workspace_code = :new_code, workspace_name = :name "
"WHERE workspace_code = :old_code"
).bindparams(new_code="Q&A_Platform", name="Q&A_Platform", old_code="INTRANET_QNA")
)
def downgrade() -> None:
op.execute(
sa.text(
"UPDATE software_apps SET app_code = :new_code, app_name = :name, description = :description "
"WHERE app_code = :old_code"
).bindparams(
new_code="INTRANET_QNA",
name="INTRANET_QNA",
description="인트라넷 공통 Q&A",
old_code="Q&A_Platform",
)
)
op.execute(
sa.text(
"UPDATE workspaces SET workspace_code = :new_code, workspace_name = :name "
"WHERE workspace_code = :old_code"
).bindparams(new_code="INTRANET_QNA", name="INTRANET_QNA", old_code="Q&A_Platform")
)
@@ -0,0 +1,123 @@
"""add internal support identities and role assignments
Revision ID: 0006_internal_identity_roles
Revises: 0005_qna_workspace_code
"""
from alembic import op
import sqlalchemy as sa
revision = "0006_internal_identity_roles"
down_revision = "0005_qna_workspace_code"
branch_labels = None
depends_on = None
ROLE_SEED = (
("SYSTEM_ADMIN", "시스템 관리자", "전체 workspace와 운영 설정에 접근"),
("SUPER_ADMIN", "전체 관리자", "모든 workspace의 운영 권한"),
("PROJECT_MANAGER", "프로젝트 관리자", "배정된 workspace의 운영·댓글·승인 권한"),
("END_USER", "일반 사용자", "피드백 작성과 본인 데이터 조회"),
("FEEDBACK_PROVIDER", "피드백 제공자", "피드백 작성과 배정된 workspace 조회"),
)
def upgrade() -> None:
op.create_table(
"support_users",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("sso_subject", sa.String(length=100), nullable=False),
sa.Column("tenant_id", sa.String(length=100), nullable=False),
sa.Column("email", sa.String(length=320), nullable=True),
sa.Column("name", sa.String(length=100), nullable=True),
sa.Column("department", sa.String(length=100), nullable=True),
sa.Column("status", sa.String(length=20), server_default=sa.text("'ACTIVE'"), nullable=False),
sa.Column("created_at", sa.TIMESTAMP(), server_default=sa.text("CURRENT_TIMESTAMP"), nullable=False),
sa.Column(
"updated_at",
sa.TIMESTAMP(),
server_default=sa.text("CURRENT_TIMESTAMP"),
server_onupdate=sa.text("CURRENT_TIMESTAMP"),
nullable=False,
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("sso_subject", "tenant_id", name="uq_support_users_subject_tenant"),
mysql_engine="InnoDB",
mysql_charset="utf8mb4",
)
op.create_table(
"support_roles",
sa.Column("code", sa.String(length=40), nullable=False),
sa.Column("name", sa.String(length=100), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("created_at", sa.TIMESTAMP(), server_default=sa.text("CURRENT_TIMESTAMP"), nullable=False),
sa.PrimaryKeyConstraint("code"),
mysql_engine="InnoDB",
mysql_charset="utf8mb4",
)
op.create_table(
"support_role_assignments",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("support_user_id", sa.BigInteger(), nullable=False),
sa.Column("role_code", sa.String(length=40), nullable=False),
sa.Column("workspace_id", sa.BigInteger(), nullable=True),
sa.Column("created_at", sa.TIMESTAMP(), server_default=sa.text("CURRENT_TIMESTAMP"), nullable=False),
sa.ForeignKeyConstraint(["role_code"], ["support_roles.code"], name="fk_support_role_assignments_role"),
sa.ForeignKeyConstraint(["support_user_id"], ["support_users.id"], name="fk_support_role_assignments_user"),
sa.ForeignKeyConstraint(["workspace_id"], ["workspaces.id"], name="fk_support_role_assignments_workspace"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"support_user_id",
"role_code",
"workspace_id",
name="uq_support_role_assignment_user_role_workspace",
),
mysql_engine="InnoDB",
mysql_charset="utf8mb4",
)
roles = sa.table(
"support_roles",
sa.column("code", sa.String(40)),
sa.column("name", sa.String(100)),
sa.column("description", sa.Text()),
)
op.bulk_insert(
roles,
[
{"code": code, "name": name, "description": description}
for code, name, description in ROLE_SEED
],
)
# Preserve existing access rows while moving the authority to the internal
# identity/role tables. Existing rows are legacy effective permissions.
op.execute(sa.text("""
INSERT IGNORE INTO support_users
(sso_subject, tenant_id, status)
SELECT DISTINCT user_id, tenant_id, 'ACTIVE'
FROM user_workspace_access
"""))
op.execute(sa.text("""
INSERT IGNORE INTO support_role_assignments
(support_user_id, role_code, workspace_id)
SELECT su.id,
CASE
WHEN uwa.workspace_role IN (
'SYSTEM_ADMIN', 'SUPER_ADMIN', 'PROJECT_MANAGER',
'END_USER', 'FEEDBACK_PROVIDER'
) THEN uwa.workspace_role
ELSE 'END_USER'
END,
uwa.workspace_id
FROM user_workspace_access uwa
JOIN support_users su
ON su.sso_subject = uwa.user_id
AND su.tenant_id = uwa.tenant_id
"""))
def downgrade() -> None:
op.drop_table("support_role_assignments")
op.drop_table("support_roles")
op.drop_table("support_users")
@@ -0,0 +1,21 @@
"""store BARON-SSO phone numbers for support identities"""
from alembic import op
import sqlalchemy as sa
revision = "0007_support_user_profile_phone"
down_revision = "0006_internal_identity_roles"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column(
"support_users",
sa.Column("phone_number", sa.String(length=50), nullable=True),
)
def downgrade() -> None:
op.drop_column("support_users", "phone_number")
@@ -0,0 +1,24 @@
"""store requester profile snapshot on support tickets"""
from alembic import op
import sqlalchemy as sa
revision = "0008_ticket_requester_profile"
down_revision = "0007_support_user_profile_phone"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column("support_tickets", sa.Column("requester_email", sa.String(length=320), nullable=True))
op.add_column("support_tickets", sa.Column("requester_name", sa.String(length=100), nullable=True))
op.add_column("support_tickets", sa.Column("requester_department", sa.String(length=100), nullable=True))
op.add_column("support_tickets", sa.Column("requester_phone_number", sa.String(length=50), nullable=True))
def downgrade() -> None:
op.drop_column("support_tickets", "requester_phone_number")
op.drop_column("support_tickets", "requester_department")
op.drop_column("support_tickets", "requester_name")
op.drop_column("support_tickets", "requester_email")
@@ -0,0 +1,28 @@
"""add the ticket secret flag for existing support databases"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
revision = "0009_ticket_secret_flag"
down_revision = "0008_ticket_requester_profile"
branch_labels = None
depends_on = None
def upgrade() -> None:
bind = op.get_bind()
columns = {column["name"] for column in inspect(bind).get_columns("support_tickets")}
if "is_secret" not in columns:
op.add_column(
"support_tickets",
sa.Column("is_secret", sa.Boolean(), nullable=False, server_default=sa.false()),
)
def downgrade() -> None:
bind = op.get_bind()
columns = {column["name"] for column in inspect(bind).get_columns("support_tickets")}
if "is_secret" in columns:
op.drop_column("support_tickets", "is_secret")
@@ -0,0 +1,93 @@
"""add migration batch and source mapping tables
Revision ID: 0010_egbim_migration_tracking
Revises: 0009_ticket_secret_flag
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
revision = "0010_egbim_migration_tracking"
down_revision = "0009_ticket_secret_flag"
branch_labels = None
depends_on = None
def upgrade() -> None:
bind = op.get_bind()
existing_tables = set(inspect(bind).get_table_names())
if "migration_batches" not in existing_tables:
op.create_table(
"migration_batches",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("batch_name", sa.String(length=100), nullable=False),
sa.Column("source_system", sa.String(length=50), nullable=False),
sa.Column("started_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.Column("completed_at", sa.TIMESTAMP(), nullable=True),
sa.Column("status", sa.String(length=20), nullable=False, server_default=sa.text("'RUNNING'")),
sa.Column("executed_by", sa.String(length=100), nullable=True),
sa.Column("notes", sa.Text(), nullable=True),
sa.PrimaryKeyConstraint("id"),
mysql_engine="InnoDB",
)
if "migration_mappings" not in existing_tables:
op.create_table(
"migration_mappings",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("batch_id", sa.BigInteger(), nullable=False),
sa.Column("source_system", sa.String(length=50), nullable=False),
sa.Column("source_entity_type", sa.String(length=30), nullable=False),
sa.Column("source_entity_id", sa.String(length=100), nullable=False),
sa.Column("source_parent_id", sa.String(length=100), nullable=True),
sa.Column("workspace_id", sa.BigInteger(), nullable=False),
sa.Column("ticket_id", sa.BigInteger(), nullable=True),
sa.Column("abc_feedback_id", sa.String(length=100), nullable=True),
sa.Column("migration_status", sa.String(length=20), nullable=False),
sa.Column("error_message", sa.Text(), nullable=True),
sa.Column("created_at", sa.TIMESTAMP(), nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
sa.ForeignKeyConstraint(
["batch_id"], ["migration_batches.id"], name="fk_migration_mappings_batch"
),
sa.ForeignKeyConstraint(
["workspace_id"], ["workspaces.id"], name="fk_migration_mappings_workspace"
),
sa.ForeignKeyConstraint(
["ticket_id"], ["support_tickets.id"], name="fk_migration_mappings_ticket"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"source_system",
"source_entity_type",
"source_entity_id",
name="uq_migration_mappings_source_entity",
),
mysql_engine="InnoDB",
)
op.create_index(
"idx_migration_mappings_batch_status",
"migration_mappings",
["batch_id", "migration_status"],
)
op.create_index(
"idx_migration_mappings_ticket",
"migration_mappings",
["ticket_id"],
)
def downgrade() -> None:
bind = op.get_bind()
existing_tables = set(inspect(bind).get_table_names())
if "migration_mappings" in existing_tables:
op.drop_index("idx_migration_mappings_ticket", table_name="migration_mappings")
op.drop_index("idx_migration_mappings_batch_status", table_name="migration_mappings")
op.drop_table("migration_mappings")
if "migration_batches" in existing_tables:
op.drop_table("migration_batches")
@@ -0,0 +1,54 @@
"""add review statuses required by the EGBIM migration
Revision ID: 0011_egbim_review_statuses
Revises: 0010_egbim_migration_tracking
"""
from __future__ import annotations
from alembic import op
revision = "0011_egbim_review_statuses"
down_revision = "0010_egbim_migration_tracking"
branch_labels = None
depends_on = None
def upgrade() -> None:
# Keep this migration idempotent because some staging databases may have
# received the status rows through an earlier manual migration.
op.execute(
"""
INSERT INTO support_status_codes (code, name, sort_order)
SELECT 'IN_REVIEW', '검토중', 45
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM support_status_codes WHERE code = 'IN_REVIEW'
)
"""
)
op.execute(
"""
INSERT INTO support_status_codes (code, name, sort_order)
SELECT 'DETAILED_REVIEW', '정밀검토중', 48
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM support_status_codes WHERE code = 'DETAILED_REVIEW'
)
"""
)
def downgrade() -> None:
# Do not remove a status that is already referenced by migrated tickets.
op.execute(
"""
DELETE FROM support_status_codes
WHERE code IN ('IN_REVIEW', 'DETAILED_REVIEW')
AND NOT EXISTS (
SELECT 1 FROM support_tickets
WHERE support_tickets.status_code = support_status_codes.code
)
"""
)
@@ -0,0 +1,46 @@
"""add Q&A category codes used by the feedback workspace
Revision ID: 0012_qna_category_codes
Revises: 0011_egbim_review_statuses
"""
from __future__ import annotations
from alembic import op
revision = "0012_qna_category_codes"
down_revision = "0011_egbim_review_statuses"
branch_labels = None
depends_on = None
def upgrade() -> None:
# Some existing staging databases were created before the Q&A category
# split was added. Keep this safe to run against both old and new DBs.
op.execute(
"""
INSERT INTO support_category_codes (code, name, sort_order)
VALUES
('ERROR_QNA', '오류 문의', 51),
('IMPROVEMENT_QNA', '개선 문의', 52)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
sort_order = VALUES(sort_order)
"""
)
def downgrade() -> None:
# Preserve codes that are already referenced by tickets.
op.execute(
"""
DELETE FROM support_category_codes
WHERE code IN ('ERROR_QNA', 'IMPROVEMENT_QNA')
AND NOT EXISTS (
SELECT 1
FROM support_tickets
WHERE support_tickets.category_code = support_category_codes.code
)
"""
)
@@ -0,0 +1,30 @@
"""store SSO tenant memberships on internal support identities"""
from alembic import op
from sqlalchemy import inspect
import sqlalchemy as sa
revision = "0013_tenant_memberships"
down_revision = "0012_qna_category_codes"
branch_labels = None
depends_on = None
def upgrade() -> None:
bind = op.get_bind()
if "tenant_ids" in {
column["name"] for column in inspect(bind).get_columns("support_users")
}:
return
op.add_column(
"support_users",
sa.Column("tenant_ids", sa.JSON(), nullable=True),
)
def downgrade() -> None:
bind = op.get_bind()
if "tenant_ids" in {
column["name"] for column in inspect(bind).get_columns("support_users")
}:
op.drop_column("support_users", "tenant_ids")
@@ -0,0 +1,54 @@
"""store ABC project/channel targets in baron_support"""
from alembic import op
from sqlalchemy import inspect
import sqlalchemy as sa
revision = "0014_abc_workspace_mapping"
down_revision = "0013_tenant_memberships"
branch_labels = None
depends_on = None
def upgrade() -> None:
bind = op.get_bind()
columns = {
column["name"]
for column in inspect(bind).get_columns("workspace_channel_mappings")
}
if "abc_project_id" not in columns:
op.add_column(
"workspace_channel_mappings",
sa.Column("abc_project_id", sa.BigInteger(), nullable=True),
)
op.execute(
sa.text(
"""
INSERT INTO workspace_channel_mappings
(workspace_id, abc_project_id, abc_channel_id, is_active)
SELECT w.id, :project_id, :channel_id, 1
FROM workspaces w
WHERE w.workspace_code = :workspace_code
AND NOT EXISTS (
SELECT 1
FROM workspace_channel_mappings m
WHERE m.workspace_id = w.id
AND m.abc_channel_id = :channel_id
)
""",
).bindparams(
project_id=1,
channel_id="1",
workspace_code="Q&A_Platform",
),
)
def downgrade() -> None:
bind = op.get_bind()
if "abc_project_id" in {
column["name"]
for column in inspect(bind).get_columns("workspace_channel_mappings")
}:
op.drop_column("workspace_channel_mappings", "abc_project_id")
@@ -0,0 +1,73 @@
"""separate feedback handling status from ticket and issue lifecycle status
Revision ID: 0015_feedback_status_separation
Revises: 0014_abc_workspace_mapping
"""
from __future__ import annotations
from alembic import op
from sqlalchemy import inspect, text
import sqlalchemy as sa
revision = "0015_feedback_status_separation"
down_revision = "0014_abc_workspace_mapping"
branch_labels = None
depends_on = None
def upgrade() -> None:
bind = op.get_bind()
columns = {
column["name"]
for column in inspect(bind).get_columns("support_tickets")
}
if "feedback_status" not in columns:
op.add_column(
"support_tickets",
sa.Column(
"feedback_status",
sa.String(length=20),
nullable=False,
server_default="NEW",
),
)
if "external_issue_status" not in columns:
op.add_column(
"support_tickets",
sa.Column("external_issue_status", sa.String(length=30), nullable=True),
)
# Preserve the existing lifecycle state while giving old records a useful
# initial feedback state. Future issue-link changes must not mutate this
# field automatically.
op.execute(
text(
"""
UPDATE support_tickets
SET feedback_status = CASE
WHEN status_code = 'CLOSED' THEN 'CLOSED'
WHEN status_code = 'RESOLVED' THEN 'ANSWERED'
WHEN status_code IN ('APPROVED', 'IN_PROGRESS', 'IN_REVIEW', 'DETAILED_REVIEW')
THEN 'IN_REVIEW'
ELSE 'NEW'
END
"""
)
)
def downgrade() -> None:
bind = op.get_bind()
columns = {
column["name"]
for column in inspect(bind).get_columns("support_tickets")
}
if "external_issue_status" in columns:
op.drop_column("support_tickets", "external_issue_status")
if "feedback_status" in columns:
op.drop_column("support_tickets", "feedback_status")
@@ -0,0 +1,55 @@
"""align feedback status values with the six issue kanban stages
Revision ID: 0016_feedback_status_stages
Revises: 0015_feedback_status_separation
"""
from __future__ import annotations
from alembic import op
from sqlalchemy import text
revision = "0016_feedback_status_stages"
down_revision = "0015_feedback_status_separation"
branch_labels = None
depends_on = None
def upgrade() -> None:
# Keep the lifecycle field independent while normalizing values created by
# the previous four-state implementation.
op.execute(
text(
"""
UPDATE support_tickets
SET feedback_status = CASE feedback_status
WHEN 'NEW' THEN 'INIT'
WHEN 'IN_REVIEW' THEN 'ON_REVIEW'
WHEN 'ANSWERED' THEN 'RESOLVED'
WHEN 'CLOSED' THEN 'RESOLVED'
ELSE feedback_status
END
WHERE feedback_status IN ('NEW', 'IN_REVIEW', 'ANSWERED', 'CLOSED')
"""
)
)
def downgrade() -> None:
op.execute(
text(
"""
UPDATE support_tickets
SET feedback_status = CASE feedback_status
WHEN 'INIT' THEN 'NEW'
WHEN 'ON_REVIEW' THEN 'IN_REVIEW'
WHEN 'DETAILED_REVIEW' THEN 'IN_REVIEW'
WHEN 'IN_PROGRESS' THEN 'IN_REVIEW'
WHEN 'RESOLVED' THEN 'ANSWERED'
WHEN 'PENDING' THEN 'IN_REVIEW'
ELSE feedback_status
END
"""
)
)