fix(form4): перенумеровать миграцию 1938 в 0055 и пересобрать _apply_form_cell поверх 0050 (СПОД, AURORA-1906)
Миграция 0050_form4_transfer_econ_calc -> 0055 (down 0054): номер 0050 занят на test. Тело v3._apply_form_cell = актуальное тело из 0050_form2_ahr_spod_column + только дельта AURORA-1938 (запрет ручной записи FORM_4 q*.transfer_econ); раньше копия опиралась на 0023 и откатывала СПОД. v_form4_sheet_sections после 0033 на test не менялась — тело без изменений. Добавлен тест на сохранение обеих правок в итоговом определении функции. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
37c2b96d61
commit
e75ed6d44f
@ -1,14 +1,14 @@
|
|||||||
from sql_migration import run_sql_file_migration
|
from sql_migration import run_sql_file_migration
|
||||||
|
|
||||||
|
|
||||||
revision = "0050"
|
revision = "0055"
|
||||||
down_revision = "0049"
|
down_revision = "0054"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> 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:
|
def downgrade() -> None:
|
||||||
@ -11,6 +11,9 @@
|
|||||||
-- Буквальный Excel-перенос (ещё и минус transfer_q2 для Q1 / transfer_q3 для Q2)
|
-- Буквальный Excel-перенос (ещё и минус transfer_q2 для Q1 / transfer_q3 для Q2)
|
||||||
-- задваивал бы вычитание — residual_after_actual_QN уже его учитывает.
|
-- задваивал бы вычитание — 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 больше нельзя писать напрямую —
|
-- 2) v3._apply_form_cell: для FORM_4 q*.transfer_econ больше нельзя писать напрямую —
|
||||||
-- поле стало вычисляемым, как residual_after_actual.
|
-- поле стало вычисляемым, как residual_after_actual.
|
||||||
|
|
||||||
@ -900,7 +903,7 @@ BEGIN
|
|||||||
ELSE
|
ELSE
|
||||||
RAISE EXCEPTION 'unknown_column: %', p_column;
|
RAISE EXCEPTION 'unknown_column: %', p_column;
|
||||||
END CASE;
|
END CASE;
|
||||||
|
|
||||||
IF (v_target_type = 'NUMERIC' OR v_target_type = 'INTEGER') AND p_value = '""'::jsonb THEN
|
IF (v_target_type = 'NUMERIC' OR v_target_type = 'INTEGER') AND p_value = '""'::jsonb THEN
|
||||||
p_value := '"0"'::jsonb;
|
p_value := '"0"'::jsonb;
|
||||||
END IF;
|
END IF;
|
||||||
@ -940,7 +943,7 @@ BEGIN
|
|||||||
IF (v_parts[1]='fact_q1' AND v_parts[2] NOT IN ('jan','feb','mar')) OR
|
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_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_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;
|
RAISE EXCEPTION 'unknown_column: % (месяц вне квартала)', p_column;
|
||||||
END IF;
|
END IF;
|
||||||
v_target_col := 'actual_' || v_parts[2];
|
v_target_col := 'actual_' || v_parts[2];
|
||||||
@ -949,7 +952,7 @@ BEGIN
|
|||||||
RAISE EXCEPTION 'unknown_column: %', p_column;
|
RAISE EXCEPTION 'unknown_column: %', p_column;
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
|
|
||||||
IF v_target_type = 'NUMERIC' AND p_value = '""'::jsonb THEN
|
IF v_target_type = 'NUMERIC' AND p_value = '""'::jsonb THEN
|
||||||
p_value := '"0"'::jsonb;
|
p_value := '"0"'::jsonb;
|
||||||
END IF;
|
END IF;
|
||||||
@ -1228,9 +1231,9 @@ BEGIN
|
|||||||
|
|
||||||
ELSIF v_key_kind = 'line_quarter' THEN
|
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_q IS NULL THEN RAISE EXCEPTION 'internal: quarter not set for %', p_column; END IF;
|
||||||
|
|
||||||
IF v_target_type = 'DATE' THEN
|
IF v_target_type = 'DATE' THEN
|
||||||
|
|
||||||
v_sql := format(
|
v_sql := format(
|
||||||
'INSERT INTO v3.%1$I (line_id, quarter, %2$I) VALUES ($1, $2, to_date($3->>0, ''DD.MM.YYYY'')) '
|
'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',
|
|| 'ON CONFLICT (line_id, quarter) DO UPDATE SET %2$I = EXCLUDED.%2$I',
|
||||||
@ -1245,12 +1248,12 @@ BEGIN
|
|||||||
);
|
);
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
|
|
||||||
EXECUTE v_sql USING p_line_id, v_q, p_value;
|
EXECUTE v_sql USING p_line_id, v_q, p_value;
|
||||||
|
|
||||||
ELSIF v_key_kind = 'line_actor' THEN
|
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_actor IS NULL THEN RAISE EXCEPTION 'internal: actor not set for %', p_column; END IF;
|
||||||
|
|
||||||
IF v_target_type = 'DATE' THEN
|
IF v_target_type = 'DATE' THEN
|
||||||
v_sql := format(
|
v_sql := format(
|
||||||
'INSERT INTO v3.%1$I (line_id, actor, %2$I) VALUES ($1, $2, to_date($3->>0, ''DD.MM.YYYY'')) '
|
'INSERT INTO v3.%1$I (line_id, actor, %2$I) VALUES ($1, $2, to_date($3->>0, ''DD.MM.YYYY'')) '
|
||||||
@ -231,3 +231,34 @@ def test_form4_ahr_transfer_econ_cell_is_read_only(client, admin_tokens, auth_he
|
|||||||
payload = response.json()
|
payload = response.json()
|
||||||
assert "message" in payload
|
assert "message" in payload
|
||||||
assert "computed_field" in payload["message"] or "transfer_econ" in payload["message"]
|
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) потерян"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user