feat(platform): close AI expense value loop
Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
@@ -0,0 +1,639 @@
|
||||
"""establish trusted tenant identity foundation
|
||||
|
||||
Revision ID: 20260717_0025
|
||||
Revises: 20260717_0024
|
||||
Create Date: 2026-07-17 10:20:00
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260717_0025"
|
||||
down_revision: str | None = "20260717_0024"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
_DEFAULT_TENANT = "default"
|
||||
_TENANT_SOURCE_TABLES = (
|
||||
"auth_sessions",
|
||||
"expense_cases",
|
||||
"commercial_billing_periods",
|
||||
"tenant_subscriptions",
|
||||
"financial_connector_configs",
|
||||
"agent_asset_release_observations",
|
||||
)
|
||||
|
||||
|
||||
def _require_postgresql() -> None:
|
||||
dialect_name = op.get_bind().dialect.name
|
||||
if dialect_name != "postgresql":
|
||||
raise RuntimeError(
|
||||
"20260717_0025 only supports PostgreSQL; "
|
||||
f"refusing to mutate {dialect_name} without transactional constraint DDL"
|
||||
)
|
||||
|
||||
|
||||
def _inspector() -> sa.Inspector:
|
||||
return sa.inspect(op.get_bind())
|
||||
|
||||
|
||||
def _has_table(table_name: str) -> bool:
|
||||
return _inspector().has_table(table_name)
|
||||
|
||||
|
||||
def _has_column(table_name: str, column_name: str) -> bool:
|
||||
if not _has_table(table_name):
|
||||
return False
|
||||
return column_name in {
|
||||
str(item["name"]) for item in _inspector().get_columns(table_name)
|
||||
}
|
||||
|
||||
|
||||
def _constraint_exists(table_name: str, constraint_name: str) -> bool:
|
||||
inspector = _inspector()
|
||||
names = {
|
||||
str(item.get("name") or "")
|
||||
for item in (
|
||||
*inspector.get_unique_constraints(table_name),
|
||||
*inspector.get_foreign_keys(table_name),
|
||||
*inspector.get_check_constraints(table_name),
|
||||
)
|
||||
}
|
||||
return constraint_name in names
|
||||
|
||||
|
||||
def _index_exists(table_name: str, index_name: str) -> bool:
|
||||
return index_name in {
|
||||
str(item.get("name") or "") for item in _inspector().get_indexes(table_name)
|
||||
}
|
||||
|
||||
|
||||
def _drop_unique_for_columns(table_name: str, columns: tuple[str, ...]) -> None:
|
||||
if not _has_table(table_name):
|
||||
return
|
||||
for item in _inspector().get_unique_constraints(table_name):
|
||||
if tuple(item.get("column_names") or ()) != columns:
|
||||
continue
|
||||
name = str(item.get("name") or "")
|
||||
if name:
|
||||
op.drop_constraint(name, table_name, type_="unique")
|
||||
|
||||
|
||||
def _drop_foreign_keys_for_columns(
|
||||
table_name: str,
|
||||
columns: tuple[str, ...],
|
||||
) -> None:
|
||||
if not _has_table(table_name):
|
||||
return
|
||||
for item in _inspector().get_foreign_keys(table_name):
|
||||
if tuple(item.get("constrained_columns") or ()) != columns:
|
||||
continue
|
||||
name = str(item.get("name") or "")
|
||||
if name:
|
||||
op.drop_constraint(name, table_name, type_="foreignkey")
|
||||
|
||||
|
||||
def _ensure_unique(
|
||||
table_name: str,
|
||||
name: str,
|
||||
columns: tuple[str, ...],
|
||||
) -> None:
|
||||
if not _constraint_exists(table_name, name):
|
||||
op.create_unique_constraint(name, table_name, list(columns))
|
||||
|
||||
|
||||
def _ensure_index(
|
||||
table_name: str,
|
||||
name: str,
|
||||
columns: tuple[str, ...],
|
||||
) -> None:
|
||||
if not _index_exists(table_name, name):
|
||||
op.create_index(name, table_name, list(columns))
|
||||
|
||||
|
||||
def _ensure_tenant_column(table_name: str) -> None:
|
||||
if not _has_table(table_name):
|
||||
return
|
||||
if not _has_column(table_name, "tenant_id"):
|
||||
op.add_column(
|
||||
table_name,
|
||||
sa.Column("tenant_id", sa.String(length=64), nullable=True),
|
||||
)
|
||||
if table_name == "expense_claims" and _has_table("expense_case_links"):
|
||||
op.execute(
|
||||
sa.text(
|
||||
"UPDATE expense_claims AS claim SET tenant_id = COALESCE(("
|
||||
"SELECT link.tenant_id FROM expense_case_links AS link "
|
||||
"WHERE link.resource_type = 'expense_claim' "
|
||||
"AND link.resource_id = claim.id ORDER BY link.created_at ASC LIMIT 1"
|
||||
"), :default_tenant) WHERE claim.tenant_id IS NULL"
|
||||
).bindparams(default_tenant=_DEFAULT_TENANT)
|
||||
)
|
||||
else:
|
||||
op.execute(
|
||||
sa.text(
|
||||
f"UPDATE {table_name} SET tenant_id = :default_tenant "
|
||||
"WHERE tenant_id IS NULL"
|
||||
).bindparams(default_tenant=_DEFAULT_TENANT)
|
||||
)
|
||||
op.alter_column(
|
||||
table_name,
|
||||
"tenant_id",
|
||||
existing_type=sa.String(length=64),
|
||||
nullable=False,
|
||||
server_default=None,
|
||||
)
|
||||
|
||||
|
||||
def _create_tenant_registry() -> None:
|
||||
if not _has_table("tenants"):
|
||||
op.create_table(
|
||||
"tenants",
|
||||
sa.Column("tenant_id", sa.String(length=64), nullable=False),
|
||||
sa.Column("tenant_code", sa.String(length=64), nullable=False),
|
||||
sa.Column("name", sa.String(length=160), nullable=False),
|
||||
sa.Column("status", sa.String(length=20), nullable=False),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now(),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now(),
|
||||
nullable=False,
|
||||
),
|
||||
sa.CheckConstraint(
|
||||
"status IN ('active', 'suspended', 'disabled')",
|
||||
name="ck_tenants_status",
|
||||
),
|
||||
sa.CheckConstraint(
|
||||
"length(trim(tenant_id)) > 0 AND length(trim(tenant_code)) > 0 "
|
||||
"AND length(trim(name)) > 0",
|
||||
name="ck_tenants_identity",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("tenant_id"),
|
||||
sa.UniqueConstraint("tenant_code", name="uq_tenants_tenant_code"),
|
||||
)
|
||||
op.create_index("ix_tenants_status", "tenants", ["status"])
|
||||
op.execute(
|
||||
sa.text(
|
||||
"INSERT INTO tenants (tenant_id, tenant_code, name, status) VALUES "
|
||||
"('default', 'default', '默认企业', 'active'), "
|
||||
"('platform', 'platform', '平台管理域', 'active') "
|
||||
"ON CONFLICT DO NOTHING"
|
||||
)
|
||||
)
|
||||
for table_name in _TENANT_SOURCE_TABLES:
|
||||
if not _has_column(table_name, "tenant_id"):
|
||||
continue
|
||||
op.execute(
|
||||
sa.text(
|
||||
f"INSERT INTO tenants (tenant_id, tenant_code, name, status) "
|
||||
f"SELECT DISTINCT trim(tenant_id), trim(tenant_id), trim(tenant_id), "
|
||||
f"'active' FROM {table_name} "
|
||||
"WHERE tenant_id IS NOT NULL AND length(trim(tenant_id)) > 0 "
|
||||
"ON CONFLICT DO NOTHING"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _tenantize_organizations() -> None:
|
||||
table = "organization_units"
|
||||
if not _has_table(table):
|
||||
return
|
||||
_ensure_tenant_column(table)
|
||||
_drop_unique_for_columns(table, ("unit_code",))
|
||||
_drop_foreign_keys_for_columns(table, ("parent_id",))
|
||||
_ensure_unique(table, "uq_organization_units_tenant_id", ("tenant_id", "id"))
|
||||
_ensure_unique(
|
||||
table,
|
||||
"uq_organization_units_tenant_code",
|
||||
("tenant_id", "unit_code"),
|
||||
)
|
||||
if not _constraint_exists(table, "fk_organization_units_tenant"):
|
||||
op.create_foreign_key(
|
||||
"fk_organization_units_tenant",
|
||||
table,
|
||||
"tenants",
|
||||
["tenant_id"],
|
||||
["tenant_id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
if not _constraint_exists(table, "fk_organization_units_tenant_parent"):
|
||||
op.create_foreign_key(
|
||||
"fk_organization_units_tenant_parent",
|
||||
table,
|
||||
table,
|
||||
["tenant_id", "parent_id"],
|
||||
["tenant_id", "id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
_ensure_index(
|
||||
table,
|
||||
"ix_organization_units_tenant_name",
|
||||
("tenant_id", "name"),
|
||||
)
|
||||
|
||||
|
||||
def _tenantize_employees() -> None:
|
||||
table = "employees"
|
||||
if not _has_table(table):
|
||||
return
|
||||
_ensure_tenant_column(table)
|
||||
_drop_unique_for_columns(table, ("employee_no",))
|
||||
_drop_unique_for_columns(table, ("email",))
|
||||
_drop_foreign_keys_for_columns(table, ("organization_unit_id",))
|
||||
_drop_foreign_keys_for_columns(table, ("manager_id",))
|
||||
_ensure_unique(table, "uq_employees_tenant_id", ("tenant_id", "id"))
|
||||
_ensure_unique(
|
||||
table,
|
||||
"uq_employees_tenant_employee_no",
|
||||
("tenant_id", "employee_no"),
|
||||
)
|
||||
_ensure_unique(table, "uq_employees_tenant_email", ("tenant_id", "email"))
|
||||
if not _constraint_exists(table, "fk_employees_tenant"):
|
||||
op.create_foreign_key(
|
||||
"fk_employees_tenant",
|
||||
table,
|
||||
"tenants",
|
||||
["tenant_id"],
|
||||
["tenant_id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
if _has_table("organization_units") and not _constraint_exists(
|
||||
table,
|
||||
"fk_employees_tenant_organization_unit",
|
||||
):
|
||||
op.create_foreign_key(
|
||||
"fk_employees_tenant_organization_unit",
|
||||
table,
|
||||
"organization_units",
|
||||
["tenant_id", "organization_unit_id"],
|
||||
["tenant_id", "id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
if not _constraint_exists(table, "fk_employees_tenant_manager"):
|
||||
op.create_foreign_key(
|
||||
"fk_employees_tenant_manager",
|
||||
table,
|
||||
table,
|
||||
["tenant_id", "manager_id"],
|
||||
["tenant_id", "id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
_ensure_index(table, "ix_employees_tenant_status", ("tenant_id", "employment_status"))
|
||||
_ensure_index(table, "ix_employees_tenant_name", ("tenant_id", "name"))
|
||||
|
||||
|
||||
def _tenantize_financial_records() -> None:
|
||||
if _has_table("expense_claims"):
|
||||
table = "expense_claims"
|
||||
_ensure_tenant_column(table)
|
||||
_drop_unique_for_columns(table, ("claim_no",))
|
||||
_drop_foreign_keys_for_columns(table, ("employee_id",))
|
||||
_drop_foreign_keys_for_columns(table, ("department_id",))
|
||||
_ensure_unique(table, "uq_expense_claims_tenant_id", ("tenant_id", "id"))
|
||||
_ensure_unique(
|
||||
table,
|
||||
"uq_expense_claims_tenant_claim_no",
|
||||
("tenant_id", "claim_no"),
|
||||
)
|
||||
if not _constraint_exists(table, "fk_expense_claims_tenant"):
|
||||
op.create_foreign_key(
|
||||
"fk_expense_claims_tenant",
|
||||
table,
|
||||
"tenants",
|
||||
["tenant_id"],
|
||||
["tenant_id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
if _has_table("employees") and not _constraint_exists(
|
||||
table,
|
||||
"fk_expense_claims_tenant_employee",
|
||||
):
|
||||
op.create_foreign_key(
|
||||
"fk_expense_claims_tenant_employee",
|
||||
table,
|
||||
"employees",
|
||||
["tenant_id", "employee_id"],
|
||||
["tenant_id", "id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
if _has_table("organization_units") and not _constraint_exists(
|
||||
table,
|
||||
"fk_expense_claims_tenant_department",
|
||||
):
|
||||
op.create_foreign_key(
|
||||
"fk_expense_claims_tenant_department",
|
||||
table,
|
||||
"organization_units",
|
||||
["tenant_id", "department_id"],
|
||||
["tenant_id", "id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
_ensure_index(table, "ix_expense_claims_tenant_status", ("tenant_id", "status"))
|
||||
_ensure_index(
|
||||
table,
|
||||
"ix_expense_claims_tenant_occurred",
|
||||
("tenant_id", "occurred_at"),
|
||||
)
|
||||
for table, number_column, constraint_name, dimension_column, index_name in (
|
||||
(
|
||||
"accounts_receivable",
|
||||
"receivable_no",
|
||||
"uq_accounts_receivable_tenant_no",
|
||||
"customer_id",
|
||||
"ix_accounts_receivable_tenant_customer",
|
||||
),
|
||||
(
|
||||
"accounts_payable",
|
||||
"payable_no",
|
||||
"uq_accounts_payable_tenant_no",
|
||||
"vendor_id",
|
||||
"ix_accounts_payable_tenant_vendor",
|
||||
),
|
||||
):
|
||||
if not _has_table(table):
|
||||
continue
|
||||
_ensure_tenant_column(table)
|
||||
_drop_unique_for_columns(table, (number_column,))
|
||||
_ensure_unique(table, constraint_name, ("tenant_id", number_column))
|
||||
tenant_fk = f"fk_{table}_tenant"
|
||||
if not _constraint_exists(table, tenant_fk):
|
||||
op.create_foreign_key(
|
||||
tenant_fk,
|
||||
table,
|
||||
"tenants",
|
||||
["tenant_id"],
|
||||
["tenant_id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
_ensure_index(table, index_name, ("tenant_id", dimension_column))
|
||||
|
||||
|
||||
def _ensure_memberships() -> None:
|
||||
if not _has_table("employees"):
|
||||
return
|
||||
if not _has_table("tenant_memberships"):
|
||||
op.create_table(
|
||||
"tenant_memberships",
|
||||
sa.Column("id", sa.String(length=36), nullable=False),
|
||||
sa.Column("tenant_id", sa.String(length=64), nullable=False),
|
||||
sa.Column("employee_id", sa.String(length=36), nullable=False),
|
||||
sa.Column("status", sa.String(length=20), nullable=False),
|
||||
sa.Column("is_primary", sa.Boolean(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now(),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now(),
|
||||
nullable=False,
|
||||
),
|
||||
sa.CheckConstraint(
|
||||
"status IN ('active', 'inactive')",
|
||||
name="ck_tenant_memberships_status",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["tenant_id"],
|
||||
["tenants.tenant_id"],
|
||||
name="fk_tenant_memberships_tenant",
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["tenant_id", "employee_id"],
|
||||
["employees.tenant_id", "employees.id"],
|
||||
name="fk_tenant_memberships_tenant_employee",
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint(
|
||||
"tenant_id",
|
||||
"employee_id",
|
||||
name="uq_tenant_memberships_tenant_employee",
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_tenant_memberships_employee_active",
|
||||
"tenant_memberships",
|
||||
["employee_id", "status"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_tenant_memberships_tenant_active",
|
||||
"tenant_memberships",
|
||||
["tenant_id", "status"],
|
||||
)
|
||||
op.execute(
|
||||
sa.text(
|
||||
"INSERT INTO tenant_memberships "
|
||||
"(id, tenant_id, employee_id, status, is_primary) "
|
||||
"SELECT gen_random_uuid()::text, employee.tenant_id, employee.id, "
|
||||
"CASE WHEN employee.employment_status = '停用' THEN 'inactive' "
|
||||
"ELSE 'active' END, true FROM employees AS employee "
|
||||
"ON CONFLICT (tenant_id, employee_id) DO NOTHING"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _bind_auth_sessions() -> None:
|
||||
if not _has_table("auth_sessions"):
|
||||
return
|
||||
if not _constraint_exists("auth_sessions", "fk_auth_sessions_tenant"):
|
||||
op.create_foreign_key(
|
||||
"fk_auth_sessions_tenant",
|
||||
"auth_sessions",
|
||||
"tenants",
|
||||
["tenant_id"],
|
||||
["tenant_id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
_require_postgresql()
|
||||
_create_tenant_registry()
|
||||
_tenantize_organizations()
|
||||
_tenantize_employees()
|
||||
_tenantize_financial_records()
|
||||
_ensure_memberships()
|
||||
_bind_auth_sessions()
|
||||
|
||||
|
||||
def _require_safe_downgrade() -> None:
|
||||
for table_name in (
|
||||
"organization_units",
|
||||
"employees",
|
||||
"expense_claims",
|
||||
"accounts_receivable",
|
||||
"accounts_payable",
|
||||
):
|
||||
if not _has_column(table_name, "tenant_id"):
|
||||
continue
|
||||
count = int(
|
||||
op.get_bind().scalar(
|
||||
sa.text(
|
||||
f"SELECT COUNT(*) FROM {table_name} "
|
||||
"WHERE tenant_id <> :default_tenant"
|
||||
).bindparams(default_tenant=_DEFAULT_TENANT)
|
||||
)
|
||||
or 0
|
||||
)
|
||||
if count:
|
||||
raise RuntimeError(
|
||||
"cannot downgrade tenant identity foundation: "
|
||||
f"{table_name} contains non-default tenant data ({count})"
|
||||
)
|
||||
|
||||
|
||||
def _restore_legacy_financial_constraints() -> None:
|
||||
if _has_table("expense_claims"):
|
||||
table = "expense_claims"
|
||||
for name in (
|
||||
"fk_expense_claims_tenant_department",
|
||||
"fk_expense_claims_tenant_employee",
|
||||
"fk_expense_claims_tenant",
|
||||
"uq_expense_claims_tenant_claim_no",
|
||||
"uq_expense_claims_tenant_id",
|
||||
):
|
||||
if _constraint_exists(table, name):
|
||||
op.drop_constraint(name, table)
|
||||
for name in (
|
||||
"ix_expense_claims_tenant_occurred",
|
||||
"ix_expense_claims_tenant_status",
|
||||
):
|
||||
if _index_exists(table, name):
|
||||
op.drop_index(name, table_name=table)
|
||||
_ensure_unique(table, "uq_expense_claims_claim_no", ("claim_no",))
|
||||
if _has_table("employees"):
|
||||
op.create_foreign_key(
|
||||
"fk_expense_claims_employee_id",
|
||||
table,
|
||||
"employees",
|
||||
["employee_id"],
|
||||
["id"],
|
||||
)
|
||||
if _has_table("organization_units"):
|
||||
op.create_foreign_key(
|
||||
"fk_expense_claims_department_id",
|
||||
table,
|
||||
"organization_units",
|
||||
["department_id"],
|
||||
["id"],
|
||||
)
|
||||
op.drop_column(table, "tenant_id")
|
||||
for table, constraint_name, number_column, index_name in (
|
||||
(
|
||||
"accounts_receivable",
|
||||
"uq_accounts_receivable_tenant_no",
|
||||
"receivable_no",
|
||||
"ix_accounts_receivable_tenant_customer",
|
||||
),
|
||||
(
|
||||
"accounts_payable",
|
||||
"uq_accounts_payable_tenant_no",
|
||||
"payable_no",
|
||||
"ix_accounts_payable_tenant_vendor",
|
||||
),
|
||||
):
|
||||
if not _has_column(table, "tenant_id"):
|
||||
continue
|
||||
tenant_fk = f"fk_{table}_tenant"
|
||||
if _constraint_exists(table, tenant_fk):
|
||||
op.drop_constraint(tenant_fk, table, type_="foreignkey")
|
||||
if _constraint_exists(table, constraint_name):
|
||||
op.drop_constraint(constraint_name, table, type_="unique")
|
||||
if _index_exists(table, index_name):
|
||||
op.drop_index(index_name, table_name=table)
|
||||
_ensure_unique(table, f"uq_{table}_{number_column}", (number_column,))
|
||||
op.drop_column(table, "tenant_id")
|
||||
|
||||
|
||||
def _restore_legacy_employee_constraints() -> None:
|
||||
if _has_table("employees"):
|
||||
table = "employees"
|
||||
for name in (
|
||||
"fk_employees_tenant_manager",
|
||||
"fk_employees_tenant_organization_unit",
|
||||
"fk_employees_tenant",
|
||||
"uq_employees_tenant_email",
|
||||
"uq_employees_tenant_employee_no",
|
||||
"uq_employees_tenant_id",
|
||||
):
|
||||
if _constraint_exists(table, name):
|
||||
op.drop_constraint(name, table)
|
||||
for name in ("ix_employees_tenant_name", "ix_employees_tenant_status"):
|
||||
if _index_exists(table, name):
|
||||
op.drop_index(name, table_name=table)
|
||||
_ensure_unique(table, "uq_employees_employee_no", ("employee_no",))
|
||||
_ensure_unique(table, "uq_employees_email", ("email",))
|
||||
if _has_table("organization_units"):
|
||||
op.create_foreign_key(
|
||||
"fk_employees_organization_unit_id",
|
||||
table,
|
||||
"organization_units",
|
||||
["organization_unit_id"],
|
||||
["id"],
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_employees_manager_id",
|
||||
table,
|
||||
table,
|
||||
["manager_id"],
|
||||
["id"],
|
||||
)
|
||||
op.drop_column(table, "tenant_id")
|
||||
if _has_table("organization_units"):
|
||||
table = "organization_units"
|
||||
for name in (
|
||||
"fk_organization_units_tenant_parent",
|
||||
"fk_organization_units_tenant",
|
||||
"uq_organization_units_tenant_code",
|
||||
"uq_organization_units_tenant_id",
|
||||
):
|
||||
if _constraint_exists(table, name):
|
||||
op.drop_constraint(name, table)
|
||||
if _index_exists(table, "ix_organization_units_tenant_name"):
|
||||
op.drop_index("ix_organization_units_tenant_name", table_name=table)
|
||||
_ensure_unique(table, "uq_organization_units_unit_code", ("unit_code",))
|
||||
op.create_foreign_key(
|
||||
"fk_organization_units_parent_id",
|
||||
table,
|
||||
table,
|
||||
["parent_id"],
|
||||
["id"],
|
||||
)
|
||||
op.drop_column(table, "tenant_id")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
_require_postgresql()
|
||||
_require_safe_downgrade()
|
||||
if _has_table("auth_sessions") and _constraint_exists(
|
||||
"auth_sessions",
|
||||
"fk_auth_sessions_tenant",
|
||||
):
|
||||
op.drop_constraint(
|
||||
"fk_auth_sessions_tenant",
|
||||
"auth_sessions",
|
||||
type_="foreignkey",
|
||||
)
|
||||
if _has_table("tenant_memberships"):
|
||||
op.drop_table("tenant_memberships")
|
||||
_restore_legacy_financial_constraints()
|
||||
_restore_legacy_employee_constraints()
|
||||
if _has_table("tenants"):
|
||||
if _index_exists("tenants", "ix_tenants_status"):
|
||||
op.drop_index("ix_tenants_status", table_name="tenants")
|
||||
op.drop_table("tenants")
|
||||
Reference in New Issue
Block a user