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.card_service_rank)
 | 
						||
                .order_by(ServiceCategory.card_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.card_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}")
 |