fix(export): единицы и НДС шапки плана в Excel как на экране; collegial.approved — денежная (AURORA-1900)

Возврат тестировщика 12.11.

- Шапка «План {year} (тыс. руб.) {vat_plan}» подставляла текст подколонок:
  Ф.1 АХР и все листы Ф.4 выгружались «без НДС», а на экране — «с учётом
  складского НДС» (Ф.4 КВ — «с учётом НДС»). Теперь у шапки своя подпись
  по экрану (FORM1/FORM4_SHEET_PLAN_VAT_WORDING), подколонки не менялись.
- Раскладки Excel (FORM1/FORM2 depth, АХР-сателлиты, проекты, SMETA формы 2/4,
  патчи формы 4): «тыс. руб.» по тому же правилу, что на экране; у комментариев
  убран подзаголовок «Сумма, тыс. руб. (…)».
- «Коллегиальные органы → Сумма (без НДС)» (collegial.approved,
  collegial_approval.approved_amount NUMERIC) считалась текстом (AURORA-1899):
  не масштабировалась разрядностью и не получала единицу. Теперь денежная.
- test_export_unit_labels.py: на реальных раскладках выгрузки форм 1/2/4 у каждой
  денежной колонки есть «тыс. руб.», у текстовых нет, шапка плана — как на экране.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-11-12 14:44:43 +04:00
parent 45b23b2547
commit f5e9ff37a4
15 changed files with 334 additions and 187 deletions

View File

