Files
tgex-backend/src/usecase/placement/update_post_status_cycle.py
2026-02-28 13:06:30 +03:00

142 lines
5.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.
import datetime
import logging
from typing import TYPE_CHECKING
from tortoise import timezone
from src import domain
from src.domain.placement_post import PlacementPostStatus
if TYPE_CHECKING:
from .. import Usecase
log = logging.getLogger(__name__)
# Статусы, которые не должны обновляться автоматически (финальные или ручные)
FINAL_STATUSES = {
PlacementPostStatus.COMPLETED_DELETED,
PlacementPostStatus.COMPLETED_NOT_DELETED,
PlacementPostStatus.CHECK_COMPLETED,
}
# Статусы, при которых нужно проверять условия для автоматического обновления
AUTO_UPDATE_STATUSES = {
PlacementPostStatus.NO_STATUS,
PlacementPostStatus.SEND_POST,
PlacementPostStatus.POST_APPROVAL,
PlacementPostStatus.WAITING_SCHEDULE,
PlacementPostStatus.SCHEDULED,
PlacementPostStatus.POST_PUBLISHED,
PlacementPostStatus.CHECK_DELETED_EARLY,
PlacementPostStatus.CHECK_NOT_PUBLISHED,
}
async def update_post_status_cycle(self: 'Usecase', interval_seconds: int) -> None:
"""Автоматически обновляет статусы PlacementPost на основе состояния постов"""
log.debug('Starting update_post_status_cycle')
# Получаем все PlacementPost со статусами, которые могут быть обновлены
placement_posts = (
await domain.PlacementPost.filter(
status__in=list(AUTO_UPDATE_STATUSES),
)
.prefetch_related('placement', 'post', 'post__channel')
.all()
)
if not placement_posts:
log.debug('No placement_posts to update')
return
updated_count = 0
now = timezone.now()
for placement_post in placement_posts:
placement = placement_post.placement
post = placement_post.post
if not placement:
log.warning('PlacementPost %s missing placement', placement_post.id)
continue
new_status = _determine_status(placement, placement_post, post, now)
if new_status and new_status != placement_post.status:
old_status = placement_post.status
placement_post.status = new_status
await placement_post.save()
updated_count += 1
log.info(
'Updated PlacementPost %s status: %s -> %s',
placement_post.id,
old_status,
new_status,
)
log.debug('Update post status cycle completed. Updated %s placement_posts', updated_count)
def _determine_status(
placement: domain.Placement,
placement_post: domain.PlacementPost,
post: domain.Post | None,
now: datetime.datetime,
) -> PlacementPostStatus | None:
"""Определяет новый статус PlacementPost на основе текущего состояния"""
# Если поста нет и прошло время размещения - "Пост не вышел"
if post is None:
if placement.placement_at and placement.placement_at < now:
return PlacementPostStatus.CHECK_NOT_PUBLISHED
return None
# Если пост есть, но не опубликован - оставляем как есть
if not post.published_at:
return None
# Пост опубликован - проверяем условия для перехода статусов
published_at = post.published_at
deleted_at = post.deleted_from_channel_at
# Вычисляем время в топе
if deleted_at:
time_on_top = int((deleted_at - published_at).total_seconds())
else:
time_on_top = int((now - published_at).total_seconds())
# Получаем требуемую длительность из формата
required_duration = domain.get_feed_duration_seconds(placement)
# Если формат "без удаления" - размещение отработало, если прошло 24 часа
if required_duration is None:
# По умолчанию считаем что "без удаления" = 24 часа минимум
required_duration = 24 * 3600
# Проверяем условия для автоматических статусов
# Пост удалён раньше срока
if deleted_at and time_on_top < required_duration:
return PlacementPostStatus.CHECK_DELETED_EARLY
# Размещение отработало - пост удалён
if deleted_at and time_on_top >= required_duration:
return PlacementPostStatus.COMPLETED_DELETED
# Размещение отработало - пост не удалён
if not deleted_at and time_on_top >= required_duration:
return PlacementPostStatus.COMPLETED_NOT_DELETED
# Пост вышел, но ещё не отработал
if placement_post.status in {
PlacementPostStatus.NO_STATUS,
PlacementPostStatus.SEND_POST,
PlacementPostStatus.POST_APPROVAL,
PlacementPostStatus.WAITING_SCHEDULE,
PlacementPostStatus.SCHEDULED,
}:
return PlacementPostStatus.POST_PUBLISHED
return None