crappy
This commit is contained in:
		@@ -25,7 +25,7 @@ class ClientDetails(BaseModel):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    address = Column(String)
 | 
					    address = Column(String)
 | 
				
			||||||
    phone_number = Column(String)
 | 
					    phone_number = Column(String)
 | 
				
			||||||
    inn = Column(BigInteger)
 | 
					    inn = Column(String)
 | 
				
			||||||
    email = Column(String)
 | 
					    email = Column(String)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    last_modified_at = Column(DateTime, nullable=False)
 | 
					    last_modified_at = Column(DateTime, nullable=False)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,6 +35,7 @@ class Deal(BaseModel):
 | 
				
			|||||||
    services = relationship('DealService', back_populates='deal')
 | 
					    services = relationship('DealService', back_populates='deal')
 | 
				
			||||||
    products = relationship('DealProduct', back_populates='deal')
 | 
					    products = relationship('DealProduct', back_populates='deal')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										100
									
								
								routers/deal.py
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								routers/deal.py
									
									
									
									
									
								
							@@ -52,6 +52,44 @@ async def get_summary(
 | 
				
			|||||||
    return await DealService(session).get_summary()
 | 
					    return await DealService(session).get_summary()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@deal_router.post(
 | 
				
			||||||
 | 
					    '/update-general-info',
 | 
				
			||||||
 | 
					    response_model=DealUpdateGeneralInfoResponse,
 | 
				
			||||||
 | 
					    operation_id='updateDealGeneralInfo'
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def update_general_info(
 | 
				
			||||||
 | 
					        request: DealUpdateGeneralInfoRequest,
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)]
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await DealService(session).update_general_info(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# region Deal services
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@deal_router.post(
 | 
					@deal_router.post(
 | 
				
			||||||
    '/services/add/multiple',
 | 
					    '/services/add/multiple',
 | 
				
			||||||
    response_model=DealAddServicesResponse,
 | 
					    response_model=DealAddServicesResponse,
 | 
				
			||||||
@@ -87,6 +125,7 @@ async def services_update(
 | 
				
			|||||||
):
 | 
					):
 | 
				
			||||||
    return await DealService(session).update_service_quantity(request)
 | 
					    return await DealService(session).update_service_quantity(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@deal_router.post(
 | 
					@deal_router.post(
 | 
				
			||||||
    '/services/delete',
 | 
					    '/services/delete',
 | 
				
			||||||
    response_model=DealDeleteServiceResponse,
 | 
					    response_model=DealDeleteServiceResponse,
 | 
				
			||||||
@@ -98,6 +137,7 @@ async def services_delete(
 | 
				
			|||||||
):
 | 
					):
 | 
				
			||||||
    return await DealService(session).delete_service(request)
 | 
					    return await DealService(session).delete_service(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@deal_router.post(
 | 
					@deal_router.post(
 | 
				
			||||||
    '/services/delete/multiple',
 | 
					    '/services/delete/multiple',
 | 
				
			||||||
    response_model=DealDeleteServicesResponse,
 | 
					    response_model=DealDeleteServicesResponse,
 | 
				
			||||||
@@ -110,26 +150,46 @@ async def services_delete(
 | 
				
			|||||||
    return await DealService(session).delete_services(request)
 | 
					    return await DealService(session).delete_services(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@deal_router.get(
 | 
					# endregion
 | 
				
			||||||
    '/get-all',
 | 
					
 | 
				
			||||||
    response_model=DealGetAllResponse,
 | 
					# region Deal products
 | 
				
			||||||
    operation_id='getAllDeals'
 | 
					@deal_router.post(
 | 
				
			||||||
)
 | 
					    '/products/update-quantity',
 | 
				
			||||||
async def get_all(
 | 
					    response_model=DealUpdateProductQuantityResponse,
 | 
				
			||||||
        session: Annotated[AsyncSession, Depends(get_session)]
 | 
					    operation_id='updateDealProductQuantity')
 | 
				
			||||||
):
 | 
					async def products_update(
 | 
				
			||||||
    return await DealService(session).get_all()
 | 
					        request: DealUpdateProductQuantityRequest,
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)]):
 | 
				
			||||||
 | 
					    return await DealService(session).update_product_quantity(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# endpoint to get deal by id
 | 
					@deal_router.post(
 | 
				
			||||||
@deal_router.get(
 | 
					    '/products/add',
 | 
				
			||||||
    '/get/{deal_id}',
 | 
					    response_model=DealAddProductResponse,
 | 
				
			||||||
    response_model=DealSchema,
 | 
					    operation_id='addDealProduct')
 | 
				
			||||||
    operation_id='getDealById'
 | 
					async def products_add(
 | 
				
			||||||
)
 | 
					        request: DealAddProductRequest,
 | 
				
			||||||
async def get_deal_by_id(
 | 
					        session: Annotated[AsyncSession, Depends(get_session)]):
 | 
				
			||||||
        deal_id: int,
 | 
					    return await DealService(session).add_product(request)
 | 
				
			||||||
        session: Annotated[AsyncSession, Depends(get_session)]
 | 
					 | 
				
			||||||
):
 | 
					 | 
				
			||||||
    return await DealService(session).get_by_id(deal_id)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@deal_router.post(
 | 
				
			||||||
 | 
					    '/products/delete',
 | 
				
			||||||
 | 
					    response_model=DealDeleteProductResponse,
 | 
				
			||||||
 | 
					    operation_id='deleteDealProduct')
 | 
				
			||||||
 | 
					async def products_delete(
 | 
				
			||||||
 | 
					        request: DealDeleteProductRequest,
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)]):
 | 
				
			||||||
 | 
					    return await DealService(session).delete_product(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@deal_router.post(
 | 
				
			||||||
 | 
					    '/products/delete/multiple',
 | 
				
			||||||
 | 
					    response_model=DealDeleteProductsResponse,
 | 
				
			||||||
 | 
					    operation_id='deleteMultipleDealProducts')
 | 
				
			||||||
 | 
					async def products_delete(
 | 
				
			||||||
 | 
					        request: DealDeleteProductsRequest,
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)]):
 | 
				
			||||||
 | 
					    return await DealService(session).delete_products(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# endregion
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,3 +62,49 @@ async def get_product(
 | 
				
			|||||||
        session: Annotated[AsyncSession, Depends(get_session)]
 | 
					        session: Annotated[AsyncSession, Depends(get_session)]
 | 
				
			||||||
):
 | 
					):
 | 
				
			||||||
    return await ProductService(session).get_by_client_id(client_id, pagination)
 | 
					    return await ProductService(session).get_by_client_id(client_id, pagination)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@product_router.get('/get-by-id',
 | 
				
			||||||
 | 
					                    response_model=ProductSchema,
 | 
				
			||||||
 | 
					                    operation_id='get_product_by_id')
 | 
				
			||||||
 | 
					async def get_product_by_id(
 | 
				
			||||||
 | 
					        product_id: int,
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)]
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await ProductService(session).get_by_id(product_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@product_router.post(
 | 
				
			||||||
 | 
					    '/barcode/add',
 | 
				
			||||||
 | 
					    response_model=ProductAddBarcodeResponse,
 | 
				
			||||||
 | 
					    operation_id='add_product_barcode'
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def add_product_barcode(
 | 
				
			||||||
 | 
					        request: ProductAddBarcodeRequest,
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)]
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await ProductService(session).add_barcode(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@product_router.get(
 | 
				
			||||||
 | 
					    '/barcode/exists',
 | 
				
			||||||
 | 
					    response_model=ProductExistsBarcodeResponse,
 | 
				
			||||||
 | 
					    operation_id='exists_product_barcode'
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def exists_product_barcode(
 | 
				
			||||||
 | 
					        product_id: int,
 | 
				
			||||||
 | 
					        barcode: str,
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)]
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await ProductService(session).exists_barcode(product_id, barcode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@product_router.post(
 | 
				
			||||||
 | 
					    '/barcode/generate',
 | 
				
			||||||
 | 
					    response_model=ProductGenerateBarcodeResponse,
 | 
				
			||||||
 | 
					    operation_id='generate_product_barcode'
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def generate_product_barcode(
 | 
				
			||||||
 | 
					        request: ProductGenerateBarcodeRequest,
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)]
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await ProductService(session).generate_barcode(request)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
from typing import List
 | 
					from typing import List
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from pydantic import validator, field_validator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from schemas.base import CustomModelCamel, OkMessageSchema
 | 
					from schemas.base import CustomModelCamel, OkMessageSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -7,9 +9,13 @@ from schemas.base import CustomModelCamel, OkMessageSchema
 | 
				
			|||||||
class ClientDetailsSchema(CustomModelCamel):
 | 
					class ClientDetailsSchema(CustomModelCamel):
 | 
				
			||||||
    address: str | None = None
 | 
					    address: str | None = None
 | 
				
			||||||
    phone_number: str | None = None
 | 
					    phone_number: str | None = None
 | 
				
			||||||
    inn: int | None = None
 | 
					    inn: str | None = None
 | 
				
			||||||
    email: str | None = None
 | 
					    email: str | None = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @field_validator("phone_number", "inn", "email", "address", mode="before")
 | 
				
			||||||
 | 
					    def empty_string_to_none(cls, v):
 | 
				
			||||||
 | 
					        return '' if v is None else v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ClientSchema(CustomModelCamel):
 | 
					class ClientSchema(CustomModelCamel):
 | 
				
			||||||
    id: int
 | 
					    id: int
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ from schemas.base import CustomModelCamel, OkMessageSchema
 | 
				
			|||||||
from schemas.client import ClientSchema
 | 
					from schemas.client import ClientSchema
 | 
				
			||||||
from schemas.product import ProductSchema
 | 
					from schemas.product import ProductSchema
 | 
				
			||||||
from schemas.service import ServiceSchema
 | 
					from schemas.service import ServiceSchema
 | 
				
			||||||
 | 
					from schemas.user import UserSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# region Entities
 | 
					# region Entities
 | 
				
			||||||
@@ -34,6 +35,14 @@ class DealProductSchema(CustomModelCamel):
 | 
				
			|||||||
    quantity: int
 | 
					    quantity: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealStatusHistorySchema(CustomModelCamel):
 | 
				
			||||||
 | 
					    user: UserSchema
 | 
				
			||||||
 | 
					    changed_at: datetime.datetime
 | 
				
			||||||
 | 
					    from_status: int
 | 
				
			||||||
 | 
					    to_status: int
 | 
				
			||||||
 | 
					    next_status_deadline: datetime.datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealSchema(CustomModelCamel):
 | 
					class DealSchema(CustomModelCamel):
 | 
				
			||||||
    id: int
 | 
					    id: int
 | 
				
			||||||
    name: str
 | 
					    name: str
 | 
				
			||||||
@@ -42,7 +51,16 @@ class DealSchema(CustomModelCamel):
 | 
				
			|||||||
    current_status: int
 | 
					    current_status: int
 | 
				
			||||||
    services: List[DealServiceSchema]
 | 
					    services: List[DealServiceSchema]
 | 
				
			||||||
    products: List[DealProductSchema]
 | 
					    products: List[DealProductSchema]
 | 
				
			||||||
    # total_price: int
 | 
					    status_history: List[DealStatusHistorySchema]
 | 
				
			||||||
 | 
					    is_deleted: bool
 | 
				
			||||||
 | 
					    is_completed: bool
 | 
				
			||||||
 | 
					    client: ClientSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealGeneralInfoSchema(CustomModelCamel):
 | 
				
			||||||
 | 
					    name: str
 | 
				
			||||||
 | 
					    is_deleted: bool
 | 
				
			||||||
 | 
					    is_completed: bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# endregion Entities
 | 
					# endregion Entities
 | 
				
			||||||
@@ -74,10 +92,6 @@ class DealAddServicesRequest(CustomModelCamel):
 | 
				
			|||||||
    services: list[DealServiceSchema]
 | 
					    services: list[DealServiceSchema]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealGetAllResponse(CustomModelCamel):
 | 
					 | 
				
			||||||
    deals: List[DealSchema]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DealUpdateServiceQuantityRequest(CustomModelCamel):
 | 
					class DealUpdateServiceQuantityRequest(CustomModelCamel):
 | 
				
			||||||
    deal_id: int
 | 
					    deal_id: int
 | 
				
			||||||
    service_id: int
 | 
					    service_id: int
 | 
				
			||||||
@@ -95,18 +109,52 @@ class DealDeleteServiceRequest(CustomModelCamel):
 | 
				
			|||||||
    service_id: int
 | 
					    service_id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealDeleteServicesResponse(OkMessageSchema):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DealDeleteServicesRequest(CustomModelCamel):
 | 
					class DealDeleteServicesRequest(CustomModelCamel):
 | 
				
			||||||
    deal_id: int
 | 
					    deal_id: int
 | 
				
			||||||
    service_ids: List[int]
 | 
					    service_ids: List[int]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealUpdateProductQuantityRequest(CustomModelCamel):
 | 
				
			||||||
 | 
					    deal_id: int
 | 
				
			||||||
 | 
					    product_id: int
 | 
				
			||||||
 | 
					    quantity: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealAddProductRequest(CustomModelCamel):
 | 
				
			||||||
 | 
					    deal_id: int
 | 
				
			||||||
 | 
					    product_id: int
 | 
				
			||||||
 | 
					    quantity: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealDeleteProductRequest(CustomModelCamel):
 | 
				
			||||||
 | 
					    deal_id: int
 | 
				
			||||||
 | 
					    product_id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealDeleteProductsRequest(CustomModelCamel):
 | 
				
			||||||
 | 
					    deal_id: int
 | 
				
			||||||
 | 
					    product_ids: List[int]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealUpdateGeneralInfoRequest(CustomModelCamel):
 | 
				
			||||||
 | 
					    deal_id: int
 | 
				
			||||||
 | 
					    data: DealGeneralInfoSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# endregion Requests
 | 
					# endregion Requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# region Responses
 | 
					# region Responses
 | 
				
			||||||
 | 
					class DealUpdateProductQuantityResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealDeleteServicesResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealGetAllResponse(CustomModelCamel):
 | 
				
			||||||
 | 
					    deals: List[DealSchema]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealChangeStatusResponse(CustomModelCamel):
 | 
					class DealChangeStatusResponse(CustomModelCamel):
 | 
				
			||||||
    ok: bool
 | 
					    ok: bool
 | 
				
			||||||
@@ -140,4 +188,20 @@ class DealAddServiceResponse(OkMessageSchema):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class DealDeleteServiceResponse(OkMessageSchema):
 | 
					class DealDeleteServiceResponse(OkMessageSchema):
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealDeleteProductResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealDeleteProductsResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealAddProductResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealUpdateGeneralInfoResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
# endregion Responses
 | 
					# endregion Responses
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,25 @@
 | 
				
			|||||||
from typing import List
 | 
					from typing import List
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from pydantic import validator, field_validator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from models import ProductBarcode
 | 
				
			||||||
from schemas.base import CustomModelCamel, PaginationInfoSchema, OkMessageSchema
 | 
					from schemas.base import CustomModelCamel, PaginationInfoSchema, OkMessageSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# region Entities
 | 
					# region Entities
 | 
				
			||||||
class ProductBarcodeSchema(CustomModelCamel):
 | 
					 | 
				
			||||||
    barcode: str
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ProductSchema(CustomModelCamel):
 | 
					class ProductSchema(CustomModelCamel):
 | 
				
			||||||
    id: int
 | 
					    id: int
 | 
				
			||||||
    name: str
 | 
					    name: str
 | 
				
			||||||
    article: str
 | 
					    article: str
 | 
				
			||||||
    client_id: int
 | 
					    client_id: int
 | 
				
			||||||
    barcodes: list[ProductBarcodeSchema]
 | 
					    barcodes: list[str]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @field_validator('barcodes', mode="before")
 | 
				
			||||||
 | 
					    def barcodes_to_list(cls, v):
 | 
				
			||||||
 | 
					        if isinstance(v, list) and all([type(barcode) is ProductBarcode for barcode in v]):
 | 
				
			||||||
 | 
					            return [barcode.barcode for barcode in v]
 | 
				
			||||||
 | 
					        return v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# endregion
 | 
					# endregion
 | 
				
			||||||
@@ -34,6 +40,20 @@ class ProductUpdateRequest(CustomModelCamel):
 | 
				
			|||||||
    product: ProductSchema
 | 
					    product: ProductSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProductAddBarcodeRequest(CustomModelCamel):
 | 
				
			||||||
 | 
					    product_id: int
 | 
				
			||||||
 | 
					    barcode: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProductDeleteBarcodeRequest(CustomModelCamel):
 | 
				
			||||||
 | 
					    product_id: int
 | 
				
			||||||
 | 
					    barcode: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProductGenerateBarcodeRequest(CustomModelCamel):
 | 
				
			||||||
 | 
					    product_id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# endregion
 | 
					# endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# region Responses
 | 
					# region Responses
 | 
				
			||||||
@@ -52,4 +72,20 @@ class ProductDeleteResponse(OkMessageSchema):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class ProductUpdateResponse(OkMessageSchema):
 | 
					class ProductUpdateResponse(OkMessageSchema):
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProductAddBarcodeResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProductDeleteBarcodeResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProductGenerateBarcodeResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    barcode: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProductExistsBarcodeResponse(CustomModelCamel):
 | 
				
			||||||
 | 
					    exists: bool
 | 
				
			||||||
# endregion
 | 
					# endregion
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8
									
								
								schemas/user.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								schemas/user.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					from schemas.base import CustomModelCamel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UserSchema(CustomModelCamel):
 | 
				
			||||||
 | 
					    id: int
 | 
				
			||||||
 | 
					    telegram_id: int
 | 
				
			||||||
 | 
					    phone_number: str | None = None
 | 
				
			||||||
 | 
					    is_admin: bool
 | 
				
			||||||
@@ -75,7 +75,7 @@ class ClientService(BaseService):
 | 
				
			|||||||
        try:
 | 
					        try:
 | 
				
			||||||
            client = await self.get_by_name(request.data.name)
 | 
					            client = await self.get_by_name(request.data.name)
 | 
				
			||||||
            if client:
 | 
					            if client:
 | 
				
			||||||
                return ClientCreateResponse(ok=False, message='Client already exists')
 | 
					                return ClientCreateResponse(ok=False, message='Клиент с таким именем уже существует')
 | 
				
			||||||
            await self.create_client_raw(user, request.data.name, request.data.details)
 | 
					            await self.create_client_raw(user, request.data.name, request.data.details)
 | 
				
			||||||
            await self.session.commit()
 | 
					            await self.session.commit()
 | 
				
			||||||
            return ClientCreateResponse(ok=True, message='Client created')
 | 
					            return ClientCreateResponse(ok=True, message='Client created')
 | 
				
			||||||
@@ -86,11 +86,11 @@ class ClientService(BaseService):
 | 
				
			|||||||
        try:
 | 
					        try:
 | 
				
			||||||
            client = await self.get_by_id(request.data.id)
 | 
					            client = await self.get_by_id(request.data.id)
 | 
				
			||||||
            if not client:
 | 
					            if not client:
 | 
				
			||||||
                return ClientUpdateResponse(ok=False, message='Client not found')
 | 
					                return ClientUpdateResponse(ok=False, message='Клиент не найден')
 | 
				
			||||||
            await self.session.execute(update(Client).where(Client.id == client.id).values(name=request.data.name))
 | 
					            await self.session.execute(update(Client).where(Client.id == client.id).values(name=request.data.name))
 | 
				
			||||||
            await self.update_details(user, client, request.data.details)
 | 
					            await self.update_details(user, client, request.data.details)
 | 
				
			||||||
            await self.session.commit()
 | 
					            await self.session.commit()
 | 
				
			||||||
            return ClientUpdateResponse(ok=True, message='Client updated')
 | 
					            return ClientUpdateResponse(ok=True, message='Клиент обновлен')
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            return ClientUpdateResponse(ok=False, message=str(e))
 | 
					            return ClientUpdateResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -98,9 +98,9 @@ class ClientService(BaseService):
 | 
				
			|||||||
        try:
 | 
					        try:
 | 
				
			||||||
            client = await self.get_by_id(request.client_id)
 | 
					            client = await self.get_by_id(request.client_id)
 | 
				
			||||||
            if not client:
 | 
					            if not client:
 | 
				
			||||||
                return ClientDeleteResponse(ok=False, message='Client not found')
 | 
					                return ClientDeleteResponse(ok=False, message='Клиент не найден')
 | 
				
			||||||
            await self.session.delete(client)
 | 
					            await self.session.delete(client)
 | 
				
			||||||
            await self.session.commit()
 | 
					            await self.session.commit()
 | 
				
			||||||
            return ClientDeleteResponse(ok=True, message='Client deleted')
 | 
					            return ClientDeleteResponse(ok=True, message='Клиент удален')
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            return ClientDeleteResponse(ok=False, message=str(e))
 | 
					            return ClientDeleteResponse(ok=False, message=str(e))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										165
									
								
								services/deal.py
									
									
									
									
									
								
							
							
						
						
									
										165
									
								
								services/deal.py
									
									
									
									
									
								
							@@ -5,7 +5,7 @@ from fastapi import HTTPException
 | 
				
			|||||||
from sqlalchemy import select, func
 | 
					from sqlalchemy import select, func
 | 
				
			||||||
from sqlalchemy.orm import joinedload, selectinload
 | 
					from sqlalchemy.orm import joinedload, selectinload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from models import User, Service
 | 
					from models import User, Service, Client
 | 
				
			||||||
from models.deal import *
 | 
					from models.deal import *
 | 
				
			||||||
from schemas.client import ClientDetailsSchema
 | 
					from schemas.client import ClientDetailsSchema
 | 
				
			||||||
from schemas.deal import *
 | 
					from schemas.deal import *
 | 
				
			||||||
@@ -15,6 +15,7 @@ from services.client import ClientService
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class DealService(BaseService):
 | 
					class DealService(BaseService):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # region Deal
 | 
				
			||||||
    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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -116,6 +117,56 @@ class DealService(BaseService):
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
        return DealSummaryResponse(summaries=summaries)
 | 
					        return DealSummaryResponse(summaries=summaries)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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).joinedload(Client.details),
 | 
				
			||||||
 | 
					                selectinload(Deal.services)
 | 
				
			||||||
 | 
					                .joinedload(models.secondary.DealService.service)
 | 
				
			||||||
 | 
					                .joinedload(Service.category),
 | 
				
			||||||
 | 
					                selectinload(Deal.products)
 | 
				
			||||||
 | 
					                .joinedload(models.secondary.DealProduct.product)
 | 
				
			||||||
 | 
					                .joinedload(models.Product.client),
 | 
				
			||||||
 | 
					                selectinload(Deal.products)
 | 
				
			||||||
 | 
					                .joinedload(models.secondary.DealProduct.product)
 | 
				
			||||||
 | 
					                .joinedload(models.Product.barcodes),
 | 
				
			||||||
 | 
					                selectinload(Deal.status_history)
 | 
				
			||||||
 | 
					                .joinedload(DealStatusHistory.user),
 | 
				
			||||||
 | 
					                selectinload(Deal.status_history)
 | 
				
			||||||
 | 
					                .noload(DealStatusHistory.deal)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .where(Deal.id == deal_id)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        if not deal:
 | 
				
			||||||
 | 
					            raise HTTPException(status_code=404, detail="Сделка не найдена")
 | 
				
			||||||
 | 
					        return DealSchema.model_validate(deal)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def update_general_info(self, request: DealUpdateGeneralInfoRequest) -> DealUpdateGeneralInfoResponse:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id))
 | 
				
			||||||
 | 
					            if not deal:
 | 
				
			||||||
 | 
					                raise HTTPException(status_code=404, detail="Сделка не найдена")
 | 
				
			||||||
 | 
					            deal.name = request.data.name
 | 
				
			||||||
 | 
					            deal.is_deleted = request.data.is_deleted
 | 
				
			||||||
 | 
					            deal.is_completed = request.data.is_completed
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					            return DealUpdateGeneralInfoResponse(ok=True, message='Данные о сделке успешно обновлены')
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            await self.session.rollback()
 | 
				
			||||||
 | 
					            return DealUpdateGeneralInfoResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # region Deal services
 | 
				
			||||||
    async def add_services(self, request: DealAddServicesRequest):
 | 
					    async def add_services(self, request: DealAddServicesRequest):
 | 
				
			||||||
        # TODO refactor
 | 
					        # TODO refactor
 | 
				
			||||||
        deal: Deal = await self.session.scalar(
 | 
					        deal: Deal = await self.session.scalar(
 | 
				
			||||||
@@ -149,41 +200,14 @@ class DealService(BaseService):
 | 
				
			|||||||
            quantity = request_services_dict[service.id]
 | 
					            quantity = request_services_dict[service.id]
 | 
				
			||||||
            deal.services.append(
 | 
					            deal.services.append(
 | 
				
			||||||
                models.secondary.DealService(
 | 
					                models.secondary.DealService(
 | 
				
			||||||
                    service=service,
 | 
					                    service_id=service.id,
 | 
				
			||||||
                    deal=deal,
 | 
					                    deal_id=deal.id,
 | 
				
			||||||
                    quantity=quantity
 | 
					                    quantity=quantity
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        await self.session.commit()
 | 
					        await self.session.commit()
 | 
				
			||||||
        return DealAddServicesResponse(ok=True, message='Услуги успешно добавлены')
 | 
					        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),
 | 
					 | 
				
			||||||
                selectinload(Deal.products)
 | 
					 | 
				
			||||||
                .joinedload(models.secondary.DealProduct.product)
 | 
					 | 
				
			||||||
                .joinedload(models.Product.client)
 | 
					 | 
				
			||||||
                .selectinload(models.Product.barcodes)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            .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,
 | 
					    async def update_service_quantity(self,
 | 
				
			||||||
                                      request: DealUpdateServiceQuantityRequest) -> DealUpdateServiceQuantityResponse:
 | 
					                                      request: DealUpdateServiceQuantityRequest) -> DealUpdateServiceQuantityResponse:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
@@ -259,3 +283,84 @@ class DealService(BaseService):
 | 
				
			|||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            await self.session.rollback()
 | 
					            await self.session.rollback()
 | 
				
			||||||
            return DealDeleteServicesResponse(ok=False, message=str(e))
 | 
					            return DealDeleteServicesResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # region Deal products
 | 
				
			||||||
 | 
					    async def update_product_quantity(self,
 | 
				
			||||||
 | 
					                                      request: DealUpdateProductQuantityRequest) -> DealUpdateProductQuantityResponse:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # check if there is no deal or no product with different exceptions
 | 
				
			||||||
 | 
					            deal_product = await self.session.scalar(
 | 
				
			||||||
 | 
					                select(models.secondary.DealProduct)
 | 
				
			||||||
 | 
					                .where(models.secondary.DealProduct.deal_id == request.deal_id,
 | 
				
			||||||
 | 
					                       models.secondary.DealProduct.product_id == request.product_id)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            if not deal_product:
 | 
				
			||||||
 | 
					                raise HTTPException(status_code=404, detail="Сделка или товар не найдена")
 | 
				
			||||||
 | 
					            deal_product.quantity = request.quantity
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					            return DealUpdateProductQuantityResponse(ok=True, message='Количество успешно обновлено')
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            await self.session.rollback()
 | 
				
			||||||
 | 
					            return DealUpdateProductQuantityResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def add_product(self, request: DealAddProductRequest) -> DealAddProductResponse:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id))
 | 
				
			||||||
 | 
					            if not deal:
 | 
				
			||||||
 | 
					                raise HTTPException(status_code=404, detail="Сделка не найдена")
 | 
				
			||||||
 | 
					            product = await self.session.scalar(select(models.Product).where(models.Product.id == request.product_id))
 | 
				
			||||||
 | 
					            if not product:
 | 
				
			||||||
 | 
					                raise HTTPException(status_code=404, detail="Товар не найден")
 | 
				
			||||||
 | 
					            # Preventing duplicates
 | 
				
			||||||
 | 
					            deal_product = await self.session.scalar(
 | 
				
			||||||
 | 
					                select(models.secondary.DealProduct)
 | 
				
			||||||
 | 
					                .where(models.secondary.DealProduct.deal_id == request.deal_id,
 | 
				
			||||||
 | 
					                       models.secondary.DealProduct.product_id == request.product_id)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            if deal_product:
 | 
				
			||||||
 | 
					                raise HTTPException(status_code=400, detail="Товар уже добавлен")
 | 
				
			||||||
 | 
					            deal_product = models.secondary.DealProduct(
 | 
				
			||||||
 | 
					                deal_id=request.deal_id,
 | 
				
			||||||
 | 
					                product_id=request.product_id,
 | 
				
			||||||
 | 
					                quantity=request.quantity
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            self.session.add(deal_product)
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					            return DealAddProductResponse(ok=True, message='Товар успешно добавлен')
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            await self.session.rollback()
 | 
				
			||||||
 | 
					            return DealAddProductResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def delete_product(self, request: DealDeleteProductRequest) -> DealDeleteProductResponse:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            deal_product = await self.session.scalar(
 | 
				
			||||||
 | 
					                select(models.secondary.DealProduct)
 | 
				
			||||||
 | 
					                .where(models.secondary.DealProduct.deal_id == request.deal_id,
 | 
				
			||||||
 | 
					                       models.secondary.DealProduct.product_id == request.product_id)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            if not deal_product:
 | 
				
			||||||
 | 
					                raise HTTPException(status_code=404, detail="Сделка не найдена")
 | 
				
			||||||
 | 
					            await self.session.delete(deal_product)
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					            return DealDeleteProductResponse(ok=True, message='Товар успешно удален')
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            await self.session.rollback()
 | 
				
			||||||
 | 
					            return DealDeleteProductResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def delete_products(self, request: DealDeleteProductsRequest) -> DealDeleteProductsResponse:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            deal_products = await self.session.scalars(
 | 
				
			||||||
 | 
					                select(models.secondary.DealProduct)
 | 
				
			||||||
 | 
					                .where(models.secondary.DealProduct.deal_id == request.deal_id,
 | 
				
			||||||
 | 
					                       models.secondary.DealProduct.product_id.in_(request.product_ids))
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            for deal_product in deal_products:
 | 
				
			||||||
 | 
					                await self.session.delete(deal_product)
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					            return DealDeleteProductsResponse(ok=True, message='Товары успешно удалены')
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            await self.session.rollback()
 | 
				
			||||||
 | 
					            return DealDeleteProductsResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					    # endregion
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ from fastapi import HTTPException
 | 
				
			|||||||
from sqlalchemy import select, func, Integer, update
 | 
					from sqlalchemy import select, func, Integer, update
 | 
				
			||||||
from sqlalchemy.orm import selectinload
 | 
					from sqlalchemy.orm import selectinload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import utils.barcodes
 | 
				
			||||||
from models.product import Product, ProductBarcode
 | 
					from models.product import Product, ProductBarcode
 | 
				
			||||||
from schemas.base import PaginationSchema
 | 
					from schemas.base import PaginationSchema
 | 
				
			||||||
from services.base import BaseService
 | 
					from services.base import BaseService
 | 
				
			||||||
@@ -85,6 +86,9 @@ class ProductService(BaseService):
 | 
				
			|||||||
        return ProductUpdateResponse(ok=True, message='Товар успешно обновлен')
 | 
					        return ProductUpdateResponse(ok=True, message='Товар успешно обновлен')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def get_by_client_id(self, client_id: int, pagination: PaginationSchema) -> ProductGetResponse:
 | 
					    async def get_by_client_id(self, client_id: int, pagination: PaginationSchema) -> ProductGetResponse:
 | 
				
			||||||
 | 
					        is_pagination_valid = is_valid_pagination(pagination)
 | 
				
			||||||
 | 
					        total_pages = 0
 | 
				
			||||||
 | 
					        total_items = 0
 | 
				
			||||||
        stmt = (
 | 
					        stmt = (
 | 
				
			||||||
            select(Product)
 | 
					            select(Product)
 | 
				
			||||||
            .options(selectinload(Product.barcodes)
 | 
					            .options(selectinload(Product.barcodes)
 | 
				
			||||||
@@ -92,7 +96,7 @@ class ProductService(BaseService):
 | 
				
			|||||||
            .where(Product.client_id == client_id)
 | 
					            .where(Product.client_id == client_id)
 | 
				
			||||||
            .order_by(Product.id)
 | 
					            .order_by(Product.id)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        if is_valid_pagination(pagination):
 | 
					        if is_pagination_valid:
 | 
				
			||||||
            total_products_query = await self.session.execute(
 | 
					            total_products_query = await self.session.execute(
 | 
				
			||||||
                select(
 | 
					                select(
 | 
				
			||||||
                    func.cast(func.ceil(func.count() / pagination.items_per_page), Integer),
 | 
					                    func.cast(func.ceil(func.count() / pagination.items_per_page), Integer),
 | 
				
			||||||
@@ -101,36 +105,101 @@ class ProductService(BaseService):
 | 
				
			|||||||
                .select_from(stmt.subquery())
 | 
					                .select_from(stmt.subquery())
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            total_pages, total_items = total_products_query.first()
 | 
					            total_pages, total_items = total_products_query.first()
 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            total_items_query = await self.session.execute(
 | 
					 | 
				
			||||||
                select(func.count())
 | 
					 | 
				
			||||||
                .select_from(stmt.subquery())
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            total_items = total_items_query.scalar()
 | 
					 | 
				
			||||||
            total_pages = 1
 | 
					 | 
				
			||||||
        pagination_info = PaginationInfoSchema(total_pages=total_pages, total_items=total_items)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if is_valid_pagination(pagination):
 | 
					 | 
				
			||||||
            stmt = (
 | 
					            stmt = (
 | 
				
			||||||
                stmt
 | 
					                stmt
 | 
				
			||||||
                .offset(pagination.page * pagination.items_per_page)
 | 
					                .offset(pagination.page * pagination.items_per_page)
 | 
				
			||||||
                .limit(pagination.items_per_page)
 | 
					                .limit(pagination.items_per_page)
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					 | 
				
			||||||
        query = await self.session.execute(
 | 
					        query = await self.session.execute(
 | 
				
			||||||
            stmt
 | 
					            stmt
 | 
				
			||||||
            .order_by(Product.id)
 | 
					            .order_by(Product.id)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        product_orm = query.scalars().all()
 | 
				
			||||||
 | 
					        if not is_pagination_valid:
 | 
				
			||||||
 | 
					            total_pages = 1
 | 
				
			||||||
 | 
					            total_items = len(product_orm)
 | 
				
			||||||
 | 
					        pagination_info = PaginationInfoSchema(total_pages=total_pages, total_items=total_items)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        products: list[ProductSchema] = []
 | 
					        products: list[ProductSchema] = []
 | 
				
			||||||
        for product in query.scalars().all():
 | 
					        for product in product_orm:
 | 
				
			||||||
            product: Product
 | 
					            products.append(ProductSchema.model_validate(product))
 | 
				
			||||||
 | 
					 | 
				
			||||||
            barcodes = []
 | 
					 | 
				
			||||||
            for barcode_obj in product.barcodes:
 | 
					 | 
				
			||||||
                barcode_obj: ProductBarcode
 | 
					 | 
				
			||||||
                barcodes.append(barcode_obj.barcode)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            products.append(
 | 
					 | 
				
			||||||
                ProductSchema.from_sql_model(product, {'barcodes': barcodes})
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        return ProductGetResponse(products=products, pagination_info=pagination_info)
 | 
					        return ProductGetResponse(products=products, pagination_info=pagination_info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def get_by_id(self, product_id: int) -> ProductSchema:
 | 
				
			||||||
 | 
					        stmt = (
 | 
				
			||||||
 | 
					            select(Product)
 | 
				
			||||||
 | 
					            .options(selectinload(Product.barcodes)
 | 
				
			||||||
 | 
					                     .noload(ProductBarcode.product))
 | 
				
			||||||
 | 
					            .where(Product.id == product_id)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        query = await self.session.execute(stmt)
 | 
				
			||||||
 | 
					        product = query.scalar()
 | 
				
			||||||
 | 
					        if not product:
 | 
				
			||||||
 | 
					            raise HTTPException(status_code=404, detail='Товар не найден')
 | 
				
			||||||
 | 
					        return ProductSchema.model_validate(product)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # region Barcodes
 | 
				
			||||||
 | 
					    async def add_barcode(self, request: ProductAddBarcodeRequest):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            product = await self.session.get(Product, request.product_id)
 | 
				
			||||||
 | 
					            if not product:
 | 
				
			||||||
 | 
					                raise HTTPException(status_code=404, detail='Товар не найден')
 | 
				
			||||||
 | 
					            existing_barcode_query = await self.session.execute(
 | 
				
			||||||
 | 
					                select(ProductBarcode)
 | 
				
			||||||
 | 
					                .where(ProductBarcode.product_id == request.product_id,
 | 
				
			||||||
 | 
					                       ProductBarcode.barcode == request.barcode)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            existing_barcode = existing_barcode_query.first()
 | 
				
			||||||
 | 
					            if existing_barcode:
 | 
				
			||||||
 | 
					                return ProductAddBarcodeResponse(ok=False, message='Штрих-код уже существует у товара')
 | 
				
			||||||
 | 
					            product_barcode = ProductBarcode(product_id=product.id,
 | 
				
			||||||
 | 
					                                             barcode=request.barcode)
 | 
				
			||||||
 | 
					            self.session.add(product_barcode)
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					            return ProductAddBarcodeResponse(ok=True, message='Штрих-код успешно добавлен')
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            await self.session.rollback()
 | 
				
			||||||
 | 
					            return ProductAddBarcodeResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def delete_barcode(self, request: ProductDeleteBarcodeRequest):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            product_barcode = await self.session.get(ProductBarcode, (request.product_id, request.barcode))
 | 
				
			||||||
 | 
					            if not product_barcode:
 | 
				
			||||||
 | 
					                return ProductDeleteBarcodeResponse(ok=False, message='Штрих-код не найден')
 | 
				
			||||||
 | 
					            await self.session.delete(product_barcode)
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					            return ProductDeleteBarcodeResponse(ok=True, message='Штрих-код успешно удален')
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            await self.session.rollback()
 | 
				
			||||||
 | 
					            return ProductDeleteBarcodeResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def exists_barcode(self, product_id: int, barcode: str) -> ProductExistsBarcodeResponse:
 | 
				
			||||||
 | 
					        product_barcode_query = await self.session.execute(
 | 
				
			||||||
 | 
					            select(ProductBarcode)
 | 
				
			||||||
 | 
					            .where(ProductBarcode.product_id == product_id,
 | 
				
			||||||
 | 
					                   ProductBarcode.barcode == barcode)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        product_barcode = product_barcode_query.first()
 | 
				
			||||||
 | 
					        return ProductExistsBarcodeResponse(exists=bool(product_barcode))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def generate_barcode(self, request: ProductGenerateBarcodeRequest) -> ProductGenerateBarcodeResponse:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            product = await self.session.get(Product, request.product_id)
 | 
				
			||||||
 | 
					            if not product:
 | 
				
			||||||
 | 
					                raise HTTPException(status_code=404, detail='Товар не найден')
 | 
				
			||||||
 | 
					            barcode = utils.barcodes.generate_barcode(product.id)
 | 
				
			||||||
 | 
					            barcode_exists_query = await self.session.execute(
 | 
				
			||||||
 | 
					                select(ProductBarcode)
 | 
				
			||||||
 | 
					                .where(ProductBarcode.barcode == barcode)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            barcode_exists = barcode_exists_query.first()
 | 
				
			||||||
 | 
					            if barcode_exists:
 | 
				
			||||||
 | 
					                raise Exception('Штрих-код уже существует')
 | 
				
			||||||
 | 
					            product_barcode = ProductBarcode(product_id=product.id,
 | 
				
			||||||
 | 
					                                             barcode=barcode)
 | 
				
			||||||
 | 
					            self.session.add(product_barcode)
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					            return ProductGenerateBarcodeResponse(ok=True, message='Штрих-код успешно сгенерирован', barcode=barcode)
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            return ProductGenerateBarcodeResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					    # endregion
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								utils/barcodes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								utils/barcodes.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					def generate_ean13_checksum(ean12):
 | 
				
			||||||
 | 
					    if len(ean12) != 12:
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					    digits = [int(digit) for digit in ean12]
 | 
				
			||||||
 | 
					    odd_sum = sum(digits[::2])
 | 
				
			||||||
 | 
					    even_sum = sum(digits[1::2])
 | 
				
			||||||
 | 
					    total = odd_sum + 3 * even_sum
 | 
				
			||||||
 | 
					    checksum = 10 - (total % 10)
 | 
				
			||||||
 | 
					    if checksum == 10:
 | 
				
			||||||
 | 
					        checksum = 0
 | 
				
			||||||
 | 
					    return checksum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def generate_barcode(product_id: int):
 | 
				
			||||||
 | 
					    product_id = str(product_id)
 | 
				
			||||||
 | 
					    # 4 - D
 | 
				
			||||||
 | 
					    # 6 - E
 | 
				
			||||||
 | 
					    # 5 - N
 | 
				
			||||||
 | 
					    # 2 - C
 | 
				
			||||||
 | 
					    # 4652 - DENCO_C (C - CRM)
 | 
				
			||||||
 | 
					    supplier_part = 4652_5
 | 
				
			||||||
 | 
					    article_part = product_id.ljust(7, '0')
 | 
				
			||||||
 | 
					    barcode = f'{supplier_part}{article_part}'
 | 
				
			||||||
 | 
					    checksum_code = generate_ean13_checksum(barcode)
 | 
				
			||||||
 | 
					    return f'{barcode}{checksum_code}'
 | 
				
			||||||
		Reference in New Issue
	
	Block a user