Files
arrr-erp/alembic/versions/20260522_phase_b1_b2_billing.py
2026-06-20 15:01:44 +05:30

182 lines
12 KiB
Python

"""Phase B1 B2 firm billing and fee structure import
Revision ID: 20260522_phase_b1_b2_billing
Revises: 20260521_phase_6l_branch_timezone_attendance_rules
Create Date: 2026-05-22
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
revision = "20260522_phase_b1_b2_billing"
down_revision = "20260521_phase_6l_branch_timezone_attendance_rules"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_table(
"billing_settings",
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
sa.Column("tenant_id", sa.Integer(), nullable=False),
sa.Column("branch_id", sa.Integer(), nullable=True),
sa.Column("invoice_prefix", sa.String(length=40), nullable=False, server_default="INV"),
sa.Column("next_invoice_no", sa.Integer(), nullable=False, server_default="1"),
sa.Column("padding", sa.Integer(), nullable=False, server_default="4"),
sa.Column("default_gst_rate", sa.Numeric(5, 2), nullable=False, server_default="18.00"),
sa.Column("default_tax_type", sa.String(length=20), nullable=False, server_default="CGST_SGST"),
sa.Column("legal_name", sa.String(length=200), nullable=True),
sa.Column("gstin", sa.String(length=20), nullable=True),
sa.Column("billing_address", sa.Text(), nullable=True),
sa.Column("bank_details", sa.Text(), nullable=True),
sa.Column("terms", sa.Text(), nullable=True),
sa.Column("footer_note", sa.Text(), nullable=True),
sa.Column("created_at_utc", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at_utc", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["branch_id"], ["branches.id"], ondelete="SET NULL"),
sa.UniqueConstraint("tenant_id", "branch_id", name="uq_billing_settings_tenant_branch"),
)
op.create_index("ix_billing_settings_tenant_id", "billing_settings", ["tenant_id"])
op.create_index("ix_billing_settings_branch_id", "billing_settings", ["branch_id"])
op.create_table(
"billing_fee_groups",
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
sa.Column("tenant_id", sa.Integer(), nullable=False),
sa.Column("branch_id", sa.Integer(), nullable=True),
sa.Column("client_id", sa.Integer(), nullable=False),
sa.Column("partner_id", sa.Integer(), nullable=True),
sa.Column("group_code", sa.String(length=80), nullable=False),
sa.Column("group_name", sa.String(length=200), nullable=False),
sa.Column("billing_mode", sa.String(length=20), nullable=False, server_default="PACKAGE"),
sa.Column("frequency", sa.String(length=20), nullable=False, server_default="Monthly"),
sa.Column("fee_amount", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("gst_rate", sa.Numeric(5, 2), nullable=False, server_default="18.00"),
sa.Column("tax_type", sa.String(length=20), nullable=False, server_default="CGST_SGST"),
sa.Column("effective_from", sa.Date(), nullable=True),
sa.Column("effective_to", sa.Date(), nullable=True),
sa.Column("auto_generate", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("notes", sa.Text(), nullable=True),
sa.Column("created_by_user_id", sa.Integer(), nullable=True),
sa.Column("updated_by_user_id", sa.Integer(), nullable=True),
sa.Column("created_at_utc", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at_utc", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["branch_id"], ["branches.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["client_id"], ["clients.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["partner_id"], ["users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["created_by_user_id"], ["users.id"]),
sa.ForeignKeyConstraint(["updated_by_user_id"], ["users.id"]),
sa.UniqueConstraint("tenant_id", "group_code", name="uq_billing_fee_groups_tenant_code"),
)
op.create_index("ix_billing_fee_groups_tenant_id", "billing_fee_groups", ["tenant_id"])
op.create_index("ix_billing_fee_groups_branch_id", "billing_fee_groups", ["branch_id"])
op.create_index("ix_billing_fee_groups_client_id", "billing_fee_groups", ["client_id"])
op.create_index("ix_billing_fee_groups_partner_id", "billing_fee_groups", ["partner_id"])
op.create_index("ix_billing_fee_groups_group_code", "billing_fee_groups", ["group_code"])
op.create_index("ix_billing_fee_groups_is_active", "billing_fee_groups", ["is_active"])
op.create_table(
"billing_invoices",
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
sa.Column("tenant_id", sa.Integer(), nullable=False),
sa.Column("branch_id", sa.Integer(), nullable=True),
sa.Column("client_id", sa.Integer(), nullable=False),
sa.Column("engagement_id", sa.Integer(), nullable=True),
sa.Column("invoice_no", sa.String(length=60), nullable=False),
sa.Column("invoice_date", sa.Date(), nullable=False),
sa.Column("due_date", sa.Date(), nullable=True),
sa.Column("billing_period_from", sa.Date(), nullable=True),
sa.Column("billing_period_to", sa.Date(), nullable=True),
sa.Column("tax_type", sa.String(length=20), nullable=False, server_default="CGST_SGST"),
sa.Column("subtotal", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("discount_amount", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("taxable_amount", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("cgst_amount", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("sgst_amount", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("igst_amount", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("round_off", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("total_amount", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("status", sa.String(length=20), nullable=False, server_default="DRAFT"),
sa.Column("notes", sa.Text(), nullable=True),
sa.Column("terms", sa.Text(), nullable=True),
sa.Column("created_by_user_id", sa.Integer(), nullable=True),
sa.Column("approved_by_user_id", sa.Integer(), nullable=True),
sa.Column("posted_at_utc", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at_utc", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at_utc", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["branch_id"], ["branches.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["client_id"], ["clients.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["engagement_id"], ["client_service_subscriptions.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["created_by_user_id"], ["users.id"]),
sa.ForeignKeyConstraint(["approved_by_user_id"], ["users.id"]),
sa.UniqueConstraint("tenant_id", "invoice_no", name="uq_billing_invoices_tenant_invoice_no"),
)
op.create_index("ix_billing_invoices_tenant_id", "billing_invoices", ["tenant_id"])
op.create_index("ix_billing_invoices_branch_id", "billing_invoices", ["branch_id"])
op.create_index("ix_billing_invoices_client_id", "billing_invoices", ["client_id"])
op.create_index("ix_billing_invoices_engagement_id", "billing_invoices", ["engagement_id"])
op.create_index("ix_billing_invoices_invoice_no", "billing_invoices", ["invoice_no"])
op.create_index("ix_billing_invoices_invoice_date", "billing_invoices", ["invoice_date"])
op.create_index("ix_billing_invoices_due_date", "billing_invoices", ["due_date"])
op.create_index("ix_billing_invoices_status", "billing_invoices", ["status"])
op.create_table(
"billing_fee_group_services",
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
sa.Column("fee_group_id", sa.Integer(), nullable=False),
sa.Column("service_id", sa.Integer(), nullable=False),
sa.Column("line_description", sa.String(length=500), nullable=True),
sa.Column("allocation_type", sa.String(length=20), nullable=False, server_default="Included"),
sa.Column("line_amount", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("percentage", sa.Numeric(5, 2), nullable=True),
sa.Column("sort_order", sa.Integer(), nullable=False, server_default="1"),
sa.ForeignKeyConstraint(["fee_group_id"], ["billing_fee_groups.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["service_id"], ["service_catalogues.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("fee_group_id", "service_id", name="uq_billing_fee_group_services_group_service"),
)
op.create_index("ix_billing_fee_group_services_fee_group_id", "billing_fee_group_services", ["fee_group_id"])
op.create_index("ix_billing_fee_group_services_service_id", "billing_fee_group_services", ["service_id"])
op.create_table(
"billing_invoice_lines",
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
sa.Column("invoice_id", sa.Integer(), nullable=False),
sa.Column("service_id", sa.Integer(), nullable=True),
sa.Column("engagement_id", sa.Integer(), nullable=True),
sa.Column("fee_group_id", sa.Integer(), nullable=True),
sa.Column("description", sa.String(length=500), nullable=False),
sa.Column("billing_period_from", sa.Date(), nullable=True),
sa.Column("billing_period_to", sa.Date(), nullable=True),
sa.Column("quantity", sa.Numeric(12, 2), nullable=False, server_default="1.00"),
sa.Column("rate", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("discount_amount", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("taxable_amount", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("gst_rate", sa.Numeric(5, 2), nullable=False, server_default="18.00"),
sa.Column("cgst_amount", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("sgst_amount", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("igst_amount", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("line_total", sa.Numeric(14, 2), nullable=False, server_default="0.00"),
sa.Column("sort_order", sa.Integer(), nullable=False, server_default="1"),
sa.ForeignKeyConstraint(["invoice_id"], ["billing_invoices.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["service_id"], ["service_catalogues.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["engagement_id"], ["client_service_subscriptions.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["fee_group_id"], ["billing_fee_groups.id"], ondelete="SET NULL"),
)
op.create_index("ix_billing_invoice_lines_invoice_id", "billing_invoice_lines", ["invoice_id"])
op.create_index("ix_billing_invoice_lines_service_id", "billing_invoice_lines", ["service_id"])
op.create_index("ix_billing_invoice_lines_engagement_id", "billing_invoice_lines", ["engagement_id"])
op.create_index("ix_billing_invoice_lines_fee_group_id", "billing_invoice_lines", ["fee_group_id"])
def downgrade() -> None:
op.drop_table("billing_invoice_lines")
op.drop_table("billing_fee_group_services")
op.drop_table("billing_invoices")
op.drop_table("billing_fee_groups")
op.drop_table("billing_settings")