feat: deal product services

This commit is contained in:
2024-05-19 04:08:10 +03:00
parent 30886d223c
commit e2de43064a
6 changed files with 233 additions and 30 deletions

View File

@@ -33,7 +33,6 @@ class Deal(BaseModel):
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('DealService', back_populates='deal', cascade="all, delete-orphan") 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") products = relationship('DealProduct', back_populates='deal', cascade="all, delete-orphan")

View File

@@ -21,11 +21,20 @@ class DealService(BaseModel):
class DealProductService(BaseModel): class DealProductService(BaseModel):
__tablename__ = 'deal_product_services' __tablename__ = 'deal_product_services'
deal_id = Column(Integer, nullable=False, primary_key=True, comment='ID Сделки') deal_id = Column(Integer, primary_key=True, nullable=False, comment='ID Сделки')
product_id = Column(Integer, nullable=False, primary_key=True, comment='ID Продукта') product_id = Column(Integer, primary_key=True, nullable=False, comment='ID Продукта')
service_id = Column(Integer, ForeignKey('services.id'), nullable=False, comment='ID Услуги') service_id = Column(Integer, ForeignKey('services.id'), primary_key=True, nullable=False, comment='ID Услуги')
price = Column(Integer, nullable=False, comment='Цена услуги') 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__ = ( __table_args__ = (
ForeignKeyConstraint( ForeignKeyConstraint(
['deal_id', 'product_id'], ['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): class DealProduct(BaseModel):
__tablename__ = 'deal_products' __tablename__ = 'deal_products'
deal_id = Column(Integer, ForeignKey('deals.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'), nullable=False, comment='ID Продукта', primary_key=True) product_id = Column(Integer, ForeignKey('products.id'), primary_key=True, nullable=False, comment='ID Продукта')
quantity = Column(Integer, nullable=False, comment='Кол-во продукта') quantity = Column(Integer, nullable=False, comment='Кол-во продукта')
deal = relationship('Deal', back_populates='products') deal = relationship('Deal',
product = relationship('Product') 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', services = relationship('DealProductService',
cascade="all, delete-orphan") 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( barcode_template_attribute_link = Table(

View File

@@ -145,13 +145,25 @@ async def services_add(
response_model=DealUpdateServiceQuantityResponse, response_model=DealUpdateServiceQuantityResponse,
operation_id='updateDealServiceQuantity' operation_id='updateDealServiceQuantity'
) )
async def services_update( async def services_update_quantity(
request: DealUpdateServiceQuantityRequest, request: DealUpdateServiceQuantityRequest,
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)]
): ):
return await DealService(session).update_service_quantity(request) 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( @deal_router.post(
'/services/delete', '/services/delete',
response_model=DealDeleteServiceResponse, response_model=DealDeleteServiceResponse,
@@ -218,4 +230,15 @@ async def products_delete(
session: Annotated[AsyncSession, Depends(get_session)]): session: Annotated[AsyncSession, Depends(get_session)]):
return await DealService(session).delete_products(request) 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 # endregion

View File

@@ -109,10 +109,16 @@ class DealUpdateServiceQuantityRequest(CustomModelCamel):
quantity: int quantity: int
class DealUpdateServiceRequest(CustomModelCamel):
deal_id: int
service: DealServiceSchema
class DealAddServiceRequest(CustomModelCamel): class DealAddServiceRequest(CustomModelCamel):
deal_id: int deal_id: int
service_id: int service_id: int
quantity: int quantity: int
price: int
class DealDeleteServiceRequest(CustomModelCamel): class DealDeleteServiceRequest(CustomModelCamel):
@@ -163,6 +169,11 @@ class DealDeleteRequest(CustomModelCamel):
deal_id: int deal_id: int
class DealUpdateProductRequest(CustomModelCamel):
deal_id: int
product: DealProductSchema
# endregion Requests # endregion Requests
# region Responses # region Responses
@@ -204,6 +215,10 @@ class DealUpdateServiceQuantityResponse(CustomModelCamel):
message: str message: str
class DealUpdateServiceResponse(OkMessageSchema):
pass
class DealAddServiceResponse(OkMessageSchema): class DealAddServiceResponse(OkMessageSchema):
pass pass
@@ -234,4 +249,8 @@ class DealSummaryReorderResponse(OkMessageSchema):
class DealDeleteResponse(OkMessageSchema): class DealDeleteResponse(OkMessageSchema):
pass pass
class DealUpdateProductResponse(OkMessageSchema):
pass
# endregion Responses # endregion Responses

View File

@@ -4,7 +4,7 @@ import models.secondary
from typing import Union from typing import Union
import models.deal import models.deal
from fastapi import HTTPException from fastapi import HTTPException
from sqlalchemy import select, func from sqlalchemy import select, func, update
from sqlalchemy.orm import joinedload, selectinload from sqlalchemy.orm import joinedload, selectinload
from models import User, Service, Client from models import User, Service, Client
@@ -118,20 +118,42 @@ class DealService(BaseService):
await self.session.commit() await self.session.commit()
return DealChangeStatusResponse(ok=True) return DealChangeStatusResponse(ok=True)
async def get_summary(self) -> DealSummaryResponse: def _get_price_subquery(self):
service_subquery = ( deal_services_subquery = (
select( select(
models.secondary.DealService.deal_id, 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) .join(Service)
.group_by(models.secondary.DealService.deal_id) .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() .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 = ( q = (
select( select(
Deal, Deal,
func.coalesce(service_subquery.c.total_price, 0), func.coalesce(price_subquery.c.total_price, 0),
func.row_number().over( func.row_number().over(
partition_by=Deal.current_status, partition_by=Deal.current_status,
order_by=Deal.lexorank order_by=Deal.lexorank
@@ -142,7 +164,7 @@ class DealService(BaseService):
joinedload(Deal.client) joinedload(Deal.client)
) )
.outerjoin( .outerjoin(
service_subquery, Deal.id == service_subquery.c.deal_id) price_subquery, Deal.id == price_subquery.c.deal_id)
.where( .where(
Deal.is_deleted == False, Deal.is_deleted == False,
Deal.is_completed == False Deal.is_completed == False
@@ -177,6 +199,7 @@ class DealService(BaseService):
return DealGetAllResponse(deals=result) return DealGetAllResponse(deals=result)
async def get_by_id(self, deal_id: int) -> DealSchema: async def get_by_id(self, deal_id: int) -> DealSchema:
deal = await self.session.scalar( deal = await self.session.scalar(
select(Deal) select(Deal)
.options( .options(
@@ -190,13 +213,17 @@ class DealService(BaseService):
selectinload(Deal.products) selectinload(Deal.products)
.joinedload(models.secondary.DealProduct.product) .joinedload(models.secondary.DealProduct.product)
.joinedload(models.Product.barcodes), .joinedload(models.Product.barcodes),
selectinload(Deal.products)
.joinedload(models.secondary.DealProduct.services)
.joinedload(models.secondary.DealProductService.service),
selectinload(Deal.status_history) selectinload(Deal.status_history)
.joinedload(DealStatusHistory.user), .joinedload(DealStatusHistory.user),
selectinload(Deal.status_history) selectinload(Deal.status_history)
.noload(DealStatusHistory.deal) .noload(DealStatusHistory.deal),
) )
.where(Deal.id == deal_id) .where(Deal.id == deal_id)
) )
if not deal: if not deal:
raise HTTPException(status_code=404, detail="Сделка не найдена") raise HTTPException(status_code=404, detail="Сделка не найдена")
return DealSchema.model_validate(deal) 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)) deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id))
if not deal: if not deal:
raise HTTPException(status_code=404, detail="Сделка не найдена") 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: if not service:
raise HTTPException(status_code=404, detail="Услуга не найдена") raise HTTPException(status_code=404, detail="Услуга не найдена")
# Preventing duplicates # Preventing duplicates
@@ -342,7 +369,8 @@ class DealService(BaseService):
deal_service = models.secondary.DealService( deal_service = models.secondary.DealService(
deal_id=request.deal_id, deal_id=request.deal_id,
service_id=request.service_id, service_id=request.service_id,
quantity=request.quantity quantity=request.quantity,
price=request.price
) )
self.session.add(deal_service) self.session.add(deal_service)
await self.session.commit() await self.session.commit()
@@ -382,6 +410,30 @@ class DealService(BaseService):
await self.session.rollback() await self.session.rollback()
return DealDeleteServicesResponse(ok=False, message=str(e)) 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 # endregion
# region Deal products # region Deal products
@@ -408,23 +460,33 @@ class DealService(BaseService):
deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id)) deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id))
if not deal: if not deal:
raise HTTPException(status_code=404, detail="Сделка не найдена") 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: if not product:
raise HTTPException(status_code=404, detail="Товар не найден") raise HTTPException(status_code=404, detail="Товар не найден")
# Preventing duplicates # Preventing duplicates
deal_product = await self.session.scalar( deal_product = await self.session.scalar(
select(models.secondary.DealProduct) select(models.secondary.DealProduct)
.where(models.secondary.DealProduct.deal_id == request.deal_id, .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: if deal_product:
raise HTTPException(status_code=400, detail="Товар уже добавлен") raise HTTPException(status_code=400, detail="Товар уже добавлен")
deal_product = models.secondary.DealProduct( deal_product = models.secondary.DealProduct(
deal_id=request.deal_id, deal_id=request.deal_id,
product_id=request.product_id, product_id=request.product.product.id,
quantity=request.quantity quantity=request.product.quantity
) )
self.session.add(deal_product) 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() await self.session.commit()
return DealAddProductResponse(ok=True, message='Товар успешно добавлен') return DealAddProductResponse(ok=True, message='Товар успешно добавлен')
except Exception as e: except Exception as e:
@@ -461,4 +523,56 @@ class DealService(BaseService):
except Exception as e: except Exception as e:
await self.session.rollback() await self.session.rollback()
return DealDeleteProductsResponse(ok=False, message=str(e)) 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 # endregion

View File

@@ -1,14 +1,46 @@
import asyncio import asyncio
from sqlalchemy import select, func, union
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload
from backend.session import session_maker from backend.session import session_maker
from migrations.env import run_async_migrations from models import Deal, DealProduct, Service
from models import Product, ProductBarcode
import models
import models.secondary
async def main(session: AsyncSession): 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 def preload():
async with session_maker() as session: async with session_maker() as session: