"""Phase 6 PAN login, invitation and client identity. Revision ID: 20260723_phase6_pan_login_client_identity Revises: 20260723_phase5_secure_credential_vault """ from alembic import op import sqlalchemy as sa revision="20260723_phase6_pan_login_client_identity" down_revision="20260723_phase5_secure_credential_vault" branch_labels=None depends_on=None def upgrade(): op.create_table("client_portal_identities", sa.Column("id",sa.Integer(),primary_key=True), sa.Column("tenant_id",sa.Integer(),sa.ForeignKey("tenants.id",ondelete="CASCADE"),nullable=False), sa.Column("branch_id",sa.Integer(),sa.ForeignKey("branches.id",ondelete="CASCADE"),nullable=False), sa.Column("client_id",sa.Integer(),sa.ForeignKey("clients.id",ondelete="CASCADE"),nullable=False), sa.Column("user_id",sa.Integer(),sa.ForeignKey("users.id",ondelete="SET NULL")), sa.Column("normalized_pan",sa.String(10),nullable=False), sa.Column("login_email",sa.String(255),nullable=False), sa.Column("email_is_placeholder",sa.Boolean(),nullable=False,server_default=sa.false()), sa.Column("status",sa.String(30),nullable=False,server_default="not_invited"), sa.Column("invited_at_utc",sa.DateTime(timezone=True)), sa.Column("activated_at_utc",sa.DateTime(timezone=True)), sa.Column("last_invited_at_utc",sa.DateTime(timezone=True)), sa.Column("created_by_user_id",sa.Integer(),sa.ForeignKey("users.id",ondelete="SET NULL")), sa.Column("updated_by_user_id",sa.Integer(),sa.ForeignKey("users.id",ondelete="SET NULL")), sa.Column("created_at_utc",sa.DateTime(timezone=True),nullable=False), sa.Column("updated_at_utc",sa.DateTime(timezone=True),nullable=False), sa.UniqueConstraint("client_id",name="uq_client_portal_identity_client"), sa.UniqueConstraint("tenant_id","normalized_pan",name="uq_client_portal_identity_tenant_pan")) for c in ["tenant_id","branch_id","client_id","user_id","normalized_pan","login_email","status"]: op.create_index(f"ix_client_portal_identities_{c}","client_portal_identities",[c]) op.create_table("client_portal_invitations", sa.Column("id",sa.Integer(),primary_key=True),sa.Column("identity_id",sa.Integer(),sa.ForeignKey("client_portal_identities.id",ondelete="CASCADE"),nullable=False),sa.Column("user_id",sa.Integer(),sa.ForeignKey("users.id",ondelete="CASCADE"),nullable=False),sa.Column("delivery_mode",sa.String(30),nullable=False,server_default="email"),sa.Column("recipient_email",sa.String(255)),sa.Column("status",sa.String(30),nullable=False,server_default="issued"),sa.Column("issued_by_user_id",sa.Integer(),sa.ForeignKey("users.id",ondelete="SET NULL")),sa.Column("issued_at_utc",sa.DateTime(timezone=True),nullable=False),sa.Column("expires_at_utc",sa.DateTime(timezone=True)),sa.Column("accepted_at_utc",sa.DateTime(timezone=True)),sa.Column("note",sa.Text())) for c in ["identity_id","user_id","status"]: op.create_index(f"ix_client_portal_invitations_{c}","client_portal_invitations",[c]) op.create_table("client_identity_audit_logs", sa.Column("id",sa.Integer(),primary_key=True),sa.Column("tenant_id",sa.Integer(),sa.ForeignKey("tenants.id",ondelete="CASCADE"),nullable=False),sa.Column("client_id",sa.Integer(),sa.ForeignKey("clients.id",ondelete="CASCADE"),nullable=False),sa.Column("identity_id",sa.Integer(),sa.ForeignKey("client_portal_identities.id",ondelete="SET NULL")),sa.Column("actor_user_id",sa.Integer(),sa.ForeignKey("users.id",ondelete="SET NULL")),sa.Column("action",sa.String(50),nullable=False),sa.Column("summary",sa.String(255),nullable=False),sa.Column("created_at_utc",sa.DateTime(timezone=True),nullable=False)) for c in ["tenant_id","client_id","identity_id","action"]: op.create_index(f"ix_client_identity_audit_logs_{c}","client_identity_audit_logs",[c]) def downgrade(): op.drop_table("client_identity_audit_logs") op.drop_table("client_portal_invitations") op.drop_table("client_portal_identities")