From 6030591e3ce74ad056fb0f69c3b818c7d9d53480 Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Sun, 9 Mar 2025 19:30:52 +0400 Subject: [PATCH] feat: added tags for cards, aligned status headers --- enums/profit_table_group_by.py | 1 + main.py | 1 + models/__init__.py | 1 + models/card.py | 15 ++- models/card_tag.py | 45 +++++++++ models/project.py | 10 +- routers/__init__.py | 1 + routers/card_tag.py | 63 ++++++++++++ schemas/card.py | 4 + schemas/card_tag.py | 53 ++++++++++ schemas/project.py | 2 + schemas/statistics.py | 1 + services/barcode.py | 2 - services/card.py | 16 +-- services/card_tag.py | 122 +++++++++++++++++++++++ services/project.py | 5 +- services/statistics/profit_statistics.py | 56 +++++++++-- 17 files changed, 375 insertions(+), 23 deletions(-) create mode 100644 models/card_tag.py create mode 100644 routers/card_tag.py create mode 100644 schemas/card_tag.py create mode 100644 services/card_tag.py diff --git a/enums/profit_table_group_by.py b/enums/profit_table_group_by.py index a545941..8004ce9 100644 --- a/enums/profit_table_group_by.py +++ b/enums/profit_table_group_by.py @@ -10,3 +10,4 @@ class ProfitTableGroupBy(IntEnum): BY_WAREHOUSES = 5 BY_MARKETPLACES = 6 BY_MANAGERS = 7 + BY_TAGS = 8 diff --git a/main.py b/main.py index a79c07e..a969ddd 100644 --- a/main.py +++ b/main.py @@ -57,6 +57,7 @@ routers_list = [ routers.project_router, routers.board_router, routers.status_router, + routers.card_tag_router, ] for router in routers_list: app.include_router(router) diff --git a/models/__init__.py b/models/__init__.py index abb95c7..1ee7bfa 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -6,6 +6,7 @@ from .board import * from .status import * from .attribute import * from .card import * +from .card_tag import * from .auth import * from .card import * from .client import * diff --git a/models/card.py b/models/card.py index 69a8e34..90f29e6 100644 --- a/models/card.py +++ b/models/card.py @@ -10,8 +10,10 @@ from .shipping import Pallet, Box from .shipping_warehouse import ShippingWarehouse if TYPE_CHECKING: - from . import CardBillRequest, User, BaseModel, Board, CardStatus, CardGroup, CardAttribute, CardService as CardServiceModel, \ - CardProduct, Client + from . import ( + CardBillRequest, User, BaseModel, Board, CardStatus, CardGroup, CardAttribute, Client, CardTag, + CardService as CardServiceModel, CardProduct, + ) class Card(BaseModel): @@ -56,6 +58,13 @@ class Card(BaseModel): lazy='joined', back_populates='cards' ) + + tags: Mapped[list['CardTag']] = relationship( + 'CardTag', + secondary='cards_card_tags', + back_populates='cards', + lazy='selectin', + ) # endregion # region Attributes handled by modules @@ -107,6 +116,7 @@ class Card(BaseModel): # endregion + class CardEmployees(BaseModel): __tablename__ = 'card_employees' user_id: Mapped[int] = mapped_column(ForeignKey('users.id'), primary_key=True) @@ -116,4 +126,3 @@ class CardEmployees(BaseModel): card: Mapped[Card] = relationship('Card', back_populates='employees', lazy='selectin') created_at: Mapped[datetime] = mapped_column() - diff --git a/models/card_tag.py b/models/card_tag.py new file mode 100644 index 0000000..61d4a23 --- /dev/null +++ b/models/card_tag.py @@ -0,0 +1,45 @@ +from typing import TYPE_CHECKING + +from sqlalchemy import ForeignKey, Column, Table, UniqueConstraint, Index +from sqlalchemy.orm import mapped_column, Mapped, relationship + +from models import BaseModel + +if TYPE_CHECKING: + from models import Project, Card + + +cards_card_tags = Table( + 'cards_card_tags', + BaseModel.metadata, + Column('card_id', ForeignKey('cards.id'), primary_key=True), + Column('card_tag_id', ForeignKey('card_tags.id'), primary_key=True), +) + + +class CardTag(BaseModel): + __tablename__ = 'card_tags' + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(nullable=False) + is_deleted: Mapped[bool] = mapped_column(default=False, server_default='0') + + project_id: Mapped[int] = mapped_column( + ForeignKey('projects.id'), + nullable=False, + ) + project: Mapped['Project'] = relationship( + 'Project', + back_populates='tags', + lazy='noload', + ) + + cards: Mapped[list['Card']] = relationship( + secondary='cards_card_tags', + lazy='noload', + back_populates='tags', + ) + + __table_args__ = ( + Index('idx_card_name_project_id', 'name', 'project_id', 'is_deleted'), + ) diff --git a/models/project.py b/models/project.py index 9f3a303..1ae5511 100644 --- a/models/project.py +++ b/models/project.py @@ -9,6 +9,7 @@ if TYPE_CHECKING: from board import Board from attribute import Attribute from module import Module + from card_tag import CardTag class Project(BaseModel): @@ -27,7 +28,6 @@ class Project(BaseModel): attributes: Mapped[list['Attribute']] = relationship( 'Attribute', - uselist=True, secondary='project_attribute', back_populates='projects', lazy='selectin', @@ -35,10 +35,16 @@ class Project(BaseModel): modules: Mapped[list['Module']] = relationship( 'Module', - uselist=True, secondary='project_module', back_populates='projects', lazy='selectin', order_by='asc(Module.id)', ) + tags: Mapped[list['CardTag']] = relationship( + 'CardTag', + back_populates='project', + primaryjoin="and_(Project.id == CardTag.project_id, CardTag.is_deleted == False)", + order_by='asc(CardTag.id)', + lazy='selectin', + ) diff --git a/routers/__init__.py b/routers/__init__.py index 17d7505..b9a55a1 100644 --- a/routers/__init__.py +++ b/routers/__init__.py @@ -25,3 +25,4 @@ from .residues import residues_router from .project import project_router from .board import board_router from .status import status_router +from .card_tag import card_tag_router diff --git a/routers/card_tag.py b/routers/card_tag.py new file mode 100644 index 0000000..1810a7b --- /dev/null +++ b/routers/card_tag.py @@ -0,0 +1,63 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from backend.session import get_session +from schemas.card_tag import * +from services.auth import authorized_user +from services.card_tag import CardTagService + +card_tag_router = APIRouter( + prefix='/card-tag', + tags=['card-tag'], + dependencies=[Depends(authorized_user)] +) + + +@card_tag_router.post( + '/', + operation_id='create_tag', + response_model=CreateTagResponse +) +async def create_tag( + request: CreateTagRequest, + session: Annotated[AsyncSession, Depends(get_session)] +): + return await CardTagService(session).create(request) + + +@card_tag_router.patch( + '/', + operation_id='update_tag', + response_model=UpdateTagResponse +) +async def update_tag( + request: UpdateTagRequest, + session: Annotated[AsyncSession, Depends(get_session)] +): + return await CardTagService(session).update(request) + + +@card_tag_router.delete( + '/{card_tag_id}', + response_model=DeleteTagResponse, + operation_id="delete_tag", +) +async def delete_tag( + session: Annotated[AsyncSession, Depends(get_session)], + card_tag_id: int, +): + return await CardTagService(session).delete(card_tag_id) + + +@card_tag_router.post( + '/switch', + response_model=SwitchTagResponse, + operation_id='switch_tag', +) +async def switch_tag( + session: Annotated[AsyncSession, Depends(get_session)], + request: SwitchTagRequest, +): + return await CardTagService(session).switch_tag(request) diff --git a/schemas/card.py b/schemas/card.py index 5f540f4..5284bfc 100644 --- a/schemas/card.py +++ b/schemas/card.py @@ -7,6 +7,7 @@ from schemas.attribute import CardAttributeSchema from schemas.base import BaseSchema, OkMessageSchema from schemas.billing import CardBillRequestSchema from schemas.board import BoardSchema +from schemas.card_tag import CardTagSchema from schemas.client import ClientSchema from schemas.group import CardGroupSchema from schemas.marketplace import BaseMarketplaceSchema @@ -41,6 +42,7 @@ class CardSummary(BaseSchema): rank: int base_marketplace: Optional[BaseMarketplaceSchema] = None total_products: int + tags: list[CardTagSchema] shipment_warehouse_id: Optional[int] shipment_warehouse_name: Optional[str] @@ -100,6 +102,7 @@ class BaseCardSchema(BaseSchema): pallets: List[PalletSchema] = [] boxes: List[BoxSchema] = [] employees: List[CardEmployeesSchema] = [] + tags: List[CardTagSchema] = [] class CardSchema(BaseCardSchema): @@ -115,6 +118,7 @@ class CardGeneralInfoSchema(BaseSchemaWithAttributes): board_id: int status_id: int client_id: Optional[int] + tags: List[str] class ProductsAndServicesGeneralInfoSchema(BaseSchema): diff --git a/schemas/card_tag.py b/schemas/card_tag.py new file mode 100644 index 0000000..fed728c --- /dev/null +++ b/schemas/card_tag.py @@ -0,0 +1,53 @@ +from typing import Optional + +from schemas.base import BaseSchema, OkMessageSchema + + +# region Entities + +class BaseCardTagSchema(BaseSchema): + name: str + project_id: int + + +class CardTagSchema(BaseCardTagSchema): + id: int + + +# endregion + +# region Requests + +class CreateTagRequest(BaseSchema): + tag: BaseCardTagSchema + + +class UpdateTagRequest(BaseSchema): + tag: CardTagSchema + + +class SwitchTagRequest(BaseSchema): + tag_id: int + card_id: Optional[int] = None + group_id: Optional[int] = None + +# endregion + +# region Responses + +class CreateTagResponse(OkMessageSchema): + pass + + +class UpdateTagResponse(OkMessageSchema): + pass + + +class DeleteTagResponse(OkMessageSchema): + pass + + +class SwitchTagResponse(OkMessageSchema): + pass + +# endregion diff --git a/schemas/project.py b/schemas/project.py index 318e1d2..c769ec4 100644 --- a/schemas/project.py +++ b/schemas/project.py @@ -2,6 +2,7 @@ from typing import Optional from schemas.attribute import AttributeSchema from schemas.base import BaseSchema, OkMessageSchema +from schemas.card_tag import CardTagSchema # region Entities @@ -26,6 +27,7 @@ class ModuleSchema(BaseSchema): class ProjectSchema(ProjectGeneralInfoSchema): attributes: list[AttributeSchema] modules: list[ModuleSchema] + tags: list[CardTagSchema] class FullProjectSchema(ProjectSchema): diff --git a/schemas/statistics.py b/schemas/statistics.py index 65e4c65..bf64cec 100644 --- a/schemas/statistics.py +++ b/schemas/statistics.py @@ -32,6 +32,7 @@ class CommonProfitFilters(BaseSchema): project_id: int board_id: int card_status_id: int + card_tag_id: int manager_id: int expense_tag_id: int income_tag_id: int diff --git a/services/barcode.py b/services/barcode.py index 9b3bc2f..a57dc4b 100644 --- a/services/barcode.py +++ b/services/barcode.py @@ -148,8 +148,6 @@ class BarcodeService(BaseService): "template": barcode_template, "num_duplicates": card_product.quantity }) - else: - print("jaja") default_generator = DefaultBarcodeGenerator() filename = f'{card.id}_deal_barcodes.pdf' diff --git a/services/card.py b/services/card.py index 84a7650..be8300f 100644 --- a/services/card.py +++ b/services/card.py @@ -14,6 +14,7 @@ from schemas.client import ClientDetailsSchema from services import card_group from services.auth import AuthService from services.base import BaseService +from services.card_tag import CardTagService from services.client import ClientService from services.service import ServiceService from services.shipping_warehouse import ShippingWarehouseService @@ -240,6 +241,7 @@ class CardsService(BaseService): shipment_warehouse_name=shipment_warehouse_name, total_products=products_count, bill_request=card.bill_request, + tags=card.tags, ) ) return CardSummaryResponse(summaries=summaries) @@ -347,7 +349,8 @@ class CardsService(BaseService): select(Card) .options( selectinload(Card.group) - .selectinload(CardGroup.cards) + .selectinload(CardGroup.cards), + joinedload(Card.board), ) .where(Card.id == request.card_id) ) @@ -374,6 +377,9 @@ class CardsService(BaseService): else: card.manager = None + tag_service = CardTagService(self.session) + await tag_service.apply_tags(card, request.data.tags) + card_attrs_handler = CardAttributesCommandHandler(self.session) await card_attrs_handler.set_attributes(card, request.data.attributes) @@ -388,7 +394,7 @@ class CardsService(BaseService): ): card: Optional[Card] = await self.session.get(Card, request.card_id) if not card: - raise HTTPException(status_code=404, detail='Карточка не найдена') + return ProductsAndServicesGeneralInfoRequest(ok=False, message='Карточка не найдена') # Updating shipping warehouse shipping_warehouse_service = ShippingWarehouseService(self.session) @@ -409,7 +415,7 @@ class CardsService(BaseService): async def update_card_manager(self, request: UpdateCardManagerRequest) -> UpdateCardManagerResponse: card: Optional[Card] = await self.session.get(Card, request.card_id) if not card: - raise HTTPException(status_code=404, detail='Карточка не найдена') + return UpdateCardManagerResponse(ok=False, message='Карточка не найдена') card.manager_id = request.manager_id await self.session.commit() @@ -418,7 +424,7 @@ class CardsService(BaseService): async def update_card_client(self, request: UpdateCardClientRequest) -> UpdateCardClientResponse: card: Optional[Card] = await self.session.get(Card, request.card_id) if not card: - raise HTTPException(status_code=404, detail='Карточка не найдена') + return UpdateCardClientResponse(ok=False, message='Карточка не найдена') card.client_id = request.client_id await self.session.commit() @@ -513,8 +519,6 @@ class CardsService(BaseService): return CardAddKitResponse(ok=False, message=str(e)) def create_guest_url(self, user: User, request: CardCreateGuestUrlRequest) -> CardCreateGuestUrlResponse: - # if not user.is_admin: - # return CardCreateGuestUrlResponse(ok=False, message='Создать ссылку может только администратор', url="") access_token = AuthService(self.session).create_deal_guest_token(request.card_id) url = f"deals/{request.card_id}?accessToken={access_token}" return CardCreateGuestUrlResponse(ok=True, message='Ссылка успешно создана!', url=url) diff --git a/services/card_tag.py b/services/card_tag.py new file mode 100644 index 0000000..fd4f9cc --- /dev/null +++ b/services/card_tag.py @@ -0,0 +1,122 @@ +from sqlalchemy import select +from sqlalchemy.orm import selectinload + +from models import CardTag, Card, CardGroup +from schemas.card_tag import * +from services.base import BaseService + + +class CardTagService(BaseService): + async def _get_by_name_and_project_id(self, name: str, project_id: int) -> Optional[CardTag]: + stmt = ( + select(CardTag) + .where( + CardTag.name == name, + CardTag.project_id == project_id, + CardTag.is_deleted == False, + ) + ) + card_tag = await self.session.scalars(stmt) + return card_tag.first() + + async def create(self, request: CreateTagRequest) -> CreateTagResponse: + existing_tag = await self._get_by_name_and_project_id(request.tag.name, request.tag.project_id) + if existing_tag: + return UpdateTagResponse(ok=False, message='Тег с таким названием уже существует') + + tag = CardTag(name=request.tag.name, project_id=request.tag.project_id) + self.session.add(tag) + await self.session.commit() + return CreateTagResponse(ok=True, message='Тег успешно создан') + + async def update(self, request: UpdateTagRequest) -> UpdateTagResponse: + card_tag = await self.session.get(CardTag, request.tag.id) + if not card_tag: + return UpdateTagResponse(ok=False, message=f'Тег с ID {request.tag.id} не найден') + + if card_tag.name != request.tag.name: + existing_tag = await self._get_by_name_and_project_id(request.tag.name, request.tag.project_id) + if existing_tag: + return UpdateTagResponse(ok=False, message='Тег с таким названием уже существует') + + card_tag.name = request.tag.name + + await self.session.commit() + return UpdateTagResponse(ok=True, message='Тег успешно обновлен') + + async def delete(self, card_tag_id: int) -> DeleteTagResponse: + card_tag = await self.session.get(CardTag, card_tag_id) + if not card_tag: + return DeleteTagResponse(ok=False, message=f'Тег с ID {card_tag_id} не найден') + + card_tag.is_deleted = True + await self.session.commit() + return DeleteTagResponse(ok=True, message='Тег успешно удален') + + async def _switch_tag_in_card(self, card_tag: CardTag, card_id: int) -> tuple[bool, str]: + stmt = ( + select(Card) + .options(selectinload(Card.tags)) + .where(Card.id == card_id) + ) + card: Optional[Card] = (await self.session.scalars(stmt)).first() + if not card: + return False, f'Карточка с ID {card_id} не найдена' + + if card_tag in card.tags: + card.tags.remove(card_tag) + return True, 'Тег откреплен от карточки' + + card.tags.append(card_tag) + return True, 'Тег прикреплен к карточке' + + async def _get_cards_by_group_id(self, group_id) -> list[Card]: + group: CardGroup | None = await self.session.scalar( + select(CardGroup) + .where(CardGroup.id == group_id) + ) + return group.cards if group else [] + + async def _switch_tag_in_group(self, card_tag: CardTag, group_id: int): + cards = await self._get_cards_by_group_id(group_id) + + for card in cards: + if card_tag in card.tags: + card.tags.remove(card_tag) + else: + card.tags.append(card_tag) + + return True, 'Теги группы изменены' + + async def switch_tag(self, request: SwitchTagRequest) -> SwitchTagResponse: + card_tag: Optional[CardTag] = await self.session.get(CardTag, request.tag_id) + if not card_tag: + return SwitchTagResponse(ok=False, message=f'Тег с ID {request.tag_id} не найден') + + if request.card_id: + ok, message = await self._switch_tag_in_card(card_tag, request.card_id) + else: + ok, message = await self._switch_tag_in_group(card_tag, request.group_id) + + await self.session.commit() + return SwitchTagResponse(ok=True, message=message) + + async def apply_tags(self, card: Card, tag_names: list[str]): + stmt = ( + select(CardTag) + .where( + CardTag.name.in_(tag_names), + CardTag.is_deleted == False, + CardTag.project_id == card.board.project_id + ) + ) + tags = (await self.session.scalars(stmt)).all() + + cards = [card] + if card.group: + cards = await self._get_cards_by_group_id(card.group.id) + + for card in cards: + card.tags = tags + + await self.session.flush() diff --git a/services/project.py b/services/project.py index 8890c66..e740433 100644 --- a/services/project.py +++ b/services/project.py @@ -1,11 +1,10 @@ from datetime import datetime -from typing import Optional from sqlalchemy import select, update, func, delete from sqlalchemy.orm import selectinload from card_attributes import CardAttributesCommandHandler -from models import Project, Board, Module, Attribute +from models import Project, Board, Module from schemas.project import * from services.base import BaseService @@ -30,6 +29,7 @@ class ProjectService(BaseService): selectinload(Project.attributes), selectinload(Project.modules), ) + .where(Project.is_deleted == False) .order_by(Project.id) ) project_data = (await self.session.execute(stmt)).all() @@ -42,6 +42,7 @@ class ProjectService(BaseService): boards_count=boards_count, attributes=project.attributes, modules=project.modules, + tags=project.tags, ) projects.append(project_schema) diff --git a/services/statistics/profit_statistics.py b/services/statistics/profit_statistics.py index 9f4fa34..48a7a5e 100644 --- a/services/statistics/profit_statistics.py +++ b/services/statistics/profit_statistics.py @@ -5,7 +5,7 @@ from sqlalchemy import select, and_, union_all, func, Subquery, literal from enums.profit_table_group_by import ProfitTableGroupBy from models import CardService, Card, CardStatusHistory, CardProductService, CardProduct, Service, Client, \ - ShippingWarehouse, BaseMarketplace, User, Project, Board + ShippingWarehouse, BaseMarketplace, User, Project, Board, CardTag, cards_card_tags, user_position from schemas.statistics import GetProfitChartDataResponse, GetProfitChartDataRequest, ProfitChartDataItem, \ GetProfitTableDataResponse, GetProfitTableDataRequest, ProfitTableDataItem, CommonProfitFilters from services.base import BaseService @@ -112,13 +112,11 @@ class ProfitStatisticsService(BaseService): .join(Service, CardService.service_id == Service.id) .join(sub_filtered_status_history, Card.id == sub_filtered_status_history.c.card_id) .where( - and_( - Card.is_deleted == False, - Card.is_services_profit_accounted == True, - Card.is_completed == True if self.is_completed_only else True - ) + Card.is_deleted == False, + Card.is_services_profit_accounted == True, + Card.is_completed == True if self.is_completed_only else True, ) - .group_by(Card.id, "date") + .group_by(Card.id, sub_filtered_status_history.c.date) ) @staticmethod @@ -129,6 +127,14 @@ class ProfitStatisticsService(BaseService): ) return stmt.where(Card.board_id.in_(board_ids_stmt)) + @staticmethod + def _apply_tag_filter(tag_id: int, stmt): + sub_card_ids = ( + select(cards_card_tags.c.card_id) + .where(cards_card_tags.c.card_tag_id == tag_id) + ) + return stmt.where(Card.id.in_(sub_card_ids)) + @staticmethod def _apply_filters(request: CommonProfitFilters, stmt_card_services, stmt_card_product_services): if request.client_id != -1: @@ -156,6 +162,13 @@ class ProfitStatisticsService(BaseService): stmt_card_product_services = stmt_card_product_services.where( Card.current_status_id == request.card_status_id) + if request.card_tag_id != -1: + stmt_card_services = ProfitStatisticsService._apply_tag_filter(request.card_tag_id, stmt_card_services) + stmt_card_product_services = ProfitStatisticsService._apply_tag_filter( + request.card_tag_id, + stmt_card_product_services + ) + if request.manager_id != -1: stmt_card_services = stmt_card_services.where(Card.manager_id == request.manager_id) stmt_card_product_services = stmt_card_product_services.where(Card.manager_id == request.manager_id) @@ -334,7 +347,8 @@ class ProfitStatisticsService(BaseService): def _join_and_group_by_managers(stmt): managers = ( select(User) - .where(User.role_key == "employee") + .join(user_position) + .where(and_(User.is_deleted == False, user_position.c.position_key == "sales_manager")) .subquery() ) return ( @@ -350,6 +364,23 @@ class ProfitStatisticsService(BaseService): .group_by(managers.c.id, "grouped_value") ) + @staticmethod + def _join_and_group_by_tags(stmt): + return ( + select( + CardTag.id, + CardTag.name.label("grouped_value"), + CardTag.is_deleted, + func.count(stmt.c.card_id).label("cards_count"), + func.sum(stmt.c.revenue).label("revenue"), + func.sum(stmt.c.profit).label("profit"), + ) + .join(cards_card_tags, cards_card_tags.c.card_id == stmt.c.card_id) + .join(CardTag, cards_card_tags.c.card_tag_id == CardTag.id) + .where(CardTag.is_deleted == False) + .group_by(CardTag.is_deleted, CardTag.id, CardTag.name) + ) + async def _get_data_rows_grouped_by_date( self, stmt_card_services, @@ -476,6 +507,7 @@ class ProfitStatisticsService(BaseService): async def _get_table_grouped_by_statuses(self, request: GetProfitTableDataRequest) -> GetProfitTableDataResponse: date_from, date_to = request.date_range + self.is_completed_only = request.is_completed_only sub_cards_dates = self._get_filtered_sub_status_history(date_from, date_to) @@ -518,6 +550,12 @@ class ProfitStatisticsService(BaseService): return await self._table_data_from_stmt(stmt_grouped_by_managers) + async def _get_table_grouped_by_tags(self, request: GetProfitTableDataRequest) -> GetProfitTableDataResponse: + sub_grouped_by_cards = self._get_common_table_grouped(request) + stmt_grouped_by_tags = self._join_and_group_by_tags(sub_grouped_by_cards) + + return await self._table_data_from_stmt(stmt_grouped_by_tags) + async def get_profit_table_data(self, request: GetProfitTableDataRequest) -> GetProfitTableDataResponse: match request.group_table_by: case ProfitTableGroupBy.BY_DATES: @@ -536,5 +574,7 @@ class ProfitStatisticsService(BaseService): return await self._get_table_grouped_by_marketplace(request) case ProfitTableGroupBy.BY_MANAGERS: return await self._get_table_grouped_by_managers(request) + case ProfitTableGroupBy.BY_TAGS: + return await self._get_table_grouped_by_tags(request) raise HTTPException(status_code=400, detail='Указана некорректная группировка')