This commit is contained in:
2024-04-28 04:55:47 +03:00
parent 4e7626d2e6
commit c8a62b4952
10 changed files with 156 additions and 56 deletions

3
constants.py Normal file
View File

@@ -0,0 +1,3 @@
allowed_telegram_ids = [
454777987,
]

View File

@@ -37,10 +37,11 @@ class Deal(BaseModel):
products = relationship('DealProduct', back_populates='deal') products = relationship('DealProduct', back_populates='deal')
# TODO remake with sequence # TODO remake with sequence
rank = Column(String, nullable=False, comment='Lexorank') lexorank = Column(String, nullable=False, comment='Lexorank', index=True)
comment = Column(String, nullable=False, server_default='', comment='Коментарий к заданию') comment = Column(String, nullable=False, server_default='', comment='Коментарий к заданию')
class DealStatusHistory(BaseModel): class DealStatusHistory(BaseModel):
__tablename__ = 'deals_status_history' __tablename__ = 'deals_status_history'
id = Column(Integer, autoincrement=True, primary_key=True, index=True) id = Column(Integer, autoincrement=True, primary_key=True, index=True)

View File

@@ -11,7 +11,8 @@ from services.client import ClientService
client_router = APIRouter( client_router = APIRouter(
prefix="/client", prefix="/client",
tags=['client'] tags=['client'],
dependencies=[Depends(get_current_user)]
) )

View File

