feat: services kit and copy

This commit is contained in:
2024-08-06 04:53:50 +03:00
parent 93b0315b4a
commit a7c4fabed0
9 changed files with 442 additions and 28 deletions

View File

@@ -103,7 +103,7 @@ class DealProduct(BaseModel):
primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, "
"DealProductService.product_id == DealProduct.product_id)",
foreign_keys=[DealProductService.deal_id, DealProductService.product_id],
lazy='joined',
lazy='selectin',
order_by="desc(DealProductService.service_id)"
)
@@ -114,3 +114,4 @@ barcode_template_attribute_link = Table(
Column('barcode_template_id', ForeignKey('barcode_templates.id')),
Column('attribute_id', ForeignKey('barcode_template_attributes.id'))
)

View File

@@ -1,9 +1,17 @@
from sqlalchemy import Column, Integer, String, ForeignKey, Double, asc
from sqlalchemy import Column, Integer, String, ForeignKey, Double, asc, Table
from sqlalchemy.orm import relationship, mapped_column, Mapped
import enums.service
from models import BaseModel
services_kit_services = Table(
'services_kit_services',
BaseModel.metadata,
Column('services_kit_id', ForeignKey('services_kits.id')),
Column('service_id', ForeignKey('services.id')),
)
class Service(BaseModel):
__tablename__ = 'services'
@@ -50,3 +58,18 @@ class ServiceCategory(BaseModel):
__tablename__ = 'service_categories'
id = Column(Integer, autoincrement=True, primary_key=True, index=True)
name = Column(String, nullable=False)
class ServicesKit(BaseModel):
__tablename__ = 'services_kits'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(nullable=False)
service_type: Mapped[int] = mapped_column(
server_default=f'{enums.service.ServiceType.DEAL_SERVICE}',
nullable=False,
comment='Тип услуги'
)
services: Mapped[list["Service"]] = relationship(
secondary=services_kit_services,
lazy='selectin'
)

View File

