fix(export): Смета РФ — выгрузка Операц/АХР/КВ своей раскладкой, не формы 1
Листы Операц/АХР/КВ формы «Смета РФ» (FORM_2) выгружались раскладкой формы 1
(FORM1_DEPTH_COLUMNS, 180 колонок формы 1) — из 163 колонок экрана совпадали
по ключу только 83, 97 колонок выгрузки были всегда пустыми (блоки формы 1,
которых нет в данных формы 2), заголовки групп — от формы 1. Также на экране
был лишний столбец «Адрес ВСП», которого нет в исходной форме РФ, и ВСП
выгружался бы числовым id вместо номера, который виден на экране.
- FORM2_DEPTH_COLUMNS/FORM2_COLORS (export_mappings.py) — сгенерированы из
web/src/.../FORM_2/{OPER,AHR,CAP}.js (колонки идентичны у всех трёх листов,
проверено программно; различаются только цвета). 162 листа дерева —
байт-в-байт тот же порядок, что на экране, минус header.vsp_address.
Каждая группа — чисто визуальная обёртка ("key": None), каждый лист несёт
полный путь данных как "key" (например "booking.y2027.q1"), поэтому порядок
вложенности групп для Excel-заголовков не завязан на реальный путь к данным.
- Form2DepthRowNormalizer (export_normalizers.py) — в отличие от
Form1DepthRowNormalizer, не поднимает header.* на верхний уровень: FORM_2
ожидает их как header.section_code, header.vsp_id и т.д.
- _create_xlsxwriter_sheet_writers: отдельная ветка для FormTypeEnum.FORM_2
(раньше FORM_2 попадала в общий else вместе с FORM_1). Сателлиты АХР
(AHR_RENT/UTILITY/SECURITY, AURORA-1904) и STRUCTURE не трогал.
- VSPRepository.get_reg_numbers_by_ids + ExportService._resolve_form2_vsp_numbers:
один запрос резолвит все встретившиеся header.vsp_id (включая закрытые ВСП)
в registration_number перед записью в Excel, как уже показывает экран.
- web/.../FORM_2/{OPER,AHR,CAP}.js: убран столбец «Адрес ВСП» (E1) и его цвет —
по умолчанию, т.к. его нет в исходной форме РФ (см. Rulings в PR). Убрана
мёртвая ссылка на него в Stages/constants.js.
Issue: #252 (AURORA-1902, эпик AURORA-1876, Высокий приоритет)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
2a287480b6
commit
daed54d436
@ -14,6 +14,17 @@ class VSPRepository:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
|
||||
async def get_reg_numbers_by_ids(self, vsp_ids: list[int]) -> dict[int, str]:
|
||||
"""Номера ВСП по id, включая закрытые (is_active=False), для подписи в экспорте."""
|
||||
if not vsp_ids:
|
||||
return {}
|
||||
rows = (
|
||||
await self.db.execute(
|
||||
select(Vsp.id, Vsp.reg_number).where(Vsp.id.in_(vsp_ids), Vsp.is_deleted.is_(False))
|
||||
)
|
||||
).all()
|
||||
return {row.id: row.reg_number for row in rows}
|
||||
|
||||
async def get(
|
||||
self,
|
||||
vsp_id: int,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -249,6 +249,34 @@ class SmetaRowNormalizer:
|
||||
return cur
|
||||
|
||||
|
||||
class Form2DepthRowNormalizer:
|
||||
"""Для FORM_2 (Операц/АХР/КВ Сметы РФ): в отличие от Form1DepthRowNormalizer,
|
||||
поля header.* не поднимаются на верхний уровень — FORM_2_DEPTH_COLUMNS
|
||||
ожидает их как header.section_code, header.vsp_id и т.д."""
|
||||
|
||||
def __init__(self, serializer: ExcelValueSerializer):
|
||||
self.serializer = serializer
|
||||
|
||||
def normalize(self, data: dict[str, Any]) -> dict[str, Any]:
|
||||
normalized: dict[str, Any] = {}
|
||||
self._normalize_depth(normalized=normalized, original=data)
|
||||
return normalized
|
||||
|
||||
def _normalize_depth(
|
||||
self,
|
||||
normalized: dict[str, Any],
|
||||
original: dict[str, Any],
|
||||
prefix: str = "",
|
||||
) -> None:
|
||||
for key, value in original.items():
|
||||
if key == "line_id":
|
||||
continue
|
||||
if isinstance(value, dict):
|
||||
self._normalize_depth(normalized=normalized, original=value, prefix=f"{prefix}{key}.")
|
||||
else:
|
||||
normalized[f"{prefix}{key}"] = value
|
||||
|
||||
|
||||
class Form3DepthRowNormalizer:
|
||||
def __init__(self, serializer: ExcelValueSerializer):
|
||||
self.serializer = serializer
|
||||
|
||||
@ -27,13 +27,16 @@ from src.services.budget_form_service import BudgetFormService
|
||||
from src.services.export_service.export_normalizers import (
|
||||
ExcelValueSerializer,
|
||||
Form1DepthRowNormalizer,
|
||||
Form2DepthRowNormalizer,
|
||||
Form3DepthRowNormalizer,
|
||||
SmetaRowNormalizer,
|
||||
)
|
||||
from src.services.export_service.export_mappings import (
|
||||
FORM1_COLORS,
|
||||
FORM1_DEPTH_COLUMNS,
|
||||
FORM2_COLORS,
|
||||
FORM2_DEPTH_AHR_SUB_COLUMNS,
|
||||
FORM2_DEPTH_COLUMNS,
|
||||
FORM3_COLORS,
|
||||
FORM4_COLORS,
|
||||
FORM4_DEPTH_COLUMNS,
|
||||
@ -58,6 +61,7 @@ from src.services.export_service.export_writers import (
|
||||
from src.services.project_service import ProjectService
|
||||
from src.services.sheet_service import SheetService
|
||||
from src.services.summary_service import SummaryService
|
||||
from src.repository.vsp_repository import VSPRepository
|
||||
|
||||
XLSX_MEDIA_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
ZIP_MEDIA_TYPE = "application/zip"
|
||||
@ -100,8 +104,10 @@ class ExportService:
|
||||
self.sheet_service = SheetService(db)
|
||||
self.project_service = ProjectService(db)
|
||||
self.summary_service = SummaryService(db)
|
||||
self.vsp_repo = VSPRepository(db)
|
||||
self.value_serializer = ExcelValueSerializer()
|
||||
self.form1_row_normalizer = Form1DepthRowNormalizer(self.value_serializer)
|
||||
self.form2_row_normalizer = Form2DepthRowNormalizer(self.value_serializer)
|
||||
self.form3_row_normalizer = Form3DepthRowNormalizer(self.value_serializer)
|
||||
self.smeta_row_normalizer = SmetaRowNormalizer()
|
||||
self.sheet_writers: dict[str, TabularSheetWriter] = {
|
||||
@ -441,6 +447,8 @@ class ExportService:
|
||||
direction=plan.effective_direction,
|
||||
sections=plan.effective_sections,
|
||||
)
|
||||
if form.form_type.code == FormTypeEnum.FORM_2.value and plan.sheet_name in ("OPER", "AHR", "CAP"):
|
||||
await self._resolve_form2_vsp_numbers(rows)
|
||||
sheet_loaded_at = perf_counter()
|
||||
|
||||
# Этап 4: рендер листа уводим с event loop, чтобы экспорт не блокировал API.
|
||||
@ -897,6 +905,28 @@ class ExportService:
|
||||
"right_color": "#9EAFA3" if is_group_end else "#EDF0F2",
|
||||
}
|
||||
|
||||
async def _resolve_form2_vsp_numbers(self, rows: list[tuple]) -> None:
|
||||
"""На экране колонка «ВСП РФ» показывает registration_number, а не id,
|
||||
который реально лежит в data.header.vsp_id — подменяем перед записью
|
||||
в Excel, одним запросом на все встретившиеся id (включая закрытые ВСП)."""
|
||||
headers_with_vsp: list[dict] = []
|
||||
vsp_ids: set[int] = set()
|
||||
for row in rows:
|
||||
if len(row) <= 3 or not isinstance(row[3], dict):
|
||||
continue
|
||||
header = row[3].get("header")
|
||||
vsp_id = header.get("vsp_id") if isinstance(header, dict) else None
|
||||
if isinstance(vsp_id, int):
|
||||
headers_with_vsp.append(header)
|
||||
vsp_ids.add(vsp_id)
|
||||
if not vsp_ids:
|
||||
return
|
||||
reg_numbers = await self.vsp_repo.get_reg_numbers_by_ids(list(vsp_ids))
|
||||
for header in headers_with_vsp:
|
||||
reg_number = reg_numbers.get(header["vsp_id"])
|
||||
if reg_number is not None:
|
||||
header["vsp_id"] = reg_number
|
||||
|
||||
def _create_xlsxwriter_sheet_writers(
|
||||
self,
|
||||
workbook: XlsxWorkbook,
|
||||
@ -920,26 +950,38 @@ class ExportService:
|
||||
}
|
||||
if form_type_code == FormTypeEnum.FORM_4.value:
|
||||
writer_configs = {
|
||||
"OPER": (FORM4_OPER_DEPTH_COLUMNS, FORM4_OPER_COLORS),
|
||||
"AHR": (FORM4_DEPTH_COLUMNS, FORM4_COLORS["AHR"]),
|
||||
"CAP": (FORM4_DEPTH_COLUMNS, FORM4_COLORS["CAP"]),
|
||||
"STRUCTURE": (FORM4_STRUCTURE_DEPTH_COLUMNS, FORM4_STRUCTURE_COLORS),
|
||||
"OPER": (FORM4_OPER_DEPTH_COLUMNS, FORM4_OPER_COLORS, self.form1_row_normalizer),
|
||||
"AHR": (FORM4_DEPTH_COLUMNS, FORM4_COLORS["AHR"], self.form1_row_normalizer),
|
||||
"CAP": (FORM4_DEPTH_COLUMNS, FORM4_COLORS["CAP"], self.form1_row_normalizer),
|
||||
"STRUCTURE": (FORM4_STRUCTURE_DEPTH_COLUMNS, FORM4_STRUCTURE_COLORS, self.form1_row_normalizer),
|
||||
}
|
||||
elif form_type_code == FormTypeEnum.FORM_2.value:
|
||||
# AURORA-1902: FORM_2 (Операц/АХР/КВ Сметы РФ) имеет свою раскладку колонок
|
||||
# (v3.v_form2_view), раньше ошибочно экспортировалась раскладкой формы 1.
|
||||
writer_configs = {
|
||||
"OPER": (FORM2_DEPTH_COLUMNS, FORM2_COLORS["OPER"], self.form2_row_normalizer),
|
||||
"AHR": (FORM2_DEPTH_COLUMNS, FORM2_COLORS["AHR"], self.form2_row_normalizer),
|
||||
"CAP": (FORM2_DEPTH_COLUMNS, FORM2_COLORS["CAP"], self.form2_row_normalizer),
|
||||
"AHR_RENT": (FORM2_DEPTH_AHR_SUB_COLUMNS, None, self.form1_row_normalizer),
|
||||
"AHR_UTILITY": (FORM2_DEPTH_AHR_SUB_COLUMNS, None, self.form1_row_normalizer),
|
||||
"AHR_SECURITY": (FORM2_DEPTH_AHR_SUB_COLUMNS, None, self.form1_row_normalizer),
|
||||
"STRUCTURE": (FORM4_STRUCTURE_DEPTH_COLUMNS, FORM4_STRUCTURE_COLORS, self.form1_row_normalizer),
|
||||
}
|
||||
else:
|
||||
writer_configs = {
|
||||
"OPER": (FORM1_DEPTH_COLUMNS, FORM1_COLORS["OPER"]),
|
||||
"AHR": (FORM1_DEPTH_COLUMNS, FORM1_COLORS["AHR"]),
|
||||
"CAP": (FORM1_DEPTH_COLUMNS, FORM1_COLORS["CAP"]),
|
||||
"AHR_RENT": (FORM2_DEPTH_AHR_SUB_COLUMNS, None),
|
||||
"AHR_UTILITY": (FORM2_DEPTH_AHR_SUB_COLUMNS, None),
|
||||
"AHR_SECURITY": (FORM2_DEPTH_AHR_SUB_COLUMNS, None),
|
||||
"STRUCTURE": (FORM4_STRUCTURE_DEPTH_COLUMNS, FORM4_STRUCTURE_COLORS),
|
||||
"OPER": (FORM1_DEPTH_COLUMNS, FORM1_COLORS["OPER"], self.form1_row_normalizer),
|
||||
"AHR": (FORM1_DEPTH_COLUMNS, FORM1_COLORS["AHR"], self.form1_row_normalizer),
|
||||
"CAP": (FORM1_DEPTH_COLUMNS, FORM1_COLORS["CAP"], self.form1_row_normalizer),
|
||||
"AHR_RENT": (FORM2_DEPTH_AHR_SUB_COLUMNS, None, self.form1_row_normalizer),
|
||||
"AHR_UTILITY": (FORM2_DEPTH_AHR_SUB_COLUMNS, None, self.form1_row_normalizer),
|
||||
"AHR_SECURITY": (FORM2_DEPTH_AHR_SUB_COLUMNS, None, self.form1_row_normalizer),
|
||||
"STRUCTURE": (FORM4_STRUCTURE_DEPTH_COLUMNS, FORM4_STRUCTURE_COLORS, self.form1_row_normalizer),
|
||||
}
|
||||
for sheet_name, (columns, colors) in writer_configs.items():
|
||||
for sheet_name, (columns, colors, row_normalizer) in writer_configs.items():
|
||||
writers[sheet_name] = XlsxDepthSheetWriter(
|
||||
workbook=workbook,
|
||||
columns_dict=render_column_years(columns, year),
|
||||
row_normalizer=self.form1_row_normalizer.normalize,
|
||||
row_normalizer=row_normalizer.normalize,
|
||||
value_serializer=self.value_serializer.to_excel_value,
|
||||
colors_config=colors,
|
||||
)
|
||||
|
||||
175
api/tests/unit/test_export_form2_mapping.py
Normal file
175
api/tests/unit/test_export_form2_mapping.py
Normal file
@ -0,0 +1,175 @@
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
from xlsxwriter import Workbook as XlsxWorkbook
|
||||
|
||||
from src.db.models.form_type import FormTypeEnum
|
||||
from src.services.export_service import ExportService
|
||||
from src.services.export_service.export_mappings import (
|
||||
FORM1_DEPTH_COLUMNS,
|
||||
FORM2_COLORS,
|
||||
FORM2_DEPTH_COLUMNS,
|
||||
render_column_years,
|
||||
)
|
||||
from src.services.export_service.export_normalizers import (
|
||||
ExcelValueSerializer,
|
||||
Form2DepthRowNormalizer,
|
||||
)
|
||||
from src.services.export_service.export_writers import XlsxDepthSheetWriter
|
||||
|
||||
# Ожидаемый порядок листьев = порядок колонок экрана (web/src/.../FORM_2/OPER.js),
|
||||
# минус "header.vsp_address" (AURORA-1902: лишний столбец, которого нет в форме РФ).
|
||||
EXPECTED_LEAF_ORDER = [
|
||||
"header.section_code", "header.item_id", "header.num_group", "header.name", "header.vsp_id",
|
||||
"plan.q1", "plan.q2", "plan.q3", "plan.q4", "plan.year", "plan.comment",
|
||||
"seq_dfip.q1", "seq_dfip.q2", "seq_dfip.q3", "seq_dfip.q4", "seq_dfip.year", "seq_dfip.justification",
|
||||
"seq_ssp.q1", "seq_ssp.q2", "seq_ssp.q3", "seq_ssp.q4", "seq_ssp.year", "seq_ssp.justification",
|
||||
"approved.q1", "approved.q2", "approved.q3", "approved.q4", "approved.year",
|
||||
"contract.counterparty", "contract.reference", "contract.date", "contract.subject",
|
||||
"contract.currency", "contract.ceiling", "contract.vat_rate", "contract.deadline",
|
||||
"contract.scheme", "contract.act", "contract.comment",
|
||||
"booking.y2026.q1", "booking.y2026.q2", "booking.y2026.q3", "booking.y2026.q4",
|
||||
"booking.y2027.q1", "booking.y2027.q2", "booking.y2027.q3", "booking.y2027.q4",
|
||||
"q1.adj_current", "q1.adj_ssp", "q1.adj_reserve", "q1.adj_comment", "q1.corrected_plan",
|
||||
"q1.pay_date", "q1.pay_amount", "q1.pay_comment", "q1.pay_act",
|
||||
"q1.booking", "q1.actual_m1", "q1.actual_m2", "q1.actual_m3", "q1.actual_quarter",
|
||||
"q1.residual_after_booking", "q1.residual_after_actual",
|
||||
"q1.transfer_q2", "q1.transfer_q2_delay_acts", "q1.transfer_q2_delay_procurement",
|
||||
"q1.transfer_q2_economy_rf", "q1.transfer_next_comment", "q1.transfer_q3", "q1.transfer_q4",
|
||||
"q1.transfer_far_comment", "q1.transfer_econ", "q1.total",
|
||||
"q2.target_change", "q2.base_correction", "q2.base_correction_comment",
|
||||
"q2.revision_inc", "q2.revision_seq", "q2.revision_comment", "q2.new_plan",
|
||||
"q2.adj_current", "q2.adj_ssp", "q2.adj_reserve", "q2.adj_comment", "q2.corrected_plan",
|
||||
"q2.pay_date", "q2.pay_amount", "q2.pay_comment", "q2.pay_act",
|
||||
"q2.booking", "q2.actual_m1", "q2.actual_m2", "q2.actual_m3", "q2.actual_quarter",
|
||||
"q2.residual_after_booking", "q2.residual_after_actual",
|
||||
"q2.transfer_q3", "q2.transfer_q3_delay_acts", "q2.transfer_q3_delay_procurement",
|
||||
"q2.transfer_q3_economy_rf", "q2.transfer_next_comment", "q2.transfer_q4",
|
||||
"q2.transfer_far_comment", "q2.transfer_econ", "q2.total",
|
||||
"q3.target_change", "q3.base_correction", "q3.base_correction_comment",
|
||||
"q3.revision_inc", "q3.revision_seq", "q3.revision_comment", "q3.new_plan",
|
||||
"q3.adj_current", "q3.adj_ssp", "q3.adj_reserve", "q3.adj_comment", "q3.corrected_plan",
|
||||
"q3.pay_date", "q3.pay_amount", "q3.pay_comment", "q3.pay_act",
|
||||
"q3.booking", "q3.actual_m1", "q3.actual_m2", "q3.actual_m3", "q3.actual_quarter",
|
||||
"q3.residual_after_booking", "q3.residual_after_actual",
|
||||
"q3.transfer_q4", "q3.transfer_q4_delay_acts", "q3.transfer_q4_delay_procurement",
|
||||
"q3.transfer_q4_economy_rf", "q3.transfer_next_comment", "q3.transfer_econ", "q3.total",
|
||||
"q4.target_change", "q4.base_correction", "q4.base_correction_comment",
|
||||
"q4.revision_inc", "q4.revision_seq", "q4.revision_comment", "q4.new_plan",
|
||||
"q4.adj_current", "q4.adj_ssp", "q4.adj_reserve", "q4.adj_comment", "q4.corrected_plan",
|
||||
"q4.pay_date", "q4.pay_amount", "q4.pay_comment", "q4.pay_act",
|
||||
"q4.booking", "q4.actual_m1", "q4.actual_m2", "q4.actual_m3", "q4.actual_spod", "q4.actual_quarter",
|
||||
"q4.residual_after_booking", "q4.residual_after_actual", "q4.transfer_econ",
|
||||
"totals.fact_year", "totals.pay_year",
|
||||
]
|
||||
|
||||
|
||||
def test_form2_depth_columns_leaf_order_matches_screen_config():
|
||||
writer = XlsxDepthSheetWriter(
|
||||
workbook=XlsxWorkbook(),
|
||||
columns_dict=FORM2_DEPTH_COLUMNS,
|
||||
row_normalizer=lambda row: row,
|
||||
value_serializer=lambda value: value,
|
||||
)
|
||||
assert writer.columns == EXPECTED_LEAF_ORDER
|
||||
|
||||
|
||||
def test_form2_depth_columns_has_no_vsp_address():
|
||||
assert "header.vsp_address" not in EXPECTED_LEAF_ORDER
|
||||
writer = XlsxDepthSheetWriter(
|
||||
workbook=XlsxWorkbook(),
|
||||
columns_dict=FORM2_DEPTH_COLUMNS,
|
||||
row_normalizer=lambda row: row,
|
||||
value_serializer=lambda value: value,
|
||||
)
|
||||
assert "header.vsp_address" not in writer.columns
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sheet", ["OPER", "AHR", "CAP"])
|
||||
def test_form2_colors_cover_every_leaf(sheet):
|
||||
missing = [key for key in EXPECTED_LEAF_ORDER if key not in FORM2_COLORS[sheet]]
|
||||
assert missing == []
|
||||
|
||||
|
||||
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 "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", ""))
|
||||
|
||||
|
||||
def test_create_xlsxwriter_sheet_writers_form2_uses_its_own_layout():
|
||||
service = ExportService(AsyncMock())
|
||||
workbook = XlsxWorkbook()
|
||||
writers = service._create_xlsxwriter_sheet_writers(workbook, FormTypeEnum.FORM_2.value, 2026)
|
||||
|
||||
for sheet in ("OPER", "AHR", "CAP"):
|
||||
assert writers[sheet].columns == EXPECTED_LEAF_ORDER
|
||||
|
||||
|
||||
def test_create_xlsxwriter_sheet_writers_form1_is_unaffected():
|
||||
service = ExportService(AsyncMock())
|
||||
workbook = XlsxWorkbook()
|
||||
writers = service._create_xlsxwriter_sheet_writers(workbook, FormTypeEnum.FORM_1.value, 2026)
|
||||
|
||||
form1_writer = XlsxDepthSheetWriter(
|
||||
workbook=XlsxWorkbook(),
|
||||
columns_dict=FORM1_DEPTH_COLUMNS,
|
||||
row_normalizer=lambda row: row,
|
||||
value_serializer=lambda value: value,
|
||||
)
|
||||
assert writers["OPER"].columns == form1_writer.columns
|
||||
assert writers["OPER"].columns != EXPECTED_LEAF_ORDER
|
||||
|
||||
|
||||
def test_form2_row_normalizer_keeps_header_nesting():
|
||||
# В отличие от Form1DepthRowNormalizer, поля header.* НЕ поднимаются наверх:
|
||||
# FORM2_DEPTH_COLUMNS ожидает их как header.section_code, header.vsp_id и т.д.
|
||||
normalizer = Form2DepthRowNormalizer(ExcelValueSerializer())
|
||||
row = {
|
||||
"header": {"section_code": "01", "vsp_id": 5},
|
||||
"plan": {"q1": 10},
|
||||
"q4": {"actual_spod": 1},
|
||||
"line_id": 999,
|
||||
}
|
||||
normalized = normalizer.normalize(row)
|
||||
assert normalized["header.section_code"] == "01"
|
||||
assert normalized["header.vsp_id"] == 5
|
||||
assert normalized["plan.q1"] == 10
|
||||
assert normalized["q4.actual_spod"] == 1
|
||||
assert "line_id" not in normalized
|
||||
assert "header" not in normalized
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_form2_vsp_numbers_replaces_id_with_registration_number():
|
||||
service = ExportService(AsyncMock())
|
||||
service.vsp_repo.get_reg_numbers_by_ids = AsyncMock(return_value={5: "1234/0001", 7: "5678/0002"})
|
||||
|
||||
rows = [
|
||||
("INPUT", 1, 0, {"header": {"vsp_id": 5}, "plan": {"q1": 10}}),
|
||||
("INPUT", 1, 1, {"header": {"vsp_id": 7}, "plan": {"q1": 20}}),
|
||||
("GROUP", 0, 0, {"header": {"vsp_id": None}}),
|
||||
("META", 0, 0, None),
|
||||
]
|
||||
|
||||
await service._resolve_form2_vsp_numbers(rows)
|
||||
|
||||
assert rows[0][3]["header"]["vsp_id"] == "1234/0001"
|
||||
assert rows[1][3]["header"]["vsp_id"] == "5678/0002"
|
||||
assert rows[2][3]["header"]["vsp_id"] is None
|
||||
service.vsp_repo.get_reg_numbers_by_ids.assert_awaited_once_with([5, 7])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_form2_vsp_numbers_skips_db_call_when_no_vsp_ids():
|
||||
service = ExportService(AsyncMock())
|
||||
service.vsp_repo.get_reg_numbers_by_ids = AsyncMock(return_value={})
|
||||
|
||||
rows = [("INPUT", 1, 0, {"plan": {"q1": 10}})]
|
||||
await service._resolve_form2_vsp_numbers(rows)
|
||||
|
||||
service.vsp_repo.get_reg_numbers_by_ids.assert_not_awaited()
|
||||
@ -681,10 +681,6 @@ export const config = {
|
||||
color_type: blueColumn,
|
||||
accessorKey: "data.totals.pay_year",
|
||||
},
|
||||
"data.header.vsp_address": {
|
||||
color_type: greenColumn,
|
||||
accessorKey: "data.header.vsp_address",
|
||||
},
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
@ -731,14 +727,6 @@ export const config = {
|
||||
filterFn: "contains",
|
||||
editType: "vsp_dropdown",
|
||||
},
|
||||
{
|
||||
header: "Адрес ВСП",
|
||||
accessorKey: "data.header.vsp_address",
|
||||
columnLetter: "E1",
|
||||
size: 220,
|
||||
filterFn: "contains",
|
||||
editType: "vsp_dropdown",
|
||||
},
|
||||
],
|
||||
muiTableHeadCellProps: {
|
||||
sx: {
|
||||
|
||||
@ -681,10 +681,6 @@ export const config = {
|
||||
color_type: blueColumn,
|
||||
accessorKey: "data.totals.pay_year",
|
||||
},
|
||||
"data.header.vsp_address": {
|
||||
color_type: orangeColumn,
|
||||
accessorKey: "data.header.vsp_address",
|
||||
},
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
@ -731,14 +727,6 @@ export const config = {
|
||||
filterFn: "contains",
|
||||
editType: "vsp_dropdown",
|
||||
},
|
||||
{
|
||||
header: "Адрес ВСП",
|
||||
accessorKey: "data.header.vsp_address",
|
||||
columnLetter: "E1",
|
||||
size: 220,
|
||||
filterFn: "contains",
|
||||
editType: "vsp_dropdown",
|
||||
},
|
||||
],
|
||||
muiTableHeadCellProps: {
|
||||
sx: {
|
||||
|
||||
@ -681,10 +681,6 @@ export const config = {
|
||||
color_type: blueColumn,
|
||||
accessorKey: "data.totals.pay_year",
|
||||
},
|
||||
"data.header.vsp_address": {
|
||||
color_type: blueColumn,
|
||||
accessorKey: "data.header.vsp_address",
|
||||
},
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
@ -731,14 +727,6 @@ export const config = {
|
||||
filterFn: "contains",
|
||||
editType: "vsp_dropdown",
|
||||
},
|
||||
{
|
||||
header: "Адрес ВСП",
|
||||
accessorKey: "data.header.vsp_address",
|
||||
columnLetter: "E1",
|
||||
size: 220,
|
||||
filterFn: "contains",
|
||||
editType: "vsp_dropdown",
|
||||
},
|
||||
],
|
||||
muiTableHeadCellProps: {
|
||||
sx: {
|
||||
|
||||
@ -8,7 +8,6 @@ export const stageColumnsHiddenFromPicker = [
|
||||
// 'data.header.name',
|
||||
'data.header.internal_order',
|
||||
// 'data.header.vsp_id',
|
||||
'data.header.vsp_address',
|
||||
'data.header.section_code',
|
||||
// 'data.header.justification',
|
||||
];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user