arm-tabular-parser/api/tests/synthetic/generate_statements.py
Andrey Epifancev 4907935e46 test(statements): st02 согласованные данные, безусловные проверки, шапка документа не верхняя строка заголовка (T13.I1, T13.I2)
Генератор st02: значения ST02_VALUES сходятся по всем жёстким соотношениям. _upper_header_row: верхняя строка берётся,
только если подпись склеивается с датой в период. Refs #14

Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
Claude-Session: cedd7915-2efe-41e5-988c-a9117033598b
2026-10-06 19:29:08 +04:00

177 lines
7.5 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.

"""Синтетические формы ББ/ОФР (Срез 2b): `python -m tests.synthetic.generate_statements <каталог>` из `api/`.
st01 — ББ и ОФР на одном листе; st02 — «csv-подобный» лист: заголовок формы, коды без подписи «Код», заголовок в две
строки, колонка «Изменение, %», отрицательные в скобках; st03 — форма без колонки кодов; st04 — книга с листом ОСВ
и листом баланса; st05 — одинокая страница 2. Детерминирован (см. `generate.py`).
"""
import argparse
import sys
from collections.abc import Callable, Sequence
from pathlib import Path
from tests.synthetic.generate import ORG, OsvSpec, SheetSpec, base_tree, build_osv, to_xlsx
__all__ = ["FILE_NAMES", "generate_all", "main"]
BALANCE_LINES = [
("Основные средства", "1150", 100, 90),
("Итого по разделу I", "1100", 100, 90),
("Запасы", "1210", 50, 40),
("Итого по разделу II", "1200", 50, 40),
("БАЛАНС", "1600", 150, 130),
("Уставный капитал", "1310", 150, 130),
("Итого по разделу III", "1300", 150, 130),
("БАЛАНС", "1700", 150, 130),
]
INCOME_LINES = [
("Выручка", "2110", 1000, 900),
("Себестоимость продаж", "2120", "(600)", "(500)"),
("Валовая прибыль (убыток)", "2100", 400, 400),
("Коммерческие расходы", "2210", "(50)", "(40)"),
("Управленческие расходы", "2220", "(50)", "(60)"),
("Прибыль (убыток) от продаж", "2200", 300, 300),
("Проценты к уплате", "2330", "(100)", "(100)"),
("Прибыль (убыток) до налогообложения", "2300", 200, 200),
("Налог на прибыль", "2410", "(40)", "(40)"),
("Чистая прибыль (убыток)", "2400", 160, 160),
] # полный набор: все жёсткие соотношения ОФР сходятся; скобки — запись 66н
def _head(sheet: SheetSpec, row: int, title: str, subtitle: str, okud: str) -> int:
sheet.put(row, 2, title)
sheet.put(row + 1, 2, subtitle)
sheet.put(row + 2, 6, "Форма по ОКУД")
sheet.put(row + 2, 7, okud)
sheet.put(row + 3, 1, "Организация")
sheet.put(row + 3, 3, ORG)
sheet.put(row + 3, 6, "ИНН")
sheet.put(row + 3, 7, "7700000000")
sheet.put(row + 4, 1, "Единица измерения:")
sheet.put(row + 4, 3, "тыс. руб.")
return row + 6
def _table(
sheet: SheetSpec, row: int, labels: Sequence[str], lines: Sequence[tuple], *, caption: str | None = "Код"
) -> int:
sheet.put(row, 2, "Наименование показателя")
if caption:
sheet.put(row, 4, caption)
for offset, label in enumerate(labels):
sheet.put(row, 5 + offset, label)
for index, (name, code, *values) in enumerate(lines, start=row + 1):
sheet.put(index, 2, name)
sheet.put(index, 4, code)
for offset, value in enumerate(values):
sheet.put(index, 5 + offset, value)
return row + 1 + len(lines)
def _st01() -> list[SheetSpec]:
sheet = SheetSpec("Отчетность")
row = _head(sheet, 1, "Бухгалтерский баланс", "на 31 декабря 2024 г.", "0710001")
row = _table(sheet, row, ["На 31 декабря 2024 г.", "На 31 декабря 2023 г."], BALANCE_LINES)
row = _head(sheet, row + 1, "Отчет о финансовых результатах", "за 2024 г.", "0710002")
_table(sheet, row, ["За 2024 г.", "За 2023 г."], INCOME_LINES)
return [sheet]
# Значения st02, согласованные со всеми жёсткими соотношениями ББ (statement_checks.yaml) в обеих колонках:
# 1100 = 1150, 1200 = 1210 (скобки — запись 66н), 1600 = 1100 + 1200 = 1700, 1300 = 1310, 1700 = 1300.
ST02_VALUES: dict[str, tuple[int | str, int | str]] = {
"1150": (100, 120),
"1100": (100, 120),
"1210": ("(50)", "(40)"),
"1200": ("(50)", "(40)"),
"1600": (50, 80),
"1310": (50, 80),
"1300": (50, 80),
"1700": (50, 80),
}
def _st02() -> list[SheetSpec]:
sheet = SheetSpec("Лист1")
sheet.put(1, 1, "Бухгалтерский баланс")
sheet.put(2, 1, "Организация")
sheet.put(2, 2, 'ООО "Синтетика"')
sheet.put(3, 1, "ИНН")
sheet.put(3, 2, "7700000001")
sheet.put(5, 1, "Показатель")
sheet.put(5, 3, "На 31 декабря")
sheet.put(5, 4, "На 31 декабря")
sheet.put(5, 5, "Изменение, %")
sheet.put(6, 3, "2024 г.")
sheet.put(6, 4, "2023 г.")
for index, (name, code, current, previous) in enumerate(BALANCE_LINES, start=7):
sheet.put(index, 1, name)
sheet.put(index, 2, code)
value_now, value_before = ST02_VALUES.get(code, (current, previous))
sheet.put(index, 3, value_now)
sheet.put(index, 4, value_before)
sheet.put(index, 5, 5)
return [sheet]
def _st03() -> list[SheetSpec]:
sheet = SheetSpec("ББ")
row = _head(sheet, 1, "Бухгалтерский баланс", "на 31 декабря 2024 г.", "0710001")
sheet.put(row, 2, "Показатель")
sheet.put(row, 5, "На 31 декабря 2024 г.")
for index, (name, _, current, _) in enumerate(BALANCE_LINES, start=row + 1):
sheet.put(index, 2, name)
sheet.put(index, 5, current)
return [sheet]
def _st04() -> list[SheetSpec]:
balance = SheetSpec("Баланс")
row = _head(balance, 1, "Бухгалтерский баланс", "на 31 декабря 2024 г.", "0710001")
_table(balance, row, ["На 31 декабря 2024 г."], [(name, code, current) for name, code, current, _ in BALANCE_LINES])
return [build_osv(OsvSpec(), base_tree()), balance]
def _st05() -> list[SheetSpec]:
sheet = SheetSpec("Лист_1")
sheet.put(1, 8, "Форма 0710001 с.2")
_table(sheet, 2, ["На 31 декабря 2024 г.", "На 31 декабря 2023 г."], BALANCE_LINES[5:])
return [sheet]
FILES: dict[str, tuple[Callable[[], list[SheetSpec]], Callable[[Sequence[SheetSpec]], bytes]]] = {
"st01_both_forms_one_sheet.xlsx": (_st01, to_xlsx),
"st02_csv_like.xlsx": (_st02, to_xlsx),
"st03_no_codes.xlsx": (_st03, to_xlsx),
"st04_mixed_book.xlsx": (_st04, to_xlsx),
"st05_continuation_only.xlsx": (_st05, to_xlsx),
}
FILE_NAMES = tuple(FILES)
def generate_all(directory: Path) -> list[Path]:
directory.mkdir(parents=True, exist_ok=True)
written: list[Path] = []
for name, (build, serialize) in FILES.items():
path = directory / name
path.write_bytes(serialize(build()))
written.append(path)
return written
def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(
prog="python -m tests.synthetic.generate_statements", description="Синтетические формы st01…st05."
)
parser.add_argument(
"directory", type=Path, help="каталог назначения, например ../../fixtures/statements-xlsx/synthetic"
)
args = parser.parse_args(argv)
for path in generate_all(args.directory):
print(path)
return 0
if __name__ == "__main__":
sys.exit(main())