""" Напоминания о платежах ЖКХ Только для администраторов """ import logging from datetime import datetime, timedelta from aiogram import Router, F from aiogram.types import Message, CallbackQuery from aiogram.filters import Command, StateFilter from aiogram.fsm.state import State, StatesGroup from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from database.db import AsyncSessionLocal from database.models import PaymentReminder, User from utils.decorators import admin_only from keyboards.inline import get_payment_type_menu import config import json logger = logging.getLogger(__name__) router = Router() # ============================================================================= # FSM для создания напоминания # ============================================================================= class PaymentState(StatesGroup): waiting_for_type = State() waiting_for_title = State() waiting_for_amount = State() waiting_for_due_date = State() waiting_for_description = State() # ============================================================================= # СОЗДАНИЕ НАПОМИНАНИЯ # ============================================================================= @router.message(Command('payment_add')) @admin_only async def cmd_payment_add(message: Message, state: FSMContext): """ Добавить напоминание о платеже Использование: /payment_add """ # Загружаем типы платежей try: with open('data/payment_links.json', 'r', encoding='utf-8') as f: payment_types = json.load(f).get('payment_types', {}) except Exception: payment_types = {} await message.answer( '💳 Добавление напоминания о платеже\n\n' 'Выберите тип платежа:\n\n' '1️⃣ ⚡ Электроэнергия\n' '2️⃣ 💧 Вода\n' '3️⃣ 🔥 Отопление\n' '4️⃣ 🔶 Газ\n' '5️⃣ 🏠 Содержание жилья\n' '6️⃣ 🗑️ Вывоз мусора\n' '7️⃣ 📡 Интернет\n' '8️⃣ 📋 Другое\n\n' 'Или отправьте номер типа (1-8):', reply_markup=get_payment_type_menu(), parse_mode='HTML' ) await state.set_state(PaymentState.waiting_for_type) @router.callback_query(F.data.startswith('payment_type_')) async def callback_payment_type(callback: CallbackQuery, state: FSMContext): """Выбор типа платежа""" type_map = { 'payment_type_electricity': ('electricity', '⚡ Электроэнергия'), 'payment_type_water': ('water', '💧 Вода'), 'payment_type_heating': ('heating', '🔥 Отопление'), 'payment_type_gas': ('gas', '🔶 Газ'), 'payment_type_maintenance': ('maintenance', '🏠 Содержание жилья'), 'payment_type_trash': ('trash', '🗑️ Вывоз мусора'), 'payment_type_internet': ('internet', '📡 Интернет'), 'payment_type_other': ('other', '📋 Другое'), } type_key, type_name = type_map.get(callback.data, ('other', '📋 Другое')) await state.update_data(payment_type=type_key, type_name=type_name) await callback.message.answer( f'✅ Тип: {type_name}\n\n' 'Теперь отправьте название платежа:\n' 'Например: "Квартплата за февраль"', parse_mode='HTML' ) await state.set_state(PaymentState.waiting_for_title) await callback.answer() @router.message(PaymentState.waiting_for_type) async def process_payment_type(message: Message, state: FSMContext): """Выбор типа платежа текстом""" type_map = { '1': ('electricity', '⚡ Электроэнергия'), '2': ('water', '💧 Вода'), '3': ('heating', '🔥 Отопление'), '4': ('gas', '🔶 Газ'), '5': ('maintenance', '🏠 Содержание жилья'), '6': ('trash', '🗑️ Вывоз мусора'), '7': ('internet', '📡 Интернет'), '8': ('other', '📋 Другое'), } text = message.text.strip() if text in type_map: type_key, type_name = type_map[text] await state.update_data(payment_type=type_key, type_name=type_name) await message.answer( f'✅ Тип: {type_name}\n\n' 'Теперь отправьте название платежа:\n' 'Например: "Квартплата за февраль"', parse_mode='HTML' ) await state.set_state(PaymentState.waiting_for_title) else: await message.answer('❌ Неверный тип. Выберите из меню или отправьте номер 1-8:') @router.message(PaymentState.waiting_for_title) async def process_payment_title(message: Message, state: FSMContext): """Получили название платежа""" title = message.text await state.update_data(title=title) await message.answer( f'✅ Название: {title}\n\n' 'Укажите сумму платежа (в рублях) или отправьте "0" если переменная:\n' 'Например: 3500', parse_mode='HTML' ) await state.set_state(PaymentState.waiting_for_amount) @router.message(PaymentState.waiting_for_amount) async def process_payment_amount(message: Message, state: FSMContext): """Получили сумму""" text = message.text.strip() try: amount = int(text) if amount < 0: raise ValueError except ValueError: await message.answer('❌ Неверный формат суммы. Отправьте число:') return await state.update_data(amount=amount if amount > 0 else None) await message.answer( f'✅ Сумма: {amount if amount > 0 else "Переменная"}\n\n' 'Укажите дату оплаты (число месяца):\n' 'Например: 10 (ежемесячно до 10 числа)', parse_mode='HTML' ) await state.set_state(PaymentState.waiting_for_due_date) @router.message(PaymentState.waiting_for_due_date) async def process_payment_due_date(message: Message, state: FSMContext): """Получили дату оплаты""" text = message.text.strip() try: due_date = int(text) if due_date < 1 or due_date > 31: raise ValueError except ValueError: await message.answer('❌ Неверный формат даты. Отправьте число от 1 до 31:') return await state.update_data(due_date=due_date) await message.answer( f'✅ Дата оплаты: {due_date} число каждого месяца\n\n' 'Отправьте описание (необязательно) или нажмите "Пропустить":', parse_mode='HTML' ) await state.set_state(PaymentState.waiting_for_description) @router.message(PaymentState.waiting_for_description) async def process_payment_description(message: Message, state: FSMContext): """Получили описание или пропустили""" if message.text and message.text.lower() != 'пропустить': description = message.text else: description = None await state.update_data(description=description) # Сохраняем напоминание в БД data = await state.get_data() # Создаём дату с текущим месяцем now = datetime.utcnow() due_date = now.replace(day=data['due_date']) async with AsyncSessionLocal() as session: reminder = PaymentReminder( title=data['title'], amount=data.get('amount'), due_date=due_date, description=description, is_recurring=True, created_by=message.from_user.id, ) session.add(reminder) await session.commit() await session.refresh(reminder) # Формируем сообщение о создании type_name = data.get('type_name', '📋 Платёж') amount_text = f'{data["amount"]} ₽' if data.get('amount') else 'Переменная' await message.answer( f'✅ Напоминание о платеже добавлено!\n\n' f'{type_name} — {data["title"]}\n\n' f'💰 Сумма: {amount_text}\n' f'📅 Срок оплаты: {data["due_date"]} число каждого месяца\n' f'🔖 ID: {reminder.reminder_id}\n\n' f'Для просмотра: /payments', parse_mode='HTML' ) await state.clear() # ============================================================================= # ПРОСМОТР НАПОМИНАНИЙ # ============================================================================= @router.message(Command('payments')) @router.message(Command('платежи')) async def cmd_payments_list(message: Message): """Показать все активные напоминания о платежах""" async with AsyncSessionLocal() as session: stmt = ( select(PaymentReminder) .where(PaymentReminder.is_active == True) .order_by(PaymentReminder.due_date) ) result = await session.execute(stmt) reminders = list(result.scalars().all()) if not reminders: await message.answer('✅ Нет активных напоминаний о платежах') return text = '💳 Напоминания о платежах ЖКХ\n\n' current_day = datetime.utcnow().day for i, r in enumerate(reminders, 1): due_day = r.due_date.day if hasattr(r.due_date, 'day') else 10 days_left = (due_day - current_day) % 30 amount_text = f'{r.amount} ₽' if r.amount else 'Переменная' text += f'{i}. {r.title}\n' text += f' └─ Сумма: {amount_text}\n' text += f' └─ Срок: {due_day} число\n' text += f' └─ Осталось дней: {days_left}\n\n' text += '💡 Для добавления: /payment_add' await message.answer(text, parse_mode='HTML') # ============================================================================= # УДАЛЕНИЕ НАПОМИНАНИЯ # ============================================================================= @router.message(Command('payment_del')) @admin_only async def cmd_payment_del(message: Message): """ Удалить напоминание Использование: /payment_del """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/payment_del <ID>', parse_mode='HTML' ) return try: reminder_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID') return async with AsyncSessionLocal() as session: reminder = await session.get(PaymentReminder, reminder_id) if not reminder: await message.answer(f'❌ Напоминание {reminder_id} не найдено') return reminder.is_active = False await session.commit() await message.answer(f'✅ Напоминание "{reminder.title}" удалено') # ============================================================================= # РАССЫЛКА НАПОМИНАНИЙ # ============================================================================= async def send_payment_reminders(bot): """Отправить напоминания о платежах (5-го и 9-го числа)""" current_day = datetime.utcnow().day # Проверяем нужно ли отправлять (5 или 9 число) if current_day not in [5, 9]: return async with AsyncSessionLocal() as session: # Получаем все активные напоминания stmt = select(PaymentReminder).where(PaymentReminder.is_active == True) result = await session.execute(stmt) reminders = list(result.scalars().all()) if not reminders: return # Формируем сообщение text = f'⏰ Напоминание об оплате ЖКХ!\n\n' text += f'Сегодня {current_day} число. Не забудьте оплатить:\n\n' for r in reminders: due_day = r.due_date.day if hasattr(r.due_date, 'day') else 10 days_left = due_day - current_day if days_left < 0: days_left += 30 amount_text = f'{r.amount} ₽' if r.amount else '' emoji = '⚠️' if days_left <= 2 else '✅' text += f'{emoji} {r.title}\n' text += f' └─ Срок: {due_day} число (осталось {days_left} дн.)\n' if amount_text: text += f' └─ Сумма: {amount_text}\n' text += '\n' # Загружаем ссылки на оплату try: with open('data/payment_links.json', 'r', encoding='utf-8') as f: payment_data = json.load(f) links = payment_data.get('payment_links', []) if links: text += '💳 Ссылки на оплату:\n' for link in links[:3]: text += f'• [{link["name"]}]({link["url"]})\n' except Exception: pass text += '\n_Оплатить можно через Госуслуги, Сбербанк или Тинькофф_' # Отправляем в чат в тему "Важные объявления" try: await bot.send_message( chat_id=config.ADMIN_CHAT_ID, message_thread_id=config.get_topic_id('general'), # В общую тему text=text, parse_mode='HTML', disable_web_page_preview=True ) # Обновляем last_reminder_sent for r in reminders: r.last_reminder_sent = datetime.utcnow() await session.commit() logger.info(f'Напоминания о платежах отправлены ({len(reminders)} платежей)') except Exception as e: logger.error(f'Не удалось отправить напоминания о платежах: {e}')