update logic, add time zones, add new db fields, send payment link

This commit is contained in:
o.vodianov 2025-08-24 23:27:02 +04:00
parent 547cac174d
commit dfb3de958b
7 changed files with 113 additions and 10 deletions

View File

@ -2,6 +2,7 @@ import uuid
import hashlib
import base64
import re
import pytz
from datetime import datetime
from urllib.parse import urlparse
@ -11,7 +12,7 @@ def UUID_code_generator(code_length: int) -> str:
Генерирует уникальный код в формате UUID.
Возвращает строку с кодом.
"""
return str(uuid.uuid4().hex[:{code_length}].upper())
return str(uuid.uuid4().hex[:code_length].upper())
def generate_session_code(telegram_id: int, phone: str, consultation_date_time: str) -> str:
@ -40,6 +41,22 @@ def is_phone_correct(phone: str) -> bool:
return re.match(regex, normalized)
def get_timezones():
return_value = {}
for tz in pytz.common_timezones:
c = tz.split("/")
if len(c) > 1:
if c[0] not in return_value.keys():
return_value[c[0]] = []
return_value[c[0]].append(c[1])
for i in ["GMT"]:
if i in return_value.keys():
return_value.pop(i)
return return_value
def is_valid_url(text: str) -> bool:
try:
result = urlparse(text)

View File

@ -83,6 +83,8 @@ class Doctors(Base):
__tablename__ = "doctors"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
code: Mapped[str] = mapped_column(unique=True, nullable=True)
time_zone: Mapped[Optional[str]] = mapped_column(nullable=True)
telegram_id: Mapped[int] = mapped_column(unique=True, nullable=False)
name: Mapped[str] = mapped_column(nullable=False)
available_formats: Mapped[Optional[List[str]]

View File

@ -5,6 +5,7 @@ from telegram.ext import (
CommandHandler,
)
from core.logging import logger
from docbot.services.doctors_service import get_doctors_names
from docbot.services.admins_service import get_admin_info
@ -22,9 +23,14 @@ async def get_doctors(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int
return ConversationHandler.END
doctors = await get_doctors_names()
doctors_table = ""
for row in doctors:
logger.info(f"{row.Doctors.name} {row.VerificationRequests.code}")
doctors_table += f"\n- {row.Doctors.name} (код врача: {row.Doctors.code}) (код верификации: {row.VerificationRequests.code})"
if doctors:
await update.message.reply_text(
f"📝 Список активных врачей. \n {doctors}",
f"📝 Список активных врачей. \n {doctors_table}",
parse_mode="Markdown"
)
return ConversationHandler.END

View File

@ -1,4 +1,4 @@
from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove
from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove, InlineKeyboardButton
from telegram.ext import (
ContextTypes,
ConversationHandler,
@ -11,7 +11,8 @@ from docbot.handlers.utils.cancel_handler import get_cancel_handler
from docbot.services.referral_service import validate_referral_code
from docbot.services.doctors_service import create_doctor
from core.enums.dialog_helpers import ConfirmationMessage, Acknowledgement
from core.utils import UUID_code_generator
from core.utils import UUID_code_generator, get_timezones
from core.logging import logger
ASK_REFERRAL_CODE = 1
SEND_ACKNOWLEDGEMENT_INFO = 2
@ -19,6 +20,8 @@ ASK_NAME = 3
ASK_SPECIALITY = 4
SEND_DIPLOMA_ACK_INFO = 5
SEND_CONSULTATION_TYPE_ACK_INFO = 6
ASK_TIMEZONE = 7
ASK_CONTINENT = 8
SEND_ME_REFERRAL_CODE_TEXT = (
"✌️ Пожалуйста, пришлите реферальный код, чтобы начать процесс регистрации."
@ -34,6 +37,9 @@ SEND_ME_YOUR_SPECIALITY_TEXT = (
"📝 Пожалуйста, введите вашу специальность/специальности, в соответствии с которой планируете проводить консультации.\n"
"Например: терапевт, кардиолог, невролог и т.д."
)
SELECT_YOUR_TIMEZONE_TEXT = (
"📝 Пожалуйста, выбирете временную зону, в соответствии с которой планируете проводить консультации.\n"
)
WAIT_FOR_ACTIVATION_TEXT = (
"📝 Заявка принята, направьте диплом и аккредитацию с указанием кода верификации {0} в теме письма на адрес электронной почты: docbot@docbot.ru\n"
"В этом шаге нужно встроить подсказку: в каком формате направлять диплом, как скачать с госуслуг\n"
@ -101,6 +107,43 @@ async def receive_doctor_name(update: Update, context: ContextTypes.DEFAULT_TYPE
doctor_name = update.message.text.strip()
context.user_data["doctor_name"] = doctor_name
keyboard = []
for continent in sorted(get_timezones().keys()):
keyboard.append([InlineKeyboardButton(continent)])
reply_markup = ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)
await update.message.reply_text(
SELECT_YOUR_TIMEZONE_TEXT,
parse_mode="Markdown",
reply_markup=reply_markup,
)
return ASK_CONTINENT
async def receive_continent(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
keyboard = []
context.user_data["selected_continent"] = selected_continent = update.message.text
for continent in sorted(get_timezones()[selected_continent]):
keyboard.append([InlineKeyboardButton(continent)])
reply_markup = ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)
await update.message.reply_text(
SELECT_YOUR_TIMEZONE_TEXT,
parse_mode="Markdown",
reply_markup=reply_markup,
)
return ASK_TIMEZONE
async def receive_time_zone(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
context.user_data["time_zone"] = context.user_data["selected_continent"] + "/" + update.message.text
logger.info(f"User {context.user_data['doctor_telegram_id']} selected timezone {context.user_data['time_zone']}")
await update.message.reply_text(
SEND_ME_YOUR_SPECIALITY_TEXT,
parse_mode="Markdown"
@ -168,11 +211,15 @@ async def receive_doctor_consultation_packages_acknowledgement_status(update: Up
reply_markup=ReplyKeyboardRemove()
)
doc_code = UUID_code_generator(code_length=5)
await create_doctor(
context.user_data["ref_obj"],
context.user_data["doctor_telegram_id"],
context.user_data["doctor_name"],
context.user_data["verification_request_code"]
context.user_data["verification_request_code"],
code=doc_code,
time_zone=context.user_data["time_zone"]
)
return ConversationHandler.END
@ -198,6 +245,14 @@ def get_register_doctor_first_stage_handler() -> ConversationHandler:
MessageHandler(filters.TEXT & ~filters.COMMAND,
receive_doctor_name)
],
ASK_TIMEZONE: [
MessageHandler(filters.TEXT & ~filters.COMMAND,
receive_time_zone)
],
ASK_CONTINENT: [
MessageHandler(filters.TEXT & ~filters.COMMAND,
receive_continent)
],
ASK_SPECIALITY: [
MessageHandler(filters.TEXT & ~filters.COMMAND,
receive_doctor_speciality)

View File

@ -17,6 +17,7 @@ from docbot.services.patients_service import (
create_patient, update_patient_phone, get_patient_by_telegram_id,
is_user_has_phone
)
from docbot.services.doctors_service import get_doctors_payment_link
from docbot.services.session_service import create_session
from core.logging import logger
@ -283,7 +284,8 @@ async def pay_consultation(update: Update, context: ContextTypes.DEFAULT_TYPE) -
patient=patient
)
link = f"https://example.com/consultation/{user_id}"
link = await get_doctors_payment_link(context.user_data['doctor_number'])
await update.callback_query.answer()
await update.callback_query.message.reply_text(
f"Чтобы оплатить консультацию перейдите по ссылке {link}.",

View File

@ -24,6 +24,7 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
"/start Запустить бота\n"
"/genref Сгенерировать новый реферальный код для врача\n"
"/doctors Посмотреть список зарегистрированных врачей\n"
"/verify Верифицировать врача по реферальному коду\n"
"/help Показать это меню\n"
)
# (При необходимости в будущем можно добавить другие admin-команды)

View File

@ -24,10 +24,28 @@ async def get_doctor(telegram_id: int) -> Doctors | None:
async def get_doctors_names() -> Doctors | None:
async with AsyncSessionLocal() as session:
result = await session.execute(
select(Doctors.name)
select(Doctors, VerificationRequests)
.join(VerificationRequests, Doctors.id == VerificationRequests.doctor_id)
.where(Doctors.is_active)
)
return result.all()
return result.fetchall()
async def get_doctors_payment_link(doctor_code: str) -> str | None:
async with AsyncSessionLocal() as session:
result = await session.execute(
select(PaymentMethod.method)
.join(Doctors, PaymentMethod.doctor_id == Doctors.id)
.where(
Doctors.code == doctor_code,
PaymentMethod.is_active.is_(True),
PaymentMethod.is_primary.is_(True)
)
)
payment_link = result.scalar_one_or_none()
if payment_link:
return payment_link
return None
async def is_there_primary_payment_method() -> PaymentMethod | None:
@ -67,7 +85,7 @@ async def add_doctor(telegram_id: int, name: str, available_formats: Consultatio
))
async def create_doctor(referral_obj: ReferralCode, telegram_id: int, name: str, verification_code: str) -> Doctors:
async def create_doctor(referral_obj: ReferralCode, telegram_id: int, name: str, verification_code: str, code: str, time_zone: str) -> Doctors:
"""
Помечает referral_obj как использованный, создаёт запись в таблице Doctors
с привязкой к telegram_id и возвращает объект Doctor.
@ -84,7 +102,9 @@ async def create_doctor(referral_obj: ReferralCode, telegram_id: int, name: str,
name=name,
is_active=False,
created_at=datetime.utcnow(),
referral=referral_obj
referral=referral_obj,
code=code,
time_zone=time_zone
)
verification_request = VerificationRequests()