@ -88,7 +88,7 @@ FORM1_DEPTH_COLUMNS: dict = {
"name": "Договоры", "name": "Договоры",
"children": { "children": {
"total": { "total": {
"name": "Сумма", "name": "Сумма, тыс. руб.",
}, },
"counterparty": { "counterparty": {
"name": "Контрагент", "name": "Контрагент",
@ -100,13 +100,13 @@ FORM1_DEPTH_COLUMNS: dict = {
"name": "Комментарии", "name": "Комментарии",
}, },
"future_y1": { "future_y1": {
"name": "Платежи в {next_year} году", "name": "Платежи в {next_year} году, тыс. руб.",
}, },
"future_y2": { "future_y2": {
"name": "Платежи в {year_plus_2} году", "name": "Платежи в {year_plus_2} году, тыс. руб.",
}, },
"other_ssp": { "other_ssp": {
"name": "Сумма в других ССП", "name": "Сумма в других ССП, тыс. руб.",
}, },
} }
} }
@ -127,7 +127,7 @@ FORM1_DEPTH_COLUMNS: dict = {
"name": "Секвестирование ДФиП", "name": "Секвестирование ДФиП",
"children": { "children": {
"correct": { "correct": {
"name": "Корректировка сметы расходов", "name": "Корректировка сметы расходов, тыс. руб.",
"key": None, "key": None,
"children": { "children": {
"q1": { "q1": {
@ -156,7 +156,7 @@ FORM1_DEPTH_COLUMNS: dict = {
"name": "Отнесение в резерв", "name": "Отнесение в резерв",
"children": { "children": {
"correct": { "correct": {
"name": "Резерв Финансового Комитета", "name": "Резерв Финансового Комитета, тыс. руб.",
"key": None, "key": None,
"children": { "children": {
"q1": { "q1": {
@ -182,7 +182,7 @@ FORM1_DEPTH_COLUMNS: dict = {
} }
}, },
"approved": { "approved": {
"name": "Итоговая смета расходов", "name": "Итоговая смета расходов, тыс. руб.",
"children": { "children": {
"plan": { "plan": {
"name": "План {year}", "name": "План {year}",
@ -215,7 +215,7 @@ FORM1_DEPTH_COLUMNS: dict = {
"name": "Коллегиальные органы", "name": "Коллегиальные органы",
"children": { "children": {
"approved": { "approved": {
"name": "Сумма ({vat})", "name": "Сумма, тыс. руб. ({vat})",
}, },
"protocol": { "protocol": {
"name": "Реквизиты протокола (дата, номер)", "name": "Реквизиты протокола (дата, номер)",
@ -369,7 +369,7 @@ FORM1_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"adj_comment": { "adj_comment": {
"name": "Сумма, тыс. руб. ({vat})", "name": "",
} }
} }
}, },
@ -396,10 +396,10 @@ FORM1_DEPTH_COLUMNS: dict = {
"name": "Сумма, тыс. руб. ({vat})" "name": "Сумма, тыс. руб. ({vat})"
}, },
"pay_ho": { "pay_ho": {
"name": "в т.ч. сумма ({vat}) ГО" "name": "в т.ч. сумма, тыс. руб. ({vat}) ГО"
}, },
"pay_rf": { "pay_rf": {
"name": "в т.ч. сумма ({vat}) РФ" "name": "в т.ч. сумма, тыс. руб. ({vat}) РФ"
}, },
"pay_comment": { "pay_comment": {
"name": "Комментарий" "name": "Комментарий"
@ -476,7 +476,7 @@ FORM1_DEPTH_COLUMNS: dict = {
} }
}, },
"close_q1": { "close_q1": {
"name": "Закрытие 1-го квартала", "name": "Закрытие 1-го квартала, тыс. руб.",
"key": None, "key": None,
"children": { "children": {
"transfer_q2": { "transfer_q2": {
@ -544,7 +544,7 @@ FORM1_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"rev_comment": { "rev_comment": {
"name": "Сумма, тыс. руб. ({vat})", "name": "",
} }
} }
}, },
@ -603,7 +603,7 @@ FORM1_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"adj_comment": { "adj_comment": {
"name": "Сумма, тыс. руб. ({vat})", "name": "",
} }
} }
}, },
@ -630,10 +630,10 @@ FORM1_DEPTH_COLUMNS: dict = {
"name": "Сумма, тыс. руб. ({vat})" "name": "Сумма, тыс. руб. ({vat})"
}, },
"pay_ho": { "pay_ho": {
"name": "в т.ч. сумма ({vat}) ГО" "name": "в т.ч. сумма, тыс. руб. ({vat}) ГО"
}, },
"pay_rf": { "pay_rf": {
"name": "в т.ч. сумма ({vat}) РФ" "name": "в т.ч. сумма, тыс. руб. ({vat}) РФ"
}, },
"pay_comment": { "pay_comment": {
"name": "Комментарий" "name": "Комментарий"
@ -707,7 +707,7 @@ FORM1_DEPTH_COLUMNS: dict = {
} }
}, },
"close_q2": { "close_q2": {
"name": "Закрытие 2-го квартала", "name": "Закрытие 2-го квартала, тыс. руб.",
"key": None, "key": None,
"children": { "children": {
"transfer_q3": { "transfer_q3": {
@ -771,7 +771,7 @@ FORM1_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"rev_comment": { "rev_comment": {
"name": "Сумма, тыс. руб. ({vat})", "name": "",
} }
} }
}, },
@ -830,7 +830,7 @@ FORM1_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"adj_comment": { "adj_comment": {
"name": "Сумма, тыс. руб. ({vat})", "name": "",
} }
} }
}, },
@ -857,10 +857,10 @@ FORM1_DEPTH_COLUMNS: dict = {
"name": "Сумма, тыс. руб. ({vat})" "name": "Сумма, тыс. руб. ({vat})"
}, },
"pay_ho": { "pay_ho": {
"name": "в т.ч. сумма ({vat}) ГО" "name": "в т.ч. сумма, тыс. руб. ({vat}) ГО"
}, },
"pay_rf": { "pay_rf": {
"name": "в т.ч. сумма ({vat}) РФ" "name": "в т.ч. сумма, тыс. руб. ({vat}) РФ"
}, },
"pay_comment": { "pay_comment": {
"name": "Комментарий" "name": "Комментарий"
@ -934,7 +934,7 @@ FORM1_DEPTH_COLUMNS: dict = {
} }
}, },
"close_q3": { "close_q3": {
"name": "Закрытие 3-го квартала", "name": "Закрытие 3-го квартала, тыс. руб.",
"key": None, "key": None,
"children": { "children": {
"transfer_q4": { "transfer_q4": {
@ -995,7 +995,7 @@ FORM1_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"rev_comment": { "rev_comment": {
"name": "Сумма, тыс. руб. ({vat})", "name": "",
}, },
} }
}, },
@ -1054,7 +1054,7 @@ FORM1_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"adj_comment": { "adj_comment": {
"name": "Сумма, тыс. руб. ({vat})", "name": "",
} }
} }
}, },
@ -1081,10 +1081,10 @@ FORM1_DEPTH_COLUMNS: dict = {
"name": "Сумма, тыс. руб. ({vat})" "name": "Сумма, тыс. руб. ({vat})"
}, },
"pay_ho": { "pay_ho": {
"name": "в т.ч. сумма ({vat}) ГО" "name": "в т.ч. сумма, тыс. руб. ({vat}) ГО"
}, },
"pay_rf": { "pay_rf": {
"name": "в т.ч. сумма ({vat}) РФ" "name": "в т.ч. сумма, тыс. руб. ({vat}) РФ"
}, },
"pay_comment": { "pay_comment": {
"name": "Комментарий" "name": "Комментарий"
@ -1167,7 +1167,7 @@ FORM1_DEPTH_COLUMNS: dict = {
} }
}, },
"close_q4": { "close_q4": {
"name": "Закрытие 4-го квартала", "name": "Закрытие 4-го квартала, тыс. руб.",
"key": None, "key": None,
"children": { "children": {
"economy": { "economy": {
@ -1184,7 +1184,7 @@ FORM1_DEPTH_COLUMNS: dict = {
"name": "Факт {year_short}", "name": "Факт {year_short}",
"children": { "children": {
"fact_year": { "fact_year": {
"name": "Факт {year_short}", "name": "Факт {year_short}, тыс. руб.",
}, },
# "empty": { # "empty": {
# "name": "", # "name": "",
@ -1239,7 +1239,7 @@ FORM2_DEPTH_AHR_SUB_COLUMNS: dict = {
}, },
"plan": { "plan": {
# "key": None, # "key": None,
"name": "Планируемая сумма расходов на {next_year} год", "name": "Планируемая сумма расходов на {next_year} год, тыс. руб.",
"children": { "children": {
"q1": { "q1": {
"name": "I квартал" "name": "I квартал"
@ -3323,7 +3323,7 @@ def _patch_form4_contract_summary_columns() -> None:
summary_parent = FORM4_DEPTH_COLUMNS["upper_ssp_smeta"]["children"]["contract_summary"] summary_parent = FORM4_DEPTH_COLUMNS["upper_ssp_smeta"]["children"]["contract_summary"]
summary = summary_parent["children"] summary = summary_parent["children"]
summary["reference"] = {"name": "Реквизиты договора"} summary["reference"] = {"name": "Реквизиты договора"}
summary["future_y2"] = {"name": "Платежи в {year_plus_2} году"} summary["future_y2"] = {"name": "Платежи в {year_plus_2} году, тыс. руб."}
summary["centralized_flag"] = {"name": "Централизованный договор"} summary["centralized_flag"] = {"name": "Централизованный договор"}
summary_parent["children"] = _reorder_children( summary_parent["children"] = _reorder_children(
summary, summary,
@ -3335,7 +3335,7 @@ def _patch_form4_contract_summary_columns() -> None:
structure_summary_parent = FORM4_STRUCTURE_DEPTH_COLUMNS["upper_ssp_smeta"]["children"]["contract_summary"] structure_summary_parent = FORM4_STRUCTURE_DEPTH_COLUMNS["upper_ssp_smeta"]["children"]["contract_summary"]
structure_summary = structure_summary_parent["children"] structure_summary = structure_summary_parent["children"]
structure_summary["future_payments_y2"] = {"name": "Платежи в {year_plus_2} году"} structure_summary["future_payments_y2"] = {"name": "Платежи в {year_plus_2} году, тыс. руб."}
structure_summary_parent["children"] = _reorder_children( structure_summary_parent["children"] = _reorder_children(
structure_summary, structure_summary,
( (
@ -3353,7 +3353,7 @@ def _patch_form4_actual_rsbu_spod_columns() -> None:
leaf_key = f"q{quarter}.actual_rsbu_spod" if quarter == 4 else f"q{quarter}.actual_quarter" leaf_key = f"q{quarter}.actual_rsbu_spod" if quarter == 4 else f"q{quarter}.actual_quarter"
FORM4_DEPTH_COLUMNS[f"actual_rsbu_spod_q{quarter}"] = { FORM4_DEPTH_COLUMNS[f"actual_rsbu_spod_q{quarter}"] = {
"key": None, "key": None,
"name": f"Факт {quarter} кв" if quarter < 4 else "Факт 4 кв + СПОД", "name": f"Факт {quarter} кв, тыс. руб." if quarter < 4 else "Факт 4 кв + СПОД, тыс. руб.",
"children": { "children": {
"actual_rsbu_spod": { "actual_rsbu_spod": {
"key": leaf_key, "key": leaf_key,
@ -3442,46 +3442,46 @@ SMETA_FORM1_COLUMNS: list[tuple[str, str]] = [
SMETA_FORM2_COLUMNS: list[tuple[str, str]] = [ SMETA_FORM2_COLUMNS: list[tuple[str, str]] = [
("section_code", "Код раздела"), ("section_code", "Код раздела"),
("name", "Наименование"), ("name", "Наименование"),
("plan_support_q1", "План поддержка I квартал"), ("plan_support_q1", "План поддержка I квартал, тыс. руб."),
("plan_support_q2", "План поддержка II квартал"), ("plan_support_q2", "План поддержка II квартал, тыс. руб."),
("plan_support_q3", "План поддержка III квартал"), ("plan_support_q3", "План поддержка III квартал, тыс. руб."),
("plan_support_q4", "План поддержка IV квартал"), ("plan_support_q4", "План поддержка IV квартал, тыс. руб."),
("plan_support_year", "План поддержка Год"), ("plan_support_year", "План поддержка Год, тыс. руб."),
("approved_support_q1", "Утверждено поддержка I квартал"), ("approved_support_q1", "Утверждено поддержка I квартал, тыс. руб."),
("approved_support_q2", "Утверждено поддержка II квартал"), ("approved_support_q2", "Утверждено поддержка II квартал, тыс. руб."),
("approved_support_q3", "Утверждено поддержка III квартал"), ("approved_support_q3", "Утверждено поддержка III квартал, тыс. руб."),
("approved_support_q4", "Утверждено поддержка IV квартал"), ("approved_support_q4", "Утверждено поддержка IV квартал, тыс. руб."),
("approved_support_year", "Утверждено поддержка Год"), ("approved_support_year", "Утверждено поддержка Год, тыс. руб."),
("fact_support_q1", "Факт поддержка I квартал"), ("fact_support_q1", "Факт поддержка I квартал, тыс. руб."),
("fact_support_q2", "Факт поддержка II квартал"), ("fact_support_q2", "Факт поддержка II квартал, тыс. руб."),
("fact_support_q3", "Факт поддержка III квартал"), ("fact_support_q3", "Факт поддержка III квартал, тыс. руб."),
("fact_support_q4", "Факт поддержка IV квартал"), ("fact_support_q4", "Факт поддержка IV квартал, тыс. руб."),
("fact_support_year", "Факт поддержка Год"), ("fact_support_year", "Факт поддержка Год, тыс. руб."),
("corrected_support_q2", "Корректировка поддержка II квартал"), ("corrected_support_q2", "Корректировка поддержка II квартал, тыс. руб."),
("corrected_support_q3", "Корректировка поддержка III квартал"), ("corrected_support_q3", "Корректировка поддержка III квартал, тыс. руб."),
("corrected_support_q4", "Корректировка поддержка IV квартал"), ("corrected_support_q4", "Корректировка поддержка IV квартал, тыс. руб."),
] ]
SMETA_FORM4_COLUMNS: list[tuple[str, str]] = [ SMETA_FORM4_COLUMNS: list[tuple[str, str]] = [
("section_code", "Код раздела"), ("section_code", "Код раздела"),
("name", "Наименование"), ("name", "Наименование"),
("plan_development_q1", "План развитие I квартал"), ("plan_development_q1", "План развитие I квартал, тыс. руб."),
("plan_development_q2", "План развитие II квартал"), ("plan_development_q2", "План развитие II квартал, тыс. руб."),
("plan_development_q3", "План развитие III квартал"), ("plan_development_q3", "План развитие III квартал, тыс. руб."),
("plan_development_q4", "План развитие IV квартал"), ("plan_development_q4", "План развитие IV квартал, тыс. руб."),
("plan_development_year", "План развитие Год"), ("plan_development_year", "План развитие Год, тыс. руб."),
("approved_development_q1", "Утверждено развитие I квартал"), ("approved_development_q1", "Утверждено развитие I квартал, тыс. руб."),
("approved_development_q2", "Утверждено развитие II квартал"), ("approved_development_q2", "Утверждено развитие II квартал, тыс. руб."),
("approved_development_q3", "Утверждено развитие III квартал"), ("approved_development_q3", "Утверждено развитие III квартал, тыс. руб."),
("approved_development_q4", "Утверждено развитие IV квартал"), ("approved_development_q4", "Утверждено развитие IV квартал, тыс. руб."),
("approved_development_year", "Утверждено развитие Год"), ("approved_development_year", "Утверждено развитие Год, тыс. руб."),
("fact_development_q1", "Факт развитие I квартал"), ("fact_development_q1", "Факт развитие I квартал, тыс. руб."),
("fact_development_q2", "Факт развитие II квартал"), ("fact_development_q2", "Факт развитие II квартал, тыс. руб."),
("fact_development_q3", "Факт развитие III квартал"), ("fact_development_q3", "Факт развитие III квартал, тыс. руб."),
("fact_development_q4", "Факт развитие IV квартал"), ("fact_development_q4", "Факт развитие IV квартал, тыс. руб."),
("fact_development_year", "Факт развитие Год"), ("fact_development_year", "Факт развитие Год, тыс. руб."),
("corrected_development_q2", "Корректировка развитие II квартал"), ("corrected_development_q2", "Корректировка развитие II квартал, тыс. руб."),
("corrected_development_q3", "Корректировка развитие III квартал"), ("corrected_development_q3", "Корректировка развитие III квартал, тыс. руб."),
("corrected_development_q4", "Корректировка развитие IV квартал"), ("corrected_development_q4", "Корректировка развитие IV квартал, тыс. руб."),
] ]
SMETA_COLUMNS_BY_FORM: dict[str, list[tuple[str, str]]] = { SMETA_COLUMNS_BY_FORM: dict[str, list[tuple[str, str]]] = {
"FORM_1": SMETA_FORM1_COLUMNS, "FORM_1": SMETA_FORM1_COLUMNS,
@ -3503,7 +3503,7 @@ FORM3_LIMIT_DEPTH_COLUMNS: dict = {
}, },
}, },
"q1": { "q1": {
"name": "Отчет об исполнении лимита затрат, утверждённого Правлением Банка в I квартале", "name": "Отчет об исполнении лимита затрат, утверждённого Правлением Банка в I квартале, тыс. руб.",
"children": { "children": {
"korrektirovka_limita": { "korrektirovka_limita": {
"key": None, "key": None,
@ -3536,7 +3536,7 @@ FORM3_LIMIT_DEPTH_COLUMNS: dict = {
}, },
}, },
"q2": { "q2": {
"name": "Отчет об исполнении лимита затрат, утверждённого Правлением Банка в II квартале", "name": "Отчет об исполнении лимита затрат, утверждённого Правлением Банка в II квартале, тыс. руб.",
"children": { "children": {
"limit_ostatok": { "limit_ostatok": {
"key": None, "key": None,
@ -3574,7 +3574,7 @@ FORM3_LIMIT_DEPTH_COLUMNS: dict = {
}, },
}, },
"q3": { "q3": {
"name": "Отчет об исполнении лимита затрат, утверждённого Правлением Банка в III квартале", "name": "Отчет об исполнении лимита затрат, утверждённого Правлением Банка в III квартале, тыс. руб.",
"children": { "children": {
"limit_ostatok": { "limit_ostatok": {
"key": None, "key": None,
@ -3612,7 +3612,7 @@ FORM3_LIMIT_DEPTH_COLUMNS: dict = {
}, },
}, },
"q4": { "q4": {
"name": "Отчет об исполнении лимита затрат, утверждённого Правлением Банка в IV квартале", "name": "Отчет об исполнении лимита затрат, утверждённого Правлением Банка в IV квартале, тыс. руб.",
"children": { "children": {
"limit_ostatok": { "limit_ostatok": {
"key": None, "key": None,
@ -3663,7 +3663,7 @@ FORM3_CURRENT_EXPENSES_DEPTH_COLUMNS: dict = {
}, },
}, },
"q1": { "q1": {
"name": "Отчет об исполнении текущих расходов в I квартале", "name": "Отчет об исполнении текущих расходов в I квартале, тыс. руб.",
"children": { "children": {
"korrektirovka_limita": { "korrektirovka_limita": {
"key": None, "key": None,
@ -3696,7 +3696,7 @@ FORM3_CURRENT_EXPENSES_DEPTH_COLUMNS: dict = {
}, },
}, },
"q2": { "q2": {
"name": "Отчет об исполнении текущих расходов в II квартале", "name": "Отчет об исполнении текущих расходов в II квартале, тыс. руб.",
"children": { "children": {
"limit_ostatok": { "limit_ostatok": {
"key": None, "key": None,
@ -3734,7 +3734,7 @@ FORM3_CURRENT_EXPENSES_DEPTH_COLUMNS: dict = {
}, },
}, },
"q3": { "q3": {
"name": "Отчет об исполнении текущих расходов в III квартале", "name": "Отчет об исполнении текущих расходов в III квартале, тыс. руб.",
"children": { "children": {
"limit_ostatok": { "limit_ostatok": {
"key": None, "key": None,
@ -3772,7 +3772,7 @@ FORM3_CURRENT_EXPENSES_DEPTH_COLUMNS: dict = {
}, },
}, },
"q4": { "q4": {
"name": "Отчет об исполнении текущих расходов в IV квартале", "name": "Отчет об исполнении текущих расходов в IV квартале, тыс. руб.",
"children": { "children": {
"limit_ostatok": { "limit_ostatok": {
"key": None, "key": None,
@ -4117,23 +4117,23 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"q1": { "q1": {
"name": "1 квартал", "name": "1 квартал, тыс. руб.",
"key": "plan.q1", "key": "plan.q1",
}, },
"q2": { "q2": {
"name": "2 квартал", "name": "2 квартал, тыс. руб.",
"key": "plan.q2", "key": "plan.q2",
}, },
"q3": { "q3": {
"name": "3 квартал", "name": "3 квартал, тыс. руб.",
"key": "plan.q3", "key": "plan.q3",
}, },
"q4": { "q4": {
"name": "4 квартал", "name": "4 квартал, тыс. руб.",
"key": "plan.q4", "key": "plan.q4",
}, },
"year": { "year": {
"name": "Год", "name": "Год, тыс. руб.",
"key": "plan.year", "key": "plan.year",
}, },
"comment": { "comment": {
@ -4147,23 +4147,23 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"q1": { "q1": {
"name": "1 квартал", "name": "1 квартал, тыс. руб.",
"key": "seq_dfip.q1", "key": "seq_dfip.q1",
}, },
"q2": { "q2": {
"name": "2 квартал", "name": "2 квартал, тыс. руб.",
"key": "seq_dfip.q2", "key": "seq_dfip.q2",
}, },
"q3": { "q3": {
"name": "3 квартал", "name": "3 квартал, тыс. руб.",
"key": "seq_dfip.q3", "key": "seq_dfip.q3",
}, },
"q4": { "q4": {
"name": "4 квартал", "name": "4 квартал, тыс. руб.",
"key": "seq_dfip.q4", "key": "seq_dfip.q4",
}, },
"year": { "year": {
"name": "Год", "name": "Год, тыс. руб.",
"key": "seq_dfip.year", "key": "seq_dfip.year",
}, },
"justification": { "justification": {
@ -4177,23 +4177,23 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"q1": { "q1": {
"name": "1 квартал", "name": "1 квартал, тыс. руб.",
"key": "seq_ssp.q1", "key": "seq_ssp.q1",
}, },
"q2": { "q2": {
"name": "2 квартал", "name": "2 квартал, тыс. руб.",
"key": "seq_ssp.q2", "key": "seq_ssp.q2",
}, },
"q3": { "q3": {
"name": "3 квартал", "name": "3 квартал, тыс. руб.",
"key": "seq_ssp.q3", "key": "seq_ssp.q3",
}, },
"q4": { "q4": {
"name": "4 квартал", "name": "4 квартал, тыс. руб.",
"key": "seq_ssp.q4", "key": "seq_ssp.q4",
}, },
"year": { "year": {
"name": "Год", "name": "Год, тыс. руб.",
"key": "seq_ssp.year", "key": "seq_ssp.year",
}, },
"justification": { "justification": {
@ -4203,7 +4203,7 @@ FORM2_DEPTH_COLUMNS: dict = {
}, },
}, },
"utverzhdennaya_smeta_raskhodov": { "utverzhdennaya_smeta_raskhodov": {
"name": "Утвержденная смета расходов", "name": "Утвержденная смета расходов, тыс. руб.",
"key": None, "key": None,
"children": { "children": {
"q1": { "q1": {
@ -4253,7 +4253,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "contract.currency", "key": "contract.currency",
}, },
"ceiling": { "ceiling": {
"name": "Предельная стоимость", "name": "Предельная стоимость, тыс. руб.",
"key": "contract.ceiling", "key": "contract.ceiling",
}, },
"vat_rate": { "vat_rate": {
@ -4277,19 +4277,19 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "contract.comment", "key": "contract.comment",
}, },
"q1": { "q1": {
"name": "Бронь средств в 1 квартале", "name": "Бронь средств в 1 квартале, тыс. руб.",
"key": "booking.y2026.q1", "key": "booking.y2026.q1",
}, },
"q2": { "q2": {
"name": "Бронь средств во 2 квартале", "name": "Бронь средств во 2 квартале, тыс. руб.",
"key": "booking.y2026.q2", "key": "booking.y2026.q2",
}, },
"q3": { "q3": {
"name": "Бронь средств в 3 квартале", "name": "Бронь средств в 3 квартале, тыс. руб.",
"key": "booking.y2026.q3", "key": "booking.y2026.q3",
}, },
"q4": { "q4": {
"name": "Бронь средств в 4 квартале", "name": "Бронь средств в 4 квартале, тыс. руб.",
"key": "booking.y2026.q4", "key": "booking.y2026.q4",
}, },
}, },
@ -4297,7 +4297,7 @@ FORM2_DEPTH_COLUMNS: dict = {
}, },
}, },
"god_2": { "god_2": {
"name": "{next_year} год", "name": "{next_year} год, тыс. руб.",
"key": None, "key": None,
"children": { "children": {
"q1": { "q1": {
@ -4327,15 +4327,15 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"adj_current": { "adj_current": {
"name": "Текущие корректировки (= 0)", "name": "Текущие корректировки, тыс. руб. (= 0)",
"key": "q1.adj_current", "key": "q1.adj_current",
}, },
"adj_ssp": { "adj_ssp": {
"name": "Корректировка из сметы ГО", "name": "Корректировка из сметы ГО, тыс. руб.",
"key": "q1.adj_ssp", "key": "q1.adj_ssp",
}, },
"adj_reserve": { "adj_reserve": {
"name": "Корректировка из резерва", "name": "Корректировка из резерва, тыс. руб.",
"key": "q1.adj_reserve", "key": "q1.adj_reserve",
}, },
"adj_comment": { "adj_comment": {
@ -4343,7 +4343,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q1.adj_comment", "key": "q1.adj_comment",
}, },
"corrected_plan": { "corrected_plan": {
"name": "Базовый план с учётом корректировок", "name": "Базовый план с учётом корректировок, тыс. руб.",
"key": "q1.corrected_plan", "key": "q1.corrected_plan",
}, },
}, },
@ -4357,7 +4357,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q1.pay_date", "key": "q1.pay_date",
}, },
"pay_amount": { "pay_amount": {
"name": "Сумма платежа", "name": "Сумма платежа, тыс. руб.",
"key": "q1.pay_amount", "key": "q1.pay_amount",
}, },
"pay_comment": { "pay_comment": {
@ -4371,7 +4371,7 @@ FORM2_DEPTH_COLUMNS: dict = {
}, },
}, },
"fakticheskie_raskhody": { "fakticheskie_raskhody": {
"name": "Фактические расходы", "name": "Фактические расходы, тыс. руб.",
"key": None, "key": None,
"children": { "children": {
"booking": { "booking": {
@ -4409,19 +4409,19 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"transfer_q2": { "transfer_q2": {
"name": "Перенос во 2 квартал", "name": "Перенос во 2 квартал, тыс. руб.",
"key": "q1.transfer_q2", "key": "q1.transfer_q2",
}, },
"transfer_q2_delay_acts": { "transfer_q2_delay_acts": {
"name": "Задержка списания расходов / предоставления актов", "name": "Задержка списания расходов / предоставления актов, тыс. руб.",
"key": "q1.transfer_q2_delay_acts", "key": "q1.transfer_q2_delay_acts",
}, },
"transfer_q2_delay_procurement": { "transfer_q2_delay_procurement": {
"name": "Задержка закупочных процедур", "name": "Задержка закупочных процедур, тыс. руб.",
"key": "q1.transfer_q2_delay_procurement", "key": "q1.transfer_q2_delay_procurement",
}, },
"transfer_q2_economy_rf": { "transfer_q2_economy_rf": {
"name": "Экономия (будет использована РФ)", "name": "Экономия (будет использована РФ), тыс. руб.",
"key": "q1.transfer_q2_economy_rf", "key": "q1.transfer_q2_economy_rf",
}, },
"transfer_next_comment": { "transfer_next_comment": {
@ -4429,11 +4429,11 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q1.transfer_next_comment", "key": "q1.transfer_next_comment",
}, },
"transfer_q3": { "transfer_q3": {
"name": "Перенос в 3 квартал", "name": "Перенос в 3 квартал, тыс. руб.",
"key": "q1.transfer_q3", "key": "q1.transfer_q3",
}, },
"transfer_q4": { "transfer_q4": {
"name": "Перенос в 4 квартал", "name": "Перенос в 4 квартал, тыс. руб.",
"key": "q1.transfer_q4", "key": "q1.transfer_q4",
}, },
"transfer_far_comment": { "transfer_far_comment": {
@ -4441,11 +4441,11 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q1.transfer_far_comment", "key": "q1.transfer_far_comment",
}, },
"transfer_econ": { "transfer_econ": {
"name": "Перенос в фонд экономии", "name": "Перенос в фонд экономии, тыс. руб.",
"key": "q1.transfer_econ", "key": "q1.transfer_econ",
}, },
"total": { "total": {
"name": "Всего", "name": "Всего, тыс. руб.",
"key": "q1.total", "key": "q1.total",
}, },
}, },
@ -4461,11 +4461,11 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"target_change": { "target_change": {
"name": "Изменение целевого назначения перенесённой экономии (= 0)", "name": "Изменение целевого назначения перенесённой экономии, тыс. руб. (= 0)",
"key": "q2.target_change", "key": "q2.target_change",
}, },
"base_correction": { "base_correction": {
"name": "Корректировка статей базового плана (= 0)", "name": "Корректировка статей базового плана, тыс. руб. (= 0)",
"key": "q2.base_correction", "key": "q2.base_correction",
}, },
"base_correction_comment": { "base_correction_comment": {
@ -4473,11 +4473,11 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q2.base_correction_comment", "key": "q2.base_correction_comment",
}, },
"revision_inc": { "revision_inc": {
"name": "ДФиП: увеличение плана (> 0)", "name": "ДФиП: увеличение плана, тыс. руб. (> 0)",
"key": "q2.revision_inc", "key": "q2.revision_inc",
}, },
"revision_seq": { "revision_seq": {
"name": "ДФиП: секвестр плана (< 0)", "name": "ДФиП: секвестр плана, тыс. руб. (< 0)",
"key": "q2.revision_seq", "key": "q2.revision_seq",
}, },
"revision_comment": { "revision_comment": {
@ -4485,7 +4485,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q2.revision_comment", "key": "q2.revision_comment",
}, },
"new_plan": { "new_plan": {
"name": "Скорректированный план на подпись", "name": "Скорректированный план на подпись, тыс. руб.",
"key": "q2.new_plan", "key": "q2.new_plan",
}, },
}, },
@ -4495,15 +4495,15 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"adj_current": { "adj_current": {
"name": "Текущие корректировки (= 0)", "name": "Текущие корректировки, тыс. руб. (= 0)",
"key": "q2.adj_current", "key": "q2.adj_current",
}, },
"adj_ssp": { "adj_ssp": {
"name": "Корректировка из сметы ГО", "name": "Корректировка из сметы ГО, тыс. руб.",
"key": "q2.adj_ssp", "key": "q2.adj_ssp",
}, },
"adj_reserve": { "adj_reserve": {
"name": "Корректировка из резерва", "name": "Корректировка из резерва, тыс. руб.",
"key": "q2.adj_reserve", "key": "q2.adj_reserve",
}, },
"adj_comment": { "adj_comment": {
@ -4511,7 +4511,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q2.adj_comment", "key": "q2.adj_comment",
}, },
"corrected_plan": { "corrected_plan": {
"name": "Скорректированный план с учётом корректировок", "name": "Скорректированный план с учётом корректировок, тыс. руб.",
"key": "q2.corrected_plan", "key": "q2.corrected_plan",
}, },
}, },
@ -4525,7 +4525,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q2.pay_date", "key": "q2.pay_date",
}, },
"pay_amount": { "pay_amount": {
"name": "Сумма платежа", "name": "Сумма платежа, тыс. руб.",
"key": "q2.pay_amount", "key": "q2.pay_amount",
}, },
"pay_comment": { "pay_comment": {
@ -4539,7 +4539,7 @@ FORM2_DEPTH_COLUMNS: dict = {
}, },
}, },
"fakticheskie_raskhody": { "fakticheskie_raskhody": {
"name": "Фактические расходы", "name": "Фактические расходы, тыс. руб.",
"key": None, "key": None,
"children": { "children": {
"booking": { "booking": {
@ -4577,19 +4577,19 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"transfer_q3": { "transfer_q3": {
"name": "Перенос в 3 квартал", "name": "Перенос в 3 квартал, тыс. руб.",
"key": "q2.transfer_q3", "key": "q2.transfer_q3",
}, },
"transfer_q3_delay_acts": { "transfer_q3_delay_acts": {
"name": "Задержка списания расходов / предоставления актов", "name": "Задержка списания расходов / предоставления актов, тыс. руб.",
"key": "q2.transfer_q3_delay_acts", "key": "q2.transfer_q3_delay_acts",
}, },
"transfer_q3_delay_procurement": { "transfer_q3_delay_procurement": {
"name": "Задержка закупочных процедур", "name": "Задержка закупочных процедур, тыс. руб.",
"key": "q2.transfer_q3_delay_procurement", "key": "q2.transfer_q3_delay_procurement",
}, },
"transfer_q3_economy_rf": { "transfer_q3_economy_rf": {
"name": "Экономия (будет использована РФ)", "name": "Экономия (будет использована РФ), тыс. руб.",
"key": "q2.transfer_q3_economy_rf", "key": "q2.transfer_q3_economy_rf",
}, },
"transfer_next_comment": { "transfer_next_comment": {
@ -4597,7 +4597,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q2.transfer_next_comment", "key": "q2.transfer_next_comment",
}, },
"transfer_q4": { "transfer_q4": {
"name": "Перенос в 4 квартал", "name": "Перенос в 4 квартал, тыс. руб.",
"key": "q2.transfer_q4", "key": "q2.transfer_q4",
}, },
"transfer_far_comment": { "transfer_far_comment": {
@ -4605,11 +4605,11 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q2.transfer_far_comment", "key": "q2.transfer_far_comment",
}, },
"transfer_econ": { "transfer_econ": {
"name": "Перенос в фонд экономии", "name": "Перенос в фонд экономии, тыс. руб.",
"key": "q2.transfer_econ", "key": "q2.transfer_econ",
}, },
"total": { "total": {
"name": "Всего", "name": "Всего, тыс. руб.",
"key": "q2.total", "key": "q2.total",
}, },
}, },
@ -4625,11 +4625,11 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"target_change": { "target_change": {
"name": "Изменение целевого назначения перенесённой экономии (= 0)", "name": "Изменение целевого назначения перенесённой экономии, тыс. руб. (= 0)",
"key": "q3.target_change", "key": "q3.target_change",
}, },
"base_correction": { "base_correction": {
"name": "Корректировка статей базового плана (= 0)", "name": "Корректировка статей базового плана, тыс. руб. (= 0)",
"key": "q3.base_correction", "key": "q3.base_correction",
}, },
"base_correction_comment": { "base_correction_comment": {
@ -4637,11 +4637,11 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q3.base_correction_comment", "key": "q3.base_correction_comment",
}, },
"revision_inc": { "revision_inc": {
"name": "ДФиП: увеличение плана (> 0)", "name": "ДФиП: увеличение плана, тыс. руб. (> 0)",
"key": "q3.revision_inc", "key": "q3.revision_inc",
}, },
"revision_seq": { "revision_seq": {
"name": "ДФиП: секвестр плана (< 0)", "name": "ДФиП: секвестр плана, тыс. руб. (< 0)",
"key": "q3.revision_seq", "key": "q3.revision_seq",
}, },
"revision_comment": { "revision_comment": {
@ -4649,7 +4649,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q3.revision_comment", "key": "q3.revision_comment",
}, },
"new_plan": { "new_plan": {
"name": "Скорректированный план на подпись", "name": "Скорректированный план на подпись, тыс. руб.",
"key": "q3.new_plan", "key": "q3.new_plan",
}, },
}, },
@ -4659,15 +4659,15 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"adj_current": { "adj_current": {
"name": "Текущие корректировки (= 0)", "name": "Текущие корректировки, тыс. руб. (= 0)",
"key": "q3.adj_current", "key": "q3.adj_current",
}, },
"adj_ssp": { "adj_ssp": {
"name": "Корректировка из сметы ГО", "name": "Корректировка из сметы ГО, тыс. руб.",
"key": "q3.adj_ssp", "key": "q3.adj_ssp",
}, },
"adj_reserve": { "adj_reserve": {
"name": "Корректировка из резерва", "name": "Корректировка из резерва, тыс. руб.",
"key": "q3.adj_reserve", "key": "q3.adj_reserve",
}, },
"adj_comment": { "adj_comment": {
@ -4675,7 +4675,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q3.adj_comment", "key": "q3.adj_comment",
}, },
"corrected_plan": { "corrected_plan": {
"name": "Скорректированный план с учётом корректировок", "name": "Скорректированный план с учётом корректировок, тыс. руб.",
"key": "q3.corrected_plan", "key": "q3.corrected_plan",
}, },
}, },
@ -4689,7 +4689,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q3.pay_date", "key": "q3.pay_date",
}, },
"pay_amount": { "pay_amount": {
"name": "Сумма платежа", "name": "Сумма платежа, тыс. руб.",
"key": "q3.pay_amount", "key": "q3.pay_amount",
}, },
"pay_comment": { "pay_comment": {
@ -4703,7 +4703,7 @@ FORM2_DEPTH_COLUMNS: dict = {
}, },
}, },
"fakticheskie_raskhody": { "fakticheskie_raskhody": {
"name": "Фактические расходы", "name": "Фактические расходы, тыс. руб.",
"key": None, "key": None,
"children": { "children": {
"booking": { "booking": {
@ -4741,19 +4741,19 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"transfer_q4": { "transfer_q4": {
"name": "Перенос в 4 квартал", "name": "Перенос в 4 квартал, тыс. руб.",
"key": "q3.transfer_q4", "key": "q3.transfer_q4",
}, },
"transfer_q4_delay_acts": { "transfer_q4_delay_acts": {
"name": "Задержка списания расходов / предоставления актов", "name": "Задержка списания расходов / предоставления актов, тыс. руб.",
"key": "q3.transfer_q4_delay_acts", "key": "q3.transfer_q4_delay_acts",
}, },
"transfer_q4_delay_procurement": { "transfer_q4_delay_procurement": {
"name": "Задержка закупочных процедур", "name": "Задержка закупочных процедур, тыс. руб.",
"key": "q3.transfer_q4_delay_procurement", "key": "q3.transfer_q4_delay_procurement",
}, },
"transfer_q4_economy_rf": { "transfer_q4_economy_rf": {
"name": "Экономия (будет использована РФ)", "name": "Экономия (будет использована РФ), тыс. руб.",
"key": "q3.transfer_q4_economy_rf", "key": "q3.transfer_q4_economy_rf",
}, },
"transfer_next_comment": { "transfer_next_comment": {
@ -4761,11 +4761,11 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q3.transfer_next_comment", "key": "q3.transfer_next_comment",
}, },
"transfer_econ": { "transfer_econ": {
"name": "Перенос в фонд экономии", "name": "Перенос в фонд экономии, тыс. руб.",
"key": "q3.transfer_econ", "key": "q3.transfer_econ",
}, },
"total": { "total": {
"name": "Всего", "name": "Всего, тыс. руб.",
"key": "q3.total", "key": "q3.total",
}, },
}, },
@ -4781,11 +4781,11 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"target_change": { "target_change": {
"name": "Изменение целевого назначения перенесённой экономии (= 0)", "name": "Изменение целевого назначения перенесённой экономии, тыс. руб. (= 0)",
"key": "q4.target_change", "key": "q4.target_change",
}, },
"base_correction": { "base_correction": {
"name": "Корректировка статей базового плана (= 0)", "name": "Корректировка статей базового плана, тыс. руб. (= 0)",
"key": "q4.base_correction", "key": "q4.base_correction",
}, },
"base_correction_comment": { "base_correction_comment": {
@ -4793,11 +4793,11 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q4.base_correction_comment", "key": "q4.base_correction_comment",
}, },
"revision_inc": { "revision_inc": {
"name": "ДФиП: увеличение плана (> 0)", "name": "ДФиП: увеличение плана, тыс. руб. (> 0)",
"key": "q4.revision_inc", "key": "q4.revision_inc",
}, },
"revision_seq": { "revision_seq": {
"name": "ДФиП: секвестр плана (< 0)", "name": "ДФиП: секвестр плана, тыс. руб. (< 0)",
"key": "q4.revision_seq", "key": "q4.revision_seq",
}, },
"revision_comment": { "revision_comment": {
@ -4805,7 +4805,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q4.revision_comment", "key": "q4.revision_comment",
}, },
"new_plan": { "new_plan": {
"name": "Скорректированный план на подпись", "name": "Скорректированный план на подпись, тыс. руб.",
"key": "q4.new_plan", "key": "q4.new_plan",
}, },
}, },
@ -4815,15 +4815,15 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"adj_current": { "adj_current": {
"name": "Текущие корректировки (= 0)", "name": "Текущие корректировки, тыс. руб. (= 0)",
"key": "q4.adj_current", "key": "q4.adj_current",
}, },
"adj_ssp": { "adj_ssp": {
"name": "Корректировка из сметы ГО", "name": "Корректировка из сметы ГО, тыс. руб.",
"key": "q4.adj_ssp", "key": "q4.adj_ssp",
}, },
"adj_reserve": { "adj_reserve": {
"name": "Корректировка из резерва", "name": "Корректировка из резерва, тыс. руб.",
"key": "q4.adj_reserve", "key": "q4.adj_reserve",
}, },
"adj_comment": { "adj_comment": {
@ -4831,7 +4831,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q4.adj_comment", "key": "q4.adj_comment",
}, },
"corrected_plan": { "corrected_plan": {
"name": "Скорректированный план с учётом корректировок", "name": "Скорректированный план с учётом корректировок, тыс. руб.",
"key": "q4.corrected_plan", "key": "q4.corrected_plan",
}, },
}, },
@ -4845,7 +4845,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": "q4.pay_date", "key": "q4.pay_date",
}, },
"pay_amount": { "pay_amount": {
"name": "Сумма платежа", "name": "Сумма платежа, тыс. руб.",
"key": "q4.pay_amount", "key": "q4.pay_amount",
}, },
"pay_comment": { "pay_comment": {
@ -4859,7 +4859,7 @@ FORM2_DEPTH_COLUMNS: dict = {
}, },
}, },
"fakticheskie_raskhody": { "fakticheskie_raskhody": {
"name": "Фактические расходы", "name": "Фактические расходы, тыс. руб.",
"key": None, "key": None,
"children": { "children": {
"booking": { "booking": {
@ -4897,7 +4897,7 @@ FORM2_DEPTH_COLUMNS: dict = {
}, },
}, },
"ekonomiya": { "ekonomiya": {
"name": "Экономия", "name": "Экономия, тыс. руб.",
"key": None, "key": None,
"children": { "children": {
"transfer_econ": { "transfer_econ": {
@ -4913,7 +4913,7 @@ FORM2_DEPTH_COLUMNS: dict = {
"key": None, "key": None,
"children": { "children": {
"fact_year": { "fact_year": {
"name": "Сумма фактических расходов за год", "name": "Сумма фактических расходов за год, тыс. руб.",
"key": "totals.fact_year", "key": "totals.fact_year",
}, },
"pay_year": { "pay_year": {

View File

@ -88,18 +88,40 @@ SHEET_NAMES = {
} }
SKIP_SHEETS = ('OTCH9F',) SKIP_SHEETS = ('OTCH9F',)
# AURORA-1900: текст НДС в экспорте должен совпадать с экраном (вместо текстовой # AURORA-1900: текст НДС в экспорте должен совпадать с экраном (вместо текстовой
# опечатки «с учетом НДC» — Latin C). Ф.1 AHR и все листы Ф.4 показывают суммы без НДС, # опечатки «с учетом НДC» — Latin C). Подколонки ({vat}): Ф.1 AHR и все листы Ф.4 —
# Ф.1 OPER/CAP — с учётом НДС (см. render_column_years(..., extra=...) ниже). # «без НДС», Ф.1 OPER/CAP — «с учётом НДС». Шапка «План {year} (тыс. руб.) {vat_plan}»
# на экране подписана иначе, чем подколонки под ней (возврат 12.11): Ф.1 AHR и Ф.4
# AHR/OPER/STRUCTURE — «с учётом складского НДС», Ф.4 CAP — «с учётом НДС».
# См. render_column_years(..., extra=...) ниже.
FORM1_SHEET_VAT_WORDING = { FORM1_SHEET_VAT_WORDING = {
"OPER": "с учётом НДС", "OPER": "с учётом НДС",
"CAP": "с учётом НДС", "CAP": "с учётом НДС",
"AHR": "без НДС", "AHR": "без НДС",
} }
FORM4_SHEET_VAT_WORDING = "без НДС" FORM4_SHEET_VAT_WORDING = "без НДС"
FORM1_SHEET_PLAN_VAT_WORDING = {
"OPER": "с учётом НДС",
"CAP": "с учётом НДС",
"AHR": "с учётом складского НДС",
}
FORM4_SHEET_PLAN_VAT_WORDING = {
"OPER": "с учётом складского НДС",
"AHR": "с учётом складского НДС",
"CAP": "с учётом НДС",
"STRUCTURE": "с учётом складского НДС",
}
def _vat_placeholders(vat_phrase: str) -> dict[str, str]: def _vat_placeholders(vat_phrase: str, plan_vat_phrase: str) -> dict[str, str]:
return {"vat": vat_phrase, "vat_plan": vat_phrase} return {"vat": vat_phrase, "vat_plan": plan_vat_phrase}
def _form1_vat(sheet: str) -> dict[str, str]:
return _vat_placeholders(FORM1_SHEET_VAT_WORDING[sheet], FORM1_SHEET_PLAN_VAT_WORDING[sheet])
def _form4_vat(sheet: str) -> dict[str, str]:
return _vat_placeholders(FORM4_SHEET_VAT_WORDING, FORM4_SHEET_PLAN_VAT_WORDING[sheet])
BULK_EXPORT_CONCURRENCY = 2 BULK_EXPORT_CONCURRENCY = 2
EXPORT_TMPDIR = os.getenv("EXPORT_TMPDIR") or tempfile.gettempdir() EXPORT_TMPDIR = os.getenv("EXPORT_TMPDIR") or tempfile.gettempdir()
@ -954,7 +976,7 @@ class ExportService:
default_writer = XlsxDepthSheetWriter( default_writer = XlsxDepthSheetWriter(
workbook=workbook, workbook=workbook,
columns_dict=render_column_years( columns_dict=render_column_years(
FORM1_DEPTH_COLUMNS, year, _vat_placeholders(FORM1_SHEET_VAT_WORDING["OPER"]) FORM1_DEPTH_COLUMNS, year, _form1_vat("OPER")
), ),
row_normalizer=self.form1_row_normalizer.normalize, row_normalizer=self.form1_row_normalizer.normalize,
value_serializer=self.value_serializer.to_excel_value, value_serializer=self.value_serializer.to_excel_value,
@ -973,25 +995,25 @@ class ExportService:
"OPER": ( "OPER": (
FORM4_OPER_DEPTH_COLUMNS, FORM4_OPER_DEPTH_COLUMNS,
FORM4_OPER_COLORS, FORM4_OPER_COLORS,
_vat_placeholders(FORM4_SHEET_VAT_WORDING), _form4_vat("OPER"),
self.form4_row_normalizer, self.form4_row_normalizer,
), ),
"AHR": ( "AHR": (
FORM4_DEPTH_COLUMNS, FORM4_DEPTH_COLUMNS,
FORM4_COLORS["AHR"], FORM4_COLORS["AHR"],
_vat_placeholders(FORM4_SHEET_VAT_WORDING), _form4_vat("AHR"),
self.form4_row_normalizer, self.form4_row_normalizer,
), ),
"CAP": ( "CAP": (
FORM4_DEPTH_COLUMNS, FORM4_DEPTH_COLUMNS,
FORM4_COLORS["CAP"], FORM4_COLORS["CAP"],
_vat_placeholders(FORM4_SHEET_VAT_WORDING), _form4_vat("CAP"),
self.form4_row_normalizer, self.form4_row_normalizer,
), ),
"STRUCTURE": ( "STRUCTURE": (
FORM4_STRUCTURE_DEPTH_COLUMNS, FORM4_STRUCTURE_DEPTH_COLUMNS,
FORM4_STRUCTURE_COLORS, FORM4_STRUCTURE_COLORS,
_vat_placeholders(FORM4_SHEET_VAT_WORDING), _form4_vat("STRUCTURE"),
self.form1_row_normalizer, self.form1_row_normalizer,
), ),
} }
@ -1010,7 +1032,7 @@ class ExportService:
"STRUCTURE": ( "STRUCTURE": (
FORM4_STRUCTURE_DEPTH_COLUMNS, FORM4_STRUCTURE_DEPTH_COLUMNS,
FORM4_STRUCTURE_COLORS, FORM4_STRUCTURE_COLORS,
_vat_placeholders(FORM4_SHEET_VAT_WORDING), _form4_vat("STRUCTURE"),
self.form1_row_normalizer, self.form1_row_normalizer,
), ),
} }
@ -1020,19 +1042,19 @@ class ExportService:
"OPER": ( "OPER": (
FORM1_OPER_DEPTH_COLUMNS, FORM1_OPER_DEPTH_COLUMNS,
FORM1_COLORS["OPER"], FORM1_COLORS["OPER"],
_vat_placeholders(FORM1_SHEET_VAT_WORDING["OPER"]), _form1_vat("OPER"),
self.form1_row_normalizer, self.form1_row_normalizer,
), ),
"AHR": ( "AHR": (
FORM1_DEPTH_COLUMNS, FORM1_DEPTH_COLUMNS,
FORM1_COLORS["AHR"], FORM1_COLORS["AHR"],
_vat_placeholders(FORM1_SHEET_VAT_WORDING["AHR"]), _form1_vat("AHR"),
self.form1_row_normalizer, self.form1_row_normalizer,
), ),
"CAP": ( "CAP": (
FORM1_DEPTH_COLUMNS, FORM1_DEPTH_COLUMNS,
FORM1_COLORS["CAP"], FORM1_COLORS["CAP"],
_vat_placeholders(FORM1_SHEET_VAT_WORDING["CAP"]), _form1_vat("CAP"),
self.form1_row_normalizer, self.form1_row_normalizer,
), ),
"AHR_RENT": (FORM2_DEPTH_AHR_SUB_COLUMNS, None, None, self.form1_row_normalizer), "AHR_RENT": (FORM2_DEPTH_AHR_SUB_COLUMNS, None, None, self.form1_row_normalizer),
@ -1041,7 +1063,7 @@ class ExportService:
"STRUCTURE": ( "STRUCTURE": (
FORM4_STRUCTURE_DEPTH_COLUMNS, FORM4_STRUCTURE_DEPTH_COLUMNS,
FORM4_STRUCTURE_COLORS, FORM4_STRUCTURE_COLORS,
_vat_placeholders(FORM4_SHEET_VAT_WORDING), _form4_vat("STRUCTURE"),
self.form1_row_normalizer, self.form1_row_normalizer,
), ),
} }

View File

@ -78,7 +78,7 @@ def test_form1_oper_has_no_future_y2_but_ahr_cap_do():
def test_render_column_years_form4(): def test_render_column_years_form4():
rendered = render_column_years(FORM4_DEPTH_COLUMNS, 2026) rendered = render_column_years(FORM4_DEPTH_COLUMNS, 2026)
assert _tree_node(rendered, "upper_ssp_smeta.contract_summary.future_y2")["name"] == "Платежи в 2028 году" assert _tree_node(rendered, "upper_ssp_smeta.contract_summary.future_y2")["name"] == "Платежи в 2028 году, тыс. руб."
assert _tree_node(rendered, "approved.plan")["name"] == "План 2026" assert _tree_node(rendered, "approved.plan")["name"] == "План 2026"
assert _tree_node(rendered, "q4.quarter_correct")["name"] == "Факт РСБУ 4 кв 26" assert _tree_node(rendered, "q4.quarter_correct")["name"] == "Факт РСБУ 4 кв 26"
@ -87,7 +87,7 @@ def test_render_column_years_form4_structure():
rendered = render_column_years(FORM4_STRUCTURE_DEPTH_COLUMNS, 2026) rendered = render_column_years(FORM4_STRUCTURE_DEPTH_COLUMNS, 2026)
assert ( assert (
_tree_node(rendered, "upper_ssp_smeta.contract_summary.future_payments_y2")["name"] _tree_node(rendered, "upper_ssp_smeta.contract_summary.future_payments_y2")["name"]
== "Платежи в 2028 году" == "Платежи в 2028 году, тыс. руб."
) )

View File

@ -122,7 +122,7 @@ def test_render_column_years_resolves_form2_top_group_names():
rendered = render_column_years(FORM2_DEPTH_COLUMNS, 2026) rendered = render_column_years(FORM2_DEPTH_COLUMNS, 2026)
names = {node["name"] for node in rendered.values()} names = {node["name"] for node in rendered.values()}
assert "2026 год" in names assert "2026 год" in names
assert "2027 год" in names assert "2027 год, тыс. руб." in names
assert "1 квартал 2026 года" in names assert "1 квартал 2026 года" in names
# Original templates are untouched (render_column_years deep-copies). # Original templates are untouched (render_column_years deep-copies).
assert any("{year}" in node["name"] for node in FORM2_DEPTH_COLUMNS.values() if "{year}" in node.get("name", "")) assert any("{year}" in node["name"] for node in FORM2_DEPTH_COLUMNS.values() if "{year}" in node.get("name", ""))

View File

@ -0,0 +1,123 @@
"""AURORA-1900 (возврат 12.11): подписи единиц и НДС в выгрузке Excel так же, как на экране.
Проверяются раскладки, которые реально использует выгрузка формы
(ExportService._create_xlsxwriter_sheet_writers), после подстановки года и НДС:
- у каждой денежной колонки «тыс. руб.» есть в её заголовке или в заголовке группы;
- у текстовых колонок (комментарии, коды, реквизиты) денежной подписи нет;
- подпись НДС в шапке плана совпадает с экраном того же листа.
Деление на денежные и текстовые колонки повторяет web/src/components/RealtimeTable/
utils/moneyUtils.js (getMoneyUnit/isTextColumn) по последнему сегменту ключа.
"""
from unittest.mock import MagicMock
import pytest
from src.services.export_service.export_service import ExportService
TEXT_LAST_SEGMENTS = {
"section", "section_code", "section_no", "kod_razdela", "item_id", "num_group", "num_group_id",
"internal_order", "contract_number", "vsp_id", "pay_year", "order", "expense_item_code", "id_stati",
"comment", "justification", "note", "name", "counterparty", "subject", "reference", "contract_ref",
"deadline", "delivery_deadline", "date", "pay_date", "contract_end_date", "currency", "act", "pay_act",
"scheme", "payment_scheme", "rf_schedule", "addenda", "proc_plan", "proc_method", "procurement_plan",
"procurement_method", "property", "allocation_purpose", "protocol", "protocol_reference",
"centralized_flag", "object_type", "unit", "address", "equipment_name",
}
NUMERIC_NON_MONEY_LAST_SEGMENTS = {
"qty_q1", "qty_q2", "qty_q3", "qty_q4", "rented_area", "vat_rate", "exchange_rate", "amount_foreign",
}
# Текст шапки «План {year} (…) <НДС>» на экране (web/src/components/RealtimeTable/constants).
SCREEN_PLAN_VAT = {
("FORM_1", "OPER"): "с учётом НДС",
("FORM_1", "CAP"): "с учётом НДС",
("FORM_1", "AHR"): "с учётом складского НДС",
("FORM_1", "STRUCTURE"): "с учётом складского НДС",
("FORM_2", "STRUCTURE"): "с учётом складского НДС",
("FORM_4", "OPER"): "с учётом складского НДС",
("FORM_4", "AHR"): "с учётом складского НДС",
("FORM_4", "CAP"): "с учётом НДС",
("FORM_4", "STRUCTURE"): "с учётом складского НДС",
}
FORMS = ("FORM_1", "FORM_2", "FORM_4")
def _kind(flat_key: str) -> str:
last = flat_key.rsplit(".", 1)[-1]
if last in TEXT_LAST_SEGMENTS or last.endswith("_comment"):
return "text"
if last in NUMERIC_NON_MONEY_LAST_SEGMENTS or last.endswith("_qty"):
return "number"
return "money"
def _has_unit(label) -> bool:
return isinstance(label, str) and "руб" in label
def _leaves(columns: dict, prefix: str = "", trail: tuple = ()) -> list[tuple[str, tuple]]:
# Ключи собираются так же, как XlsxDepthSheetWriter._columns_dict_to_flat.
result = []
for key, node in columns.items():
new_key = node.get("key", key)
path = (*trail, node.get("name", ""))
if "children" in node:
result += _leaves(node["children"], f"{prefix}{new_key}." if new_key else prefix, path)
elif new_key:
result.append((f"{prefix}{new_key}", path))
return result
def _sheet_writers():
service = ExportService(MagicMock())
for form in FORMS:
writers = service._create_xlsxwriter_sheet_writers(MagicMock(), form, 2026)
for sheet, writer in writers.items():
if sheet != "__default__":
yield form, sheet, writer
SHEET_WRITERS = list(_sheet_writers())
IDS = [f"{form}/{sheet}" for form, sheet, _ in SHEET_WRITERS]
def _labelled_leaves(writer) -> list[tuple[str, tuple]]:
if hasattr(writer, "columns_dict"):
return _leaves(writer.columns_dict)
return [(key, (label,)) for key, label in writer.columns]
@pytest.mark.parametrize(("form", "sheet", "writer"), SHEET_WRITERS, ids=IDS)
def test_every_money_column_has_unit(form, sheet, writer):
missing = [
f"{key}: {' → '.join(path)}"
for key, path in _labelled_leaves(writer)
if _kind(key) == "money" and not any(map(_has_unit, path))
]
assert missing == []
@pytest.mark.parametrize(("form", "sheet", "writer"), SHEET_WRITERS, ids=IDS)
def test_text_columns_have_no_money_label(form, sheet, writer):
labelled = [
f"{key}: {' → '.join(path)}"
for key, path in _labelled_leaves(writer)
if _kind(key) == "text" and _has_unit(path[-1])
]
assert labelled == []
@pytest.mark.parametrize(("form", "sheet"), sorted(SCREEN_PLAN_VAT), ids=[f"{f}/{s}" for f, s in sorted(SCREEN_PLAN_VAT)])
def test_plan_header_vat_matches_screen(form, sheet):
writer = next(w for f, s, w in SHEET_WRITERS if (f, s) == (form, sheet))
plan_headers = {
label
for _key, path in _leaves(writer.columns_dict)
for label in path
# «Итоговая смета расходов → План 2026, …» — другая группа, без НДС в тексте.
if isinstance(label, str) and label.startswith("План 2026") and "НДС" in label
}
assert plan_headers == {f"План 2026 (тыс. руб.) {SCREEN_PLAN_VAT[(form, sheet)]}"}

View File

@ -1402,7 +1402,7 @@ export const config = {
accessorKey: 'field_kollegialnye_organy', accessorKey: 'field_kollegialnye_organy',
columns: [ columns: [
{ {
header: 'Сумма (без НДС)', header: 'Сумма, {unit} (без НДС)',
accessorKey: 'data.collegial.approved', accessorKey: 'data.collegial.approved',
columnLetter: 'AS', columnLetter: 'AS',
size: 150, size: 150,

View File

@ -1390,7 +1390,7 @@ export const config = {
accessorKey: 'field_kollegialnye_organy', accessorKey: 'field_kollegialnye_organy',
columns: [ columns: [
{ {
header: 'Сумма (без НДС)', header: 'Сумма, {unit} (без НДС)',
accessorKey: 'data.collegial.approved', accessorKey: 'data.collegial.approved',
columnLetter: 'AS', columnLetter: 'AS',
size: 150, size: 150,

View File

@ -1410,7 +1410,7 @@ export const config = {
accessorKey: 'field_kollegialnye_organy', accessorKey: 'field_kollegialnye_organy',
columns: [ columns: [
{ {
header: 'Сумма (с учётом НДС)', header: 'Сумма, {unit} (с учётом НДС)',
accessorKey: 'data.collegial.approved', accessorKey: 'data.collegial.approved',
columnLetter: 'AS', columnLetter: 'AS',
size: 200, size: 200,

View File

@ -1410,7 +1410,7 @@ export const config = {
accessorKey: 'field_kollegialnye_organy', accessorKey: 'field_kollegialnye_organy',
columns: [ columns: [
{ {
header: 'Сумма (с учётом НДС)', header: 'Сумма, {unit} (с учётом НДС)',
accessorKey: 'data.collegial.approved', accessorKey: 'data.collegial.approved',
columnLetter: 'AS', columnLetter: 'AS',
size: 200, size: 200,

View File

@ -1393,7 +1393,7 @@ export const config = {
accessorKey: 'field_kollegialnye_organy', accessorKey: 'field_kollegialnye_organy',
columns: [ columns: [
{ {
header: 'Сумма (с учётом НДС)', header: 'Сумма, {unit} (с учётом НДС)',
accessorKey: 'data.collegial.approved', accessorKey: 'data.collegial.approved',
columnLetter: 'AS', columnLetter: 'AS',
size: 200, size: 200,

View File

@ -1490,7 +1490,7 @@ export const config = {
accessorKey: "field_kollegialnye_organy", accessorKey: "field_kollegialnye_organy",
columns: [ columns: [
{ {
header: "Сумма (без НДС)", header: "Сумма, {unit} (без НДС)",
accessorKey: "data.collegial.approved", accessorKey: "data.collegial.approved",
columnLetter: "AS", columnLetter: "AS",
size: 150, size: 150,

View File

@ -1490,7 +1490,7 @@ export const config = {
accessorKey: "field_kollegialnye_organy", accessorKey: "field_kollegialnye_organy",
columns: [ columns: [
{ {
header: "Сумма (без НДС)", header: "Сумма, {unit} (без НДС)",
accessorKey: "data.collegial.approved", accessorKey: "data.collegial.approved",
columnLetter: "AS", columnLetter: "AS",
size: 150, size: 150,

View File

@ -1491,7 +1491,7 @@ export const config = {
accessorKey: "field_kollegialnye_organy", accessorKey: "field_kollegialnye_organy",
columns: [ columns: [
{ {
header: "Сумма (без НДС)", header: "Сумма, {unit} (без НДС)",
accessorKey: "data.collegial.approved", accessorKey: "data.collegial.approved",
columnLetter: "AS", columnLetter: "AS",
size: 150, size: 150,

View File

@ -105,7 +105,6 @@ const TEXT_LAST_SEGMENTS = new Set([
'allocation_purpose', 'allocation_purpose',
'protocol', 'protocol',
'protocol_reference', 'protocol_reference',
'approved',
'centralized_flag', 'centralized_flag',
'object_type', 'object_type',
'unit', 'unit',

View File

@ -20,6 +20,10 @@ test('getDisplayFactor converts between units with thousand as the base', () =>
test('getMoneyUnit defaults ordinary money columns to thousand', () => { test('getMoneyUnit defaults ordinary money columns to thousand', () => {
assert.equal(getMoneyUnit({ accessorKey: 'data.plan.q1' }, 'AHR'), 'thousand'); assert.equal(getMoneyUnit({ accessorKey: 'data.plan.q1' }, 'AHR'), 'thousand');
// AURORA-1900 (возврат 12.11): «Коллегиальные органы → Сумма (без НДС)» —
// v3 collegial_approval.approved_amount NUMERIC, денежная колонка, а не флаг.
assert.equal(getMoneyUnit({ accessorKey: 'data.collegial.approved' }, 'AHR'), 'thousand');
assert.equal(isTextColumn({ accessorKey: 'data.collegial.approved' }), false);
}); });
test('getMoneyUnit returns null for quantities, rates, codes and ids regardless of sheet', () => { test('getMoneyUnit returns null for quantities, rates, codes and ids regardless of sheet', () => {
@ -95,7 +99,6 @@ test('getMoneyUnit returns null for text columns: comments, justifications and o
'data.ckk.proc_method', 'data.ckk.proc_method',
'data.allocation.property', 'data.allocation.property',
'data.collegial.protocol', 'data.collegial.protocol',
'data.collegial.approved',
'data.contract_summary.centralized_flag', 'data.contract_summary.centralized_flag',
'data.contract_end_date', 'data.contract_end_date',
'data.object_type', 'data.object_type',