partner-core/main.py

748 lines
34 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import uuid
from fastapi import (
FastAPI,
Depends,
HTTPException,
status,
Query,
Body,
)
from fastapi.security import OAuth2PasswordRequestForm
from sqlmodel import SQLModel, Session, select
from typing import Optional, List, Dict
from datetime import timedelta, datetime
from bff_models import (
Token,
RegisterResponse,
DashboardCardsResponse,
DashboardChartTotalResponse,
DashboardChartAgentResponse,
StatAgentsResponse,
StatReferralsResponse,
StatSalesResponse,
BillingCardsResponse,
BillingChartStatResponse,
BillingChartPieResponse,
AccountResponse,
AccountProfileResponse,
AccountProfileUpdateResponse,
AccountPasswordChangeResponse,
AutoApproveSettingsGetResponse,
AutoApproveSettingsUpdateResponse,
ApproveTransactionsResult,
TgAuthResponse,
BillingPayoutsTransactionsResponse,
AccountProfileUpdateRequest,
AccountPasswordChangeRequest,
AutoApproveSettingsRequest,
ApproveTransactionsRequest,
AgentTransactionResponse,
TransactionStatus
)
from tg_models import RefAddRequest, RefResponse, RegisterRequest, RefAddResponse, RefStatResponse, StatResponse
from sql_models import (
Company,
TgAgent,
Ref,
Sale,
AgentTransaction,
PartnerTransaction,
AgentBalance,
Account
)
from sqlalchemy import func
import hashlib
from helpers_bff import (
AUTH_DB_ENGINE,
get_db,
get_tg_agent_by_tg_id,
get_current_account,
get_current_tg_agent,
create_access_token,
verify_password,
get_account_by_login,
ACCESS_TOKEN_EXPIRE_MINUTES,
pwd_context,
)
# Создание движка базы данных
SQLModel.metadata.create_all(AUTH_DB_ENGINE)
# FastAPI app
app = FastAPI()
@app.post("/register", tags=["partner-tg"], response_model=RegisterResponse)
def register(req: RegisterRequest, db: Session = Depends(get_db)):
"""
Регистрирует нового Telegram-агента в системе.
"""
tg_id = req.tg_id
chat_id = req.chat_id
phone = req.phone
name = getattr(req, 'name', None)
login = getattr(req, 'login', None)
company_key = req.company_key
print(f'tg_id: {tg_id}, chat_id: {chat_id}, phone: {phone}, name: {name}, login: {login}, company_key: {company_key}')
tg_agent = get_tg_agent_by_tg_id(db, tg_id)
if tg_agent:
raise HTTPException(status_code=400, detail="tg_id already registered")
# Поиск компании по ключу
company = db.exec(select(Company).where(Company.key == company_key)).first()
if not company:
raise HTTPException(status_code=400, detail="Компания с таким ключом не найдена")
hash_value = hashlib.sha256(f"{tg_id}sold".encode()).hexdigest()
new_tg_agent = TgAgent(
tg_id=tg_id,
chat_id=chat_id,
phone=phone,
name=name,
login=login,
hash=hash_value,
company_id=company.id
)
db.add(new_tg_agent)
db.commit()
db.refresh(new_tg_agent)
return {"msg": "TgAgent registered successfully"}
@app.post("/token", response_model=Token, tags=["bff", "token"])
def login_account_for_access_token(
form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(get_db)
):
"""
Авторизует аккаунт и возвращает токен доступа.
"""
# login: str = Body(...),
# password: str = Body(...),
account = get_account_by_login(db, form_data.username)
if not account or not verify_password(form_data.password, account.password_hash):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect login or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token = create_access_token(
data={"sub": account.login},
expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
)
return Token(access_token=access_token, token_type="bearer")
@app.get("/ref", response_model=List[RefResponse], tags=["partner-tg"])
def get_refs(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_db)):
"""
Возвращает список реферальных ссылок текущего Telegram-агента.
"""
refs = db.exec(select(Ref).where(Ref.tg_agent_id == current_tg_agent.id)).all()
return [RefResponse(ref=r.ref, description=r.description or "") for r in refs]
@app.post("/ref/add", tags=["partner-tg"], response_model=RefAddResponse)
def add_ref(req: RefAddRequest, current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_db)):
"""
Добавляет новую реферальную ссылку для текущего Telegram-агента.
"""
new_ref = Ref(
tg_agent_id=current_tg_agent.id,
ref=str(uuid.uuid4()),
description=req.description
)
db.add(new_ref)
db.commit()
db.refresh(new_ref)
return {"ref": new_ref.ref}
@app.get("/ref/stat", tags=["partner-tg"], response_model=RefStatResponse)
def get_ref_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_db)):
"""
Возвращает статистику по реферальным ссылкам текущего Telegram-агента.
"""
# 1. Получаем все реферальные ссылки пользователя
refs = db.exec(select(Ref).where(Ref.tg_agent_id == current_tg_agent.id)).all()
result = []
for ref in refs:
# 2. Для каждой ссылки считаем продажи и сумму
sales = db.exec(select(Sale).where(Sale.ref == ref.id)).all()
sales_count = len(sales)
income = sum(sale.crediting for sale in sales)
result.append({
"description": ref.description or "",
"sales": sales_count,
"income": income
})
return {"refData": result}
@app.get("/stat", tags=["partner-tg"], response_model=StatResponse)
def get_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_db)):
"""
Возвращает общую статистику для текущего Telegram-агента.
"""
# 1. Получаем все реферальные ссылки пользователя
refs = db.exec(select(Ref).where(Ref.tg_agent_id == current_tg_agent.id)).all()
ref_ids = [r.id for r in refs]
# 2. Считаем totalSales (продажи по всем рефам пользователя)
total_sales = db.exec(select(Sale).where(Sale.ref.in_(ref_ids))).all()
totalSales = len(total_sales)
totalIncome = sum(sale.crediting for sale in total_sales)
# Заменено получение доступного остатка из AgentBalance
agent_balance = db.exec(select(AgentBalance).where(AgentBalance.tg_agent_id == current_tg_agent.id)).first()
availableWithdrawal = agent_balance.available_balance if agent_balance else 0.0
return {
"totalSales": totalSales,
"totalIncome": totalIncome,
"availableWithdrawal": availableWithdrawal
}
@app.get("/dashboard/cards", tags=["bff", "dashboard"], response_model=DashboardCardsResponse)
def get_dashboard_cards(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает данные для карточек на главной панели (dashboard) пользователя, включая общий доход, выплаты, активных рефералов, ожидающие выплаты и общее количество продаж.
"""
# 1. Общий доход - сумма всех Sale.cost
total_revenue = db.exec(select(Sale).where(Sale.company_id == current_account.company_id)).all()
totalRevenue = sum(sale.cost for sale in total_revenue)
# 2. Общие выплаты - сумма всех Sale.crediting
totalPayouts = sum(sale.crediting for sale in total_revenue)
# 3. Активные рефералы - количество уникальных TgAgent.tg_id
unique_agents = db.exec(select(TgAgent.tg_id).where(TgAgent.company_id == current_account.company_id)).all()
activeReferrals = len(set(unique_agents))
# 4. Ожидающие выплаты - сумма AgentTransaction со статусом 'waiting'
pending_agent_transactions = db.exec(select(AgentTransaction).join(TgAgent).where(TgAgent.company_id == current_account.company_id).where(AgentTransaction.status == 'waiting')).all()
pendingPayouts = sum(t.amount for t in pending_agent_transactions)
# 5. Количество продаж
totalSales = len(total_revenue)
return {
"totalRevenue": totalRevenue,
"totalPayouts": totalPayouts,
"activeReferrals": activeReferrals,
"pendingPayouts": pendingPayouts,
"totalSales": totalSales
}
@app.get("/dashboard/chart/total", tags=["bff", "dashboard"], response_model=DashboardChartTotalResponse)
def get_dashboard_chart_total(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает данные для графика "Общий доход и продажи по датам" на главной панели (dashboard).
"""
# Группируем продажи по дате (день)
result = db.exec(
select(
func.strftime('%Y-%m-%d', Sale.create_dttm).label('date'),
func.sum(Sale.cost).label('revenue'),
func.count(Sale.id).label('sales')
).where(Sale.company_id == current_account.company_id).group_by(func.strftime('%Y-%m-%d', Sale.create_dttm))
.order_by(func.strftime('%Y-%m-%d', Sale.create_dttm))
).all()
# Преобразуем результат в нужный формат
data = [
{"date": row.date, "revenue": row.revenue or 0, "sales": row.sales or 0}
for row in result
]
return DashboardChartTotalResponse(items=data)
@app.get("/dashboard/chart/agent", tags=["bff", "dashboard"], response_model=DashboardChartAgentResponse)
def get_dashboard_chart_agent(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает данные для графика "Продажи по агентам" на главной панели (dashboard).
"""
# Получаем всех агентов
agents = db.exec(select(TgAgent).where(TgAgent.company_id == current_account.company_id)).all()
result = []
for agent in agents:
# Получаем все рефы этого агента
refs = db.exec(select(Ref).where(Ref.tg_agent_id == agent.id)).all()
ref_ids = [r.id for r in refs]
if not ref_ids:
result.append({
"name": agent.name or f"Агент {agent.id}",
"count": 0,
"sum": 0.0
})
continue
# Получаем все продажи по этим рефам
sales = db.exec(select(Sale).where(Sale.ref.in_(ref_ids))).all()
sales_count = len(sales)
sales_sum = sum(sale.cost for sale in sales)
result.append({
"name": agent.name or f"Агент {agent.id}",
"count": sales_count,
"sum": sales_sum
})
return DashboardChartAgentResponse(items=result)
@app.get("/stat/agents", tags=["bff", "stat"], response_model=StatAgentsResponse)
def get_agents_stat(
db: Session = Depends(get_db),
date_start: str = Query(None),
date_end: str = Query(None),
current_account: Account = Depends(get_current_account),
):
"""
Возвращает статистику по агентам компании, включая количество рефералов, продаж, сумму продаж и начислений.
Возможна фильтрация по дате создания агентов.
"""
agents_query = select(TgAgent).where(TgAgent.company_id == current_account.company_id)
if date_start:
agents_query = agents_query.where(TgAgent.create_dttm >= date_start)
if date_end:
agents_query = agents_query.where(TgAgent.create_dttm <= date_end)
agents = db.exec(agents_query).all()
result = []
for agent in agents:
refs = db.exec(select(Ref).where(Ref.tg_agent_id == agent.id)).all()
ref_ids = [r.id for r in refs]
ref_count = len(ref_ids)
if not ref_ids:
result.append({
"name": agent.name or f"Агент {agent.id}",
"refCount": 0,
"salesCount": 0,
"salesSum": 0.0,
"crediting": 0.0
})
continue
sales = db.exec(select(Sale).where(Sale.ref.in_(ref_ids))).all()
sales_count = len(sales)
sales_sum = sum(sale.cost for sale in sales)
crediting_sum = sum(sale.crediting for sale in sales)
result.append({
"name": agent.name or f"Агент {agent.id}",
"refCount": ref_count,
"salesCount": sales_count,
"salesSum": sales_sum,
"crediting": crediting_sum
})
return StatAgentsResponse(items=result)
@app.get("/stat/referrals", tags=["bff", "stat"], response_model=StatReferralsResponse)
def get_referrals_stat(
db: Session = Depends(get_db),
date_start: str = Query(None),
date_end: str = Query(None),
current_account: Account = Depends(get_current_account),
):
"""
Возвращает статистику по реферальным ссылкам компании, включая имя агента, описание, сумму и количество продаж.
Возможна фильтрация по дате создания рефералов.
"""
refs_query = select(Ref).join(TgAgent).where(TgAgent.company_id == current_account.company_id)
if date_start:
refs_query = refs_query.where(Ref.create_dttm >= date_start)
if date_end:
refs_query = refs_query.where(Ref.create_dttm <= date_end)
refs = db.exec(refs_query).all()
result = []
for ref in refs:
agent = db.exec(select(TgAgent).where(TgAgent.id == ref.tg_agent_id)).first()
sales = db.exec(select(Sale).where(Sale.ref == ref.id)).all()
sales_count = len(sales)
sales_sum = sum(sale.cost for sale in sales)
result.append({
"ref": ref.ref,
"agent": agent.name if agent and agent.name else f"Агент {ref.tg_agent_id}",
"description": ref.description or "",
"salesSum": sales_sum,
"salesCount": sales_count
})
return StatReferralsResponse(items=result)
@app.get("/stat/sales", tags=["bff", "stat"], response_model=StatSalesResponse)
def get_sales_stat(
db: Session = Depends(get_db),
date_start: str = Query(None),
date_end: str = Query(None),
current_account: Account = Depends(get_current_account),
):
"""
Возвращает статистику по продажам компании, включая ID продажи, стоимость, начисления, реферала и имя агента.
Возможна фильтрация по дате создания продаж.
"""
sales_query = select(Sale).where(Sale.company_id == current_account.company_id)
if date_start:
sales_query = sales_query.where(Sale.create_dttm >= date_start)
if date_end:
sales_query = sales_query.where(Sale.create_dttm <= date_end)
sales = db.exec(sales_query).all()
ref_ids = list(set(sale.ref for sale in sales))
refs = db.exec(select(Ref).where(Ref.id.in_(ref_ids))).all() if ref_ids else []
ref_map = {ref.id: ref for ref in refs}
agent_ids = list(set(ref.tg_agent_id for ref in refs)) if refs else []
agents = db.exec(select(TgAgent).where(TgAgent.id.in_(agent_ids))).all() if agent_ids else []
agent_map = {agent.id: agent for agent in agents}
result = []
for sale in sales:
ref_obj = ref_map.get(sale.ref)
agent_obj = agent_map.get(ref_obj.tg_agent_id) if ref_obj else None
result.append({
"saleId": sale.sale_id,
"cost": sale.cost,
"crediting": sale.crediting,
"ref": ref_obj.ref if ref_obj else None,
"name": agent_obj.name if agent_obj else None
})
return StatSalesResponse(items=result)
@app.get("/billing/cards", tags=["bff", "billing"], response_model=BillingCardsResponse)
def get_billing_cards(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает ключевые показатели биллинга для компании, включая общий заработок, общие выплаты и доступные к выводу средства.
"""
# 1. cost - Общий заработок (сумма всех Sale.cost)
sales = db.exec(select(Sale).where(Sale.company_id == current_account.company_id)).all()
cost = sum(sale.cost for sale in sales)
# 2. crediting - Общие выплаты (сумма PartnerTransaction типа 'agent_payout' со статусом 'done')
completed_payouts = db.exec(select(PartnerTransaction).where(PartnerTransaction.type == 'agent_payout').where(PartnerTransaction.status == 'done').where(PartnerTransaction.company_id == current_account.company_id)).all()
crediting = sum(t.amount for t in completed_payouts)
# 3. pendingPayouts - Доступно к выводу всеми партнерами (сумма всех доступных балансов агентов)
agent_balances = db.exec(select(AgentBalance).join(TgAgent).where(TgAgent.company_id == current_account.company_id)).all()
pendingPayouts = sum(balance.available_balance for balance in agent_balances)
return {
"cost": cost,
"crediting": crediting,
"pendingPayouts": pendingPayouts
}
@app.get("/billing/payouts/transactions", tags=["bff", "billing"], response_model=BillingPayoutsTransactionsResponse)
def get_billing_payouts_transactions(
db: Session = Depends(get_db),
date_start: str = Query(None),
date_end: str = Query(None),
current_account: Account = Depends(get_current_account),
):
"""
Возвращает список транзакций выплат для компании текущего пользователя.
Возможна фильтрация по дате создания.
"""
# Используем AgentTransaction вместо Transaction
# Явно выбираем обе модели для корректной распаковки
query = select(AgentTransaction, TgAgent).join(TgAgent).where(TgAgent.company_id == current_account.company_id)
if date_start:
query = query.where(AgentTransaction.create_dttm >= date_start)
if date_end:
query = query.where(AgentTransaction.create_dttm <= date_end)
# Заказываем по дате создания
query = query.order_by(AgentTransaction.create_dttm.desc())
# Выполняем запрос и формируем результат
results = db.exec(query).all()
result = []
for agent_trans, agent in results:
try:
status_enum = TransactionStatus(agent_trans.status)
except ValueError:
# Если статус из БД не соответствует Enum, используем статус ERROR
status_enum = TransactionStatus.ERROR
result.append({
"id": agent_trans.transaction_group, # Используем id, как и запрошено
"amount": agent_trans.amount,
"agent": agent.name if agent else None,
"status": status_enum,
"create_dttm": agent_trans.create_dttm,
"update_dttm": agent_trans.update_dttm,
})
return BillingPayoutsTransactionsResponse(items=result)
@app.get("/billing/chart/stat", tags=["bff", "billing"], response_model=BillingChartStatResponse)
def get_billing_chart_stat(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает статистику выплат по датам и статусам для компании текущего пользователя.
"""
# Группируем агентские транзакции по дате (день) и статусу
result = db.exec(
select(
func.strftime('%Y-%m-%d', AgentTransaction.create_dttm).label('date'),
AgentTransaction.status.label('status'),
func.count(AgentTransaction.id).label('count')
).join(TgAgent).where(TgAgent.company_id == current_account.company_id).group_by(
func.strftime('%Y-%m-%d', AgentTransaction.create_dttm),
AgentTransaction.status
).order_by(
func.strftime('%Y-%m-%d', AgentTransaction.create_dttm),
AgentTransaction.status
)
).all()
data = [
{"date": row.date, "status": row.status, "count": row.count}
for row in result
]
return BillingChartStatResponse(items=data)
@app.get("/billing/chart/pie", tags=["bff", "billing"], response_model=BillingChartPieResponse)
def get_billing_chart_pie(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает круговую диаграмму статистики выплат по статусам для компании текущего пользователя.
"""
# Группируем агентские транзакции по статусу
result = db.exec(
select(
AgentTransaction.status.label('status'),
func.count(AgentTransaction.id).label('count')
).join(TgAgent).where(TgAgent.company_id == current_account.company_id).group_by(AgentTransaction.status)
).all()
data = [
{"status": row.status, "count": row.count}
for row in result
]
return BillingChartPieResponse(items=data)
@app.post("/tg_auth", tags=["partner-tg"], response_model=TgAuthResponse)
def tg_auth(hash: str = Body(..., embed=True), db: Session = Depends(get_db)):
"""
Авторизует Telegram-агента по хешу.
"""
tg_agent = db.exec(select(TgAgent).where(TgAgent.hash == hash)).first()
if not tg_agent:
raise HTTPException(status_code=401, detail="Hash not found")
return {"msg": "Auth success", "tg_id": tg_agent.tg_id}
# --- Новый функционал для Account ---
@app.get("/account", tags=["bff", "account"], response_model=AccountResponse)
def get_account(current_account: Account = Depends(get_current_account)):
"""
Возвращает базовую информацию об аккаунте текущего пользователя (имя и фамилию).
"""
return {
"firstName": current_account.firstName,
"surname": current_account.surname
}
@app.get("/account/profile", tags=["bff", "account"], response_model=AccountProfileResponse)
def get_account_profile(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает полную информацию о профиле аккаунта текущего пользователя, включая данные компании.
"""
company = db.exec(select(Company).where(Company.id == current_account.company_id)).first()
if not company:
raise HTTPException(status_code=404, detail="Компания не найдена")
return {
"firstName": current_account.firstName,
"surname": current_account.surname,
"phone": current_account.phone,
"email": current_account.email,
"create_dttm": current_account.create_dttm,
"company": {
"name": company.name,
"key": company.key,
"commission": company.commission
}
}
@app.post("/account/profile", tags=["bff", "account"], response_model=AccountProfileUpdateResponse)
def update_account_profile(
req: AccountProfileUpdateRequest,
current_account: Account = Depends(get_current_account),
db: Session = Depends(get_db)
):
"""
Обновляет информацию профиля текущего пользователя.
"""
# Проверка, что все поля заполнены (Pydantic уже валидирует email и обязательность)
if not req.firstName.strip() or not req.surname.strip() or not req.email or not req.phone.strip():
raise HTTPException(status_code=400, detail="Все поля должны быть заполнены")
# Обновляем поля
current_account.firstName = req.firstName.strip()
current_account.surname = req.surname.strip()
current_account.email = req.email
current_account.phone = req.phone.strip()
db.add(current_account)
db.commit()
db.refresh(current_account)
return {"msg": "Профиль обновлён успешно"}
@app.post("/account/password", tags=["bff", "account"], response_model=AccountPasswordChangeResponse)
def change_account_password(
req: AccountPasswordChangeRequest,
current_account: Account = Depends(get_current_account),
db: Session = Depends(get_db)
):
"""
Изменяет пароль текущего пользователя.
"""
# Проверяем текущий пароль
if not verify_password(req.currentPassword, current_account.password_hash):
raise HTTPException(status_code=400, detail="Текущий пароль неверный")
# Проверяем, что новый пароль не пустой и отличается от текущего
if not req.newPassword.strip():
raise HTTPException(status_code=400, detail="Новый пароль не может быть пустым")
if verify_password(req.newPassword, current_account.password_hash):
raise HTTPException(status_code=400, detail="Новый пароль не должен совпадать с текущим")
# Хешируем и сохраняем новый пароль
current_account.password_hash = pwd_context.hash(req.newPassword)
db.add(current_account)
db.commit()
db.refresh(current_account)
return {"msg": "Пароль успешно изменён"}
# --- Новый функционал для агентских транзакций партнера ---
@app.get("/account/agent-transaction", response_model=List[AgentTransactionResponse], tags=["bff", "account"])
def get_account_agent_transactions(
statuses: Optional[List[TransactionStatus]] = Query(None),
date_start: str = Query(None),
date_end: str = Query(None),
current_account: Account = Depends(get_current_account),
db: Session = Depends(get_db)
):
"""
Возвращает список агентских транзакций для компании текущего пользователя,
с возможностью фильтрации по статусу и дате создания.
"""
# Получаем ID компании текущего аккаунта
company_id = current_account.company_id
# Строим базовый запрос: выбрать AgentTransaction и TgAgent, связанные с агентами этой компании
query = select(AgentTransaction, TgAgent).join(TgAgent).where(TgAgent.company_id == company_id)
# Если переданы статусы, добавляем фильтрацию по статусам
if statuses:
query = query.where(AgentTransaction.status.in_(statuses))
# Если передана дата начала, добавляем фильтрацию по дате создания >= date_start
if date_start:
query = query.where(AgentTransaction.create_dttm >= date_start)
# Если передана дата окончания, добавляем фильтрацию по дате создания <= date_end
if date_end:
query = query.where(AgentTransaction.create_dttm <= date_end)
# Выполняем запрос
results = db.exec(query).all()
# Формируем список ответов в формате AgentTransactionResponse
agent_transactions_response = []
for agent_trans, agent in results:
try:
status_enum = TransactionStatus(agent_trans.status)
except ValueError:
# Если статус из БД не соответствует Enum, используем статус ERROR
status_enum = TransactionStatus.ERROR
agent_transactions_response.append(
AgentTransactionResponse(
amount=agent_trans.amount,
status=status_enum,
transaction_group=agent_trans.transaction_group,
create_dttm=agent_trans.create_dttm,
update_dttm=agent_trans.update_dttm,
agent_name=agent.name
)
)
return agent_transactions_response
@app.get("/account/auto-approve", tags=["bff", "account"], response_model=AutoApproveSettingsGetResponse)
def get_auto_approve_settings(
current_account: Account = Depends(get_current_account),
db: Session = Depends(get_db)
):
"""
Возвращает текущую настройку автоматического подтверждения для компании пользователя.
"""
company = db.exec(select(Company).where(Company.id == current_account.company_id)).first()
if not company:
raise HTTPException(status_code=404, detail="Компания не найдена")
return {"auto_approve_transactions": company.auto_approve_transactions}
@app.post("/account/auto-approve", tags=["bff", "account"], response_model=AutoApproveSettingsUpdateResponse)
def update_auto_approve_settings(
req: AutoApproveSettingsRequest,
current_account: Account = Depends(get_current_account),
db: Session = Depends(get_db)
):
"""
Обновляет настройку автоматического подтверждения транзакций для компании пользователя.
При необходимости переводит транзакции из 'waiting' в 'new'.
"""
company = db.exec(select(Company).where(Company.id == current_account.company_id)).first()
if not company:
raise HTTPException(status_code=404, detail="Компания не найдена")
company.auto_approve_transactions = req.auto_approve
company.update_dttm = datetime.utcnow()
db.add(company)
if req.apply_to_current and req.auto_approve: # Применяем только если авто-аппрув включается и запрошено применение к текущим
# Находим все агентские транзакции компании в статусе 'waiting'
agent_transactions_to_update = db.exec(
select(AgentTransaction)
.join(TgAgent)
.where(TgAgent.company_id == company.id)
.where(AgentTransaction.status == TransactionStatus.WAITING)
).all()
for agent_trans in agent_transactions_to_update:
agent_trans.status = TransactionStatus.NEW
agent_trans.update_dttm = datetime.utcnow()
db.add(agent_trans)
# Находим соответствующие партнерские транзакции и обновляем их статус
partner_transactions_to_update = db.exec(
select(PartnerTransaction)
.where(PartnerTransaction.agent_transaction_id == agent_trans.id)
.where(PartnerTransaction.status == TransactionStatus.PROCESS)
).all()
for partner_trans in partner_transactions_to_update:
partner_trans.status = TransactionStatus.NEW
partner_trans.update_dttm = datetime.utcnow()
db.add(partner_trans)
db.commit()
db.refresh(company)
return {"msg": "Настройка автоматического подтверждения обновлена", "auto_approve_transactions": company.auto_approve_transactions}
@app.post("/account/approve-transactions", tags=["bff", "account"], response_model=ApproveTransactionsResult)
def approve_agent_transactions(
req: ApproveTransactionsRequest,
current_account: Account = Depends(get_current_account),
db: Session = Depends(get_db)
):
"""
Утверждение выбранных агентских транзакций для компании текущего пользователя.
Переводит транзакции из статуса 'waiting' в 'new'.
"""
company_id = current_account.company_id
approved_count = 0
if not req.transaction_ids:
return {"msg": "Нет транзакций для утверждения", "approved_count": 0}
# Find transactions belonging to the company and with specified IDs and statuses
transactions_to_approve = db.exec(
select(AgentTransaction)
.join(TgAgent)
.where(TgAgent.company_id == company_id)
.where(AgentTransaction.transaction_group.in_(req.transaction_ids))
.where(AgentTransaction.status == TransactionStatus.WAITING)
).all()
for agent_trans in transactions_to_approve:
agent_trans.status = TransactionStatus.NEW
agent_trans.update_dttm = datetime.utcnow()
db.add(agent_trans)
approved_count += 1
db.commit()
return {"msg": f"Переведено в статус NEW {approved_count} транзакций", "approved_count": approved_count}