from __future__ import annotations import asyncio import logging from contextlib import suppress from datetime import datetime, time from zoneinfo import ZoneInfo, ZoneInfoNotFoundError from aiogram import Bot from aiogram.exceptions import TelegramForbiddenError from app.database import Database from app.keyboards import main_keyboard from app.messages import daily_report_text from app.periods import day_bounds, month_bounds logger = logging.getLogger(__name__) async def daily_report_loop(bot: Bot, database: Database) -> None: """Check report schedules periodically; the database makes delivery idempotent.""" while True: try: await _send_due_reports(bot, database) except asyncio.CancelledError: raise except Exception: logger.exception("Ошибка цикла ежедневных отчётов") await asyncio.sleep(30) async def _send_due_reports(bot: Bot, database: Database) -> None: users = await database.users_for_reports() for user in users: try: timezone = ZoneInfo(user["timezone"]) report_at = time.fromisoformat(user["report_time"]) except (ZoneInfoNotFoundError, ValueError): logger.error("Некорректные настройки времени у пользователя %s", user["user_id"]) continue now = datetime.now(timezone) if now.time().replace(tzinfo=None) < report_at: continue local_date = now.date().isoformat() day_start, day_end = day_bounds(now) month_start, month_end = month_bounds(now) day = await database.totals(user["user_id"], day_start, day_end) month = await database.totals(user["user_id"], month_start, month_end) debt = await database.family_debt(user["user_id"]) report_text = daily_report_text(day, month, debt) if not await database.report_was_sent(user["user_id"], local_date): try: await bot.send_message( chat_id=user["chat_id"], text=report_text, reply_markup=main_keyboard(), ) except TelegramForbiddenError: logger.info("Пользователь %s заблокировал бота", user["user_id"]) await database.mark_report_sent(user["user_id"], local_date, now) family_target = await database.family_report_target(user["user_id"]) if family_target is None: continue household_id, family_chat_id = family_target if await database.family_report_was_sent(household_id, local_date): continue try: await bot.send_message(chat_id=family_chat_id, text=report_text) except TelegramForbiddenError: logger.info("Бот не может писать в семейный чат %s", family_chat_id) await database.mark_family_report_sent(household_id, local_date, now) async def stop_task(task: asyncio.Task) -> None: task.cancel() with suppress(asyncio.CancelledError): await task