Files
maskanx_crm_frontend/alembic/versions/eca4681c9f66_initial_crm_schema.py
T
AFFAANhandClaude Opus 5 f6a54d6f93 Initial commit: Maskan CRM backend
Independent FastAPI backend for Maskan CRM.

Owns contacts, organizations, leads, pipelines, activities, products,
quotes, users, permissions, audit records and first-party integration
credentials, with Alembic migrations against PostgreSQL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:28:41 +05:30

417 lines
26 KiB
Python

"""initial_crm_schema
Revision ID: eca4681c9f66
Revises:
Create Date: 2026-07-29 12:34:04.058619
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = 'eca4681c9f66'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('crm_tenants',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('slug', sa.String(length=80), nullable=False),
sa.Column('name', sa.String(length=160), nullable=False),
sa.Column('mode', sa.String(length=24), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('settings', sa.JSON(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('slug')
)
op.create_table('crm_external_links',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('provider', sa.String(length=80), nullable=False),
sa.Column('entity_type', sa.String(length=80), nullable=False),
sa.Column('entity_id', sa.String(length=36), nullable=False),
sa.Column('external_id', sa.String(length=255), nullable=False),
sa.Column('metadata_json', sa.JSON(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('tenant_id', 'provider', 'entity_type', 'external_id', name='uq_crm_external_links_provider_entity')
)
op.create_index('ix_crm_external_links_local', 'crm_external_links', ['tenant_id', 'entity_type', 'entity_id'], unique=False)
op.create_table('crm_idempotency_records',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('scope', sa.String(length=100), nullable=False),
sa.Column('idempotency_key', sa.String(length=255), nullable=False),
sa.Column('response_code', sa.Integer(), nullable=False),
sa.Column('response_body', sa.JSON(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('tenant_id', 'scope', 'idempotency_key', name='uq_crm_idempotency_scope_key')
)
op.create_table('crm_integration_credentials',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('name', sa.String(length=120), nullable=False),
sa.Column('key_prefix', sa.String(length=20), nullable=False),
sa.Column('key_hash', sa.String(length=64), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('last_used_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('key_hash'),
sa.UniqueConstraint('tenant_id', 'name', name='uq_crm_integration_key_name')
)
op.create_index(op.f('ix_crm_integration_credentials_tenant_id'), 'crm_integration_credentials', ['tenant_id'], unique=False)
op.create_table('crm_integration_events',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('topic', sa.String(length=140), nullable=False),
sa.Column('payload', sa.JSON(), nullable=False),
sa.Column('status', sa.String(length=24), nullable=False),
sa.Column('attempts', sa.Integer(), nullable=False),
sa.Column('available_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('delivered_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index('ix_crm_integration_events_delivery', 'crm_integration_events', ['status', 'available_at'], unique=False)
op.create_table('crm_lead_sources',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('name', sa.String(length=120), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('tenant_id', 'name', name='uq_crm_lead_sources_tenant_name')
)
op.create_index(op.f('ix_crm_lead_sources_tenant_id'), 'crm_lead_sources', ['tenant_id'], unique=False)
op.create_table('crm_lead_types',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('name', sa.String(length=120), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('tenant_id', 'name', name='uq_crm_lead_types_tenant_name')
)
op.create_index(op.f('ix_crm_lead_types_tenant_id'), 'crm_lead_types', ['tenant_id'], unique=False)
op.create_table('crm_pipelines',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('name', sa.String(length=140), nullable=False),
sa.Column('is_default', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('tenant_id', 'name', name='uq_crm_pipelines_tenant_name')
)
op.create_index(op.f('ix_crm_pipelines_tenant_id'), 'crm_pipelines', ['tenant_id'], unique=False)
op.create_table('crm_products',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('sku', sa.String(length=100), nullable=False),
sa.Column('name', sa.String(length=200), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('quantity', sa.Integer(), nullable=False),
sa.Column('price', sa.Numeric(precision=14, scale=2), nullable=False),
sa.Column('currency', sa.String(length=3), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('tenant_id', 'sku', name='uq_crm_products_tenant_sku')
)
op.create_index(op.f('ix_crm_products_tenant_id'), 'crm_products', ['tenant_id'], unique=False)
op.create_index('ix_crm_products_tenant_name', 'crm_products', ['tenant_id', 'name'], unique=False)
op.create_table('crm_users',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('email', sa.String(length=255), nullable=False),
sa.Column('full_name', sa.String(length=160), nullable=False),
sa.Column('password_hash', sa.String(length=255), nullable=False),
sa.Column('role', sa.String(length=24), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('last_login_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('tenant_id', 'email', name='uq_crm_users_tenant_email')
)
op.create_index(op.f('ix_crm_users_tenant_id'), 'crm_users', ['tenant_id'], unique=False)
op.create_index('ix_crm_users_tenant_role', 'crm_users', ['tenant_id', 'role'], unique=False)
op.create_table('crm_audit_events',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('actor_id', sa.String(length=36), nullable=True),
sa.Column('action', sa.String(length=100), nullable=False),
sa.Column('entity_type', sa.String(length=80), nullable=False),
sa.Column('entity_id', sa.String(length=80), nullable=False),
sa.Column('payload', sa.JSON(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['actor_id'], ['crm_users.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index('ix_crm_audit_tenant_created', 'crm_audit_events', ['tenant_id', 'created_at'], unique=False)
op.create_index('ix_crm_audit_tenant_entity', 'crm_audit_events', ['tenant_id', 'entity_type', 'entity_id'], unique=False)
op.create_table('crm_organizations',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('name', sa.String(length=200), nullable=False),
sa.Column('legal_name', sa.String(length=240), nullable=True),
sa.Column('website', sa.String(length=500), nullable=True),
sa.Column('primary_email', sa.String(length=255), nullable=True),
sa.Column('phone', sa.String(length=80), nullable=True),
sa.Column('industry', sa.String(length=120), nullable=True),
sa.Column('address', sa.JSON(), nullable=False),
sa.Column('attributes', sa.JSON(), nullable=False),
sa.Column('owner_id', sa.String(length=36), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['owner_id'], ['crm_users.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_crm_organizations_tenant_id'), 'crm_organizations', ['tenant_id'], unique=False)
op.create_index('ix_crm_organizations_tenant_name', 'crm_organizations', ['tenant_id', 'name'], unique=False)
op.create_table('crm_stages',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('pipeline_id', sa.String(length=36), nullable=False),
sa.Column('name', sa.String(length=140), nullable=False),
sa.Column('position', sa.Integer(), nullable=False),
sa.Column('probability', sa.Integer(), nullable=False),
sa.Column('color', sa.String(length=16), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['pipeline_id'], ['crm_pipelines.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('pipeline_id', 'name', name='uq_crm_stages_pipeline_name')
)
op.create_index('ix_crm_stages_pipeline_position', 'crm_stages', ['pipeline_id', 'position'], unique=False)
op.create_index(op.f('ix_crm_stages_tenant_id'), 'crm_stages', ['tenant_id'], unique=False)
op.create_table('crm_contacts',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('first_name', sa.String(length=100), nullable=False),
sa.Column('last_name', sa.String(length=100), nullable=False),
sa.Column('job_title', sa.String(length=160), nullable=True),
sa.Column('primary_email', sa.String(length=255), nullable=True),
sa.Column('emails', sa.JSON(), nullable=False),
sa.Column('phones', sa.JSON(), nullable=False),
sa.Column('lifecycle_stage', sa.String(length=40), nullable=False),
sa.Column('lead_source', sa.String(length=120), nullable=True),
sa.Column('score', sa.Integer(), nullable=False),
sa.Column('organization_id', sa.String(length=36), nullable=True),
sa.Column('owner_id', sa.String(length=36), nullable=True),
sa.Column('attributes', sa.JSON(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['organization_id'], ['crm_organizations.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['owner_id'], ['crm_users.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('tenant_id', 'primary_email', name='uq_crm_contacts_tenant_primary_email')
)
op.create_index(op.f('ix_crm_contacts_organization_id'), 'crm_contacts', ['organization_id'], unique=False)
op.create_index(op.f('ix_crm_contacts_tenant_id'), 'crm_contacts', ['tenant_id'], unique=False)
op.create_index('ix_crm_contacts_tenant_name', 'crm_contacts', ['tenant_id', 'last_name', 'first_name'], unique=False)
op.create_index('ix_crm_contacts_tenant_score', 'crm_contacts', ['tenant_id', 'score'], unique=False)
op.create_table('crm_leads',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('title', sa.String(length=240), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('value', sa.Numeric(precision=14, scale=2), nullable=False),
sa.Column('currency', sa.String(length=3), nullable=False),
sa.Column('status', sa.String(length=24), nullable=False),
sa.Column('score', sa.Integer(), nullable=False),
sa.Column('position', sa.Integer(), nullable=False),
sa.Column('lost_reason', sa.Text(), nullable=True),
sa.Column('expected_close_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('closed_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('contact_id', sa.String(length=36), nullable=True),
sa.Column('organization_id', sa.String(length=36), nullable=True),
sa.Column('owner_id', sa.String(length=36), nullable=True),
sa.Column('pipeline_id', sa.String(length=36), nullable=False),
sa.Column('stage_id', sa.String(length=36), nullable=False),
sa.Column('source_id', sa.String(length=36), nullable=True),
sa.Column('type_id', sa.String(length=36), nullable=True),
sa.Column('attributes', sa.JSON(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['contact_id'], ['crm_contacts.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['organization_id'], ['crm_organizations.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['owner_id'], ['crm_users.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['pipeline_id'], ['crm_pipelines.id'], ondelete='RESTRICT'),
sa.ForeignKeyConstraint(['source_id'], ['crm_lead_sources.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['stage_id'], ['crm_stages.id'], ondelete='RESTRICT'),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['type_id'], ['crm_lead_types.id'], ondelete='SET NULL'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_crm_leads_contact_id'), 'crm_leads', ['contact_id'], unique=False)
op.create_index(op.f('ix_crm_leads_organization_id'), 'crm_leads', ['organization_id'], unique=False)
op.create_index(op.f('ix_crm_leads_tenant_id'), 'crm_leads', ['tenant_id'], unique=False)
op.create_index('ix_crm_leads_tenant_owner', 'crm_leads', ['tenant_id', 'owner_id'], unique=False)
op.create_index('ix_crm_leads_tenant_stage', 'crm_leads', ['tenant_id', 'stage_id', 'position'], unique=False)
op.create_index('ix_crm_leads_tenant_status', 'crm_leads', ['tenant_id', 'status'], unique=False)
op.create_table('crm_activities',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('activity_type', sa.String(length=32), nullable=False),
sa.Column('title', sa.String(length=240), nullable=False),
sa.Column('details', sa.Text(), nullable=True),
sa.Column('starts_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('ends_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('due_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('is_done', sa.Boolean(), nullable=False),
sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('owner_id', sa.String(length=36), nullable=True),
sa.Column('contact_id', sa.String(length=36), nullable=True),
sa.Column('organization_id', sa.String(length=36), nullable=True),
sa.Column('lead_id', sa.String(length=36), nullable=True),
sa.Column('additional', sa.JSON(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['contact_id'], ['crm_contacts.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['lead_id'], ['crm_leads.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['organization_id'], ['crm_organizations.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['owner_id'], ['crm_users.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_crm_activities_contact_id'), 'crm_activities', ['contact_id'], unique=False)
op.create_index(op.f('ix_crm_activities_lead_id'), 'crm_activities', ['lead_id'], unique=False)
op.create_index(op.f('ix_crm_activities_organization_id'), 'crm_activities', ['organization_id'], unique=False)
op.create_index('ix_crm_activities_tenant_due', 'crm_activities', ['tenant_id', 'is_done', 'due_at'], unique=False)
op.create_index(op.f('ix_crm_activities_tenant_id'), 'crm_activities', ['tenant_id'], unique=False)
op.create_table('crm_quotes',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('number', sa.String(length=80), nullable=False),
sa.Column('subject', sa.String(length=240), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('status', sa.String(length=32), nullable=False),
sa.Column('currency', sa.String(length=3), nullable=False),
sa.Column('billing_address', sa.JSON(), nullable=False),
sa.Column('shipping_address', sa.JSON(), nullable=False),
sa.Column('discount_amount', sa.Numeric(precision=14, scale=2), nullable=False),
sa.Column('tax_amount', sa.Numeric(precision=14, scale=2), nullable=False),
sa.Column('adjustment_amount', sa.Numeric(precision=14, scale=2), nullable=False),
sa.Column('subtotal', sa.Numeric(precision=14, scale=2), nullable=False),
sa.Column('grand_total', sa.Numeric(precision=14, scale=2), nullable=False),
sa.Column('expires_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('contact_id', sa.String(length=36), nullable=True),
sa.Column('organization_id', sa.String(length=36), nullable=True),
sa.Column('lead_id', sa.String(length=36), nullable=True),
sa.Column('owner_id', sa.String(length=36), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['contact_id'], ['crm_contacts.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['lead_id'], ['crm_leads.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['organization_id'], ['crm_organizations.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['owner_id'], ['crm_users.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('tenant_id', 'number', name='uq_crm_quotes_tenant_number')
)
op.create_index(op.f('ix_crm_quotes_tenant_id'), 'crm_quotes', ['tenant_id'], unique=False)
op.create_index('ix_crm_quotes_tenant_status', 'crm_quotes', ['tenant_id', 'status'], unique=False)
op.create_table('crm_quote_items',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('tenant_id', sa.String(length=36), nullable=False),
sa.Column('quote_id', sa.String(length=36), nullable=False),
sa.Column('product_id', sa.String(length=36), nullable=True),
sa.Column('name', sa.String(length=200), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('quantity', sa.Numeric(precision=12, scale=2), nullable=False),
sa.Column('unit_price', sa.Numeric(precision=14, scale=2), nullable=False),
sa.Column('tax_rate', sa.Numeric(precision=6, scale=3), nullable=False),
sa.Column('line_total', sa.Numeric(precision=14, scale=2), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['product_id'], ['crm_products.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['quote_id'], ['crm_quotes.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['crm_tenants.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_crm_quote_items_tenant_id'), 'crm_quote_items', ['tenant_id'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_crm_quote_items_tenant_id'), table_name='crm_quote_items')
op.drop_table('crm_quote_items')
op.drop_index('ix_crm_quotes_tenant_status', table_name='crm_quotes')
op.drop_index(op.f('ix_crm_quotes_tenant_id'), table_name='crm_quotes')
op.drop_table('crm_quotes')
op.drop_index(op.f('ix_crm_activities_tenant_id'), table_name='crm_activities')
op.drop_index('ix_crm_activities_tenant_due', table_name='crm_activities')
op.drop_index(op.f('ix_crm_activities_organization_id'), table_name='crm_activities')
op.drop_index(op.f('ix_crm_activities_lead_id'), table_name='crm_activities')
op.drop_index(op.f('ix_crm_activities_contact_id'), table_name='crm_activities')
op.drop_table('crm_activities')
op.drop_index('ix_crm_leads_tenant_status', table_name='crm_leads')
op.drop_index('ix_crm_leads_tenant_stage', table_name='crm_leads')
op.drop_index('ix_crm_leads_tenant_owner', table_name='crm_leads')
op.drop_index(op.f('ix_crm_leads_tenant_id'), table_name='crm_leads')
op.drop_index(op.f('ix_crm_leads_organization_id'), table_name='crm_leads')
op.drop_index(op.f('ix_crm_leads_contact_id'), table_name='crm_leads')
op.drop_table('crm_leads')
op.drop_index('ix_crm_contacts_tenant_score', table_name='crm_contacts')
op.drop_index('ix_crm_contacts_tenant_name', table_name='crm_contacts')
op.drop_index(op.f('ix_crm_contacts_tenant_id'), table_name='crm_contacts')
op.drop_index(op.f('ix_crm_contacts_organization_id'), table_name='crm_contacts')
op.drop_table('crm_contacts')
op.drop_index(op.f('ix_crm_stages_tenant_id'), table_name='crm_stages')
op.drop_index('ix_crm_stages_pipeline_position', table_name='crm_stages')
op.drop_table('crm_stages')
op.drop_index('ix_crm_organizations_tenant_name', table_name='crm_organizations')
op.drop_index(op.f('ix_crm_organizations_tenant_id'), table_name='crm_organizations')
op.drop_table('crm_organizations')
op.drop_index('ix_crm_audit_tenant_entity', table_name='crm_audit_events')
op.drop_index('ix_crm_audit_tenant_created', table_name='crm_audit_events')
op.drop_table('crm_audit_events')
op.drop_index('ix_crm_users_tenant_role', table_name='crm_users')
op.drop_index(op.f('ix_crm_users_tenant_id'), table_name='crm_users')
op.drop_table('crm_users')
op.drop_index('ix_crm_products_tenant_name', table_name='crm_products')
op.drop_index(op.f('ix_crm_products_tenant_id'), table_name='crm_products')
op.drop_table('crm_products')
op.drop_index(op.f('ix_crm_pipelines_tenant_id'), table_name='crm_pipelines')
op.drop_table('crm_pipelines')
op.drop_index(op.f('ix_crm_lead_types_tenant_id'), table_name='crm_lead_types')
op.drop_table('crm_lead_types')
op.drop_index(op.f('ix_crm_lead_sources_tenant_id'), table_name='crm_lead_sources')
op.drop_table('crm_lead_sources')
op.drop_index('ix_crm_integration_events_delivery', table_name='crm_integration_events')
op.drop_table('crm_integration_events')
op.drop_index(op.f('ix_crm_integration_credentials_tenant_id'), table_name='crm_integration_credentials')
op.drop_table('crm_integration_credentials')
op.drop_table('crm_idempotency_records')
op.drop_index('ix_crm_external_links_local', table_name='crm_external_links')
op.drop_table('crm_external_links')
op.drop_table('crm_tenants')
# ### end Alembic commands ###