83 lines
4.3 KiB
Python
83 lines
4.3 KiB
Python
"""Сборка шаблонов ББ и ОФР в оформлении 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)
|