crappy
This commit is contained in:
3
main.py
3
main.py
@@ -20,7 +20,8 @@ routers_list = [
|
||||
routers.auth_router,
|
||||
routers.deal_router,
|
||||
routers.client_router,
|
||||
routers.service_router
|
||||
routers.service_router,
|
||||
routers.product_router,
|
||||
]
|
||||
for router in routers_list:
|
||||
app.include_router(router)
|
||||
|
||||
@@ -4,6 +4,7 @@ from .auth import *
|
||||
from .deal import *
|
||||
from .client import *
|
||||
from .service import *
|
||||
from .product import *
|
||||
from .secondary import *
|
||||
|
||||
configure_mappers()
|
||||
|
||||
@@ -10,6 +10,8 @@ class Client(BaseModel):
|
||||
name = Column(String, nullable=False, unique=True, comment='Название клиента')
|
||||
created_at = Column(DateTime, nullable=False, comment='Дата создания')
|
||||
|
||||
products = relationship('Product', back_populates='client')
|
||||
|
||||
|
||||
class ClientDetails(BaseModel):
|
||||
__tablename__ = 'client_details'
|
||||
|
||||
@@ -4,7 +4,6 @@ from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from models.base import BaseModel
|
||||
from models.secondary import deal_services
|
||||
|
||||
|
||||
@unique
|
||||
@@ -33,7 +32,7 @@ class Deal(BaseModel):
|
||||
is_deleted = Column(Boolean, nullable=False, server_default='0', default=False, comment='Удалена')
|
||||
is_completed = Column(Boolean, nullable=False, server_default='0', default=False, comment='Завершена')
|
||||
|
||||
services = relationship('Service', secondary=deal_services)
|
||||
services = relationship('DealService', back_populates='deal')
|
||||
|
||||
|
||||
class DealStatusHistory(BaseModel):
|
||||
|
||||
@@ -11,5 +11,5 @@ class Product(BaseModel):
|
||||
article = Column(String, nullable=False, index=True)
|
||||
|
||||
client_id = Column(Integer, ForeignKey('clients.id'), nullable=False, comment='ID сделки')
|
||||
client = relationship('Client', back_populates='status_history')
|
||||
client = relationship('Client', back_populates='products')
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
from sqlalchemy import Table, Column, Integer, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from models.base import metadata
|
||||
from models.base import metadata, BaseModel
|
||||
|
||||
deal_services = Table(
|
||||
'deal_services', metadata,
|
||||
Column('deal_id', Integer, ForeignKey('deals.id')),
|
||||
Column('service_id', Integer, ForeignKey('services.id')),
|
||||
Column('quantity', Integer)
|
||||
)
|
||||
|
||||
class DealService(BaseModel):
|
||||
__tablename__ = 'deal_services'
|
||||
deal_id = Column(Integer, ForeignKey('deals.id'), nullable=False, comment='ID Сделки', primary_key=True)
|
||||
deal = relationship('Deal', back_populates='services')
|
||||
|
||||
service_id = Column(Integer, ForeignKey('services.id'), nullable=False, comment='ID Услуги', primary_key=True)
|
||||
service = relationship('Service')
|
||||
|
||||
quantity = Column(Integer, nullable=False, comment='Кол-во услуги')
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, Double
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
||||
|
||||
from models import BaseModel
|
||||
|
||||
|
||||
class Service(BaseModel):
|
||||
__tablename__ = 'services'
|
||||
id = Column(Integer, autoincrement=True, primary_key=True, index=True)
|
||||
id: Mapped[int] = mapped_column(Integer, autoincrement=True, primary_key=True, index=True)
|
||||
name = Column(String, nullable=False, comment='Название услуги')
|
||||
|
||||
category_id = Column(Integer, ForeignKey('service_categories.id'), nullable=False, comment='ID категории услуги')
|
||||
|
||||
@@ -2,4 +2,4 @@ from .auth import auth_router
|
||||
from .deal import deal_router
|
||||
from .client import client_router
|
||||
from .service import service_router
|
||||
|
||||
from .product import product_router
|
||||
|
||||
@@ -50,3 +50,14 @@ async def get_summary(
|
||||
session: Annotated[AsyncSession, Depends(get_session)]
|
||||
):
|
||||
return await DealService(session).get_summary()
|
||||
|
||||
|
||||
@deal_router.post(
|
||||
'/services/add',
|
||||
response_model=DealAddServicesResponse,
|
||||
)
|
||||
async def services_add(
|
||||
request: DealAddServicesRequest,
|
||||
session: Annotated[AsyncSession, Depends(get_session)]
|
||||
):
|
||||
return await DealService(session).add_services(request)
|
||||
|
||||
39
routers/product.py
Normal file
39
routers/product.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
import utils.dependecies
|
||||
from backend.session import get_session
|
||||
from schemas.base import PaginationSchema
|
||||
from schemas.product import *
|
||||
from services.product import ProductService
|
||||
|
||||
product_router = APIRouter(
|
||||
prefix="/product",
|
||||
tags=["product"]
|
||||
)
|
||||
|
||||
|
||||
@product_router.post(
|
||||
'/create',
|
||||
response_model=ProductCreateResponse
|
||||
)
|
||||
async def create_product(
|
||||
request: ProductCreateRequest,
|
||||
session: Annotated[AsyncSession, Depends(get_session)]
|
||||
):
|
||||
return await ProductService(session).create(request)
|
||||
|
||||
|
||||
@product_router.get(
|
||||
'/get',
|
||||
response_model=ProductGetResponse,
|
||||
operation_id='get_products_by_client_id'
|
||||
)
|
||||
async def get_product(
|
||||
client_id: int,
|
||||
pagination: Annotated[PaginationSchema, Depends(utils.dependecies.pagination_parameters)],
|
||||
session: Annotated[AsyncSession, Depends(get_session)]
|
||||
):
|
||||
return await ProductService(session).get_by_client_id(client_id, pagination)
|
||||
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from backend.session import get_session
|
||||
from schemas.services import *
|
||||
from schemas.service import *
|
||||
from services.service import ServiceService
|
||||
|
||||
service_router = APIRouter(
|
||||
|
||||
@@ -14,3 +14,13 @@ class CustomModel(BaseModel):
|
||||
class OkMessageSchema(BaseModel):
|
||||
ok: bool
|
||||
message: str
|
||||
|
||||
|
||||
class PaginationSchema(CustomModel):
|
||||
page: int
|
||||
items_per_page: int
|
||||
|
||||
|
||||
class PaginationInfoSchema(CustomModel):
|
||||
total_pages: int
|
||||
total_items: int
|
||||
|
||||
@@ -19,6 +19,12 @@ class DealSummary(CustomModel):
|
||||
client_name: str
|
||||
changed_at: datetime.datetime
|
||||
status: int
|
||||
total_price: int
|
||||
|
||||
|
||||
class DealServiceSchema(CustomModel):
|
||||
id: int
|
||||
quantity: int
|
||||
|
||||
|
||||
# endregion Entities
|
||||
@@ -45,6 +51,11 @@ class DealSummaryRequest(CustomModel):
|
||||
pass
|
||||
|
||||
|
||||
class DealAddServicesRequest(CustomModel):
|
||||
deal_id: int
|
||||
services: list[DealServiceSchema]
|
||||
|
||||
|
||||
# endregion Requests
|
||||
|
||||
# region Responses
|
||||
@@ -64,4 +75,8 @@ class DealQuickCreateResponse(CustomModel):
|
||||
class DealSummaryResponse(CustomModel):
|
||||
summaries: List[DealSummary]
|
||||
|
||||
|
||||
class DealAddServicesResponse(CustomModel):
|
||||
ok: bool
|
||||
message: str
|
||||
# endregion Responses
|
||||
|
||||
33
schemas/product.py
Normal file
33
schemas/product.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from typing import List
|
||||
|
||||
from schemas.base import CustomModel, PaginationInfoSchema
|
||||
|
||||
|
||||
# region Entities
|
||||
class ProductSchema(CustomModel):
|
||||
id: int
|
||||
name: str
|
||||
article: str
|
||||
client_id: int
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Requests
|
||||
class ProductCreateRequest(CustomModel):
|
||||
name: str
|
||||
article: str
|
||||
client_id: int
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Responses
|
||||
class ProductCreateResponse(CustomModel):
|
||||
product_id: int
|
||||
|
||||
|
||||
class ProductGetResponse(CustomModel):
|
||||
products: List[ProductSchema]
|
||||
pagination_info: PaginationInfoSchema
|
||||
# endregion
|
||||
@@ -1,10 +1,11 @@
|
||||
import datetime
|
||||
from typing import Type, Union
|
||||
import models.secondary
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import select
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.orm import joinedload, selectinload
|
||||
|
||||
from models import User, Deal
|
||||
from models import User, Service
|
||||
from models.deal import *
|
||||
from schemas.client import ClientDetailsSchema
|
||||
from schemas.deal import *
|
||||
@@ -80,13 +81,27 @@ class DealService(BaseService):
|
||||
return DealChangeStatusResponse(ok=True)
|
||||
|
||||
async def get_summary(self) -> DealSummaryResponse:
|
||||
deals_query = await self.session.scalars(select(Deal)
|
||||
.options(selectinload(Deal.status_history),
|
||||
joinedload(Deal.client))
|
||||
.where(Deal.is_deleted == False,
|
||||
Deal.is_completed == False))
|
||||
service_subquery = (
|
||||
select(
|
||||
models.secondary.DealService.deal_id,
|
||||
func.sum(models.secondary.DealService.quantity * Service.price).label('total_price')
|
||||
)
|
||||
.join(Service)
|
||||
.group_by(models.secondary.DealService.deal_id)
|
||||
.subquery()
|
||||
)
|
||||
q = (select(
|
||||
Deal,
|
||||
func.coalesce(service_subquery.c.total_price, 0)
|
||||
)
|
||||
.options(selectinload(Deal.status_history),
|
||||
joinedload(Deal.client))
|
||||
.outerjoin(service_subquery, Deal.id == service_subquery.c.deal_id)
|
||||
.where(Deal.is_deleted == False,
|
||||
Deal.is_completed == False))
|
||||
deals_query = await self.session.execute(q)
|
||||
summaries = []
|
||||
for deal in deals_query.all():
|
||||
for deal, total_price in deals_query.all():
|
||||
deal: Deal
|
||||
last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at)
|
||||
summaries.append(
|
||||
@@ -95,7 +110,49 @@ class DealService(BaseService):
|
||||
client_name=deal.client.name,
|
||||
name=deal.name,
|
||||
changed_at=last_status.changed_at,
|
||||
status=last_status.to_status
|
||||
status=last_status.to_status,
|
||||
total_price=total_price
|
||||
)
|
||||
)
|
||||
return DealSummaryResponse(summaries=summaries)
|
||||
return DealSummaryResponse(summaries=summaries)
|
||||
|
||||
async def add_services(self, request: DealAddServicesRequest):
|
||||
# TODO refactor
|
||||
deal: Deal = await self.session.scalar(
|
||||
select(Deal)
|
||||
.options(selectinload(Deal.services))
|
||||
.where(Deal.id == request.deal_id)
|
||||
)
|
||||
if not deal:
|
||||
raise HTTPException(status_code=404, detail="Deal is not found")
|
||||
|
||||
services_ids = [service.id for service in request.services]
|
||||
existing_service_ids = {service.service_id for service in deal.services}
|
||||
request_services_dict = {service.id: service.quantity for service in request.services}
|
||||
|
||||
services_query = await self.session.scalars(select(Service).where(Service.id.in_(services_ids)))
|
||||
services = services_query.all()
|
||||
if len(services) != len(services_ids):
|
||||
raise HTTPException(status_code=404, detail="Some of services is not found")
|
||||
|
||||
# Adding quantity
|
||||
for deal_service in deal.services:
|
||||
deal_service: models.secondary.DealService
|
||||
if deal_service.service_id not in services_ids:
|
||||
continue
|
||||
deal_service.quantity += request_services_dict[deal_service.service_id]
|
||||
|
||||
# Adding new services
|
||||
for service in services:
|
||||
if service.id in existing_service_ids:
|
||||
continue
|
||||
quantity = request_services_dict[service.id]
|
||||
deal.services.append(
|
||||
models.secondary.DealService(
|
||||
service=service,
|
||||
deal=deal,
|
||||
quantity=quantity
|
||||
)
|
||||
)
|
||||
await self.session.commit()
|
||||
return DealAddServicesResponse(ok=True, message='Услуги успешно добавлены')
|
||||
|
||||
37
services/product.py
Normal file
37
services/product.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import select
|
||||
|
||||
from models.product import Product
|
||||
from schemas.base import PaginationSchema
|
||||
from services.base import BaseService
|
||||
from schemas.product import *
|
||||
|
||||
|
||||
class ProductService(BaseService):
|
||||
async def create(self, request: ProductCreateRequest) -> ProductCreateResponse:
|
||||
existing_product_query = await self.session.execute(
|
||||
select(Product)
|
||||
.where(Product.client_id == request.client_id,
|
||||
Product.article == request.article)
|
||||
)
|
||||
existing_product = existing_product_query.first()
|
||||
if existing_product:
|
||||
raise HTTPException(status_code=403, detail="Product already exists")
|
||||
product = Product(**request.dict())
|
||||
self.session.add(product)
|
||||
await self.session.commit()
|
||||
return ProductCreateResponse(product_id=product.id)
|
||||
|
||||
async def get_by_client_id(self, client_id: int, pagination: PaginationSchema) -> ProductGetResponse:
|
||||
query = await self.session.execute(
|
||||
select(Product)
|
||||
.where(Product.client_id == client_id)
|
||||
.offset(pagination.page * pagination.items_per_page)
|
||||
.limit(pagination.items_per_page)
|
||||
)
|
||||
products: list[ProductSchema] = []
|
||||
for product in query.scalars().all():
|
||||
products.append(
|
||||
ProductSchema.model_validate(product)
|
||||
)
|
||||
return ProductGetResponse(products=products)
|
||||
@@ -3,7 +3,7 @@ from sqlalchemy.orm import joinedload
|
||||
|
||||
from models import Service, ServiceCategory
|
||||
from services.base import BaseService
|
||||
from schemas.services import ServiceGetAllResponse, ServiceSchema, ServiceGetAllCategoriesResponse, \
|
||||
from schemas.service import ServiceGetAllResponse, ServiceSchema, ServiceGetAllCategoriesResponse, \
|
||||
ServiceCategorySchema, ServiceCreateRequest, ServiceCreateResponse, ServiceCreateCategoryRequest, \
|
||||
ServiceCreateCategoryResponse
|
||||
|
||||
|
||||
5
utils/dependecies.py
Normal file
5
utils/dependecies.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from schemas.base import PaginationSchema
|
||||
|
||||
|
||||
async def pagination_parameters(page: int, items_per_page: int) -> PaginationSchema:
|
||||
return PaginationSchema(page=page, items_per_page=items_per_page)
|
||||
Reference in New Issue
Block a user