439 lines
20 KiB
Python
439 lines
20 KiB
Python
import lexorank
|
||
from lexorank import Bucket, middle
|
||
from sqlalchemy import select, update, insert, delete, desc
|
||
from sqlalchemy.exc import IntegrityError
|
||
from sqlalchemy.orm import joinedload
|
||
|
||
from enums.service import ServiceType
|
||
from models import Service, ServiceCategory, ServicePriceRange, ServicesKit, services_kit_services
|
||
from schemas.service import *
|
||
from services.base import BaseService
|
||
from utils.list_utils import previous_current_next
|
||
|
||
|
||
class ServiceService(BaseService):
|
||
async def get_all(self, with_placeholders: bool) -> ServiceGetAllResponse:
|
||
query = await (
|
||
self.session
|
||
.scalars(
|
||
select(
|
||
Service
|
||
)
|
||
.options(
|
||
joinedload(Service.category)
|
||
)
|
||
.order_by(
|
||
Service.rank
|
||
)
|
||
.filter(
|
||
Service.is_deleted == False
|
||
)
|
||
)
|
||
)
|
||
services_raw = query.all()
|
||
services = []
|
||
|
||
for service in services_raw:
|
||
services.append(ServiceSchema.model_validate(service))
|
||
|
||
if not with_placeholders:
|
||
return ServiceGetAllResponse(services=services)
|
||
|
||
category_ids = list(set([service.category_id for service in services_raw]))
|
||
rest_categories = await (
|
||
self.session
|
||
.scalars(
|
||
select(
|
||
ServiceCategory
|
||
)
|
||
.where(
|
||
ServiceCategory.id.notin_(category_ids),
|
||
ServiceCategory.is_deleted == False
|
||
)
|
||
)
|
||
)
|
||
for category in rest_categories:
|
||
for service_type in [ServiceType.DEAL_SERVICE, ServiceType.PRODUCT_SERVICE]:
|
||
services.append(ServiceSchema(
|
||
id=0,
|
||
name='Пусто',
|
||
category=ServiceCategorySchema.model_validate(category),
|
||
price=0,
|
||
service_type=service_type,
|
||
price_ranges=[],
|
||
cost=0,
|
||
rank='',
|
||
is_placeholder=True
|
||
))
|
||
|
||
return ServiceGetAllResponse(services=services)
|
||
|
||
async def get_latest_rank_in_category(self, category_id: int, service_type: ServiceType) -> str:
|
||
stmt = (
|
||
select(Service.rank)
|
||
.where(Service.category_id == category_id)
|
||
.where(Service.service_type == service_type)
|
||
.order_by(desc(Service.rank))
|
||
.limit(1)
|
||
)
|
||
return await self.session.scalar(stmt) or ''
|
||
|
||
async def create(self, request: ServiceCreateRequest) -> ServiceCreateResponse:
|
||
try:
|
||
raw_service = request.service
|
||
service_dict = raw_service.model_dump()
|
||
service_dict['category_id'] = raw_service.category.id
|
||
del service_dict['id']
|
||
del service_dict['category']
|
||
del service_dict['price_ranges']
|
||
del service_dict['is_placeholder']
|
||
service_type = ServiceType(raw_service.service_type)
|
||
category_id = raw_service.category.id
|
||
|
||
latest_rank = await self.get_latest_rank_in_category(category_id, service_type)
|
||
if not latest_rank:
|
||
latest_rank = middle(Bucket.BUCEKT_0)
|
||
else:
|
||
latest_rank = lexorank.parse(latest_rank)
|
||
service_rank = str(latest_rank.next())
|
||
service_dict['rank'] = service_rank
|
||
service = Service(**service_dict)
|
||
self.session.add(service)
|
||
await self.session.flush()
|
||
price_ranges = request.service.price_ranges
|
||
for price_range in price_ranges:
|
||
price_range_dict = price_range.model_dump()
|
||
price_range_dict['service_id'] = service.id
|
||
del price_range_dict['id']
|
||
price_range_obj = ServicePriceRange(**price_range_dict)
|
||
self.session.add(price_range_obj)
|
||
await self.session.commit()
|
||
return ServiceCreateResponse(ok=True, message="Услуга успешно создана")
|
||
except Exception as e:
|
||
return ServiceCreateResponse(ok=False, message=f"Неудалось создать услугу, ошибка: {e}")
|
||
|
||
async def update(self, request: ServiceUpdateRequest) -> ServiceUpdateResponse:
|
||
try:
|
||
raw_service = request.data
|
||
service = await (self.session.get(Service, raw_service.id))
|
||
if not service:
|
||
return ServiceUpdateResponse(ok=False, message="Услуга не найдена")
|
||
prev_category_id = service.category_id
|
||
new_category_id = request.data.category.id
|
||
service_dict = raw_service.dict()
|
||
service_dict['category_id'] = raw_service.category.id
|
||
del service_dict['category']
|
||
del service_dict['price_ranges']
|
||
del service_dict['is_placeholder']
|
||
if prev_category_id != new_category_id:
|
||
latest_rank = await self.get_latest_rank_in_category(new_category_id, ServiceType(raw_service.service_type))
|
||
if not latest_rank:
|
||
latest_rank = middle(Bucket.BUCEKT_0)
|
||
else:
|
||
latest_rank = lexorank.parse(latest_rank)
|
||
service_dict['rank'] = str(latest_rank.next())
|
||
await self.session.execute(
|
||
update(Service)
|
||
.where(Service.id == raw_service.id)
|
||
.values(**service_dict)
|
||
)
|
||
# checking if old price ranges are still in the request
|
||
request_price_range_ids = [price_range.id for price_range in raw_service.price_ranges if price_range.id]
|
||
price_ranges_to_delete = []
|
||
for price_range in service.price_ranges:
|
||
if price_range.id not in request_price_range_ids:
|
||
price_ranges_to_delete.append(price_range)
|
||
for price_range in price_ranges_to_delete:
|
||
await self.session.delete(price_range)
|
||
await self.session.flush()
|
||
for price_range in raw_service.price_ranges:
|
||
price_range_dict = price_range.dict()
|
||
price_range_dict['service_id'] = raw_service.id
|
||
if price_range.id:
|
||
await self.session.execute(
|
||
update(ServicePriceRange)
|
||
.where(ServicePriceRange.id == price_range.id)
|
||
.values(**price_range_dict)
|
||
)
|
||
else:
|
||
del price_range_dict['id']
|
||
price_range_obj = ServicePriceRange(**price_range_dict)
|
||
self.session.add(price_range_obj)
|
||
|
||
await self.session.commit()
|
||
return ServiceUpdateResponse(ok=True, message="Услуга успешно обновлена")
|
||
except Exception as e:
|
||
return ServiceUpdateResponse(ok=False, message=f"Неудалось обновить услугу, ошибка: {e}")
|
||
|
||
async def delete(self, request: ServiceDeleteRequest) -> ServiceDeleteResponse:
|
||
try:
|
||
service = await (self.session
|
||
.scalar(select(Service)
|
||
.filter(Service.id == request.service_id)))
|
||
if not service:
|
||
return ServiceDeleteResponse(ok=False, message="Услуга не найдена")
|
||
await self.session.delete(service)
|
||
await self.session.commit()
|
||
return ServiceDeleteResponse(ok=True, message="Услуга успешно удалена")
|
||
except IntegrityError:
|
||
await self.session.rollback()
|
||
service = await (self.session
|
||
.scalar(select(Service)
|
||
.filter(Service.id == request.service_id)))
|
||
service.is_deleted = True
|
||
await self.session.commit()
|
||
return ServiceDeleteResponse(ok=True, message="Услуга успешно удалена")
|
||
except Exception as e:
|
||
return ServiceDeleteResponse(ok=False, message=f"Неудалось удалить услугу, ошибка: {e}")
|
||
|
||
async def create_category(self, request: ServiceCreateCategoryRequest) -> ServiceCreateCategoryResponse:
|
||
try:
|
||
raw_category = request.category
|
||
category_dict = raw_category.model_dump()
|
||
del category_dict['id']
|
||
last_deal_service_rank = await self.session.scalar(
|
||
select(ServiceCategory.deal_service_rank)
|
||
.order_by(ServiceCategory.deal_service_rank.desc())
|
||
.limit(1)
|
||
)
|
||
last_product_service_rank = await self.session.scalar(
|
||
select(ServiceCategory.product_service_rank)
|
||
.order_by(ServiceCategory.product_service_rank.desc())
|
||
.limit(1)
|
||
)
|
||
last_product_service_rank = lexorank.parse(
|
||
last_product_service_rank) if last_product_service_rank else middle(
|
||
Bucket.BUCEKT_0)
|
||
last_deal_service_rank = lexorank.parse(last_deal_service_rank) if last_deal_service_rank else middle(
|
||
Bucket.BUCEKT_0)
|
||
last_deal_service_rank = last_deal_service_rank.next()
|
||
last_product_service_rank = last_product_service_rank.next()
|
||
category_dict['deal_service_rank'] = str(last_deal_service_rank)
|
||
category_dict['product_service_rank'] = str(last_product_service_rank)
|
||
|
||
category = ServiceCategory(**category_dict)
|
||
self.session.add(category)
|
||
await self.session.commit()
|
||
return ServiceCreateCategoryResponse(ok=True, message="Категория успешно создана")
|
||
|
||
except Exception as e:
|
||
return ServiceCreateCategoryResponse(ok=False, message=f"Неудалось создать категорию, ошибка: {e}")
|
||
|
||
async def get_all_categories(self) -> ServiceGetAllCategoriesResponse:
|
||
query = await (self.session
|
||
.scalars(select(ServiceCategory)
|
||
.order_by(ServiceCategory.id)))
|
||
categories = []
|
||
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))
|
||
|
||
async def reorder(self, request: ServiceReorderRequest) -> ServiceReorderResponse:
|
||
try:
|
||
draining_service = await (self.session.get(Service, request.draining_service_id))
|
||
hovered_service = await (self.session.get(Service, request.hovered_service_id))
|
||
if not draining_service or not hovered_service:
|
||
return ServiceReorderResponse(ok=False, message="Услуга не найдена")
|
||
if draining_service.category_id == hovered_service.category_id:
|
||
temp = draining_service.rank
|
||
draining_service.rank = hovered_service.rank
|
||
hovered_service.rank = temp
|
||
else:
|
||
return ServiceReorderResponse(ok=False, message="Нельзя перемещать услуги между категориями")
|
||
await self.session.commit()
|
||
return ServiceReorderResponse(ok=True, message="Услуги успешно пересортированы")
|
||
except Exception as e:
|
||
return ServiceReorderResponse(ok=False, message=f"Неудалось найти услугу, ошибка: {e}")
|
||
|
||
async def reorder_category(self, request: ServiceCategoryReorderRequest) -> ServiceCategoryReorderResponse:
|
||
try:
|
||
if request.move_down and request.move_up:
|
||
return ServiceCategoryReorderResponse(ok=False,
|
||
message="Невозможно выполнить два действия одновременно")
|
||
if not request.move_down and not request.move_up:
|
||
return ServiceCategoryReorderResponse(ok=False, message="Не указано действие")
|
||
if request.service_type == ServiceType.DEAL_SERVICE:
|
||
order_by = ServiceCategory.deal_service_rank
|
||
rank_field = 'deal_service_rank'
|
||
else:
|
||
order_by = ServiceCategory.product_service_rank
|
||
rank_field = 'product_service_rank'
|
||
service_categories = await (
|
||
self.session
|
||
.scalars(select(ServiceCategory)
|
||
.order_by(order_by))
|
||
)
|
||
service_categories = service_categories.all()
|
||
for prv, cur, nxt in previous_current_next(service_categories):
|
||
if request.category_id == cur.id:
|
||
if request.move_down:
|
||
if nxt:
|
||
temp = getattr(cur, rank_field)
|
||
# cur.rank = nxt.rank
|
||
setattr(cur, rank_field, getattr(nxt, rank_field))
|
||
# nxt.rank = temp
|
||
setattr(nxt, rank_field, temp)
|
||
else:
|
||
return ServiceCategoryReorderResponse(ok=False, message="Невозможно переместить вниз")
|
||
elif request.move_up:
|
||
if prv:
|
||
temp = getattr(cur, rank_field)
|
||
# cur.rank = prv.rank
|
||
setattr(cur, rank_field, getattr(prv, rank_field))
|
||
# prv.rank = temp
|
||
setattr(prv, rank_field, temp)
|
||
else:
|
||
return ServiceCategoryReorderResponse(ok=False, message="Невозможно переместить вверх")
|
||
await self.session.commit()
|
||
return ServiceCategoryReorderResponse(ok=True, message="Категории успешно пересортированы")
|
||
except Exception as e:
|
||
return ServiceCategoryReorderResponse(ok=False, message=f"Неудалось пересортировать категорию, ошибка: {e}")
|
||
|
||
async def update_category(self, request: ServiceUpdateCategoryRequest) -> ServiceUpdateResponse:
|
||
try:
|
||
raw_category = request.category
|
||
category = await (self.session.get(ServiceCategory, raw_category.id))
|
||
if not category:
|
||
return ServiceUpdateResponse(ok=False, message="Категория не найдена")
|
||
category_dict = raw_category.dict()
|
||
del category_dict['id']
|
||
await self.session.execute(
|
||
update(ServiceCategory)
|
||
.where(ServiceCategory.id == raw_category.id)
|
||
.values(**category_dict)
|
||
)
|
||
await self.session.commit()
|
||
return ServiceUpdateResponse(ok=True, message="Категория успешно обновлена")
|
||
except Exception as e:
|
||
return ServiceUpdateResponse(ok=False, message=f"Неудалось обновить категорию, ошибка: {e}")
|
||
|
||
async def delete_category(self, request: ServiceDeleteCategoryRequest) -> ServiceDeleteResponse:
|
||
try:
|
||
active_services = await self.session.scalars(
|
||
select(
|
||
Service
|
||
)
|
||
.where(
|
||
Service.category_id == request.category_id,
|
||
Service.is_deleted == False
|
||
)
|
||
)
|
||
if active_services.first():
|
||
return ServiceDeleteResponse(ok=False, message="Категория используется в услугах")
|
||
|
||
# Check if category is used in deleted services
|
||
deleted_services = await self.session.scalars(
|
||
select(
|
||
Service
|
||
)
|
||
.where(
|
||
Service.category_id == request.category_id,
|
||
Service.is_deleted == True
|
||
)
|
||
)
|
||
category = await self.session.scalar(
|
||
select(
|
||
ServiceCategory
|
||
)
|
||
.filter(
|
||
ServiceCategory.id == request.category_id
|
||
)
|
||
)
|
||
if not category:
|
||
return ServiceDeleteResponse(ok=False, message="Категория не найдена")
|
||
|
||
if deleted_services.first():
|
||
category.is_deleted = True
|
||
else:
|
||
await self.session.delete(category)
|
||
|
||
await self.session.commit()
|
||
return ServiceDeleteResponse(ok=True, message="Категория успешно удалена")
|
||
except Exception as e:
|
||
return ServiceDeleteResponse(ok=False, message=f"Неудалось удалить категорию, ошибка: {e}")
|