182 lines
12 KiB
Python
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")
|