from fastapi import FastAPI, Depends, HTTPException, status, Header from fastapi.security import OAuth2PasswordBearer from sqlmodel import Session, select from typing import Optional from datetime import datetime 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 helpers_bff import AUTH_DB_ENGINE, get_integration_db, create_integration_jwt_token, get_current_company_from_jwt 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.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 }