93 lines
4.2 KiB
Python
93 lines
4.2 KiB
Python
"""Разовый импорт CSV-пакета admin/jira-sync: служебные колонки → issue_note; links.csv — сверочный набор
|
||
(ожидаемые связи против зеркала), не команды. Повтор обновляет заметки, не дублирует.
|
||
Пустая ячейка CSV не меняет существующее поле заметки (правки, сделанные в хабе, сохраняются)."""
|
||
import csv
|
||
from pathlib import Path
|
||
|
||
from . import db, mirror, settings
|
||
|
||
|
||
def _rows(path: Path) -> list[dict]:
|
||
if not path.exists():
|
||
return []
|
||
with open(path, encoding="utf-8-sig", newline="") as f:
|
||
return list(csv.DictReader(f))
|
||
|
||
|
||
def _clean(value) -> str | None:
|
||
value = (value or "").strip()
|
||
return value or None
|
||
|
||
|
||
def _note(conn, key, fields: dict) -> None:
|
||
"""Записать только непустые поля; если их нет — заметку не трогаем."""
|
||
fields = {k: v for k, v in fields.items() if v is not None}
|
||
if fields:
|
||
mirror.set_note(conn, key, fields, "import")
|
||
|
||
|
||
def import_package(conn, folder) -> dict:
|
||
folder = Path(folder)
|
||
with db.transaction(conn):
|
||
return _import(conn, folder)
|
||
|
||
|
||
def _import(conn, folder: Path) -> dict:
|
||
notes = 0
|
||
csv_keys = []
|
||
|
||
for r in _rows(folder / "tasks.csv"):
|
||
key = _clean(r.get("Код"))
|
||
if not key:
|
||
continue
|
||
_note(conn, key, {
|
||
"fact_status": _clean(r.get("Статус по факту")), "work_kind": _clean(r.get("Вид работы")),
|
||
"done_text": _clean(r.get("Что сделано по факту (внутр.)")), "rest_text": _clean(r.get("Что осталось (внутр.)")),
|
||
"owner_note": _clean(r.get("Комментарий актуализации"))})
|
||
notes += 1
|
||
csv_keys.append(key)
|
||
|
||
new_keys = {} # new:<строка> → ключ Jira (как в jira_sync.py)
|
||
for i, r in enumerate(_rows(folder / "new_tasks.csv"), 2):
|
||
real = _clean(r.get("Ключ"))
|
||
key = real or f"new:{i}"
|
||
if real:
|
||
new_keys[f"new:{i}"] = real
|
||
csv_keys.append(real)
|
||
_note(conn, key, {
|
||
"fact_status": _clean(r.get("Статус по факту")), "work_kind": _clean(r.get("Вид работы")),
|
||
"rest_text": _clean(r.get("Основание (внутр.)")),
|
||
"owner_note": None if real else _clean(r.get("Тема"))})
|
||
notes += 1
|
||
|
||
for r in _rows(folder / "epics.csv"):
|
||
key = _clean(r.get("Ключ"))
|
||
if not key:
|
||
continue
|
||
_note(conn, key, {"work_kind": "эпик", "owner_note": _clean(r.get("Комментарий"))})
|
||
notes += 1
|
||
csv_keys.append(key)
|
||
|
||
link_types = settings.get(conn, "link_types") or {}
|
||
expected, missing_links, reversed_links = [], [], []
|
||
for r in _rows(folder / "links.csv"):
|
||
src = new_keys.get(_clean(r.get("Откуда")) or "", _clean(r.get("Откуда")))
|
||
dst = new_keys.get(_clean(r.get("Куда")) or "", _clean(r.get("Куда")))
|
||
kind = (_clean(r.get("Тип")) or "").lower()
|
||
if not src or not dst or src.startswith("new:") or dst.startswith("new:"):
|
||
continue
|
||
link = {"source": src, "target": dst, "link_type": link_types.get(kind, kind)}
|
||
expected.append(link)
|
||
issue = mirror.get_issue(conn, src)
|
||
if issue and any(l["target_key"] == dst and l["link_type"] == link["link_type"] for l in issue["links_out"]):
|
||
continue
|
||
if issue and any(l["source_key"] == dst and l["link_type"] == link["link_type"] for l in issue["links_in"]):
|
||
reversed_links.append(link) # связь есть, но в Jira стоит в обратную сторону (O-3)
|
||
else:
|
||
missing_links.append(link)
|
||
|
||
known = set(mirror.known_keys(conn))
|
||
missing_keys = sorted({k for k in csv_keys if k not in known})
|
||
return {"notes": notes, "expected_links": len(expected), "missing_keys": missing_keys, "missing_links": missing_links,
|
||
"reversed_links": reversed_links}
|