Compare commits

..

8 Commits

Author SHA1 Message Date
Redsandyg
2c8cbe753e Добавлен новый файл call_validation_promo_api.py для валидации промокодов через API. Реализована логика получения JWT токена и проверки валидности промокода. Обновлены модели в integration_models.py для поддержки запросов и ответов на валидацию промокодов. Добавлен новый эндпоинт /validationPromo в integration_api.py для обработки запросов на валидацию промокодов. 2025-06-22 14:39:14 +03:00
Redsandyg
a6308582cb Удалено поле agent_commission из модели CompanyProfileResponse и соответствующих мест в коде. Обновлены значения комиссии в функции fill_db для создания компании. Изменены теги в эндпоинтах API, убраны лишние теги "bff" для упрощения структуры. Улучшена читаемость кода. 2025-06-21 15:02:21 +03:00
Redsandyg
9157570e58 Обновлены модели и функции для поддержки групповых продаж. Внесены изменения в API для передачи идентификаторов групповых продаж. Обновлены SQL-скрипты и модели для учета нового поля group_sale_id. Изменены данные для создания продажи, включая категорию и стоимость. 2025-06-18 11:31:32 +03:00
Redsandyg
bf6a6a8987 Добавлены новые модели SaleCategoryRequest и SaleCategoryResponse в bff_models.py для работы с категориями продаж. Обновлены функции в fill_db.py для заполнения базы данных категориями продаж. Изменены эндпоинты в main.py для создания и получения категорий продаж. Обновлены модели и SQL-скрипты для поддержки новых полей и связей. Улучшена логика обработки продаж с учетом категорий. 2025-06-18 10:48:29 +03:00
Redsandyg
4f366680bf Обновлен эндпоинт создания продажи в integration_api.py: добавлена проверка на наличие ref и promocode, улучшена логика поиска реферальных ссылок и проверка их принадлежности компании. Модель SaleCreateRequest обновлена для поддержки опционального поля promocode. 2025-06-16 11:11:41 +03:00
Redsandyg
d113ae4adb Добавлено новое поле promocode в модель StatReferralsItem и модель Ref. Обновлены функции в fill_db.py для генерации уникальных промокодов при создании реферальных ссылок. Обновлены эндпоинты в integration_api.py для возврата промокодов вместе с реферальными ссылками. Удалены устаревшие функции работы с промокодами из кода. Обновлены SQL-скрипты и модели для учета новых полей. 2025-06-15 17:03:41 +03:00
Redsandyg
92df59ad23 Добавлены новые функции для работы с промокодами в integration_api.py, включая создание и получение промокодов для Telegram-агентов. Обновлены модели и добавлен новый файл generate_sql.py для генерации SQL-скрипта создания таблиц. Обновлены fill_db.py для генерации промокодов при заполнении базы данных. Также обновлены sql_models.py для добавления модели PromoCode и соответствующих связей. Улучшена структура кода и добавлены отладочные сообщения. 2025-06-15 15:20:28 +03:00
Redsandyg
3973d6404d Добавлен новый эндпоинт для запроса на вывод средств для Telegram-агентов в integration_api.py. Обновлены теги для существующих эндпоинтов, изменив их с "partner-tg" на "agent-tg". В integration_models.py добавлены модели WithdrawRequest и WithdrawResponse для обработки запросов на вывод средств. Улучшена логика обработки транзакций и проверок баланса. 2025-06-13 14:12:58 +03:00
11 changed files with 606 additions and 79 deletions

View File

@@ -89,6 +89,7 @@ class StatReferralsItem(BaseModel):
ref: str
agent: Optional[str] = None
description: str
promocode: str
salesSum: float
salesCount: int
@@ -144,7 +145,6 @@ class CompanyProfileResponse(BaseModel):
name: str
key: str
commission: float
agent_commission: float
class AccountProfileResponse(BaseModel):
firstName: Optional[str] = None
@@ -188,3 +188,20 @@ class IntegrationTokenCreateRequest(BaseModel):
class IntegrationTokenUpdateRequest(BaseModel):
id: int
description: str
# New models for sale categories
class SaleCategoryRequest(BaseModel):
id: int | None = None
category: str
description: str | None = None
perc: float
class SaleCategoryResponse(BaseModel):
id: int
category: str
description: str | None = None
perc: float
create_dttm: datetime
update_dttm: datetime
model_config = ConfigDict(from_attributes=True)

View File

@@ -4,15 +4,17 @@ import json
# Конфигурация API
BASE_URL = "http://127.0.0.1:8001"
API_KEY = "672a1437-70e8-461f-9bff-20f5ce4a023d"
REF = "9bd1a6bd-98e1-48f4-a120-3b3d016011c0"
API_KEY = "de058226-37d3-4d0e-a483-3c2a7fac3573"
REF = "0d9aaa96-80e6-424c-84c9-ff70a6eb915e"
# Данные для запроса на создание продажи
# Замените эти значения на актуальные для вашей продажи
sale_data = {
"cost": 100.50, # Стоимость продажи
"cost": 100, # Стоимость продажи
"ref": REF, # Ваш реферальный код
"sale_id": str(uuid.uuid4()) # Уникальный идентификатор продажи для вашей компании
"sale_id": str(uuid.uuid4()), # Уникальный идентификатор продажи для вашей компании
"category": "vip", # название категории (например, 'basic', 'premium', 'vip')
"group_sale_id": str(uuid.uuid4()) # уникальный идентификатор группы продаж
}
# Эндпоинты

View File

