Добавлены новые функции для работы с записями о повреждениях в классе OracleDatabase и обновлены обработчики в main.py:

- Реализован метод count_salvage_records для подсчета записей по VIN.
- Добавлен метод fetch_salvage_detailed_info для получения детальной информации о повреждениях.
- Обновлены обработчики для проверки VIN и получения детальной информации о записях, включая интеграцию с платежной системой.
Эти изменения улучшают функциональность бота и позволяют пользователям получать более полную информацию о состоянии автомобилей.
This commit is contained in:
Vlad 2025-06-01 12:14:59 +03:00
parent 5ee0435758
commit e589ddafc5
2 changed files with 327 additions and 1 deletions

74
db.py
View File

@ -62,6 +62,80 @@ class OracleDatabase:
import asyncio
return await asyncio.to_thread(_query)
async def count_salvage_records(self, vin: str) -> int:
"""
Подсчитывает количество записей в таблице salvagedb.salvagedb для данного VIN
"""
def _query():
with self._pool.acquire() as conn:
with conn.cursor() as cur:
cur.execute("SELECT COUNT(*) FROM salvagedb.salvagedb WHERE vin = :vin", {"vin": vin})
result = cur.fetchone()
return result[0] if result else 0
import asyncio
return await asyncio.to_thread(_query)
async def fetch_salvage_detailed_info(self, vin: str) -> list:
"""
Получает детальную информацию о поврежденияхи истории из таблицы salvagedb.salvagedb
"""
def _query():
with self._pool.acquire() as conn:
with conn.cursor() as cur:
query = """
SELECT
vin,
make,
model,
vehicle_year,
vehicle_type,
primary_damage,
secondary_damage,
sale_date,
odometer,
sale_title_state,
sale_title_type,
seller,
lot,
estimate_repair_cost,
actual_cash_value,
sale_country,
sale_location
FROM salvagedb.salvagedb
WHERE vin = :vin
ORDER BY sale_date DESC
"""
cur.execute(query, {"vin": vin})
results = cur.fetchall()
# Преобразуем результаты в список словарей
detailed_records = []
for row in results:
record = {
'vin': row[0],
'make': row[1],
'model': row[2],
'vehicle_year': row[3],
'vehicle_type': row[4],
'primary_damage': row[5],
'secondary_damage': row[6],
'sale_date': row[7],
'odometer': row[8],
'sale_title_state': row[9],
'sale_title_type': row[10],
'seller': row[11],
'lot': row[12],
'estimate_repair_cost': row[13],
'actual_cash_value': row[14],
'sale_country': row[15],
'sale_location': row[16]
}
detailed_records.append(record)
return detailed_records
import asyncio
return await asyncio.to_thread(_query)
async def fetch_detailed_vin_info(self, vin: str) -> dict:
# Manual async wrapper since oracledb is synchronous (threaded)
def _query():

254
main.py
View File

