from fastapi import FastAPI, Depends, HTTPException, status, Header, Body from sqlmodel import Session, select, Field from typing import Optional, List, Dict from datetime import datetime, timedelta import hashlib import uuid from sql_models import Company, IntegrationToken, Ref, Sale, AgentTransaction, PartnerTransaction, AgentBalance, TgAgent, CompanyBalance from integration_models import Token, SaleCreateRequest, SaleCreateResponse, TransactionStatus from bff_models import RegisterResponse, TgAuthResponse from tg_models import RefAddRequest, RefResponse, RefAddResponse, RefStatResponse, RegisterRequest, StatResponse from helpers_bff import AUTH_DB_ENGINE, get_integration_db, create_integration_jwt_token, get_current_company_from_jwt, get_tg_agent_by_tg_id, get_current_tg_agent app = FastAPI() #7c06945b-f4b8-4929-8350-b9841405a609 @app.post("/token", tags=["integration"], response_model=Token) async def get_token_for_api_key( x_api_key: str = Header(..., alias="X-API-Key"), db: Session = Depends(get_integration_db) ): """ Обменивает API-ключ на JWT токен. """ if not x_api_key: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="API-ключ не предоставлен", headers={"WWW-Authenticate": "Bearer"}, ) api_key_hash = hashlib.sha256(x_api_key.encode()).hexdigest() integration_token_db = db.exec( select(IntegrationToken).where(IntegrationToken.token_hash == api_key_hash) ).first() if not integration_token_db: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Неверный API-ключ", headers={"WWW-Authenticate": "Bearer"}, ) # Обновляем use_dttm токена integration_token_db.use_dttm = datetime.utcnow() db.add(integration_token_db) db.commit() db.refresh(integration_token_db) jwt_token = create_integration_jwt_token(integration_token_db.company_id) return {"access_token": jwt_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_integration_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_integration_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_integration_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_integration_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.post("/tg_auth", tags=["partner-tg"], response_model=TgAuthResponse) def tg_auth(hash: str = Body(..., embed=True), db: Session = Depends(get_integration_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} @app.post("/sale", tags=["integration"], response_model=SaleCreateResponse) async def create_sale( req: SaleCreateRequest, company: Company = Depends(get_current_company_from_jwt), # Используем новую зависимость db: Session = Depends(get_integration_db) ): """ Регистрирует новую продажу в системе. """ # Устанавливаем уровень изоляции для текущей транзакции db.connection(execution_options={'isolation_level': 'SERIALIZABLE'}) # 1. Найти Ref по `ref` и `company.id` # Сначала находим TgAgent, связанный с компанией, затем Ref tg_agent = db.exec( select(TgAgent) .join(Ref) .where(TgAgent.company_id == company.id) .where(Ref.ref == req.ref) ).first() if not tg_agent: raise HTTPException(status_code=404, detail="Реферальная ссылка не найдена или не принадлежит данной компании") referral = db.exec(select(Ref).where(Ref.ref == req.ref).where(Ref.tg_agent_id == tg_agent.id)).first() if not referral: raise HTTPException(status_code=404, detail="Реферальная ссылка не найдена") # 2. Проверить, что sale_id уникален для данной компании existing_sale = db.exec( select(Sale) .where(Sale.company_id == company.id) .where(Sale.sale_id == req.sale_id) ).first() if existing_sale: raise HTTPException(status_code=400, detail="Продажа с таким sale_id уже существует для данной компании") # 3. Рассчитать crediting crediting_amount = req.cost * (company.agent_commission / 100.0) # 4. Проверить и обновить AgentBalance и CompanyBalance # AgentBalance agent_balance = db.exec(select(AgentBalance).where(AgentBalance.tg_agent_id == tg_agent.id)).first() if not agent_balance: raise HTTPException(status_code=404, detail="Баланс агента не найден") # CompanyBalance company_balance = db.exec(select(CompanyBalance).where(CompanyBalance.company_id == company.id)).first() if not company_balance: raise HTTPException(status_code=404, detail="Баланс компании не найден") # 5. Создать Sale new_sale = Sale( cost=req.cost, crediting=crediting_amount, ref=referral.id, sale_id=req.sale_id, company_id=company.id, sale_dttm=datetime.utcnow() ) db.add(new_sale) # Создать AgentTransaction agent_transaction_status = TransactionStatus.DONE # auto_approve_transactions отвечает только за апрув агентских транзакций на вывод agent_transaction = AgentTransaction( tg_agent_id=tg_agent.id, amount=crediting_amount, status=agent_transaction_status.value, transaction_group=uuid.uuid4() ) db.add(agent_transaction) # Обновление балансов для продаж - всегда в замороженный/ожидающий баланс agent_balance.frozen_balance += crediting_amount company_balance.pending_balance -= crediting_amount company_balance.updated_dttm = datetime.utcnow() db.commit() db.refresh(new_sale) db.refresh(agent_balance) db.refresh(company_balance) db.refresh(agent_transaction) return { "msg": "Продажа успешно зарегистрирована", "sale_id": new_sale.sale_id, "crediting": new_sale.crediting } @app.post("/register", tags=["partner-tg"], response_model=RegisterResponse) def register(req: RegisterRequest, db: Session = Depends(get_integration_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"}