diff --git a/main.py b/main.py index 660a5b9..c6e5655 100644 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ import routers origins = [ 'http://localhost:5173' ] -app = FastAPI() +app = FastAPI(separate_input_output_schemas=False) app.add_middleware( CORSMiddleware, diff --git a/models/deal.py b/models/deal.py index 82296a1..efbfd99 100644 --- a/models/deal.py +++ b/models/deal.py @@ -33,7 +33,7 @@ class Deal(BaseModel): is_completed = Column(Boolean, nullable=False, server_default='0', default=False, comment='Завершена') services = relationship('DealService', back_populates='deal') - + products = relationship('DealProduct', back_populates='deal') class DealStatusHistory(BaseModel): __tablename__ = 'deals_status_history' diff --git a/models/secondary.py b/models/secondary.py index fc73fc5..a44eae5 100644 --- a/models/secondary.py +++ b/models/secondary.py @@ -13,3 +13,15 @@ class DealService(BaseModel): service = relationship('Service') quantity = Column(Integer, nullable=False, comment='Кол-во услуги') + + +class DealProduct(BaseModel): + __tablename__ = 'deal_products' + deal_id = Column(Integer, ForeignKey('deals.id'), nullable=False, comment='ID Сделки', primary_key=True) + deal = relationship('Deal', back_populates='products') + + product_id = Column(Integer, ForeignKey('products.id'), nullable=False, comment='ID Продукта', primary_key=True) + product = relationship('Product') + + quantity = Column(Integer, nullable=False, comment='Кол-во продукта') + diff --git a/routers/deal.py b/routers/deal.py index 672d7e6..279ed0e 100644 --- a/routers/deal.py +++ b/routers/deal.py @@ -53,11 +53,82 @@ async def get_summary( @deal_router.post( - '/services/add', + '/services/add/multiple', response_model=DealAddServicesResponse, + operation_id='addMultipleDealServices' ) async def services_add( request: DealAddServicesRequest, session: Annotated[AsyncSession, Depends(get_session)] ): return await DealService(session).add_services(request) + + +@deal_router.post( + '/services/add', + response_model=DealAddServiceResponse, + operation_id='addDealService' +) +async def services_add( + request: DealAddServiceRequest, + session: Annotated[AsyncSession, Depends(get_session)] +): + return await DealService(session).add_service(request) + + +@deal_router.post( + '/services/update-quantity', + response_model=DealUpdateServiceQuantityResponse, + operation_id='updateDealServiceQuantity' +) +async def services_update( + request: DealUpdateServiceQuantityRequest, + session: Annotated[AsyncSession, Depends(get_session)] +): + return await DealService(session).update_service_quantity(request) + +@deal_router.post( + '/services/delete', + response_model=DealDeleteServiceResponse, + operation_id='deleteDealService' +) +async def services_delete( + request: DealDeleteServiceRequest, + session: Annotated[AsyncSession, Depends(get_session)] +): + return await DealService(session).delete_service(request) + +@deal_router.post( + '/services/delete/multiple', + response_model=DealDeleteServicesResponse, + operation_id='deleteMultipleDealServices' +) +async def services_delete( + request: DealDeleteServicesRequest, + session: Annotated[AsyncSession, Depends(get_session)] +): + return await DealService(session).delete_services(request) + + +@deal_router.get( + '/get-all', + response_model=DealGetAllResponse, + operation_id='getAllDeals' +) +async def get_all( + session: Annotated[AsyncSession, Depends(get_session)] +): + return await DealService(session).get_all() + + +# endpoint to get deal by id +@deal_router.get( + '/get/{deal_id}', + response_model=DealSchema, + operation_id='getDealById' +) +async def get_deal_by_id( + deal_id: int, + session: Annotated[AsyncSession, Depends(get_session)] +): + return await DealService(session).get_by_id(deal_id) diff --git a/schemas/client.py b/schemas/client.py index 18e00f5..14a23d5 100644 --- a/schemas/client.py +++ b/schemas/client.py @@ -3,6 +3,7 @@ from typing import List from schemas.base import CustomModelCamel, OkMessageSchema +# region Entities class ClientDetailsSchema(CustomModelCamel): address: str | None = None phone_number: str | None = None @@ -16,19 +17,37 @@ class ClientSchema(CustomModelCamel): details: ClientDetailsSchema | None = None +# endregion + +# region Requests class ClientSearchRequest(CustomModelCamel): name: str -class ClientSearchResponse(CustomModelCamel): - clients: List[ClientSchema] - - class ClientUpdateDetailsRequest(CustomModelCamel): client_id: int details: ClientDetailsSchema +class ClientCreateRequest(CustomModelCamel): + data: ClientSchema + + +class ClientUpdateRequest(CustomModelCamel): + data: ClientSchema + + +class ClientDeleteRequest(CustomModelCamel): + client_id: int + + +# endregion + +# region Responses +class ClientSearchResponse(CustomModelCamel): + clients: List[ClientSchema] + + class ClientUpdateDetailsResponse(CustomModelCamel): ok: bool @@ -41,21 +60,10 @@ class ClientCreateResponse(OkMessageSchema): pass -class ClientCreateRequest(CustomModelCamel): - data: ClientSchema - - -class ClientUpdateRequest(CustomModelCamel): - data: ClientSchema - - class ClientUpdateResponse(OkMessageSchema): pass -class ClientDeleteRequest(CustomModelCamel): - client_id: int - - class ClientDeleteResponse(OkMessageSchema): pass +# endregion diff --git a/schemas/deal.py b/schemas/deal.py index a304a9d..e2915d0 100644 --- a/schemas/deal.py +++ b/schemas/deal.py @@ -1,8 +1,9 @@ import datetime from typing import List -from schemas.base import CustomModelCamel +from schemas.base import CustomModelCamel, OkMessageSchema from schemas.client import ClientSchema +from schemas.service import ServiceSchema # region Entities @@ -23,10 +24,20 @@ class DealSummary(CustomModelCamel): class DealServiceSchema(CustomModelCamel): - id: int + service: ServiceSchema quantity: int +class DealSchema(CustomModelCamel): + id: int + name: str + client_id: int + created_at: datetime.datetime + current_status: int + services: List[DealServiceSchema] + # total_price: int + + # endregion Entities # region Requests @@ -56,6 +67,36 @@ class DealAddServicesRequest(CustomModelCamel): services: list[DealServiceSchema] +class DealGetAllResponse(CustomModelCamel): + deals: List[DealSchema] + + +class DealUpdateServiceQuantityRequest(CustomModelCamel): + deal_id: int + service_id: int + quantity: int + + +class DealAddServiceRequest(CustomModelCamel): + deal_id: int + service_id: int + quantity: int + + +class DealDeleteServiceRequest(CustomModelCamel): + deal_id: int + service_id: int + + +class DealDeleteServicesResponse(OkMessageSchema): + pass + + +class DealDeleteServicesRequest(CustomModelCamel): + deal_id: int + service_ids: List[int] + + # endregion Requests # region Responses @@ -79,4 +120,17 @@ class DealSummaryResponse(CustomModelCamel): class DealAddServicesResponse(CustomModelCamel): ok: bool message: str + + +class DealUpdateServiceQuantityResponse(CustomModelCamel): + ok: bool + message: str + + +class DealAddServiceResponse(OkMessageSchema): + pass + + +class DealDeleteServiceResponse(OkMessageSchema): + pass # endregion Responses diff --git a/services/client.py b/services/client.py index 0a9b630..acbde1d 100644 --- a/services/client.py +++ b/services/client.py @@ -1,14 +1,12 @@ import datetime -from typing import Union, Annotated +from typing import Union -from fastapi import Depends from sqlalchemy import select, update from sqlalchemy.orm import joinedload from models import Client, ClientDetails, User -from services.auth import get_current_user -from services.base import BaseService from schemas.client import * +from services.base import BaseService class ClientService(BaseService): diff --git a/services/deal.py b/services/deal.py index 95b4a4c..0f4a4c5 100644 --- a/services/deal.py +++ b/services/deal.py @@ -1,6 +1,6 @@ import models.secondary from typing import Union - +import models.deal from fastapi import HTTPException from sqlalchemy import select, func from sqlalchemy.orm import joinedload, selectinload @@ -156,3 +156,101 @@ class DealService(BaseService): ) await self.session.commit() return DealAddServicesResponse(ok=True, message='Услуги успешно добавлены') + + async def get_all(self) -> DealGetAllResponse: + deals_query = await self.session.scalars(select(Deal).options(joinedload(Deal.client))) + deals = deals_query.all() + result = [] + for deal in deals: + result.append(DealSchema.model_validate(deal)) + return DealGetAllResponse(deals=result) + + async def get_by_id(self, deal_id: int) -> DealSchema: + deal = await self.session.scalar( + select(Deal) + .options( + joinedload(Deal.client), + selectinload(Deal.services) + .joinedload(models.secondary.DealService.service) + .joinedload(Service.category)) + .where(Deal.id == deal_id) + ) + if not deal: + raise HTTPException(status_code=404, detail="Сделка не найдена") + return DealSchema.model_validate(deal) + + async def update_service_quantity(self, + request: DealUpdateServiceQuantityRequest) -> DealUpdateServiceQuantityResponse: + try: + deal_service = await self.session.scalar( + select(models.secondary.DealService) + .where(models.secondary.DealService.deal_id == request.deal_id, + models.secondary.DealService.service_id == request.service_id) + ) + if not deal_service: + raise HTTPException(status_code=404, detail="Сделка не найдена") + deal_service.quantity = request.quantity + await self.session.commit() + return DealUpdateServiceQuantityResponse(ok=True, message='Количество успешно обновлено') + except Exception as e: + await self.session.rollback() + return DealUpdateServiceQuantityResponse(ok=False, message=str(e)) + + async def add_service(self, request: DealAddServiceRequest) -> DealAddServiceResponse: + try: + deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id)) + if not deal: + raise HTTPException(status_code=404, detail="Сделка не найдена") + service = await self.session.scalar(select(Service).where(Service.id == request.service_id)) + if not service: + raise HTTPException(status_code=404, detail="Услуга не найдена") + # Preventing duplicates + deal_service = await self.session.scalar( + select(models.secondary.DealService) + .where(models.secondary.DealService.deal_id == request.deal_id, + models.secondary.DealService.service_id == request.service_id) + ) + if deal_service: + raise HTTPException(status_code=400, detail="Услуга уже добавлена") + deal_service = models.secondary.DealService( + deal_id=request.deal_id, + service_id=request.service_id, + quantity=request.quantity + ) + self.session.add(deal_service) + await self.session.commit() + return DealAddServiceResponse(ok=True, message='Услуга успешно добавлена') + except Exception as e: + await self.session.rollback() + return DealAddServiceResponse(ok=False, message=str(e)) + + async def delete_service(self, request: DealDeleteServiceRequest) -> DealDeleteServiceResponse: + try: + deal_service = await self.session.scalar( + select(models.secondary.DealService) + .where(models.secondary.DealService.deal_id == request.deal_id, + models.secondary.DealService.service_id == request.service_id) + ) + if not deal_service: + raise HTTPException(status_code=404, detail="Сделка не найдена") + await self.session.delete(deal_service) + await self.session.commit() + return DealDeleteServiceResponse(ok=True, message='Услуга успешно удалена') + except Exception as e: + await self.session.rollback() + return DealDeleteServiceResponse(ok=False, message=str(e)) + + async def delete_services(self, request: DealDeleteServicesRequest) -> DealDeleteServicesResponse: + try: + deal_services = await self.session.scalars( + select(models.secondary.DealService) + .where(models.secondary.DealService.deal_id == request.deal_id, + models.secondary.DealService.service_id.in_(request.service_ids)) + ) + for deal_service in deal_services: + await self.session.delete(deal_service) + await self.session.commit() + return DealDeleteServicesResponse(ok=True, message='Услуги успешно удалены') + except Exception as e: + await self.session.rollback() + return DealDeleteServicesResponse(ok=False, message=str(e))