Compare commits
No commits in common. "7045d6790a1b9a56befae54171ffae4edba15066" and "076cdd18281bb2130aebcb31477dcd9b87de752b" have entirely different histories.
7045d6790a
...
076cdd1828
@ -144,7 +144,6 @@ class CompanyProfileResponse(BaseModel):
|
|||||||
name: str
|
name: str
|
||||||
key: str
|
key: str
|
||||||
commission: float
|
commission: float
|
||||||
agent_commission: float
|
|
||||||
|
|
||||||
class AccountProfileResponse(BaseModel):
|
class AccountProfileResponse(BaseModel):
|
||||||
firstName: Optional[str] = None
|
firstName: Optional[str] = None
|
||||||
@ -187,4 +186,4 @@ class IntegrationTokenCreateRequest(BaseModel):
|
|||||||
|
|
||||||
class IntegrationTokenUpdateRequest(BaseModel):
|
class IntegrationTokenUpdateRequest(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
description: str
|
description: str
|
||||||
@ -1,61 +0,0 @@
|
|||||||
import uuid
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
|
|
||||||
# Конфигурация API
|
|
||||||
BASE_URL = "http://127.0.0.1:8001"
|
|
||||||
API_KEY = "672a1437-70e8-461f-9bff-20f5ce4a023d"
|
|
||||||
REF = "9bd1a6bd-98e1-48f4-a120-3b3d016011c0"
|
|
||||||
|
|
||||||
# Данные для запроса на создание продажи
|
|
||||||
# Замените эти значения на актуальные для вашей продажи
|
|
||||||
sale_data = {
|
|
||||||
"cost": 100.50, # Стоимость продажи
|
|
||||||
"ref": REF, # Ваш реферальный код
|
|
||||||
"sale_id": str(uuid.uuid4()) # Уникальный идентификатор продажи для вашей компании
|
|
||||||
}
|
|
||||||
|
|
||||||
# Эндпоинты
|
|
||||||
token_endpoint = f"{BASE_URL}/token"
|
|
||||||
sale_endpoint = f"{BASE_URL}/sale"
|
|
||||||
|
|
||||||
# Шаг 1: Получение JWT токена
|
|
||||||
print(f"Отправка запроса на получение токена на {token_endpoint}")
|
|
||||||
token_headers = {
|
|
||||||
"X-API-Key": API_KEY,
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
token_response = requests.post(token_endpoint, headers=token_headers)
|
|
||||||
token_response.raise_for_status()
|
|
||||||
|
|
||||||
token_data = token_response.json()
|
|
||||||
jwt_token = token_data["access_token"]
|
|
||||||
print("JWT токен успешно получен.")
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"Произошла ошибка при получении токена: {e}")
|
|
||||||
if hasattr(e, 'response') and e.response is not None:
|
|
||||||
print("Тело ответа с ошибкой:", e.response.json())
|
|
||||||
exit() # Прерываем выполнение, если не удалось получить токен
|
|
||||||
|
|
||||||
# Шаг 2: Вызов эндпоинта /sale с использованием полученного JWT токена
|
|
||||||
headers_with_jwt = {
|
|
||||||
"Authorization": f"Bearer {jwt_token}",
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
|
|
||||||
print(f"Отправка запроса на {sale_endpoint} с данными: {sale_data}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
sale_response = requests.post(sale_endpoint, headers=headers_with_jwt, data=json.dumps(sale_data))
|
|
||||||
sale_response.raise_for_status() # Вызовет исключение для ошибок HTTP (4xx или 5xx)
|
|
||||||
|
|
||||||
print("Статус ответа:", sale_response.status_code)
|
|
||||||
print("Тело ответа:", sale_response.json())
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"Произошла ошибка при вызове API sale: {e}")
|
|
||||||
if hasattr(e, 'response') and e.response is not None:
|
|
||||||
print("Тело ответа с ошибкой:", e.response.json())
|
|
||||||
@ -85,7 +85,6 @@ def fill_db():
|
|||||||
company = Company(
|
company = Company(
|
||||||
name="RE: Premium",
|
name="RE: Premium",
|
||||||
commission=10.0,
|
commission=10.0,
|
||||||
agent_commission=15.0,
|
|
||||||
key="re-premium-key",
|
key="re-premium-key",
|
||||||
)
|
)
|
||||||
session.add(company)
|
session.add(company)
|
||||||
@ -175,7 +174,7 @@ def fill_db():
|
|||||||
sale_count = random.randint(20, int(20 * 1.25)) # от 20 до 25
|
sale_count = random.randint(20, int(20 * 1.25)) # от 20 до 25
|
||||||
for _ in range(sale_count):
|
for _ in range(sale_count):
|
||||||
cost = round(random.uniform(100, 1000), 2)
|
cost = round(random.uniform(100, 1000), 2)
|
||||||
crediting = round(cost * (company.agent_commission / 100.0), 2)
|
crediting = round(cost * random.uniform(0.5, 1.0), 2)
|
||||||
|
|
||||||
# Генерируем случайную дату и время в пределах последних 7 дней
|
# Генерируем случайную дату и время в пределах последних 7 дней
|
||||||
end_dttm = datetime.utcnow()
|
end_dttm = datetime.utcnow()
|
||||||
|
|||||||
@ -3,13 +3,12 @@ from passlib.context import CryptContext
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from bff_models import Token, TransactionStatus
|
from bff_models import Token, TransactionStatus
|
||||||
from sql_models import Company, TgAgent, Account, AgentBalance, AgentTransaction, PartnerTransaction, Sale, Ref, IntegrationToken, CompanyBalance
|
from sql_models import Company, TgAgent, Account, AgentBalance, AgentTransaction, PartnerTransaction, Sale, Ref
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
import jwt
|
import jwt
|
||||||
from jwt.exceptions import InvalidTokenError
|
from jwt.exceptions import InvalidTokenError
|
||||||
from fastapi import HTTPException, status, Depends, Request, Header
|
from fastapi import HTTPException, status, Depends, Request
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
import hashlib
|
|
||||||
|
|
||||||
# Конфигурация
|
# Конфигурация
|
||||||
AUTH_DATABASE_ADDRESS = "sqlite:///partner.db"
|
AUTH_DATABASE_ADDRESS = "sqlite:///partner.db"
|
||||||
@ -19,60 +18,9 @@ SECRET_KEY = "supersecretkey"
|
|||||||
ALGORITHM = "HS256"
|
ALGORITHM = "HS256"
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES = 60
|
ACCESS_TOKEN_EXPIRE_MINUTES = 60
|
||||||
|
|
||||||
# JWT Configuration for Integration API
|
|
||||||
INTEGRATION_SECRET_KEY = "your-super-secret-jwt-key" # TODO: Замените на реальный секретный ключ из переменных окружения
|
|
||||||
INTEGRATION_ALGORITHM = "HS256"
|
|
||||||
INTEGRATION_ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # Токен действителен 7 дней
|
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
|
||||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
|
||||||
def get_integration_db():
|
|
||||||
with Session(AUTH_DB_ENGINE) as session:
|
|
||||||
yield session
|
|
||||||
|
|
||||||
def create_integration_jwt_token(company_id: int):
|
|
||||||
expires = datetime.utcnow() + timedelta(minutes=INTEGRATION_ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
||||||
payload = {
|
|
||||||
"sub": str(company_id),
|
|
||||||
"exp": expires,
|
|
||||||
"type": "access"
|
|
||||||
}
|
|
||||||
return jwt.encode(payload, INTEGRATION_SECRET_KEY, algorithm=INTEGRATION_ALGORITHM)
|
|
||||||
|
|
||||||
async def get_current_company_from_jwt(
|
|
||||||
token: str = Depends(OAuth2PasswordBearer(tokenUrl="/token")),
|
|
||||||
db: Session = Depends(get_integration_db)
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Зависимость для получения текущей компании на основе JWT токена для Integration API.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
payload = jwt.decode(token, INTEGRATION_SECRET_KEY, algorithms=[INTEGRATION_ALGORITHM])
|
|
||||||
company_id: int = int(payload.get("sub"))
|
|
||||||
if company_id is None:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Недействительная полезная нагрузка токена",
|
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
|
||||||
)
|
|
||||||
company = db.exec(select(Company).where(Company.id == company_id)).first()
|
|
||||||
if not company:
|
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Компания не найдена")
|
|
||||||
return company
|
|
||||||
except InvalidTokenError:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Недействительный токен",
|
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
|
||||||
)
|
|
||||||
except jwt.ExpiredSignatureError:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Срок действия токена истек",
|
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_tg_agent_by_tg_id(db: Session, tg_id: int) -> Optional[TgAgent]:
|
def get_tg_agent_by_tg_id(db: Session, tg_id: int) -> Optional[TgAgent]:
|
||||||
statement = select(TgAgent).where(TgAgent.tg_id == tg_id)
|
statement = select(TgAgent).where(TgAgent.tg_id == tg_id)
|
||||||
return db.exec(statement).first()
|
return db.exec(statement).first()
|
||||||
|
|||||||
@ -1,256 +1,75 @@
|
|||||||
from fastapi import FastAPI, Depends, HTTPException, status, Header, Body
|
from fastapi import FastAPI, HTTPException, status, Depends, Request
|
||||||
from sqlmodel import Session, select, Field
|
from sqlmodel import Session, select
|
||||||
from typing import Optional, List, Dict
|
from typing import Optional
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import hashlib
|
import jwt
|
||||||
import uuid
|
from jwt.exceptions import InvalidTokenError
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from sql_models import Company, IntegrationToken, Ref, Sale, AgentTransaction, PartnerTransaction, AgentBalance, TgAgent, CompanyBalance
|
from sql_models import Sale
|
||||||
from integration_models import Token, SaleCreateRequest, SaleCreateResponse, TransactionStatus
|
from helpers_bff import get_db, AUTH_DB_ENGINE # Assuming these are the correct imports
|
||||||
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()
|
app = FastAPI()
|
||||||
|
|
||||||
#7c06945b-f4b8-4929-8350-b9841405a609
|
# Конфигурация для интеграционного API токена
|
||||||
|
INTEGRATION_SECRET_KEY = "your-integration-super-secret-key" # Смените это на безопасный ключ!
|
||||||
|
INTEGRATION_ALGORITHM = "HS256"
|
||||||
|
INTEGRATION_TOKEN_EXPIRE_MINUTES = 60 * 24 # 24 часа
|
||||||
|
|
||||||
|
class IntegrationTokenData(BaseModel):
|
||||||
|
client_id: str
|
||||||
|
|
||||||
@app.post("/token", tags=["integration"], response_model=Token)
|
class SaleCreate(BaseModel):
|
||||||
async def get_token_for_api_key(
|
cost: float
|
||||||
x_api_key: str = Header(..., alias="X-API-Key"),
|
crediting: float
|
||||||
db: Session = Depends(get_integration_db)
|
ref_id: int
|
||||||
):
|
sale_id: str
|
||||||
"""
|
company_id: int
|
||||||
Обменивает API-ключ на JWT токен.
|
sale_date: datetime = datetime.utcnow()
|
||||||
"""
|
|
||||||
|
|
||||||
if not x_api_key:
|
def create_integration_access_token(data: dict, expires_delta: timedelta = None):
|
||||||
raise HTTPException(
|
to_encode = data.copy()
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
if expires_delta:
|
||||||
detail="API-ключ не предоставлен",
|
expire = datetime.utcnow() + expires_delta
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
else:
|
||||||
)
|
expire = datetime.utcnow() + timedelta(minutes=INTEGRATION_TOKEN_EXPIRE_MINUTES)
|
||||||
|
to_encode.update({"exp": expire})
|
||||||
|
encoded_jwt = jwt.encode(to_encode, INTEGRATION_SECRET_KEY, algorithm=INTEGRATION_ALGORITHM)
|
||||||
|
return encoded_jwt
|
||||||
|
|
||||||
api_key_hash = hashlib.sha256(x_api_key.encode()).hexdigest()
|
async def verify_integration_token(request: Request):
|
||||||
integration_token_db = db.exec(
|
credentials_exception = HTTPException(
|
||||||
select(IntegrationToken).where(IntegrationToken.token_hash == api_key_hash)
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
).first()
|
detail="Could not validate integration credentials",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
auth_header = request.headers.get("Authorization")
|
||||||
|
if not auth_header or not auth_header.startswith("Bearer "):
|
||||||
|
raise credentials_exception
|
||||||
|
token = auth_header.replace("Bearer ", "").strip()
|
||||||
|
try:
|
||||||
|
payload = jwt.decode(token, INTEGRATION_SECRET_KEY, algorithms=[INTEGRATION_ALGORITHM])
|
||||||
|
client_id: str = payload.get("client_id")
|
||||||
|
if client_id is None:
|
||||||
|
raise credentials_exception
|
||||||
|
# Здесь вы можете добавить логику для проверки client_id, например, из базы данных
|
||||||
|
except InvalidTokenError:
|
||||||
|
raise credentials_exception
|
||||||
|
return True # Токен действителен
|
||||||
|
|
||||||
if not integration_token_db:
|
@app.post("/sale", status_code=status.HTTP_201_CREATED)
|
||||||
raise HTTPException(
|
async def upload_sale(sale_data: SaleCreate, db: Session = Depends(get_db), verified: bool = Depends(verify_integration_token)):
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
if not verified:
|
||||||
detail="Неверный API-ключ",
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authorized")
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Обновляем use_dttm токена
|
db_sale = Sale(cost=sale_data.cost, crediting=sale_data.crediting, ref=sale_data.ref_id, sale_id=sale_data.sale_id, company_id=sale_data.company_id, sale_date=sale_data.sale_date)
|
||||||
integration_token_db.use_dttm = datetime.utcnow()
|
db.add(db_sale)
|
||||||
db.add(integration_token_db)
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(integration_token_db)
|
db.refresh(db_sale)
|
||||||
|
return {"message": "Sale uploaded successfully", "sale_id": db_sale.id}
|
||||||
|
|
||||||
jwt_token = create_integration_jwt_token(integration_token_db.company_id)
|
@app.get("/generate-integration-token")
|
||||||
return {"access_token": jwt_token, "token_type": "bearer"}
|
async def generate_token_endpoint(client_id: str):
|
||||||
|
token_data = {"client_id": client_id}
|
||||||
@app.get("/ref", response_model=List[RefResponse], tags=["partner-tg"])
|
token = create_integration_access_token(token_data)
|
||||||
def get_refs(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_integration_db)):
|
return {"access_token": token, "token_type": "bearer"}
|
||||||
"""
|
|
||||||
Возвращает список реферальных ссылок текущего 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"}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
from datetime import datetime
|
|
||||||
from pydantic import BaseModel
|
|
||||||
import uuid
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
# Models for /token endpoint
|
|
||||||
class Token(BaseModel):
|
|
||||||
access_token: str
|
|
||||||
token_type: str
|
|
||||||
|
|
||||||
class IntegrationTokenResponse(BaseModel):
|
|
||||||
msg: str
|
|
||||||
company_name: str
|
|
||||||
company_key: str
|
|
||||||
|
|
||||||
# Models for /sale endpoint
|
|
||||||
class SaleCreateRequest(BaseModel):
|
|
||||||
ref: str
|
|
||||||
sale_id: str
|
|
||||||
cost: float
|
|
||||||
|
|
||||||
class SaleCreateResponse(BaseModel):
|
|
||||||
msg: str
|
|
||||||
sale_id: str
|
|
||||||
crediting: float
|
|
||||||
|
|
||||||
class TransactionStatus(str, Enum):
|
|
||||||
NEW = "new"
|
|
||||||
PROCESS = "process"
|
|
||||||
WAITING = "waiting"
|
|
||||||
DONE = "done"
|
|
||||||
CANCELED = "canceled"
|
|
||||||
ERROR = "error"
|
|
||||||
123
main.py
123
main.py
@ -1,3 +1,4 @@
|
|||||||
|
import uuid
|
||||||
from fastapi import (
|
from fastapi import (
|
||||||
FastAPI,
|
FastAPI,
|
||||||
Depends,
|
Depends,
|
||||||
@ -12,6 +13,7 @@ from typing import Optional, List, Dict
|
|||||||
from datetime import timedelta, datetime
|
from datetime import timedelta, datetime
|
||||||
from bff_models import (
|
from bff_models import (
|
||||||
Token,
|
Token,
|
||||||
|
RegisterResponse,
|
||||||
DashboardCardsResponse,
|
DashboardCardsResponse,
|
||||||
DashboardChartTotalResponse,
|
DashboardChartTotalResponse,
|
||||||
DashboardChartAgentResponse,
|
DashboardChartAgentResponse,
|
||||||
@ -28,6 +30,7 @@ from bff_models import (
|
|||||||
AutoApproveSettingsGetResponse,
|
AutoApproveSettingsGetResponse,
|
||||||
AutoApproveSettingsUpdateResponse,
|
AutoApproveSettingsUpdateResponse,
|
||||||
ApproveTransactionsResult,
|
ApproveTransactionsResult,
|
||||||
|
TgAuthResponse,
|
||||||
BillingPayoutsTransactionsResponse,
|
BillingPayoutsTransactionsResponse,
|
||||||
AccountProfileUpdateRequest,
|
AccountProfileUpdateRequest,
|
||||||
AccountPasswordChangeRequest,
|
AccountPasswordChangeRequest,
|
||||||
@ -39,6 +42,7 @@ from bff_models import (
|
|||||||
IntegrationTokenCreateRequest,
|
IntegrationTokenCreateRequest,
|
||||||
IntegrationTokenUpdateRequest
|
IntegrationTokenUpdateRequest
|
||||||
)
|
)
|
||||||
|
from tg_models import RefAddRequest, RefResponse, RegisterRequest, RefAddResponse, RefStatResponse, StatResponse
|
||||||
from sql_models import (
|
from sql_models import (
|
||||||
Company,
|
Company,
|
||||||
TgAgent,
|
TgAgent,
|
||||||
@ -55,7 +59,9 @@ import hashlib
|
|||||||
from helpers_bff import (
|
from helpers_bff import (
|
||||||
AUTH_DB_ENGINE,
|
AUTH_DB_ENGINE,
|
||||||
get_db,
|
get_db,
|
||||||
|
get_tg_agent_by_tg_id,
|
||||||
get_current_account,
|
get_current_account,
|
||||||
|
get_current_tg_agent,
|
||||||
create_access_token,
|
create_access_token,
|
||||||
verify_password,
|
verify_password,
|
||||||
get_account_by_login,
|
get_account_by_login,
|
||||||
@ -71,6 +77,41 @@ SQLModel.metadata.create_all(AUTH_DB_ENGINE)
|
|||||||
app = FastAPI()
|
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"])
|
@app.post("/token", response_model=Token, tags=["bff", "token"])
|
||||||
def login_account_for_access_token(
|
def login_account_for_access_token(
|
||||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||||
@ -95,6 +136,72 @@ def login_account_for_access_token(
|
|||||||
return Token(access_token=access_token, token_type="bearer")
|
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)
|
@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)):
|
def get_dashboard_cards(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
|
||||||
"""
|
"""
|
||||||
@ -397,6 +504,19 @@ def get_billing_chart_pie(current_account: Account = Depends(get_current_account
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@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)
|
@app.get("/account", tags=["bff", "account"], response_model=AccountResponse)
|
||||||
def get_account(current_account: Account = Depends(get_current_account)):
|
def get_account(current_account: Account = Depends(get_current_account)):
|
||||||
"""
|
"""
|
||||||
@ -424,8 +544,7 @@ def get_account_profile(current_account: Account = Depends(get_current_account),
|
|||||||
"company": {
|
"company": {
|
||||||
"name": company.name,
|
"name": company.name,
|
||||||
"key": company.key,
|
"key": company.key,
|
||||||
"commission": company.commission,
|
"commission": company.commission
|
||||||
"agent_commission": company.agent_commission
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,54 +1,146 @@
|
|||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile pyproject.toml -o requirements.txt
|
||||||
annotated-types==0.7.0
|
annotated-types==0.7.0
|
||||||
|
# via pydantic
|
||||||
anyio==4.8.0
|
anyio==4.8.0
|
||||||
|
# via
|
||||||
|
# httpx
|
||||||
|
# starlette
|
||||||
|
# watchfiles
|
||||||
bcrypt==4.2.1
|
bcrypt==4.2.1
|
||||||
|
# via passlib
|
||||||
behave==1.2.6
|
behave==1.2.6
|
||||||
|
# via epai-auth (pyproject.toml)
|
||||||
certifi==2024.12.14
|
certifi==2024.12.14
|
||||||
|
# via
|
||||||
|
# httpcore
|
||||||
|
# httpx
|
||||||
|
# requests
|
||||||
cffi==1.17.1
|
cffi==1.17.1
|
||||||
|
# via cryptography
|
||||||
charset-normalizer==3.4.2
|
charset-normalizer==3.4.2
|
||||||
|
# via requests
|
||||||
click==8.1.8
|
click==8.1.8
|
||||||
colorama==0.4.6
|
# via
|
||||||
|
# rich-toolkit
|
||||||
|
# typer
|
||||||
|
# uvicorn
|
||||||
cryptography==44.0.0
|
cryptography==44.0.0
|
||||||
|
# via epai-auth (pyproject.toml)
|
||||||
dnspython==2.7.0
|
dnspython==2.7.0
|
||||||
|
# via email-validator
|
||||||
email-validator==2.2.0
|
email-validator==2.2.0
|
||||||
|
# via fastapi
|
||||||
fastapi==0.115.6
|
fastapi==0.115.6
|
||||||
|
# via epai-auth (pyproject.toml)
|
||||||
fastapi-cli==0.0.7
|
fastapi-cli==0.0.7
|
||||||
|
# via fastapi
|
||||||
greenlet==3.1.1
|
greenlet==3.1.1
|
||||||
|
# via sqlalchemy
|
||||||
h11==0.14.0
|
h11==0.14.0
|
||||||
|
# via
|
||||||
|
# httpcore
|
||||||
|
# uvicorn
|
||||||
httpcore==1.0.7
|
httpcore==1.0.7
|
||||||
|
# via httpx
|
||||||
httptools==0.6.4
|
httptools==0.6.4
|
||||||
|
# via uvicorn
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
|
# via fastapi
|
||||||
idna==3.10
|
idna==3.10
|
||||||
|
# via
|
||||||
|
# anyio
|
||||||
|
# email-validator
|
||||||
|
# httpx
|
||||||
|
# requests
|
||||||
jinja2==3.1.5
|
jinja2==3.1.5
|
||||||
|
# via fastapi
|
||||||
markdown-it-py==3.0.0
|
markdown-it-py==3.0.0
|
||||||
|
# via rich
|
||||||
markupsafe==3.0.2
|
markupsafe==3.0.2
|
||||||
|
# via jinja2
|
||||||
mdurl==0.1.2
|
mdurl==0.1.2
|
||||||
|
# via markdown-it-py
|
||||||
parse==1.20.2
|
parse==1.20.2
|
||||||
|
# via
|
||||||
|
# behave
|
||||||
|
# parse-type
|
||||||
parse-type==0.6.4
|
parse-type==0.6.4
|
||||||
|
# via behave
|
||||||
passlib==1.7.4
|
passlib==1.7.4
|
||||||
|
# via epai-auth (pyproject.toml)
|
||||||
psycopg2==2.9.10
|
psycopg2==2.9.10
|
||||||
|
# via epai-auth (pyproject.toml)
|
||||||
pycparser==2.22
|
pycparser==2.22
|
||||||
|
# via cffi
|
||||||
pydantic==2.10.5
|
pydantic==2.10.5
|
||||||
|
# via
|
||||||
|
# fastapi
|
||||||
|
# pydantic-settings
|
||||||
|
# sqlmodel
|
||||||
pydantic-core==2.27.2
|
pydantic-core==2.27.2
|
||||||
|
# via pydantic
|
||||||
pydantic-settings==2.9.1
|
pydantic-settings==2.9.1
|
||||||
|
# via epai-auth (pyproject.toml)
|
||||||
pygments==2.19.1
|
pygments==2.19.1
|
||||||
|
# via rich
|
||||||
pyjwt==2.10.1
|
pyjwt==2.10.1
|
||||||
|
# via epai-auth (pyproject.toml)
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
|
# via
|
||||||
|
# pydantic-settings
|
||||||
|
# uvicorn
|
||||||
python-multipart==0.0.20
|
python-multipart==0.0.20
|
||||||
|
# via fastapi
|
||||||
pyyaml==6.0.2
|
pyyaml==6.0.2
|
||||||
|
# via uvicorn
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
|
# via epai-auth (pyproject.toml)
|
||||||
rich==13.9.4
|
rich==13.9.4
|
||||||
|
# via
|
||||||
|
# rich-toolkit
|
||||||
|
# typer
|
||||||
rich-toolkit==0.12.0
|
rich-toolkit==0.12.0
|
||||||
|
# via fastapi-cli
|
||||||
ruff==0.9.1
|
ruff==0.9.1
|
||||||
|
# via epai-auth (pyproject.toml)
|
||||||
shellingham==1.5.4
|
shellingham==1.5.4
|
||||||
|
# via typer
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
|
# via
|
||||||
|
# behave
|
||||||
|
# parse-type
|
||||||
sniffio==1.3.1
|
sniffio==1.3.1
|
||||||
|
# via anyio
|
||||||
sqlalchemy==2.0.37
|
sqlalchemy==2.0.37
|
||||||
|
# via sqlmodel
|
||||||
sqlmodel==0.0.22
|
sqlmodel==0.0.22
|
||||||
|
# via epai-auth (pyproject.toml)
|
||||||
starlette==0.41.3
|
starlette==0.41.3
|
||||||
|
# via fastapi
|
||||||
typer==0.15.1
|
typer==0.15.1
|
||||||
|
# via fastapi-cli
|
||||||
typing-extensions==4.12.2
|
typing-extensions==4.12.2
|
||||||
|
# via
|
||||||
|
# anyio
|
||||||
|
# fastapi
|
||||||
|
# pydantic
|
||||||
|
# pydantic-core
|
||||||
|
# rich-toolkit
|
||||||
|
# sqlalchemy
|
||||||
|
# typer
|
||||||
|
# typing-inspection
|
||||||
typing-inspection==0.4.0
|
typing-inspection==0.4.0
|
||||||
|
# via pydantic-settings
|
||||||
urllib3==2.4.0
|
urllib3==2.4.0
|
||||||
|
# via requests
|
||||||
uvicorn==0.34.0
|
uvicorn==0.34.0
|
||||||
|
# via
|
||||||
|
# fastapi
|
||||||
|
# fastapi-cli
|
||||||
|
#uvloop==0.21.0
|
||||||
|
# via uvicorn
|
||||||
watchfiles==1.0.4
|
watchfiles==1.0.4
|
||||||
|
# via uvicorn
|
||||||
websockets==14.1
|
websockets==14.1
|
||||||
|
# via uvicorn
|
||||||
|
|||||||
@ -7,12 +7,11 @@ from sqlalchemy import Column, String
|
|||||||
class Company(SQLModel, table=True):
|
class Company(SQLModel, table=True):
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
name: str
|
name: str
|
||||||
commission: float # процент комиссии, который взымается за пользование сервисом
|
commission: float # процент комиссии
|
||||||
agent_commission: float = Field(default=0.0) # процент от продаж, который начисляется агенту
|
|
||||||
key: str = Field(index=True, unique=True)
|
key: str = Field(index=True, unique=True)
|
||||||
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
auto_approve_transactions: bool = Field(default=False) # Отвечает за автоматическое одобрение агентских транзакций на вывод.
|
auto_approve_transactions: bool = Field(default=False)
|
||||||
|
|
||||||
integration_tokens: List["IntegrationToken"] = Relationship(back_populates="company")
|
integration_tokens: List["IntegrationToken"] = Relationship(back_populates="company")
|
||||||
|
|
||||||
@ -41,7 +40,7 @@ class Sale(SQLModel, table=True):
|
|||||||
cost: float
|
cost: float
|
||||||
crediting: float # сколько начислено за продажу
|
crediting: float # сколько начислено за продажу
|
||||||
ref: int = Field(foreign_key="ref.id")
|
ref: int = Field(foreign_key="ref.id")
|
||||||
sale_id: str
|
sale_id: str
|
||||||
company_id: int = Field(foreign_key="company.id")
|
company_id: int = Field(foreign_key="company.id")
|
||||||
sale_dttm: datetime = Field(default_factory=datetime.utcnow)
|
sale_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
@ -53,7 +52,7 @@ class AgentTransaction(SQLModel, table=True):
|
|||||||
tg_agent_id: int = Field(foreign_key="tgagent.id")
|
tg_agent_id: int = Field(foreign_key="tgagent.id")
|
||||||
amount: float
|
amount: float
|
||||||
status: str
|
status: str
|
||||||
transaction_group: uuid.UUID = Field(default_factory=uuid.uuid4, unique=True)
|
transaction_group: uuid.UUID = Field(default_factory=uuid.uuid4)
|
||||||
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
|
||||||
@ -64,7 +63,7 @@ class PartnerTransaction(SQLModel, table=True):
|
|||||||
type: str
|
type: str
|
||||||
amount: float
|
amount: float
|
||||||
status: str
|
status: str
|
||||||
transaction_group: uuid.UUID
|
transaction_group: uuid.UUID
|
||||||
agent_transaction_id: Optional[int] = Field(default=None, foreign_key="agent_transactions.id")
|
agent_transaction_id: Optional[int] = Field(default=None, foreign_key="agent_transactions.id")
|
||||||
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user