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

View File

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

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',
columns: [
{
header: 'Сумма (без НДС)',
header: 'Сумма, {unit} (без НДС)',
accessorKey: 'data.collegial.approved',
columnLetter: 'AS',
size: 150,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -105,7 +105,6 @@ const TEXT_LAST_SEGMENTS = new Set([
'allocation_purpose',
'protocol',
'protocol_reference',
'approved',
'centralized_flag',
'object_type',
'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', () => {
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', () => {
@ -95,7 +99,6 @@ test('getMoneyUnit returns null for text columns: comments, justifications and o
'data.ckk.proc_method',
'data.allocation.property',
'data.collegial.protocol',
'data.collegial.approved',
'data.contract_summary.centralized_flag',
'data.contract_end_date',
'data.object_type',