diff --git a/bff_models.py b/bff_models.py new file mode 100644 index 0000000..46fa612 --- /dev/null +++ b/bff_models.py @@ -0,0 +1,171 @@ +from pydantic import BaseModel, Field, EmailStr +from typing import Optional, List +from datetime import datetime +import uuid +from enum import Enum + + +# BFF Models +class Token(BaseModel): + access_token: str + token_type: str + +class AccountProfileUpdateRequest(BaseModel): + firstName: str + surname: str + email: EmailStr + phone: str + +class AccountPasswordChangeRequest(BaseModel): + currentPassword: str + newPassword: str + +class TransactionStatus(str, Enum): + WAITING = 'waiting' + PROCESS = 'process' + DONE = 'done' + REJECT = 'reject' + ERROR = 'error' + NEW = 'new' + +class AgentTransactionResponse(BaseModel): + amount: float + status: TransactionStatus + transaction_group: uuid.UUID + create_dttm: datetime + update_dttm: datetime + agent_name: Optional[str] = None + +class AutoApproveSettingsRequest(BaseModel): + auto_approve: bool + apply_to_current: Optional[bool] = False + +class ApproveTransactionsRequest(BaseModel): + transaction_ids: List[uuid.UUID] + +# New Response Models for BFF APIs + +class RegisterResponse(BaseModel): + msg: str + +class TgAuthResponse(BaseModel): + msg: str + tg_id: int + +class DashboardCardsResponse(BaseModel): + totalRevenue: float + totalPayouts: float + activeReferrals: int + pendingPayouts: float + totalSales: int + +class DashboardChartTotalItem(BaseModel): + date: str + revenue: float + sales: int + +class DashboardChartTotalResponse(BaseModel): + items: List[DashboardChartTotalItem] + +class DashboardChartAgentItem(BaseModel): + name: str + count: int + sum: float + +class DashboardChartAgentResponse(BaseModel): + items: List[DashboardChartAgentItem] + +class StatAgentsItem(BaseModel): + name: str + refCount: int + salesCount: int + salesSum: float + crediting: float + +class StatAgentsResponse(BaseModel): + items: List[StatAgentsItem] + +class StatReferralsItem(BaseModel): + ref: str + agent: Optional[str] = None + description: str + salesSum: float + salesCount: int + +class StatReferralsResponse(BaseModel): + items: List[StatReferralsItem] + +class StatSalesItem(BaseModel): + saleId: str + cost: float + crediting: float + ref: Optional[str] = None + name: Optional[str] = None + +class StatSalesResponse(BaseModel): + items: List[StatSalesItem] + +class BillingCardsResponse(BaseModel): + cost: float + crediting: float + pendingPayouts: float + +class BillingChartStatItem(BaseModel): + date: str + status: str + count: int + +class BillingChartStatResponse(BaseModel): + items: List[BillingChartStatItem] + +class BillingChartPieItem(BaseModel): + status: str + count: int + +class BillingChartPieResponse(BaseModel): + items: List[BillingChartPieItem] + +class BillingPayoutsTransactionsItem(BaseModel): + id: uuid.UUID + amount: float + agent: Optional[str] = None + status: TransactionStatus + create_dttm: datetime + update_dttm: datetime + +class BillingPayoutsTransactionsResponse(BaseModel): + items: List[BillingPayoutsTransactionsItem] + +class AccountResponse(BaseModel): + firstName: Optional[str] = None + surname: Optional[str] = None + +class CompanyProfileResponse(BaseModel): + name: str + key: str + commission: float + +class AccountProfileResponse(BaseModel): + firstName: Optional[str] = None + surname: Optional[str] = None + phone: Optional[str] = None + email: Optional[EmailStr] = None + create_dttm: datetime + company: CompanyProfileResponse + +class AccountProfileUpdateResponse(BaseModel): + msg: str + +class AccountPasswordChangeResponse(BaseModel): + msg: str + +class AutoApproveSettingsGetResponse(BaseModel): + auto_approve_transactions: bool + +class AutoApproveSettingsUpdateResponse(BaseModel): + msg: str + auto_approve_transactions: bool + +class ApproveTransactionsResult(BaseModel): + msg: str + approved_count: int \ No newline at end of file diff --git a/main.py b/main.py index fac30a3..1e98dd5 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,8 @@ from sqlmodel import SQLModel, Field, create_engine, Session, select from passlib.context import CryptContext from typing import Optional, List, Dict from datetime import datetime, timedelta -from models import RefAddRequest, RefResponse, RegisterRequest, Token, TokenRequest +from bff_models import Token, AccountProfileUpdateRequest, AccountPasswordChangeRequest, AgentTransactionResponse, AutoApproveSettingsRequest, ApproveTransactionsRequest, TransactionStatus, RegisterResponse, DashboardCardsResponse, DashboardChartTotalResponse, DashboardChartAgentResponse, StatAgentsResponse, StatReferralsResponse, StatSalesResponse, BillingCardsResponse, BillingChartStatResponse, BillingChartPieResponse, AccountResponse, CompanyProfileResponse, AccountProfileResponse, AccountProfileUpdateResponse, AccountPasswordChangeResponse, AutoApproveSettingsGetResponse, AutoApproveSettingsUpdateResponse, ApproveTransactionsResult, TgAuthResponse, BillingPayoutsTransactionsResponse +from tg_models import RefAddRequest, RefResponse, RegisterRequest, TokenRequest, RefAddResponse, RefStatResponse, StatResponse from uuid import uuid4 from fastapi.responses import JSONResponse from sqlalchemy import func @@ -199,7 +200,7 @@ async def get_current_tg_agent(request: Request, db: Session = Depends(get_db)): # Регистрация -@app.post("/register", tags=["partner-tg"]) +@app.post("/register", tags=["partner-tg"], response_model=RegisterResponse) def register(req: RegisterRequest, db: Session = Depends(get_db)): tg_id = req.tg_id chat_id = req.chat_id @@ -277,12 +278,12 @@ def login_account_for_access_token( -@app.get("/ref", response_model=list[RefResponse], tags=["partner-tg"]) +@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)): 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"]) +@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)): new_ref = Ref( tg_agent_id=current_tg_agent.id, @@ -294,7 +295,7 @@ def add_ref(req: RefAddRequest, current_tg_agent: TgAgent = Depends(get_current_ db.refresh(new_ref) return {"ref": new_ref.ref} -@app.get("/ref/stat", tags=["partner-tg"]) +@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)): # 1. Получаем все реферальные ссылки пользователя refs = db.exec(select(Ref).where(Ref.tg_agent_id == current_tg_agent.id)).all() @@ -311,7 +312,7 @@ def get_ref_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: }) return {"refData": result} -@app.get("/stat", tags=["partner-tg"]) +@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)): # 1. Получаем все реферальные ссылки пользователя refs = db.exec(select(Ref).where(Ref.tg_agent_id == current_tg_agent.id)).all() @@ -330,7 +331,7 @@ def get_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Sess "availableWithdrawal": availableWithdrawal } -@app.get("/dashboard/cards", tags=["bff", "dashboard"]) +@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)): # 1. Общий доход - сумма всех Sale.cost total_revenue = db.exec(select(Sale).where(Sale.company_id == current_account.company_id)).all() @@ -358,7 +359,7 @@ def get_dashboard_cards(current_account: Account = Depends(get_current_account), "totalSales": totalSales } -@app.get("/dashboard/chart/total", tags=["bff", "dashboard"]) +@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)): # Группируем продажи по дате (день) result = db.exec( @@ -374,9 +375,9 @@ def get_dashboard_chart_total(current_account: Account = Depends(get_current_acc {"date": row.date, "revenue": row.revenue or 0, "sales": row.sales or 0} for row in result ] - return JSONResponse(content=data) + return DashboardChartTotalResponse(items=data) -@app.get("/dashboard/chart/agent", tags=["bff", "dashboard"]) +@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)): # Получаем всех агентов agents = db.exec(select(TgAgent).where(TgAgent.company_id == current_account.company_id)).all() @@ -401,9 +402,9 @@ def get_dashboard_chart_agent(current_account: Account = Depends(get_current_acc "count": sales_count, "sum": sales_sum }) - return JSONResponse(content=result) + return DashboardChartAgentResponse(items=result) -@app.get("/stat/agents", tags=["bff", "stat"]) +@app.get("/stat/agents", tags=["bff", "stat"], response_model=StatAgentsResponse) def get_agents_stat( db: Session = Depends(get_db), date_start: str = Query(None), @@ -441,9 +442,9 @@ def get_agents_stat( "salesSum": sales_sum, "crediting": crediting_sum }) - return JSONResponse(content=result) + return StatAgentsResponse(items=result) -@app.get("/stat/referrals", tags=["bff", "stat"]) +@app.get("/stat/referrals", tags=["bff", "stat"], response_model=StatReferralsResponse) def get_referrals_stat( db: Session = Depends(get_db), date_start: str = Query(None), @@ -469,9 +470,9 @@ def get_referrals_stat( "salesSum": sales_sum, "salesCount": sales_count }) - return JSONResponse(content=result) + return StatReferralsResponse(items=result) -@app.get("/stat/sales", tags=["bff", "stat"]) +@app.get("/stat/sales", tags=["bff", "stat"], response_model=StatSalesResponse) def get_sales_stat( db: Session = Depends(get_db), date_start: str = Query(None), @@ -501,9 +502,9 @@ def get_sales_stat( "ref": ref_obj.ref if ref_obj else None, "name": agent_obj.name if agent_obj else None }) - return JSONResponse(content=result) + return StatSalesResponse(items=result) -@app.get("/billing/cards", tags=["bff", "billing"]) +@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() @@ -523,7 +524,7 @@ def get_billing_cards(current_account: Account = Depends(get_current_account), d "pendingPayouts": pendingPayouts } -@app.get("/billing/payouts/transactions", tags=["bff", "billing"]) +@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), @@ -544,17 +545,22 @@ def get_billing_payouts_transactions( 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, # Используем transaction_group как ID транзакции группы + "id": agent_trans.transaction_group, # Используем id, как и запрошено "amount": agent_trans.amount, - "agent": agent.name if agent else None, # Имя агента из join - "status": agent_trans.status, + "agent": agent.name if agent else None, + "status": status_enum, "create_dttm": agent_trans.create_dttm, "update_dttm": agent_trans.update_dttm, }) - return result + return BillingPayoutsTransactionsResponse(items=result) -@app.get("/billing/chart/stat", tags=["bff", "billing"]) +@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( @@ -574,9 +580,9 @@ def get_billing_chart_stat(current_account: Account = Depends(get_current_accoun {"date": row.date, "status": row.status, "count": row.count} for row in result ] - return JSONResponse(content=data) + return BillingChartStatResponse(items=data) -@app.get("/billing/chart/pie", tags=["bff", "billing"]) +@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( @@ -589,11 +595,11 @@ def get_billing_chart_pie(current_account: Account = Depends(get_current_account {"status": row.status, "count": row.count} for row in result ] - return JSONResponse(content=data) + return BillingChartPieResponse(items=data) -@app.post("/tg_auth", tags=["partner-tg"]) +@app.post("/tg_auth", tags=["partner-tg"], response_model=TgAuthResponse) def tg_auth(hash: str = Body(..., embed=True), db: Session = Depends(get_db)): tg_agent = db.exec(select(TgAgent).where(TgAgent.hash == hash)).first() if not tg_agent: @@ -612,14 +618,14 @@ def get_account_by_login(db: Session, login: str) -> Optional[Account]: -@app.get("/account", tags=["bff", "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"]) +@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: @@ -637,7 +643,7 @@ def get_account_profile(current_account: Account = Depends(get_current_account), } } -@app.post("/account/profile", tags=["bff", "account"]) +@app.post("/account/profile", tags=["bff", "account"], response_model=AccountProfileUpdateResponse) def update_account_profile( req: AccountProfileUpdateRequest, current_account: Account = Depends(get_current_account), @@ -656,7 +662,7 @@ def update_account_profile( db.refresh(current_account) return {"msg": "Профиль обновлён успешно"} -@app.post("/account/password", tags=["bff", "account"]) +@app.post("/account/password", tags=["bff", "account"], response_model=AccountPasswordChangeResponse) def change_account_password( req: AccountPasswordChangeRequest, current_account: Account = Depends(get_current_account), @@ -733,7 +739,7 @@ class AutoApproveSettingsRequest(BaseModel): auto_approve: bool apply_to_current: Optional[bool] = False -@app.get("/account/auto-approve", tags=["bff", "account"]) +@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) @@ -746,7 +752,7 @@ def get_auto_approve_settings( raise HTTPException(status_code=404, detail="Компания не найдена") return {"auto_approve_transactions": company.auto_approve_transactions} -@app.post("/account/auto-approve", tags=["bff", "account"]) +@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), @@ -798,7 +804,7 @@ def update_auto_approve_settings( class ApproveTransactionsRequest(BaseModel): transaction_ids: List[uuid.UUID] -@app.post("/account/approve-transactions", tags=["bff", "account"]) +@app.post("/account/approve-transactions", tags=["bff", "account"], response_model=ApproveTransactionsResult) def approve_agent_transactions( req: ApproveTransactionsRequest, current_account: Account = Depends(get_current_account), diff --git a/models.py b/models.py deleted file mode 100644 index c4fd30c..0000000 --- a/models.py +++ /dev/null @@ -1,32 +0,0 @@ -from pydantic import BaseModel, Field -from typing import Optional - -from sqlmodel import SQLModel - - - - - - -#API models -class Token(BaseModel): - access_token: str - token_type: str - -class RefResponse(BaseModel): - ref: str - description: str - -class RefAddRequest(BaseModel): - description: str - -class TokenRequest(BaseModel): - tg_id: int - -class RegisterRequest(BaseModel): - tg_id: int - chat_id: Optional[int] = None - phone: Optional[str] = None - name: Optional[str] = None - login: Optional[str] = None - company_key: str \ No newline at end of file diff --git a/tg_models.py b/tg_models.py new file mode 100644 index 0000000..2e2e3ff --- /dev/null +++ b/tg_models.py @@ -0,0 +1,40 @@ +from pydantic import BaseModel, Field +from typing import Optional, List +from uuid import UUID + + +# TG Models +class RefResponse(BaseModel): + ref: str + description: str + +class RefAddRequest(BaseModel): + description: str + +class TokenRequest(BaseModel): + tg_id: int + +class RegisterRequest(BaseModel): + tg_id: int + chat_id: Optional[int] = None + phone: Optional[str] = None + name: Optional[str] = None + login: Optional[str] = None + company_key: str + +# New Response Models for TG APIs +class RefAddResponse(BaseModel): + ref: str + +class RefStatItem(BaseModel): + description: str + sales: int + income: float + +class RefStatResponse(BaseModel): + refData: List[RefStatItem] + +class StatResponse(BaseModel): + totalSales: int + totalIncome: float + availableWithdrawal: float \ No newline at end of file