# -*- coding: utf-8 -*- """Базовый класс читателя источника.""" from __future__ import annotations import abc import datetime from pathlib import Path from typing import List from ..config import REGISTER_COLUMNS, SOURCE_NUMBER_COL from ..models import CellValue, ReadResult, Record # Позиция колонки «Примечание» (L) в раскладке реестра (A=0 … M=12). NOTE_COL = 11 class SourceReader(abc.ABC): """Читает файл приёма и возвращает записи для внесения. По заданию: «внести все строки, начиная со второй». Поэтому пропускается только шапка (строка 1), а каждая последующая непустая строка вносится как отдельная запись — без фильтрации по № п/п. № п/п в реестре назначается заново (сквозной), исходный № отбрасывается. В реестре 13 колонок (A..M). Если в источнике есть данные правее колонки M (например, безымянная 14-я колонка), переносить их в отдельную колонку некуда — они добавляются в «Примечание» той же строки, а факт фиксируется в ``notes`` (информационно). Подклассы реализуют только :meth:`_rows` — выдачу «сырых» строк таблицы (список типизированных значений ячеек) первого листа. """ def __init__(self, path: Path): self.path = Path(path) @abc.abstractmethod def _rows(self) -> List[List[CellValue]]: """Сырые строки первого листа, типизированные значения ячеек.""" def read(self) -> ReadResult: result = ReadResult() for index, row in enumerate(self._rows()): if index == 0: continue # шапка (строка 1) if not self._has_content(row): continue # пустые строки не вносим record = Record(values=list(row[SOURCE_NUMBER_COL + 1: REGISTER_COLUMNS])) self._fold_overflow(row, record, result, index + 1) result.records.append(record) return result def _fold_overflow(self, row: List[CellValue], record: Record, result: ReadResult, human_row: int) -> None: overflow = [v for i, v in enumerate(row) if i >= REGISTER_COLUMNS and v not in (None, "")] for value in overflow: _merge_cell(record, NOTE_COL, value) if overflow: result.notes.append( "строка %d: данные за колонкой M добавлены в «Примечание»: %s" % (human_row, ", ".join(_as_text(v) for v in overflow))) @staticmethod def _has_content(row: List[CellValue]) -> bool: return any(v not in (None, "") for v in row) def _merge_cell(record: Record, position: int, value: CellValue) -> None: """Записать значение в колонку записи; занятое поле дополнить через «; ».""" idx = position - 1 while len(record.values) <= idx: record.values.append(None) current = record.values[idx] if current in (None, ""): record.values[idx] = value else: record.values[idx] = "%s; %s" % (_as_text(current), _as_text(value)) def _as_text(value: CellValue) -> str: if isinstance(value, datetime.date): # покрывает и datetime return value.strftime("%d.%m.%Y") if isinstance(value, float) and value.is_integer(): return str(int(value)) return str(value)