test
This commit is contained in:
195
services/form_filler.py
Normal file
195
services/form_filler.py
Normal file
@@ -0,0 +1,195 @@
|
||||
"""Заполнение одной формы 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)
|
||||
Reference in New Issue
Block a user