Files
kadastr-17/services/form_filler.py
2026-03-10 20:21:31 +05:00

196 lines
8.2 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.
"""Заполнение одной формы XLSX по шаблону."""
import copy
import datetime
from openpyxl import load_workbook
from openpyxl.styles import Font
from services.data_loader import ki_val
from services.mif_processor import get_contours, build_coord_rows
# ─────────────────────────────── Константы ──────────────────────────────── #
BASE_FONT = Font(name="Times New Roman", size=11)
# Позиции строк шаблона (1-based, до вставки доп. строк координат)
TMPL = dict(
KV=2, # A2: кадастровый квартал
DATE=5, # F5: дата подготовки
FIO=11, # F11: ФИО КИ
SNILS=12, # F12: СНИЛС
PHONE=13, # F13: телефон
EMAIL=15, # A15: email + адрес
SRO=17, # A17: наименование СРО
REGNR=19, # A19: рег. номер
SOGL=24, # A24: соглашение
LOC_ZU=29, # F29: местоположение ЗУ
AREA_ZU=31, # F31: площадь ЗУ
OBOSN_ZU=33, # F33: обоснование ЗУ
ZU_COORD_START=37, # первая строка данных координат ЗУ
ZU_COORD_PRE=5, # кол-во предзаполненных строк (37-41)
LOC_OKS=54, # F54: местоположение ОКС (до смещения)
OBOSN_OKS=58, # F58: обоснование ОКС (до смещения)
OKS_COORD_START=62, # первая строка данных ОКС (до смещения)
OKS_COORD_PRE=1, # кол-во предзаполненных строк ОКС
)
# ──────────────────────────── Вспомогательные ───────────────────────────── #
def _sv(ws, row: int, col: int, value, font=None):
cell = ws.cell(row=row, column=col)
cell.value = value
if font:
cell.font = copy.copy(font)
def _safe_insert_rows(ws, idx: int, amount: int):
"""
Вставить строки с корректным сдвигом слиянных ячеек.
openpyxl's ws.insert_rows() имеет известный баг: не сдвигает все merge-диапазоны.
Решение: вручную снимаем/пересчитываем merge до вызова insert_rows.
"""
# Собираем все диапазоны, которые пересекаются с точкой вставки или ниже неё
to_readjust = []
for rng in list(ws.merged_cells.ranges):
if rng.max_row >= idx:
to_readjust.append((rng.min_row, rng.min_col, rng.max_row, rng.max_col))
ws.merged_cells.remove(rng)
ws.insert_rows(idx, amount)
# Восстанавливаем диапазоны с поправкой
for min_r, min_c, max_r, max_c in to_readjust:
new_min_r = min_r + amount if min_r >= idx else min_r
new_max_r = max_r + amount if max_r >= idx else max_r
ws.merge_cells(
start_row=new_min_r, start_column=min_c,
end_row=new_max_r, end_column=max_c,
)
def _copy_row_style(ws, src_row: int, dst_row: int):
for col in range(1, ws.max_column + 1):
src = ws.cell(row=src_row, column=col)
dst = ws.cell(row=dst_row, column=col)
if src.has_style:
dst.font = copy.copy(src.font)
dst.border = copy.copy(src.border)
dst.fill = copy.copy(src.fill)
dst.number_format = src.number_format
dst.alignment = copy.copy(src.alignment)
def _insert_coord_rows(ws, coord_data: list, start_row: int, pre_allocated: int) -> int:
"""Вставить строки координат. Возвращает кол-во добавленных строк (смещение)."""
needed = len(coord_data)
extra = max(0, needed - pre_allocated)
if extra > 0:
_safe_insert_rows(ws, start_row + pre_allocated, extra)
for i in range(extra):
_copy_row_style(ws, start_row, start_row + pre_allocated + i)
F = BASE_FONT
for i, entry in enumerate(coord_data):
row_idx = start_row + i
if entry is None:
continue
num, x_str, y_str, method, mt = entry
_sv(ws, row_idx, 1, num, F)
_sv(ws, row_idx, 3, x_str, F)
_sv(ws, row_idx, 4, y_str, F)
_sv(ws, row_idx, 5, method, F)
_sv(ws, row_idx, 6, mt, F)
return extra
def _apply_font_all(ws):
"""Применить Times New Roman 11 ко всем заполненным ячейкам листа."""
for row in ws.iter_rows():
for cell in row:
if cell.value is not None:
old = cell.font
cell.font = Font(
name="Times New Roman", size=11,
bold=old.bold if old else False,
italic=old.italic if old else False,
underline=old.underline if old else None,
)
# ─────────────────────────────── Публичная функция ──────────────────────── #
def fill_form(template_path: str, output_path: str,
ki: dict, t2_row: tuple, mif_index: dict):
"""Заполнить шаблон данными одной строки Таблицы 2 и сохранить файл."""
def col(i, default=None):
return t2_row[i] if len(t2_row) > i and t2_row[i] is not None else default
zu_file = col(0, "")
kv = col(1, "")
date_prep = col(2)
sogl = col(3, "")
loc_zu = col(4, "")
area_zu = col(5, "")
err_zu = col(6, "")
obosn_zu = col(7, "")
method_zu = col(8, "")
mt_zu = col(9, "")
oks_file = col(10, "")
loc_oks = col(11, "")
obosn_oks = col(12, "")
method_oks = col(13, "")
mt_oks = col(14, "")
wb = load_workbook(template_path)
ws = wb.active
F = BASE_FONT
T = TMPL
# Шапка
_sv(ws, T["KV"], 1, str(kv), F)
# Дата: конвертируем datetime в строку dd.mm.yyyy
if isinstance(date_prep, datetime.datetime):
date_str = date_prep.strftime("%d.%m.%Y")
else:
date_str = str(date_prep) if date_prep else ""
_sv(ws, T["DATE"], 6, date_str, F)
# Раздел 2: КИ
_sv(ws, T["FIO"], 6, ki_val(ki, "ФИО"), F)
_sv(ws, T["SNILS"], 6, ki_val(ki, "Страховой"), F)
_sv(ws, T["PHONE"], 6, ki_val(ki, "Контактный"), F)
_sv(ws, T["EMAIL"], 1, ki_val(ki, "Адрес электронной почты"), F)
_sv(ws, T["SRO"], 1, ki_val(ki, "Наименование саморегулируемой"), F)
_sv(ws, T["REGNR"], 1, ki_val(ki, "Уникальный"), F)
# Раздел 3: Основания
_sv(ws, T["SOGL"], 1, sogl, F)
# Раздел 4: ЗУ
_sv(ws, T["LOC_ZU"], 6, loc_zu, F)
area_str = f"{area_zu}±{err_zu}" if (area_zu or err_zu) else ""
_sv(ws, T["AREA_ZU"], 6, area_str, F)
_sv(ws, T["OBOSN_ZU"], 6, obosn_zu, F)
# Раздел 5: координаты ЗУ
zu_path = mif_index.get(str(zu_file).strip())
zu_contours = get_contours(zu_path) if zu_path else []
zu_coord_data = build_coord_rows(zu_contours, str(method_zu), mt_zu)
zu_offset = _insert_coord_rows(ws, zu_coord_data, T["ZU_COORD_START"], T["ZU_COORD_PRE"])
# Разделы 7-9: ОКС (со смещением)
ofs = zu_offset
_sv(ws, T["LOC_OKS"] + ofs, 6, loc_oks or "-", F)
_sv(ws, T["OBOSN_OKS"] + ofs, 6, obosn_oks or "-", F)
oks_path = mif_index.get(str(oks_file).strip()) if oks_file else None
oks_contours = get_contours(oks_path) if oks_path else []
oks_coord_data = build_coord_rows(oks_contours, str(method_oks or ""), mt_oks)
if oks_coord_data:
_insert_coord_rows(ws, oks_coord_data, T["OKS_COORD_START"] + ofs, T["OKS_COORD_PRE"])
_apply_font_all(ws)
wb.save(output_path)