111 lines
4.4 KiB
Python
111 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
||
"""Быстрый осмотр ОСВ-файлов из 1С: шапка, схема группировок, колонки сумм, уровни строк.
|
||
|
||
Назначение — исследовательский инструмент для Среза 2 (парсер ОСВ), а не парсер.
|
||
Запуск: python scripts/osv_inspect.py tests/fixtures/osv --rows 15
|
||
Зависимости: openpyxl, xlrd (pip install openpyxl xlrd).
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
import openpyxl
|
||
import xlrd
|
||
|
||
TITLE_RE = re.compile(r"по счету\s+([\d.]+)\s*(?:за\s+(.+?))?\s*$")
|
||
|
||
|
||
def load_rows(path: Path):
|
||
"""Возвращает список (level, [cells...]) и имя листа. xlsx — openpyxl (не read_only!), xls — xlrd."""
|
||
if path.suffix.lower() == ".xlsx":
|
||
wb = openpyxl.load_workbook(path, data_only=True)
|
||
ws = wb.worksheets[0]
|
||
out = []
|
||
for r in range(1, ws.max_row + 1):
|
||
lvl = ws.row_dimensions[r].outline_level or 0
|
||
out.append((lvl, [ws.cell(r, c).value for c in range(1, ws.max_column + 1)]))
|
||
return ws.title, out
|
||
wb = xlrd.open_workbook(path, formatting_info=True)
|
||
ws = wb.sheets()[0]
|
||
out = []
|
||
for r in range(ws.nrows):
|
||
lvl = ws.rowinfo_map[r].outline_level if r in ws.rowinfo_map else 0
|
||
out.append((lvl, [c.value if c.value != "" else None for c in ws.row(r)]))
|
||
return ws.name, out
|
||
|
||
|
||
def col_letter(i: int) -> str:
|
||
return openpyxl.utils.get_column_letter(i + 1)
|
||
|
||
|
||
def inspect(path: Path, max_rows: int) -> None:
|
||
sheet, rows = load_rows(path)
|
||
print("=" * 110)
|
||
print(path.name, f"[{sheet}]", f"rows={len(rows)}")
|
||
first = [str(r[1][0]).strip() for r in rows[:3] if r[1] and r[1][0]]
|
||
org = first[0] if first else "?"
|
||
title = next((str(c) for _, cells in rows[:5] for c in cells if c and "по счету" in str(c)), "")
|
||
m = TITLE_RE.search(title)
|
||
acc, per = (m.group(1), m.group(2)) if m else ("?", "?")
|
||
data_line = next((str(c) for _, cells in rows[:8] for c in cells if c and "Выводимые данные" in str(c)), "")
|
||
print(f" org={org!r} account={acc} period={per!r} {data_line}")
|
||
|
||
# заголовок таблицы: строка с «Счет» в первой колонке
|
||
hdr_idx = next((i for i, (_, cells) in enumerate(rows) if cells and str(cells[0]).strip() in ("Счет", "Субконто")), None)
|
||
if hdr_idx is None:
|
||
print(" !! заголовок таблицы не найден")
|
||
return
|
||
groups = []
|
||
inn_col = None
|
||
sums = {}
|
||
i = hdr_idx
|
||
while i < len(rows):
|
||
_, cells = rows[i]
|
||
c0 = str(cells[0]).strip() if cells and cells[0] else ""
|
||
if i > hdr_idx and (not c0 or re.match(r"^\d", c0)):
|
||
break
|
||
if c0:
|
||
groups.append(c0)
|
||
for j, v in enumerate(cells):
|
||
if v is None:
|
||
continue
|
||
s = str(v).strip()
|
||
if s == "ИНН":
|
||
inn_col = col_letter(j)
|
||
if s in ("Дебет", "Кредит"):
|
||
sums.setdefault(s, []).append(col_letter(j))
|
||
i += 1
|
||
data_start = i
|
||
print(f" group rows (шапка): {groups} inn_col={inn_col} Дебет={sums.get('Дебет')} Кредит={sums.get('Кредит')}")
|
||
print(f" table starts at row {data_start + 1}")
|
||
for lvl, cells in rows[data_start : data_start + max_rows]:
|
||
vals = [f"{col_letter(j)}={str(v)[:38]!r}" for j, v in enumerate(cells) if v is not None]
|
||
print(f" L{lvl} | " + " ".join(vals)[:170])
|
||
last = next(((lvl, cells) for lvl, cells in reversed(rows) if cells and cells[0]), None)
|
||
if last:
|
||
print(f" last: L{last[0]} {str(last[1][0])!r} {[v for v in last[1][1:] if v is not None][:6]}")
|
||
|
||
|
||
def main() -> int:
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("paths", nargs="+")
|
||
ap.add_argument("--rows", type=int, default=12)
|
||
a = ap.parse_args()
|
||
files: list[Path] = []
|
||
for p in a.paths:
|
||
p = Path(p)
|
||
files += sorted(p.rglob("*.xls*")) if p.is_dir() else [p]
|
||
for f in files:
|
||
try:
|
||
inspect(f, a.rows)
|
||
except Exception as e: # noqa: BLE001 — исследовательский скрипт
|
||
print(f"!! {f}: {e}")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|