@@ -11,7 +11,8 @@ from services.deal import DealService
deal_router = APIRouter( deal_router = APIRouter(
prefix='/deal', prefix='/deal',
tags=['deal'] tags=['deal'],
dependencies=[Depends(get_current_user)]
) )
@@ -24,6 +25,18 @@ async def create(
return await DealService(session).create(request, user) return await DealService(session).create(request, user)
@deal_router.post(
'/delete',
response_model=DealDeleteResponse,
operation_id='deleteDeal'
)
async def delete(
request: DealDeleteRequest,
session: Annotated[AsyncSession, Depends(get_session)]
):
return await DealService(session).delete(request)
@deal_router.post('/quickCreate', response_model=DealQuickCreateResponse) @deal_router.post('/quickCreate', response_model=DealQuickCreateResponse)
async def quick_create( async def quick_create(
request: DealQuickCreateRequest, request: DealQuickCreateRequest,
@@ -39,7 +52,7 @@ async def change_status(
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: Annotated[User, Depends(get_current_user)] user: Annotated[User, Depends(get_current_user)]
): ):
return await DealService(session).change_status(request, user) return await DealService(session).change_status_manual(request, user)
@deal_router.get('/summaries', @deal_router.get('/summaries',
@@ -51,9 +64,10 @@ async def get_summary(
): ):
return await DealService(session).get_summary() return await DealService(session).get_summary()
@deal_router.post( @deal_router.post(
'/summaries/reorder', '/summaries/reorder',
response_model=DealSummaryReorderResponse, response_model=DealSummaryResponse,
operation_id='reorderDealSummaries' operation_id='reorderDealSummaries'
) )
async def reorder( async def reorder(

View File

@@ -7,11 +7,13 @@ import utils.dependecies
from backend.session import get_session from backend.session import get_session
from schemas.base import PaginationSchema from schemas.base import PaginationSchema
from schemas.product import * from schemas.product import *
from services.auth import get_current_user
from services.product import ProductService from services.product import ProductService
product_router = APIRouter( product_router = APIRouter(
prefix="/product", prefix="/product",
tags=["product"] tags=["product"],
dependencies=[Depends(get_current_user)]
) )
@@ -98,6 +100,7 @@ async def exists_product_barcode(
): ):
return await ProductService(session).exists_barcode(product_id, barcode) return await ProductService(session).exists_barcode(product_id, barcode)
@product_router.post( @product_router.post(
'/barcode/generate', '/barcode/generate',
response_model=ProductGenerateBarcodeResponse, response_model=ProductGenerateBarcodeResponse,

View File

@@ -5,11 +5,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
from backend.session import get_session from backend.session import get_session
from schemas.service import * from schemas.service import *
from services.auth import get_current_user
from services.service import ServiceService from services.service import ServiceService
service_router = APIRouter( service_router = APIRouter(
prefix="/service", prefix="/service",
tags=['service'] tags=['service'],
dependencies=[Depends(get_current_user)]
) )

View File

@@ -21,6 +21,7 @@ class DealSummary(CustomModelCamel):
name: str name: str
client_name: str client_name: str
changed_at: datetime.datetime changed_at: datetime.datetime
deadline: datetime.datetime
status: int status: int
total_price: int total_price: int
rank: int rank: int
@@ -41,7 +42,8 @@ class DealStatusHistorySchema(CustomModelCamel):
changed_at: datetime.datetime changed_at: datetime.datetime
from_status: int from_status: int
to_status: int to_status: int
next_status_deadline: datetime.datetime next_status_deadline: datetime.datetime | None
comment: str | None = None
class DealSchema(CustomModelCamel): class DealSchema(CustomModelCamel):
@@ -146,12 +148,16 @@ class DealUpdateGeneralInfoRequest(CustomModelCamel):
class DealSummaryReorderRequest(CustomModelCamel): class DealSummaryReorderRequest(CustomModelCamel):
deal_id: int deal_id: int
new_status: int status: int
rank: int index: int
next_status_deadline: datetime.datetime deadline: datetime.datetime
comment: str comment: str
class DealDeleteRequest(CustomModelCamel):
deal_id: int
# endregion Requests # endregion Requests
# region Responses # region Responses
@@ -219,4 +225,8 @@ class DealUpdateGeneralInfoResponse(OkMessageSchema):
class DealSummaryReorderResponse(OkMessageSchema): class DealSummaryReorderResponse(OkMessageSchema):
pass pass
class DealDeleteResponse(OkMessageSchema):
pass
# endregion Responses # endregion Responses

View File

@@ -8,6 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from starlette import status from starlette import status
import backend.config import backend.config
import constants
from backend.session import get_session from backend.session import get_session
from models import User from models import User
from schemas.auth import * from schemas.auth import *
@@ -46,6 +47,9 @@ class AuthService(BaseService):
return jwt.encode(payload, backend.config.SECRET_KEY, algorithm=algorithm) return jwt.encode(payload, backend.config.SECRET_KEY, algorithm=algorithm)
async def authenticate(self, request: AuthLoginRequest): async def authenticate(self, request: AuthLoginRequest):
if request.id not in constants.allowed_telegram_ids:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid credentials')
user: Union[User, None] = await self.session.scalar(select(User).where(User.telegram_id == request.id)) user: Union[User, None] = await self.session.scalar(select(User).where(User.telegram_id == request.id))
if not user: if not user:
user = User(telegram_id=request.id, user = User(telegram_id=request.id,

View File

@@ -21,18 +21,22 @@ class DealService(BaseService):
async def _get_deal_by_id(self, deal_id) -> Union[Deal, None]: async def _get_deal_by_id(self, deal_id) -> Union[Deal, None]:
return await self.session.get(Deal, deal_id) return await self.session.get(Deal, deal_id)
async def _get_rank(self): async def _get_rank_for_deal(self, deal_status: DealStatus) -> int:
query = await self.session.execute(select(Deal.rank).order_by(Deal.rank.desc())) deal_query = await self.session.execute(
result = query.scalar_one_or_none() select(Deal).where(Deal.current_status == deal_status).order_by(Deal.lexorank.desc()).limit(1))
if not result: deal = deal_query.scalar_one_or_none()
return str(lexorank.Bucket.BUCEKT_0.next()) if not deal:
rank = lexorank.parse(result) prev = lexorank.middle(lexorank.Bucket.BUCEKT_0)
return str(rank.next()) return str(prev.next())
return str(lexorank.parse(deal.lexorank).next())
async def change_status(self, deal: Deal, async def change_status(self, deal: Deal,
status: DealStatus, status: DealStatus,
user: User, user: User,
deadline: datetime.datetime = None) -> DealStatusHistory: deadline: datetime.datetime = None,
rank=None,
comment: str = '') -> DealStatusHistory:
if not deal.current_status == status:
deadline = deadline deadline = deadline
status_change = DealStatusHistory( status_change = DealStatusHistory(
deal_id=deal.id, deal_id=deal.id,
@@ -40,19 +44,24 @@ class DealService(BaseService):
changed_at=datetime.datetime.now(), changed_at=datetime.datetime.now(),
from_status=deal.current_status, from_status=deal.current_status,
to_status=status, to_status=status,
next_status_deadline=deadline next_status_deadline=deadline,
comment=comment
) )
self.session.add(status_change) self.session.add(status_change)
deal.current_status = status
if not rank:
rank = await self._get_rank_for_deal(status)
if rank:
deal.lexorank = rank
await self.session.flush() await self.session.flush()
return status_change
async def create(self, request: DealCreateRequest, user: User) -> DealCreateResponse: async def create(self, request: DealCreateRequest, user: User) -> DealCreateResponse:
rank = await self._get_rank() rank = self._get_rank_for_deal(DealStatus.CREATED)
deal = Deal( deal = Deal(
name=request.name, name=request.name,
created_at=datetime.datetime.now(), created_at=datetime.datetime.now(),
current_status=DealStatus.CREATED, current_status=DealStatus.CREATED,
rank=rank lexorank=rank
) )
self.session.add(deal) self.session.add(deal)
await self.session.flush() await self.session.flush()
@@ -63,6 +72,14 @@ class DealService(BaseService):
await self.session.commit() await self.session.commit()
return DealCreateResponse(ok=True) return DealCreateResponse(ok=True)
async def delete(self, request: DealDeleteRequest) -> DealDeleteResponse:
deal = await self._get_deal_by_id(request.deal_id)
if not deal:
return DealDeleteResponse(ok=False, message="Сделка не найдена")
deal.is_deleted = True
await self.session.commit()
return DealDeleteResponse(ok=True, message="Сделка успешно удалена")
async def quick_create(self, request: DealQuickCreateRequest, user: User) -> DealQuickCreateResponse: async def quick_create(self, request: DealQuickCreateRequest, user: User) -> DealQuickCreateResponse:
client_service = ClientService(self.session) client_service = ClientService(self.session)
client = await client_service.get_by_name(request.client_name) client = await client_service.get_by_name(request.client_name)
@@ -72,13 +89,13 @@ class DealService(BaseService):
request.client_name, request.client_name,
ClientDetailsSchema(address=request.client_address)) ClientDetailsSchema(address=request.client_address))
await client_service.update_details(user, client, ClientDetailsSchema(address=request.client_address)) await client_service.update_details(user, client, ClientDetailsSchema(address=request.client_address))
rank = await self._get_rank() rank = await self._get_rank_for_deal(DealStatus.CREATED)
deal = Deal( deal = Deal(
name=request.name, name=request.name,
created_at=datetime.datetime.now(), created_at=datetime.datetime.now(),
client_id=client.id, client_id=client.id,
current_status=DealStatus.CREATED, current_status=DealStatus.CREATED,
rank=rank lexorank=rank
) )
self.session.add(deal) self.session.add(deal)
await self.session.flush() await self.session.flush()
@@ -108,10 +125,11 @@ class DealService(BaseService):
q = ( q = (
select( select(
Deal, Deal,
func.coalesce(service_subquery.c.total_price, 0) func.coalesce(service_subquery.c.total_price, 0),
) func.row_number().over(
.order_by( partition_by=Deal.current_status,
Deal.rank.desc() order_by=Deal.lexorank
).label('rank')
) )
.options( .options(
selectinload(Deal.status_history), selectinload(Deal.status_history),
@@ -126,18 +144,20 @@ class DealService(BaseService):
) )
deals_query = await self.session.execute(q) deals_query = await self.session.execute(q)
summaries = [] summaries = []
for deal, total_price in deals_query.all(): for deal, total_price, rank in deals_query.all():
deal: Deal deal: Deal
last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at) last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at)
deadline = last_status.next_status_deadline
summaries.append( summaries.append(
DealSummary( DealSummary(
id=deal.id, id=deal.id,
client_name=deal.client.name, client_name=deal.client.name,
name=deal.name, name=deal.name,
changed_at=last_status.changed_at, changed_at=last_status.changed_at,
deadline=deadline,
status=last_status.to_status, status=last_status.to_status,
total_price=total_price, total_price=total_price,
rank=deal.rank rank=rank
) )
) )
return DealSummaryResponse(summaries=summaries) return DealSummaryResponse(summaries=summaries)
@@ -190,8 +210,51 @@ class DealService(BaseService):
await self.session.rollback() await self.session.rollback()
return DealUpdateGeneralInfoResponse(ok=False, message=str(e)) return DealUpdateGeneralInfoResponse(ok=False, message=str(e))
async def reorder(self, request: DealSummaryReorderRequest, user: User) -> DealSummaryReorderResponse: async def reorder(self, request: DealSummaryReorderRequest, user: User) -> DealSummaryResponse:
pass deal: Deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id))
if request.index == 1:
request.index = 0
is_first = request.index == 0
stmt = (
select(Deal)
.where(Deal.current_status == request.status,
Deal.id != request.deal_id)
.order_by(Deal.lexorank)
.offset(max([request.index - 2, 0]))
.limit(2 if not is_first else 1)
)
query = await self.session.execute(stmt)
boundaries = query.scalars().all()
top_boundary: Union[Deal, None] = boundaries[0] if not is_first else None
bottom_boundary: Union[Deal, None] = boundaries[1] if len(boundaries) == 2 else None
if top_boundary:
print(top_boundary.name)
if bottom_boundary:
print(bottom_boundary.name)
# working when between two elements
if top_boundary and bottom_boundary:
top_lexorank = lexorank.parse(top_boundary.lexorank)
bottom_lexorank = lexorank.parse(bottom_boundary.lexorank)
new_rank = lexorank.between(top_lexorank, bottom_lexorank)
# working when at the bottom
elif top_boundary and not bottom_boundary:
new_rank = lexorank.parse(top_boundary.lexorank).next()
# working when at the top
elif bottom_boundary and not top_boundary:
new_rank = lexorank.parse(bottom_boundary.lexorank).prev()
elif not top_boundary and not bottom_boundary and len(boundaries) > 0:
new_rank = lexorank.parse(boundaries[0].lexorank).prev()
else:
new_rank = lexorank.middle(lexorank.Bucket.BUCEKT_0)
await self.change_status(deal, request.status, user,
deadline=request.deadline,
comment=request.comment,
rank=str(new_rank))
await self.session.commit()
return await self.get_summary()
# endregion # endregion

View File

@@ -1,17 +1,16 @@
import string from pprint import pprint
import lexorank import lexorank
from lexorank import Bucket, middle
prev = middle(Bucket.BUCEKT_0)
def main():
prev = lexorank.middle(lexorank.Bucket.BUCEKT_0)
ranks = [prev] ranks = [prev]
for _ in range(1_000_000): for _ in range(9):
ranks.append(prev.next()) ranks.append(prev.next())
prev = ranks[-1] prev = ranks[-1]
print('generated') middle_ranks = lexorank.between(ranks[1], ranks[2])
sorted_ranks = list(sorted(ranks)) pprint(ranks)
for idx in range(len(sorted_ranks)): print('----------')
print(sorted_ranks[idx], ' ', ranks[idx]) ranks.append(middle_ranks)
if __name__ == '__main__': ranks = sorted(ranks)
main() pprint(ranks)