"""Синтетические формы ББ/ОФР (Срез 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())