Добавлены новые модели SaleCategoryRequest и SaleCategoryResponse в bff_models.py для работы с категориями продаж. Обновлены функции в fill_db.py для заполнения базы данных категориями продаж. Изменены эндпоинты в main.py для создания и получения категорий продаж. Обновлены модели и SQL-скрипты для поддержки новых полей и связей. Улучшена логика обработки продаж с учетом категорий.

This commit is contained in:
Redsandyg 2025-06-18 10:48:29 +03:00
parent 4f366680bf
commit bf6a6a8987
8 changed files with 143 additions and 11 deletions

View File

@ -189,3 +189,20 @@ class IntegrationTokenCreateRequest(BaseModel):
class IntegrationTokenUpdateRequest(BaseModel): class IntegrationTokenUpdateRequest(BaseModel):
id: int id: int
description: str 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

@ -12,7 +12,8 @@ REF = "9bd1a6bd-98e1-48f4-a120-3b3d016011c0"
sale_data = { sale_data = {
"cost": 100.50, # Стоимость продажи "cost": 100.50, # Стоимость продажи
"ref": REF, # Ваш реферальный код "ref": REF, # Ваш реферальный код
"sale_id": str(uuid.uuid4()) # Уникальный идентификатор продажи для вашей компании "sale_id": str(uuid.uuid4()), # Уникальный идентификатор продажи для вашей компании
"category": 1 # id категории (например, 1 - basic)
} }
# Эндпоинты # Эндпоинты

View File

