Files
arrr-erp/alembic/versions/20260811_normal_review_separation.py
T
2026-08-11 09:05:09 +05:30

113 lines
6.8 KiB
Python

"""separate normal operational review from AQMM review
Revision ID: 20260811_normal_review
Revises: 20260808_task_role_eligibility
Create Date: 2026-08-11
"""
from alembic import op
import sqlalchemy as sa
revision = "20260811_normal_review"
down_revision = "20260808_task_role_eligibility"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column("service_default_task_templates", sa.Column("normal_review_role", sa.String(length=30), nullable=True))
op.add_column("firm_service_task_templates", sa.Column("normal_review_role", sa.String(length=30), nullable=True))
op.add_column("client_service_task_instances", sa.Column("normal_review_required", sa.Boolean(), nullable=False, server_default=sa.false()))
op.add_column("client_service_task_instances", sa.Column("normal_review_role", sa.String(length=30), nullable=True))
op.add_column("client_service_task_instances", sa.Column("normal_review_status", sa.String(length=30), nullable=False, server_default="not_required"))
op.add_column("client_service_task_instances", sa.Column("normal_review_note", sa.Text(), nullable=True))
op.add_column("client_service_task_instances", sa.Column("normal_reviewed_by_user_id", sa.Integer(), nullable=True))
op.add_column("client_service_task_instances", sa.Column("normal_reviewed_at_utc", sa.DateTime(timezone=True), nullable=True))
op.create_foreign_key(
"fk_task_instance_normal_reviewed_by",
"client_service_task_instances",
"users",
["normal_reviewed_by_user_id"],
["id"],
ondelete="SET NULL",
)
op.create_index("ix_task_instance_normal_review_required", "client_service_task_instances", ["normal_review_required"])
op.create_index("ix_task_instance_normal_review_role", "client_service_task_instances", ["normal_review_role"])
op.create_index("ix_task_instance_normal_review_status", "client_service_task_instances", ["normal_review_status"])
op.create_index("ix_task_instance_normal_reviewed_by", "client_service_task_instances", ["normal_reviewed_by_user_id"])
# Preserve the old intent of requires_review while making the reviewer explicit.
# Reviewer inference follows task hierarchy: Staff -> Manager, Manager -> Partner.
role_case = """
CASE
WHEN NOT requires_review THEN NULL
WHEN lower(coalesce(default_role_name,'')) IN ('manager','branch manager')
AND lower(coalesce(eligible_role_names,'')) LIKE '%partner%' THEN 'partner'
WHEN lower(coalesce(default_role_name,'')) NOT IN ('manager','branch manager','partner')
AND (lower(coalesce(eligible_role_names,'')) LIKE '%manager%' OR lower(coalesce(eligible_role_names,'')) LIKE '%branch manager%') THEN 'manager'
WHEN lower(coalesce(eligible_role_names,'')) LIKE '%partner%'
AND lower(coalesce(eligible_role_names,'')) NOT LIKE '%manager%' THEN 'partner'
ELSE 'manager'
END
"""
op.execute(sa.text(f"UPDATE service_default_task_templates SET normal_review_role = {role_case} WHERE requires_review = TRUE"))
op.execute(sa.text(f"UPDATE firm_service_task_templates SET normal_review_role = {role_case} WHERE requires_review = TRUE"))
# Snapshot normal-review configuration into every existing engagement task.
op.execute(sa.text("""
UPDATE client_service_task_instances AS t
SET normal_review_required = f.requires_review,
normal_review_role = f.normal_review_role
FROM firm_service_task_templates AS f
WHERE t.firm_task_template_id = f.id
"""))
# Preserve prior generic approvals where they were already recorded in the legacy
# manager/partner fields. Otherwise completed/submitted work becomes Pending Review;
# unstarted work remains Not Ready. AQMM fields themselves are not changed.
op.execute(sa.text("""
UPDATE client_service_task_instances
SET normal_review_status = CASE
WHEN NOT normal_review_required THEN 'not_required'
WHEN normal_review_role IN ('manager','manager_or_partner') AND manager_review_status = 'reviewed' THEN 'reviewed'
WHEN normal_review_role = 'partner' AND partner_review_status = 'reviewed' THEN 'reviewed'
WHEN rework_status = 'open' THEN 'rework_required'
WHEN submitted_for_review_at_utc IS NOT NULL OR lower(coalesce(status,'')) = 'completed' THEN 'pending'
ELSE 'not_ready'
END,
normal_review_note = CASE
WHEN normal_review_role IN ('manager','manager_or_partner') AND manager_review_status IN ('reviewed','rework_required') THEN manager_review_note
WHEN normal_review_role = 'partner' AND partner_review_status IN ('reviewed','rework_required') THEN partner_review_note
ELSE NULL
END,
normal_reviewed_by_user_id = CASE
WHEN normal_review_role IN ('manager','manager_or_partner') AND manager_review_status = 'reviewed' THEN manager_reviewed_by_user_id
WHEN normal_review_role = 'partner' AND partner_review_status = 'reviewed' THEN partner_reviewed_by_user_id
ELSE NULL
END,
normal_reviewed_at_utc = CASE
WHEN normal_review_role IN ('manager','manager_or_partner') AND manager_review_status = 'reviewed' THEN manager_reviewed_at_utc
WHEN normal_review_role = 'partner' AND partner_review_status = 'reviewed' THEN partner_reviewed_at_utc
ELSE NULL
END
"""))
op.alter_column("client_service_task_instances", "normal_review_required", server_default=None)
op.alter_column("client_service_task_instances", "normal_review_status", server_default=None)
def downgrade() -> None:
op.drop_index("ix_task_instance_normal_reviewed_by", table_name="client_service_task_instances")
op.drop_index("ix_task_instance_normal_review_status", table_name="client_service_task_instances")
op.drop_index("ix_task_instance_normal_review_role", table_name="client_service_task_instances")
op.drop_index("ix_task_instance_normal_review_required", table_name="client_service_task_instances")
op.drop_constraint("fk_task_instance_normal_reviewed_by", "client_service_task_instances", type_="foreignkey")
op.drop_column("client_service_task_instances", "normal_reviewed_at_utc")
op.drop_column("client_service_task_instances", "normal_reviewed_by_user_id")
op.drop_column("client_service_task_instances", "normal_review_note")
op.drop_column("client_service_task_instances", "normal_review_status")
op.drop_column("client_service_task_instances", "normal_review_role")
op.drop_column("client_service_task_instances", "normal_review_required")
op.drop_column("firm_service_task_templates", "normal_review_role")
op.drop_column("service_default_task_templates", "normal_review_role")