List open Redmine issues assigned to the current (or overridden) user, sorted by priority rank (P1..P5) then version due date with a stable id tie-break. Renders an OSC 8 hyperlinked console table on stdout and a counts/hours summary line on stderr; version due dates are fetched once per unique version, unavailable versions degrade to an empty due date. Also migrates the codebase to ruff 0.17.3 defaults (PEP 585/604 annotations, targeted noqa for intentional blind excepts and CLI root logging). Behavior-neutral: 408 tests pass with -W error.
57 lines
1.4 KiB
Python
57 lines
1.4 KiB
Python
from datetime import datetime
|
|
|
|
|
|
def get_month_name_from_range(from_date: str, to_date: str) -> str:
|
|
"""Определяет название месяца по диапазону дат"""
|
|
|
|
try:
|
|
end = datetime.strptime(to_date, "%Y-%m-%d")
|
|
except ValueError:
|
|
return "Январь"
|
|
|
|
months = [
|
|
"",
|
|
"Январь",
|
|
"Февраль",
|
|
"Март",
|
|
"Апрель",
|
|
"Май",
|
|
"Июнь",
|
|
"Июль",
|
|
"Август",
|
|
"Сентябрь",
|
|
"Октябрь",
|
|
"Ноябрь",
|
|
"Декабрь",
|
|
]
|
|
|
|
return months[end.month]
|
|
|
|
|
|
def get_version(issue, default: str = "<N/A>") -> str:
|
|
"""Возвращает версию задачи или default, если не задана."""
|
|
version = getattr(issue, "fixed_version", None)
|
|
if version is None:
|
|
return default
|
|
name = getattr(version, "name", None)
|
|
return str(name) if name else str(version)
|
|
|
|
|
|
def hours_to_human(hours: float) -> str:
|
|
"""Преобразует часы в человекочитаемый формат: '2ч 30м'."""
|
|
|
|
if hours <= 0:
|
|
return "0ч"
|
|
|
|
total_minutes = round(hours * 60)
|
|
h = total_minutes // 60
|
|
m = total_minutes % 60
|
|
parts = []
|
|
|
|
if h:
|
|
parts.append(f"{h}ч")
|
|
if m:
|
|
parts.append(f"{m}м")
|
|
|
|
return " ".join(parts) if parts else "0ч"
|