добавил объединение повторных активностей Clockify

This commit is contained in:
2026-07-14 15:23:02 +03:00
parent 9352a84723
commit 5dca3ae446
4 changed files with 181 additions and 14 deletions

View File

@@ -4,11 +4,48 @@ import logging
from colorama import Fore
from classes import Activity, AggregatedActivity
from utils import format_date, format_hours
logger = logging.getLogger(__name__)
def aggregate_activities(activities: list[Activity]) -> list[AggregatedActivity]:
"""Объединяет одинаковые активности и суммирует их трудозатраты."""
grouped_activities = {}
for activity in activities:
key = (
activity.author_id,
activity.task_id,
activity.tag_id,
activity.project_id,
activity.vetro_project_id,
activity.date_start.date(),
)
grouped_activities.setdefault(key, []).append(activity)
result = []
for grouped in grouped_activities.values():
descriptions = list(
dict.fromkeys(
description.rstrip(".")
for activity in grouped
if (description := activity.description.strip())
)
)
result.append(
AggregatedActivity(
**{
**grouped[0].__dict__,
"description": ". ".join(descriptions),
"time_spent": sum(activity.time_spent for activity in grouped),
},
source_activities=tuple(grouped),
)
)
return result
class TrackingService:
"""Координирует загрузку, сохранение и перенос активностей в Redmine."""
@@ -35,13 +72,20 @@ class TrackingService:
if not activities:
logger.info(Fore.YELLOW + "Нет задач для занесения трудочасов" + Fore.RESET)
return []
activities_to_track = aggregate_activities(
[
activity
for activity in activities
if not activity.is_tracked and activity.date_end
]
)
if self.is_dry_run:
self._log_preview(activities)
self._log_preview(activities, activities_to_track)
return activities
self.db.insert_activities(activities)
self.red.track_activities(
activities,
activities_to_track,
on_success=None if self.is_debug else self._save_tracked_activity,
)
return activities
@@ -66,22 +110,20 @@ class TrackingService:
def _save_tracked_activity(self, activity):
"""Сохраняет активность как успешно перенесенную в Redmine."""
self.db.insert_activities([activity])
self.db.insert_time_entries([activity])
source_activities = list(activity.source_activities or (activity,))
for source_activity in source_activities:
source_activity.is_tracked = True
self.db.insert_activities(source_activities)
self.db.insert_time_entries(source_activities)
def _log_preview(self, activities):
def _log_preview(self, activities, activities_to_track):
"""Выводит план переноса активностей без сохранения и отправки."""
total_hours = sum(activity.time_spent for activity in activities if activity.date_end)
trackable_activities = [
activity
for activity in activities
if not activity.is_tracked and activity.date_end
]
total_hours = sum(activity.time_spent for activity in activities_to_track)
logger.info(f"{Fore.YELLOW}Предпросмотр переноса трудочасов{Fore.RESET}")
logger.info(f"Найдено активностей: {len(activities)}")
logger.info(f"Готово к переносу: {len(trackable_activities)}")
logger.info(f"Готово к переносу: {len(activities_to_track)}")
logger.info(f"Всего часов: {format_hours(total_hours)}")
for activity in trackable_activities:
for activity in activities_to_track:
logger.info(
f"{activity.date_start.date()} | #{activity.task_id} | "
f"{format_hours(activity.time_spent)} | {activity.description}"