map payments

This commit is contained in:
oleg.vodyanov91@gmail.com 2025-10-21 00:05:53 +04:00
parent 6a69c0d4d2
commit 852c924383
4 changed files with 79 additions and 3 deletions

View File

@ -1,15 +1,38 @@
import logging import logging
import sys import sys
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from typing import Sequence
from core.config import settings from core.config import settings
class ExtendedLogger(logging.Logger):
"""Расширенный логгер с дополнительными методами для форматирования."""
def list(self, message: str, items: Sequence, limit: int | None = 20) -> None:
"""
Логирует сообщение и список элементов в читаемом виде.
Пример:
logger.list("Common codes", ["A1", "B2", "C3"])
"""
if not items:
formatted = "(empty)"
else:
items = [str(i) for i in items]
if limit and len(items) > limit:
formatted = ", ".join(items[:limit]) + f" ... (+{len(items) - limit} more)"
else:
formatted = ", ".join(items)
self.info(f"{message}: {formatted}")
def setup_logging(): def setup_logging():
""" """
Инициализирует логирование для всего приложения. Инициализирует логирование для всего приложения.
Конфигурирует консольный и файловый логгеры с ротацией. Конфигурирует консольный и файловый логгеры с ротацией.
""" """
# Используем наш кастомный класс логгера
logging.setLoggerClass(ExtendedLogger)
# Получаем уровень логирования из настроек, по умолчанию INFO # Получаем уровень логирования из настроек, по умолчанию INFO
level_name = settings.LOGGING_LEVEL if hasattr(settings, 'LOGGING_LEVEL') else 'INFO' level_name = settings.LOGGING_LEVEL if hasattr(settings, 'LOGGING_LEVEL') else 'INFO'
log_level = getattr(logging, level_name, logging.INFO) log_level = getattr(logging, level_name, logging.INFO)

View File

@ -29,4 +29,37 @@ async def get_not_mapped_payments() -> Sequence[Row[Tuple[PaymentsRegistered]]]
select(PaymentsRegistered) select(PaymentsRegistered)
) )
return result.all() return result.all()
async def update_payment_and_session(code: str) -> bool:
"""
Updates both PaymentsRegistered and Sessions in a single transaction.
Returns True if both updates succeed, False if any update fails.
Automatically rolls back if any error occurs.
"""
async with AsyncSessionLocal() as session:
async with session.begin():
try:
# Update PaymentsRegistered
payment_result = await session.execute(
select(PaymentsRegistered).where(PaymentsRegistered.code == code)
)
payment = payment_result.scalar_one_or_none()
if not payment or payment.mapped is not None:
return False
payment.mapped = True
# Update Sessions
session_result = await session.execute(
select(Sessions).where(Sessions.code == code)
)
session_rec = session_result.scalar_one_or_none()
if not session_rec or session_rec.paid_at is not None:
return False
session_rec.paid_at = datetime.utcnow()
return True
except Exception as e:
logger.error(f"Transaction failed: {str(e)}")
raise # This will trigger rollback

View File

@ -101,7 +101,7 @@ async def get_session_info(code: str) -> dict | None:
} }
async def get_sessions_awaiting_payments() -> Sequence[Row[Sessions]]: async def get_sessions_awaiting_payments() -> Sequence[Row[Sessions]] | bool:
async with AsyncSessionLocal() as session: async with AsyncSessionLocal() as session:
async with session.begin(): async with session.begin():
result = await session.execute( result = await session.execute(

View File

@ -1,9 +1,12 @@
from telegram.ext import ( from telegram.ext import (
ContextTypes ContextTypes
) )
from docbot.services.payments_service import get_not_mapped_payments from typing import Sequence, List
from docbot.services.payments_service import get_not_mapped_payments, update_payment_and_session
from docbot.services.session_service import get_sessions_awaiting_payments from docbot.services.session_service import get_sessions_awaiting_payments
from core.logging import logger from core.logging import logger
from sqlalchemy import Row
from db.models import PaymentsRegistered, Sessions
# Сопоставляем оплаты, которые пришли от продамуса с сессиями, по которым ещё не было оплаты # Сопоставляем оплаты, которые пришли от продамуса с сессиями, по которым ещё не было оплаты
async def map_payments(context: ContextTypes.DEFAULT_TYPE) -> None: async def map_payments(context: ContextTypes.DEFAULT_TYPE) -> None:
@ -15,3 +18,20 @@ async def map_payments(context: ContextTypes.DEFAULT_TYPE) -> None:
for session in sessions_awaiting_payments: for session in sessions_awaiting_payments:
logger.info(f"Неоплаченная сессия: {session.Sessions.code}") logger.info(f"Неоплаченная сессия: {session.Sessions.code}")
np_codes = [payment.PaymentsRegistered.code for payment in not_mapped_payments]
sa_codes = [session.Sessions.code for session in sessions_awaiting_payments]
common_codes: List[str] = list(set(np_codes).intersection(sa_codes))
if common_codes:
logger.list("Common codes", common_codes, None)
# Update the database for matching codes (example using raw SQL)
for code in common_codes:
logger.info(f"Updating payment and session for {code}")
success = await update_payment_and_session(code)
if not success:
logger.error(f"Failed to update payment and session for {code}")
else:
logger.info("Совпадения не найдены!")