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

143 lines
7.8 KiB
Python

"""services phase 4A due date engine foundation
Revision ID: 20260504_due_date_engine_foundation
Revises: 20260503_assurance_review_partner
Create Date: 2026-05-04
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "20260504_due_date_engine_foundation"
down_revision: Union[str, None] = "20260503_assurance_review_partner"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def _insp():
return sa.inspect(op.get_bind())
def _has_table(table_name: str) -> bool:
return table_name in _insp().get_table_names()
def _has_column(table_name: str, column_name: str) -> bool:
if not _has_table(table_name):
return False
return any(c["name"] == column_name for c in _insp().get_columns(table_name))
def _has_index(table_name: str, index_name: str) -> bool:
if not _has_table(table_name):
return False
return any(ix.get("name") == index_name for ix in _insp().get_indexes(table_name))
def _create_index_if_missing(table_name: str, index_name: str, columns: list[str]) -> None:
if _has_table(table_name) and not _has_index(table_name, index_name):
op.create_index(index_name, table_name, columns)
def upgrade() -> None:
if not _has_table("service_due_date_rules"):
op.create_table(
"service_due_date_rules",
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
sa.Column("service_catalogue_id", sa.Integer(), nullable=False),
sa.Column("rule_name", sa.String(length=150), nullable=False),
sa.Column("period_type", sa.String(length=30), nullable=False, server_default="yearly"),
sa.Column("due_year_basis", sa.String(length=40), nullable=False, server_default="assessment_year_start"),
sa.Column("due_day", sa.Integer(), nullable=True),
sa.Column("due_month", sa.Integer(), nullable=True),
sa.Column("due_month_offset", sa.Integer(), nullable=False, server_default="0"),
sa.Column("days_offset_after_event", sa.Integer(), nullable=True),
sa.Column("sort_order", sa.Integer(), nullable=False, server_default="100"),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("remarks", 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, server_default=sa.func.now()),
sa.Column("updated_at_utc", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
sa.ForeignKeyConstraint(["service_catalogue_id"], ["service_catalogues.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["created_by_user_id"], ["users.id"]),
sa.ForeignKeyConstraint(["updated_by_user_id"], ["users.id"]),
sa.UniqueConstraint("service_catalogue_id", "rule_name", name="uq_service_due_date_rules_catalogue_name"),
)
_create_index_if_missing("service_due_date_rules", "ix_service_due_date_rules_service_catalogue_id", ["service_catalogue_id"])
_create_index_if_missing("service_due_date_rules", "ix_service_due_date_rules_period_type", ["period_type"])
_create_index_if_missing("service_due_date_rules", "ix_service_due_date_rules_is_active", ["is_active"])
if not _has_table("service_due_date_extensions"):
op.create_table(
"service_due_date_extensions",
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
sa.Column("tenant_id", sa.Integer(), nullable=False),
sa.Column("service_catalogue_id", sa.Integer(), nullable=False),
sa.Column("due_date_rule_id", sa.Integer(), nullable=True),
sa.Column("financial_year", sa.String(length=9), nullable=False),
sa.Column("assessment_year", sa.String(length=9), nullable=True),
sa.Column("period_label", sa.String(length=30), nullable=True),
sa.Column("previous_due_date", sa.Date(), nullable=True),
sa.Column("extended_due_date", sa.Date(), nullable=False),
sa.Column("extension_sequence", sa.Integer(), nullable=False, server_default="1"),
sa.Column("notification_reference", sa.String(length=200), nullable=True),
sa.Column("notification_date", sa.Date(), nullable=True),
sa.Column("remarks", 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, server_default=sa.func.now()),
sa.Column("updated_at_utc", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["service_catalogue_id"], ["service_catalogues.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["due_date_rule_id"], ["service_due_date_rules.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["created_by_user_id"], ["users.id"]),
sa.ForeignKeyConstraint(["updated_by_user_id"], ["users.id"]),
)
for index_name, columns in [
("ix_service_due_date_extensions_tenant_id", ["tenant_id"]),
("ix_service_due_date_extensions_service_catalogue_id", ["service_catalogue_id"]),
("ix_service_due_date_extensions_due_date_rule_id", ["due_date_rule_id"]),
("ix_service_due_date_extensions_financial_year", ["financial_year"]),
("ix_service_due_date_extensions_assessment_year", ["assessment_year"]),
("ix_service_due_date_extensions_period_label", ["period_label"]),
("ix_service_due_date_extensions_extended_due_date", ["extended_due_date"]),
]:
_create_index_if_missing("service_due_date_extensions", index_name, columns)
subscription_columns = [
("due_date_rule_id", sa.Column("due_date_rule_id", sa.Integer(), nullable=True)),
("original_due_date", sa.Column("original_due_date", sa.Date(), nullable=True)),
("current_due_date", sa.Column("current_due_date", sa.Date(), nullable=True)),
("due_date_source", sa.Column("due_date_source", sa.String(length=30), nullable=True)),
]
for column_name, column in subscription_columns:
if not _has_column("client_service_subscriptions", column_name):
op.add_column("client_service_subscriptions", column)
for index_name, columns in [
("ix_client_service_subscriptions_due_date_rule_id", ["due_date_rule_id"]),
("ix_client_service_subscriptions_original_due_date", ["original_due_date"]),
("ix_client_service_subscriptions_current_due_date", ["current_due_date"]),
]:
_create_index_if_missing("client_service_subscriptions", index_name, columns)
def downgrade() -> None:
for index_name in [
"ix_client_service_subscriptions_current_due_date",
"ix_client_service_subscriptions_original_due_date",
"ix_client_service_subscriptions_due_date_rule_id",
]:
if _has_index("client_service_subscriptions", index_name):
op.drop_index(index_name, table_name="client_service_subscriptions")
for column_name in ["due_date_source", "current_due_date", "original_due_date", "due_date_rule_id"]:
if _has_column("client_service_subscriptions", column_name):
op.drop_column("client_service_subscriptions", column_name)
if _has_table("service_due_date_extensions"):
op.drop_table("service_due_date_extensions")
if _has_table("service_due_date_rules"):
op.drop_table("service_due_date_rules")