diff --git a/api/alembic/versions/0050_form4_transfer_econ_calc.py b/api/alembic/versions/0055_form4_transfer_econ_calc.py similarity index 58% rename from api/alembic/versions/0050_form4_transfer_econ_calc.py rename to api/alembic/versions/0055_form4_transfer_econ_calc.py index 6ce72dd..78d6c44 100644 --- a/api/alembic/versions/0050_form4_transfer_econ_calc.py +++ b/api/alembic/versions/0055_form4_transfer_econ_calc.py @@ -1,14 +1,14 @@ from sql_migration import run_sql_file_migration -revision = "0050" -down_revision = "0049" +revision = "0055" +down_revision = "0054" branch_labels = None depends_on = None def upgrade() -> None: - run_sql_file_migration("0050_form4_transfer_econ_calc.sql") + run_sql_file_migration("0055_form4_transfer_econ_calc.sql") def downgrade() -> None: diff --git a/api/alembic/versions/sql/0050_form4_transfer_econ_calc.sql b/api/alembic/versions/sql/0055_form4_transfer_econ_calc.sql similarity index 99% rename from api/alembic/versions/sql/0050_form4_transfer_econ_calc.sql rename to api/alembic/versions/sql/0055_form4_transfer_econ_calc.sql index 73e2b20..f4132f3 100644 --- a/api/alembic/versions/sql/0050_form4_transfer_econ_calc.sql +++ b/api/alembic/versions/sql/0055_form4_transfer_econ_calc.sql @@ -11,6 +11,9 @@ -- Буквальный Excel-перенос (ещё и минус transfer_q2 для Q1 / transfer_q3 для Q2) -- задваивал бы вычитание — residual_after_actual_QN уже его учитывает. -- +-- 0055: пересобрано поверх 0050_form2_ahr_spod_column (AURORA-1906): тело _apply_form_cell = актуальное +-- на test + только дельта AURORA-1938 (v_form4_sheet_sections на test после 0033 не менялась). +-- -- 2) v3._apply_form_cell: для FORM_4 q*.transfer_econ больше нельзя писать напрямую — -- поле стало вычисляемым, как residual_after_actual. @@ -900,7 +903,7 @@ BEGIN ELSE RAISE EXCEPTION 'unknown_column: %', p_column; END CASE; - + IF (v_target_type = 'NUMERIC' OR v_target_type = 'INTEGER') AND p_value = '""'::jsonb THEN p_value := '"0"'::jsonb; END IF; @@ -940,7 +943,7 @@ BEGIN IF (v_parts[1]='fact_q1' AND v_parts[2] NOT IN ('jan','feb','mar')) OR (v_parts[1]='fact_q2' AND v_parts[2] NOT IN ('apr','may','jun')) OR (v_parts[1]='fact_q3' AND v_parts[2] NOT IN ('jul','aug','sep')) OR - (v_parts[1]='fact_q4' AND v_parts[2] NOT IN ('oct','nov','dec')) THEN + (v_parts[1]='fact_q4' AND v_parts[2] NOT IN ('oct','nov','dec','spod')) THEN RAISE EXCEPTION 'unknown_column: % (месяц вне квартала)', p_column; END IF; v_target_col := 'actual_' || v_parts[2]; @@ -949,7 +952,7 @@ BEGIN RAISE EXCEPTION 'unknown_column: %', p_column; END IF; - + IF v_target_type = 'NUMERIC' AND p_value = '""'::jsonb THEN p_value := '"0"'::jsonb; END IF; @@ -1228,9 +1231,9 @@ BEGIN ELSIF v_key_kind = 'line_quarter' THEN IF v_q IS NULL THEN RAISE EXCEPTION 'internal: quarter not set for %', p_column; END IF; - + IF v_target_type = 'DATE' THEN - + v_sql := format( 'INSERT INTO v3.%1$I (line_id, quarter, %2$I) VALUES ($1, $2, to_date($3->>0, ''DD.MM.YYYY'')) ' || 'ON CONFLICT (line_id, quarter) DO UPDATE SET %2$I = EXCLUDED.%2$I', @@ -1245,12 +1248,12 @@ BEGIN ); END IF; - + EXECUTE v_sql USING p_line_id, v_q, p_value; ELSIF v_key_kind = 'line_actor' THEN IF v_actor IS NULL THEN RAISE EXCEPTION 'internal: actor not set for %', p_column; END IF; - + IF v_target_type = 'DATE' THEN v_sql := format( 'INSERT INTO v3.%1$I (line_id, actor, %2$I) VALUES ($1, $2, to_date($3->>0, ''DD.MM.YYYY'')) ' diff --git a/api/tests/integration/test_form4_transfer_econ.py b/api/tests/integration/test_form4_transfer_econ.py index a4a06de..3fcd487 100644 --- a/api/tests/integration/test_form4_transfer_econ.py +++ b/api/tests/integration/test_form4_transfer_econ.py @@ -231,3 +231,34 @@ def test_form4_ahr_transfer_econ_cell_is_read_only(client, admin_tokens, auth_he payload = response.json() assert "message" in payload assert "computed_field" in payload["message"] or "transfer_econ" in payload["message"] + + +async def test_apply_form_cell_keeps_aurora_1906_spod_and_aurora_1938_guard(): + """Регрессия на порядок миграций: 0055 (AURORA-1938) пересобирает + v3._apply_form_cell поверх 0050_form2_ahr_spod_column (AURORA-1906), а не + поверх старого тела 0023 — иначе CREATE OR REPLACE тихо откатил бы СПОД. + Проверяем итоговое определение функции в БД: в нём одновременно есть + разрешение fact_q4.spod (1906) и запрет ручной записи transfer_econ (1938).""" + from sqlalchemy import text + from sqlalchemy.ext.asyncio import create_async_engine + from sqlalchemy.pool import NullPool + + from src.core.config import settings + + engine = create_async_engine(settings.DATABASE_URL, poolclass=NullPool) + try: + async with engine.connect() as conn: + definition = ( + await conn.execute( + text( + "SELECT pg_get_functiondef(p.oid) FROM pg_proc p " + "JOIN pg_namespace n ON n.oid = p.pronamespace " + "WHERE n.nspname = 'v3' AND p.proname = '_apply_form_cell'" + ) + ) + ).scalar_one() + finally: + await engine.dispose() + + assert "'oct','nov','dec','spod'" in definition, "AURORA-1906 (СПОД) откачен" + assert "v_field = 'transfer_econ'" in definition, "AURORA-1938 (computed transfer_econ) потерян"