"""Сборка шаблонов ББ и ОФР в оформлении 1С (`bb_1c.xlsx`, `ofr_1c.xlsx`) из реальных образцов — один раз, результат коммитится. Запуск из корня worktree: uv run -q --with openpyxl==3.1.5 python -m synthgen.templates.build_forms_templates [каталог образцов] Книга образца копируется целиком (шрифты Times New Roman, объединения, рамки, высоты строк, форматы, блок «КОДЫ», разрывы страниц, неизменяемые тексты форм 66н). В шаблоне очищено всё, что принадлежит образцу или считается генератором: - все формулы (итоги, заголовки второй страницы, служебные расчёты) заменены пустыми значениями; - числа в колонках значений строк с кодами, наименование организации, ИНН, ОКПО, ОКВЭД, ОКОПФ/ОКФС, вид деятельности, адрес, дата формы, периодные подписи колонок и заголовка, ФИО подписантов; - ячейки правее последней колонки формы (служебные расчёты автора образца) — значения и оформление. Генератор (`synthgen.forms_xlsx`) пишет свои значения по кодам строк. """ import sys from pathlib import Path import openpyxl from openpyxl.cell.cell import MergedCell from openpyxl.styles import Alignment, Border, Font, PatternFill from synthgen import xlsx SAMPLES = Path("/Users/andrey.epifancev/projects/awp-ca/fixtures/statements-xlsx/mezheninovskaya/2025-Q3") HERE = Path(__file__).parent # (файл образца, имя шаблона, последняя колонка формы, колонка кодов, ячейки реквизитов и подписей для очистки) BUILD = { "BALANCE": ( "1_Форма1_бух_09.xlsx", "bb_1c.xlsx", 20, 11, ("B3", "G7", "R6", "S6", "T6", "R7", "R8", "G9", "R9", "G10", "R10", "S10", "G13", "M23", "Q23", "S23", "M48", "Q48", "S48", "Q80", "Q83"), ), "INCOME": ( "1_Форма2_бух_09.xlsx", "ofr_1c.xlsx", 18, 13, ("B3", "G7", "P6", "Q6", "R6", "P7", "P8", "G9", "P9", "G10", "P10", "Q10", "O14", "Q14", "O36", "Q36", "O49", "O52"), ), } # fmt: skip def _is_code(value) -> bool: return isinstance(value, str) and value.isdigit() and len(value) == 4 def build(kind: str, samples: Path = SAMPLES, out_dir: Path = HERE) -> Path: sample, name, last_col, code_col, clear = BUILD[kind] wb = openpyxl.load_workbook(samples / sample) ws = wb.active value_cols: set[int] = set() for row in ws.iter_rows(): for cell in row: # колонки значений: ячейки номеров колонок (строка под «Коды») правее колонки кодов if cell.value == "Коды" and cell.column == code_col: value_cols |= {c.column for c in ws[cell.row + 1] if c.value is not None and c.column > code_col} code_rows = [r for r in range(1, ws.max_row + 1) if _is_code(ws.cell(r, code_col).value)] for coord in clear: ws[coord].value = None for r in code_rows: for c in value_cols: cell = ws.cell(r, c) if not isinstance(cell, MergedCell): cell.value = None for row in ws.iter_rows(): for cell in row: if isinstance(cell.value, str) and cell.value.startswith("="): cell.value = None if cell.column > last_col and not isinstance(cell, MergedCell): cell.value = None cell.font, cell.fill, cell.border, cell.alignment = Font(), PatternFill(), Border(), Alignment() cell.number_format = "General" wb.properties.title = None path = out_dir / name xlsx.save_deterministic(wb, path) return path def main(argv: list[str]) -> None: samples = Path(argv[1]) if len(argv) > 1 else SAMPLES for kind in BUILD: print(build(kind, samples)) if __name__ == "__main__": main(sys.argv)