@ -1,7 +1,7 @@
import random import random
from uuid import uuid4 from uuid import uuid4
from sqlmodel import Session 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 sqlalchemy import text
from datetime import datetime, timedelta from datetime import datetime, timedelta
from hashlib import sha256 from hashlib import sha256
@ -81,6 +81,7 @@ def fill_db():
session.execute(text('DELETE FROM "partner_transactions"')) session.execute(text('DELETE FROM "partner_transactions"'))
session.execute(text('DELETE FROM "company_balances"')) session.execute(text('DELETE FROM "company_balances"'))
session.execute(text('DELETE FROM "agent_balances"')) session.execute(text('DELETE FROM "agent_balances"'))
session.execute(text("DELETE FROM salecategory"))
session.execute(text("DELETE FROM company")) session.execute(text("DELETE FROM company"))
session.commit() session.commit()
# 0. Company # 0. Company
@ -110,6 +111,18 @@ def fill_db():
session.add(integration_token) session.add(integration_token)
session.commit() 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 # 1. Accounts
accounts = [] accounts = []
for i in range(4): for i in range(4):
@ -182,25 +195,26 @@ def fill_db():
for ref in refs: for ref in refs:
session.refresh(ref) session.refresh(ref)
# 4. Sales (минимум 20 на каждый ref) # 4. Sales (минимум 20 на каждый ref)
all_categories = session.query(SaleCategory).filter_by(company_id=company.id).all()
for ref in refs: for ref in refs:
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) sale_category = random.choice(all_categories)
crediting = round(cost * (sale_category.perc / 100.0), 2)
# Генерируем случайную дату и время в пределах последних 7 дней # Генерируем случайную дату и время в пределах последних 7 дней
end_dttm = datetime.utcnow() end_dttm = datetime.utcnow()
start_dttm = end_dttm - timedelta(days=7) start_dttm = end_dttm - timedelta(days=7)
time_diff = end_dttm - start_dttm time_diff = end_dttm - start_dttm
random_seconds = random.uniform(0, time_diff.total_seconds()) random_seconds = random.uniform(0, time_diff.total_seconds())
sale_dttm = start_dttm + timedelta(seconds=random_seconds) sale_dttm = start_dttm + timedelta(seconds=random_seconds)
sale = Sale( sale = Sale(
cost=cost, cost=cost,
crediting=crediting, crediting=crediting,
ref=ref.id, ref=ref.id,
sale_id=str(uuid4()), sale_id=str(uuid4()),
company_id=company.id, company_id=company.id,
category=sale_category.id,
sale_dttm=sale_dttm, sale_dttm=sale_dttm,
create_dttm=sale_dttm, # create_dttm также будет случайным в этом диапазоне create_dttm=sale_dttm, # create_dttm также будет случайным в этом диапазоне
update_dttm=sale_dttm # update_dttm также будет случайным в этом диапазоне update_dttm=sale_dttm # update_dttm также будет случайным в этом диапазоне

View File

@ -7,7 +7,7 @@ import uuid
from random import choices from random import choices
import string import string
from sql_models import Company, IntegrationToken, Ref, Sale, AgentTransaction, PartnerTransaction, AgentBalance, TgAgent, CompanyBalance from sql_models import Company, IntegrationToken, Ref, Sale, AgentTransaction, PartnerTransaction, AgentBalance, TgAgent, CompanyBalance, SaleCategory
from integration_models import Token, SaleCreateRequest, SaleCreateResponse, TransactionStatus, WithdrawRequest, WithdrawResponse from integration_models import Token, SaleCreateRequest, SaleCreateResponse, TransactionStatus, WithdrawRequest, WithdrawResponse
from bff_models import RegisterResponse, TgAuthResponse from bff_models import RegisterResponse, TgAuthResponse
from tg_models import RefAddRequest, RefResponse, RefAddResponse, RefStatResponse, RegisterRequest, StatResponse from tg_models import RefAddRequest, RefResponse, RefAddResponse, RefStatResponse, RegisterRequest, StatResponse
@ -201,6 +201,8 @@ async def create_sale(
# Проверка входных данных # Проверка входных данных
if not req.ref and not req.promocode: if not req.ref and not req.promocode:
raise HTTPException(status_code=400, detail="Необходимо передать либо ref, либо 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 # 1. Найти Ref по ref и/или promocode
referral = None referral = None
@ -235,8 +237,11 @@ async def create_sale(
if existing_sale: if existing_sale:
raise HTTPException(status_code=400, detail="Продажа с таким sale_id уже существует для данной компании") raise HTTPException(status_code=400, detail="Продажа с таким sale_id уже существует для данной компании")
# 3. Рассчитать crediting # 3. Найти категорию и рассчитать crediting
crediting_amount = req.cost * (company.agent_commission / 100.0) 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 # 4. Проверить и обновить AgentBalance и CompanyBalance
agent_balance = db.exec(select(AgentBalance).where(AgentBalance.tg_agent_id == tg_agent.id)).first() agent_balance = db.exec(select(AgentBalance).where(AgentBalance.tg_agent_id == tg_agent.id)).first()
@ -253,6 +258,7 @@ async def create_sale(
ref=referral.id, ref=referral.id,
sale_id=req.sale_id, sale_id=req.sale_id,
company_id=company.id, company_id=company.id,
category=sale_category.id,
sale_dttm=datetime.utcnow() sale_dttm=datetime.utcnow()
) )
db.add(new_sale) db.add(new_sale)

View File

@ -20,6 +20,7 @@ class SaleCreateRequest(BaseModel):
promocode: Optional[str] = None promocode: Optional[str] = None
sale_id: str sale_id: str
cost: float cost: float
category: int # id категории продажи
class SaleCreateResponse(BaseModel): class SaleCreateResponse(BaseModel):
msg: str msg: str

65
main.py
View File

@ -38,7 +38,9 @@ from bff_models import (
TransactionStatus, TransactionStatus,
IntegrationTokenResponse, IntegrationTokenResponse,
IntegrationTokenCreateRequest, IntegrationTokenCreateRequest,
IntegrationTokenUpdateRequest IntegrationTokenUpdateRequest,
SaleCategoryRequest,
SaleCategoryResponse
) )
from sql_models import ( from sql_models import (
Company, Company,
@ -49,7 +51,8 @@ from sql_models import (
PartnerTransaction, PartnerTransaction,
AgentBalance, AgentBalance,
Account, Account,
IntegrationToken IntegrationToken,
SaleCategory
) )
from sqlalchemy import func from sqlalchemy import func
import hashlib import hashlib
@ -64,6 +67,7 @@ from helpers_bff import (
pwd_context, pwd_context,
) )
import os import os
from pydantic import BaseModel
# Создание движка базы данных # Создание движка базы данных
@ -726,3 +730,60 @@ def delete_integration_token(
db.delete(token) db.delete(token)
db.commit() db.commit()
return {"msg": "Токен удален успешно"} return {"msg": "Токен удален успешно"}
# --- Категории продаж ---
@app.get("/account/category", tags=["bff", "account"], 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("/account/category", tags=["bff", "account"], 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)

View File

@ -62,6 +62,21 @@ CREATE TABLE integrationtoken (
; ;
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 ( CREATE TABLE tgagent (
id INTEGER NOT NULL, id INTEGER NOT NULL,
tg_id INTEGER NOT NULL, tg_id INTEGER NOT NULL,
@ -150,12 +165,14 @@ CREATE TABLE sale (
ref INTEGER NOT NULL, ref INTEGER NOT NULL,
sale_id VARCHAR NOT NULL, sale_id VARCHAR NOT NULL,
company_id INTEGER NOT NULL, company_id INTEGER NOT NULL,
category INTEGER NOT NULL,
sale_dttm DATETIME NOT NULL, sale_dttm DATETIME NOT NULL,
create_dttm DATETIME NOT NULL, create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL, update_dttm DATETIME NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
FOREIGN KEY(ref) REFERENCES ref (id), FOREIGN KEY(ref) REFERENCES ref (id),
FOREIGN KEY(company_id) REFERENCES company (id) FOREIGN KEY(company_id) REFERENCES company (id),
FOREIGN KEY(category) REFERENCES salecategory (id)
) )
; ;

View File

@ -20,6 +20,7 @@ class Company(SQLModel, table=True):
partner_transactions: List["PartnerTransaction"] = Relationship(back_populates="company") partner_transactions: List["PartnerTransaction"] = Relationship(back_populates="company")
company_balance: Optional["CompanyBalance"] = Relationship(back_populates="company") company_balance: Optional["CompanyBalance"] = Relationship(back_populates="company")
accounts: List["Account"] = Relationship(back_populates="company") accounts: List["Account"] = Relationship(back_populates="company")
sale_categories: List["SaleCategory"] = Relationship(back_populates="company")
class TgAgent(SQLModel, table=True): class TgAgent(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True) id: Optional[int] = Field(default=None, primary_key=True)
@ -50,6 +51,18 @@ class Ref(SQLModel, table=True):
tg_agent: "TgAgent" = Relationship(back_populates="refs") tg_agent: "TgAgent" = Relationship(back_populates="refs")
sales: List["Sale"] = Relationship(back_populates="ref_obj") 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): class Sale(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True) id: Optional[int] = Field(default=None, primary_key=True)
cost: float cost: float
@ -57,12 +70,14 @@ class Sale(SQLModel, table=True):
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")
category: int = Field(foreign_key="salecategory.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)
update_dttm: datetime = Field(default_factory=datetime.utcnow) update_dttm: datetime = Field(default_factory=datetime.utcnow)
ref_obj: "Ref" = Relationship(back_populates="sales") ref_obj: "Ref" = Relationship(back_populates="sales")
company: "Company" = Relationship(back_populates="sales") company: "Company" = Relationship(back_populates="sales")
sale_category: "SaleCategory" = Relationship(back_populates="sales")
class AgentTransaction(SQLModel, table=True): class AgentTransaction(SQLModel, table=True):
__tablename__ = "agent_transactions" __tablename__ = "agent_transactions"