crappy
This commit is contained in:
3
main.py
3
main.py
@@ -20,7 +20,8 @@ routers_list = [
|
|||||||
routers.auth_router,
|
routers.auth_router,
|
||||||
routers.deal_router,
|
routers.deal_router,
|
||||||
routers.client_router,
|
routers.client_router,
|
||||||
routers.service_router
|
routers.service_router,
|
||||||
|
routers.product_router,
|
||||||
]
|
]
|
||||||
for router in routers_list:
|
for router in routers_list:
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from .auth import *
|
|||||||
from .deal import *
|
from .deal import *
|
||||||
from .client import *
|
from .client import *
|
||||||
from .service import *
|
from .service import *
|
||||||
|
from .product import *
|
||||||
from .secondary import *
|
from .secondary import *
|
||||||
|
|
||||||
configure_mappers()
|
configure_mappers()
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ class Client(BaseModel):
|
|||||||
name = Column(String, nullable=False, unique=True, comment='Название клиента')
|
name = Column(String, nullable=False, unique=True, comment='Название клиента')
|
||||||
created_at = Column(DateTime, nullable=False, comment='Дата создания')
|
created_at = Column(DateTime, nullable=False, comment='Дата создания')
|
||||||
|
|
||||||
|
products = relationship('Product', back_populates='client')
|
||||||
|
|
||||||
|
|
||||||
class ClientDetails(BaseModel):
|
class ClientDetails(BaseModel):
|
||||||
__tablename__ = 'client_details'
|
__tablename__ = 'client_details'
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean
|
|||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from models.base import BaseModel
|
from models.base import BaseModel
|
||||||
from models.secondary import deal_services
|
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
@@ -33,7 +32,7 @@ class Deal(BaseModel):
|
|||||||
is_deleted = Column(Boolean, nullable=False, server_default='0', default=False, comment='Удалена')
|
is_deleted = Column(Boolean, nullable=False, server_default='0', default=False, comment='Удалена')
|
||||||
is_completed = 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):
|
class DealStatusHistory(BaseModel):
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ class Product(BaseModel):
|
|||||||
article = Column(String, nullable=False, index=True)
|
article = Column(String, nullable=False, index=True)
|
||||||
|
|
||||||
client_id = Column(Integer, ForeignKey('clients.id'), nullable=False, comment='ID сделки')
|
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 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,
|
class DealService(BaseModel):
|
||||||
Column('deal_id', Integer, ForeignKey('deals.id')),
|
__tablename__ = 'deal_services'
|
||||||
Column('service_id', Integer, ForeignKey('services.id')),
|
deal_id = Column(Integer, ForeignKey('deals.id'), nullable=False, comment='ID Сделки', primary_key=True)
|
||||||
Column('quantity', Integer)
|
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 import Column, Integer, String, ForeignKey, Double
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
||||||
|
|
||||||
from models import BaseModel
|
from models import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class Service(BaseModel):
|
class Service(BaseModel):
|
||||||
__tablename__ = 'services'
|
__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='Название услуги')
|
name = Column(String, nullable=False, comment='Название услуги')
|
||||||
|
|
||||||
category_id = Column(Integer, ForeignKey('service_categories.id'), nullable=False, comment='ID категории услуги')
|
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 .deal import deal_router
|
||||||
from .client import client_router
|
from .client import client_router
|
||||||
from .service import service_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)]
|
session: Annotated[AsyncSession, Depends(get_session)]
|
||||||
):
|
):
|
||||||
return await DealService(session).get_summary()
|
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 sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from backend.session import get_session
|
from backend.session import get_session
|
||||||
from schemas.services import *
|
from schemas.service import *
|
||||||
from services.service import ServiceService
|
from services.service import ServiceService
|
||||||
|
|
||||||
service_router = APIRouter(
|
service_router = APIRouter(
|
||||||
|
|||||||
@@ -14,3 +14,13 @@ class CustomModel(BaseModel):
|
|||||||
class OkMessageSchema(BaseModel):
|
class OkMessageSchema(BaseModel):
|
||||||
ok: bool
|
ok: bool
|
||||||
message: str
|
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
|
client_name: str
|
||||||
changed_at: datetime.datetime
|
changed_at: datetime.datetime
|
||||||
status: int
|
status: int
|
||||||
|
total_price: int
|
||||||
|
|
||||||
|
|
||||||
|
class DealServiceSchema(CustomModel):
|
||||||
|
id: int
|
||||||
|
quantity: int
|
||||||
|
|
||||||
|
|
||||||
# endregion Entities
|
# endregion Entities
|
||||||
@@ -45,6 +51,11 @@ class DealSummaryRequest(CustomModel):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DealAddServicesRequest(CustomModel):
|
||||||
|
deal_id: int
|
||||||
|
services: list[DealServiceSchema]
|
||||||
|
|
||||||
|
|
||||||
# endregion Requests
|
# endregion Requests
|
||||||
|
|
||||||
# region Responses
|
# region Responses
|
||||||
@@ -64,4 +75,8 @@ class DealQuickCreateResponse(CustomModel):
|
|||||||
class DealSummaryResponse(CustomModel):
|
class DealSummaryResponse(CustomModel):
|
||||||
summaries: List[DealSummary]
|
summaries: List[DealSummary]
|
||||||
|
|
||||||
|
|
||||||
|
class DealAddServicesResponse(CustomModel):
|
||||||
|
ok: bool
|
||||||
|
message: str
|
||||||
# endregion Responses
|
# 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
|
import models.secondary
|
||||||
from typing import Type, Union
|
from typing import Union
|
||||||
|
|
||||||
from sqlalchemy import select
|
from fastapi import HTTPException
|
||||||
|
from sqlalchemy import select, func
|
||||||
from sqlalchemy.orm import joinedload, selectinload
|
from sqlalchemy.orm import joinedload, selectinload
|
||||||
|
|
||||||
from models import User, Deal
|
from models import User, Service
|
||||||
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 *
|
||||||
@@ -80,13 +81,27 @@ class DealService(BaseService):
|
|||||||
return DealChangeStatusResponse(ok=True)
|
return DealChangeStatusResponse(ok=True)
|
||||||
|
|
||||||
async def get_summary(self) -> DealSummaryResponse:
|
async def get_summary(self) -> DealSummaryResponse:
|
||||||
deals_query = await self.session.scalars(select(Deal)
|
service_subquery = (
|
||||||
.options(selectinload(Deal.status_history),
|
select(
|
||||||
joinedload(Deal.client))
|
models.secondary.DealService.deal_id,
|
||||||
.where(Deal.is_deleted == False,
|
func.sum(models.secondary.DealService.quantity * Service.price).label('total_price')
|
||||||
Deal.is_completed == False))
|
)
|
||||||
|
.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 = []
|
summaries = []
|
||||||
for deal in deals_query.all():
|
for deal, total_price in deals_query.all():
|
||||||
deal: Deal
|
deal: Deal
|
||||||
last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at)
|
last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at)
|
||||||
summaries.append(
|
summaries.append(
|
||||||
@@ -95,7 +110,49 @@ class DealService(BaseService):
|
|||||||
client_name=deal.client.name,
|
client_name=deal.client.name,
|
||||||
name=deal.name,
|
name=deal.name,
|
||||||
changed_at=last_status.changed_at,
|
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 models import Service, ServiceCategory
|
||||||
from services.base import BaseService
|
from services.base import BaseService
|
||||||
from schemas.services import ServiceGetAllResponse, ServiceSchema, ServiceGetAllCategoriesResponse, \
|
from schemas.service import ServiceGetAllResponse, ServiceSchema, ServiceGetAllCategoriesResponse, \
|
||||||
ServiceCategorySchema, ServiceCreateRequest, ServiceCreateResponse, ServiceCreateCategoryRequest, \
|
ServiceCategorySchema, ServiceCreateRequest, ServiceCreateResponse, ServiceCreateCategoryRequest, \
|
||||||
ServiceCreateCategoryResponse
|
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