"""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")