diff --git a/api/alembic/versions/sql/0063_form2_transfer_econ_calc.sql b/api/alembic/versions/sql/0063_form2_transfer_econ_calc.sql index f6c27aa..a230773 100644 --- a/api/alembic/versions/sql/0063_form2_transfer_econ_calc.sql +++ b/api/alembic/versions/sql/0063_form2_transfer_econ_calc.sql @@ -1967,3 +1967,239 @@ BEGIN END; $function$ ; + +-- 4) AURORA-1938 / #335: Свод 2 «РФ» (FORM_2). v3.v_svod2_rf (тело - версия 0057): q1..q4.transfer_to_economy +-- берётся не из budget_line_quarter.transfer_to_economy, а считается по формулам п. 1 из тех же +-- таблиц строки (план+секвестр+резерв, корректировки, пересмотры плана, входящие переносы, факт). +-- v_svod2_go / v_svod2_go_export / summary_repository: GO = FORM_1 и FORM_4, у FORM_1 своё поле, +-- у FORM_4 фонд сырой (откат 0055 в п. 3) - не менялись. + +CREATE OR REPLACE FUNCTION v3.v_svod2_rf( + p_year integer, + p_org_unit_ids integer[] DEFAULT NULL, + p_offset integer DEFAULT NULL, + p_limit integer DEFAULT NULL, + p_sort_by varchar DEFAULT 'org_name', + p_sort_direction varchar DEFAULT 'asc', + p_search text DEFAULT NULL, + p_smeta_type text DEFAULT NULL, + p_smeta_direction text DEFAULT NULL, + p_org_name text DEFAULT NULL, + p_compact boolean DEFAULT false +) +RETURNS TABLE( + source_form varchar, source_id integer, source_org_unit_id integer, + org_name varchar, smeta_type varchar, + source_line_id integer, sort_order bigint, data jsonb, total_count bigint +) +-- jit off: оценка стоимости большого плана включает JIT-компиляцию на сотни миллисекунд, а выигрыша нет. +LANGUAGE sql STABLE SET search_path TO 'v3', 'pg_catalog' SET jit TO off +AS $function$ +WITH empty_sections AS MATERIALIZED ( + SELECT + (to_jsonb(jsonb_populate_record(NULL::v3.plan, '{}'::jsonb)) - 'line_id') + || jsonb_build_object('plan_year', NULL) AS plan, + (to_jsonb(jsonb_populate_record(NULL::v3.sequestration, '{}'::jsonb)) + - 'id' - 'line_id' - 'actor') || jsonb_build_object('adj_year', NULL) AS sequestration, + (to_jsonb(jsonb_populate_record(NULL::v3.reserve, '{}'::jsonb)) - 'line_id') + || jsonb_build_object('amount_year', NULL) AS reserve, + jsonb_build_object( + 'approved_q1', NULL, 'approved_q2', NULL, + 'approved_q3', NULL, 'approved_q4', NULL, + 'approved_year', NULL + ) AS approved, + to_jsonb(jsonb_populate_record(NULL::v3.contract_summary, '{}'::jsonb)) - 'line_id' AS contract_summary, + to_jsonb(jsonb_populate_record(NULL::v3.allocation, '{}'::jsonb)) - 'line_id' AS allocation, + to_jsonb(jsonb_populate_record(NULL::v3.collegial_approval, '{}'::jsonb)) - 'line_id' AS collegial, + to_jsonb(jsonb_populate_record(NULL::v3.ckk, '{}'::jsonb)) - 'line_id' AS ckk, + to_jsonb(jsonb_populate_record(NULL::v3.contract_detail, '{}'::jsonb)) - 'line_id' AS contract_detail, + to_jsonb(jsonb_populate_record(NULL::v3.booking, '{}'::jsonb)) - 'id' - 'line_id' AS booking, + to_jsonb(jsonb_populate_record(NULL::v3.budget_line_quarter, '{}'::jsonb)) + - 'line_id' - 'quarter' AS budget_quarter +), numbered_lines AS ( + SELECT el.*, + count(*) OVER () AS total_count, + row_number() OVER ( + ORDER BY + CASE WHEN p_sort_by = 'org_name' AND lower(p_sort_direction) = 'asc' + THEN el.org_name END ASC NULLS LAST, + CASE WHEN p_sort_by = 'org_name' AND lower(p_sort_direction) = 'desc' + THEN el.org_name END DESC NULLS LAST, + CASE WHEN p_sort_by = 'org_name' AND lower(p_sort_direction) = 'asc' + THEN el.item_id END ASC NULLS LAST, + CASE WHEN p_sort_by = 'org_name' AND lower(p_sort_direction) = 'desc' + THEN el.item_id END DESC NULLS LAST, + CASE WHEN p_sort_by = 'section_code' AND lower(p_sort_direction) = 'asc' + THEN el.section_code END ASC NULLS LAST, + CASE WHEN p_sort_by = 'section_code' AND lower(p_sort_direction) = 'desc' + THEN el.section_code END DESC NULLS LAST, + el.form_type_code, el.form_id, el.line_id + ) AS sort_order + FROM v3.svod2_lines('RF', p_year, p_org_unit_ids, + NULLIF(v3.svod2_casefold(v3.svod2_js_trim(p_search)), '')) el + WHERE (NULLIF(v3.svod2_js_trim(v3.svod2_casefold(p_smeta_type)), '') IS NULL + OR (el.smeta_type IS NOT NULL + AND v3.svod2_js_trim(v3.svod2_casefold(el.smeta_type)) + = NULLIF(v3.svod2_js_trim(v3.svod2_casefold(p_smeta_type)), ''))) + AND (NULLIF(v3.svod2_js_trim(v3.svod2_casefold(p_smeta_direction)), '') IS NULL + OR (el.smeta_direction IS NOT NULL + AND v3.svod2_js_trim(v3.svod2_casefold(el.smeta_direction)) + = NULLIF(v3.svod2_js_trim(v3.svod2_casefold(p_smeta_direction)), ''))) + AND (NULLIF(v3.svod2_js_trim(v3.svod2_casefold(p_org_name)), '') IS NULL + OR (el.org_name IS NOT NULL + AND v3.svod2_js_trim(v3.svod2_casefold(el.org_name)) + = NULLIF(v3.svod2_js_trim(v3.svod2_casefold(p_org_name)), ''))) +), page AS MATERIALIZED ( + SELECT * FROM numbered_lines + ORDER BY sort_order + LIMIT p_limit OFFSET COALESCE(p_offset, 0) +), booking_data AS ( + SELECT b.line_id, + jsonb_agg(to_jsonb(b) - 'id' - 'line_id' ORDER BY b.quarter, b.source) AS booking + FROM page pg + JOIN v3.booking b ON b.line_id = pg.line_id + GROUP BY b.line_id +) +SELECT x.source_form, x.source_id, x.source_org_unit_id, x.org_name, x.smeta_type, + x.source_line_id, x.sort_order, + CASE WHEN p_compact THEN v3.svod2_compact(x.full_data) ELSE x.full_data END AS data, + x.total_count +FROM ( + SELECT + pg.form_type_code AS source_form, pg.form_id AS source_id, + pg.org_unit_id AS source_org_unit_id, pg.org_name, pg.smeta_type, + pg.line_id AS source_line_id, pg.sort_order, + jsonb_build_object( + 'line_id', bl.id, + 'header', jsonb_build_object( + 'section_code', ei.section_code, 'item_id', ei.item_id, + 'num_group', ei.num_group_id, 'name', COALESCE(bl.name, ei.name), + 'expense_item_id', ei.id, 'justification', bl.justification, + 'internal_order', bl.internal_order, 'vsp_id', bl.vsp_id, + 'project_id', bl.project_id, 'direction', bl.direction, + 'smeta_direction', CASE bl.direction + WHEN 'Support' THEN 'Поддержка' + WHEN 'Development' THEN 'Развитие' + ELSE NULL + END, + 'source_form', pg.form_type_code, 'source_id', pg.form_id, + 'source_org_unit_id', pg.org_unit_id, 'org_name', pg.org_name, + 'smeta_type', pg.smeta_type), + 'plan', COALESCE( + (to_jsonb(pl) - 'line_id') || jsonb_build_object( + 'plan_year', COALESCE(pl.plan_q1, 0) + COALESCE(pl.plan_q2, 0) + + COALESCE(pl.plan_q3, 0) + COALESCE(pl.plan_q4, 0)), + e.plan || jsonb_build_object('plan_year', NULL)), + 'sequestration', jsonb_build_object( + 'DFIP', COALESCE( + (to_jsonb(sd) - 'id' - 'line_id' - 'actor') || jsonb_build_object( + 'adj_year', COALESCE(sd.adj_q1, 0) + COALESCE(sd.adj_q2, 0) + + COALESCE(sd.adj_q3, 0) + COALESCE(sd.adj_q4, 0)), + e.sequestration || jsonb_build_object('adj_year', NULL)), + 'SSP_GO', COALESCE( + (to_jsonb(ss) - 'id' - 'line_id' - 'actor') || jsonb_build_object( + 'adj_year', COALESCE(ss.adj_q1, 0) + COALESCE(ss.adj_q2, 0) + + COALESCE(ss.adj_q3, 0) + COALESCE(ss.adj_q4, 0)), + e.sequestration || jsonb_build_object('adj_year', NULL))), + 'reserve', COALESCE( + (to_jsonb(rv) - 'line_id') || jsonb_build_object( + 'amount_year', COALESCE(rv.amount_q1, 0) + COALESCE(rv.amount_q2, 0) + + COALESCE(rv.amount_q3, 0) + COALESCE(rv.amount_q4, 0)), + e.reserve || jsonb_build_object('amount_year', NULL)), + 'approved', CASE WHEN pl.line_id IS NULL AND sd.id IS NULL + AND ss.id IS NULL AND rv.line_id IS NULL + THEN e.approved + ELSE jsonb_build_object( + 'approved_q1', COALESCE(pl.plan_q1, 0) + COALESCE(sd.adj_q1, 0) + + COALESCE(ss.adj_q1, 0) + COALESCE(rv.amount_q1, 0), + 'approved_q2', COALESCE(pl.plan_q2, 0) + COALESCE(sd.adj_q2, 0) + + COALESCE(ss.adj_q2, 0) + COALESCE(rv.amount_q2, 0), + 'approved_q3', COALESCE(pl.plan_q3, 0) + COALESCE(sd.adj_q3, 0) + + COALESCE(ss.adj_q3, 0) + COALESCE(rv.amount_q3, 0), + 'approved_q4', COALESCE(pl.plan_q4, 0) + COALESCE(sd.adj_q4, 0) + + COALESCE(ss.adj_q4, 0) + COALESCE(rv.amount_q4, 0), + 'approved_year', COALESCE(pl.plan_q1, 0) + COALESCE(pl.plan_q2, 0) + + COALESCE(pl.plan_q3, 0) + COALESCE(pl.plan_q4, 0) + + COALESCE(sd.adj_q1, 0) + COALESCE(sd.adj_q2, 0) + + COALESCE(sd.adj_q3, 0) + COALESCE(sd.adj_q4, 0) + + COALESCE(ss.adj_q1, 0) + COALESCE(ss.adj_q2, 0) + + COALESCE(ss.adj_q3, 0) + COALESCE(ss.adj_q4, 0) + + COALESCE(rv.amount_q1, 0) + COALESCE(rv.amount_q2, 0) + + COALESCE(rv.amount_q3, 0) + COALESCE(rv.amount_q4, 0)) END, + 'contract_summary', COALESCE(to_jsonb(cs) - 'line_id', e.contract_summary), + 'allocation', COALESCE(to_jsonb(al) - 'line_id', e.allocation), + 'collegial', COALESCE(to_jsonb(ca) - 'line_id', e.collegial), + 'ckk', COALESCE(to_jsonb(ck) - 'line_id', e.ckk), + 'contract_detail', COALESCE(to_jsonb(cd) - 'line_id', e.contract_detail) || jsonb_build_object( + 'expenses_q1', COALESCE(cd.expenses_q1, ck.expenses_q1), + 'expenses_q2', COALESCE(cd.expenses_q2, ck.expenses_q2), + 'expenses_q3', COALESCE(cd.expenses_q3, ck.expenses_q3), + 'expenses_q4', COALESCE(cd.expenses_q4, ck.expenses_q4)), + 'booking', COALESCE(bk.booking, jsonb_build_array(e.booking)), + 'q1', COALESCE(to_jsonb(q1) - 'line_id' - 'quarter', e.budget_quarter) || jsonb_build_object( + 'transfer_to_economy', ec.econ_q1, + 'booking_amount', COALESCE(q1.booking_amount, + CASE WHEN ei.sheet = 'AHR' THEN ck.expenses_q1 + WHEN ck.expenses_q1 > 0 THEN ck.expenses_q1 + ELSE cd.expenses_q1 END)), + 'q2', COALESCE(to_jsonb(q2) - 'line_id' - 'quarter', e.budget_quarter) || jsonb_build_object( + 'transfer_to_economy', ec.econ_q2, + 'booking_amount', COALESCE(q2.booking_amount, + CASE WHEN ei.sheet = 'AHR' THEN ck.expenses_q2 + WHEN ck.expenses_q2 > 0 THEN ck.expenses_q2 + ELSE cd.expenses_q2 END), + 'plan_revision_eco_change', COALESCE(q2.target_change, q2.plan_revision_eco_change), + 'plan_revision_item_adj', COALESCE(q2.base_plan_correction, q2.plan_revision_item_adj)), + 'q3', COALESCE(to_jsonb(q3) - 'line_id' - 'quarter', e.budget_quarter) || jsonb_build_object( + 'transfer_to_economy', ec.econ_q3, + 'booking_amount', COALESCE(q3.booking_amount, + CASE WHEN ei.sheet = 'AHR' THEN ck.expenses_q3 + WHEN ck.expenses_q3 > 0 THEN ck.expenses_q3 + ELSE cd.expenses_q3 END), + 'plan_revision_eco_change', COALESCE(q3.target_change, q3.plan_revision_eco_change), + 'plan_revision_item_adj', COALESCE(q3.base_plan_correction, q3.plan_revision_item_adj)), + 'q4', COALESCE(to_jsonb(q4) - 'line_id' - 'quarter', e.budget_quarter) || jsonb_build_object( + 'transfer_to_economy', ec.econ_q4, + 'booking_amount', COALESCE(q4.booking_amount, + CASE WHEN ei.sheet = 'AHR' THEN ck.expenses_q4 + WHEN ck.expenses_q4 > 0 THEN ck.expenses_q4 + ELSE cd.expenses_q4 END), + 'plan_revision_eco_change', COALESCE(q4.target_change, q4.plan_revision_eco_change), + 'plan_revision_item_adj', COALESCE(q4.base_plan_correction, q4.plan_revision_item_adj)) + ) AS full_data, + pg.total_count + FROM page pg + JOIN v3.budget_line bl ON bl.id = pg.line_id + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN v3.contract_detail cd ON cd.line_id = bl.id + CROSS JOIN empty_sections e + LEFT JOIN v3.plan pl ON pl.line_id = bl.id + LEFT JOIN v3.sequestration sd ON sd.line_id = bl.id AND sd.actor = 'DFIP' + LEFT JOIN v3.sequestration ss ON ss.line_id = bl.id AND ss.actor = 'SSP_GO' + LEFT JOIN v3.reserve rv ON rv.line_id = bl.id + LEFT JOIN v3.contract_summary cs ON cs.line_id = bl.id + LEFT JOIN v3.allocation al ON al.line_id = bl.id + LEFT JOIN v3.collegial_approval ca ON ca.line_id = bl.id + LEFT JOIN v3.ckk ck ON ck.line_id = bl.id + LEFT JOIN booking_data bk ON bk.line_id = bl.id + LEFT JOIN v3.budget_line_quarter q1 ON q1.line_id = bl.id AND q1.quarter = 1 + LEFT JOIN v3.budget_line_quarter q2 ON q2.line_id = bl.id AND q2.quarter = 2 + LEFT JOIN v3.budget_line_quarter q3 ON q3.line_id = bl.id AND q3.quarter = 3 + LEFT JOIN v3.budget_line_quarter q4 ON q4.line_id = bl.id AND q4.quarter = 4 + -- AURORA-1938 / #335: фонд экономии FORM_2 считается, а не читается из transfer_to_economy + -- (формулы v_form2_sheet_sections: cp - скорректированный план, aq - факт квартала). + CROSS JOIN LATERAL ( + SELECT + (COALESCE(pl.plan_q1,0)+COALESCE(sd.adj_q1,0)+COALESCE(ss.adj_q1,0)+COALESCE(rv.amount_q1,0)+COALESCE(q1.adj_current,0)+COALESCE(q1.adj_ssp,0)+COALESCE(q1.adj_reserve,0)-(COALESCE(q1.actual_m1,0)+COALESCE(q1.actual_m2,0)+COALESCE(q1.actual_m3,0)) + -(COALESCE(q1.transfer_delay_acts,0)+COALESCE(q1.transfer_delay_procurement,0)+COALESCE(q1.transfer_economy_rf,0))-COALESCE(q1.transfer_to_q3,0)-COALESCE(q1.transfer_to_q4,0)) AS econ_q1, + (COALESCE(pl.plan_q2,0)+COALESCE(sd.adj_q2,0)+COALESCE(ss.adj_q2,0)+COALESCE(rv.amount_q2,0)+COALESCE(q1.transfer_to_q2,0)+COALESCE(q2.plan_revision_increase,0)+COALESCE(q2.plan_revision_sequester,0)+COALESCE(q2.base_plan_correction,0)+COALESCE(q2.adj_current,0)+COALESCE(q2.adj_ssp,0)+COALESCE(q2.adj_reserve,0)-(COALESCE(q2.actual_m1,0)+COALESCE(q2.actual_m2,0)+COALESCE(q2.actual_m3,0)) + -(COALESCE(q2.transfer_delay_acts,0)+COALESCE(q2.transfer_delay_procurement,0)+COALESCE(q2.transfer_economy_rf,0))-COALESCE(q2.transfer_to_q4,0)) AS econ_q2, + (COALESCE(pl.plan_q3,0)+COALESCE(sd.adj_q3,0)+COALESCE(ss.adj_q3,0)+COALESCE(rv.amount_q3,0)+COALESCE(q1.transfer_to_q3,0)+COALESCE(q2.transfer_to_q3,0)+COALESCE(q3.plan_revision_increase,0)+COALESCE(q3.plan_revision_sequester,0)+COALESCE(q3.base_plan_correction,0)+COALESCE(q3.adj_current,0)+COALESCE(q3.adj_ssp,0)+COALESCE(q3.adj_reserve,0)-(COALESCE(q3.actual_m1,0)+COALESCE(q3.actual_m2,0)+COALESCE(q3.actual_m3,0)) + -(COALESCE(q3.transfer_delay_acts,0)+COALESCE(q3.transfer_delay_procurement,0)+COALESCE(q3.transfer_economy_rf,0))) AS econ_q3, + (COALESCE(pl.plan_q4,0)+COALESCE(sd.adj_q4,0)+COALESCE(ss.adj_q4,0)+COALESCE(rv.amount_q4,0)+COALESCE(q1.transfer_to_q4,0)+COALESCE(q2.transfer_to_q4,0)+COALESCE(q3.transfer_to_q4,0)+COALESCE(q4.plan_revision_increase,0)+COALESCE(q4.plan_revision_sequester,0)+COALESCE(q4.base_plan_correction,0)+COALESCE(q4.adj_current,0)+COALESCE(q4.adj_ssp,0)+COALESCE(q4.adj_reserve,0) + -(COALESCE(q4.actual_m1,0)+COALESCE(q4.actual_m2,0)+COALESCE(q4.actual_m3,0)+COALESCE(q4.actual_spod,0))) AS econ_q4 + ) ec +) x +ORDER BY x.sort_order; +$function$; + diff --git a/api/tests/fixtures/svod2_legacy_golden.json b/api/tests/fixtures/svod2_legacy_golden.json index 1217f98..8d1c22a 100644 --- a/api/tests/fixtures/svod2_legacy_golden.json +++ b/api/tests/fixtures/svod2_legacy_golden.json @@ -1771,7 +1771,7 @@ } }, "RF-admin_all": { - "sha256": "16f6324ffdbd750f099cd7879ed15e8c968424cf65c9ad9e54ee4b529050ee49", + "sha256": "80833aca8a902058e030f77db86e24bd7592816e58dc669d7834abe5bef23cef", "body": { "success": true, "message": null, @@ -1780,10 +1780,18 @@ "row_type": "INPUT", "depth": 3, "data": { - "q1": {}, - "q2": {}, - "q3": {}, - "q4": {}, + "q1": { + "transfer_to_economy": 300 + }, + "q2": { + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 + }, "ckk": {}, "plan": { "plan_q1": 300, @@ -1833,13 +1841,19 @@ "depth": 3, "data": { "q1": { - "booking_amount": 777.25 + "booking_amount": 777.25, + "transfer_to_economy": 0 }, "q2": { - "booking_amount": 5 + "booking_amount": 5, + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 }, - "q3": {}, - "q4": {}, "ckk": { "expenses_q1": 777.25, "expenses_q2": 5, @@ -1884,10 +1898,18 @@ "row_type": "INPUT", "depth": 3, "data": { - "q1": {}, - "q2": {}, - "q3": {}, - "q4": {}, + "q1": { + "transfer_to_economy": 42 + }, + "q2": { + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 + }, "ckk": {}, "plan": { "plan_q1": 42, @@ -1934,10 +1956,18 @@ "row_type": "INPUT", "depth": 3, "data": { - "q1": {}, - "q2": {}, - "q3": {}, - "q4": {}, + "q1": { + "transfer_to_economy": 9 + }, + "q2": { + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 + }, "ckk": {}, "plan": { "plan_q1": 9, @@ -1987,7 +2017,7 @@ } }, "RF-admin_one": { - "sha256": "a463b0b1858f4333b27713987cef3ddfdf9eb207df84122c41f654f58620b504", + "sha256": "a9bf8e54d4aef2f7f2fe0e5951d762bb3f2dadc00cd689f3d257aac0465cd5e6", "body": { "success": true, "message": null, @@ -1996,10 +2026,18 @@ "row_type": "INPUT", "depth": 3, "data": { - "q1": {}, - "q2": {}, - "q3": {}, - "q4": {}, + "q1": { + "transfer_to_economy": 300 + }, + "q2": { + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 + }, "ckk": {}, "plan": { "plan_q1": 300, @@ -2049,13 +2087,19 @@ "depth": 3, "data": { "q1": { - "booking_amount": 777.25 + "booking_amount": 777.25, + "transfer_to_economy": 0 }, "q2": { - "booking_amount": 5 + "booking_amount": 5, + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 }, - "q3": {}, - "q4": {}, "ckk": { "expenses_q1": 777.25, "expenses_q2": 5, @@ -2100,10 +2144,18 @@ "row_type": "INPUT", "depth": 3, "data": { - "q1": {}, - "q2": {}, - "q3": {}, - "q4": {}, + "q1": { + "transfer_to_economy": 42 + }, + "q2": { + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 + }, "ckk": {}, "plan": { "plan_q1": 42, @@ -2151,7 +2203,7 @@ } }, "RF-admin_page50": { - "sha256": "16f6324ffdbd750f099cd7879ed15e8c968424cf65c9ad9e54ee4b529050ee49", + "sha256": "80833aca8a902058e030f77db86e24bd7592816e58dc669d7834abe5bef23cef", "body": { "success": true, "message": null, @@ -2160,10 +2212,18 @@ "row_type": "INPUT", "depth": 3, "data": { - "q1": {}, - "q2": {}, - "q3": {}, - "q4": {}, + "q1": { + "transfer_to_economy": 300 + }, + "q2": { + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 + }, "ckk": {}, "plan": { "plan_q1": 300, @@ -2213,13 +2273,19 @@ "depth": 3, "data": { "q1": { - "booking_amount": 777.25 + "booking_amount": 777.25, + "transfer_to_economy": 0 }, "q2": { - "booking_amount": 5 + "booking_amount": 5, + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 }, - "q3": {}, - "q4": {}, "ckk": { "expenses_q1": 777.25, "expenses_q2": 5, @@ -2264,10 +2330,18 @@ "row_type": "INPUT", "depth": 3, "data": { - "q1": {}, - "q2": {}, - "q3": {}, - "q4": {}, + "q1": { + "transfer_to_economy": 42 + }, + "q2": { + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 + }, "ckk": {}, "plan": { "plan_q1": 42, @@ -2314,10 +2388,18 @@ "row_type": "INPUT", "depth": 3, "data": { - "q1": {}, - "q2": {}, - "q3": {}, - "q4": {}, + "q1": { + "transfer_to_economy": 9 + }, + "q2": { + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 + }, "ckk": {}, "plan": { "plan_q1": 9, @@ -2367,7 +2449,7 @@ } }, "RF-admin_two": { - "sha256": "f96f208a936ce1b609f7f940399152353aafc89953052ef9f312a3b7159a468f", + "sha256": "d3080fd50d726df99044450b08ecbc2a390b8627883d0cfca3b3f779227bc5b6", "body": { "success": true, "message": null, @@ -2376,10 +2458,18 @@ "row_type": "INPUT", "depth": 3, "data": { - "q1": {}, - "q2": {}, - "q3": {}, - "q4": {}, + "q1": { + "transfer_to_economy": 300 + }, + "q2": { + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 + }, "ckk": {}, "plan": { "plan_q1": 300, @@ -2429,13 +2519,19 @@ "depth": 3, "data": { "q1": { - "booking_amount": 777.25 + "booking_amount": 777.25, + "transfer_to_economy": 0 }, "q2": { - "booking_amount": 5 + "booking_amount": 5, + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 }, - "q3": {}, - "q4": {}, "ckk": { "expenses_q1": 777.25, "expenses_q2": 5, @@ -2481,7 +2577,7 @@ } }, "RF-isp_all": { - "sha256": "a463b0b1858f4333b27713987cef3ddfdf9eb207df84122c41f654f58620b504", + "sha256": "a9bf8e54d4aef2f7f2fe0e5951d762bb3f2dadc00cd689f3d257aac0465cd5e6", "body": { "success": true, "message": null, @@ -2490,10 +2586,18 @@ "row_type": "INPUT", "depth": 3, "data": { - "q1": {}, - "q2": {}, - "q3": {}, - "q4": {}, + "q1": { + "transfer_to_economy": 300 + }, + "q2": { + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 + }, "ckk": {}, "plan": { "plan_q1": 300, @@ -2543,13 +2647,19 @@ "depth": 3, "data": { "q1": { - "booking_amount": 777.25 + "booking_amount": 777.25, + "transfer_to_economy": 0 }, "q2": { - "booking_amount": 5 + "booking_amount": 5, + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 }, - "q3": {}, - "q4": {}, "ckk": { "expenses_q1": 777.25, "expenses_q2": 5, @@ -2594,10 +2704,18 @@ "row_type": "INPUT", "depth": 3, "data": { - "q1": {}, - "q2": {}, - "q3": {}, - "q4": {}, + "q1": { + "transfer_to_economy": 42 + }, + "q2": { + "transfer_to_economy": 0 + }, + "q3": { + "transfer_to_economy": 0 + }, + "q4": { + "transfer_to_economy": 0 + }, "ckk": {}, "plan": { "plan_q1": 42, diff --git a/api/tests/integration/test_form2_transfer_econ.py b/api/tests/integration/test_form2_transfer_econ.py index 74c0349..ff4b6b8 100644 --- a/api/tests/integration/test_form2_transfer_econ.py +++ b/api/tests/integration/test_form2_transfer_econ.py @@ -173,3 +173,62 @@ def test_form2_transfer_econ_cell_is_read_only(client, headers, svod2_sql, colum ) assert r.status_code in (400, 422), r.text assert "computed_field" in r.json()["message"] + + +# --- Свод 2 («РФ» = FORM_2) показывает тот же расчётный фонд экономии, что лист --- + +def _svod2_rf_quarters(svod2_sql, year, line_id): + rows = svod2_sql( + "SELECT data FROM v3.v_svod2_rf(:y, NULL, NULL, NULL, 'org_name', 'asc', " + "NULL, NULL, NULL, NULL, false) WHERE source_line_id = :l", + {"y": year, "l": line_id}, + ) + assert len(rows) == 1 + data = rows[0][0] + return [data[q].get("transfer_to_economy") for q in ("q1", "q2", "q3", "q4")] + + +def _sheet_econ(client, headers, form_id, sheet, line_id): + d = _input(_rows(client, headers, form_id, sheet), line_id) + return [d[q].get("transfer_econ") for q in ("q1", "q2", "q3", "q4")] + + +def test_svod2_rf_transfer_econ_ticket_repro(client, headers, svod2_sql): + form_id = _new_form(client, headers, 91030) + line_id = _add_line(client, headers, form_id, "AHR", _leaf_items(svod2_sql, "AHR", 1)[0]) + for col, val in [("plan.q1", 1000), ("plan.q2", 2000), ("plan.q3", 3000)]: + _set(client, headers, form_id, "AHR", line_id, col, val) + + assert _svod2_rf_quarters(svod2_sql, 91030, line_id) == [1000, 2000, 3000, 0] + assert _svod2_rf_quarters(svod2_sql, 91030, line_id) == _sheet_econ(client, headers, form_id, "AHR", line_id) + + +def test_svod2_rf_transfer_econ_subcolumns_match_sheet(client, headers, svod2_sql): + form_id = _new_form(client, headers, 91031) + line_id = _add_line(client, headers, form_id, "AHR", _leaf_items(svod2_sql, "AHR", 1)[0]) + for col, val in [ + ("plan.q1", 1000), ("plan.q2", 2000), ("plan.q3", 3000), + ("q1.transfer_q2_delay_acts", 100), ("q1.transfer_q3", 200), ("q1.transfer_q4", 300), + ("q2.transfer_q3_delay_acts", 400), ("q2.transfer_q4", 500), + ("q3.transfer_q4_delay_acts", 600), + ]: + _set(client, headers, form_id, "AHR", line_id, col, val) + + assert _svod2_rf_quarters(svod2_sql, 91031, line_id) == [400, 1100, 2600, 800] + assert _svod2_rf_quarters(svod2_sql, 91031, line_id) == _sheet_econ(client, headers, form_id, "AHR", line_id) + + +def test_svod2_rf_ignores_raw_transfer_to_economy(client, headers, svod2_sql): + form_id = _new_form(client, headers, 91032) + line_id = _add_line(client, headers, form_id, "AHR", _leaf_items(svod2_sql, "AHR", 1)[0]) + for col, val in [("plan.q1", 1000), ("plan.q2", 2000), ("plan.q3", 3000)]: + _set(client, headers, form_id, "AHR", line_id, col, val) + for quarter in (1, 2, 3, 4): + svod2_sql( + "INSERT INTO v3.budget_line_quarter (line_id, quarter, transfer_to_economy) " + "VALUES (:l, :q, 777) ON CONFLICT (line_id, quarter) " + "DO UPDATE SET transfer_to_economy = 777", + {"l": line_id, "q": quarter}, + ) + + assert _svod2_rf_quarters(svod2_sql, 91032, line_id) == [1000, 2000, 3000, 0]