From aae7e9600132f94d36d1cec2ae5ed93244520602 Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 7 Oct 2024 23:42:37 +0300 Subject: [PATCH] feat: crappy reordering --- migrations/env.py | 1 - models/__init__.py | 1 + models/deal.py | 11 ++++- models/service.py | 16 +++++++ routers/service.py | 26 +++++++++++ schemas/deal.py | 7 +++ schemas/service.py | 26 +++++++++-- services/deal.py | 12 ++++-- services/service.py | 102 ++++++++++++++++++++++++++++++++++++++++---- utils/list_utils.py | 23 +++++++++- 10 files changed, 208 insertions(+), 17 deletions(-) diff --git a/migrations/env.py b/migrations/env.py index b194424..05b7df7 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -30,7 +30,6 @@ target_metadata = BaseModel.metadata # my_important_option = config.get_main_option("my_important_option") # ... etc. def include_object(object, name, type_, reflected, compare_to): - print(f"{type_}: {name}") return True # Temporarily return True to debug all objects diff --git a/models/__init__.py b/models/__init__.py index 7e80116..933c019 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -12,5 +12,6 @@ from .marketplace import * from .payroll import * from .billing import * from .marketplace_products import * +# from .deal_group import * configure_mappers() diff --git a/models/deal.py b/models/deal.py index a3e29bf..f3f4dd3 100644 --- a/models/deal.py +++ b/models/deal.py @@ -10,7 +10,9 @@ from .marketplace import BaseMarketplace from .shipping_warehouse import ShippingWarehouse if TYPE_CHECKING: - from . import DealBillRequest, ServicePriceCategory + from . import (DealBillRequest, ServicePriceCategory, + # DealGroup + ) # @unique @@ -89,6 +91,13 @@ class Deal(BaseModel): category: Mapped[Optional["ServicePriceCategory"]] = relationship('ServicePriceCategory', secondary=DealPriceCategory.__table__, lazy='joined') + # group: Mapped[Optional["DealGroup"]] = relationship( + # 'DealGroup', + # secondary='deal_relations', + # lazy='joined', + # uselist=False, + # back_populates='deals' + # ) class DealStatusHistory(BaseModel): diff --git a/models/service.py b/models/service.py index 324846f..2c20c0e 100644 --- a/models/service.py +++ b/models/service.py @@ -46,6 +46,11 @@ class Service(BaseModel): back_populates='service', lazy='selectin', cascade="all, delete-orphan") + rank: Mapped[str] = mapped_column( + nullable=False, + server_default='', + comment='Ранг услуги' + ) class ServicePriceRange(BaseModel): @@ -80,6 +85,17 @@ class ServiceCategory(BaseModel): id = Column(Integer, autoincrement=True, primary_key=True, index=True) name = Column(String, nullable=False) + deal_service_rank: Mapped[str] = mapped_column( + nullable=False, + server_default='', + comment='Ранг услуги для сделки' + ) + product_service_rank: Mapped[str] = mapped_column( + nullable=False, + server_default='', + comment='Ранг услуги для товара' + ) + class ServicesKit(BaseModel): __tablename__ = 'services_kits' diff --git a/routers/service.py b/routers/service.py index 1b5cad0..3436127 100644 --- a/routers/service.py +++ b/routers/service.py @@ -70,6 +70,19 @@ async def delete( return await ServiceService(session).delete(request) +@service_router.post( + '/reorder', + response_model=ServiceReorderResponse, + operation_id="reorder_service", + +) +async def reorder( + session: Annotated[AsyncSession, Depends(get_session)], + request: ServiceReorderRequest +): + return await ServiceService(session).reorder(request) + + # endregion # region Categories @@ -99,6 +112,19 @@ async def create_category( return await ServiceService(session).create_category(request) +@service_router.post( + '/categories/reorder', + response_model=ServiceCategoryReorderResponse, + operation_id="reorder_service_category", + dependencies=[Depends(authorized_user)] +) +async def reorder_category( + session: Annotated[AsyncSession, Depends(get_session)], + request: ServiceCategoryReorderRequest +): + return await ServiceService(session).reorder_category(request) + + # endregion # region Types diff --git a/schemas/deal.py b/schemas/deal.py index df6e7c3..8a06203 100644 --- a/schemas/deal.py +++ b/schemas/deal.py @@ -23,6 +23,12 @@ class FastDeal(BaseSchema): acceptance_date: datetime.datetime +class DealGroupSchema(BaseSchema): + id: int + name: Optional[str] = None + lexorank: str + + class DealSummary(BaseSchema): id: int name: str @@ -42,6 +48,7 @@ class DealSummary(BaseSchema): delivery_date: Optional[datetime.datetime] = None receiving_slot_date: Optional[datetime.datetime] = None bill_request: Optional[DealBillRequestSchema] = None + # group: Optional[DealGroupSchema] = None class DealServiceSchema(BaseSchema): diff --git a/schemas/service.py b/schemas/service.py index f854cdd..1be839c 100644 --- a/schemas/service.py +++ b/schemas/service.py @@ -1,6 +1,6 @@ from typing import List, Optional -from schemas.base import BaseSchema, OkMessageSchema, BaseEnumSchema +from schemas.base import BaseSchema, OkMessageSchema # region Entities @@ -16,6 +16,8 @@ class ServicePriceRangeSchema(BaseSchema): class ServiceCategorySchema(BaseSchema): id: int name: str + deal_service_rank: str + product_service_rank: str class ServicePriceCategorySchema(BaseSchema): @@ -37,6 +39,7 @@ class ServiceSchema(BaseSchema): price_ranges: List[ServicePriceRangeSchema] category_prices: List[ServiceCategoryPriceSchema] cost: Optional[int] + rank: str # endregion @@ -69,7 +72,6 @@ class UpdateServiceKitSchema(BaseServiceKitSchema): # endregion - # region Requests class ServiceCreateRequest(BaseSchema): service: ServiceSchema @@ -108,9 +110,20 @@ class DeletePriceCategoryRequest(BaseSchema): id: int -# endregion +class ServiceReorderRequest(BaseSchema): + draining_service_id: int + hovered_service_id: int +class ServiceCategoryReorderRequest(BaseSchema): + move_down: bool + move_up: bool + category_id: int + service_type: int + + +# endregion + # region Responses class ServiceGetAllResponse(BaseSchema): services: List[ServiceSchema] @@ -163,4 +176,11 @@ class UpdatePriceCategoryResponse(OkMessageSchema): class DeletePriceCategoryResponse(OkMessageSchema): pass + +class ServiceReorderResponse(OkMessageSchema): + pass + + +class ServiceCategoryReorderResponse(OkMessageSchema): + pass # endregion diff --git a/services/deal.py b/services/deal.py index e366bb2..a6fedfe 100644 --- a/services/deal.py +++ b/services/deal.py @@ -668,9 +668,15 @@ class DealService(BaseService): models.DealProductService.deal_id == request.deal_id, ) ) - deal_product_services: list[models.DealProductService] = ( - await self.session.scalars(source_services_stmt)).all() - # source_services: list[models.Service] = [dpc.service for dpc in deal_product_services] + deal_product_services = ( + ( + await self.session.scalars( + source_services_stmt + ) + ) + .all() + ) + destination_deal_products_stmt = ( select( models.DealProduct diff --git a/services/service.py b/services/service.py index 4c9a476..b5b7ee7 100644 --- a/services/service.py +++ b/services/service.py @@ -1,25 +1,40 @@ -from typing import Union - -from sqlalchemy import select, update, insert, delete +import lexorank +from lexorank import Bucket, middle +from sqlalchemy import select, update, insert, delete, desc from sqlalchemy.orm import joinedload +from enums.service import ServiceType from models import Service, ServiceCategory, ServicePriceRange, ServicesKit, services_kit_services, \ ServiceCategoryPrice, ServicePriceCategory -from services.base import BaseService from schemas.service import * +from services.base import BaseService +from utils.list_utils import previous_current_next class ServiceService(BaseService): async def get_all(self) -> ServiceGetAllResponse: - query = await (self.session - .scalars(select(Service) - .options(joinedload(Service.category)) - .order_by(Service.category_id, Service.id))) + query = await ( + self.session + .scalars(select(Service) + .options(joinedload(Service.category)) + .order_by(Service.rank) + ) + ) services = [] for service in query.all(): services.append(ServiceSchema.model_validate(service)) return ServiceGetAllResponse(services=services) + async def get_latest_rank_in_category(self, category_id: int, service_type: ServiceType) -> str: + stmt = ( + select(Service.rank) + .where(Service.category_id == category_id) + .where(Service.service_type == service_type) + .order_by(desc(Service.rank)) + .limit(1) + ) + return await self.session.scalar(stmt) or '' + async def create(self, request: ServiceCreateRequest) -> ServiceCreateResponse: try: raw_service = request.service @@ -29,6 +44,16 @@ class ServiceService(BaseService): del service_dict['category'] del service_dict['price_ranges'] del service_dict['category_prices'] + service_type = ServiceType(raw_service.service_type) + category_id = raw_service.category.id + + latest_rank = await self.get_latest_rank_in_category(category_id, service_type) + if not latest_rank: + latest_rank = middle(Bucket.BUCEKT_0) + else: + latest_rank = lexorank.parse(latest_rank) + service_rank = str(latest_rank.next()) + service_dict['rank'] = service_rank service = Service(**service_dict) self.session.add(service) await self.session.flush() @@ -293,3 +318,64 @@ class ServiceService(BaseService): return ServiceDeleteResponse(ok=True, message="Категория цен успешно удалена") except Exception as e: return ServiceDeleteResponse(ok=False, message=f"Неудалось удалить категорию цен, ошибка: {e}") + + async def reorder(self, request: ServiceReorderRequest) -> ServiceReorderResponse: + try: + draining_service = await (self.session.get(Service, request.draining_service_id)) + hovered_service = await (self.session.get(Service, request.hovered_service_id)) + if not draining_service or not hovered_service: + return ServiceReorderResponse(ok=False, message="Услуга не найдена") + if draining_service.category_id == hovered_service.category_id: + temp = draining_service.rank + draining_service.rank = hovered_service.rank + hovered_service.rank = temp + else: + return ServiceReorderResponse(ok=False, message="Нельзя перемещать услуги между категориями") + await self.session.commit() + return ServiceReorderResponse(ok=True, message="Услуги успешно пересортированы") + except Exception as e: + return ServiceReorderResponse(ok=False, message=f"Неудалось найти услугу, ошибка: {e}") + + async def reorder_category(self, request: ServiceCategoryReorderRequest) -> ServiceCategoryReorderResponse: + try: + if request.move_down and request.move_up: + return ServiceCategoryReorderResponse(ok=False, + message="Невозможно выполнить два действия одновременно") + if not request.move_down and not request.move_up: + return ServiceCategoryReorderResponse(ok=False, message="Не указано действие") + if request.service_type == ServiceType.DEAL_SERVICE: + order_by = ServiceCategory.deal_service_rank + rank_field = 'deal_service_rank' + else: + order_by = ServiceCategory.product_service_rank + rank_field = 'product_service_rank' + service_categories = await ( + self.session + .scalars(select(ServiceCategory) + .order_by(order_by)) + ) + service_categories = service_categories.all() + for prv, cur, nxt in previous_current_next(service_categories): + if request.category_id == cur.id: + if request.move_down: + if nxt: + temp = getattr(cur, rank_field) + # cur.rank = nxt.rank + setattr(cur, rank_field, getattr(nxt, rank_field)) + # nxt.rank = temp + setattr(nxt, rank_field, temp) + else: + return ServiceCategoryReorderResponse(ok=False, message="Невозможно переместить вниз") + elif request.move_up: + if prv: + temp = getattr(cur, rank_field) + # cur.rank = prv.rank + setattr(cur, rank_field, getattr(prv, rank_field)) + # prv.rank = temp + setattr(prv, rank_field, temp) + else: + return ServiceCategoryReorderResponse(ok=False, message="Невозможно переместить вверх") + await self.session.commit() + return ServiceCategoryReorderResponse(ok=True, message="Категории успешно пересортированы") + except Exception as e: + return ServiceCategoryReorderResponse(ok=False, message=f"Неудалось пересортировать категорию, ошибка: {e}") diff --git a/utils/list_utils.py b/utils/list_utils.py index 4085582..a5c468f 100644 --- a/utils/list_utils.py +++ b/utils/list_utils.py @@ -7,5 +7,26 @@ def compile_query_to_plain_sql(query) -> str: return query.compile(compile_kwargs={ 'literal_binds': True }) + + def to_locale_number(value): - return '{:,}'.format(value).replace(',', ' ') \ No newline at end of file + return '{:,}'.format(value).replace(',', ' ') + + +def previous_current_next(iterable): + """Make an iterator that yields a (previous, current, next) tuple per element. + + Returns None if the value does not make sense (i.e. previous before + first and next after last). + """ + iterable = iter(iterable) + prv = None + cur = next(iterable) + try: + while True: + nxt = next(iterable) + yield prv, cur, nxt + prv = cur + cur = nxt + except StopIteration: + yield prv, cur, None