export-hierarchy: иерархия expense-item при выгрузке
This commit is contained in:
parent
01fc7d4a01
commit
1463b3ad8b
@ -21,6 +21,22 @@ RowFormatResolver = Callable[[tuple, str, int], dict[str, Any] | None]
|
||||
ColumnFormatResolver = Callable[[str, int], dict[str, Any] | None]
|
||||
|
||||
|
||||
def _hierarchical_rows(rows: list[tuple]):
|
||||
"""Use the same row-type hierarchy as the realtime table, skipping META."""
|
||||
levels = {"ROOT": 0, "GROUP": 1, "ITEM": 2, "SUB_ITEM": 3, "INPUT": 4}
|
||||
stack: list[int] = []
|
||||
for row in rows:
|
||||
item_type = row[0] if row else None
|
||||
if item_type == "META":
|
||||
continue
|
||||
level = levels.get(item_type, 999)
|
||||
while stack and stack[-1] >= level:
|
||||
stack.pop()
|
||||
yield row, len(stack)
|
||||
if item_type != "INPUT":
|
||||
stack.append(level)
|
||||
|
||||
|
||||
class AbstractWriter(abc.ABC):
|
||||
|
||||
@staticmethod
|
||||
@ -226,8 +242,10 @@ class DepthSheetWriter(AbstractWriter):
|
||||
def write(self, worksheet: Worksheet, rows: list[tuple]) -> None:
|
||||
column_widths: dict[int, int] = {}
|
||||
self._write_header(worksheet, column_widths=column_widths)
|
||||
worksheet.sheet_properties.outlinePr.summaryBelow = False
|
||||
next_row = worksheet.max_row + 1
|
||||
for row in rows:
|
||||
for row, outline_level in _hierarchical_rows(rows):
|
||||
worksheet.row_dimensions[next_row].outlineLevel = min(outline_level, 7)
|
||||
if len(row) > 3 and isinstance(row[3], dict):
|
||||
if row[0] == 'META':
|
||||
continue
|
||||
@ -489,6 +507,7 @@ class XlsxDepthSheetWriter(AbstractWriter):
|
||||
column_widths: dict[int, int] = {}
|
||||
header_rows = self._get_header_max_row(self.columns_dict)
|
||||
self._write_header(worksheet, column_widths=column_widths)
|
||||
worksheet.outline_settings(visible=True, symbols_below=False)
|
||||
output_row = header_rows
|
||||
column_formats = None
|
||||
if self.column_format_resolver:
|
||||
@ -499,7 +518,8 @@ class XlsxDepthSheetWriter(AbstractWriter):
|
||||
for column, column_key in enumerate(self.columns)
|
||||
]
|
||||
|
||||
for row in rows:
|
||||
for row, outline_level in _hierarchical_rows(rows):
|
||||
worksheet.set_row(output_row, None, None, {"level": min(outline_level, 7)})
|
||||
if len(row) > 3 and isinstance(row[3], dict):
|
||||
if row[0] == "META":
|
||||
continue
|
||||
|
||||
@ -1,13 +1,54 @@
|
||||
from unittest.mock import AsyncMock
|
||||
from io import BytesIO
|
||||
|
||||
from openpyxl import Workbook
|
||||
import pytest
|
||||
from openpyxl import Workbook, load_workbook
|
||||
from xlsxwriter import Workbook as XlsxWorkbook
|
||||
|
||||
from src.services.export_service import ExportService
|
||||
from src.services.export_service.export_mappings import (
|
||||
FORM1_FLAT_COLUMNS,
|
||||
SMETA_COLUMNS,
|
||||
)
|
||||
from src.services.export_service.export_writers import TabularSheetWriter
|
||||
from src.services.export_service.export_writers import (
|
||||
DepthSheetWriter,
|
||||
TabularSheetWriter,
|
||||
XlsxDepthSheetWriter,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("backend", ["openpyxl", "xlsxwriter", "xlsxwriter_columns"])
|
||||
def test_depth_writer_preserves_collapsible_row_hierarchy(backend):
|
||||
stream = BytesIO()
|
||||
workbook = Workbook() if backend == "openpyxl" else XlsxWorkbook(stream)
|
||||
worksheet = workbook.active if backend == "openpyxl" else workbook.add_worksheet()
|
||||
options = {}
|
||||
if backend == "xlsxwriter_columns":
|
||||
options["column_format_resolver"] = lambda key, column: {"bold": True}
|
||||
writer_class = DepthSheetWriter if backend == "openpyxl" else XlsxDepthSheetWriter
|
||||
if backend != "openpyxl":
|
||||
options["workbook"] = workbook
|
||||
writer = writer_class(
|
||||
columns_dict={"header": {"name": "Header", "key": "", "children": {
|
||||
"name": {"name": "Name"},
|
||||
}}},
|
||||
row_normalizer=lambda row: row,
|
||||
value_serializer=lambda value: value,
|
||||
**options,
|
||||
)
|
||||
# Missing GROUP/SUB_ITEM levels and misleading numeric depths match frontend cases.
|
||||
types = ["ROOT", "ITEM", "INPUT", "META", "INPUT", "ITEM", "SUB_ITEM", "INPUT", "ROOT"]
|
||||
writer.write(worksheet, [(kind, 42, index, {"name": str(index)}) for index, kind in enumerate(types)])
|
||||
if backend == "openpyxl":
|
||||
workbook.save(stream)
|
||||
else:
|
||||
workbook.close()
|
||||
stream.seek(0)
|
||||
sheet = load_workbook(stream).active
|
||||
assert sheet.sheet_properties.outlinePr.summaryBelow is False
|
||||
assert [sheet.row_dimensions[index].outlineLevel for index in range(3, 11)] == [0, 1, 2, 2, 1, 2, 3, 0]
|
||||
assert [sheet.cell(index, 1).value for index in range(3, 11)] == ["0", "1", "2", "4", "5", "6", "7", "8"]
|
||||
assert all(not sheet.row_dimensions[index].hidden for index in range(3, 11))
|
||||
|
||||
|
||||
def test_tabular_sheet_writer_writes_headers_and_rows():
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user