diff --git a/models/secondary.py b/models/secondary.py index 10ac177..5be39a3 100644 --- a/models/secondary.py +++ b/models/secondary.py @@ -103,7 +103,7 @@ class DealProduct(BaseModel): primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, " "DealProductService.product_id == DealProduct.product_id)", foreign_keys=[DealProductService.deal_id, DealProductService.product_id], - lazy='joined', + lazy='selectin', order_by="desc(DealProductService.service_id)" ) @@ -114,3 +114,4 @@ barcode_template_attribute_link = Table( Column('barcode_template_id', ForeignKey('barcode_templates.id')), Column('attribute_id', ForeignKey('barcode_template_attributes.id')) ) + diff --git a/models/service.py b/models/service.py index d25a872..879c14e 100644 --- a/models/service.py +++ b/models/service.py @@ -1,9 +1,17 @@ -from sqlalchemy import Column, Integer, String, ForeignKey, Double, asc +from sqlalchemy import Column, Integer, String, ForeignKey, Double, asc, Table from sqlalchemy.orm import relationship, mapped_column, Mapped import enums.service from models import BaseModel +services_kit_services = Table( + 'services_kit_services', + BaseModel.metadata, + Column('services_kit_id', ForeignKey('services_kits.id')), + Column('service_id', ForeignKey('services.id')), + +) + class Service(BaseModel): __tablename__ = 'services' @@ -50,3 +58,18 @@ class ServiceCategory(BaseModel): __tablename__ = 'service_categories' id = Column(Integer, autoincrement=True, primary_key=True, index=True) name = Column(String, nullable=False) + + +class ServicesKit(BaseModel): + __tablename__ = 'services_kits' + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(nullable=False) + service_type: Mapped[int] = mapped_column( + server_default=f'{enums.service.ServiceType.DEAL_SERVICE}', + nullable=False, + comment='Тип услуги' + ) + services: Mapped[list["Service"]] = relationship( + secondary=services_kit_services, + lazy='selectin' + ) diff --git a/routers/deal.py b/routers/deal.py index 48e1a9a..bda12d8 100644 --- a/routers/deal.py +++ b/routers/deal.py @@ -3,6 +3,7 @@ from typing import Annotated from fastapi import APIRouter, Depends from sqlalchemy.ext.asyncio import AsyncSession +from backend.dependecies import SessionDependency from backend.session import get_session from models import User from schemas.deal import * @@ -16,6 +17,7 @@ deal_router = APIRouter( ) +# region Deal @deal_router.post('/create') async def create( request: DealCreateRequest, @@ -113,6 +115,17 @@ async def update_general_info( ): return await DealService(session).update_general_info(request) +@deal_router.post( + '/add-kit', + response_model=DealAddKitResponse, + operation_id='add_kit_to_deal' +) +async def add_kit_to_deal( + session:SessionDependency, + request:DealAddKitRequest +): + return await DealService(session).add_kit_to_deal(request) +# endregion # region Deal services @@ -188,6 +201,18 @@ async def services_delete( return await DealService(session).delete_services(request) +@deal_router.post( + '/services/copy', + response_model=DealServicesCopyResponse, + operation_id='copy_product_services' +) +async def services_copy( + session: SessionDependency, + request: DealServicesCopyRequest +): + return await DealService(session).copy_services(request) + + # endregion # region Deal products @@ -241,4 +266,16 @@ async def products_update( session: Annotated[AsyncSession, Depends(get_session)] ): return await DealService(session).update_product(request) + + +@deal_router.post( + '/product/add-kit', + response_model=DealProductAddKitResponse, + operation_id='add_kit_to_deal_product' +) +async def add_kit_to_deal_product( + session: SessionDependency, + request: DealProductAddKitRequest +): + return await DealService(session).add_kit_to_deal_product(request) # endregion diff --git a/routers/service.py b/routers/service.py index 2249a42..2ef74f4 100644 --- a/routers/service.py +++ b/routers/service.py @@ -4,6 +4,7 @@ from fastapi import APIRouter, Depends from sqlalchemy.ext.asyncio import AsyncSession import enums.service +from backend.dependecies import SessionDependency from backend.session import get_session from schemas.base import BaseEnumSchema, BaseEnumListSchema from schemas.service import * @@ -98,3 +99,38 @@ async def get_all_service_types( for key, value in enums.service.SERVICE_TYPE_LABELS.items(): result.append({"id": key, "name": value}) return BaseEnumListSchema(items=result) + + +@service_router.get( + '/kits/get-all', + response_model=GetAllServicesKitsResponse, + operation_id='get_all_services_kits' +) +async def get_all_services_kits( + session: SessionDependency +): + return await ServiceService(session).get_all_kits() + + +@service_router.post( + '/kits/create', + response_model=CreateServicesKitResponse, + operation_id='create_services_kit' +) +async def create_services_kit( + session: SessionDependency, + request: CreateServicesKitRequest +): + return await ServiceService(session).create_kit(request) + + +@service_router.post( + '/kits/update', + response_model=UpdateServicesKitResponse, + operation_id='update_services_kit' +) +async def update_services_kit( + session: SessionDependency, + request: UpdateServicesKitRequest +): + return await ServiceService(session).update_kit(request) diff --git a/schemas/deal.py b/schemas/deal.py index 9f78782..ffb988b 100644 --- a/schemas/deal.py +++ b/schemas/deal.py @@ -185,6 +185,23 @@ class DealUpdateProductRequest(BaseSchema): product: DealProductSchema +class DealServicesCopyRequest(BaseSchema): + deal_id: int + source_product_id: int + destination_product_ids: List[int] + + +class DealProductAddKitRequest(BaseSchema): + deal_id: int + product_id: int + kit_id: int + + +class DealAddKitRequest(BaseSchema): + deal_id: int + kit_id: int + + # endregion Requests # region Responses @@ -264,4 +281,16 @@ class DealDeleteResponse(OkMessageSchema): class DealUpdateProductResponse(OkMessageSchema): pass + + +class DealServicesCopyResponse(OkMessageSchema): + pass + + +class DealProductAddKitResponse(OkMessageSchema): + pass + + +class DealAddKitResponse(OkMessageSchema): + pass # endregion Responses diff --git a/schemas/service.py b/schemas/service.py index ee97b29..12be97e 100644 --- a/schemas/service.py +++ b/schemas/service.py @@ -26,6 +26,25 @@ class ServiceSchema(BaseSchema): cost: Optional[int] +class BaseServiceKitSchema(BaseSchema): + name: str + service_type: int + + +class GetServiceKitSchema(BaseServiceKitSchema): + id: int + services: List[ServiceSchema] + + +class CreateServiceKitSchema(BaseServiceKitSchema): + services_ids: List[int] + + +class UpdateServiceKitSchema(BaseServiceKitSchema): + id: int + services_ids: List[int] + + # endregion @@ -46,6 +65,14 @@ class ServiceDeleteRequest(BaseSchema): service_id: int +class CreateServicesKitRequest(BaseSchema): + data: CreateServiceKitSchema + + +class UpdateServicesKitRequest(BaseSchema): + data: UpdateServiceKitSchema + + # endregion @@ -72,4 +99,16 @@ class ServiceUpdateResponse(OkMessageSchema): class ServiceDeleteResponse(OkMessageSchema): pass + + +class CreateServicesKitResponse(OkMessageSchema): + pass + + +class UpdateServicesKitResponse(OkMessageSchema): + pass + + +class GetAllServicesKitsResponse(BaseSchema): + services_kits: List[GetServiceKitSchema] # endregion diff --git a/services/deal.py b/services/deal.py index 637146b..047afb3 100644 --- a/services/deal.py +++ b/services/deal.py @@ -13,6 +13,7 @@ from schemas.client import ClientDetailsSchema from schemas.deal import * from services.base import BaseService from services.client import ClientService +from services.service import ServiceService from services.shipping_warehouse import ShippingWarehouseService @@ -317,6 +318,48 @@ class DealService(BaseService): await self.session.commit() return await self.get_summary() + async def add_kit_to_deal(self, request: DealAddKitRequest) -> DealAddKitResponse: + try: + deal = await self._get_deal_by_id(request.deal_id) + if not deal: + return DealAddKitResponse(ok=False, message="Указанная сделка не найдена") + kit = await ServiceService(self.session).get_kit_by_id(request.kit_id) + if not kit: + return DealAddKitResponse(ok=False, message="Указанный набор услуг не найден") + services: list[models.Service] = kit.services + insert_data = [] + for service in services: + price = self.get_service_price(service, 1) + insert_data.append({ + 'deal_id': deal.id, + 'service_id': service.id, + 'quantity': 1, + 'price': price + }) + if not insert_data: + return DealAddKitResponse(ok=True, message="Набор услуг успешно добавлен к сделке") + # Deleting previous services + delete_stmt = ( + delete( + models.DealService + ) + .where( + models.DealService.deal_id == request.deal_id + ) + ) + await self.session.execute(delete_stmt) + await self.session.flush() + + await self.session.execute( + insert(models.DealService), + insert_data + ) + await self.session.flush() + await self.session.commit() + return DealAddKitResponse(ok=True, message="Набор услуг успешно добавлен к сделке") + except Exception as e: + return DealAddKitResponse(ok=False, message=str(e)) + # endregion # region Deal services @@ -492,6 +535,82 @@ class DealService(BaseService): await self.session.rollback() return DealUpdateServiceQuantityResponse(ok=False, message=str(e)) + async def copy_services(self, request: DealServicesCopyRequest) -> DealServicesCopyResponse: + try: + + source_services_stmt = ( + select( + models.DealProductService + ) + .where( + models.DealProductService.product_id == request.source_product_id, + 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] + + destination_deal_products_stmt = ( + select( + models.DealProduct + ) + .where( + models.DealProduct.product_id.in_(request.destination_product_ids), + models.DealProduct.deal_id == request.deal_id + ) + ) + destination_deal_products = (await self.session.scalars(destination_deal_products_stmt)).all() + insert_data = [] + for deal_product in destination_deal_products: + for service in source_services: + service_price = self.get_service_price(service, deal_product.quantity) + insert_data.append({ + 'deal_id': request.deal_id, + 'product_id': deal_product.product_id, + 'service_id': service.id, + 'price': service_price + }) + if not insert_data: + return DealServicesCopyResponse(ok=True, message='Услуги успешно перенесены') + # Deleting previous DealProductService-s + delete_stmt = ( + delete( + models.DealProductService + ) + .where( + models.DealProductService.product_id.in_(request.destination_product_ids), + models.DealProductService.deal_id == request.deal_id + ) + ) + await self.session.execute(delete_stmt) + await self.session.flush() + + await self.session.execute( + insert(models.DealProductService), + insert_data + ) + await self.session.flush() + await self.session.commit() + return DealServicesCopyResponse(ok=True, message='Услуги успешно перенесены') + except Exception as e: + return DealServicesCopyResponse(ok=False, message=str(e)) + + @staticmethod + def get_service_price(service: models.Service, quantity: int): + price = 0 + price_ranges: list[models.ServicePriceRange] = service.price_ranges + for price_range in price_ranges: + if price_range.from_quantity <= quantity <= price_range.to_quantity: + price = price_range.price + break + + if not price and len(price_ranges) > 0: + price = price_ranges[0].price + if not price: + price = service.price + return price + # endregion # region Deal products @@ -662,4 +781,56 @@ class DealService(BaseService): await self.session.rollback() return DealUpdateProductResponse(ok=False, message=str(e)) + async def add_kit_to_deal_product(self, request: DealProductAddKitRequest) -> DealProductAddKitResponse: + try: + service_service = ServiceService(self.session) + kit = await service_service.get_kit_by_id(request.kit_id) + if not kit: + return DealProductAddKitResponse(ok=False, message='Указанный набор услуг не найден') + services: list[models.Service] = kit.services + deal_product_stmt = ( + select( + models.DealProduct + ) + .where( + models.DealProduct.deal_id == request.deal_id, + models.DealProduct.product_id == request.product_id + ) + ) + deal_product: Optional[models.DealProduct] = await self.session.scalar(deal_product_stmt) + if not deal_product: + return DealProductAddKitResponse(ok=False, message='Указанный товар не найден') + insert_data = [] + for service in services: + service_price = self.get_service_price(service, deal_product.quantity) + insert_data.append({ + 'deal_id': request.deal_id, + 'product_id': deal_product.product_id, + 'service_id': service.id, + 'price': service_price + }) + if not insert_data: + return DealProductAddKitResponse(ok=True, message='Набор услуг успешно добавлен к товару') + # Deleting previous DealProductService-s + delete_stmt = ( + delete( + models.DealProductService + ) + .where( + models.DealProductService.product_id == deal_product.product_id, + models.DealProductService.deal_id == request.deal_id + ) + ) + await self.session.execute(delete_stmt) + await self.session.flush() + + await self.session.execute( + insert(models.DealProductService), + insert_data + ) + await self.session.flush() + await self.session.commit() + return DealProductAddKitResponse(ok=True, message='Набор услуг успешно добавлен к товару') + except Exception as e: + return DealProductAddKitResponse(ok=False, message=str(e)) # endregion diff --git a/services/service.py b/services/service.py index 75a065c..d8c1859 100644 --- a/services/service.py +++ b/services/service.py @@ -1,12 +1,11 @@ -from sqlalchemy import select, update +from typing import Union + +from sqlalchemy import select, update, insert, delete from sqlalchemy.orm import joinedload -from models import Service, ServiceCategory, ServicePriceRange +from models import Service, ServiceCategory, ServicePriceRange, ServicesKit, services_kit_services from services.base import BaseService -from schemas.service import ServiceGetAllResponse, ServiceSchema, ServiceGetAllCategoriesResponse, \ - ServiceCategorySchema, ServiceCreateRequest, ServiceCreateResponse, ServiceCreateCategoryRequest, \ - ServiceCreateCategoryResponse, ServiceUpdateRequest, ServiceUpdateResponse, ServiceDeleteResponse, \ - ServiceDeleteRequest +from schemas.service import * class ServiceService(BaseService): @@ -121,3 +120,88 @@ class ServiceService(BaseService): for category in query.all(): categories.append(ServiceCategorySchema.model_validate(category)) return ServiceGetAllCategoriesResponse(categories=categories) + + async def get_kit_by_name(self, name: str) -> Optional[ServicesKit]: + return await self.session.scalar(select(ServicesKit).where(ServicesKit.name == name)) + + async def get_kit_by_id(self, kit_id: int) -> Optional[ServicesKit]: + return await self.session.scalar(select(ServicesKit).where(ServicesKit.id == kit_id)) + + async def get_all_kits(self) -> GetAllServicesKitsResponse: + stmt = ( + select( + ServicesKit + ) + .order_by( + ServicesKit.id.desc() + ) + ) + kits = (await self.session.scalars(stmt)).all() + kits_schemas = GetServiceKitSchema.from_orm_list(kits) + return GetAllServicesKitsResponse(services_kits=kits_schemas) + + async def create_kit(self, request: CreateServicesKitRequest) -> CreateServicesKitResponse: + try: + if await self.get_kit_by_name(request.data.name): + return CreateServicesKitResponse(ok=False, message='Набор услуг с таким названием уже существует') + base_fields = request.data.model_dump_parent() + kit = ServicesKit(**base_fields) + self.session.add(kit) + await self.session.flush() + # Appending services + insert_data = [] + for service_id in request.data.services_ids: + insert_data.append({ + 'service_id': service_id, + 'services_kit_id': kit.id + }) + if insert_data: + await self.session.execute( + insert(services_kit_services), + insert_data + ) + await self.session.flush() + await self.session.commit() + return CreateServicesKitResponse(ok=True, message='Набор услуг успешно создан') + + except Exception as e: + return CreateServicesKitResponse(ok=False, message=str(e)) + + async def update_kit(self, request: UpdateServicesKitRequest) -> UpdateServicesKitResponse: + try: + kit = await self.get_kit_by_id(request.data.id) + if not kit: + return UpdateServicesKitResponse(ok=False, message='Указанный набор услуг не существует') + base_fields = request.data.model_dump_parent() + stmt = update(ServicesKit).values(**base_fields).where(ServicesKit.id == request.data.id) + await self.session.execute(stmt) + await self.session.flush() + + # Deleting previous services + stmt = ( + delete( + services_kit_services + ).where( + services_kit_services.c.services_kit_id == kit.id + ) + ) + await self.session.execute(stmt) + await self.session.flush() + + insert_data = [] + for service_id in request.data.services_ids: + insert_data.append({ + 'service_id': service_id, + 'services_kit_id': kit.id + }) + if insert_data: + await self.session.execute( + insert(services_kit_services), + insert_data + ) + await self.session.flush() + await self.session.commit() + return UpdateServicesKitResponse(ok=True, message='Набор услуг успешно обновлен') + + except Exception as e: + return UpdateServicesKitResponse(ok=False, message=str(e)) diff --git a/test.py b/test.py index a4735e2..3bcfe44 100644 --- a/test.py +++ b/test.py @@ -5,6 +5,7 @@ from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import joinedload +import models from backend.session import session_maker from models import User, PaymentRecord @@ -12,31 +13,24 @@ from models import User, PaymentRecord async def main(): session: AsyncSession = session_maker() try: - d = datetime.date.today() - d = d.replace(day=1) - print(d) - stmt = ( + deal_id = 133 + source_product_id = 253 + source_services_stmt = ( select( - PaymentRecord - ) - .select_from(PaymentRecord) - .options( - joinedload( - PaymentRecord.user - ) + models.DealProductService ) .where( - func.date(func.date_trunc('month', PaymentRecord.start_date)) == d, - func.date(func.date_trunc('month', PaymentRecord.end_date)) == d, - PaymentRecord.start_date == PaymentRecord.end_date, - # PaymentRecord.user_id.in_(request.user_ids) + models.DealProductService.product_id == source_product_id, + models.DealProductService.deal_id == deal_id, ) ) - print(stmt.compile(compile_kwargs={ - 'literal_binds': True - })) - query_result = (await session.scalars(stmt)).all() - print(query_result) + result = (await session.scalars(source_services_stmt)).all() + services = [d.service for d in result] + for service in services: + print( + service.price_ranges + + ) except Exception as e: print(e) await session.close()