139 lines
5.4 KiB
Python
139 lines
5.4 KiB
Python
"""Метки строк-показателей (§1.3, #58): колонка «Показатели» и текст строки."""
|
||
|
||
from decimal import Decimal
|
||
|
||
import pytest
|
||
from gridbuilder import L, M, at, sheet_from_rows
|
||
|
||
from src.parser.deadline import Deadline
|
||
from src.parser.detect import TableHeader, find_table_header
|
||
from src.parser.grid import Sheet
|
||
from src.parser.hierarchy import BodyRow
|
||
from src.parser.indicators import RowLabel, is_indicator_total, levels_with_indicators, read_label
|
||
from src.parser.rules import Rules, load_rules
|
||
|
||
TITLE = "Оборотно-сальдовая ведомость по счету 60 за 1 квартал 2025 г."
|
||
LABEL_COL = 2
|
||
FIRST_ROW = 4
|
||
|
||
|
||
@pytest.fixture(scope="module")
|
||
def rules() -> Rules:
|
||
return load_rules()
|
||
|
||
|
||
def _sheet(*labels: str, text: str = "60") -> tuple[Sheet, TableHeader]:
|
||
rows: list[list[object]] = [
|
||
[TITLE],
|
||
["Счет", None, M("Сальдо на начало периода", 2), M("Обороты за период", 2), M("Сальдо на конец периода", 2)],
|
||
["Счет", "Показатели", "Дебет", "Кредит", "Дебет", "Кредит", "Дебет", "Кредит"],
|
||
]
|
||
rows.extend([L(0), *at({1: text, LABEL_COL: label, 3: 10, 8: 10})] for label in labels)
|
||
sheet = sheet_from_rows(rows)
|
||
return sheet, find_table_header(sheet, load_rules())
|
||
|
||
|
||
def _read(label: str, rules: Rules, text: str = "60") -> tuple[RowLabel, int]:
|
||
sheet, header = _sheet(label, text=text)
|
||
return read_label(sheet, FIRST_ROW, text, header, rules), FIRST_ROW
|
||
|
||
|
||
def test_bu_label_is_data_row(rules: Rules) -> None:
|
||
result, _ = _read("БУ", rules)
|
||
assert (result.indicator, result.data_type, result.text) == (False, "BU", None)
|
||
|
||
|
||
def test_nu_label_is_indicator_with_label_text(rules: Rules) -> None:
|
||
result, _ = _read("НУ", rules)
|
||
assert (result.indicator, result.data_type, result.text, result.warning) == (True, "NU", "НУ", None)
|
||
|
||
|
||
def test_untyped_label_has_no_data_type_and_no_warning(rules: Rules) -> None:
|
||
result, _ = _read("ПР", rules)
|
||
assert (result.indicator, result.data_type, result.text, result.warning) == (True, None, "ПР", None)
|
||
|
||
|
||
def test_unknown_label_warns_with_row_number(rules: Rules) -> None:
|
||
result, row = _read("Ед.", rules)
|
||
assert (result.indicator, result.data_type) == (True, None)
|
||
assert result.warning is not None
|
||
assert f"строка {row}" in result.warning
|
||
assert "Ед." in result.warning
|
||
assert "не участвует в проверках" in result.warning
|
||
|
||
|
||
def test_text_currency_label_without_column(rules: Rules) -> None:
|
||
sheet = sheet_from_rows(
|
||
[
|
||
[TITLE],
|
||
["Счет", M("Сальдо на начало периода", 2), M("Обороты за период", 2), M("Сальдо на конец периода", 2)],
|
||
["Счет", "Дебет", "Кредит", "Дебет", "Кредит", "Дебет", "Кредит"],
|
||
[L(0), *at({1: "В валюте", 2: 10})],
|
||
[L(0), *at({1: "Валюта USD", 2: 10})],
|
||
]
|
||
)
|
||
header = find_table_header(sheet, rules)
|
||
assert header.indicator_col is None
|
||
in_currency = read_label(sheet, 4, "В валюте", header, rules)
|
||
assert (in_currency.indicator, in_currency.data_type, in_currency.text) == (True, "CURRENCY", None)
|
||
plain = read_label(sheet, 5, "Валюта USD", header, rules)
|
||
assert (plain.indicator, plain.data_type) == (False, None)
|
||
|
||
|
||
def test_is_indicator_total(rules: Rules) -> None:
|
||
sheet, header = _sheet("НУ", "БУ", "")
|
||
assert is_indicator_total(sheet, FIRST_ROW, header, rules)
|
||
assert not is_indicator_total(sheet, FIRST_ROW + 1, header, rules)
|
||
assert not is_indicator_total(sheet, FIRST_ROW + 2, header, rules)
|
||
|
||
|
||
# --- Иерархия: показатели вне каскада ---
|
||
|
||
|
||
def _row(row: int, text: str, outline: int, *, indicator: bool = False) -> BodyRow:
|
||
return BodyRow(
|
||
row=row,
|
||
text=text,
|
||
is_account=text.isdigit(),
|
||
account_rank=1 if text.isdigit() else None,
|
||
bold=False,
|
||
indent=0,
|
||
outline=outline,
|
||
sums=(Decimal(1),) * 6,
|
||
indicator=indicator,
|
||
)
|
||
|
||
|
||
def test_indicators_are_children_of_previous_data_row_outside_cascade() -> None:
|
||
rows = [
|
||
_row(10, "60", 0),
|
||
_row(11, "НУ", 0, indicator=True),
|
||
_row(12, "Объект", 1),
|
||
_row(13, "НУ", 1, indicator=True),
|
||
_row(14, "Итого", 0),
|
||
_row(15, "НУ", 0, indicator=True),
|
||
]
|
||
levels = levels_with_indicators(rows, 14, Deadline(3600))
|
||
assert levels.strategy == "outline"
|
||
assert levels.by_row == {10: 0, 11: 1, 12: 1, 13: 2, 14: 0, 15: 0}
|
||
assert levels.parent == {10: None, 11: 10, 12: 10, 13: 12, 14: None, 15: None}
|
||
|
||
|
||
def test_indicator_first_in_table_is_root() -> None:
|
||
rows = [_row(10, "НУ", 0, indicator=True), _row(11, "60", 0)]
|
||
levels = levels_with_indicators(rows, None, Deadline(3600))
|
||
assert levels.by_row[10] == 0
|
||
assert levels.parent[10] is None
|
||
|
||
|
||
def test_levels_keep_sheet_row_order() -> None:
|
||
rows = [
|
||
_row(10, "60", 0),
|
||
_row(11, "НУ", 0, indicator=True),
|
||
_row(12, "Объект", 1),
|
||
_row(13, "НУ", 1, indicator=True),
|
||
]
|
||
levels = levels_with_indicators(rows, None, Deadline(3600))
|
||
assert list(levels.by_row) == [10, 11, 12, 13]
|
||
assert list(levels.parent) == [10, 11, 12, 13]
|