feat(activity): add --by-activity flag to break down spent time by activity type

- Load time entry activity names from Redmine enumeration.
- Aggregate hours per issue and per activity in fetch_issues_with_spent_time.
- Add --by-activity CLI flag and propagate it through report builder,
  summary, and all formatters (console, CSV, HTML, JSON, ODT).
- Keep backward-compatible 2-tuple input in build_grouped_report.
- Bump version to 1.8.0.

Closes #41
This commit is contained in:
Кокос Артем Николаевич
2026-06-29 16:44:52 +07:00
parent f6861382e6
commit 59af7ce464
12 changed files with 142 additions and 32 deletions

View File

@@ -88,6 +88,11 @@ def main(argv: Optional[List[str]] = None) -> int:
"--user-name",
help="Redmine user full name for the report (alternative to --user-id; ambiguous names are rejected)",
)
parser.add_argument(
"--by-activity",
action="store_true",
help="Break down spent time by activity type",
)
args = parser.parse_args(argv)
# Валидация взаимоисключающих флагов пользователя
@@ -130,6 +135,7 @@ def main(argv: Optional[List[str]] = None) -> int:
from_date,
to_date,
user_id=args.user_id or args.user_login or args.user_name,
by_activity=args.by_activity,
)
except RedmineAPIError as e:
print(f"{e.message}", file=sys.stderr)
@@ -150,15 +156,25 @@ def main(argv: Optional[List[str]] = None) -> int:
print(f"✅ Total issues: {len(issue_hours)} [{args.date}]", file=sys.stderr)
rows = build_grouped_report(issue_hours, fill_time=not args.no_time)
rows = build_grouped_report(
issue_hours,
fill_time=not args.no_time,
by_activity=args.by_activity,
)
if args.summary:
summary = calculate_summary(rows)
summary = calculate_summary(rows, by_activity=args.by_activity)
print(f"⏱️ Total time: {summary['total']}h", file=sys.stderr)
for key, value in summary.items():
project_keys = [k for k in sorted(summary) if k.startswith("project:")]
activity_keys = [k for k in sorted(summary) if k.startswith("activity:")]
for key in project_keys + activity_keys:
value = summary[key]
if key.startswith("project:"):
project = key.split(":", 1)[1]
print(f" {project}: {value}h", file=sys.stderr)
elif key.startswith("activity:"):
activity = key.split(":", 1)[1]
print(f" [{activity}]: {value}h", file=sys.stderr)
if args.output:
output_ext = os.path.splitext(args.output)[1].lower()