Files
kadastr18/priem_register/writers/register_writer.py
jze9 b5f7060a1f Приём документов → реестр + журнал
Внесение данных из файлов приёма в реестр невостребованных документов
и заполнение журнала. Дата внесения — текущая; журнал заполняется чисто
(без строк-примеров шаблона).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 03:11:18 +05:00

181 lines
6.6 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.
# -*- coding: utf-8 -*-
"""Запись принятых документов в реестр (.ods) через odfpy.
Новые строки дописываются сразу после последней нумерованной строки листа.
Стиль каждой ячейки (границы, формат даты/числа) и стиль строки копируются
из этой последней строки-образца, поэтому оформление реестра сохраняется.
Остальные листы, формулы и стили документа odfpy не трогает.
"""
from __future__ import annotations
import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from odf.opendocument import load
from odf.table import Table, TableCell, TableRow
from odf.teletype import extractText
from odf.text import P
from ..config import REGISTER_COLUMNS
from ..models import CellValue, Record
# Повторения больше этого — «бесконечный» пустой хвост листа, не данные.
_MAX_REPEAT = 200
class RegisterWriter:
def __init__(self, path: Path):
self.doc = load(str(path))
self._tables: Dict[str, Table] = {
t.getAttribute("name"): t
for t in self.doc.spreadsheet.getElementsByType(Table)
}
def has_sheet(self, name: str) -> bool:
return name in self._tables
def append(self, sheet: str, records: List[Record]) -> int:
"""Дописать записи на лист. Возвращает число добавленных строк."""
table = self._tables[sheet]
template, last_number = self._find_last_numbered_row(table)
col_styles = self._column_styles(template)
row_style = template.getAttribute("stylename")
anchor = template
number = last_number
for record in records:
number += 1
row = self._build_row(record, number, row_style, col_styles)
table.insertBefore(row, anchor.nextSibling)
anchor = row
return len(records)
def save(self, path: Path) -> None:
self.doc.save(str(path))
# --- поиск строки-образца -------------------------------------------
def _find_last_numbered_row(self, table: Table) -> Tuple[TableRow, int]:
last_row: Optional[TableRow] = None
last_number = 0
for tr in table.getElementsByType(TableRow):
if _row_repeat(tr) > 1:
continue # повторяющийся пустой блок
cells = tr.getElementsByType(TableCell)
if not cells:
continue
number = _as_int(cells[0])
if number is not None:
last_row, last_number = tr, number
if last_row is None:
raise ValueError("На листе нет нумерованных строк: %s"
% table.getAttribute("name"))
return last_row, last_number
@staticmethod
def _column_styles(template: TableRow) -> Dict[int, Optional[str]]:
"""Карта {индекс колонки 0..12 -> имя стиля ячейки} из образца."""
styles: Dict[int, Optional[str]] = {}
col = 0
for cell in template.getElementsByType(TableCell):
rep = _col_repeat(cell)
style = cell.getAttribute("stylename")
for _ in range(rep):
if col < REGISTER_COLUMNS:
styles[col] = style
col += 1
if col >= REGISTER_COLUMNS:
break
return styles
# --- построение строки ----------------------------------------------
def _build_row(self, record: Record, number: int,
row_style: Optional[str],
col_styles: Dict[int, Optional[str]]) -> TableRow:
row = TableRow(stylename=row_style) if row_style else TableRow()
for col in range(REGISTER_COLUMNS):
style = col_styles.get(col)
value = number if col == 0 else record.value(col)
row.addElement(_make_cell(value, style))
return row
# --- фабрика ячеек по типу значения -------------------------------------
def _make_cell(value: CellValue, style: Optional[str]) -> TableCell:
if value is None or value == "":
return _cell(style)
if isinstance(value, bool):
return _text_cell(str(value), style)
if isinstance(value, datetime.datetime):
return _date_cell(value, style, with_time=True)
if isinstance(value, datetime.date):
return _date_cell(value, style, with_time=False)
if isinstance(value, (int, float)):
return _number_cell(value, style)
return _text_cell(str(value), style)
def _cell(style: Optional[str]) -> TableCell:
return TableCell(stylename=style) if style else TableCell()
def _text_cell(text: str, style: Optional[str]) -> TableCell:
cell = TableCell(valuetype="string", stylename=style) if style \
else TableCell(valuetype="string")
for line in text.split("\n"):
cell.addElement(P(text=line))
return cell
def _number_cell(num, style: Optional[str]) -> TableCell:
text = _number_text(num)
kw = dict(valuetype="float", value=text)
if style:
kw["stylename"] = style
cell = TableCell(**kw)
cell.addElement(P(text=text))
return cell
def _date_cell(value, style: Optional[str], with_time: bool) -> TableCell:
iso = value.strftime("%Y-%m-%dT%H:%M:%S" if with_time else "%Y-%m-%d")
display = value.strftime("%d.%m.%Y")
kw = dict(valuetype="date", datevalue=iso)
if style:
kw["stylename"] = style
cell = TableCell(**kw)
cell.addElement(P(text=display))
return cell
# --- мелкие помощники ---------------------------------------------------
def _number_text(num) -> str:
if isinstance(num, float) and num.is_integer():
return str(int(num))
return str(num)
def _row_repeat(tr: TableRow) -> int:
raw = tr.getAttribute("numberrowsrepeated")
n = int(raw) if raw else 1
return n if n <= _MAX_REPEAT else _MAX_REPEAT + 1
def _col_repeat(cell: TableCell) -> int:
raw = cell.getAttribute("numbercolumnsrepeated")
n = int(raw) if raw else 1
return n if n <= _MAX_REPEAT else 1
def _as_int(cell: TableCell) -> Optional[int]:
if cell.getAttribute("valuetype") == "float":
raw = cell.getAttribute("value")
if raw:
return int(float(raw))
text = "".join(extractText(p) for p in cell.getElementsByType(P)).strip()
return int(text) if text.isdigit() else None