Добавлены методы для получения финансовой аналитики в классе OracleDatabase, включая анализ доходов, услуг, конверсии, возвратов, транзакций и эффективности. Обновлены обработчики колбеков в main.py для отображения соответствующих отчетов в админ-панели. Эти изменения улучшают функциональность финансовой аналитики и предоставляют администраторам более полное представление о финансовых показателях.
This commit is contained in:
parent
9d51031d2d
commit
49df85a222
500
db.py
500
db.py
@ -774,3 +774,503 @@ class OracleDatabase:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error getting sources stats: {e}")
|
print(f"Error getting sources stats: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# FINANCIAL ANALYTICS METHODS
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
async def get_finance_revenue_stats(self, admin_user_id: int = 0) -> dict:
|
||||||
|
"""Анализ доходов"""
|
||||||
|
def _get_stats():
|
||||||
|
with self._pool.acquire() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# Общая статистика доходов (исключаем админа)
|
||||||
|
revenue_query = """
|
||||||
|
SELECT
|
||||||
|
SUM(payment_amount) as total_revenue,
|
||||||
|
COUNT(*) as total_transactions,
|
||||||
|
AVG(payment_amount) as avg_transaction,
|
||||||
|
COUNT(DISTINCT user_id) as unique_customers,
|
||||||
|
SUM(CASE WHEN created_date >= SYSDATE - 1 THEN payment_amount ELSE 0 END) as revenue_24h,
|
||||||
|
SUM(CASE WHEN created_date >= SYSDATE - 7 THEN payment_amount ELSE 0 END) as revenue_7d,
|
||||||
|
SUM(CASE WHEN created_date >= SYSDATE - 30 THEN payment_amount ELSE 0 END) as revenue_30d,
|
||||||
|
COUNT(CASE WHEN created_date >= SYSDATE - 1 THEN 1 END) as transactions_24h,
|
||||||
|
COUNT(CASE WHEN created_date >= SYSDATE - 7 THEN 1 END) as transactions_7d,
|
||||||
|
COUNT(CASE WHEN created_date >= SYSDATE - 30 THEN 1 END) as transactions_30d
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed' AND user_id != :admin_user_id
|
||||||
|
"""
|
||||||
|
cur.execute(revenue_query, {"admin_user_id": admin_user_id})
|
||||||
|
revenue = cur.fetchone()
|
||||||
|
|
||||||
|
# Доходы по дням за последние 10 дней (исключаем админа)
|
||||||
|
daily_query = """
|
||||||
|
SELECT
|
||||||
|
TO_CHAR(created_date, 'DD.MM') as day,
|
||||||
|
SUM(payment_amount) as revenue,
|
||||||
|
COUNT(*) as transactions
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE created_date >= SYSDATE - 10 AND payment_status = 'completed' AND user_id != :admin_user_id
|
||||||
|
GROUP BY TO_CHAR(created_date, 'DD.MM'), TRUNC(created_date)
|
||||||
|
ORDER BY TRUNC(created_date) DESC
|
||||||
|
"""
|
||||||
|
cur.execute(daily_query, {"admin_user_id": admin_user_id})
|
||||||
|
daily_revenue = cur.fetchall()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total_revenue": float(revenue[0]) if revenue[0] else 0.0,
|
||||||
|
"total_transactions": revenue[1] or 0,
|
||||||
|
"avg_transaction": round(float(revenue[2]), 2) if revenue[2] else 0.0,
|
||||||
|
"unique_customers": revenue[3] or 0,
|
||||||
|
"revenue_24h": float(revenue[4]) if revenue[4] else 0.0,
|
||||||
|
"revenue_7d": float(revenue[5]) if revenue[5] else 0.0,
|
||||||
|
"revenue_30d": float(revenue[6]) if revenue[6] else 0.0,
|
||||||
|
"transactions_24h": revenue[7] or 0,
|
||||||
|
"transactions_7d": revenue[8] or 0,
|
||||||
|
"transactions_30d": revenue[9] or 0,
|
||||||
|
"daily_revenue": daily_revenue[:10] if daily_revenue else []
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
import asyncio
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
return await loop.run_in_executor(None, _get_stats)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting revenue stats: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
async def get_finance_services_stats(self, admin_user_id: int = 0) -> dict:
|
||||||
|
"""Анализ доходов по услугам"""
|
||||||
|
def _get_stats():
|
||||||
|
with self._pool.acquire() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
try:
|
||||||
|
# Сначала проверим, есть ли данные в таблице (исключаем админа)
|
||||||
|
check_query = "SELECT COUNT(*) FROM payment_logs WHERE payment_status = 'completed' AND user_id != :admin_user_id"
|
||||||
|
cur.execute(check_query, {"admin_user_id": admin_user_id})
|
||||||
|
total_records = cur.fetchone()[0]
|
||||||
|
|
||||||
|
print(f"Total completed payment records: {total_records}")
|
||||||
|
|
||||||
|
if total_records == 0:
|
||||||
|
return {
|
||||||
|
"services_breakdown": [],
|
||||||
|
"trends": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Статистика по типам услуг (исключаем админа)
|
||||||
|
services_query = """
|
||||||
|
SELECT
|
||||||
|
service_type,
|
||||||
|
COUNT(*) as transactions,
|
||||||
|
SUM(payment_amount) as revenue,
|
||||||
|
AVG(payment_amount) as avg_price,
|
||||||
|
COUNT(DISTINCT user_id) as unique_users,
|
||||||
|
COUNT(CASE WHEN service_status = 'success' THEN 1 END) as success_count,
|
||||||
|
COUNT(CASE WHEN refund_status != 'no_refund' THEN 1 END) as refunds,
|
||||||
|
AVG(data_found_count) as avg_data_found
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed' AND user_id != :admin_user_id
|
||||||
|
GROUP BY service_type
|
||||||
|
ORDER BY SUM(payment_amount) DESC
|
||||||
|
"""
|
||||||
|
|
||||||
|
print(f"Executing services query: {services_query}")
|
||||||
|
cur.execute(services_query, {"admin_user_id": admin_user_id})
|
||||||
|
services = cur.fetchall()
|
||||||
|
print(f"Services result: {services}")
|
||||||
|
|
||||||
|
# Тренды по услугам за последние 30 дней - упрощенный запрос (исключаем админа)
|
||||||
|
trends_query = """
|
||||||
|
SELECT
|
||||||
|
service_type,
|
||||||
|
SUM(CASE WHEN created_date >= SYSDATE - 7 THEN 1 ELSE 0 END) as week_transactions,
|
||||||
|
SUM(CASE WHEN created_date >= SYSDATE - 30 THEN 1 ELSE 0 END) as month_transactions
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed' AND user_id != :admin_user_id
|
||||||
|
GROUP BY service_type
|
||||||
|
"""
|
||||||
|
|
||||||
|
print(f"Executing trends query: {trends_query}")
|
||||||
|
cur.execute(trends_query, {"admin_user_id": admin_user_id})
|
||||||
|
trends_result = cur.fetchall()
|
||||||
|
print(f"Trends result: {trends_result}")
|
||||||
|
|
||||||
|
trends = {row[0]: {"week": row[1], "month": row[2]} for row in trends_result} if trends_result else {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"services_breakdown": services if services else [],
|
||||||
|
"trends": trends
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as inner_e:
|
||||||
|
print(f"Inner error in services stats: {inner_e}")
|
||||||
|
# Возвращаем базовую структуру данных
|
||||||
|
return {
|
||||||
|
"services_breakdown": [],
|
||||||
|
"trends": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
import asyncio
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
return await loop.run_in_executor(None, _get_stats)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting services stats: {e}")
|
||||||
|
return {
|
||||||
|
"services_breakdown": [],
|
||||||
|
"trends": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async def get_finance_conversion_stats(self, admin_user_id: int = 0) -> dict:
|
||||||
|
"""Анализ конверсии"""
|
||||||
|
def _get_stats():
|
||||||
|
with self._pool.acquire() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
try:
|
||||||
|
# Сначала получаем общее количество пользователей
|
||||||
|
users_query = "SELECT COUNT(DISTINCT id) FROM bot_users WHERE is_active = 1"
|
||||||
|
cur.execute(users_query)
|
||||||
|
total_users = cur.fetchone()[0] or 1
|
||||||
|
|
||||||
|
# Статистика по платежам (исключаем админа)
|
||||||
|
payment_query = """
|
||||||
|
SELECT
|
||||||
|
COUNT(DISTINCT user_id) as paying_users,
|
||||||
|
COUNT(*) as total_transactions,
|
||||||
|
COUNT(DISTINCT CASE WHEN user_is_premium = 1 THEN user_id END) as premium_buyers,
|
||||||
|
COUNT(DISTINCT CASE WHEN user_is_premium = 0 THEN user_id END) as regular_buyers,
|
||||||
|
AVG(payment_amount) as avg_purchase
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed' AND user_id != :admin_user_id
|
||||||
|
"""
|
||||||
|
cur.execute(payment_query, {"admin_user_id": admin_user_id})
|
||||||
|
payment_result = cur.fetchone()
|
||||||
|
|
||||||
|
if not payment_result:
|
||||||
|
return {
|
||||||
|
"total_users": total_users,
|
||||||
|
"paying_users": 0,
|
||||||
|
"conversion_rate": 0.0,
|
||||||
|
"total_transactions": 0,
|
||||||
|
"premium_buyers": 0,
|
||||||
|
"regular_buyers": 0,
|
||||||
|
"avg_purchase": 0.0,
|
||||||
|
"one_time_buyers": 0,
|
||||||
|
"regular_buyers": 0,
|
||||||
|
"loyal_buyers": 0,
|
||||||
|
"avg_purchases_per_user": 0.0,
|
||||||
|
"repeat_rate": 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
paying_users = payment_result[0] or 0
|
||||||
|
|
||||||
|
# Повторные покупки (исключаем админа)
|
||||||
|
repeat_query = """
|
||||||
|
SELECT
|
||||||
|
COUNT(CASE WHEN purchase_count = 1 THEN 1 END) as one_time_buyers,
|
||||||
|
COUNT(CASE WHEN purchase_count BETWEEN 2 AND 5 THEN 1 END) as regular_buyers_count,
|
||||||
|
COUNT(CASE WHEN purchase_count > 5 THEN 1 END) as loyal_buyers,
|
||||||
|
AVG(purchase_count) as avg_purchases_per_user
|
||||||
|
FROM (
|
||||||
|
SELECT user_id, COUNT(*) as purchase_count
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed' AND user_id != :admin_user_id
|
||||||
|
GROUP BY user_id
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
cur.execute(repeat_query, {"admin_user_id": admin_user_id})
|
||||||
|
repeat = cur.fetchone()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total_users": total_users,
|
||||||
|
"paying_users": paying_users,
|
||||||
|
"conversion_rate": round(paying_users / total_users * 100, 2) if total_users > 0 else 0,
|
||||||
|
"total_transactions": payment_result[1] or 0,
|
||||||
|
"premium_buyers": payment_result[2] or 0,
|
||||||
|
"regular_buyers": payment_result[3] or 0,
|
||||||
|
"avg_purchase": round(float(payment_result[4]), 2) if payment_result[4] else 0.0,
|
||||||
|
"one_time_buyers": repeat[0] or 0 if repeat else 0,
|
||||||
|
"regular_buyers_repeat": repeat[1] or 0 if repeat else 0,
|
||||||
|
"loyal_buyers": repeat[2] or 0 if repeat else 0,
|
||||||
|
"avg_purchases_per_user": round(float(repeat[3]), 2) if repeat and repeat[3] else 0.0,
|
||||||
|
"repeat_rate": round(((repeat[1] or 0) + (repeat[2] or 0)) / paying_users * 100, 2) if paying_users > 0 and repeat else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as inner_e:
|
||||||
|
print(f"Inner error in conversion stats: {inner_e}")
|
||||||
|
return {
|
||||||
|
"total_users": 0,
|
||||||
|
"paying_users": 0,
|
||||||
|
"conversion_rate": 0.0,
|
||||||
|
"total_transactions": 0,
|
||||||
|
"premium_buyers": 0,
|
||||||
|
"regular_buyers": 0,
|
||||||
|
"avg_purchase": 0.0,
|
||||||
|
"one_time_buyers": 0,
|
||||||
|
"regular_buyers_repeat": 0,
|
||||||
|
"loyal_buyers": 0,
|
||||||
|
"avg_purchases_per_user": 0.0,
|
||||||
|
"repeat_rate": 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
import asyncio
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
return await loop.run_in_executor(None, _get_stats)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting conversion stats: {e}")
|
||||||
|
return {
|
||||||
|
"total_users": 0,
|
||||||
|
"paying_users": 0,
|
||||||
|
"conversion_rate": 0.0,
|
||||||
|
"total_transactions": 0,
|
||||||
|
"premium_buyers": 0,
|
||||||
|
"regular_buyers": 0,
|
||||||
|
"avg_purchase": 0.0,
|
||||||
|
"one_time_buyers": 0,
|
||||||
|
"regular_buyers_repeat": 0,
|
||||||
|
"loyal_buyers": 0,
|
||||||
|
"avg_purchases_per_user": 0.0,
|
||||||
|
"repeat_rate": 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
async def get_finance_refunds_stats(self, admin_user_id: int = 0) -> dict:
|
||||||
|
"""Анализ возвратов"""
|
||||||
|
def _get_stats():
|
||||||
|
with self._pool.acquire() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# Статистика возвратов (исключаем админа)
|
||||||
|
refunds_query = """
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_transactions,
|
||||||
|
COUNT(CASE WHEN refund_status != 'no_refund' THEN 1 END) as refund_count,
|
||||||
|
SUM(CASE WHEN refund_status != 'no_refund' THEN payment_amount ELSE 0 END) as refund_amount,
|
||||||
|
COUNT(CASE WHEN refund_status = 'auto_refund' THEN 1 END) as auto_refunds,
|
||||||
|
COUNT(CASE WHEN refund_status = 'manual_refund' THEN 1 END) as manual_refunds,
|
||||||
|
COUNT(CASE WHEN refund_status = 'admin_refund' THEN 1 END) as admin_refunds
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed' AND user_id != :admin_user_id
|
||||||
|
"""
|
||||||
|
cur.execute(refunds_query, {"admin_user_id": admin_user_id})
|
||||||
|
refunds = cur.fetchone()
|
||||||
|
|
||||||
|
# Причины возвратов по типам услуг (исключаем админа)
|
||||||
|
reasons_query = """
|
||||||
|
SELECT
|
||||||
|
service_type,
|
||||||
|
refund_status,
|
||||||
|
COUNT(*) as count,
|
||||||
|
SUM(payment_amount) as amount
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE refund_status != 'no_refund' AND payment_status = 'completed' AND user_id != :admin_user_id
|
||||||
|
GROUP BY service_type, refund_status
|
||||||
|
ORDER BY service_type, COUNT(*) DESC
|
||||||
|
"""
|
||||||
|
cur.execute(reasons_query, {"admin_user_id": admin_user_id})
|
||||||
|
reasons = cur.fetchall()
|
||||||
|
|
||||||
|
total_transactions = refunds[0] or 1
|
||||||
|
refund_count = refunds[1] or 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total_transactions": total_transactions,
|
||||||
|
"refund_count": refund_count,
|
||||||
|
"refund_rate": round(refund_count / total_transactions * 100, 2),
|
||||||
|
"refund_amount": float(refunds[2]) if refunds[2] else 0.0,
|
||||||
|
"auto_refunds": refunds[3] or 0,
|
||||||
|
"manual_refunds": refunds[4] or 0,
|
||||||
|
"admin_refunds": refunds[5] or 0,
|
||||||
|
"refund_breakdown": reasons if reasons else []
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
import asyncio
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
return await loop.run_in_executor(None, _get_stats)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting refunds stats: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
async def get_finance_transactions_stats(self, admin_user_id: int = 0) -> dict:
|
||||||
|
"""Анализ транзакций"""
|
||||||
|
def _get_stats():
|
||||||
|
with self._pool.acquire() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# Статистика транзакций (исключаем админа)
|
||||||
|
transactions_query = """
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_attempts,
|
||||||
|
COUNT(CASE WHEN payment_status = 'completed' THEN 1 END) as completed_count,
|
||||||
|
COUNT(CASE WHEN payment_status = 'pending' THEN 1 END) as pending_count,
|
||||||
|
COUNT(CASE WHEN payment_status = 'failed' THEN 1 END) as failed_count,
|
||||||
|
COUNT(CASE WHEN service_status = 'success' THEN 1 END) as service_success_count,
|
||||||
|
COUNT(CASE WHEN service_status = 'no_data' THEN 1 END) as no_data_count,
|
||||||
|
COUNT(CASE WHEN service_status = 'error' THEN 1 END) as service_error_count
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE user_id != :admin_user_id
|
||||||
|
"""
|
||||||
|
cur.execute(transactions_query, {"admin_user_id": admin_user_id})
|
||||||
|
transactions = cur.fetchone()
|
||||||
|
|
||||||
|
# Статистика ошибок (исключаем админа)
|
||||||
|
errors_query = """
|
||||||
|
SELECT
|
||||||
|
service_type,
|
||||||
|
COUNT(CASE WHEN payment_status = 'failed' THEN 1 END) as payment_failures,
|
||||||
|
COUNT(CASE WHEN service_status = 'error' THEN 1 END) as service_errors,
|
||||||
|
COUNT(CASE WHEN service_status = 'no_data' THEN 1 END) as no_data_cases
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE user_id != :admin_user_id
|
||||||
|
GROUP BY service_type
|
||||||
|
"""
|
||||||
|
cur.execute(errors_query, {"admin_user_id": admin_user_id})
|
||||||
|
errors = cur.fetchall()
|
||||||
|
|
||||||
|
total_attempts = transactions[0] or 1
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total_attempts": total_attempts,
|
||||||
|
"completed": transactions[1] or 0,
|
||||||
|
"pending": transactions[2] or 0,
|
||||||
|
"failed": transactions[3] or 0,
|
||||||
|
"payment_success_rate": round((transactions[1] or 0) / total_attempts * 100, 2),
|
||||||
|
"service_success": transactions[4] or 0,
|
||||||
|
"no_data": transactions[5] or 0,
|
||||||
|
"service_error": transactions[6] or 0,
|
||||||
|
"service_success_rate": round((transactions[4] or 0) / (transactions[1] or 1) * 100, 2),
|
||||||
|
"error_breakdown": errors if errors else []
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
import asyncio
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
return await loop.run_in_executor(None, _get_stats)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting transactions stats: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
async def get_finance_efficiency_stats(self, admin_user_id: int = 0) -> dict:
|
||||||
|
"""Анализ эффективности"""
|
||||||
|
def _get_stats():
|
||||||
|
with self._pool.acquire() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
try:
|
||||||
|
# Разбиваем сложный запрос на несколько простых
|
||||||
|
|
||||||
|
# 1. Средний доход на транзакцию (исключаем админа)
|
||||||
|
avg_revenue_query = """
|
||||||
|
SELECT AVG(payment_amount) as avg_revenue_per_transaction
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed' AND user_id != :admin_user_id
|
||||||
|
"""
|
||||||
|
cur.execute(avg_revenue_query, {"admin_user_id": admin_user_id})
|
||||||
|
avg_revenue_result = cur.fetchone()
|
||||||
|
avg_revenue_per_transaction = round(float(avg_revenue_result[0]), 2) if avg_revenue_result and avg_revenue_result[0] else 0.0
|
||||||
|
|
||||||
|
# 2. Количество уникальных дней с транзакциями (исключаем админа)
|
||||||
|
days_query = """
|
||||||
|
SELECT COUNT(DISTINCT TRUNC(created_date)) as unique_days
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed' AND user_id != :admin_user_id
|
||||||
|
"""
|
||||||
|
cur.execute(days_query, {"admin_user_id": admin_user_id})
|
||||||
|
days_result = cur.fetchone()
|
||||||
|
unique_days = days_result[0] if days_result and days_result[0] else 1
|
||||||
|
|
||||||
|
# 3. Общее количество транзакций (исключаем админа)
|
||||||
|
total_transactions_query = """
|
||||||
|
SELECT COUNT(*) as total_transactions
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed' AND user_id != :admin_user_id
|
||||||
|
"""
|
||||||
|
cur.execute(total_transactions_query, {"admin_user_id": admin_user_id})
|
||||||
|
total_transactions_result = cur.fetchone()
|
||||||
|
total_transactions = total_transactions_result[0] if total_transactions_result else 0
|
||||||
|
|
||||||
|
avg_transactions_per_day = round(total_transactions / unique_days, 2) if unique_days > 0 else 0
|
||||||
|
|
||||||
|
# 4. Доход и транзакции на клиента (исключаем админа)
|
||||||
|
customer_stats_query = """
|
||||||
|
SELECT
|
||||||
|
SUM(payment_amount) as total_revenue,
|
||||||
|
COUNT(*) as total_transactions,
|
||||||
|
COUNT(DISTINCT user_id) as unique_customers
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed' AND user_id != :admin_user_id
|
||||||
|
"""
|
||||||
|
cur.execute(customer_stats_query, {"admin_user_id": admin_user_id})
|
||||||
|
customer_result = cur.fetchone()
|
||||||
|
|
||||||
|
if customer_result and customer_result[2] and customer_result[2] > 0:
|
||||||
|
revenue_per_customer = round(float(customer_result[0]) / customer_result[2], 2)
|
||||||
|
transactions_per_customer = round(float(customer_result[1]) / customer_result[2], 2)
|
||||||
|
else:
|
||||||
|
revenue_per_customer = 0.0
|
||||||
|
transactions_per_customer = 0.0
|
||||||
|
|
||||||
|
# 5. Эффективность по часам дня (исключаем админа)
|
||||||
|
hourly_query = """
|
||||||
|
SELECT
|
||||||
|
EXTRACT(HOUR FROM created_date) as hour,
|
||||||
|
COUNT(*) as transactions,
|
||||||
|
SUM(payment_amount) as revenue
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed' AND created_date >= SYSDATE - 30 AND user_id != :admin_user_id
|
||||||
|
GROUP BY EXTRACT(HOUR FROM created_date)
|
||||||
|
ORDER BY EXTRACT(HOUR FROM created_date)
|
||||||
|
"""
|
||||||
|
cur.execute(hourly_query, {"admin_user_id": admin_user_id})
|
||||||
|
hourly = cur.fetchall()
|
||||||
|
|
||||||
|
# 6. Топ VIN по доходам (исключаем админа)
|
||||||
|
top_vins_query = """
|
||||||
|
SELECT
|
||||||
|
vin_number,
|
||||||
|
COUNT(*) as requests,
|
||||||
|
SUM(payment_amount) as revenue
|
||||||
|
FROM payment_logs
|
||||||
|
WHERE payment_status = 'completed' AND user_id != :admin_user_id
|
||||||
|
GROUP BY vin_number
|
||||||
|
ORDER BY SUM(payment_amount) DESC
|
||||||
|
FETCH FIRST 10 ROWS ONLY
|
||||||
|
"""
|
||||||
|
cur.execute(top_vins_query, {"admin_user_id": admin_user_id})
|
||||||
|
top_vins = cur.fetchall()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"avg_revenue_per_transaction": avg_revenue_per_transaction,
|
||||||
|
"avg_transactions_per_day": avg_transactions_per_day,
|
||||||
|
"revenue_per_customer": revenue_per_customer,
|
||||||
|
"transactions_per_customer": transactions_per_customer,
|
||||||
|
"hourly_distribution": hourly if hourly else [],
|
||||||
|
"top_vins": top_vins[:10] if top_vins else []
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as inner_e:
|
||||||
|
print(f"Inner error in efficiency stats: {inner_e}")
|
||||||
|
return {
|
||||||
|
"avg_revenue_per_transaction": 0.0,
|
||||||
|
"avg_transactions_per_day": 0.0,
|
||||||
|
"revenue_per_customer": 0.0,
|
||||||
|
"transactions_per_customer": 0.0,
|
||||||
|
"hourly_distribution": [],
|
||||||
|
"top_vins": []
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
import asyncio
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
return await loop.run_in_executor(None, _get_stats)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting efficiency stats: {e}")
|
||||||
|
return {
|
||||||
|
"avg_revenue_per_transaction": 0.0,
|
||||||
|
"avg_transactions_per_day": 0.0,
|
||||||
|
"revenue_per_customer": 0.0,
|
||||||
|
"transactions_per_customer": 0.0,
|
||||||
|
"hourly_distribution": [],
|
||||||
|
"top_vins": []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
418
main.py
418
main.py
@ -690,12 +690,11 @@ async def admin_stats_callback(callback: CallbackQuery, db: OracleDatabase = Non
|
|||||||
builder.button(text="👥 Пользователи", callback_data="admin_users")
|
builder.button(text="👥 Пользователи", callback_data="admin_users")
|
||||||
builder.button(text="💰 Финансы", callback_data="admin_finance")
|
builder.button(text="💰 Финансы", callback_data="admin_finance")
|
||||||
builder.adjust(2)
|
builder.adjust(2)
|
||||||
builder.button(text="🔍 Техническая", callback_data="admin_technical")
|
|
||||||
builder.button(text="⚡ Операционная", callback_data="admin_operations")
|
builder.button(text="⚡ Операционная", callback_data="admin_operations")
|
||||||
builder.adjust(2)
|
|
||||||
builder.button(text="📈 Бизнес-аналитика", callback_data="admin_business")
|
builder.button(text="📈 Бизнес-аналитика", callback_data="admin_business")
|
||||||
|
builder.adjust(2)
|
||||||
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||||
builder.adjust(1, 1)
|
builder.adjust(1)
|
||||||
|
|
||||||
await callback.message.answer(admin_menu_text, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
await callback.message.answer(admin_menu_text, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
@ -765,35 +764,6 @@ async def admin_finance_callback(callback: CallbackQuery, db: OracleDatabase = N
|
|||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query(lambda c: c.data == "admin_technical")
|
|
||||||
async def admin_technical_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
|
||||||
"""Техническая аналитика"""
|
|
||||||
database = db or oracle_db
|
|
||||||
|
|
||||||
if callback.from_user.id != ADMIN_USER_ID:
|
|
||||||
await callback.answer("❌ Access denied.", show_alert=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
menu_text = """🔍 **Техническая аналитика**
|
|
||||||
|
|
||||||
Выберите тип отчета:"""
|
|
||||||
|
|
||||||
builder = InlineKeyboardBuilder()
|
|
||||||
builder.button(text="🚗 Популярные авто", callback_data="admin_tech_popular")
|
|
||||||
builder.button(text="📅 Тренды годов", callback_data="admin_tech_years")
|
|
||||||
builder.adjust(2)
|
|
||||||
builder.button(text="🗺️ География ДТП", callback_data="admin_tech_geography")
|
|
||||||
builder.button(text="💥 Типы повреждений", callback_data="admin_tech_damages")
|
|
||||||
builder.adjust(2)
|
|
||||||
builder.button(text="📊 Качество данных", callback_data="admin_tech_quality")
|
|
||||||
builder.button(text="📸 Статистика фото", callback_data="admin_tech_photos")
|
|
||||||
builder.adjust(2)
|
|
||||||
builder.button(text="🔙 Назад", callback_data="admin_stats")
|
|
||||||
builder.adjust(1)
|
|
||||||
|
|
||||||
await callback.message.answer(menu_text, reply_markup=builder.as_markup(), parse_mode="Markdown")
|
|
||||||
await callback.answer()
|
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query(lambda c: c.data == "admin_operations")
|
@dp.callback_query(lambda c: c.data == "admin_operations")
|
||||||
async def admin_operations_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
async def admin_operations_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||||
@ -1161,6 +1131,390 @@ async def admin_users_sources_callback(callback: CallbackQuery, db: OracleDataba
|
|||||||
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# FINANCIAL ANALYTICS REPORT HANDLERS
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
@dp.callback_query(lambda c: c.data == "admin_finance_revenue")
|
||||||
|
async def admin_finance_revenue_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||||
|
"""Анализ доходов"""
|
||||||
|
database = db or oracle_db
|
||||||
|
|
||||||
|
if callback.from_user.id != ADMIN_USER_ID:
|
||||||
|
await callback.answer("❌ Access denied.", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
stats = await database.get_finance_revenue_stats(ADMIN_USER_ID)
|
||||||
|
|
||||||
|
# Формируем график доходов
|
||||||
|
daily_chart = ""
|
||||||
|
if stats['daily_revenue']:
|
||||||
|
daily_chart = "\n📈 <b>Доходы по дням (последние 10):</b>\n"
|
||||||
|
for day, revenue, transactions in stats['daily_revenue']:
|
||||||
|
daily_chart += f"• {day}: <b>{revenue:.0f}</b> ⭐ ({transactions} транз.)\n"
|
||||||
|
|
||||||
|
report = f"""💵 <b>Анализ доходов</b>
|
||||||
|
|
||||||
|
💰 <b>Общая статистика:</b>
|
||||||
|
• Общая выручка: <b>{stats['total_revenue']:,.0f}</b> ⭐️
|
||||||
|
• Всего транзакций: <b>{stats['total_transactions']:,}</b>
|
||||||
|
• Средний чек: <b>{stats['avg_transaction']:.1f}</b> ⭐️
|
||||||
|
• Уникальных клиентов: <b>{stats['unique_customers']:,}</b>
|
||||||
|
|
||||||
|
📊 <b>Доходы по периодам:</b>
|
||||||
|
• За 24 часа: <b>{stats['revenue_24h']:,.0f}</b> ⭐ ({stats['transactions_24h']} транз.)
|
||||||
|
• За 7 дней: <b>{stats['revenue_7d']:,.0f}</b> ⭐ ({stats['transactions_7d']} транз.)
|
||||||
|
• За 30 дней: <b>{stats['revenue_30d']:,.0f}</b> ⭐ ({stats['transactions_30d']} транз.)
|
||||||
|
|
||||||
|
📈 <b>Средние показатели:</b>
|
||||||
|
• Доход на клиента: <b>{round(stats['total_revenue']/stats['unique_customers'], 1) if stats['unique_customers'] > 0 else 0:.1f}</b> ⭐
|
||||||
|
• Транзакций на клиента: <b>{round(stats['total_transactions']/stats['unique_customers'], 1) if stats['unique_customers'] > 0 else 0:.1f}</b>
|
||||||
|
|
||||||
|
{daily_chart}
|
||||||
|
|
||||||
|
📅 <b>Сгенерировано:</b> {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="🔙 Назад", callback_data="admin_finance")
|
||||||
|
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||||
|
builder.adjust(2)
|
||||||
|
|
||||||
|
await callback.message.answer(report, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error generating revenue stats: {e}")
|
||||||
|
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.callback_query(lambda c: c.data == "admin_finance_services")
|
||||||
|
async def admin_finance_services_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||||
|
"""Анализ по услугам"""
|
||||||
|
database = db or oracle_db
|
||||||
|
|
||||||
|
if callback.from_user.id != ADMIN_USER_ID:
|
||||||
|
await callback.answer("❌ Access denied.", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
stats = await database.get_finance_services_stats(ADMIN_USER_ID)
|
||||||
|
|
||||||
|
# Проверяем, есть ли данные
|
||||||
|
if not stats or not stats.get('services_breakdown'):
|
||||||
|
report = """📊 <b>Анализ по услугам</b>
|
||||||
|
|
||||||
|
📭 <b>Нет данных</b>
|
||||||
|
|
||||||
|
В системе пока нет завершенных транзакций для анализа.
|
||||||
|
|
||||||
|
💡 <b>Возможные причины:</b>
|
||||||
|
• Таблица payment_logs пустая
|
||||||
|
• Нет записей со статусом 'completed'
|
||||||
|
• Все транзакции имеют статус 'pending' или 'failed'
|
||||||
|
|
||||||
|
📅 <b>Сгенерировано:</b> {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
|
||||||
|
else:
|
||||||
|
# Статистика по услугам
|
||||||
|
services_text = ""
|
||||||
|
if stats['services_breakdown']:
|
||||||
|
services_text = "\n📊 <b>Breakdown по услугам:</b>\n"
|
||||||
|
service_names = {
|
||||||
|
'decode_vin': '🔍 Декодинг VIN',
|
||||||
|
'check_salvage': '💥 Проверка Salvage',
|
||||||
|
'get_photos': '📸 Получение фото'
|
||||||
|
}
|
||||||
|
|
||||||
|
for service, transactions, revenue, avg_price, users, success_count, refunds, avg_data in stats['services_breakdown']:
|
||||||
|
name = service_names.get(service, service)
|
||||||
|
success_rate = round(success_count/transactions*100, 1) if transactions > 0 else 0
|
||||||
|
refund_rate = round(refunds/transactions*100, 1) if transactions > 0 else 0
|
||||||
|
|
||||||
|
# Получаем тренды
|
||||||
|
trends = stats['trends'].get(service, {"week": 0, "month": 0})
|
||||||
|
|
||||||
|
services_text += f"• {name}:\n"
|
||||||
|
services_text += f" 💰 Доход: <b>{revenue:.0f}</b> ⭐ ({transactions} транз.)\n"
|
||||||
|
services_text += f" 💵 Средняя цена: <b>{avg_price:.1f}</b> ⭐\n"
|
||||||
|
services_text += f" 👥 Клиентов: <b>{users}</b>\n"
|
||||||
|
services_text += f" ✅ Успешность: <b>{success_rate}%</b>\n"
|
||||||
|
services_text += f" ↩️ Возвраты: <b>{refund_rate}%</b>\n"
|
||||||
|
services_text += f" 📈 За неделю/месяц: <b>{trends['week']}/{trends['month']}</b>\n\n"
|
||||||
|
|
||||||
|
report = f"""📊 <b>Анализ по услугам</b>
|
||||||
|
|
||||||
|
{services_text}
|
||||||
|
|
||||||
|
💡 <b>Выводы:</b>
|
||||||
|
• Наиболее доходная услуга по общей выручке
|
||||||
|
• Услуга с наивысшей конверсией
|
||||||
|
• Оптимизация ценообразования по успешности
|
||||||
|
|
||||||
|
📅 <b>Сгенерировано:</b> {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="🔙 Назад", callback_data="admin_finance")
|
||||||
|
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||||
|
builder.adjust(2)
|
||||||
|
|
||||||
|
await callback.message.answer(report, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error generating services stats: {e}")
|
||||||
|
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.callback_query(lambda c: c.data == "admin_finance_conversion")
|
||||||
|
async def admin_finance_conversion_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||||
|
"""Анализ конверсии"""
|
||||||
|
database = db or oracle_db
|
||||||
|
|
||||||
|
if callback.from_user.id != ADMIN_USER_ID:
|
||||||
|
await callback.answer("❌ Access denied.", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
stats = await database.get_finance_conversion_stats(ADMIN_USER_ID)
|
||||||
|
|
||||||
|
report = f"""🔄 <b>Анализ конверсии</b>
|
||||||
|
|
||||||
|
🎯 <b>Основная конверсия:</b>
|
||||||
|
• Всего пользователей: <b>{stats['total_users']:,}</b>
|
||||||
|
• Платящих пользователей: <b>{stats['paying_users']:,}</b>
|
||||||
|
• <b>Конверсия в покупку: {stats['conversion_rate']:.2f}%</b>
|
||||||
|
|
||||||
|
👥 <b>Сегментация покупателей:</b>
|
||||||
|
• Premium покупатели: <b>{stats['premium_buyers']:,}</b>
|
||||||
|
• Обычные покупатели: <b>{stats['regular_buyers']:,}</b>
|
||||||
|
• Средняя покупка: <b>{stats['avg_purchase']:.1f}</b> ⭐
|
||||||
|
|
||||||
|
🔄 <b>Повторные покупки:</b>
|
||||||
|
• Разовые покупатели: <b>{stats['one_time_buyers']:,}</b>
|
||||||
|
• Регулярные (2-5): <b>{stats['regular_buyers_repeat']:,}</b>
|
||||||
|
• Лояльные (5+): <b>{stats['loyal_buyers']:,}</b>
|
||||||
|
• Среднее покупок на клиента: <b>{stats['avg_purchases_per_user']:.1f}</b>
|
||||||
|
|
||||||
|
📈 <b>Retention метрики:</b>
|
||||||
|
• Repeat Rate: <b>{stats['repeat_rate']:.1f}%</b>
|
||||||
|
• Всего транзакций: <b>{stats['total_transactions']:,}</b>
|
||||||
|
|
||||||
|
💡 <b>Анализ:</b>
|
||||||
|
• {stats['repeat_rate']:.1f}% покупателей совершают повторные покупки
|
||||||
|
• Потенциал роста конверсии до {100 - stats['conversion_rate']:.1f}%
|
||||||
|
|
||||||
|
📅 <b>Сгенерировано:</b> {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="🔙 Назад", callback_data="admin_finance")
|
||||||
|
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||||
|
builder.adjust(2)
|
||||||
|
|
||||||
|
await callback.message.answer(report, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error generating conversion stats: {e}")
|
||||||
|
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.callback_query(lambda c: c.data == "admin_finance_refunds")
|
||||||
|
async def admin_finance_refunds_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||||
|
"""Анализ возвратов"""
|
||||||
|
database = db or oracle_db
|
||||||
|
|
||||||
|
if callback.from_user.id != ADMIN_USER_ID:
|
||||||
|
await callback.answer("❌ Access denied.", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
stats = await database.get_finance_refunds_stats(ADMIN_USER_ID)
|
||||||
|
|
||||||
|
# Breakdown возвратов по услугам
|
||||||
|
refunds_breakdown = ""
|
||||||
|
if stats['refund_breakdown']:
|
||||||
|
refunds_breakdown = "\n↩️ <b>Возвраты по услугам:</b>\n"
|
||||||
|
service_names = {
|
||||||
|
'decode_vin': '🔍 Декодинг VIN',
|
||||||
|
'check_salvage': '💥 Проверка Salvage',
|
||||||
|
'get_photos': '📸 Получение фото'
|
||||||
|
}
|
||||||
|
|
||||||
|
current_service = None
|
||||||
|
for service, refund_type, count, amount in stats['refund_breakdown']:
|
||||||
|
if service != current_service:
|
||||||
|
current_service = service
|
||||||
|
name = service_names.get(service, service)
|
||||||
|
refunds_breakdown += f"\n• {name}:\n"
|
||||||
|
|
||||||
|
refund_name = {
|
||||||
|
'auto_refund': 'Авто возврат',
|
||||||
|
'manual_refund': 'Ручной возврат',
|
||||||
|
'admin_refund': 'Админ возврат'
|
||||||
|
}.get(refund_type, refund_type)
|
||||||
|
|
||||||
|
refunds_breakdown += f" - {refund_name}: <b>{count}</b> шт. (<b>{amount:.0f}</b> ⭐)\n"
|
||||||
|
|
||||||
|
report = f"""↩️ <b>Анализ возвратов</b>
|
||||||
|
|
||||||
|
📊 <b>Общая статистика:</b>
|
||||||
|
• Всего транзакций: <b>{stats['total_transactions']:,}</b>
|
||||||
|
• Возвратов: <b>{stats['refund_count']:,}</b>
|
||||||
|
• <b>Процент возвратов: {stats['refund_rate']:.2f}%</b>
|
||||||
|
• Сумма возвратов: <b>{stats['refund_amount']:,.0f}</b> ⭐
|
||||||
|
|
||||||
|
🔄 <b>Типы возвратов:</b>
|
||||||
|
• Автоматические: <b>{stats['auto_refunds']:,}</b>
|
||||||
|
• Ручные: <b>{stats['manual_refunds']:,}</b>
|
||||||
|
• Админские: <b>{stats['admin_refunds']:,}</b>
|
||||||
|
|
||||||
|
{refunds_breakdown}
|
||||||
|
|
||||||
|
💡 <b>Анализ:</b>
|
||||||
|
• Низкий процент возвратов (<5%) - показатель качества
|
||||||
|
• Высокий процент (>10%) - требует оптимизации услуг
|
||||||
|
• Автовозвраты снижают нагрузку на поддержку
|
||||||
|
|
||||||
|
📅 <b>Сгенерировано:</b> {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="🔙 Назад", callback_data="admin_finance")
|
||||||
|
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||||
|
builder.adjust(2)
|
||||||
|
|
||||||
|
await callback.message.answer(report, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error generating refunds stats: {e}")
|
||||||
|
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.callback_query(lambda c: c.data == "admin_finance_transactions")
|
||||||
|
async def admin_finance_transactions_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||||
|
"""Анализ транзакций"""
|
||||||
|
database = db or oracle_db
|
||||||
|
|
||||||
|
if callback.from_user.id != ADMIN_USER_ID:
|
||||||
|
await callback.answer("❌ Access denied.", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
stats = await database.get_finance_transactions_stats(ADMIN_USER_ID)
|
||||||
|
|
||||||
|
# Breakdown ошибок по услугам
|
||||||
|
errors_breakdown = ""
|
||||||
|
if stats['error_breakdown']:
|
||||||
|
errors_breakdown = "\n🚨 <b>Ошибки по услугам:</b>\n"
|
||||||
|
service_names = {
|
||||||
|
'decode_vin': '🔍 Декодинг VIN',
|
||||||
|
'check_salvage': '💥 Проверка Salvage',
|
||||||
|
'get_photos': '📸 Получение фото'
|
||||||
|
}
|
||||||
|
|
||||||
|
for service, payment_failures, service_errors, no_data in stats['error_breakdown']:
|
||||||
|
name = service_names.get(service, service)
|
||||||
|
errors_breakdown += f"• {name}:\n"
|
||||||
|
errors_breakdown += f" - Ошибки платежей: <b>{payment_failures}</b>\n"
|
||||||
|
errors_breakdown += f" - Ошибки сервиса: <b>{service_errors}</b>\n"
|
||||||
|
errors_breakdown += f" - Нет данных: <b>{no_data}</b>\n"
|
||||||
|
|
||||||
|
report = f"""💳 <b>Анализ транзакций</b>
|
||||||
|
|
||||||
|
💰 <b>Статистика платежей:</b>
|
||||||
|
• Всего попыток: <b>{stats['total_attempts']:,}</b>
|
||||||
|
• Успешно: <b>{stats['completed']:,}</b>
|
||||||
|
• В ожидании: <b>{stats['pending']:,}</b>
|
||||||
|
• Неудачно: <b>{stats['failed']:,}</b>
|
||||||
|
• <b>Успешность платежей: {stats['payment_success_rate']:.1f}%</b>
|
||||||
|
|
||||||
|
⚙️ <b>Статистика сервисов:</b>
|
||||||
|
• Успешные сервисы: <b>{stats['service_success']:,}</b>
|
||||||
|
• Нет данных: <b>{stats['no_data']:,}</b>
|
||||||
|
• Ошибки сервиса: <b>{stats['service_error']:,}</b>
|
||||||
|
• <b>Успешность сервисов: {stats['service_success_rate']:.1f}%</b>
|
||||||
|
|
||||||
|
{errors_breakdown}
|
||||||
|
|
||||||
|
📊 <b>Ключевые метрики:</b>
|
||||||
|
• Конверсия попытка → успех: <b>{stats['payment_success_rate']:.1f}%</b>
|
||||||
|
• Конверсия платеж → данные: <b>{stats['service_success_rate']:.1f}%</b>
|
||||||
|
|
||||||
|
📅 <b>Сгенерировано:</b> {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="🔙 Назад", callback_data="admin_finance")
|
||||||
|
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||||
|
builder.adjust(2)
|
||||||
|
|
||||||
|
await callback.message.answer(report, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error generating transactions stats: {e}")
|
||||||
|
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.callback_query(lambda c: c.data == "admin_finance_efficiency")
|
||||||
|
async def admin_finance_efficiency_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||||
|
"""Анализ эффективности"""
|
||||||
|
database = db or oracle_db
|
||||||
|
|
||||||
|
if callback.from_user.id != ADMIN_USER_ID:
|
||||||
|
await callback.answer("❌ Access denied.", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
stats = await database.get_finance_efficiency_stats(ADMIN_USER_ID)
|
||||||
|
|
||||||
|
# Распределение по часам
|
||||||
|
hourly_chart = ""
|
||||||
|
if stats['hourly_distribution']:
|
||||||
|
hourly_chart = "\n⏰ <b>Активность по часам (UTC):</b>\n"
|
||||||
|
for hour, transactions, revenue in stats['hourly_distribution'][:12]: # Топ 12 часов
|
||||||
|
hourly_chart += f"• {int(hour):02d}:00 - <b>{transactions}</b> транз. (<b>{revenue:.0f}</b> ⭐)\n"
|
||||||
|
|
||||||
|
# Топ VIN по доходам
|
||||||
|
top_vins_text = ""
|
||||||
|
if stats['top_vins']:
|
||||||
|
top_vins_text = "\n🏆 <b>Топ VIN по доходам:</b>\n"
|
||||||
|
for vin, requests, revenue in stats['top_vins'][:5]: # Топ 5
|
||||||
|
top_vins_text += f"• <code>{vin}</code>: <b>{requests}</b> запр. = <b>{revenue:.0f}</b> ⭐\n"
|
||||||
|
|
||||||
|
report = f"""🎯 <b>Анализ эффективности</b>
|
||||||
|
|
||||||
|
💰 <b>Финансовая эффективность:</b>
|
||||||
|
• Доход на транзакцию: <b>{stats['avg_revenue_per_transaction']:.1f}</b> ⭐
|
||||||
|
• Транзакций в день: <b>{stats['avg_transactions_per_day']:.1f}</b>
|
||||||
|
• Доход на клиента: <b>{stats['revenue_per_customer']:.1f}</b> ⭐
|
||||||
|
• Транзакций на клиента: <b>{stats['transactions_per_customer']:.1f}</b>
|
||||||
|
|
||||||
|
{hourly_chart}
|
||||||
|
|
||||||
|
{top_vins_text}
|
||||||
|
|
||||||
|
📊 <b>Insights:</b>
|
||||||
|
• LTV клиента: <b>{stats['revenue_per_customer']:.0f}</b> ⭐
|
||||||
|
• Средняя частота покупок: <b>{stats['transactions_per_customer']:.1f}</b>
|
||||||
|
• Дневной оборот: <b>{stats['avg_transactions_per_day'] * stats['avg_revenue_per_transaction']:.0f}</b> ⭐
|
||||||
|
|
||||||
|
📅 <b>Сгенерировано:</b> {datetime.now().strftime('%d.%m.%Y %H:%M')}"""
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="🔙 Назад", callback_data="admin_finance")
|
||||||
|
builder.button(text="🏠 Main Menu", callback_data="main_menu")
|
||||||
|
builder.adjust(2)
|
||||||
|
|
||||||
|
await callback.message.answer(report, reply_markup=builder.as_markup(), parse_mode="HTML")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error generating efficiency stats: {e}")
|
||||||
|
await callback.answer("❌ Ошибка генерации отчета", show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query(lambda c: c.data and c.data.startswith("pay_detailed_info:"))
|
@dp.callback_query(lambda c: c.data and c.data.startswith("pay_detailed_info:"))
|
||||||
async def pay_detailed_info_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
async def pay_detailed_info_callback(callback: CallbackQuery, db: OracleDatabase = None):
|
||||||
# Используем переданный db или глобальный oracle_db
|
# Используем переданный db или глобальный oracle_db
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user