feat: added tags for cards, aligned status headers
This commit is contained in:
@@ -10,3 +10,4 @@ class ProfitTableGroupBy(IntEnum):
|
||||
BY_WAREHOUSES = 5
|
||||
BY_MARKETPLACES = 6
|
||||
BY_MANAGERS = 7
|
||||
BY_TAGS = 8
|
||||
|
||||
1
main.py
1
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)
|
||||
|
||||
@@ -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 *
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
45
models/card_tag.py
Normal file
45
models/card_tag.py
Normal file
@@ -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'),
|
||||
)
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
63
routers/card_tag.py
Normal file
63
routers/card_tag.py
Normal file
@@ -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)
|
||||
@@ -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):
|
||||
|
||||
53
schemas/card_tag.py
Normal file
53
schemas/card_tag.py
Normal file
@@ -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
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
122
services/card_tag.py
Normal file
122
services/card_tag.py
Normal file
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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='Указана некорректная группировка')
|
||||
|
||||
Reference in New Issue
Block a user