@@ -0,0 +1,63 @@
import requests
import json
# Конфигурация API
BASE_URL = "http://127.0.0.1:8001"
API_KEY = "9efb2df0-03d7-4212-93db-0ae6418667e1" # API-ключ
PROMOCODE = "brw0OMOz"
# Данные для запроса на валидацию промокода
validation_data = {
"promocode": PROMOCODE,
}
# Эндпоинты
token_endpoint = f"{BASE_URL}/token"
validation_endpoint = f"{BASE_URL}/validationPromo"
# Шаг 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:
try:
print("Тело ответа с ошибкой:", e.response.json())
except json.JSONDecodeError:
print("Тело ответа с ошибкой (не JSON):", e.response.text)
exit() # Прерываем выполнение, если не удалось получить токен
# Шаг 2: Вызов эндпоинта /validationPromo с использованием полученного JWT токена
headers_with_jwt = {
"Authorization": f"Bearer {jwt_token}",
"Content-Type": "application/json"
}
print(f"Отправка запроса на {validation_endpoint} с данными: {validation_data}")
try:
validation_response = requests.post(validation_endpoint, headers=headers_with_jwt, data=json.dumps(validation_data))
validation_response.raise_for_status() # Вызовет исключение для ошибок HTTP (4xx или 5xx)
print("Статус ответа:", validation_response.status_code)
print("Тело ответа:", validation_response.json())
except requests.exceptions.RequestException as e:
print(f"Произошла ошибка при вызове API validationPromo: {e}")
if hasattr(e, 'response') and e.response is not None:
try:
print("Тело ответа с ошибкой:", e.response.json())
except json.JSONDecodeError:
print("Тело ответа с ошибкой (не JSON):", e.response.text)

View File

