feat: added tags for cards, aligned status headers
This commit is contained in:
		@@ -10,3 +10,4 @@ class ProfitTableGroupBy(IntEnum):
 | 
				
			|||||||
    BY_WAREHOUSES = 5
 | 
					    BY_WAREHOUSES = 5
 | 
				
			||||||
    BY_MARKETPLACES = 6
 | 
					    BY_MARKETPLACES = 6
 | 
				
			||||||
    BY_MANAGERS = 7
 | 
					    BY_MANAGERS = 7
 | 
				
			||||||
 | 
					    BY_TAGS = 8
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								main.py
									
									
									
									
									
								
							@@ -57,6 +57,7 @@ routers_list = [
 | 
				
			|||||||
    routers.project_router,
 | 
					    routers.project_router,
 | 
				
			||||||
    routers.board_router,
 | 
					    routers.board_router,
 | 
				
			||||||
    routers.status_router,
 | 
					    routers.status_router,
 | 
				
			||||||
 | 
					    routers.card_tag_router,
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
for router in routers_list:
 | 
					for router in routers_list:
 | 
				
			||||||
    app.include_router(router)
 | 
					    app.include_router(router)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ from .board import *
 | 
				
			|||||||
from .status import *
 | 
					from .status import *
 | 
				
			||||||
from .attribute import *
 | 
					from .attribute import *
 | 
				
			||||||
from .card import *
 | 
					from .card import *
 | 
				
			||||||
 | 
					from .card_tag import *
 | 
				
			||||||
from .auth import *
 | 
					from .auth import *
 | 
				
			||||||
from .card import *
 | 
					from .card import *
 | 
				
			||||||
from .client import *
 | 
					from .client import *
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,8 +10,10 @@ from .shipping import Pallet, Box
 | 
				
			|||||||
from .shipping_warehouse import ShippingWarehouse
 | 
					from .shipping_warehouse import ShippingWarehouse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if TYPE_CHECKING:
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
    from . import CardBillRequest, User, BaseModel, Board, CardStatus, CardGroup, CardAttribute, CardService as CardServiceModel, \
 | 
					    from . import (
 | 
				
			||||||
    CardProduct, Client
 | 
					        CardBillRequest, User, BaseModel, Board, CardStatus, CardGroup, CardAttribute, Client, CardTag,
 | 
				
			||||||
 | 
					        CardService as CardServiceModel, CardProduct,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Card(BaseModel):
 | 
					class Card(BaseModel):
 | 
				
			||||||
@@ -56,6 +58,13 @@ class Card(BaseModel):
 | 
				
			|||||||
        lazy='joined',
 | 
					        lazy='joined',
 | 
				
			||||||
        back_populates='cards'
 | 
					        back_populates='cards'
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tags: Mapped[list['CardTag']] = relationship(
 | 
				
			||||||
 | 
					        'CardTag',
 | 
				
			||||||
 | 
					        secondary='cards_card_tags',
 | 
				
			||||||
 | 
					        back_populates='cards',
 | 
				
			||||||
 | 
					        lazy='selectin',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    # endregion
 | 
					    # endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # region Attributes handled by modules
 | 
					    # region Attributes handled by modules
 | 
				
			||||||
@@ -107,6 +116,7 @@ class Card(BaseModel):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    # endregion
 | 
					    # endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CardEmployees(BaseModel):
 | 
					class CardEmployees(BaseModel):
 | 
				
			||||||
    __tablename__ = 'card_employees'
 | 
					    __tablename__ = 'card_employees'
 | 
				
			||||||
    user_id: Mapped[int] = mapped_column(ForeignKey('users.id'), primary_key=True)
 | 
					    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')
 | 
					    card: Mapped[Card] = relationship('Card', back_populates='employees', lazy='selectin')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    created_at: Mapped[datetime] = mapped_column()
 | 
					    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 board import Board
 | 
				
			||||||
    from attribute import Attribute
 | 
					    from attribute import Attribute
 | 
				
			||||||
    from module import Module
 | 
					    from module import Module
 | 
				
			||||||
 | 
					    from card_tag import CardTag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Project(BaseModel):
 | 
					class Project(BaseModel):
 | 
				
			||||||
@@ -27,7 +28,6 @@ class Project(BaseModel):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    attributes: Mapped[list['Attribute']] = relationship(
 | 
					    attributes: Mapped[list['Attribute']] = relationship(
 | 
				
			||||||
        'Attribute',
 | 
					        'Attribute',
 | 
				
			||||||
        uselist=True,
 | 
					 | 
				
			||||||
        secondary='project_attribute',
 | 
					        secondary='project_attribute',
 | 
				
			||||||
        back_populates='projects',
 | 
					        back_populates='projects',
 | 
				
			||||||
        lazy='selectin',
 | 
					        lazy='selectin',
 | 
				
			||||||
@@ -35,10 +35,16 @@ class Project(BaseModel):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    modules: Mapped[list['Module']] = relationship(
 | 
					    modules: Mapped[list['Module']] = relationship(
 | 
				
			||||||
        'Module',
 | 
					        'Module',
 | 
				
			||||||
        uselist=True,
 | 
					 | 
				
			||||||
        secondary='project_module',
 | 
					        secondary='project_module',
 | 
				
			||||||
        back_populates='projects',
 | 
					        back_populates='projects',
 | 
				
			||||||
        lazy='selectin',
 | 
					        lazy='selectin',
 | 
				
			||||||
        order_by='asc(Module.id)',
 | 
					        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 .project import project_router
 | 
				
			||||||
from .board import board_router
 | 
					from .board import board_router
 | 
				
			||||||
from .status import status_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.base import BaseSchema, OkMessageSchema
 | 
				
			||||||
from schemas.billing import CardBillRequestSchema
 | 
					from schemas.billing import CardBillRequestSchema
 | 
				
			||||||
from schemas.board import BoardSchema
 | 
					from schemas.board import BoardSchema
 | 
				
			||||||
 | 
					from schemas.card_tag import CardTagSchema
 | 
				
			||||||
from schemas.client import ClientSchema
 | 
					from schemas.client import ClientSchema
 | 
				
			||||||
from schemas.group import CardGroupSchema
 | 
					from schemas.group import CardGroupSchema
 | 
				
			||||||
from schemas.marketplace import BaseMarketplaceSchema
 | 
					from schemas.marketplace import BaseMarketplaceSchema
 | 
				
			||||||
@@ -41,6 +42,7 @@ class CardSummary(BaseSchema):
 | 
				
			|||||||
    rank: int
 | 
					    rank: int
 | 
				
			||||||
    base_marketplace: Optional[BaseMarketplaceSchema] = None
 | 
					    base_marketplace: Optional[BaseMarketplaceSchema] = None
 | 
				
			||||||
    total_products: int
 | 
					    total_products: int
 | 
				
			||||||
 | 
					    tags: list[CardTagSchema]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    shipment_warehouse_id: Optional[int]
 | 
					    shipment_warehouse_id: Optional[int]
 | 
				
			||||||
    shipment_warehouse_name: Optional[str]
 | 
					    shipment_warehouse_name: Optional[str]
 | 
				
			||||||
@@ -100,6 +102,7 @@ class BaseCardSchema(BaseSchema):
 | 
				
			|||||||
    pallets: List[PalletSchema] = []
 | 
					    pallets: List[PalletSchema] = []
 | 
				
			||||||
    boxes: List[BoxSchema] = []
 | 
					    boxes: List[BoxSchema] = []
 | 
				
			||||||
    employees: List[CardEmployeesSchema] = []
 | 
					    employees: List[CardEmployeesSchema] = []
 | 
				
			||||||
 | 
					    tags: List[CardTagSchema] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CardSchema(BaseCardSchema):
 | 
					class CardSchema(BaseCardSchema):
 | 
				
			||||||
@@ -115,6 +118,7 @@ class CardGeneralInfoSchema(BaseSchemaWithAttributes):
 | 
				
			|||||||
    board_id: int
 | 
					    board_id: int
 | 
				
			||||||
    status_id: int
 | 
					    status_id: int
 | 
				
			||||||
    client_id: Optional[int]
 | 
					    client_id: Optional[int]
 | 
				
			||||||
 | 
					    tags: List[str]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ProductsAndServicesGeneralInfoSchema(BaseSchema):
 | 
					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.attribute import AttributeSchema
 | 
				
			||||||
from schemas.base import BaseSchema, OkMessageSchema
 | 
					from schemas.base import BaseSchema, OkMessageSchema
 | 
				
			||||||
 | 
					from schemas.card_tag import CardTagSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# region Entities
 | 
					# region Entities
 | 
				
			||||||
@@ -26,6 +27,7 @@ class ModuleSchema(BaseSchema):
 | 
				
			|||||||
class ProjectSchema(ProjectGeneralInfoSchema):
 | 
					class ProjectSchema(ProjectGeneralInfoSchema):
 | 
				
			||||||
    attributes: list[AttributeSchema]
 | 
					    attributes: list[AttributeSchema]
 | 
				
			||||||
    modules: list[ModuleSchema]
 | 
					    modules: list[ModuleSchema]
 | 
				
			||||||
 | 
					    tags: list[CardTagSchema]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FullProjectSchema(ProjectSchema):
 | 
					class FullProjectSchema(ProjectSchema):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,6 +32,7 @@ class CommonProfitFilters(BaseSchema):
 | 
				
			|||||||
    project_id: int
 | 
					    project_id: int
 | 
				
			||||||
    board_id: int
 | 
					    board_id: int
 | 
				
			||||||
    card_status_id: int
 | 
					    card_status_id: int
 | 
				
			||||||
 | 
					    card_tag_id: int
 | 
				
			||||||
    manager_id: int
 | 
					    manager_id: int
 | 
				
			||||||
    expense_tag_id: int
 | 
					    expense_tag_id: int
 | 
				
			||||||
    income_tag_id: int
 | 
					    income_tag_id: int
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -148,8 +148,6 @@ class BarcodeService(BaseService):
 | 
				
			|||||||
                    "template": barcode_template,
 | 
					                    "template": barcode_template,
 | 
				
			||||||
                    "num_duplicates": card_product.quantity
 | 
					                    "num_duplicates": card_product.quantity
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                print("jaja")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        default_generator = DefaultBarcodeGenerator()
 | 
					        default_generator = DefaultBarcodeGenerator()
 | 
				
			||||||
        filename = f'{card.id}_deal_barcodes.pdf'
 | 
					        filename = f'{card.id}_deal_barcodes.pdf'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ from schemas.client import ClientDetailsSchema
 | 
				
			|||||||
from services import card_group
 | 
					from services import card_group
 | 
				
			||||||
from services.auth import AuthService
 | 
					from services.auth import AuthService
 | 
				
			||||||
from services.base import BaseService
 | 
					from services.base import BaseService
 | 
				
			||||||
 | 
					from services.card_tag import CardTagService
 | 
				
			||||||
from services.client import ClientService
 | 
					from services.client import ClientService
 | 
				
			||||||
from services.service import ServiceService
 | 
					from services.service import ServiceService
 | 
				
			||||||
from services.shipping_warehouse import ShippingWarehouseService
 | 
					from services.shipping_warehouse import ShippingWarehouseService
 | 
				
			||||||
@@ -240,6 +241,7 @@ class CardsService(BaseService):
 | 
				
			|||||||
                    shipment_warehouse_name=shipment_warehouse_name,
 | 
					                    shipment_warehouse_name=shipment_warehouse_name,
 | 
				
			||||||
                    total_products=products_count,
 | 
					                    total_products=products_count,
 | 
				
			||||||
                    bill_request=card.bill_request,
 | 
					                    bill_request=card.bill_request,
 | 
				
			||||||
 | 
					                    tags=card.tags,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        return CardSummaryResponse(summaries=summaries)
 | 
					        return CardSummaryResponse(summaries=summaries)
 | 
				
			||||||
@@ -347,7 +349,8 @@ class CardsService(BaseService):
 | 
				
			|||||||
                select(Card)
 | 
					                select(Card)
 | 
				
			||||||
                .options(
 | 
					                .options(
 | 
				
			||||||
                    selectinload(Card.group)
 | 
					                    selectinload(Card.group)
 | 
				
			||||||
                    .selectinload(CardGroup.cards)
 | 
					                    .selectinload(CardGroup.cards),
 | 
				
			||||||
 | 
					                    joinedload(Card.board),
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                .where(Card.id == request.card_id)
 | 
					                .where(Card.id == request.card_id)
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
@@ -374,6 +377,9 @@ class CardsService(BaseService):
 | 
				
			|||||||
            else:
 | 
					            else:
 | 
				
			||||||
                card.manager = None
 | 
					                card.manager = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            tag_service = CardTagService(self.session)
 | 
				
			||||||
 | 
					            await tag_service.apply_tags(card, request.data.tags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            card_attrs_handler = CardAttributesCommandHandler(self.session)
 | 
					            card_attrs_handler = CardAttributesCommandHandler(self.session)
 | 
				
			||||||
            await card_attrs_handler.set_attributes(card, request.data.attributes)
 | 
					            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)
 | 
					        card: Optional[Card] = await self.session.get(Card, request.card_id)
 | 
				
			||||||
        if not card:
 | 
					        if not card:
 | 
				
			||||||
            raise HTTPException(status_code=404, detail='Карточка не найдена')
 | 
					            return ProductsAndServicesGeneralInfoRequest(ok=False, message='Карточка не найдена')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Updating shipping warehouse
 | 
					        # Updating shipping warehouse
 | 
				
			||||||
        shipping_warehouse_service = ShippingWarehouseService(self.session)
 | 
					        shipping_warehouse_service = ShippingWarehouseService(self.session)
 | 
				
			||||||
@@ -409,7 +415,7 @@ class CardsService(BaseService):
 | 
				
			|||||||
    async def update_card_manager(self, request: UpdateCardManagerRequest) -> UpdateCardManagerResponse:
 | 
					    async def update_card_manager(self, request: UpdateCardManagerRequest) -> UpdateCardManagerResponse:
 | 
				
			||||||
        card: Optional[Card] = await self.session.get(Card, request.card_id)
 | 
					        card: Optional[Card] = await self.session.get(Card, request.card_id)
 | 
				
			||||||
        if not card:
 | 
					        if not card:
 | 
				
			||||||
            raise HTTPException(status_code=404, detail='Карточка не найдена')
 | 
					            return UpdateCardManagerResponse(ok=False, message='Карточка не найдена')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        card.manager_id = request.manager_id
 | 
					        card.manager_id = request.manager_id
 | 
				
			||||||
        await self.session.commit()
 | 
					        await self.session.commit()
 | 
				
			||||||
@@ -418,7 +424,7 @@ class CardsService(BaseService):
 | 
				
			|||||||
    async def update_card_client(self, request: UpdateCardClientRequest) -> UpdateCardClientResponse:
 | 
					    async def update_card_client(self, request: UpdateCardClientRequest) -> UpdateCardClientResponse:
 | 
				
			||||||
        card: Optional[Card] = await self.session.get(Card, request.card_id)
 | 
					        card: Optional[Card] = await self.session.get(Card, request.card_id)
 | 
				
			||||||
        if not card:
 | 
					        if not card:
 | 
				
			||||||
            raise HTTPException(status_code=404, detail='Карточка не найдена')
 | 
					            return UpdateCardClientResponse(ok=False, message='Карточка не найдена')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        card.client_id = request.client_id
 | 
					        card.client_id = request.client_id
 | 
				
			||||||
        await self.session.commit()
 | 
					        await self.session.commit()
 | 
				
			||||||
@@ -513,8 +519,6 @@ class CardsService(BaseService):
 | 
				
			|||||||
            return CardAddKitResponse(ok=False, message=str(e))
 | 
					            return CardAddKitResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_guest_url(self, user: User, request: CardCreateGuestUrlRequest) -> CardCreateGuestUrlResponse:
 | 
					    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)
 | 
					        access_token = AuthService(self.session).create_deal_guest_token(request.card_id)
 | 
				
			||||||
        url = f"deals/{request.card_id}?accessToken={access_token}"
 | 
					        url = f"deals/{request.card_id}?accessToken={access_token}"
 | 
				
			||||||
        return CardCreateGuestUrlResponse(ok=True, message='Ссылка успешно создана!', url=url)
 | 
					        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 datetime import datetime
 | 
				
			||||||
from typing import Optional
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from sqlalchemy import select, update, func, delete
 | 
					from sqlalchemy import select, update, func, delete
 | 
				
			||||||
from sqlalchemy.orm import selectinload
 | 
					from sqlalchemy.orm import selectinload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from card_attributes import CardAttributesCommandHandler
 | 
					from card_attributes import CardAttributesCommandHandler
 | 
				
			||||||
from models import Project, Board, Module, Attribute
 | 
					from models import Project, Board, Module
 | 
				
			||||||
from schemas.project import *
 | 
					from schemas.project import *
 | 
				
			||||||
from services.base import BaseService
 | 
					from services.base import BaseService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -30,6 +29,7 @@ class ProjectService(BaseService):
 | 
				
			|||||||
                selectinload(Project.attributes),
 | 
					                selectinload(Project.attributes),
 | 
				
			||||||
                selectinload(Project.modules),
 | 
					                selectinload(Project.modules),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            .where(Project.is_deleted == False)
 | 
				
			||||||
            .order_by(Project.id)
 | 
					            .order_by(Project.id)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        project_data = (await self.session.execute(stmt)).all()
 | 
					        project_data = (await self.session.execute(stmt)).all()
 | 
				
			||||||
@@ -42,6 +42,7 @@ class ProjectService(BaseService):
 | 
				
			|||||||
                boards_count=boards_count,
 | 
					                boards_count=boards_count,
 | 
				
			||||||
                attributes=project.attributes,
 | 
					                attributes=project.attributes,
 | 
				
			||||||
                modules=project.modules,
 | 
					                modules=project.modules,
 | 
				
			||||||
 | 
					                tags=project.tags,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            projects.append(project_schema)
 | 
					            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 enums.profit_table_group_by import ProfitTableGroupBy
 | 
				
			||||||
from models import CardService, Card, CardStatusHistory, CardProductService, CardProduct, Service, Client, \
 | 
					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, \
 | 
					from schemas.statistics import GetProfitChartDataResponse, GetProfitChartDataRequest, ProfitChartDataItem, \
 | 
				
			||||||
    GetProfitTableDataResponse, GetProfitTableDataRequest, ProfitTableDataItem, CommonProfitFilters
 | 
					    GetProfitTableDataResponse, GetProfitTableDataRequest, ProfitTableDataItem, CommonProfitFilters
 | 
				
			||||||
from services.base import BaseService
 | 
					from services.base import BaseService
 | 
				
			||||||
@@ -112,13 +112,11 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
            .join(Service, CardService.service_id == Service.id)
 | 
					            .join(Service, CardService.service_id == Service.id)
 | 
				
			||||||
            .join(sub_filtered_status_history, Card.id == sub_filtered_status_history.c.card_id)
 | 
					            .join(sub_filtered_status_history, Card.id == sub_filtered_status_history.c.card_id)
 | 
				
			||||||
            .where(
 | 
					            .where(
 | 
				
			||||||
                and_(
 | 
					                Card.is_deleted == False,
 | 
				
			||||||
                    Card.is_deleted == False,
 | 
					                Card.is_services_profit_accounted == True,
 | 
				
			||||||
                    Card.is_services_profit_accounted == True,
 | 
					                Card.is_completed == True if self.is_completed_only else 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
 | 
					    @staticmethod
 | 
				
			||||||
@@ -129,6 +127,14 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
        return stmt.where(Card.board_id.in_(board_ids_stmt))
 | 
					        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
 | 
					    @staticmethod
 | 
				
			||||||
    def _apply_filters(request: CommonProfitFilters, stmt_card_services, stmt_card_product_services):
 | 
					    def _apply_filters(request: CommonProfitFilters, stmt_card_services, stmt_card_product_services):
 | 
				
			||||||
        if request.client_id != -1:
 | 
					        if request.client_id != -1:
 | 
				
			||||||
@@ -156,6 +162,13 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
                    stmt_card_product_services = stmt_card_product_services.where(
 | 
					                    stmt_card_product_services = stmt_card_product_services.where(
 | 
				
			||||||
                        Card.current_status_id == request.card_status_id)
 | 
					                        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:
 | 
					        if request.manager_id != -1:
 | 
				
			||||||
            stmt_card_services = stmt_card_services.where(Card.manager_id == request.manager_id)
 | 
					            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)
 | 
					            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):
 | 
					    def _join_and_group_by_managers(stmt):
 | 
				
			||||||
        managers = (
 | 
					        managers = (
 | 
				
			||||||
            select(User)
 | 
					            select(User)
 | 
				
			||||||
            .where(User.role_key == "employee")
 | 
					            .join(user_position)
 | 
				
			||||||
 | 
					            .where(and_(User.is_deleted == False, user_position.c.position_key == "sales_manager"))
 | 
				
			||||||
            .subquery()
 | 
					            .subquery()
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
@@ -350,6 +364,23 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
            .group_by(managers.c.id, "grouped_value")
 | 
					            .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(
 | 
					    async def _get_data_rows_grouped_by_date(
 | 
				
			||||||
            self,
 | 
					            self,
 | 
				
			||||||
            stmt_card_services,
 | 
					            stmt_card_services,
 | 
				
			||||||
@@ -476,6 +507,7 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    async def _get_table_grouped_by_statuses(self, request: GetProfitTableDataRequest) -> GetProfitTableDataResponse:
 | 
					    async def _get_table_grouped_by_statuses(self, request: GetProfitTableDataRequest) -> GetProfitTableDataResponse:
 | 
				
			||||||
        date_from, date_to = request.date_range
 | 
					        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)
 | 
					        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)
 | 
					        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:
 | 
					    async def get_profit_table_data(self, request: GetProfitTableDataRequest) -> GetProfitTableDataResponse:
 | 
				
			||||||
        match request.group_table_by:
 | 
					        match request.group_table_by:
 | 
				
			||||||
            case ProfitTableGroupBy.BY_DATES:
 | 
					            case ProfitTableGroupBy.BY_DATES:
 | 
				
			||||||
@@ -536,5 +574,7 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
                return await self._get_table_grouped_by_marketplace(request)
 | 
					                return await self._get_table_grouped_by_marketplace(request)
 | 
				
			||||||
            case ProfitTableGroupBy.BY_MANAGERS:
 | 
					            case ProfitTableGroupBy.BY_MANAGERS:
 | 
				
			||||||
                return await self._get_table_grouped_by_managers(request)
 | 
					                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='Указана некорректная группировка')
 | 
					        raise HTTPException(status_code=400, detail='Указана некорректная группировка')
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user