2026-10-19 13:17:34 +04:00

654 lines
27 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.

"""Шапка документа и схема группировок (spec «Шапка документа и схема группировок», `05-domain-rules.md` §1.1, §1.5).
Сетки собираются `gridbuilder`; шапка и заголовок таблицы находятся по содержимому, позиции в сетках произвольные.
"""
import datetime
import pytest
from gridbuilder import M, at, sheet_from_rows
from src.parser.detect import TableHeader, find_table_header
from src.parser.grid import Sheet
from src.parser.header import ACCOUNT_NOT_FOUND_WARNING, DocHeader, build_group_schema, parse_doc_header
from src.parser.rules import Rules, load_rules
OPENING = "Сальдо на начало периода"
TURNOVER = "Обороты за период"
CLOSING = "Сальдо на конец периода"
MISSING_FIELDS_WARNINGS = 5 # организация, счёт, период, тип данных, валюта
VARIANT_2 = 2
DATA_ROW = at({1: "60", 5: 100, 8: 20, 10: 30, 12: 40, 15: 50, 17: 60})
SUBHEAD = ["Дебет", None, None, "Кредит", None, "Дебет", "Кредит", "Дебет", None, "Кредит"]
GROUPS_ROW = [M("Счет", 4), M(OPENING, 5), M(TURNOVER, 5), M(CLOSING, 3)]
@pytest.fixture(scope="module")
def rules() -> Rules:
return load_rules()
def _table(*label_rows: list[object]) -> list[list[object]]:
"""Заголовок таблицы варианта 1: строка групп, подзаголовки сумм в строке первой метки, остальные метки ниже."""
first, *rest = label_rows
return [
GROUPS_ROW,
[*first, *at({5: "Дебет", 8: "Кредит", 10: "Дебет", 12: "Кредит", 15: "Дебет", 17: "Кредит"})[len(first) :]],
*rest,
DATA_ROW,
]
def _parse(rows: list[list[object]], rules: Rules) -> tuple[Sheet, TableHeader, DocHeader]:
sheet = sheet_from_rows(rows)
header = find_table_header(sheet, rules)
return sheet, header, parse_doc_header(sheet, header, rules)
def _variant1(
rules: Rules, *, org: object = 'АО "Ромашка"', title: str, params: object = "Валюта БУ, НУ: RUB"
) -> DocHeader:
rows: list[list[object]] = [
[org],
[title],
[],
["Выводимые данные: БУ (данные бухгалтерского учета)"],
[],
["Параметры:", None, params],
[],
*_table(["Контрагенты"]),
]
return _parse(rows, rules)[2]
def _title(period: str, account: str = "60") -> str:
return f"Оборотно-сальдовая ведомость по счету {account} за {period}"
# --- Вариант 1: организация, счёт, период, тип данных, валюта ---
def test_variant1_header_fields(rules: Rules) -> None:
header = _variant1(rules, title=_title("3 квартал 2024 г."), params="Валюта БУ, НУ: руб.")
assert header == DocHeader(
organization_name='АО "Ромашка"',
account_code_raw="60",
period_text="3 квартал 2024 г.",
period_year=2024,
period_quarter="Q3",
is_cumulative=False,
period_start=datetime.date(2024, 7, 1),
period_end=datetime.date(2024, 9, 30),
data_type="BU",
currency="RUB",
header_variant=1,
warnings=(),
)
def test_account_with_subaccount_is_kept_as_in_file(rules: Rules) -> None:
header = _variant1(rules, title=_title("3 квартал 2024 г.", "58.03"))
assert header.account_code_raw == "58.03"
@pytest.mark.parametrize("currency", ["RUB", "rub", "руб.", "Руб", "рублей"])
def test_currency_spellings_give_rub(rules: Rules, currency: str) -> None:
header = _variant1(rules, title=_title("2024 г."), params=f"Валюта БУ, НУ: {currency}")
assert header.currency == "RUB"
assert not any("алют" in warning for warning in header.warnings)
def test_other_currency_code_is_kept(rules: Rules) -> None:
header = _variant1(rules, title=_title("2024 г."), params="Валюта БУ, НУ: USD")
assert header.currency == "USD"
def test_currency_value_in_neighbour_cell(rules: Rules) -> None:
rows: list[list[object]] = [
["Организация"],
[_title("2024 г.")],
["Параметры:", None, "Валюта БУ, НУ:", None, "RUB"],
*_table(["Контрагенты"]),
]
assert _parse(rows, rules)[2].currency == "RUB"
@pytest.mark.parametrize(
("text", "expected"),
[
("БУ (данные бухгалтерского учета)", "BU"),
("НУ (данные налогового учета)", "NU"),
("сумма", "SUM"),
],
)
def test_data_type_values(rules: Rules, text: str, expected: str) -> None:
rows: list[list[object]] = [
["Организация"],
[_title("2024 г.")],
[f"Выводимые данные: {text}"],
*_table(["Контрагенты"]),
]
assert _parse(rows, rules)[2].data_type == expected
# --- Период: все строки таблицы §1.5 ---
@pytest.mark.parametrize(
("period", "quarter", "cumulative"),
[
("1 квартал 2025 г.", "Q1", True),
("I кв. 2025 г.", "Q1", True),
("3 месяца 2025 г.", "Q1", True),
("январь-март 2025 г.", "Q1", True),
("01.01.2025 - 31.03.2025", "Q1", True),
("2 квартал 2025 г.", "Q2", False),
("II кв. 2025 г.", "Q2", False),
("полугодие 2025 г.", "Q2", True),
("1 полугодие 2025 г.", "Q2", True),
("6 месяцев 2025 г.", "Q2", True),
("январь-июнь 2025 г.", "Q2", True),
("3 квартал 2025 г.", "Q3", False),
("III кв. 2025 г.", "Q3", False),
("9 месяцев 2025 г.", "Q3", True),
("январь-сентябрь 2025 г.", "Q3", True),
("4 квартал 2025 г.", "Q4", False),
("IV кв. 2025 г.", "Q4", False),
("2025 год", "Q4", True),
("2025 г.", "Q4", True),
("12 месяцев 2025 г.", "Q4", True),
("январь-декабрь 2025 г.", "Q4", True),
("2025 год (годовая)", "Q4", True),
],
)
def test_period_table_of_domain_rules(rules: Rules, period: str, quarter: str, cumulative: bool) -> None:
header = _variant1(rules, title=_title(period))
assert (header.period_quarter, header.is_cumulative, header.period_year) == (quarter, cumulative, 2025)
assert header.warnings == ()
def test_period_text_ends_at_year_marker(rules: Rules) -> None:
header = _variant1(rules, title=_title("1 квартал 2025 г. (по подразделениям)"))
assert header.period_text == "1 квартал 2025 г."
def test_period_without_year_marker_runs_to_line_end(rules: Rules) -> None:
header = _variant1(rules, title=_title("с 01.01.2025 по 31.03.2025"))
assert header.period_text == "с 01.01.2025 по 31.03.2025"
assert header.period_quarter == "Q1"
def test_unrecognised_period_text_gives_none_and_warning(rules: Rules) -> None:
header = _variant1(rules, title=_title("неделю"))
assert header.period_text == "неделю"
assert (header.period_quarter, header.is_cumulative, header.period_year) == (None, None, None)
assert any("период" in warning for warning in header.warnings)
# --- Отсутствующие поля шапки ---
def test_missing_organization_gives_empty_string_and_warning(rules: Rules) -> None:
rows: list[list[object]] = [[_title("2024 г.")], ["Выводимые данные: БУ"], *_table(["Контрагенты"])]
header = _parse(rows, rules)[2]
assert header.organization_name == ""
assert any("организац" in warning for warning in header.warnings)
assert header.account_code_raw == "60"
def test_empty_head_area_gives_empty_strings_none_and_warnings(rules: Rules) -> None:
header = _parse(_table(["Контрагенты"]), rules)[2]
assert header.organization_name == ""
assert header.account_code_raw == ""
assert header.period_text == ""
assert (header.period_year, header.period_quarter, header.is_cumulative) == (None, None, None)
assert (header.data_type, header.currency, header.header_variant) == (None, None, 1)
assert len(header.warnings) == MISSING_FIELDS_WARNINGS
assert all(warning for warning in header.warnings)
def test_numeric_and_date_cells_in_head_do_not_break_parsing(rules: Rules) -> None:
rows: list[list[object]] = [
[45658], # xlsb: даты остаются числами
["Организация", None, 45659],
[_title("2024 г.")],
*_table(["Контрагенты"]),
]
header = _parse(rows, rules)[2]
assert header.organization_name == "Организация"
assert header.period_quarter == "Q4"
def test_organization_is_first_text_cell_that_is_not_a_marker(rules: Rules) -> None:
rows: list[list[object]] = [
[],
["Оборотно-сальдовая ведомость"],
["Организация Б"],
["Организация В"],
[_title("2024 г.")],
*_table(["Контрагенты"]),
]
assert _parse(rows, rules)[2].organization_name == "Организация Б"
# --- Схема группировок ---
def test_group_schema_variant1_account_counterparty_contract(rules: Rules) -> None:
sheet, header, doc = _parse(
[["Организация"], [_title("2024 г.")], *_table(["Контрагенты"], ["Договоры"])],
rules,
)
groups, variant = build_group_schema(sheet, header, rules)
assert variant == doc.header_variant == 1
assert [(g.type, g.name, g.has_inn) for g in groups] == [
("ACCOUNT", "Счет", None),
("COUNTERPARTY", "Контрагенты", None),
("CONTRACT", "Договоры", None),
]
def test_group_schema_inn_in_separate_cell_of_counterparty_row(rules: Rules) -> None:
rows: list[list[object]] = [
["Организация"],
[_title("2024 г.")],
*_table(["Подразделение"], [M("Контрагенты", 2), "ИНН"]),
]
sheet, header, _ = _parse(rows, rules)
groups, _ = build_group_schema(sheet, header, rules)
assert [(g.type, g.has_inn) for g in groups] == [("ACCOUNT", None), ("DEPARTMENT", None), ("COUNTERPARTY", True)]
def test_group_schema_inn_in_the_label_cell(rules: Rules) -> None:
rows: list[list[object]] = [["Организация"], [_title("2024 г.")], *_table(["Контрагенты, ИНН"])]
sheet, header, _ = _parse(rows, rules)
groups, _ = build_group_schema(sheet, header, rules)
assert [(g.type, g.name, g.has_inn) for g in groups] == [
("ACCOUNT", "Счет", None),
("COUNTERPARTY", "Контрагенты, ИНН", True),
]
def test_first_group_is_account_whatever_its_text_says(rules: Rules) -> None:
rows: list[list[object]] = [
["Организация"],
[_title("2024 г.")],
[M("Подразделение", 4), M(OPENING, 5), M(TURNOVER, 5), M(CLOSING, 3)],
["Контрагенты", *at({5: "Дебет", 8: "Кредит", 10: "Дебет", 12: "Кредит", 15: "Дебет", 17: "Кредит"})[1:]],
DATA_ROW,
]
sheet, header, _ = _parse(rows, rules)
groups, _ = build_group_schema(sheet, header, rules)
assert [(g.type, g.name) for g in groups] == [("ACCOUNT", "Подразделение"), ("COUNTERPARTY", "Контрагенты")]
def test_unknown_group_text_is_other(rules: Rules) -> None:
rows: list[list[object]] = [["Организация"], [_title("2024 г.")], *_table(["Основные средства"])]
sheet, header, _ = _parse(rows, rules)
groups, _ = build_group_schema(sheet, header, rules)
assert [g.type for g in groups] == ["ACCOUNT", "OTHER"]
def test_without_inn_cell_has_inn_stays_unset(rules: Rules) -> None:
rows: list[list[object]] = [["Организация"], [_title("2024 г.")], *_table(["Контрагенты"], ["Договоры"])]
sheet, header, _ = _parse(rows, rules)
groups, _ = build_group_schema(sheet, header, rules)
assert all(g.has_inn is None for g in groups)
# --- Вариант 2 ---
def _variant2_rows(detail: str = "Детализация по субсчетам, субконто: Контрагенты, Договоры") -> list[list[object]]:
return [
["ООО Вторая"],
["Оборотно-сальдовая ведомость по счету 60"],
["Период: 1 квартал 2026 г."],
[detail],
["Выводимые данные: сумма"],
[M("Субконто", 4), *GROUPS_ROW[1:]],
["", *at({5: "Дебет", 8: "Кредит", 10: "Дебет", 12: "Кредит", 15: "Дебет", 17: "Кредит"})[1:]],
DATA_ROW,
]
def test_variant2_header(rules: Rules) -> None:
sheet, header, doc = _parse(_variant2_rows(), rules)
groups, variant = build_group_schema(sheet, header, rules)
assert variant == doc.header_variant == VARIANT_2
assert [(g.type, g.name) for g in groups] == [
("ACCOUNT", "Субконто"),
("COUNTERPARTY", "Контрагенты"),
("CONTRACT", "Договоры"),
]
assert doc.organization_name == "ООО Вторая"
assert doc.account_code_raw == "60"
assert (doc.period_text, doc.period_year, doc.period_quarter, doc.is_cumulative) == (
"1 квартал 2026 г.",
2026,
"Q1",
True,
)
assert doc.data_type == "SUM"
assert doc.currency is None
assert any("алют" in warning for warning in doc.warnings)
def test_variant2_single_item(rules: Rules) -> None:
sheet, header, _ = _parse(_variant2_rows("Детализация по субсчетам, субконто: Контрагенты"), rules)
groups, variant = build_group_schema(sheet, header, rules)
assert variant == VARIANT_2
assert [g.type for g in groups] == ["ACCOUNT", "COUNTERPARTY"]
def test_variant2_detail_row_is_not_taken_for_organization(rules: Rules) -> None:
rows = _variant2_rows()
rows[0] = []
doc = _parse(rows, rules)[2]
assert doc.organization_name == ""
# --- Название организации со словами «за» и «валюта» (T5.1) ---
@pytest.mark.parametrize("organization", ["ООО «За Родину»", "Валюта Трейд", "За Урожай ЗАО"])
def test_organization_name_with_marker_words_does_not_hijack_period_or_currency(
rules: Rules, organization: str
) -> None:
header = _variant1(rules, org=organization, title=_title("3 квартал 2024 г."))
assert header.organization_name == organization
assert header.period_text == "3 квартал 2024 г."
assert (header.period_year, header.period_quarter, header.is_cumulative) == (2024, "Q3", False)
assert header.currency == "RUB"
assert header.warnings == ()
def test_organization_with_currency_word_and_no_currency_row_is_still_organization(rules: Rules) -> None:
rows: list[list[object]] = [["Валюта Трейд"], [_title("2024 г.")], *_table(["Контрагенты"])]
header = _parse(rows, rules)[2]
assert header.organization_name == "Валюта Трейд"
assert header.currency is None
assert any("алют" in warning for warning in header.warnings)
assert header.period_quarter == "Q4"
# --- Диапазоны дат с «г.» (T5.2) ---
@pytest.mark.parametrize(
("period", "quarter"),
[
("01.01.2025 г. - 31.03.2025 г.", "Q1"),
("с 01.01.2025 г. по 30.06.2025 г.", "Q2"),
("01.01.2025 г. по 31.12.2025 г.", "Q4"),
("01.01.2025 - 30.09.2025 г. (по подразделениям)", "Q3"),
],
)
def test_date_range_with_year_marks_is_kept_whole(rules: Rules, period: str, quarter: str) -> None:
header = _variant1(rules, title=_title(period))
assert header.period_text == period.removesuffix(" (по подразделениям)")
assert (header.period_quarter, header.is_cumulative, header.period_year) == (quarter, True, 2025)
assert header.warnings == ()
# --- Диапазон месяцев с годами: скользящий период (issue #11 п. 1, OQ-131) ---
@pytest.mark.parametrize(
"period",
[
"Октябрь 2024 г. - Сентябрь 2025 г.",
"с октября 2024 г. по сентябрь 2025 г.",
"октябрь 2024 - сентябрь 2025",
"Октябрь 2024 г. - Сентябрь 2025 г. (по подразделениям)",
],
)
def test_rolling_period_is_not_read_as_first_month_quarter(rules: Rules, period: str) -> None:
header = _variant1(rules, title=_title(period))
assert header.period_text == period.removesuffix(" (по подразделениям)")
# Конец диапазона определяет год; квартал и «нарастающий» не определены: 12 месяцев — не квартал и не с начала года
assert (header.period_year, header.period_quarter, header.is_cumulative) == (2025, None, None)
assert any("не с начала года" in warning and "12 мес" in warning for warning in header.warnings)
def test_month_range_of_one_quarter_is_a_quarter_not_cumulative(rules: Rules) -> None:
header = _variant1(rules, title=_title("Апрель 2025 г. - Июнь 2025 г."))
assert header.period_text == "Апрель 2025 г. - Июнь 2025 г."
assert (header.period_year, header.period_quarter, header.is_cumulative) == (2025, "Q2", False)
assert header.warnings == ()
def test_month_range_inside_year_not_from_january_is_not_a_quarter(rules: Rules) -> None:
header = _variant1(rules, title=_title("Апрель 2025 г. - Сентябрь 2025 г."))
assert (header.period_year, header.period_quarter, header.is_cumulative) == (2025, None, None)
assert any("не с начала года" in warning for warning in header.warnings)
def test_month_range_from_january_to_quarter_end_stays_cumulative(rules: Rules) -> None:
header = _variant1(rules, title=_title("Январь 2025 г. - Сентябрь 2025 г."))
assert header.period_text == "Январь 2025 г. - Сентябрь 2025 г."
assert (header.period_year, header.period_quarter, header.is_cumulative) == (2025, "Q3", True)
assert header.warnings == ()
def test_month_range_from_january_to_mid_quarter_has_no_quarter(rules: Rules) -> None:
header = _variant1(rules, title=_title("Январь 2025 г. - Май 2025 г."))
assert (header.period_year, header.period_quarter, header.is_cumulative) == (2025, None, None)
assert any("период" in warning for warning in header.warnings)
# --- Даты числами / датами (xlsb) (T5.5) ---
def test_period_carried_only_by_date_cells_is_not_read(rules: Rules) -> None:
"""Период «за» + ячейки-даты (xlsb не отдаёт форматы, даты — числа): текста периода нет, это не ошибка разбора."""
rows: list[list[object]] = [
["Организация"],
["Оборотно-сальдовая ведомость по счету 60 за", datetime.date(2025, 1, 1), datetime.date(2025, 3, 31)],
*_table(["Контрагенты"]),
]
header = _parse(rows, rules)[2]
assert header.organization_name == "Организация"
assert header.account_code_raw == "60"
assert header.period_text == ""
assert (header.period_year, header.period_quarter, header.is_cumulative) == (None, None, None)
assert any("период" in warning for warning in header.warnings)
# --- Вариант 2: ИНН (T5.4) ---
def _variant2_with_inn_column() -> list[list[object]]:
rows = _variant2_rows()
rows[6] = [
"",
None,
"ИНН",
None,
*at({5: "Дебет", 8: "Кредит", 10: "Дебет", 12: "Кредит", 15: "Дебет", 17: "Кредит"})[4:],
]
return rows
def test_variant2_inn_column_marks_the_first_counterparty(rules: Rules) -> None:
sheet, header, _ = _parse(_variant2_with_inn_column(), rules)
groups, _ = build_group_schema(sheet, header, rules)
assert header.inn_col is not None
assert [(g.type, g.has_inn) for g in groups] == [("ACCOUNT", None), ("COUNTERPARTY", True), ("CONTRACT", None)]
def test_variant2_without_inn_column_leaves_has_inn_unset(rules: Rules) -> None:
sheet, header, _ = _parse(_variant2_rows(), rules)
groups, _ = build_group_schema(sheet, header, rules)
assert header.inn_col is None
assert all(g.has_inn is None for g in groups)
# --- Границы периода (контракт v1.2, OQ-131) ---
@pytest.mark.parametrize(
("period_text", "start", "end", "from_year_start"),
[
("1 квартал 2025 г.", datetime.date(2025, 1, 1), datetime.date(2025, 3, 31), True),
("3 квартал 2025 г.", datetime.date(2025, 7, 1), datetime.date(2025, 9, 30), False),
("9 месяцев 2025 г.", datetime.date(2025, 1, 1), datetime.date(2025, 9, 30), True),
("2024 г.", datetime.date(2024, 1, 1), datetime.date(2024, 12, 31), True),
("Октябрь 2024 г. - Сентябрь 2025 г.", datetime.date(2024, 10, 1), datetime.date(2025, 9, 30), False),
("Апрель - Июнь 2025 г.", datetime.date(2025, 4, 1), datetime.date(2025, 6, 30), False),
("Январь - Май 2025 г.", datetime.date(2025, 1, 1), datetime.date(2025, 5, 31), True),
("Январь - Февраль 2024 г.", datetime.date(2024, 1, 1), datetime.date(2024, 2, 29), True),
("Январь - Октябрь 2025 г.", datetime.date(2025, 1, 1), datetime.date(2025, 10, 31), True),
("Февраль 2024 г.", datetime.date(2024, 2, 1), datetime.date(2024, 2, 29), False),
# I-2: дата на число и квартал с датой в скобках — не «одиночный месяц»
("на 31 декабря 2024 г.", datetime.date(2024, 1, 1), datetime.date(2024, 12, 31), True),
("1 квартал 2025 г. (по состоянию на 31 марта)", datetime.date(2025, 1, 1), datetime.date(2025, 3, 31), True),
("за 3 квартал 2025 г. на 30 сентября", datetime.date(2025, 7, 1), datetime.date(2025, 9, 30), False),
],
)
def test_period_bounds(
rules: Rules, period_text: str, start: datetime.date, end: datetime.date, from_year_start: bool
) -> None:
doc = _variant1(rules, title=_title(period_text))
assert doc.period_start is not None
assert (doc.period_start, doc.period_end) == (start, end)
assert (doc.period_start == datetime.date(doc.period_start.year, 1, 1)) is from_year_start
def test_period_bounds_unknown_when_period_unrecognised(rules: Rules) -> None:
doc = _variant1(rules, title=_title("за неизвестно что"))
assert doc.period_start is None and doc.period_end is None
# --- Плоская форма 1С: «по счету: X», строка вида субконто, «План счетов» ---
def _flat_rows(*head: str, group_label: str = "Субконто") -> list[list[object]]:
"""Шапка плоской формы: строки шапки, заголовок «Субконто» с группами сумм, строка данных."""
groups_row = [M(group_label, 4), M(OPENING, 5), M(TURNOVER, 5), M(CLOSING, 3)]
subhead = at({5: "Дебет", 8: "Кредит", 10: "Дебет", 12: "Кредит", 15: "Дебет", 17: "Кредит"})
return [*[[text] for text in head], groups_row, subhead, DATA_ROW]
def test_account_code_after_colon(rules: Rules) -> None:
rows = _flat_rows("Оборотно-сальдовая ведомость по счету: 76.Н.1", "за 2025 г.", "ООО Ромашка")
doc = _parse(rows, rules)[2]
assert doc.account_code_raw == "76.Н.1"
assert ACCOUNT_NOT_FOUND_WARNING not in doc.warnings
def test_colon_without_code_falls_back_to_table_row(rules: Rules) -> None:
rows = _flat_rows("Оборотно-сальдовая ведомость по счету:", "за 2025 г.", "ООО Ромашка")
doc = _parse(rows, rules)[2]
assert doc.account_code_raw == ""
assert ACCOUNT_NOT_FOUND_WARNING in doc.warnings
def test_kind_row_between_title_and_period_is_not_organization(rules: Rules) -> None:
rows = _flat_rows(
"Оборотно-сальдовая ведомость по счету: 76", "Контрагенты", "за 2025 г.", 'ООО "Товарное хозяйство"'
)
assert _parse(rows, rules)[2].organization_name == 'ООО "Товарное хозяйство"'
def test_organization_from_chart_of_accounts_tail(rules: Rules) -> None:
rows = _flat_rows(
"Оборотно-сальдовая ведомость по счету: 60",
"Организации",
"за 2 Квартал 2026 г. План счетов ООО Хреновская Нива",
)
doc = _parse(rows, rules)[2]
assert doc.organization_name == "ООО Хреновская Нива"
assert doc.period_quarter == "Q2"
assert doc.period_year == 2026 # noqa: PLR2004
def test_title_and_period_in_one_row_keep_old_organization_rule(rules: Rules) -> None:
header = _variant1(rules, title=_title("3 квартал 2024 г."))
assert header.organization_name == 'АО "Ромашка"'
def _flat_schema(rules: Rules, kind: str) -> list[tuple[str, str | None]]:
rows = _flat_rows("Оборотно-сальдовая ведомость по счету: 76", kind, "за 2025 г.", "ООО Ромашка")
sheet, header, _ = _parse(rows, rules)
return [(g.type, g.name) for g in build_group_schema(sheet, header, rules)[0]]
def test_known_kind_row_extends_single_account_schema(rules: Rules) -> None:
assert _flat_schema(rules, "Контрагенты") == [("ACCOUNT", "Субконто"), ("COUNTERPARTY", "Контрагенты")]
def test_unknown_kind_row_is_not_added_to_schema(rules: Rules) -> None:
assert _flat_schema(rules, "Материалы") == [("ACCOUNT", "Субконто")]
def test_organizations_kind_row_is_counterparty(rules: Rules) -> None:
assert _flat_schema(rules, "Организации") == [("ACCOUNT", "Субконто"), ("COUNTERPARTY", "Организации")]
def test_currency_row_between_title_and_period_is_not_added_to_schema(rules: Rules) -> None:
assert _flat_schema(rules, "Валюта БУ, НУ: RUB") == [("ACCOUNT", "Субконто")]
def test_subkonto_cell_between_title_and_period_does_not_add_second_account(rules: Rules) -> None:
assert _flat_schema(rules, "Субконто") == [("ACCOUNT", "Субконто")]