feat: deal product services
This commit is contained in:
@@ -33,7 +33,6 @@ class Deal(BaseModel):
|
||||
is_completed = Column(Boolean, nullable=False, server_default='0', default=False, comment='Завершена')
|
||||
|
||||
services = relationship('DealService', back_populates='deal', cascade="all, delete-orphan")
|
||||
# product_services = relationship('DealProductService', back_populates='deal', cascade="all, delete-orphan")
|
||||
|
||||
products = relationship('DealProduct', back_populates='deal', cascade="all, delete-orphan")
|
||||
|
||||
|
||||
@@ -21,11 +21,20 @@ class DealService(BaseModel):
|
||||
|
||||
class DealProductService(BaseModel):
|
||||
__tablename__ = 'deal_product_services'
|
||||
deal_id = Column(Integer, nullable=False, primary_key=True, comment='ID Сделки')
|
||||
product_id = Column(Integer, nullable=False, primary_key=True, comment='ID Продукта')
|
||||
service_id = Column(Integer, ForeignKey('services.id'), nullable=False, comment='ID Услуги')
|
||||
deal_id = Column(Integer, primary_key=True, nullable=False, comment='ID Сделки')
|
||||
product_id = Column(Integer, primary_key=True, nullable=False, comment='ID Продукта')
|
||||
service_id = Column(Integer, ForeignKey('services.id'), primary_key=True, nullable=False, comment='ID Услуги')
|
||||
price = Column(Integer, nullable=False, comment='Цена услуги')
|
||||
|
||||
deal_product = relationship('DealProduct',
|
||||
back_populates='services',
|
||||
primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, "
|
||||
"DealProductService.product_id == DealProduct.product_id)",
|
||||
foreign_keys=[deal_id, product_id])
|
||||
service = relationship('Service',
|
||||
foreign_keys=[service_id],
|
||||
lazy='joined')
|
||||
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(
|
||||
['deal_id', 'product_id'],
|
||||
@@ -33,21 +42,28 @@ class DealProductService(BaseModel):
|
||||
),
|
||||
)
|
||||
|
||||
deal_product = relationship('DealProduct', back_populates='services')
|
||||
service = relationship('Service', lazy='joined')
|
||||
|
||||
|
||||
class DealProduct(BaseModel):
|
||||
__tablename__ = 'deal_products'
|
||||
deal_id = Column(Integer, ForeignKey('deals.id'), nullable=False, comment='ID Сделки', primary_key=True)
|
||||
product_id = Column(Integer, ForeignKey('products.id'), nullable=False, comment='ID Продукта', primary_key=True)
|
||||
deal_id = Column(Integer, ForeignKey('deals.id'), primary_key=True, nullable=False, comment='ID Сделки')
|
||||
product_id = Column(Integer, ForeignKey('products.id'), primary_key=True, nullable=False, comment='ID Продукта')
|
||||
quantity = Column(Integer, nullable=False, comment='Кол-во продукта')
|
||||
|
||||
deal = relationship('Deal', back_populates='products')
|
||||
product = relationship('Product')
|
||||
deal = relationship('Deal',
|
||||
back_populates='products',
|
||||
foreign_keys=[deal_id])
|
||||
product = relationship('Product',
|
||||
lazy='joined',
|
||||
foreign_keys=[product_id])
|
||||
|
||||
services = relationship('DealProductService', back_populates='deal_product', lazy='joined',
|
||||
cascade="all, delete-orphan")
|
||||
services = relationship('DealProductService',
|
||||
back_populates='deal_product',
|
||||
cascade="all, delete-orphan",
|
||||
primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, "
|
||||
"DealProductService.product_id == DealProduct.product_id)",
|
||||
foreign_keys=[DealProductService.deal_id, DealProductService.product_id],
|
||||
lazy='joined'
|
||||
)
|
||||
|
||||
|
||||
barcode_template_attribute_link = Table(
|
||||
|
||||
@@ -145,13 +145,25 @@ async def services_add(
|
||||
response_model=DealUpdateServiceQuantityResponse,
|
||||
operation_id='updateDealServiceQuantity'
|
||||
)
|
||||
async def services_update(
|
||||
async def services_update_quantity(
|
||||
request: DealUpdateServiceQuantityRequest,
|
||||
session: Annotated[AsyncSession, Depends(get_session)]
|
||||
):
|
||||
return await DealService(session).update_service_quantity(request)
|
||||
|
||||
|
||||
@deal_router.post(
|
||||
'/services/update',
|
||||
response_model=DealUpdateServiceResponse,
|
||||
operation_id='updateDealService'
|
||||
)
|
||||
async def services_update(
|
||||
request: DealUpdateServiceRequest,
|
||||
session: Annotated[AsyncSession, Depends(get_session)]
|
||||
):
|
||||
return await DealService(session).update_service(request)
|
||||
|
||||
|
||||
@deal_router.post(
|
||||
'/services/delete',
|
||||
response_model=DealDeleteServiceResponse,
|
||||
@@ -218,4 +230,15 @@ async def products_delete(
|
||||
session: Annotated[AsyncSession, Depends(get_session)]):
|
||||
return await DealService(session).delete_products(request)
|
||||
|
||||
|
||||
@deal_router.post(
|
||||
'/product/update',
|
||||
response_model=DealUpdateProductResponse,
|
||||
operation_id='updateDealProduct'
|
||||
)
|
||||
async def products_update(
|
||||
request: DealUpdateProductRequest,
|
||||
session: Annotated[AsyncSession, Depends(get_session)]
|
||||
):
|
||||
return await DealService(session).update_product(request)
|
||||
# endregion
|
||||
|
||||
@@ -109,10 +109,16 @@ class DealUpdateServiceQuantityRequest(CustomModelCamel):
|
||||
quantity: int
|
||||
|
||||
|
||||
class DealUpdateServiceRequest(CustomModelCamel):
|
||||
deal_id: int
|
||||
service: DealServiceSchema
|
||||
|
||||
|
||||
class DealAddServiceRequest(CustomModelCamel):
|
||||
deal_id: int
|
||||
service_id: int
|
||||
quantity: int
|
||||
price: int
|
||||
|
||||
|
||||
class DealDeleteServiceRequest(CustomModelCamel):
|
||||
@@ -163,6 +169,11 @@ class DealDeleteRequest(CustomModelCamel):
|
||||
deal_id: int
|
||||
|
||||
|
||||
class DealUpdateProductRequest(CustomModelCamel):
|
||||
deal_id: int
|
||||
product: DealProductSchema
|
||||
|
||||
|
||||
# endregion Requests
|
||||
|
||||
# region Responses
|
||||
@@ -204,6 +215,10 @@ class DealUpdateServiceQuantityResponse(CustomModelCamel):
|
||||
message: str
|
||||
|
||||
|
||||
class DealUpdateServiceResponse(OkMessageSchema):
|
||||
pass
|
||||
|
||||
|
||||
class DealAddServiceResponse(OkMessageSchema):
|
||||
pass
|
||||
|
||||
@@ -234,4 +249,8 @@ class DealSummaryReorderResponse(OkMessageSchema):
|
||||
|
||||
class DealDeleteResponse(OkMessageSchema):
|
||||
pass
|
||||
|
||||
|
||||
class DealUpdateProductResponse(OkMessageSchema):
|
||||
pass
|
||||
# endregion Responses
|
||||
|
||||
140
services/deal.py
140
services/deal.py
@@ -4,7 +4,7 @@ import models.secondary
|
||||
from typing import Union
|
||||
import models.deal
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy import select, func, update
|
||||
from sqlalchemy.orm import joinedload, selectinload
|
||||
|
||||
from models import User, Service, Client
|
||||
@@ -118,20 +118,42 @@ class DealService(BaseService):
|
||||
await self.session.commit()
|
||||
return DealChangeStatusResponse(ok=True)
|
||||
|
||||
async def get_summary(self) -> DealSummaryResponse:
|
||||
service_subquery = (
|
||||
def _get_price_subquery(self):
|
||||
deal_services_subquery = (
|
||||
select(
|
||||
models.secondary.DealService.deal_id,
|
||||
func.sum(models.secondary.DealService.quantity * Service.price).label('total_price')
|
||||
func.sum(models.secondary.DealService.quantity * models.secondary.DealService.price).label(
|
||||
'total_price')
|
||||
)
|
||||
.join(Service)
|
||||
.group_by(models.secondary.DealService.deal_id)
|
||||
)
|
||||
product_services_subquery = select(
|
||||
select(
|
||||
models.secondary.DealProductService.deal_id,
|
||||
func.sum(models.DealProduct.quantity * models.secondary.DealProductService.price).label('total_price')
|
||||
)
|
||||
.join(models.secondary.DealProduct)
|
||||
.group_by(models.secondary.DealProductService.deal_id)
|
||||
.subquery()
|
||||
)
|
||||
union_subqueries = deal_services_subquery.union(product_services_subquery).subquery()
|
||||
final_subquery = (
|
||||
select(
|
||||
union_subqueries.c.deal_id,
|
||||
func.sum(union_subqueries.c.total_price).label('total_price')
|
||||
)
|
||||
.group_by(union_subqueries.c.deal_id)
|
||||
.subquery()
|
||||
)
|
||||
return final_subquery
|
||||
|
||||
async def get_summary(self) -> DealSummaryResponse:
|
||||
price_subquery = self._get_price_subquery()
|
||||
q = (
|
||||
select(
|
||||
Deal,
|
||||
func.coalesce(service_subquery.c.total_price, 0),
|
||||
func.coalesce(price_subquery.c.total_price, 0),
|
||||
func.row_number().over(
|
||||
partition_by=Deal.current_status,
|
||||
order_by=Deal.lexorank
|
||||
@@ -142,7 +164,7 @@ class DealService(BaseService):
|
||||
joinedload(Deal.client)
|
||||
)
|
||||
.outerjoin(
|
||||
service_subquery, Deal.id == service_subquery.c.deal_id)
|
||||
price_subquery, Deal.id == price_subquery.c.deal_id)
|
||||
.where(
|
||||
Deal.is_deleted == False,
|
||||
Deal.is_completed == False
|
||||
@@ -177,6 +199,7 @@ class DealService(BaseService):
|
||||
return DealGetAllResponse(deals=result)
|
||||
|
||||
async def get_by_id(self, deal_id: int) -> DealSchema:
|
||||
|
||||
deal = await self.session.scalar(
|
||||
select(Deal)
|
||||
.options(
|
||||
@@ -190,13 +213,17 @@ class DealService(BaseService):
|
||||
selectinload(Deal.products)
|
||||
.joinedload(models.secondary.DealProduct.product)
|
||||
.joinedload(models.Product.barcodes),
|
||||
selectinload(Deal.products)
|
||||
.joinedload(models.secondary.DealProduct.services)
|
||||
.joinedload(models.secondary.DealProductService.service),
|
||||
selectinload(Deal.status_history)
|
||||
.joinedload(DealStatusHistory.user),
|
||||
selectinload(Deal.status_history)
|
||||
.noload(DealStatusHistory.deal)
|
||||
.noload(DealStatusHistory.deal),
|
||||
)
|
||||
.where(Deal.id == deal_id)
|
||||
)
|
||||
|
||||
if not deal:
|
||||
raise HTTPException(status_code=404, detail="Сделка не найдена")
|
||||
return DealSchema.model_validate(deal)
|
||||
@@ -328,7 +355,7 @@ class DealService(BaseService):
|
||||
deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id))
|
||||
if not deal:
|
||||
raise HTTPException(status_code=404, detail="Сделка не найдена")
|
||||
service = await self.session.scalar(select(Service).where(Service.id == request.service_id))
|
||||
service: models.Service = await self.session.scalar(select(Service).where(Service.id == request.service_id))
|
||||
if not service:
|
||||
raise HTTPException(status_code=404, detail="Услуга не найдена")
|
||||
# Preventing duplicates
|
||||
@@ -342,7 +369,8 @@ class DealService(BaseService):
|
||||
deal_service = models.secondary.DealService(
|
||||
deal_id=request.deal_id,
|
||||
service_id=request.service_id,
|
||||
quantity=request.quantity
|
||||
quantity=request.quantity,
|
||||
price=request.price
|
||||
)
|
||||
self.session.add(deal_service)
|
||||
await self.session.commit()
|
||||
@@ -382,6 +410,30 @@ class DealService(BaseService):
|
||||
await self.session.rollback()
|
||||
return DealDeleteServicesResponse(ok=False, message=str(e))
|
||||
|
||||
async def update_service(self, request: DealUpdateServiceRequest) -> DealUpdateServiceResponse:
|
||||
try:
|
||||
deal_service = await self.session.scalar(
|
||||
select(models.secondary.DealService)
|
||||
.where(models.secondary.DealService.deal_id == request.deal_id,
|
||||
models.secondary.DealService.service_id == request.service.service.id)
|
||||
)
|
||||
if not deal_service:
|
||||
raise HTTPException(status_code=404, detail="Сделка не найдена")
|
||||
service_dict = request.service.dict()
|
||||
del service_dict['service']
|
||||
service_dict['service_id'] = request.service.service.id
|
||||
await self.session.execute(
|
||||
update(models.secondary.DealService)
|
||||
.where(models.secondary.DealService.deal_id == request.deal_id,
|
||||
models.secondary.DealService.service_id == request.service.service.id)
|
||||
.values(**service_dict)
|
||||
)
|
||||
await self.session.commit()
|
||||
return DealUpdateServiceQuantityResponse(ok=True, message='Количество успешно обновлено')
|
||||
except Exception as e:
|
||||
await self.session.rollback()
|
||||
return DealUpdateServiceQuantityResponse(ok=False, message=str(e))
|
||||
|
||||
# endregion
|
||||
|
||||
# region Deal products
|
||||
@@ -408,23 +460,33 @@ class DealService(BaseService):
|
||||
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))
|
||||
product = await self.session.scalar(
|
||||
select(models.Product).where(models.Product.id == request.product.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)
|
||||
models.secondary.DealProduct.product_id == request.product.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
|
||||
product_id=request.product.product.id,
|
||||
quantity=request.product.quantity
|
||||
)
|
||||
self.session.add(deal_product)
|
||||
await self.session.flush()
|
||||
for service in request.product.services:
|
||||
deal_product_service = models.secondary.DealProductService(
|
||||
deal_id=request.deal_id,
|
||||
product_id=request.product.product.id,
|
||||
service_id=service.service.id,
|
||||
price=service.price
|
||||
)
|
||||
self.session.add(deal_product_service)
|
||||
await self.session.commit()
|
||||
return DealAddProductResponse(ok=True, message='Товар успешно добавлен')
|
||||
except Exception as e:
|
||||
@@ -461,4 +523,56 @@ class DealService(BaseService):
|
||||
except Exception as e:
|
||||
await self.session.rollback()
|
||||
return DealDeleteProductsResponse(ok=False, message=str(e))
|
||||
|
||||
async def update_product(self, request: DealUpdateProductRequest):
|
||||
try:
|
||||
deal_product: models.DealProduct = await self.session.scalar(
|
||||
select(models.secondary.DealProduct)
|
||||
.where(models.secondary.DealProduct.deal_id == request.deal_id,
|
||||
models.secondary.DealProduct.product_id == request.product.product.id)
|
||||
)
|
||||
if not deal_product:
|
||||
raise HTTPException(status_code=404, detail="Указанный товар не найден")
|
||||
# getting new services and deleted
|
||||
database_services = set([service.service_id for service in deal_product.services])
|
||||
request_services = set([service.service.id for service in request.product.services])
|
||||
|
||||
new_services = request_services.difference(database_services)
|
||||
deleted_services = database_services.difference(request_services)
|
||||
services_dict = {service.service.id: service for service in request.product.services}
|
||||
|
||||
# Deleting and updating existing services
|
||||
for service in deal_product.services:
|
||||
service: models.DealProductService
|
||||
if service.service_id in deleted_services:
|
||||
await self.session.delete(service)
|
||||
await self.session.flush()
|
||||
continue
|
||||
request_service = services_dict[service.service_id]
|
||||
service.price = request_service.price
|
||||
await self.session.flush()
|
||||
|
||||
# Creating services
|
||||
for service in request.product.services:
|
||||
if service.service.id not in new_services:
|
||||
continue
|
||||
deal_product_service = models.DealProductService(
|
||||
deal_id=request.deal_id,
|
||||
product_id=request.product.product.id,
|
||||
service_id=service.service.id,
|
||||
price=service.price
|
||||
)
|
||||
self.session.add(deal_product_service)
|
||||
await self.session.flush()
|
||||
|
||||
# Updating product
|
||||
deal_product.quantity = request.product.quantity
|
||||
|
||||
await self.session.commit()
|
||||
return DealUpdateProductResponse(ok=True, message='Товар успешно обновлен')
|
||||
|
||||
except Exception as e:
|
||||
await self.session.rollback()
|
||||
return DealUpdateProductResponse(ok=False, message=str(e))
|
||||
|
||||
# endregion
|
||||
|
||||
38
test/test.py
38
test/test.py
@@ -1,14 +1,46 @@
|
||||
import asyncio
|
||||
|
||||
from sqlalchemy import select, func, union
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from backend.session import session_maker
|
||||
from migrations.env import run_async_migrations
|
||||
from models import Product, ProductBarcode
|
||||
from models import Deal, DealProduct, Service
|
||||
|
||||
import models
|
||||
import models.secondary
|
||||
|
||||
|
||||
async def main(session: AsyncSession):
|
||||
await run_async_migrations()
|
||||
deal_services_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)
|
||||
)
|
||||
product_services_subquery = select(
|
||||
select(
|
||||
models.secondary.DealProductService.deal_id,
|
||||
func.sum(models.DealProduct.quantity * models.secondary.DealProductService.price).label('total_price')
|
||||
)
|
||||
.join(models.secondary.DealProduct)
|
||||
.group_by(models.secondary.DealProductService.deal_id)
|
||||
.subquery()
|
||||
)
|
||||
union_subqueries = deal_services_subquery.union(product_services_subquery).subquery()
|
||||
final_subquery = (
|
||||
select(
|
||||
union_subqueries.c.deal_id,
|
||||
func.sum(union_subqueries.c.total_price).label('total_sum')
|
||||
)
|
||||
.group_by(union_subqueries.c.deal_id)
|
||||
.subquery()
|
||||
)
|
||||
|
||||
print(final_subquery)
|
||||
|
||||
|
||||
async def preload():
|
||||
async with session_maker() as session:
|
||||
|
||||
Reference in New Issue
Block a user