sdlc-hub/src/jira_hub/importer.py
2026-10-13 22:48:57 +04:00

93 lines
4.2 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.

"""Разовый импорт 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}