@@ -3,6 +3,7 @@ from typing import Annotated
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from backend.dependecies import SessionDependency
from backend.session import get_session
from models import User
from schemas.deal import *
@@ -16,6 +17,7 @@ deal_router = APIRouter(
)
# region Deal
@deal_router.post('/create')
async def create(
request: DealCreateRequest,
@@ -113,6 +115,17 @@ async def update_general_info(
):
return await DealService(session).update_general_info(request)
@deal_router.post(
'/add-kit',
response_model=DealAddKitResponse,
operation_id='add_kit_to_deal'
)
async def add_kit_to_deal(
session:SessionDependency,
request:DealAddKitRequest
):
return await DealService(session).add_kit_to_deal(request)
# endregion
# region Deal services
@@ -188,6 +201,18 @@ async def services_delete(
return await DealService(session).delete_services(request)
@deal_router.post(
'/services/copy',
response_model=DealServicesCopyResponse,
operation_id='copy_product_services'
)
async def services_copy(
session: SessionDependency,
request: DealServicesCopyRequest
):
return await DealService(session).copy_services(request)
# endregion
# region Deal products
@@ -241,4 +266,16 @@ async def products_update(
session: Annotated[AsyncSession, Depends(get_session)]
):
return await DealService(session).update_product(request)
@deal_router.post(
'/product/add-kit',
response_model=DealProductAddKitResponse,
operation_id='add_kit_to_deal_product'
)
async def add_kit_to_deal_product(
session: SessionDependency,
request: DealProductAddKitRequest
):
return await DealService(session).add_kit_to_deal_product(request)
# endregion

View File

@@ -4,6 +4,7 @@ from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
import enums.service
from backend.dependecies import SessionDependency
from backend.session import get_session
from schemas.base import BaseEnumSchema, BaseEnumListSchema
from schemas.service import *
@@ -98,3 +99,38 @@ async def get_all_service_types(
for key, value in enums.service.SERVICE_TYPE_LABELS.items():
result.append({"id": key, "name": value})
return BaseEnumListSchema(items=result)
@service_router.get(
'/kits/get-all',
response_model=GetAllServicesKitsResponse,
operation_id='get_all_services_kits'
)
async def get_all_services_kits(
session: SessionDependency
):
return await ServiceService(session).get_all_kits()
@service_router.post(
'/kits/create',
response_model=CreateServicesKitResponse,
operation_id='create_services_kit'
)
async def create_services_kit(
session: SessionDependency,
request: CreateServicesKitRequest
):
return await ServiceService(session).create_kit(request)
@service_router.post(
'/kits/update',
response_model=UpdateServicesKitResponse,
operation_id='update_services_kit'
)
async def update_services_kit(
session: SessionDependency,
request: UpdateServicesKitRequest
):
return await ServiceService(session).update_kit(request)

View File

@@ -185,6 +185,23 @@ class DealUpdateProductRequest(BaseSchema):
product: DealProductSchema
class DealServicesCopyRequest(BaseSchema):
deal_id: int
source_product_id: int
destination_product_ids: List[int]
class DealProductAddKitRequest(BaseSchema):
deal_id: int
product_id: int
kit_id: int
class DealAddKitRequest(BaseSchema):
deal_id: int
kit_id: int
# endregion Requests
# region Responses
@@ -264,4 +281,16 @@ class DealDeleteResponse(OkMessageSchema):
class DealUpdateProductResponse(OkMessageSchema):
pass
class DealServicesCopyResponse(OkMessageSchema):
pass
class DealProductAddKitResponse(OkMessageSchema):
pass
class DealAddKitResponse(OkMessageSchema):
pass
# endregion Responses

View File

@@ -26,6 +26,25 @@ class ServiceSchema(BaseSchema):
cost: Optional[int]
class BaseServiceKitSchema(BaseSchema):
name: str
service_type: int
class GetServiceKitSchema(BaseServiceKitSchema):
id: int
services: List[ServiceSchema]
class CreateServiceKitSchema(BaseServiceKitSchema):
services_ids: List[int]
class UpdateServiceKitSchema(BaseServiceKitSchema):
id: int
services_ids: List[int]
# endregion
@@ -46,6 +65,14 @@ class ServiceDeleteRequest(BaseSchema):
service_id: int
class CreateServicesKitRequest(BaseSchema):
data: CreateServiceKitSchema
class UpdateServicesKitRequest(BaseSchema):
data: UpdateServiceKitSchema
# endregion
@@ -72,4 +99,16 @@ class ServiceUpdateResponse(OkMessageSchema):
class ServiceDeleteResponse(OkMessageSchema):
pass
class CreateServicesKitResponse(OkMessageSchema):
pass
class UpdateServicesKitResponse(OkMessageSchema):
pass
class GetAllServicesKitsResponse(BaseSchema):
services_kits: List[GetServiceKitSchema]
# endregion

View File

@@ -13,6 +13,7 @@ from schemas.client import ClientDetailsSchema
from schemas.deal import *
from services.base import BaseService
from services.client import ClientService
from services.service import ServiceService
from services.shipping_warehouse import ShippingWarehouseService
@@ -317,6 +318,48 @@ class DealService(BaseService):
await self.session.commit()
return await self.get_summary()
async def add_kit_to_deal(self, request: DealAddKitRequest) -> DealAddKitResponse:
try:
deal = await self._get_deal_by_id(request.deal_id)
if not deal:
return DealAddKitResponse(ok=False, message="Указанная сделка не найдена")
kit = await ServiceService(self.session).get_kit_by_id(request.kit_id)
if not kit:
return DealAddKitResponse(ok=False, message="Указанный набор услуг не найден")
services: list[models.Service] = kit.services
insert_data = []
for service in services:
price = self.get_service_price(service, 1)
insert_data.append({
'deal_id': deal.id,
'service_id': service.id,
'quantity': 1,
'price': price
})
if not insert_data:
return DealAddKitResponse(ok=True, message="Набор услуг успешно добавлен к сделке")
# Deleting previous services
delete_stmt = (
delete(
models.DealService
)
.where(
models.DealService.deal_id == request.deal_id
)
)
await self.session.execute(delete_stmt)
await self.session.flush()
await self.session.execute(
insert(models.DealService),
insert_data
)
await self.session.flush()
await self.session.commit()
return DealAddKitResponse(ok=True, message="Набор услуг успешно добавлен к сделке")
except Exception as e:
return DealAddKitResponse(ok=False, message=str(e))
# endregion
# region Deal services
@@ -492,6 +535,82 @@ class DealService(BaseService):
await self.session.rollback()
return DealUpdateServiceQuantityResponse(ok=False, message=str(e))
async def copy_services(self, request: DealServicesCopyRequest) -> DealServicesCopyResponse:
try:
source_services_stmt = (
select(
models.DealProductService
)
.where(
models.DealProductService.product_id == request.source_product_id,
models.DealProductService.deal_id == request.deal_id,
)
)
deal_product_services: list[models.DealProductService] = (
await self.session.scalars(source_services_stmt)).all()
source_services: list[models.Service] = [dpc.service for dpc in deal_product_services]
destination_deal_products_stmt = (
select(
models.DealProduct
)
.where(
models.DealProduct.product_id.in_(request.destination_product_ids),
models.DealProduct.deal_id == request.deal_id
)
)
destination_deal_products = (await self.session.scalars(destination_deal_products_stmt)).all()
insert_data = []
for deal_product in destination_deal_products:
for service in source_services:
service_price = self.get_service_price(service, deal_product.quantity)
insert_data.append({
'deal_id': request.deal_id,
'product_id': deal_product.product_id,
'service_id': service.id,
'price': service_price
})
if not insert_data:
return DealServicesCopyResponse(ok=True, message='Услуги успешно перенесены')
# Deleting previous DealProductService-s
delete_stmt = (
delete(
models.DealProductService
)
.where(
models.DealProductService.product_id.in_(request.destination_product_ids),
models.DealProductService.deal_id == request.deal_id
)
)
await self.session.execute(delete_stmt)
await self.session.flush()
await self.session.execute(
insert(models.DealProductService),
insert_data
)
await self.session.flush()
await self.session.commit()
return DealServicesCopyResponse(ok=True, message='Услуги успешно перенесены')
except Exception as e:
return DealServicesCopyResponse(ok=False, message=str(e))
@staticmethod
def get_service_price(service: models.Service, quantity: int):
price = 0
price_ranges: list[models.ServicePriceRange] = service.price_ranges
for price_range in price_ranges:
if price_range.from_quantity <= quantity <= price_range.to_quantity:
price = price_range.price
break
if not price and len(price_ranges) > 0:
price = price_ranges[0].price
if not price:
price = service.price
return price
# endregion
# region Deal products
@@ -662,4 +781,56 @@ class DealService(BaseService):
await self.session.rollback()
return DealUpdateProductResponse(ok=False, message=str(e))
async def add_kit_to_deal_product(self, request: DealProductAddKitRequest) -> DealProductAddKitResponse:
try:
service_service = ServiceService(self.session)
kit = await service_service.get_kit_by_id(request.kit_id)
if not kit:
return DealProductAddKitResponse(ok=False, message='Указанный набор услуг не найден')
services: list[models.Service] = kit.services
deal_product_stmt = (
select(
models.DealProduct
)
.where(
models.DealProduct.deal_id == request.deal_id,
models.DealProduct.product_id == request.product_id
)
)
deal_product: Optional[models.DealProduct] = await self.session.scalar(deal_product_stmt)
if not deal_product:
return DealProductAddKitResponse(ok=False, message='Указанный товар не найден')
insert_data = []
for service in services:
service_price = self.get_service_price(service, deal_product.quantity)
insert_data.append({
'deal_id': request.deal_id,
'product_id': deal_product.product_id,
'service_id': service.id,
'price': service_price
})
if not insert_data:
return DealProductAddKitResponse(ok=True, message='Набор услуг успешно добавлен к товару')
# Deleting previous DealProductService-s
delete_stmt = (
delete(
models.DealProductService
)
.where(
models.DealProductService.product_id == deal_product.product_id,
models.DealProductService.deal_id == request.deal_id
)
)
await self.session.execute(delete_stmt)
await self.session.flush()
await self.session.execute(
insert(models.DealProductService),
insert_data
)
await self.session.flush()
await self.session.commit()
return DealProductAddKitResponse(ok=True, message='Набор услуг успешно добавлен к товару')
except Exception as e:
return DealProductAddKitResponse(ok=False, message=str(e))
# endregion

View File

@@ -1,12 +1,11 @@
from sqlalchemy import select, update
from typing import Union
from sqlalchemy import select, update, insert, delete
from sqlalchemy.orm import joinedload
from models import Service, ServiceCategory, ServicePriceRange
from models import Service, ServiceCategory, ServicePriceRange, ServicesKit, services_kit_services
from services.base import BaseService
from schemas.service import ServiceGetAllResponse, ServiceSchema, ServiceGetAllCategoriesResponse, \
ServiceCategorySchema, ServiceCreateRequest, ServiceCreateResponse, ServiceCreateCategoryRequest, \
ServiceCreateCategoryResponse, ServiceUpdateRequest, ServiceUpdateResponse, ServiceDeleteResponse, \
ServiceDeleteRequest
from schemas.service import *
class ServiceService(BaseService):
@@ -121,3 +120,88 @@ class ServiceService(BaseService):
for category in query.all():
categories.append(ServiceCategorySchema.model_validate(category))
return ServiceGetAllCategoriesResponse(categories=categories)
async def get_kit_by_name(self, name: str) -> Optional[ServicesKit]:
return await self.session.scalar(select(ServicesKit).where(ServicesKit.name == name))
async def get_kit_by_id(self, kit_id: int) -> Optional[ServicesKit]:
return await self.session.scalar(select(ServicesKit).where(ServicesKit.id == kit_id))
async def get_all_kits(self) -> GetAllServicesKitsResponse:
stmt = (
select(
ServicesKit
)
.order_by(
ServicesKit.id.desc()
)
)
kits = (await self.session.scalars(stmt)).all()
kits_schemas = GetServiceKitSchema.from_orm_list(kits)
return GetAllServicesKitsResponse(services_kits=kits_schemas)
async def create_kit(self, request: CreateServicesKitRequest) -> CreateServicesKitResponse:
try:
if await self.get_kit_by_name(request.data.name):
return CreateServicesKitResponse(ok=False, message='Набор услуг с таким названием уже существует')
base_fields = request.data.model_dump_parent()
kit = ServicesKit(**base_fields)
self.session.add(kit)
await self.session.flush()
# Appending services
insert_data = []
for service_id in request.data.services_ids:
insert_data.append({
'service_id': service_id,
'services_kit_id': kit.id
})
if insert_data:
await self.session.execute(
insert(services_kit_services),
insert_data
)
await self.session.flush()
await self.session.commit()
return CreateServicesKitResponse(ok=True, message='Набор услуг успешно создан')
except Exception as e:
return CreateServicesKitResponse(ok=False, message=str(e))
async def update_kit(self, request: UpdateServicesKitRequest) -> UpdateServicesKitResponse:
try:
kit = await self.get_kit_by_id(request.data.id)
if not kit:
return UpdateServicesKitResponse(ok=False, message='Указанный набор услуг не существует')
base_fields = request.data.model_dump_parent()
stmt = update(ServicesKit).values(**base_fields).where(ServicesKit.id == request.data.id)
await self.session.execute(stmt)
await self.session.flush()
# Deleting previous services
stmt = (
delete(
services_kit_services
).where(
services_kit_services.c.services_kit_id == kit.id
)
)
await self.session.execute(stmt)
await self.session.flush()
insert_data = []
for service_id in request.data.services_ids:
insert_data.append({
'service_id': service_id,
'services_kit_id': kit.id
})
if insert_data:
await self.session.execute(
insert(services_kit_services),
insert_data
)
await self.session.flush()
await self.session.commit()
return UpdateServicesKitResponse(ok=True, message='Набор услуг успешно обновлен')
except Exception as e:
return UpdateServicesKitResponse(ok=False, message=str(e))

34
test.py
View File

@@ -5,6 +5,7 @@ from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload
import models
from backend.session import session_maker
from models import User, PaymentRecord
@@ -12,31 +13,24 @@ from models import User, PaymentRecord
async def main():
session: AsyncSession = session_maker()
try:
d = datetime.date.today()
d = d.replace(day=1)
print(d)
stmt = (
deal_id = 133
source_product_id = 253
source_services_stmt = (
select(
PaymentRecord
)
.select_from(PaymentRecord)
.options(
joinedload(
PaymentRecord.user
)
models.DealProductService
)
.where(
func.date(func.date_trunc('month', PaymentRecord.start_date)) == d,
func.date(func.date_trunc('month', PaymentRecord.end_date)) == d,
PaymentRecord.start_date == PaymentRecord.end_date,
# PaymentRecord.user_id.in_(request.user_ids)
models.DealProductService.product_id == source_product_id,
models.DealProductService.deal_id == deal_id,
)
)
print(stmt.compile(compile_kwargs={
'literal_binds': True
}))
query_result = (await session.scalars(stmt)).all()
print(query_result)
result = (await session.scalars(source_services_stmt)).all()
services = [d.service for d in result]
for service in services:
print(
service.price_ranges
)
except Exception as e:
print(e)
await session.close()