mirror of
https://github.com/olegvodyanov/docbot.git
synced 2025-12-19 23:57:05 +03:00
add verification logic
This commit is contained in:
parent
414729c41f
commit
c97cf5e0d8
@ -9,3 +9,13 @@ class ConfirmationMessage(str, Enum):
|
|||||||
class Acknowledgement(str, Enum):
|
class Acknowledgement(str, Enum):
|
||||||
OK = "Понятно 😊"
|
OK = "Понятно 😊"
|
||||||
DECLINE = "Отмена 😔"
|
DECLINE = "Отмена 😔"
|
||||||
|
|
||||||
|
|
||||||
|
class YesNo(str, Enum):
|
||||||
|
YES = "Да 😊"
|
||||||
|
NO = "Нет 😔"
|
||||||
|
|
||||||
|
|
||||||
|
class Confirmation(str, Enum):
|
||||||
|
APPROVE = "✅ Подтвердить"
|
||||||
|
REJECT = "❌ Отклонить"
|
||||||
|
|||||||
2
src/core/texts.py
Normal file
2
src/core/texts.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
NO_PERMISSIONS_TO_USE_COMMAND = "❌ У вас нет прав для этой команды."
|
||||||
@ -61,10 +61,9 @@ class Doctors(Base):
|
|||||||
back_populates="doctor", cascade="all, delete-orphan")
|
back_populates="doctor", cascade="all, delete-orphan")
|
||||||
verification_requests: Mapped[List["VerificationRequests"]] = relationship(
|
verification_requests: Mapped[List["VerificationRequests"]] = relationship(
|
||||||
back_populates="doctor", cascade="all, delete-orphan")
|
back_populates="doctor", cascade="all, delete-orphan")
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Doctors(telegram_id={self.telegram_id!r})>"
|
return f"<Doctors(telegram_id={self.telegram_id!r}, name={self.name!r}, is_active={self.is_active})>"
|
||||||
|
|
||||||
|
|
||||||
class ReferralCode(Base):
|
class ReferralCode(Base):
|
||||||
@ -93,10 +92,11 @@ class VerificationRequests(Base):
|
|||||||
reviewed_at: Mapped[Optional[datetime]] = mapped_column(nullable=True)
|
reviewed_at: Mapped[Optional[datetime]] = mapped_column(nullable=True)
|
||||||
status: Mapped[str] = mapped_column(default=False, nullable=False)
|
status: Mapped[str] = mapped_column(default=False, nullable=False)
|
||||||
# Связь поправлена — связь с doctor через doctor_id
|
# Связь поправлена — связь с doctor через doctor_id
|
||||||
doctor: Mapped["Doctors"] = relationship(back_populates="verification_requests")
|
doctor: Mapped["Doctors"] = relationship(
|
||||||
|
back_populates="verification_requests")
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<VerificationRequests(code={self.code!r})>"
|
return f"<VerificationRequests(code={self.code!r}, status={self.status!r}, sent_at={self.sent_at!r})>"
|
||||||
|
|
||||||
|
|
||||||
class FormLink(Base):
|
class FormLink(Base):
|
||||||
|
|||||||
166
src/docbot/handlers/admins/verify_handler.py
Normal file
166
src/docbot/handlers/admins/verify_handler.py
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
from telegram import Update
|
||||||
|
from telegram.ext import (
|
||||||
|
ContextTypes, CommandHandler, ConversationHandler, CallbackQueryHandler,
|
||||||
|
MessageHandler, filters
|
||||||
|
)
|
||||||
|
|
||||||
|
from core.logging import logger
|
||||||
|
from core.enums.dialog_helpers import Confirmation
|
||||||
|
from core.texts import NO_PERMISSIONS_TO_USE_COMMAND
|
||||||
|
from docbot.services.admins_service import (
|
||||||
|
get_admin_info, get_doctors_waiting_authorization, approve_doctor,
|
||||||
|
reject_doctor, get_payment_link
|
||||||
|
)
|
||||||
|
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
|
|
||||||
|
|
||||||
|
SELECT_DOCTOR, VERIFY_DOCTOR, SEND_PAYMENT_LINK = range(3)
|
||||||
|
NO_DOCTORS = "☹️ Нет врачей, ожидающих авторизации."
|
||||||
|
SELECT_DOCTOR_TO_VERIFY = "👀 Выберите врача для авторизации:"
|
||||||
|
CHOOSE_DOCTORS_DESTINY = "🙄 Врач с ID {doctor_telegram_id}. Что сделать?"
|
||||||
|
VERIFIED = "Врач с ID {doctor_telegram_id} верифицирован ✅"
|
||||||
|
REJECTED = "Верификация врача с ID {doctor_telegram_id} отклонена ❌"
|
||||||
|
PAYMENT_LINK = "Ссылка на оплату для врача с ID {doctor_telegram_id}: {payment_link}"
|
||||||
|
|
||||||
|
|
||||||
|
async def verify(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
"""
|
||||||
|
Команда /verify — Проверка верификации врача.
|
||||||
|
Доступна только администратору чата.
|
||||||
|
"""
|
||||||
|
user_id = update.effective_user.id
|
||||||
|
if not await get_admin_info(user_id):
|
||||||
|
logger.warning(
|
||||||
|
f"Пользователь {user_id} пытался проверить верификацию без прав.")
|
||||||
|
await update.message.reply_text(NO_PERMISSIONS_TO_USE_COMMAND)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Здесь должна быть логика проверки верификации врача
|
||||||
|
# Например, можно запросить информацию о врачах и их статусе верификации
|
||||||
|
logger.info(f"Admin {user_id} запрашивает список врачей для верификации.")
|
||||||
|
doctors = await get_doctors_waiting_authorization()
|
||||||
|
if not doctors:
|
||||||
|
await update.message.reply_text(NO_DOCTORS)
|
||||||
|
return
|
||||||
|
|
||||||
|
keyboard = [
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=doctor.Doctors.name + " " + doctor.VerificationRequests.code,
|
||||||
|
callback_data=f"verify_{doctor.Doctors.telegram_id}"
|
||||||
|
)
|
||||||
|
] for doctor in doctors
|
||||||
|
]
|
||||||
|
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
|
await update.message.reply_text(
|
||||||
|
SELECT_DOCTOR_TO_VERIFY,
|
||||||
|
reply_markup=reply_markup
|
||||||
|
)
|
||||||
|
|
||||||
|
return SELECT_DOCTOR
|
||||||
|
|
||||||
|
|
||||||
|
async def doctor_selected(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
# Получаем ID врача из callback_data
|
||||||
|
doctor_telegram_id = int(query.data.split("_")[1])
|
||||||
|
# (Можно получить объект врача по doctor_id из базы, если надо)
|
||||||
|
|
||||||
|
# Кнопки "Верифицировать" и "Отклонить"
|
||||||
|
keyboard = [
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
Confirmation.APPROVE.value,
|
||||||
|
callback_data=f"approve_{doctor_telegram_id}"
|
||||||
|
),
|
||||||
|
InlineKeyboardButton(
|
||||||
|
Confirmation.REJECT.value,
|
||||||
|
callback_data=f"reject_{doctor_telegram_id}"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
await query.edit_message_text(
|
||||||
|
CHOOSE_DOCTORS_DESTINY.format(doctor_telegram_id=doctor_telegram_id),
|
||||||
|
reply_markup=reply_markup,
|
||||||
|
)
|
||||||
|
|
||||||
|
return VERIFY_DOCTOR
|
||||||
|
|
||||||
|
|
||||||
|
async def doctor_action(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
data = query.data.split("_")
|
||||||
|
action = data[0] # approve или reject
|
||||||
|
doctor_telegram_id = int(data[1])
|
||||||
|
context.user_data['doctor_telegram_id'] = doctor_telegram_id
|
||||||
|
|
||||||
|
if action == "approve":
|
||||||
|
# Здесь твоя логика верификации врача (например, запись в базу)
|
||||||
|
approve_doctor(doctor_telegram_id)
|
||||||
|
logger.info(f"Врач с ID {doctor_telegram_id} верифицирован.")
|
||||||
|
# Отправляем сообщение об успешной верификации
|
||||||
|
await query.edit_message_text(
|
||||||
|
VERIFIED.format(doctor_telegram_id=doctor_telegram_id)
|
||||||
|
)
|
||||||
|
elif action == "reject":
|
||||||
|
# Логика отклонения
|
||||||
|
reject_doctor(doctor_telegram_id)
|
||||||
|
logger.info(f"Врач с ID {doctor_telegram_id} отклонен.")
|
||||||
|
# Отправляем сообщение об отклонении
|
||||||
|
await query.edit_message_text(
|
||||||
|
REJECTED.format(doctor_telegram_id=doctor_telegram_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
return SEND_PAYMENT_LINK
|
||||||
|
|
||||||
|
|
||||||
|
def send_payment_link(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""
|
||||||
|
Отправляет ссылку на оплату после верификации врача.
|
||||||
|
"""
|
||||||
|
doctor_telegram_id = context.user_data.get('doctor_telegram_id')
|
||||||
|
if not doctor_telegram_id:
|
||||||
|
logger.error("Не удалось получить ID врача из user_data.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Здесь должна быть логика получения ссылки на оплату
|
||||||
|
payment_link = get_payment_link(doctor_telegram_id)
|
||||||
|
|
||||||
|
# Отправляем ссылку на оплату
|
||||||
|
update.message.reply_text(
|
||||||
|
PAYMENT_LINK.format(
|
||||||
|
doctor_telegram_id=doctor_telegram_id,
|
||||||
|
payment_link=payment_link
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
|
||||||
|
def get_verify_handler():
|
||||||
|
"""
|
||||||
|
Возвращает список готовых CommandHandler-ов для регистрации в Application.
|
||||||
|
"""
|
||||||
|
return ConversationHandler(
|
||||||
|
entry_points=[CommandHandler("verify", verify)],
|
||||||
|
states={
|
||||||
|
SELECT_DOCTOR: [
|
||||||
|
CallbackQueryHandler(doctor_selected, pattern=r"^verify_\d+$"),
|
||||||
|
],
|
||||||
|
VERIFY_DOCTOR: [
|
||||||
|
CallbackQueryHandler(
|
||||||
|
doctor_action, pattern=r"^(approve|reject)_\d+$"),
|
||||||
|
],
|
||||||
|
SEND_PAYMENT_LINK: [
|
||||||
|
MessageHandler(filters.TEXT & ~filters.COMMAND,
|
||||||
|
send_payment_link),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fallbacks=[],
|
||||||
|
)
|
||||||
@ -9,6 +9,7 @@ from docbot.handlers.doctors.register_handler import get_register_doctor_first_s
|
|||||||
from docbot.handlers.admins.generate_ref import get_referral_handlers
|
from docbot.handlers.admins.generate_ref import get_referral_handlers
|
||||||
from docbot.handlers.utils.help import get_help_handler
|
from docbot.handlers.utils.help import get_help_handler
|
||||||
from docbot.handlers.utils.unknown import get_unknown_handler
|
from docbot.handlers.utils.unknown import get_unknown_handler
|
||||||
|
from docbot.handlers.admins.verify_handler import get_verify_handler
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -27,6 +28,7 @@ def main():
|
|||||||
app.add_handler(get_doctors_handler())
|
app.add_handler(get_doctors_handler())
|
||||||
app.add_handler(get_register_doctor_first_stage_handler())
|
app.add_handler(get_register_doctor_first_stage_handler())
|
||||||
app.add_handler(get_referral_handlers())
|
app.add_handler(get_referral_handlers())
|
||||||
|
app.add_handler(get_verify_handler())
|
||||||
app.add_handler(get_help_handler())
|
app.add_handler(get_help_handler())
|
||||||
app.add_handler(get_unknown_handler())
|
app.add_handler(get_unknown_handler())
|
||||||
logger.debug("Все хэндлеры зарегистрированы, запускаем polling")
|
logger.debug("Все хэндлеры зарегистрированы, запускаем polling")
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select, Sequence, Row, Tuple
|
||||||
|
|
||||||
from db.session import AsyncSessionLocal
|
from db.session import AsyncSessionLocal
|
||||||
from db.models import Admins
|
from db.models import Admins, Doctors, VerificationRequests
|
||||||
|
|
||||||
|
|
||||||
async def get_admin_info(telegram_id: int) -> Admins | None:
|
async def get_admin_info(telegram_id: int) -> Admins | None:
|
||||||
@ -21,3 +21,59 @@ async def mark_doctor_inactive(telegram_id: int) -> Admins | None:
|
|||||||
.where(Admins.telegram_id == telegram_id)
|
.where(Admins.telegram_id == telegram_id)
|
||||||
)
|
)
|
||||||
return result.scalar_one_or_none()
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_doctors_waiting_authorization() -> Sequence[Row[Tuple[VerificationRequests, Doctors]]] | None:
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
result = await session.execute(
|
||||||
|
select(VerificationRequests, Doctors)
|
||||||
|
.join(Doctors, VerificationRequests.doctor_id == Doctors.id)
|
||||||
|
.where(Doctors.is_verified.is_(False), Doctors.is_active.is_(False))
|
||||||
|
.order_by(VerificationRequests.sent_at.desc())
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.columns(VerificationRequests, Doctors).all()
|
||||||
|
|
||||||
|
|
||||||
|
async def approve_doctor(doctor_id: int) -> bool:
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
async with session.begin():
|
||||||
|
result = await session.execute(
|
||||||
|
select(Doctors).where(Doctors.telegram_id == doctor_id)
|
||||||
|
)
|
||||||
|
doctor = result.scalar_one_or_none()
|
||||||
|
if not doctor:
|
||||||
|
return False
|
||||||
|
|
||||||
|
doctor.is_verified = True
|
||||||
|
doctor.is_active = True
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def reject_doctor(doctor_id: int) -> bool:
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
async with session.begin():
|
||||||
|
result = await session.execute(
|
||||||
|
select(Doctors).where(Doctors.telegram_id == doctor_id)
|
||||||
|
)
|
||||||
|
doctor = result.scalar_one_or_none()
|
||||||
|
if not doctor:
|
||||||
|
return False
|
||||||
|
|
||||||
|
doctor.is_verified = False
|
||||||
|
doctor.is_active = False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def get_payment_methods(admin_telegram_id: int) -> list[str] | None:
|
||||||
|
"""
|
||||||
|
Получает список доступных методов оплаты для врача.
|
||||||
|
Возвращает список строк или None, если нет методов.
|
||||||
|
"""
|
||||||
|
async with AsyncSessionLocal() as session:
|
||||||
|
result = await session.execute(
|
||||||
|
select(Admins.available_payment_methods)
|
||||||
|
.where(Admins.telegram_id == admin_telegram_id)
|
||||||
|
)
|
||||||
|
payment_methods = result.scalar_one_or_none()
|
||||||
|
return payment_methods if payment_methods else []
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user