добавил предпросмотр и защиту от повторной отправки трудочасов

This commit is contained in:
2026-07-06 16:28:00 +03:00
parent 0778cc23ae
commit ba9eafbb46
6 changed files with 87 additions and 5 deletions

View File

@@ -1,4 +1,10 @@
"""Константы API-клиентов."""
from enums import ActivityTypes
REQUEST_TIMEOUT = 30
REDMINE_ISSUES_LIMIT = 100
REDMINE_TIME_ENTRIES_LIMIT = 100
CLOCKIFY_ACTIVITY_COMMENT_PREFIX = "Clockify activity ID:"
ACTIVITY_TYPE_BY_VETRO_PROJECT = {}
DEFAULT_ACTIVITY_TYPE = ActivityTypes.DEVELOPMENT

View File

@@ -6,9 +6,15 @@ import requests
from classes.classes import Task
from config import RedmineConfig, config
from classes import Activity, ActivityType
from enums import ActivityTypes
from exceptions import InvalidToken
from .constants import REDMINE_ISSUES_LIMIT, REQUEST_TIMEOUT
from .constants import (
ACTIVITY_TYPE_BY_VETRO_PROJECT,
CLOCKIFY_ACTIVITY_COMMENT_PREFIX,
DEFAULT_ACTIVITY_TYPE,
REDMINE_ISSUES_LIMIT,
REDMINE_TIME_ENTRIES_LIMIT,
REQUEST_TIMEOUT,
)
logger = logging.getLogger(__name__)
@@ -37,6 +43,13 @@ class RedmineAPI(RedmineConfig):
]
for activity in activities_to_track:
try:
if not config.is_debug and self.has_time_entry_for_activity(activity):
activity.is_tracked = True
if on_success:
on_success(activity)
tracked.append(activity)
logger.info(f"{Fore.GREEN}{activity}: уже есть в Redmine {Fore.RESET}".ljust(100) + "")
continue
if not config.is_debug:
resp = requests.post(
self.base_url + self.time_entries_url,
@@ -45,8 +58,8 @@ class RedmineAPI(RedmineConfig):
"time_entry": {
"issue_id": activity.task_id,
"hours": activity.time_spent / (2 if is_beginner else 1),
"activity_id": ActivityTypes.DEVELOPMENT,
"comments": activity.description,
"activity_id": self.get_activity_type_id(activity),
"comments": self.get_comment(activity),
"spent_on": activity.date_start.strftime(self.datetime_format)
}
},
@@ -79,6 +92,38 @@ class RedmineAPI(RedmineConfig):
for activity in failed_activities:
logger.info(f"{Fore.RED}{activity}{Fore.RESET}".ljust(60) + "")
return tracked
def get_comment(self, activity: Activity) -> str:
"""Формирует комментарий Redmine с внешним id активности Clockify."""
return f"{activity.description}\n{CLOCKIFY_ACTIVITY_COMMENT_PREFIX} {activity.id}"
def get_activity_type_id(self, activity: Activity) -> int:
"""Возвращает тип деятельности Redmine для активности Clockify."""
return ACTIVITY_TYPE_BY_VETRO_PROJECT.get(
activity.vetro_project_id,
DEFAULT_ACTIVITY_TYPE,
)
def has_time_entry_for_activity(self, activity: Activity) -> bool:
"""Проверяет, была ли активность Clockify уже перенесена в Redmine."""
resp = requests.get(
self.base_url + self.time_entries_url,
headers={"X-Redmine-Api-Key": self.token},
params={
"issue_id": activity.task_id,
"spent_on": activity.date_start.strftime(self.datetime_format),
"limit": REDMINE_TIME_ENTRIES_LIMIT,
},
timeout=REQUEST_TIMEOUT,
)
if not resp.ok:
logger.error(f"Ошибка RedmineAPI: {resp.text} {resp.status_code}")
return False
marker = f"{CLOCKIFY_ACTIVITY_COMMENT_PREFIX} {activity.id}"
return any(
marker in time_entry.get("comments", "")
for time_entry in resp.json().get("time_entries", [])
)
def get_time_entry_activities(self) -> list[ActivityType]:
"""Получает виды Деятельности с сервера Redmine"""