2026-10-06 15:16:39 +04:00

242 lines
9.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Заголовок таблицы формы, колонка кодов, наименований и периодов (design D-4; spec «Заголовок таблицы, колонка кодов
и колонки-периоды определяются по тексту»)."""
import datetime
from decimal import Decimal
import pytest
from gridbuilder import at, sheet_from_rows
from src.parser.grid import Cell
from src.parser.rules import Rules, load_rules
from src.parser.statements.columns import (
LayoutNotFoundError,
find_code_column,
find_layout,
line_code,
)
@pytest.fixture(scope="module")
def rules() -> Rules:
return load_rules()
# Test constants
CODE_COL_ONE_C = 4
NAME_COL_ONE_C = 2
CODE_COL_TDSHEET = 2
NAME_COL_TDSHEET = 1
HEADER_ROW_ONE_C = 4
FIRST_CODE_ROW_ONE_C = 5
PERIOD_COL_ONE_C_1 = 5
PERIOD_COL_ONE_C_2 = 6
HEADER_ROW_TDSHEET = 2
HEADER_LAST_ROW_TDSHEET = 3
FIRST_CODE_ROW_TDSHEET = 4
HEADER_ROW_TWO_ROW = 1
HEADER_LAST_ROW_TWO_ROW = 2
FIRST_CODE_ROW_TWO_ROW = 3
HEADER_ROW_NO_CAPTION = 1
CODE_COL_NO_CAPTION = 2
NAME_COL_NO_CAPTION = 1
HEADER_ROW_CONTINUATION = None
FIRST_DATA_ROW_CONTINUATION = 1
CODE_COL_CONTINUATION = 6
PERIOD_COL_CONT_1 = 7
PERIOD_COL_CONT_2 = 8
PERIOD_COL_CONT_3 = 9
HEADER_ROW_BARE_YEAR = 2
PERIOD_COL_BARE_YEAR_1 = 15
PERIOD_COL_BARE_YEAR_2 = 20
def test_line_code_from_text_and_numeric_cells(rules: Rules) -> None:
assert line_code(Cell("1110", "text"), rules) == "1110"
assert line_code(Cell(Decimal("1110"), "number"), rules) == "1110"
assert line_code(Cell(Decimal("1110.0"), "number"), rules) == "1110" # Review Focus 1: xlrd даёт float
assert line_code(Cell("11101", "text"), rules) == "11101"
assert line_code(Cell("2110", "text"), rules) == "2110"
assert line_code(Cell("5200", "text"), rules) == "5200"
assert line_code(Cell("2023", "text"), rules) is None # год, не код
assert line_code(Cell("0710001", "text"), rules) is None
assert line_code(Cell("", "empty"), rules) is None
# Test data: ONE_C_BALANCE sheet
_HEADER_ROW_ONE_C = [
"Пояснения",
"Наименование показателя",
None,
"Код",
"На 31 декабря 2024 г.",
"На 31 декабря 2023 г.",
"Изменение, %",
]
ONE_C_BALANCE = [
["Бухгалтерский баланс"],
["на 31 декабря 2024 г."],
[],
_HEADER_ROW_ONE_C,
[None, "АКТИВ"],
[None, "Нематериальные активы", None, "1110", 5971264, 27405634, 12],
[None, "Нематериальные активы в организации", None, "11101", 5852464, 27214614, 0],
[None, None, None, None, 0, 0, 0],
["1", "Основные средства", None, "1150", 2039077663, 1809528568, 7],
[None, "Итого по разделу I", None, "1100", 2045048927, 1836934202, 7],
]
def test_code_column_by_share(rules: Rules) -> None:
sheet = sheet_from_rows(ONE_C_BALANCE)
assert find_code_column(sheet, rules, 1, sheet.n_rows) == CODE_COL_ONE_C
def test_code_column_needs_three_codes_and_sixty_percent(rules: Rules) -> None:
sheet = sheet_from_rows([["x", "1110"], ["y", "abc"], ["z", "def"], ["w", "ghi"], ["v", "1150"]])
assert find_code_column(sheet, rules, 1, 5) is None
def test_layout_one_c(rules: Rules) -> None:
sheet = sheet_from_rows(ONE_C_BALANCE)
layout = find_layout(sheet, rules, 1, sheet.n_rows)
assert (layout.header_row, layout.header_last_row, layout.first_data_row) == (
HEADER_ROW_ONE_C,
HEADER_ROW_ONE_C,
FIRST_CODE_ROW_ONE_C,
)
assert layout.code_col == CODE_COL_ONE_C and layout.name_col == NAME_COL_ONE_C
expected_periods = [
(PERIOD_COL_ONE_C_1, "На 31 декабря 2024 г.", datetime.date(2024, 12, 31)),
(PERIOD_COL_ONE_C_2, "На 31 декабря 2023 г.", datetime.date(2023, 12, 31)),
]
assert [(column.col, column.label, column.period_date) for column in layout.periods] == expected_periods
assert layout.skipped_labels == ("Изменение, %",)
def test_layout_tdsheet_with_numbering_row_and_line_breaks(rules: Rules) -> None:
sheet = sheet_from_rows(
[
["БУХГАЛТЕРСКИЙ БАЛАНС"],
["Наименование показателя", "Коды", "За 9 месяцев\n2025 года", "На 31 декабря \n2024 года"],
["1", "2", "3", "4"],
["Нематериальные активы", "1110", 0, 0],
["Основные средства", "1150", Decimal("898852.21795"), 966270],
["ИТОГО по разделу I", "1100", Decimal("898852.21795"), 966270],
]
)
layout = find_layout(sheet, rules, 1, sheet.n_rows)
assert (layout.header_row, layout.header_last_row, layout.first_data_row) == (
HEADER_ROW_TDSHEET,
HEADER_LAST_ROW_TDSHEET,
FIRST_CODE_ROW_TDSHEET,
)
assert layout.name_col == NAME_COL_TDSHEET and layout.code_col == CODE_COL_TDSHEET
expected_labels = ["За 9 месяцев 2025 года", "На 31 декабря 2024 года"]
assert [column.label for column in layout.periods] == expected_labels
assert layout.periods[0].period_date == datetime.date(2025, 9, 30)
def test_layout_two_row_header(rules: Rules) -> None:
sheet = sheet_from_rows(
[
["Наименование показателя", "Код", "На 31 декабря", "На 31 декабря"],
[None, None, "2024 г.", "2023 г."],
["Запасы", "1210", 10, 20],
["Дебиторская задолженность", "1230", 30, 40],
["Итого по разделу II", "1200", 40, 60],
]
)
layout = find_layout(sheet, rules, 1, sheet.n_rows)
assert (layout.header_row, layout.header_last_row, layout.first_data_row) == (
HEADER_ROW_TWO_ROW,
HEADER_LAST_ROW_TWO_ROW,
FIRST_CODE_ROW_TWO_ROW,
)
expected_dates = [datetime.date(2024, 12, 31), datetime.date(2023, 12, 31)]
assert [column.period_date for column in layout.periods] == expected_dates
def test_layout_without_code_caption(rules: Rules) -> None:
sheet = sheet_from_rows(
[
["Показатель", None, "На 31.12.2024", "На 31.12.2023"],
["Основные средства", "1150", 100, 90],
["Запасы", "1210", 50, 40],
["БАЛАНС", "1600", 150, 130],
]
)
layout = find_layout(sheet, rules, 1, sheet.n_rows)
assert layout.header_row == HEADER_ROW_NO_CAPTION
assert layout.code_col == CODE_COL_NO_CAPTION
assert layout.name_col == NAME_COL_NO_CAPTION
expected_dates = [datetime.date(2024, 12, 31), datetime.date(2023, 12, 31)]
assert [column.period_date for column in layout.periods] == expected_dates
def test_layout_continuation_without_header(rules: Rules) -> None:
sheet = sheet_from_rows(
[
[None, None, None, None, "в том числе:"],
[None, None, None, None, "Биологические активы", "11901", 0, 0, 0],
[None, None, None, None, "Итого по разделу I", "1100", 11853698344, 11570097340, 9391157985],
[None, None, None, "II. Оборотные активы"],
[None, None, None, "Запасы", None, "1210", 1001705104, 1426756617, 3393013092],
]
)
layout = find_layout(sheet, rules, 1, sheet.n_rows)
assert layout.header_row is HEADER_ROW_CONTINUATION
assert layout.first_data_row == FIRST_DATA_ROW_CONTINUATION
assert layout.code_col == CODE_COL_CONTINUATION
expected_cols = [
(PERIOD_COL_CONT_1, "", None),
(PERIOD_COL_CONT_2, "", None),
(PERIOD_COL_CONT_3, "", None),
]
assert [(column.col, column.label, column.period_date) for column in layout.periods] == expected_cols
def test_explanations_column_is_not_name(rules: Rules) -> None:
sheet = sheet_from_rows(ONE_C_BALANCE)
assert find_layout(sheet, rules, 1, sheet.n_rows).name_col == NAME_COL_ONE_C
def test_no_code_column_raises(rules: Rules) -> None:
sheet = sheet_from_rows([["Показатель", "Сумма"], ["Основные средства", 100], ["Запасы", 50]])
with pytest.raises(LayoutNotFoundError, match="колонка кодов не найдена"):
find_layout(sheet, rules, 1, 3)
def test_bare_year_in_title_block_is_not_a_header(rules: Rules) -> None:
"""У НПЗ над таблицей стоит «Отчетная дата | 30 | 09 | 2025»: строка с подписью «Код» побеждает."""
sheet = sheet_from_rows(
[
[*at({6: "Отчетная дата", 22: "30", 24: "09", 25: "2025"})],
[*at({4: "Наименование показателя", 12: "Код", 15: "На 30 сентября 2025 г.", 20: "На 31 декабря 2024 г."})],
[*at({4: "Гудвил", 12: "1105", 15: 0, 20: 0})],
[*at({4: "Нематериальные активы", 12: "1110", 15: 586260, 20: 905786})],
[*at({4: "Итого по разделу I", 12: "1100", 15: 586260, 20: 905786})],
]
)
layout = find_layout(sheet, rules, 1, sheet.n_rows)
period_cols = [PERIOD_COL_BARE_YEAR_1, PERIOD_COL_BARE_YEAR_2]
assert layout.header_row == HEADER_ROW_BARE_YEAR
assert [column.col for column in layout.periods] == period_cols