Добавлены новые поля для агентской комиссии в модели Company и CompanyProfileResponse. Реализованы функции для обработки продаж через интеграционный API, включая создание и регистрацию продаж с учетом агентской комиссии. Обновлены соответствующие эндпоинты и модели для работы с токенами и продажами. Улучшена логика обработки транзакций и обновления балансов компаний и агентов.
This commit is contained in:
parent
076cdd1828
commit
1cc18e0364
@ -144,6 +144,7 @@ class CompanyProfileResponse(BaseModel):
|
||||
name: str
|
||||
key: str
|
||||
commission: float
|
||||
agent_commission: float
|
||||
|
||||
class AccountProfileResponse(BaseModel):
|
||||
firstName: Optional[str] = None
|
||||
@ -186,4 +187,4 @@ class IntegrationTokenCreateRequest(BaseModel):
|
||||
|
||||
class IntegrationTokenUpdateRequest(BaseModel):
|
||||
id: int
|
||||
description: str
|
||||
description: str
|
||||
|
||||
60
call_sale_api.py
Normal file
60
call_sale_api.py
Normal file
@ -0,0 +1,60 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
# Конфигурация API
|
||||
BASE_URL = "http://127.0.0.1:8001"
|
||||
API_KEY = "7c06945b-f4b8-4929-8350-b9841405a609"
|
||||
REF = "8c514fcb-7a79-4ed1-9c8a-0af2fcab88c0"
|
||||
|
||||
# Данные для запроса на создание продажи
|
||||
# Замените эти значения на актуальные для вашей продажи
|
||||
sale_data = {
|
||||
"cost": 100.50, # Стоимость продажи
|
||||
"ref": REF, # Ваш реферальный код
|
||||
"sale_id": "UNIQUE_SALE_ID_12345" # Уникальный идентификатор продажи для вашей компании
|
||||
}
|
||||
|
||||
# Эндпоинты
|
||||
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,6 +85,7 @@ def fill_db():
|
||||
company = Company(
|
||||
name="RE: Premium",
|
||||
commission=10.0,
|
||||
agent_commission=15.0,
|
||||
key="re-premium-key",
|
||||
)
|
||||
session.add(company)
|
||||
@ -174,7 +175,7 @@ def fill_db():
|
||||
sale_count = random.randint(20, int(20 * 1.25)) # от 20 до 25
|
||||
for _ in range(sale_count):
|
||||
cost = round(random.uniform(100, 1000), 2)
|
||||
crediting = round(cost * random.uniform(0.5, 1.0), 2)
|
||||
crediting = round(cost * (company.agent_commission / 100.0), 2)
|
||||
|
||||
# Генерируем случайную дату и время в пределах последних 7 дней
|
||||
end_dttm = datetime.utcnow()
|
||||
|
||||
@ -3,12 +3,13 @@ from passlib.context import CryptContext
|
||||
from typing import Optional
|
||||
from datetime import datetime, timedelta
|
||||
from bff_models import Token, TransactionStatus
|
||||
from sql_models import Company, TgAgent, Account, AgentBalance, AgentTransaction, PartnerTransaction, Sale, Ref
|
||||
from sql_models import Company, TgAgent, Account, AgentBalance, AgentTransaction, PartnerTransaction, Sale, Ref, IntegrationToken, CompanyBalance
|
||||
from hashlib import sha256
|
||||
import jwt
|
||||
from jwt.exceptions import InvalidTokenError
|
||||
from fastapi import HTTPException, status, Depends, Request
|
||||
from fastapi import HTTPException, status, Depends, Request, Header
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
import hashlib
|
||||
|
||||
# Конфигурация
|
||||
AUTH_DATABASE_ADDRESS = "sqlite:///partner.db"
|
||||
@ -18,9 +19,60 @@ SECRET_KEY = "supersecretkey"
|
||||
ALGORITHM = "HS256"
|
||||
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")
|
||||
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]:
|
||||
statement = select(TgAgent).where(TgAgent.tg_id == tg_id)
|
||||
return db.exec(statement).first()
|
||||
|
||||
@ -1,75 +1,169 @@
|
||||
from fastapi import FastAPI, HTTPException, status, Depends, Request
|
||||
from fastapi import FastAPI, Depends, HTTPException, status, Header
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from sqlmodel import Session, select
|
||||
from typing import Optional
|
||||
from datetime import datetime, timedelta
|
||||
import jwt
|
||||
from jwt.exceptions import InvalidTokenError
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import uuid
|
||||
|
||||
from sql_models import Sale
|
||||
from helpers_bff import get_db, AUTH_DB_ENGINE # Assuming these are the correct imports
|
||||
from sql_models import Company, IntegrationToken, Ref, Sale, AgentTransaction, PartnerTransaction, AgentBalance, TgAgent, CompanyBalance
|
||||
from integration_models import Token, SaleCreateRequest, SaleCreateResponse, TransactionStatus
|
||||
from helpers_bff import AUTH_DB_ENGINE, get_integration_db, create_integration_jwt_token, get_current_company_from_jwt
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# Конфигурация для интеграционного API токена
|
||||
INTEGRATION_SECRET_KEY = "your-integration-super-secret-key" # Смените это на безопасный ключ!
|
||||
INTEGRATION_ALGORITHM = "HS256"
|
||||
INTEGRATION_TOKEN_EXPIRE_MINUTES = 60 * 24 # 24 часа
|
||||
#7c06945b-f4b8-4929-8350-b9841405a609
|
||||
|
||||
class IntegrationTokenData(BaseModel):
|
||||
client_id: str
|
||||
|
||||
class SaleCreate(BaseModel):
|
||||
cost: float
|
||||
crediting: float
|
||||
ref_id: int
|
||||
sale_id: str
|
||||
company_id: int
|
||||
sale_date: datetime = datetime.utcnow()
|
||||
@app.post("/token", tags=["integration"], response_model=Token)
|
||||
async def get_token_for_api_key(
|
||||
x_api_key: str = Header(..., alias="X-API-Key"),
|
||||
db: Session = Depends(get_integration_db)
|
||||
):
|
||||
"""
|
||||
Обменивает API-ключ на JWT токен.
|
||||
"""
|
||||
|
||||
def create_integration_access_token(data: dict, expires_delta: timedelta = None):
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
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
|
||||
if not x_api_key:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="API-ключ не предоставлен",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
async def verify_integration_token(request: Request):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
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 # Токен действителен
|
||||
api_key_hash = hashlib.sha256(x_api_key.encode()).hexdigest()
|
||||
integration_token_db = db.exec(
|
||||
select(IntegrationToken).where(IntegrationToken.token_hash == api_key_hash)
|
||||
).first()
|
||||
|
||||
@app.post("/sale", status_code=status.HTTP_201_CREATED)
|
||||
async def upload_sale(sale_data: SaleCreate, db: Session = Depends(get_db), verified: bool = Depends(verify_integration_token)):
|
||||
if not verified:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authorized")
|
||||
if not integration_token_db:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Неверный API-ключ",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
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)
|
||||
db.add(db_sale)
|
||||
# Обновляем use_dttm токена
|
||||
integration_token_db.use_dttm = datetime.utcnow()
|
||||
db.add(integration_token_db)
|
||||
db.commit()
|
||||
db.refresh(db_sale)
|
||||
return {"message": "Sale uploaded successfully", "sale_id": db_sale.id}
|
||||
db.refresh(integration_token_db)
|
||||
|
||||
@app.get("/generate-integration-token")
|
||||
async def generate_token_endpoint(client_id: str):
|
||||
token_data = {"client_id": client_id}
|
||||
token = create_integration_access_token(token_data)
|
||||
return {"access_token": token, "token_type": "bearer"}
|
||||
jwt_token = create_integration_jwt_token(integration_token_db.company_id)
|
||||
return {"access_token": jwt_token, "token_type": "bearer"}
|
||||
|
||||
@app.post("/sale", tags=["integration"], response_model=SaleCreateResponse)
|
||||
async def create_sale(
|
||||
req: SaleCreateRequest,
|
||||
company: Company = Depends(get_current_company_from_jwt), # Используем новую зависимость
|
||||
db: Session = Depends(get_integration_db)
|
||||
):
|
||||
"""
|
||||
Регистрирует новую продажу в системе.
|
||||
"""
|
||||
# 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. Создать 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)
|
||||
db.commit()
|
||||
db.refresh(new_sale)
|
||||
|
||||
# 5. Обновить AgentBalance и CompanyBalance
|
||||
# AgentBalance
|
||||
agent_balance = db.exec(select(AgentBalance).where(AgentBalance.tg_agent_id == tg_agent.id)).first()
|
||||
if not agent_balance:
|
||||
agent_balance = AgentBalance(tg_agent_id=tg_agent.id, available_balance=0.0, frozen_balance=0.0)
|
||||
db.add(agent_balance)
|
||||
db.commit()
|
||||
db.refresh(agent_balance)
|
||||
|
||||
# CompanyBalance
|
||||
company_balance = db.exec(select(CompanyBalance).where(CompanyBalance.company_id == company.id)).first()
|
||||
if not company_balance:
|
||||
company_balance = CompanyBalance(company_id=company.id, available_balance=0.0, pending_balance=0.0)
|
||||
db.add(company_balance)
|
||||
db.commit()
|
||||
db.refresh(company_balance)
|
||||
|
||||
# Создать AgentTransaction
|
||||
agent_transaction_status = TransactionStatus.NEW if company.auto_approve_transactions else TransactionStatus.WAITING
|
||||
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)
|
||||
db.commit()
|
||||
db.refresh(agent_transaction)
|
||||
|
||||
# Создать PartnerTransaction
|
||||
partner_transaction_status = TransactionStatus.NEW if company.auto_approve_transactions else TransactionStatus.PROCESS
|
||||
partner_transaction = PartnerTransaction(
|
||||
company_id=company.id,
|
||||
type="sale_crediting",
|
||||
amount=crediting_amount,
|
||||
status=partner_transaction_status.value,
|
||||
transaction_group=agent_transaction.transaction_group,
|
||||
agent_transaction_id=agent_transaction.id
|
||||
)
|
||||
db.add(partner_transaction)
|
||||
db.commit()
|
||||
db.refresh(partner_transaction)
|
||||
|
||||
# Обновление балансов в зависимости от auto_approve_transactions
|
||||
if company.auto_approve_transactions:
|
||||
agent_balance.available_balance += crediting_amount
|
||||
company_balance.available_balance -= crediting_amount
|
||||
company_balance.updated_dttm = datetime.utcnow()
|
||||
else:
|
||||
agent_balance.frozen_balance += crediting_amount
|
||||
company_balance.pending_balance -= crediting_amount
|
||||
company_balance.updated_dttm = datetime.utcnow()
|
||||
|
||||
db.add(agent_balance)
|
||||
db.add(company_balance)
|
||||
db.commit()
|
||||
db.refresh(agent_balance)
|
||||
db.refresh(company_balance)
|
||||
|
||||
return {
|
||||
"msg": "Продажа успешно зарегистрирована",
|
||||
"sale_id": new_sale.sale_id,
|
||||
"crediting": new_sale.crediting
|
||||
}
|
||||
|
||||
34
integration_models.py
Normal file
34
integration_models.py
Normal file
@ -0,0 +1,34 @@
|
||||
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"
|
||||
3
main.py
3
main.py
@ -544,7 +544,8 @@ def get_account_profile(current_account: Account = Depends(get_current_account),
|
||||
"company": {
|
||||
"name": company.name,
|
||||
"key": company.key,
|
||||
"commission": company.commission
|
||||
"commission": company.commission,
|
||||
"agent_commission": company.agent_commission
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,146 +1,54 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile pyproject.toml -o requirements.txt
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
anyio==4.8.0
|
||||
# via
|
||||
# httpx
|
||||
# starlette
|
||||
# watchfiles
|
||||
bcrypt==4.2.1
|
||||
# via passlib
|
||||
behave==1.2.6
|
||||
# via epai-auth (pyproject.toml)
|
||||
certifi==2024.12.14
|
||||
# via
|
||||
# httpcore
|
||||
# httpx
|
||||
# requests
|
||||
cffi==1.17.1
|
||||
# via cryptography
|
||||
charset-normalizer==3.4.2
|
||||
# via requests
|
||||
click==8.1.8
|
||||
# via
|
||||
# rich-toolkit
|
||||
# typer
|
||||
# uvicorn
|
||||
colorama==0.4.6
|
||||
cryptography==44.0.0
|
||||
# via epai-auth (pyproject.toml)
|
||||
dnspython==2.7.0
|
||||
# via email-validator
|
||||
email-validator==2.2.0
|
||||
# via fastapi
|
||||
fastapi==0.115.6
|
||||
# via epai-auth (pyproject.toml)
|
||||
fastapi-cli==0.0.7
|
||||
# via fastapi
|
||||
greenlet==3.1.1
|
||||
# via sqlalchemy
|
||||
h11==0.14.0
|
||||
# via
|
||||
# httpcore
|
||||
# uvicorn
|
||||
httpcore==1.0.7
|
||||
# via httpx
|
||||
httptools==0.6.4
|
||||
# via uvicorn
|
||||
httpx==0.28.1
|
||||
# via fastapi
|
||||
idna==3.10
|
||||
# via
|
||||
# anyio
|
||||
# email-validator
|
||||
# httpx
|
||||
# requests
|
||||
jinja2==3.1.5
|
||||
# via fastapi
|
||||
markdown-it-py==3.0.0
|
||||
# via rich
|
||||
markupsafe==3.0.2
|
||||
# via jinja2
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
parse==1.20.2
|
||||
# via
|
||||
# behave
|
||||
# parse-type
|
||||
parse-type==0.6.4
|
||||
# via behave
|
||||
passlib==1.7.4
|
||||
# via epai-auth (pyproject.toml)
|
||||
psycopg2==2.9.10
|
||||
# via epai-auth (pyproject.toml)
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
pydantic==2.10.5
|
||||
# via
|
||||
# fastapi
|
||||
# pydantic-settings
|
||||
# sqlmodel
|
||||
pydantic-core==2.27.2
|
||||
# via pydantic
|
||||
pydantic-settings==2.9.1
|
||||
# via epai-auth (pyproject.toml)
|
||||
pygments==2.19.1
|
||||
# via rich
|
||||
pyjwt==2.10.1
|
||||
# via epai-auth (pyproject.toml)
|
||||
python-dotenv==1.0.1
|
||||
# via
|
||||
# pydantic-settings
|
||||
# uvicorn
|
||||
python-multipart==0.0.20
|
||||
# via fastapi
|
||||
pyyaml==6.0.2
|
||||
# via uvicorn
|
||||
requests==2.32.3
|
||||
# via epai-auth (pyproject.toml)
|
||||
rich==13.9.4
|
||||
# via
|
||||
# rich-toolkit
|
||||
# typer
|
||||
rich-toolkit==0.12.0
|
||||
# via fastapi-cli
|
||||
ruff==0.9.1
|
||||
# via epai-auth (pyproject.toml)
|
||||
shellingham==1.5.4
|
||||
# via typer
|
||||
six==1.17.0
|
||||
# via
|
||||
# behave
|
||||
# parse-type
|
||||
sniffio==1.3.1
|
||||
# via anyio
|
||||
sqlalchemy==2.0.37
|
||||
# via sqlmodel
|
||||
sqlmodel==0.0.22
|
||||
# via epai-auth (pyproject.toml)
|
||||
starlette==0.41.3
|
||||
# via fastapi
|
||||
typer==0.15.1
|
||||
# via fastapi-cli
|
||||
typing-extensions==4.12.2
|
||||
# via
|
||||
# anyio
|
||||
# fastapi
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# rich-toolkit
|
||||
# sqlalchemy
|
||||
# typer
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.0
|
||||
# via pydantic-settings
|
||||
urllib3==2.4.0
|
||||
# via requests
|
||||
uvicorn==0.34.0
|
||||
# via
|
||||
# fastapi
|
||||
# fastapi-cli
|
||||
#uvloop==0.21.0
|
||||
# via uvicorn
|
||||
watchfiles==1.0.4
|
||||
# via uvicorn
|
||||
websockets==14.1
|
||||
# via uvicorn
|
||||
|
||||
@ -7,7 +7,8 @@ from sqlalchemy import Column, String
|
||||
class Company(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str
|
||||
commission: float # процент комиссии
|
||||
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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user