142 lines
5.2 KiB
Python
142 lines
5.2 KiB
Python
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
|