arm-ka-fixtures/synthgen/templates/build_osv_style.py
Andrey Epifancev 6fb42be8ff synthgen: ОСВ в оформлении 1С (#38)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 00:20:44 +04:00

91 lines
4.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.

"""Сборка шаблона оформления ОСВ 1С (`osv_style.json`) из реального образца — один раз, результат коммитится.
Запуск из корня worktree:
uv run -q --with openpyxl==3.1.5 python -m synthgen.templates.build_osv_style [путь к образцу]
В JSON попадают только оформление и неизменяемые тексты шапки (строки 1–11): ширины колонок, высоты и уровни
группировки строк, шрифты, заливки, рамки, выравнивание, объединения. Название организации и период в шапке
заменены плейсхолдерами `{org}` и `{title}`; ни одно значение данных образца (контрагенты, суммы) в шаблон не попадает.
Сетка — «блок текста A:D + блок сумм E:Q»; колонка ИНН вставляется между ними при записи (`synthgen.osv`).
"""
import json
import sys
from pathlib import Path
import openpyxl
SAMPLE = Path("/Users/andrey.epifancev/projects/awp-ca/fixtures/osv/rb/2024-Q1/сч 60_ 1 кв 2024 г.xlsx")
OUT = Path(__file__).with_name("osv_style.json")
NCOLS = 17 # A…Q: дальше в образце только служебные хвосты
HEADER_ROWS = 11
ROW_TYPES = {"account": 12, "sub": 13, "cp": 14} # образцы строк данных; «Итого» ищется по тексту
PLACEHOLDERS = {(1, 1): "{org}", (2, 1): "{title}"}
def _side(side) -> dict | None:
if not side or not side.style:
return None
return {"style": side.style, "color": side.color.rgb if side.color is not None and isinstance(side.color.rgb, str) else None}
def _style(cell) -> dict:
font, fill, al, bd = cell.font, cell.fill, cell.alignment, cell.border
color = font.color.rgb if font.color is not None and isinstance(font.color.rgb, str) else None
return {
"font": {"name": font.name, "sz": font.sz, "b": bool(font.b), "i": bool(font.i), "color": color},
"fill": fill.fgColor.rgb if fill.fill_type == "solid" else None,
"border": {k: _side(getattr(bd, k)) for k in ("left", "right", "top", "bottom")},
"align": {"h": al.horizontal, "v": al.vertical, "wrap": bool(al.wrap_text), "indent": al.indent or 0},
"nf": cell.number_format,
}
def _row(ws, r: int) -> dict:
dim = ws.row_dimensions[r]
return {
"height": dim.height,
"outline": dim.outlineLevel or 0,
"cells": [_style(ws.cell(r, c)) for c in range(1, NCOLS + 1)],
}
def build(sample: Path = SAMPLE, out: Path = OUT) -> dict:
ws = openpyxl.load_workbook(sample).active
total_row = next(r for r in range(ws.max_row, 0, -1) if ws.cell(r, 1).value == "Итого")
spans = sorted(
(m.min_col, m.max_col) for m in ws.merged_cells.ranges if m.min_row == 10 and m.max_row == 11 and m.min_col > 4
)
header = []
for r in range(1, HEADER_ROWS + 1):
row = _row(ws, r)
row["values"] = {
str(c): PLACEHOLDERS.get((r, c), ws.cell(r, c).value)
for c in range(1, NCOLS + 1)
if ws.cell(r, c).value is not None
}
header.append(row)
style = {
"source": "образец 1С 8.3 (БП 3.0): ОСВ по счёту, лист Лист_1",
"ncols": NCOLS,
"text_span": [1, 4], # блок «Счет / Контрагенты»: A:D
"sum_spans": [list(s) for s in spans], # шесть блоков сумм: сальдо нач. Дт/Кт, обороты Дт/Кт, сальдо кон. Дт/Кт
"col_widths": {
str(c): ws.column_dimensions[openpyxl.utils.get_column_letter(c)].width for c in range(1, NCOLS + 1)
},
"inn_width": 11.0,
"header": header,
"header_merges": sorted([m.min_row, m.min_col, m.max_row, m.max_col] for m in ws.merged_cells.ranges if m.min_row <= HEADER_ROWS),
"rows": {name: _row(ws, r) for name, r in ROW_TYPES.items()},
"total": _row(ws, total_row),
"default_row_height": ws.sheet_format.defaultRowHeight,
"summary_below": bool(ws.sheet_properties.outlinePr.summaryBelow),
"grid_lines": ws.sheet_view.showGridLines is not False,
}
out.write_text(json.dumps(style, ensure_ascii=False, indent=1, sort_keys=True) + "\n", encoding="utf-8")
return style
if __name__ == "__main__":
build(Path(sys.argv[1]) if len(sys.argv) > 1 else SAMPLE)