@@ -1,11 +1,12 @@
import random
from uuid import uuid4
from sqlmodel import Session
from sql_models import TgAgent, Ref, Sale, Account, Company, AgentTransaction, PartnerTransaction, CompanyBalance, AgentBalance, IntegrationToken
from sql_models import TgAgent, Ref, Sale, Account, Company, AgentTransaction, PartnerTransaction, CompanyBalance, AgentBalance, IntegrationToken, SaleCategory
from sqlalchemy import text
from datetime import datetime, timedelta
from hashlib import sha256
from helpers_bff import AUTH_DB_ENGINE, get_password_hash
import string
# Константа: список user_ids
@@ -71,6 +72,7 @@ def fill_db():
date_list = get_date_list(7) # 8 дней: от недели назад до сегодня
with Session(AUTH_DB_ENGINE) as session:
# Очистка таблиц
# session.execute(text("DELETE FROM promocode"))
session.execute(text("DELETE FROM sale"))
session.execute(text("DELETE FROM ref"))
session.execute(text("DELETE FROM tgagent"))
@@ -79,13 +81,13 @@ def fill_db():
session.execute(text('DELETE FROM "partner_transactions"'))
session.execute(text('DELETE FROM "company_balances"'))
session.execute(text('DELETE FROM "agent_balances"'))
session.execute(text("DELETE FROM salecategory"))
session.execute(text("DELETE FROM company"))
session.commit()
# 0. Company
company = Company(
name="RE: Premium",
commission=10.0,
agent_commission=15.0,
commission=0.0,
key="re-premium-key",
)
session.add(company)
@@ -108,6 +110,18 @@ def fill_db():
session.add(integration_token)
session.commit()
# 0.2 SaleCategory
sale_categories = [
SaleCategory(category="basic", description="Базовая продажа", perc=10.0, company_id=company.id),
SaleCategory(category="premium", description="Премиум продажа", perc=20.0, company_id=company.id),
SaleCategory(category="vip", description="VIP продажа", perc=30.0, company_id=company.id),
]
for cat in sale_categories:
session.add(cat)
session.commit()
for cat in sale_categories:
session.refresh(cat)
# 1. Accounts
accounts = []
for i in range(4):
@@ -149,19 +163,28 @@ def fill_db():
session.commit()
for tg_agent in tg_agents:
session.refresh(tg_agent)
# Отладка: количество агентов
agent_count = session.execute(text("SELECT COUNT(*) FROM tgagent")).scalar()
print(f'Агентов в базе: {agent_count}')
# 3. Refs (минимум 22 на агента)
refs = []
desc_count = len(ALL_DESCRIPTIONS)
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
for tg_agent in tg_agents:
ref_count = random.randint(22, int(22 * 1.25)) # от 22 до 27
for j in range(ref_count):
ref_val = str(uuid4())
desc_val = ALL_DESCRIPTIONS[(j % desc_count)]
dt = random.choice(date_list)
promocode = ''.join(random.choices(alphabet, k=8))
# Проверяем уникальность промокода среди уже созданных рефов
while any(r.promocode == promocode for r in refs):
promocode = ''.join(random.choices(alphabet, k=8))
ref = Ref(
tg_agent_id=tg_agent.id,
ref=ref_val,
description=desc_val,
promocode=promocode,
create_dttm=dt,
update_dttm=dt
)
@@ -171,25 +194,30 @@ def fill_db():
for ref in refs:
session.refresh(ref)
# 4. Sales (минимум 20 на каждый ref)
all_categories = session.query(SaleCategory).filter_by(company_id=company.id).all()
for ref in refs:
sale_count = random.randint(20, int(20 * 1.25)) # от 20 до 25
for _ in range(sale_count):
group_size = 5
group_sale_ids = [str(uuid4()) for _ in range((sale_count // group_size) + 1)]
for idx in range(sale_count):
cost = round(random.uniform(100, 1000), 2)
crediting = round(cost * (company.agent_commission / 100.0), 2)
sale_category = random.choice(all_categories)
crediting = round(cost * (sale_category.perc / 100.0), 2)
# Генерируем случайную дату и время в пределах последних 7 дней
end_dttm = datetime.utcnow()
start_dttm = end_dttm - timedelta(days=7)
time_diff = end_dttm - start_dttm
random_seconds = random.uniform(0, time_diff.total_seconds())
sale_dttm = start_dttm + timedelta(seconds=random_seconds)
group_sale_id = group_sale_ids[idx // group_size]
sale = Sale(
cost=cost,
crediting=crediting,
ref=ref.id,
sale_id=str(uuid4()),
group_sale_id=group_sale_id,
company_id=company.id,
category=sale_category.id,
sale_dttm=sale_dttm,
create_dttm=sale_dttm, # create_dttm также будет случайным в этом диапазоне
update_dttm=sale_dttm # update_dttm также будет случайным в этом диапазоне

22
generate_sql.py Normal file
View File

@@ -0,0 +1,22 @@
from sqlmodel import SQLModel
from helpers_bff import AUTH_DB_ENGINE
from sqlalchemy.schema import CreateTable
import os
# --- Отладочный вывод ---
print("Таблицы в metadata:", SQLModel.metadata.sorted_tables)
print("Все зарегистрированные модели:", SQLModel.__subclasses__())
# --- Генерация и сохранение SQL-скрипта создания таблиц ---
def get_sql_create_script():
script = []
for table in SQLModel.metadata.sorted_tables:
create_table_sql = str(CreateTable(table).compile(dialect=AUTH_DB_ENGINE.dialect))
script.append(f"{create_table_sql};")
return "\n\n".join(script)
sql_script = get_sql_create_script()
sql_file_path = os.path.join(os.path.dirname(__file__), "sql_create.sql")
with open(sql_file_path, "w", encoding="utf-8") as f:
f.write(sql_script)
print("SQL-скрипт успешно сгенерирован!")

View File

@@ -1,12 +1,14 @@
from fastapi import FastAPI, Depends, HTTPException, status, Header, Body
from fastapi import FastAPI, Depends, HTTPException, status, Header, Body, Request
from sqlmodel import Session, select, Field
from typing import Optional, List, Dict
from datetime import datetime, timedelta
import hashlib
import uuid
from random import choices
import string
from sql_models import Company, IntegrationToken, Ref, Sale, AgentTransaction, PartnerTransaction, AgentBalance, TgAgent, CompanyBalance
from integration_models import Token, SaleCreateRequest, SaleCreateResponse, TransactionStatus
from sql_models import Company, IntegrationToken, Ref, Sale, AgentTransaction, PartnerTransaction, AgentBalance, TgAgent, CompanyBalance, SaleCategory
from integration_models import Token, SaleCreateRequest, SaleCreateResponse, TransactionStatus, WithdrawRequest, WithdrawResponse, PromoValidationRequest, PromoValidationResponse
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
@@ -53,30 +55,36 @@ async def get_token_for_api_key(
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"])
@app.get("/ref", response_model=List[RefResponse], tags=["agent-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]
return [RefResponse(ref=r.ref, description=r.description or "", promocode=r.promocode) for r in refs]
@app.post("/ref/add", tags=["partner-tg"], response_model=RefAddResponse)
@app.post("/ref/add", tags=["agent-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-агента.
"""
# Генерация промокода (логика как была для промокодов)
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
promocode = ''.join(choices(alphabet, k=8))
while db.exec(select(Ref).where(Ref.promocode == promocode)).first():
promocode = ''.join(choices(alphabet, k=8))
new_ref = Ref(
tg_agent_id=current_tg_agent.id,
ref=str(uuid.uuid4()),
description=req.description
description=req.description,
promocode=promocode
)
db.add(new_ref)
db.commit()
db.refresh(new_ref)
return {"ref": new_ref.ref}
return {"ref": new_ref.ref, "promocode": new_ref.promocode, "description": new_ref.description}
@app.get("/ref/stat", tags=["partner-tg"], response_model=RefStatResponse)
@app.get("/ref/stat", tags=["agent-tg"], response_model=RefStatResponse)
def get_ref_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_integration_db)):
"""
Возвращает статистику по реферальным ссылкам текущего Telegram-агента.
@@ -96,7 +104,7 @@ def get_ref_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db:
})
return {"refData": result}
@app.get("/stat", tags=["partner-tg"], response_model=StatResponse)
@app.get("/stat", tags=["agent-tg"], response_model=StatResponse)
def get_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_integration_db)):
"""
Возвращает общую статистику для текущего Telegram-агента.
@@ -118,7 +126,7 @@ def get_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Sess
"availableWithdrawal": availableWithdrawal
}
@app.post("/tg_auth", tags=["partner-tg"], response_model=TgAuthResponse)
@app.post("/tg_auth", tags=["agent-tg"], response_model=TgAuthResponse)
def tg_auth(hash: str = Body(..., embed=True), db: Session = Depends(get_integration_db)):
"""
Авторизует Telegram-агента по хешу.
@@ -128,10 +136,60 @@ def tg_auth(hash: str = Body(..., embed=True), db: Session = Depends(get_integra
raise HTTPException(status_code=401, detail="Hash not found")
return {"msg": "Auth success", "tg_id": tg_agent.tg_id}
@app.post("/withdraw", tags=["agent-tg"], response_model=WithdrawResponse)
async def withdraw_funds(
req: WithdrawRequest,
db: Session = Depends(get_integration_db)
):
"""
Запрос на вывод средств для Telegram-агента.
"""
tg_agent = db.exec(select(TgAgent).where(TgAgent.tg_id == req.tg_id)).first()
if not tg_agent:
raise HTTPException(status_code=404, detail="Telegram-агент не найден")
company = db.exec(select(Company).where(Company.id == tg_agent.company_id)).first()
if not company:
raise HTTPException(status_code=404, detail="Компания не найдена для агента")
if req.amount <= 0:
raise HTTPException(status_code=400, detail="Сумма для вывода должна быть положительной")
agent_balance = db.exec(select(AgentBalance).where(AgentBalance.tg_agent_id == tg_agent.id)).first()
if not agent_balance or agent_balance.available_balance < req.amount:
raise HTTPException(status_code=400, detail="Недостаточно средств на балансе для вывода")
# Определяем статус транзакции
transaction_status = TransactionStatus.WAITING
if company.auto_approve_transactions:
transaction_status = TransactionStatus.NEW
# Создаем запись AgentTransaction
new_agent_transaction = AgentTransaction(
tg_agent_id=tg_agent.id,
amount=req.amount,
status=transaction_status.value,
transaction_group=uuid.uuid4()
)
db.add(new_agent_transaction)
# Обновляем баланс агента
agent_balance.available_balance -= req.amount
if transaction_status == TransactionStatus.WAITING: # Если автоматически одобряется, переводим на замороженный баланс компании (т.е. компания должна выплатить)
agent_balance.frozen_balance += req.amount # Удерживаем средства, пока они не будут выведены
db.add(agent_balance)
db.commit()
db.refresh(new_agent_transaction)
db.refresh(agent_balance)
return {"msg": "Запрос на вывод средств успешно создан", "transaction_id": new_agent_transaction.transaction_group}
@app.post("/sale", tags=["integration"], response_model=SaleCreateResponse)
async def create_sale(
req: SaleCreateRequest,
company: Company = Depends(get_current_company_from_jwt), # Используем новую зависимость
company: Company = Depends(get_current_company_from_jwt),
db: Session = Depends(get_integration_db)
):
"""
@@ -140,24 +198,35 @@ async def create_sale(
# Устанавливаем уровень изоляции для текущей транзакции
db.connection(execution_options={'isolation_level': 'SERIALIZABLE'})
# 1. Найти Ref по `ref` и `company.id`
# Сначала находим TgAgent, связанный с компанией, затем Ref
# Проверка входных данных
if not req.ref and not req.promocode:
raise HTTPException(status_code=400, detail="Необходимо передать либо ref, либо promocode")
if not req.category:
raise HTTPException(status_code=400, detail="Необходимо передать category (id категории)")
# 1. Найти Ref по ref и/или promocode
referral = None
if req.ref and req.promocode:
referral_by_ref = db.exec(select(Ref).where(Ref.ref == req.ref)).first()
referral_by_code = db.exec(select(Ref).where(Ref.promocode == req.promocode)).first()
if not referral_by_ref or not referral_by_code:
raise HTTPException(status_code=404, detail="Реферальная ссылка или промокод не найдены")
if referral_by_ref.id != referral_by_code.id:
raise HTTPException(status_code=400, detail="ref и promocode не соответствуют одной ссылке")
referral = referral_by_ref
elif req.ref:
referral = db.exec(select(Ref).where(Ref.ref == req.ref)).first()
if not referral:
raise HTTPException(status_code=404, detail="Реферальная ссылка не найдена")
elif req.promocode:
referral = db.exec(select(Ref).where(Ref.promocode == req.promocode)).first()
if not referral:
raise HTTPException(status_code=404, detail="Промокод не найден")
tg_agent = db.exec(
select(TgAgent)
.join(Ref)
.where(TgAgent.company_id == company.id)
.where(Ref.ref == req.ref)
).first()
# Проверяем, что реф действительно принадлежит компании
tg_agent = db.exec(select(TgAgent).where(TgAgent.id == referral.tg_agent_id, TgAgent.company_id == company.id)).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="Реферальная ссылка не найдена")
raise HTTPException(status_code=404, detail="Реферальная ссылка не принадлежит данной компании")
# 2. Проверить, что sale_id уникален для данной компании
existing_sale = db.exec(
@@ -165,20 +234,19 @@ async def create_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)
# 3. Найти категорию и рассчитать crediting
sale_category = db.exec(select(SaleCategory).where(SaleCategory.category == req.category, SaleCategory.company_id == company.id)).first()
if not sale_category:
raise HTTPException(status_code=404, detail="Категория продажи не найдена")
crediting_amount = req.cost * (sale_category.perc / 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="Баланс компании не найден")
@@ -189,13 +257,14 @@ async def create_sale(
crediting=crediting_amount,
ref=referral.id,
sale_id=req.sale_id,
group_sale_id=req.group_sale_id,
company_id=company.id,
category=sale_category.id,
sale_dttm=datetime.utcnow()
)
db.add(new_sale)
# Создать AgentTransaction
agent_transaction_status = TransactionStatus.DONE # auto_approve_transactions отвечает только за апрув агентских транзакций на вывод
agent_transaction_status = TransactionStatus.DONE
agent_transaction = AgentTransaction(
tg_agent_id=tg_agent.id,
amount=crediting_amount,
@@ -203,7 +272,6 @@ async def create_sale(
transaction_group=uuid.uuid4()
)
db.add(agent_transaction)
# Обновление балансов для продаж - всегда в замороженный/ожидающий баланс
agent_balance.frozen_balance += crediting_amount
company_balance.pending_balance -= crediting_amount
@@ -221,7 +289,26 @@ async def create_sale(
"crediting": new_sale.crediting
}
@app.post("/register", tags=["partner-tg"], response_model=RegisterResponse)
@app.post("/validationPromo", tags=["integration"], response_model=PromoValidationResponse)
async def validate_promocode(
req: PromoValidationRequest,
company: Company = Depends(get_current_company_from_jwt),
db: Session = Depends(get_integration_db)
):
"""
Проверяет валидность промокода для текущей компании.
"""
referral = db.exec(select(Ref).where(Ref.promocode == req.promocode)).first()
if not referral:
return {"validation": False}
tg_agent = db.exec(select(TgAgent).where(TgAgent.id == referral.tg_agent_id, TgAgent.company_id == company.id)).first()
if not tg_agent:
return {"validation": False}
return {"validation": True}
@app.post("/register", tags=["agent-tg"], response_model=RegisterResponse)
def register(req: RegisterRequest, db: Session = Depends(get_integration_db)):
"""
Регистрирует нового Telegram-агента в системе.

View File

@@ -16,15 +16,26 @@ class IntegrationTokenResponse(BaseModel):
# Models for /sale endpoint
class SaleCreateRequest(BaseModel):
ref: str
ref: Optional[str] = None
promocode: Optional[str] = None
sale_id: str
cost: float
category: str # название категории продажи
group_sale_id: str # новое поле для группировки продаж
class SaleCreateResponse(BaseModel):
msg: str
sale_id: str
crediting: float
class WithdrawRequest(BaseModel):
tg_id: int
amount: float
class WithdrawResponse(BaseModel):
msg: str
transaction_id: uuid.UUID
class TransactionStatus(str, Enum):
NEW = "new"
PROCESS = "process"
@@ -32,3 +43,9 @@ class TransactionStatus(str, Enum):
DONE = "done"
CANCELED = "canceled"
ERROR = "error"
class PromoValidationRequest(BaseModel):
promocode: str
class PromoValidationResponse(BaseModel):
validation: bool

117
main.py
View File

@@ -1,3 +1,4 @@
import uuid
from fastapi import (
FastAPI,
Depends,
@@ -37,7 +38,9 @@ from bff_models import (
TransactionStatus,
IntegrationTokenResponse,
IntegrationTokenCreateRequest,
IntegrationTokenUpdateRequest
IntegrationTokenUpdateRequest,
SaleCategoryRequest,
SaleCategoryResponse
)
from sql_models import (
Company,
@@ -48,7 +51,8 @@ from sql_models import (
PartnerTransaction,
AgentBalance,
Account,
IntegrationToken
IntegrationToken,
SaleCategory
)
from sqlalchemy import func
import hashlib
@@ -62,6 +66,8 @@ from helpers_bff import (
ACCESS_TOKEN_EXPIRE_MINUTES,
pwd_context,
)
import os
from pydantic import BaseModel
# Создание движка базы данных
@@ -71,7 +77,9 @@ SQLModel.metadata.create_all(AUTH_DB_ENGINE)
app = FastAPI()
@app.post("/token", response_model=Token, tags=["bff", "token"])
@app.post("/token", response_model=Token, tags=[ "token"])
def login_account_for_access_token(
form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(get_db)
@@ -95,7 +103,7 @@ def login_account_for_access_token(
return Token(access_token=access_token, token_type="bearer")
@app.get("/dashboard/cards", tags=["bff", "dashboard"], response_model=DashboardCardsResponse)
@app.get("/dashboard/cards", tags=[ "dashboard"], response_model=DashboardCardsResponse)
def get_dashboard_cards(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает данные для карточек на главной панели (dashboard) пользователя, включая общий доход, выплаты, активных рефералов, ожидающие выплаты и общее количество продаж.
@@ -126,7 +134,7 @@ def get_dashboard_cards(current_account: Account = Depends(get_current_account),
"totalSales": totalSales
}
@app.get("/dashboard/chart/total", tags=["bff", "dashboard"], response_model=DashboardChartTotalResponse)
@app.get("/dashboard/chart/total", tags=[ "dashboard"], response_model=DashboardChartTotalResponse)
def get_dashboard_chart_total(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает данные для графика "Общий доход и продажи по датам" на главной панели (dashboard).
@@ -147,7 +155,7 @@ def get_dashboard_chart_total(current_account: Account = Depends(get_current_acc
]
return DashboardChartTotalResponse(items=data)
@app.get("/dashboard/chart/agent", tags=["bff", "dashboard"], response_model=DashboardChartAgentResponse)
@app.get("/dashboard/chart/agent", tags=[ "dashboard"], response_model=DashboardChartAgentResponse)
def get_dashboard_chart_agent(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает данные для графика "Продажи по агентам" на главной панели (dashboard).
@@ -177,7 +185,7 @@ def get_dashboard_chart_agent(current_account: Account = Depends(get_current_acc
})
return DashboardChartAgentResponse(items=result)
@app.get("/stat/agents", tags=["bff", "stat"], response_model=StatAgentsResponse)
@app.get("/stat/agents", tags=[ "stat"], response_model=StatAgentsResponse)
def get_agents_stat(
db: Session = Depends(get_db),
date_start: str = Query(None),
@@ -221,7 +229,7 @@ def get_agents_stat(
})
return StatAgentsResponse(items=result)
@app.get("/stat/referrals", tags=["bff", "stat"], response_model=StatReferralsResponse)
@app.get("/stat/referrals", tags=[ "stat"], response_model=StatReferralsResponse)
def get_referrals_stat(
db: Session = Depends(get_db),
date_start: str = Query(None),
@@ -248,12 +256,13 @@ def get_referrals_stat(
"ref": ref.ref,
"agent": agent.name if agent and agent.name else f"Агент {ref.tg_agent_id}",
"description": ref.description or "",
"promocode": ref.promocode,
"salesSum": sales_sum,
"salesCount": sales_count
})
return StatReferralsResponse(items=result)
@app.get("/stat/sales", tags=["bff", "stat"], response_model=StatSalesResponse)
@app.get("/stat/sales", tags=[ "stat"], response_model=StatSalesResponse)
def get_sales_stat(
db: Session = Depends(get_db),
date_start: str = Query(None),
@@ -289,7 +298,7 @@ def get_sales_stat(
})
return StatSalesResponse(items=result)
@app.get("/billing/cards", tags=["bff", "billing"], response_model=BillingCardsResponse)
@app.get("/billing/cards", tags=[ "billing"], response_model=BillingCardsResponse)
def get_billing_cards(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает ключевые показатели биллинга для компании, включая общий заработок, общие выплаты и доступные к выводу средства.
@@ -312,7 +321,7 @@ def get_billing_cards(current_account: Account = Depends(get_current_account), d
"pendingPayouts": pendingPayouts
}
@app.get("/billing/payouts/transactions", tags=["bff", "billing"], response_model=BillingPayoutsTransactionsResponse)
@app.get("/billing/payouts/transactions", tags=[ "billing"], response_model=BillingPayoutsTransactionsResponse)
def get_billing_payouts_transactions(
db: Session = Depends(get_db),
date_start: str = Query(None),
@@ -352,7 +361,7 @@ def get_billing_payouts_transactions(
})
return BillingPayoutsTransactionsResponse(items=result)
@app.get("/billing/chart/stat", tags=["bff", "billing"], response_model=BillingChartStatResponse)
@app.get("/billing/chart/stat", tags=[ "billing"], response_model=BillingChartStatResponse)
def get_billing_chart_stat(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает статистику выплат по датам и статусам для компании текущего пользователя.
@@ -377,7 +386,7 @@ def get_billing_chart_stat(current_account: Account = Depends(get_current_accoun
]
return BillingChartStatResponse(items=data)
@app.get("/billing/chart/pie", tags=["bff", "billing"], response_model=BillingChartPieResponse)
@app.get("/billing/chart/pie", tags=[ "billing"], response_model=BillingChartPieResponse)
def get_billing_chart_pie(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает круговую диаграмму статистики выплат по статусам для компании текущего пользователя.
@@ -397,7 +406,7 @@ def get_billing_chart_pie(current_account: Account = Depends(get_current_account
@app.get("/account", tags=["bff", "account"], response_model=AccountResponse)
@app.get("/account", tags=[ "account"], response_model=AccountResponse)
def get_account(current_account: Account = Depends(get_current_account)):
"""
Возвращает базовую информацию об аккаунте текущего пользователя (имя и фамилию).
@@ -407,7 +416,7 @@ def get_account(current_account: Account = Depends(get_current_account)):
"surname": current_account.surname
}
@app.get("/account/profile", tags=["bff", "account"], response_model=AccountProfileResponse)
@app.get("/account/profile", tags=[ "account"], response_model=AccountProfileResponse)
def get_account_profile(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
"""
Возвращает полную информацию о профиле аккаунта текущего пользователя, включая данные компании.
@@ -425,11 +434,10 @@ def get_account_profile(current_account: Account = Depends(get_current_account),
"name": company.name,
"key": company.key,
"commission": company.commission,
"agent_commission": company.agent_commission
}
}
@app.post("/account/profile", tags=["bff", "account"], response_model=AccountProfileUpdateResponse)
@app.post("/account/profile", tags=[ "account"], response_model=AccountProfileUpdateResponse)
def update_account_profile(
req: AccountProfileUpdateRequest,
current_account: Account = Depends(get_current_account),
@@ -451,7 +459,7 @@ def update_account_profile(
db.refresh(current_account)
return {"msg": "Профиль обновлён успешно"}
@app.post("/account/password", tags=["bff", "account"], response_model=AccountPasswordChangeResponse)
@app.post("/account/password", tags=[ "account"], response_model=AccountPasswordChangeResponse)
def change_account_password(
req: AccountPasswordChangeRequest,
current_account: Account = Depends(get_current_account),
@@ -477,7 +485,7 @@ def change_account_password(
# --- Новый функционал для агентских транзакций партнера ---
@app.get("/account/agent-transaction", response_model=List[AgentTransactionResponse], tags=["bff", "account"])
@app.get("/account/agent-transaction", response_model=List[AgentTransactionResponse], tags=[ "account"])
def get_account_agent_transactions(
statuses: Optional[List[TransactionStatus]] = Query(None),
date_start: str = Query(None),
@@ -534,7 +542,7 @@ def get_account_agent_transactions(
@app.get("/account/auto-approve", tags=["bff", "account"], response_model=AutoApproveSettingsGetResponse)
@app.get("/account/auto-approve", tags=[ "account"], response_model=AutoApproveSettingsGetResponse)
def get_auto_approve_settings(
current_account: Account = Depends(get_current_account),
db: Session = Depends(get_db)
@@ -547,7 +555,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"], response_model=AutoApproveSettingsUpdateResponse)
@app.post("/account/auto-approve", tags=[ "account"], response_model=AutoApproveSettingsUpdateResponse)
def update_auto_approve_settings(
req: AutoApproveSettingsRequest,
current_account: Account = Depends(get_current_account),
@@ -597,7 +605,7 @@ def update_auto_approve_settings(
@app.post("/account/approve-transactions", tags=["bff", "account"], response_model=ApproveTransactionsResult)
@app.post("/account/approve-transactions", tags=[ "account"], response_model=ApproveTransactionsResult)
def approve_agent_transactions(
req: ApproveTransactionsRequest,
current_account: Account = Depends(get_current_account),
@@ -634,7 +642,7 @@ def approve_agent_transactions(
# --- Новый функционал для интеграционных токенов ---
@app.get("/account/integration-tokens", tags=["bff", "account"], response_model=List[IntegrationTokenResponse])
@app.get("/account/integration-tokens", tags=[ "account"], response_model=List[IntegrationTokenResponse])
def get_integration_tokens(
current_account: Account = Depends(get_current_account),
db: Session = Depends(get_db)
@@ -646,7 +654,7 @@ def get_integration_tokens(
return tokens # Позволяем FastAPI самостоятельно сериализовать объекты IntegrationToken в IntegrationTokenResponse
@app.post("/account/integration-tokens", tags=["bff", "account"], response_model=IntegrationTokenResponse)
@app.post("/account/integration-tokens", tags=[ "account"], response_model=IntegrationTokenResponse)
def create_integration_token(
req: IntegrationTokenCreateRequest,
current_account: Account = Depends(get_current_account),
@@ -679,7 +687,7 @@ def create_integration_token(
return response_token
@app.post("/account/integration-tokens/update-description", tags=["bff", "account"], response_model=Dict[str, str])
@app.post("/account/integration-tokens/update-description", tags=[ "account"], response_model=Dict[str, str])
def update_integration_token_description(
req: IntegrationTokenUpdateRequest,
current_account: Account = Depends(get_current_account),
@@ -702,7 +710,7 @@ def update_integration_token_description(
db.refresh(token)
return {"msg": "Описание токена обновлено успешно"}
@app.delete("/account/integration-tokens/{token_id}", tags=["bff", "account"], response_model=Dict[str, str])
@app.delete("/account/integration-tokens/{token_id}", tags=[ "account"], response_model=Dict[str, str])
def delete_integration_token(
token_id: int,
current_account: Account = Depends(get_current_account),
@@ -721,3 +729,60 @@ def delete_integration_token(
db.delete(token)
db.commit()
return {"msg": "Токен удален успешно"}
# --- Категории продаж ---
@app.get("/category", tags=["category"], response_model=List[SaleCategoryResponse])
def get_sale_categories(
current_account: Account = Depends(get_current_account),
db: Session = Depends(get_db)
):
"""
Возвращает список всех категорий продаж компании пользователя.
"""
categories = db.exec(select(SaleCategory).where(SaleCategory.company_id == current_account.company_id)).all()
return categories
@app.post("/category", tags=["category"], response_model=SaleCategoryResponse)
def create_or_update_sale_category(
req: SaleCategoryRequest,
current_account: Account = Depends(get_current_account),
db: Session = Depends(get_db)
):
"""
Создает новую или обновляет существующую категорию продаж для компании пользователя.
"""
# Проверка уникальности category для компании
existing = db.exec(
select(SaleCategory)
.where(SaleCategory.category == req.category)
.where(SaleCategory.company_id == current_account.company_id)
).first()
if req.id is not None:
category = db.exec(select(SaleCategory).where(SaleCategory.id == req.id, SaleCategory.company_id == current_account.company_id)).first()
if not category:
raise HTTPException(status_code=404, detail="Категория не найдена")
# Если меняем имя, оно не должно совпадать с другой категорией
if existing and existing.id != req.id:
raise HTTPException(status_code=400, detail="Категория с таким именем уже существует")
category.category = req.category
category.description = req.description
category.perc = req.perc
category.update_dttm = datetime.utcnow()
db.add(category)
else:
if existing:
raise HTTPException(status_code=400, detail="Категория с таким именем уже существует")
now = datetime.utcnow()
category = SaleCategory(
category=req.category,
description=req.description,
perc=req.perc,
company_id=current_account.company_id,
create_dttm=now,
update_dttm=now
)
db.add(category)
db.commit()
db.refresh(category)
return SaleCategoryResponse.model_validate(category)

179
sql_create.sql Normal file
View File

@@ -0,0 +1,179 @@
CREATE TABLE company (
id INTEGER NOT NULL,
name VARCHAR NOT NULL,
commission FLOAT NOT NULL,
agent_commission FLOAT NOT NULL,
"key" VARCHAR NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
auto_approve_transactions BOOLEAN NOT NULL,
PRIMARY KEY (id)
)
;
CREATE TABLE account (
id INTEGER NOT NULL,
login VARCHAR NOT NULL,
password_hash VARCHAR NOT NULL,
"firstName" VARCHAR,
surname VARCHAR,
phone VARCHAR,
email VARCHAR,
company_id INTEGER NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(company_id) REFERENCES company (id)
)
;
CREATE TABLE company_balances (
id INTEGER NOT NULL,
company_id INTEGER NOT NULL,
available_balance FLOAT NOT NULL,
pending_balance FLOAT NOT NULL,
updated_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE (company_id),
FOREIGN KEY(company_id) REFERENCES company (id)
)
;
CREATE TABLE integrationtoken (
id INTEGER NOT NULL,
description VARCHAR NOT NULL,
token_hash VARCHAR,
masked_token VARCHAR,
company_id INTEGER NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
use_dttm DATETIME,
PRIMARY KEY (id),
FOREIGN KEY(company_id) REFERENCES company (id)
)
;
CREATE TABLE salecategory (
id INTEGER NOT NULL,
category VARCHAR NOT NULL,
description VARCHAR,
perc FLOAT NOT NULL,
company_id INTEGER NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(company_id) REFERENCES company (id)
)
;
CREATE TABLE tgagent (
id INTEGER NOT NULL,
tg_id INTEGER NOT NULL,
chat_id INTEGER,
phone VARCHAR,
name VARCHAR,
login VARCHAR,
hash VARCHAR,
company_id INTEGER NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(company_id) REFERENCES company (id)
)
;
CREATE TABLE agent_balances (
id INTEGER NOT NULL,
tg_agent_id INTEGER NOT NULL,
available_balance FLOAT NOT NULL,
frozen_balance FLOAT NOT NULL,
updated_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE (tg_agent_id),
FOREIGN KEY(tg_agent_id) REFERENCES tgagent (id)
)
;
CREATE TABLE agent_transactions (
id INTEGER NOT NULL,
tg_agent_id INTEGER NOT NULL,
amount FLOAT NOT NULL,
status VARCHAR NOT NULL,
transaction_group CHAR(32) NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(tg_agent_id) REFERENCES tgagent (id),
UNIQUE (transaction_group)
)
;
CREATE TABLE ref (
id INTEGER NOT NULL,
tg_agent_id INTEGER NOT NULL,
ref VARCHAR NOT NULL,
description VARCHAR,
promocode VARCHAR(8) NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(tg_agent_id) REFERENCES tgagent (id)
)
;
CREATE TABLE partner_transactions (
id INTEGER NOT NULL,
company_id INTEGER NOT NULL,
type VARCHAR NOT NULL,
amount FLOAT NOT NULL,
status VARCHAR NOT NULL,
transaction_group CHAR(32) NOT NULL,
agent_transaction_id INTEGER,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(company_id) REFERENCES company (id),
FOREIGN KEY(agent_transaction_id) REFERENCES agent_transactions (id)
)
;
CREATE TABLE sale (
id INTEGER NOT NULL,
cost FLOAT NOT NULL,
crediting FLOAT NOT NULL,
ref INTEGER NOT NULL,
sale_id VARCHAR NOT NULL,
group_sale_id VARCHAR NOT NULL,
company_id INTEGER NOT NULL,
category INTEGER NOT NULL,
sale_dttm DATETIME NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(ref) REFERENCES ref (id),
FOREIGN KEY(company_id) REFERENCES company (id),
FOREIGN KEY(category) REFERENCES salecategory (id)
)
;

View File

@@ -8,13 +8,18 @@ class Company(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
commission: float # процент комиссии, который взымается за пользование сервисом
agent_commission: float = Field(default=0.0) # процент от продаж, который начисляется агенту
key: str = Field(index=True, unique=True)
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
auto_approve_transactions: bool = Field(default=False) # Отвечает за автоматическое одобрение агентских транзакций на вывод.
integration_tokens: List["IntegrationToken"] = Relationship(back_populates="company")
tg_agents: List["TgAgent"] = Relationship(back_populates="company")
sales: List["Sale"] = Relationship(back_populates="company")
partner_transactions: List["PartnerTransaction"] = Relationship(back_populates="company")
company_balance: Optional["CompanyBalance"] = Relationship(back_populates="company")
accounts: List["Account"] = Relationship(back_populates="company")
sale_categories: List["SaleCategory"] = Relationship(back_populates="company")
class TgAgent(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
@@ -28,25 +33,52 @@ class TgAgent(SQLModel, table=True):
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
company: "Company" = Relationship(back_populates="tg_agents")
refs: List["Ref"] = Relationship(back_populates="tg_agent")
agent_transactions: List["AgentTransaction"] = Relationship(back_populates="tg_agent")
agent_balance: Optional["AgentBalance"] = Relationship(back_populates="tg_agent")
class Ref(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
tg_agent_id: int = Field(foreign_key="tgagent.id")
ref: str
description: Optional[str] = None
promocode: str = Field(index=True, unique=True, max_length=8)
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
tg_agent: "TgAgent" = Relationship(back_populates="refs")
sales: List["Sale"] = Relationship(back_populates="ref_obj")
class SaleCategory(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
category: str
description: Optional[str] = None
perc: float # процент начисления партнеру
company_id: int = Field(foreign_key="company.id")
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
company: "Company" = Relationship(back_populates="sale_categories")
sales: List["Sale"] = Relationship(back_populates="sale_category")
class Sale(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
cost: float
crediting: float # сколько начислено за продажу
ref: int = Field(foreign_key="ref.id")
sale_id: str
group_sale_id: str # новое поле для группировки продаж
company_id: int = Field(foreign_key="company.id")
category: int = Field(foreign_key="salecategory.id") # новая ссылка на категорию
sale_dttm: datetime = Field(default_factory=datetime.utcnow)
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
ref_obj: "Ref" = Relationship(back_populates="sales")
company: "Company" = Relationship(back_populates="sales")
sale_category: "SaleCategory" = Relationship(back_populates="sales")
class AgentTransaction(SQLModel, table=True):
__tablename__ = "agent_transactions"
id: Optional[int] = Field(default=None, primary_key=True)
@@ -57,6 +89,9 @@ class AgentTransaction(SQLModel, table=True):
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
tg_agent: "TgAgent" = Relationship(back_populates="agent_transactions")
partner_transactions: List["PartnerTransaction"] = Relationship(back_populates="agent_transaction")
class PartnerTransaction(SQLModel, table=True):
__tablename__ = "partner_transactions"
id: Optional[int] = Field(default=None, primary_key=True)
@@ -69,6 +104,9 @@ class PartnerTransaction(SQLModel, table=True):
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
company: "Company" = Relationship(back_populates="partner_transactions")
agent_transaction: Optional["AgentTransaction"] = Relationship(back_populates="partner_transactions")
class CompanyBalance(SQLModel, table=True):
__tablename__ = "company_balances"
id: Optional[int] = Field(default=None, primary_key=True)
@@ -77,6 +115,8 @@ class CompanyBalance(SQLModel, table=True):
pending_balance: float = Field(default=0.0)
updated_dttm: datetime = Field(default_factory=datetime.utcnow)
company: "Company" = Relationship(back_populates="company_balance")
class AgentBalance(SQLModel, table=True):
__tablename__ = "agent_balances"
id: Optional[int] = Field(default=None, primary_key=True)
@@ -85,6 +125,8 @@ class AgentBalance(SQLModel, table=True):
frozen_balance: float = Field(default=0.0)
updated_dttm: datetime = Field(default_factory=datetime.utcnow)
tg_agent: "TgAgent" = Relationship(back_populates="agent_balance")
class Account(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
login: str = Field(index=True, unique=True)
@@ -97,6 +139,8 @@ class Account(SQLModel, table=True):
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
company: "Company" = Relationship(back_populates="accounts")
# Новая модель для интеграционных токенов
class IntegrationToken(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
@@ -108,4 +152,4 @@ class IntegrationToken(SQLModel, table=True):
update_dttm: datetime = Field(default_factory=datetime.utcnow, nullable=False)
use_dttm: Optional[datetime] = None
company: Company = Relationship(back_populates="integration_tokens")
company: "Company" = Relationship(back_populates="integration_tokens")

View File

@@ -7,6 +7,7 @@ from uuid import UUID
class RefResponse(BaseModel):
ref: str
description: str
promocode: str
class RefAddRequest(BaseModel):
description: str
@@ -25,6 +26,8 @@ class RegisterRequest(BaseModel):
# New Response Models for TG APIs
class RefAddResponse(BaseModel):
ref: str
promocode: str
description: str
class RefStatItem(BaseModel):
description: str