@ -34,6 +34,7 @@ dp = Dispatcher()
class VinStates(StatesGroup):
waiting_for_vin = State()
waiting_for_check_vin = State()
# Command handler
@ -79,6 +80,19 @@ async def decode_vin_callback(callback: CallbackQuery, state: FSMContext, db: Or
await callback.answer()
@dp.callback_query(lambda c: c.data == "check_vin")
async def check_vin_callback(callback: CallbackQuery, state: FSMContext, db: OracleDatabase = None):
# Используем переданный db или глобальный oracle_db
database = db or oracle_db
# Сохраняем данные пользователя при нажатии кнопки
await database.save_user(callback.from_user, "check_vin_button")
await callback.message.answer("Please enter the vehicle VIN to check salvage records.")
await state.set_state(VinStates.waiting_for_check_vin)
await callback.answer()
@dp.callback_query(lambda c: c.data == "main_menu")
async def main_menu_callback(callback: CallbackQuery, state: FSMContext, db: OracleDatabase = None):
# Используем переданный db или глобальный oracle_db
@ -154,6 +168,129 @@ async def process_vin(message: Message, state: FSMContext, db: OracleDatabase =
await message.answer("Invalid VIN. Please enter a valid 17-character VIN (letters and numbers, no I, O, Q).")
@dp.message(VinStates.waiting_for_check_vin)
async def process_check_vin(message: Message, state: FSMContext, db: OracleDatabase = None):
# Используем переданный db или глобальный oracle_db
database = db or oracle_db
# Сохраняем данные пользователя при обработке VIN
await database.save_user(message.from_user, "check_vin_processing")
vin = message.text.strip().upper()
if len(vin) == 17 and vin.isalnum() and all(c not in vin for c in ["I", "O", "Q"]):
try:
# Получаем базовую информацию о VIN
make, model, year, cnt = await database.fetch_vin_info(vin)
# Получаем количество записей в salvagedb.salvagedb
salvage_count = await database.count_salvage_records(vin)
logging.info(f"Check VIN: make: {make}, model: {model}, year: {year}, cnt: {cnt}, salvage_count: {salvage_count}")
# Формируем текст ответа
if cnt == 0:
response_text = "❌ **Unable to decode VIN, possibly incorrect**\n\n"
else:
response_text = f"🚗 **{year} {make} {model}**\n\n"
response_text += f"📊 **Records found in database:** {salvage_count}\n\n"
# Создаем клавиатуру в зависимости от наличия записей
builder = InlineKeyboardBuilder()
if salvage_count > 0:
# Есть записи - показываем кнопки: Get detailed info, Pay 10⭐, Try another VIN, Back to main menu
builder.button(text="Get detailed info", callback_data=f"get_detailed_info:{vin}")
builder.button(text="Pay 10 ⭐️", callback_data=f"pay_check_detailed:{vin}", pay=True)
builder.button(text="Try another VIN", callback_data="check_vin")
builder.button(text="Back to Main Menu", callback_data="main_menu")
builder.adjust(1, 1, 1, 1) # Each button on separate row
else:
# Нет записей - показываем кнопки: Try another VIN, Back to main menu
response_text += " **No salvage records found for this VIN**"
builder.button(text="Try another VIN", callback_data="check_vin")
builder.button(text="Back to Main Menu", callback_data="main_menu")
builder.adjust(1, 1) # Each button on separate row
await message.answer(response_text, reply_markup=builder.as_markup(), parse_mode="Markdown")
except Exception as e:
logging.error(f"Database error for check VIN {vin}: {e}")
await message.answer("Error retrieving data from database. Please try again later.")
await state.clear()
else:
await message.answer("Invalid VIN. Please enter a valid 17-character VIN (letters and numbers, no I, O, Q).")
@dp.callback_query(lambda c: c.data and c.data.startswith("get_detailed_info:"))
async def get_detailed_info_callback(callback: CallbackQuery, db: OracleDatabase = None):
# Используем переданный db или глобальный oracle_db
database = db or oracle_db
# Сохраняем данные пользователя
await database.save_user(callback.from_user, "get_detailed_info_button")
# Извлекаем VIN из callback data
vin = callback.data.split(":")[1]
try:
# Получаем детальную информацию из таблицы salvagedb.salvagedb
salvage_count = await database.count_salvage_records(vin)
if salvage_count > 0:
response_text = f"🔍 **Detailed Salvage Information for VIN:** {vin}\n\n"
response_text += f"📊 **Total salvage records found:** {salvage_count}\n\n"
response_text += "💡 **This vehicle has salvage records in our database.**\n"
response_text += "For complete detailed analysis including damage history, "
response_text += "accident reports, and comprehensive vehicle information, "
response_text += "please use our paid detailed report service.\n\n"
response_text += "💰 **Price:** 10 ⭐️ (Telegram Stars)"
else:
response_text = f"✅ **Good news!** No salvage records found for VIN: {vin}\n\n"
response_text += "This vehicle appears to have no salvage history in our database."
# Создаем клавиатуру
builder = InlineKeyboardBuilder()
if salvage_count > 0:
builder.button(text="Pay 10 ⭐️ for Full Report", callback_data=f"pay_check_detailed:{vin}", pay=True)
builder.button(text="Try another VIN", callback_data="check_vin")
builder.button(text="Back to Main Menu", callback_data="main_menu")
builder.adjust(1, 1, 1) if salvage_count > 0 else builder.adjust(1, 1)
await callback.message.answer(response_text, reply_markup=builder.as_markup(), parse_mode="Markdown")
except Exception as e:
logging.error(f"Error getting detailed info for VIN {vin}: {e}")
await callback.message.answer("Error retrieving detailed information. Please try again later.")
await callback.answer()
@dp.callback_query(lambda c: c.data and c.data.startswith("pay_check_detailed:"))
async def pay_check_detailed_callback(callback: CallbackQuery, db: OracleDatabase = None):
# Используем переданный db или глобальный oracle_db
database = db or oracle_db
# Сохраняем данные пользователя при инициации платежа
await database.save_user(callback.from_user, "check_payment_initiation")
# Извлекаем VIN из callback data
vin = callback.data.split(":")[1]
prices = [LabeledPrice(label="Detailed Salvage Report", amount=10)]
logging.info(f"Sending invoice for salvage check VIN: {vin}")
await callback.bot.send_invoice(
chat_id=callback.message.chat.id,
title="Detailed Salvage Report",
description="Get comprehensive salvage history and damage information for 10 Telegram Stars",
payload=f"detailed_salvage_check:{vin}", # Уникальный payload для этого типа платежа
provider_token="", # Empty for Telegram Stars
currency="XTR", # Telegram Stars currency
prices=prices
)
await callback.answer()
@dp.pre_checkout_query()
async def pre_checkout_handler(pre_checkout_query: PreCheckoutQuery, db: OracleDatabase = None):
# Используем переданный db или глобальный oracle_db
@ -213,10 +350,13 @@ async def successful_payment_handler(message: Message, db: OracleDatabase = None
# Сохраняем данные о платеже пользователя
await database.save_user(message.from_user, "successful_payment")
await database.update_user_payment(message.from_user.id, 1.0) # 1 Telegram Star
payload = message.successful_payment.invoice_payload
# Определяем сумму платежа в зависимости от типа
payment_amount = 10.0 if payload.startswith("detailed_salvage_check:") else 1.0
await database.update_user_payment(message.from_user.id, payment_amount)
if payload.startswith("detailed_vin_info:"):
vin = payload.split(":")[1]
@ -374,6 +514,118 @@ async def successful_payment_handler(message: Message, db: OracleDatabase = None
"⚠️ Please contact support immediately with this transaction ID for a manual refund:\n"
f"🆔 {message.successful_payment.telegram_payment_charge_id}"
)
elif payload.startswith("detailed_salvage_check:"):
vin = payload.split(":")[1]
try:
# Получаем детальную информацию о salvage записях
salvage_records = await database.fetch_salvage_detailed_info(vin)
if salvage_records:
# Получаем базовую информацию о VIN для заголовка
make, model, year, cnt = await database.fetch_vin_info(vin)
report = f"🚗 **{year} {make} {model}**\n"
report += f"📋 **VIN:** {vin}\n\n"
report += f"🔍 **DETAILED SALVAGE HISTORY REPORT**\n"
report += f"📊 **Total Records Found:** {len(salvage_records)}\n\n"
# Добавляем информацию по каждой записи
for idx, record in enumerate(salvage_records[:5], 1): # Показываем максимум 5 записей
report += f"📋 **Record #{idx}**\n"
if record['sale_date']:
report += f"📅 **Sale Date:** {record['sale_date']}\n"
if record['primary_damage']:
report += f"⚠️ **Primary Damage:** {record['primary_damage']}\n"
if record['secondary_damage']:
report += f"⚠️ **Secondary Damage:** {record['secondary_damage']}\n"
if record['vehicle_type']:
report += f"🚙 **Vehicle Type:** {record['vehicle_type']}\n"
if record['odometer']:
report += f"🛣️ **Odometer:** {record['odometer']:,} miles\n"
if record['sale_title_state']:
report += f"📍 **Title State:** {record['sale_title_state']}\n"
if record['sale_title_type']:
report += f"📄 **Title Type:** {record['sale_title_type']}\n"
if record['seller']:
report += f"🏢 **Seller:** {record['seller']}\n"
if record['estimate_repair_cost']:
report += f"💰 **Estimated Repair Cost:** ${record['estimate_repair_cost']:,}\n"
if record['actual_cash_value']:
report += f"💵 **Actual Cash Value:** ${record['actual_cash_value']:,}\n"
if record['sale_location']:
report += f"📍 **Sale Location:** {record['sale_location']}\n"
report += "\n"
if len(salvage_records) > 5:
report += f"📋 **... and {len(salvage_records) - 5} more records**\n\n"
report += "---\n"
report += f"💰 **Transaction ID:** {message.successful_payment.telegram_payment_charge_id}\n"
report += "⚠️ **This report shows salvage/damage history. Please consult with automotive experts for vehicle evaluation.**"
# Создаем клавиатуру
builder = InlineKeyboardBuilder()
builder.button(text="Try another VIN", callback_data="check_vin")
builder.button(text="Back to Main Menu", callback_data="main_menu")
builder.adjust(2)
await message.answer(report, reply_markup=builder.as_markup(), parse_mode="Markdown")
else:
# Нет записей - возвращаем деньги
try:
await message.bot.refund_star_payment(
user_id=message.from_user.id,
telegram_payment_charge_id=message.successful_payment.telegram_payment_charge_id
)
await message.answer(
"❌ No salvage records found for this VIN in our database.\n"
"💰 Your payment has been automatically refunded.\n"
"This is actually good news - no salvage history found!"
)
logging.info(f"Refund successful for user {message.from_user.id} - no salvage data found for VIN {vin}")
except Exception as refund_error:
logging.error(f"Failed to refund payment for user {message.from_user.id}: {refund_error}")
await message.answer(
"❌ No salvage records found for this VIN.\n"
"⚠️ Please contact support with this transaction ID for a refund:\n"
f"🆔 {message.successful_payment.telegram_payment_charge_id}"
)
except Exception as e:
logging.error(f"Error getting salvage info for {vin}: {e}")
# Возвращаем деньги при ошибке
try:
await message.bot.refund_star_payment(
user_id=message.from_user.id,
telegram_payment_charge_id=message.successful_payment.telegram_payment_charge_id
)
await message.answer(
"❌ Error retrieving salvage information from our database.\n"
"💰 Your payment has been automatically refunded.\n"
"Please try again later or contact support if the issue persists."
)
logging.info(f"Refund successful for user {message.from_user.id}, charge_id: {message.successful_payment.telegram_payment_charge_id}")
except Exception as refund_error:
logging.error(f"Failed to refund payment for user {message.from_user.id}: {refund_error}")
await message.answer(
"❌ Error retrieving salvage information from our database.\n"
"⚠️ Please contact support immediately with this transaction ID for a manual refund:\n"
f"🆔 {message.successful_payment.telegram_payment_charge_id}"
)
else:
await message.answer(
f"✅ Payment successful! Thank you for your purchase.\n"