arm-ka-fixtures/synthgen/forms_xlsx.py
2026-11-13 00:30:31 +04:00

110 lines
6.6 KiB
Python
Raw Permalink 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.

"""ББ и ОФР в Excel по шаблону 1С (спека §6.2): копия книги образца (`templates/bb_1c.xlsx`, `ofr_1c.xlsx`) с подстановкой
реквизитов и значений. Значения ищутся по колонке «Коды» (и номерам колонок под заголовком), а не по номерам строк;
положение реквизитов шапки — таблица `HEADER_CELLS` (шаблон наш, собран `templates/build_forms_templates.py`).
Значения — целые тыс. руб., расходы ОФР — отрицательные числа (решение 12); ноль пишется `0` с форматом `[=0]"-";General`,
остальное — `#,##0`, как в выгрузке 1С.
"""
from pathlib import Path
import openpyxl
from synthgen import xlsx
from synthgen.forms import Form
from synthgen.rng import stable_hash
TEMPLATES = Path(__file__).with_name("templates")
TEMPLATE_FILES = {"BALANCE": "bb_1c.xlsx", "INCOME": "ofr_1c.xlsx"}
ZERO_FORMAT = '[=0]"-";General'
NUMBER_FORMAT = "#,##0"
HEADER_CELLS = {
"BALANCE": {
"title": "B3", "org": "G7", "okpo": "R7", "inn": "R8", "activity": "G9", "okved": "R9", "legal_form": "G10", "okopf": "R10",
"okfs": "S10", "address": "G13", "date": ("R6", "S6", "T6"), "head": "Q80", "accountant": "Q83",
},
"INCOME": {
"title": "B3", "org": "G7", "okpo": "P7", "inn": "P8", "activity": "G9", "okved": "P9", "legal_form": "G10", "okopf": "P10",
"okfs": "Q10", "address": None, "date": ("P6", "Q6", "R6"), "head": "O49", "accountant": "O52",
},
} # fmt: skip
ACTIVITY = {
"агро": ("Сельское хозяйство", "01.11.11"), "торговля": ("Торговля оптовая", "46.90"),
"логистика": ("Деятельность автомобильного грузового транспорта", "49.41"), "производство": ("Производство прочих изделий", "25.99"),
"строительство": ("Строительство зданий", "41.20"), "услуги": ("Деятельность консультативная", "70.22"),
} # fmt: skip
LEGAL_FORM = {"ООО": ("ООО - общество с ограниченной ответственностью", "65"), "АО": ("АО - акционерное общество", "47")}
STREETS = ("Ленина", "Советская", "Мира", "Заводская", "Промышленная", "Центральная", "Садовая", "Новая")
CITIES = ("г. Тверь", "г. Липецк", "г. Курск", "г. Тула", "г. Рязань", "г. Орёл", "г. Тамбов", "г. Калуга")
SURNAMES = ("Ветров", "Озеров", "Лугов", "Дубравин", "Ключников", "Ракитин", "Соловьёв", "Березин", "Туманов", "Зорин")
FEMALE_SURNAMES = ("Ветрова", "Озерова", "Лугова", "Дубравина", "Ключникова", "Ракитина", "Соловьёва", "Березина", "Туманова", "Зорина")
MALE_NAMES = ("Андрей Павлович", "Игорь Сергеевич", "Олег Николаевич", "Максим Андреевич", "Денис Олегович")
FEMALE_NAMES = ("Анна Игоревна", "Ольга Павловна", "Елена Сергеевна", "Марина Андреевна", "Татьяна Олеговна")
def file_name(form: Form) -> str:
prefix = "ББ" if form.kind == "BALANCE" else "ОФР"
return f"{prefix}_{form.period.label_1c[:-1]}.xlsx" # метка периода кончается точкой — она же перед расширением
def _person(form: Form, role: str) -> str:
key = f"{form.member.code}|{form.period.key}|{role}"
if stable_hash(key + "|sex", 2):
return f"{FEMALE_SURNAMES[stable_hash(key + '|s', 10)]} {FEMALE_NAMES[stable_hash(key + '|n', 5)]}"
return f"{SURNAMES[stable_hash(key + '|s', 10)]} {MALE_NAMES[stable_hash(key + '|n', 5)]}"
def _requisites(form: Form) -> dict[str, object]:
m = form.member
activity, okved = ACTIVITY.get(m.org.kind, ACTIVITY["услуги"])
legal, okopf = LEGAL_FORM.get(m.org.form, LEGAL_FORM["АО"])
h = stable_hash(m.inn, 10**6)
address = f"{100000 + stable_hash(m.inn + 'z', 899999)}, {CITIES[h % 8]}, ул. {STREETS[(h // 8) % 8]}, дом № {1 + h % 97}"
end = form.period.end
return {
"title": f"за {form.period.label_1c}", "org": m.name, "okpo": f"{stable_hash(m.inn + 'okpo', 10**8):08d}", "inn": m.inn,
"activity": activity, "okved": okved, "legal_form": legal, "okopf": okopf, "okfs": "16", "address": address,
"head": _person(form, "head"), "accountant": _person(form, "accountant"), "date": (end.day, end.month, end.year),
} # fmt: skip
def _tables(ws) -> list[tuple[int, list[int], int]]:
"""Таблицы формы по заголовку «Коды»: (колонка кодов, колонки значений, строка заголовка)."""
found = []
for row in ws.iter_rows():
for cell in row:
if cell.value == "Коды":
cols = [c.column for c in ws[cell.row + 1] if c.value is not None and c.column > cell.column]
found.append((cell.column, cols, cell.row))
return found
def write(form: Form, path: Path) -> None:
wb = openpyxl.load_workbook(TEMPLATES / TEMPLATE_FILES[form.kind])
ws = wb.active
cells = HEADER_CELLS[form.kind]
req = _requisites(form)
for key, coord in cells.items():
if coord is None:
continue
if key == "date":
for c, v in zip(coord, req["date"], strict=True):
ws[c].value = v
else:
ws[coord].value = req[key]
for code_col, value_cols, head in _tables(ws):
if len(value_cols) != len(form.columns):
raise ValueError(f"в шаблоне {len(value_cols)} колонок значений, в форме {len(form.columns)}")
for col, title in zip(value_cols, form.columns, strict=True):
ws.cell(head, col).value = title
for r in range(head + 2, ws.max_row + 1):
code = ws.cell(r, code_col).value
if not (isinstance(code, str) and code.isdigit() and len(code) == 4):
continue
for i, col in enumerate(value_cols):
value = int(form.values[code][i])
cell = ws.cell(r, col)
cell.value = value
cell.number_format = ZERO_FORMAT if value == 0 else NUMBER_FORMAT
xlsx.save_deterministic(wb, path)