feat: projects and boards
This commit is contained in:
		@@ -4,7 +4,9 @@ from enum import IntEnum
 | 
				
			|||||||
class ProfitTableGroupBy(IntEnum):
 | 
					class ProfitTableGroupBy(IntEnum):
 | 
				
			||||||
    BY_DATES = 0
 | 
					    BY_DATES = 0
 | 
				
			||||||
    BY_CLIENTS = 1
 | 
					    BY_CLIENTS = 1
 | 
				
			||||||
    BY_STATUSES = 2
 | 
					    BY_PROJECTS = 2
 | 
				
			||||||
    BY_WAREHOUSES = 3
 | 
					    BY_BOARDS = 3
 | 
				
			||||||
    BY_MARKETPLACES = 4
 | 
					    BY_STATUSES = 4
 | 
				
			||||||
    BY_MANAGERS = 5
 | 
					    BY_WAREHOUSES = 5
 | 
				
			||||||
 | 
					    BY_MARKETPLACES = 6
 | 
				
			||||||
 | 
					    BY_MANAGERS = 7
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								main.py
									
									
									
									
									
								
							@@ -32,6 +32,7 @@ app.add_middleware(
 | 
				
			|||||||
routers_list = [
 | 
					routers_list = [
 | 
				
			||||||
    routers.auth_router,
 | 
					    routers.auth_router,
 | 
				
			||||||
    routers.deal_router,
 | 
					    routers.deal_router,
 | 
				
			||||||
 | 
					    routers.deal_group_router,
 | 
				
			||||||
    routers.client_router,
 | 
					    routers.client_router,
 | 
				
			||||||
    routers.service_router,
 | 
					    routers.service_router,
 | 
				
			||||||
    routers.product_router,
 | 
					    routers.product_router,
 | 
				
			||||||
@@ -52,6 +53,9 @@ routers_list = [
 | 
				
			|||||||
    routers.shipping_router,
 | 
					    routers.shipping_router,
 | 
				
			||||||
    routers.department_router,
 | 
					    routers.department_router,
 | 
				
			||||||
    routers.residues_router,
 | 
					    routers.residues_router,
 | 
				
			||||||
 | 
					    routers.project_router,
 | 
				
			||||||
 | 
					    routers.board_router,
 | 
				
			||||||
 | 
					    routers.status_router,
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
for router in routers_list:
 | 
					for router in routers_list:
 | 
				
			||||||
    app.include_router(router)
 | 
					    app.include_router(router)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,9 @@
 | 
				
			|||||||
from sqlalchemy.orm import configure_mappers
 | 
					from sqlalchemy.orm import configure_mappers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .auth import *
 | 
					from .auth import *
 | 
				
			||||||
 | 
					from .project import *
 | 
				
			||||||
 | 
					from .board import *
 | 
				
			||||||
 | 
					from .status import *
 | 
				
			||||||
from .deal import *
 | 
					from .deal import *
 | 
				
			||||||
from .client import *
 | 
					from .client import *
 | 
				
			||||||
from .service import *
 | 
					from .service import *
 | 
				
			||||||
@@ -15,5 +18,6 @@ from .marketplace_products import *
 | 
				
			|||||||
from .deal_group import *
 | 
					from .deal_group import *
 | 
				
			||||||
from .transaction import *
 | 
					from .transaction import *
 | 
				
			||||||
from .residues import *
 | 
					from .residues import *
 | 
				
			||||||
 | 
					from .shipping import *
 | 
				
			||||||
 | 
					
 | 
				
			||||||
configure_mappers()
 | 
					configure_mappers()
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										31
									
								
								models/board.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								models/board.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					from typing import TYPE_CHECKING
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sqlalchemy import ForeignKey
 | 
				
			||||||
 | 
					from sqlalchemy.orm import Mapped, mapped_column, relationship
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from models import BaseModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
 | 
					    from models import Project, DealStatus, Deal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Board(BaseModel):
 | 
				
			||||||
 | 
					    __tablename__ = "boards"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    id: Mapped[int] = mapped_column(primary_key=True)
 | 
				
			||||||
 | 
					    name: Mapped[str] = mapped_column(nullable=False)
 | 
				
			||||||
 | 
					    created_at: Mapped[datetime] = mapped_column(nullable=False)
 | 
				
			||||||
 | 
					    is_deleted: Mapped[bool] = mapped_column(default=False)
 | 
				
			||||||
 | 
					    ordinal_number: Mapped[int] = mapped_column(nullable=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    project_id: Mapped[int] = mapped_column(ForeignKey('projects.id'), nullable=False)
 | 
				
			||||||
 | 
					    project: Mapped["Project"] = relationship(
 | 
				
			||||||
 | 
					        "Project",
 | 
				
			||||||
 | 
					        back_populates="boards",
 | 
				
			||||||
 | 
					        lazy="selectin",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    deal_statuses: Mapped[list["DealStatus"]] = relationship("DealStatus", back_populates="board", lazy="selectin", cascade="all,delete")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    deals: Mapped[list["Deal"]] = relationship("Deal", uselist=True, back_populates="board", lazy="selectin")
 | 
				
			||||||
@@ -7,6 +7,8 @@ from sqlalchemy.orm import relationship, backref, Mapped, mapped_column
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from models.base import BaseModel
 | 
					from models.base import BaseModel
 | 
				
			||||||
from .marketplace import BaseMarketplace
 | 
					from .marketplace import BaseMarketplace
 | 
				
			||||||
 | 
					from .board import Board
 | 
				
			||||||
 | 
					from .status import DealStatus
 | 
				
			||||||
from .shipping import Pallet, Box
 | 
					from .shipping import Pallet, Box
 | 
				
			||||||
from .shipping_warehouse import ShippingWarehouse
 | 
					from .shipping_warehouse import ShippingWarehouse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,18 +21,8 @@ if TYPE_CHECKING:
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# @unique
 | 
					 | 
				
			||||||
# class DealStatus(IntEnum):
 | 
					 | 
				
			||||||
#     CREATED = 0
 | 
					 | 
				
			||||||
#     AWAITING_ACCEPTANCE = 1
 | 
					 | 
				
			||||||
#     PACKAGING = 2
 | 
					 | 
				
			||||||
#     AWAITING_SHIPMENT = 3
 | 
					 | 
				
			||||||
#     AWAITING_PAYMENT = 4
 | 
					 | 
				
			||||||
#     COMPLETED = 5
 | 
					 | 
				
			||||||
#     CANCELLED = 6
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@unique
 | 
					@unique
 | 
				
			||||||
class DealStatus(IntEnum):
 | 
					class DealStatusEnum(IntEnum):
 | 
				
			||||||
    CREATED = 0
 | 
					    CREATED = 0
 | 
				
			||||||
    AWAITING_ACCEPTANCE = 1
 | 
					    AWAITING_ACCEPTANCE = 1
 | 
				
			||||||
    READY_FOR_WORK = 2
 | 
					    READY_FOR_WORK = 2
 | 
				
			||||||
@@ -53,7 +45,6 @@ class Deal(BaseModel):
 | 
				
			|||||||
    id = Column(Integer, autoincrement=True, primary_key=True, index=True)
 | 
					    id = Column(Integer, autoincrement=True, primary_key=True, index=True)
 | 
				
			||||||
    name = Column(String, nullable=False, comment='Название сделки')
 | 
					    name = Column(String, nullable=False, comment='Название сделки')
 | 
				
			||||||
    created_at = Column(DateTime, nullable=False, comment='Дата создания')
 | 
					    created_at = Column(DateTime, nullable=False, comment='Дата создания')
 | 
				
			||||||
    current_status = Column(Integer, nullable=False, comment='Текущий статус')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    client_id = Column(Integer, ForeignKey('clients.id', ondelete='CASCADE'), nullable=False, comment='ID клиента')
 | 
					    client_id = Column(Integer, ForeignKey('clients.id', ondelete='CASCADE'), nullable=False, comment='ID клиента')
 | 
				
			||||||
    client = relationship('Client', backref=backref('deals', cascade="all, delete-orphan"))
 | 
					    client = relationship('Client', backref=backref('deals', cascade="all, delete-orphan"))
 | 
				
			||||||
@@ -65,6 +56,14 @@ class Deal(BaseModel):
 | 
				
			|||||||
    is_locked: Mapped[bool] = mapped_column(default=False, server_default='0')
 | 
					    is_locked: Mapped[bool] = mapped_column(default=False, server_default='0')
 | 
				
			||||||
    is_accounted: Mapped[bool] = mapped_column(default=True, server_default='1')
 | 
					    is_accounted: Mapped[bool] = mapped_column(default=True, server_default='1')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    current_status_id: Mapped[int] = mapped_column(
 | 
				
			||||||
 | 
					        ForeignKey("deal_statuses.id"),
 | 
				
			||||||
 | 
					        nullable=False,
 | 
				
			||||||
 | 
					        comment='Текущий статус',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    status: Mapped["DealStatus"] = relationship(lazy="selectin")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    shipping_warehouse_id: Mapped[int] = mapped_column(ForeignKey('shipping_warehouses.id'), nullable=True)
 | 
					    shipping_warehouse_id: Mapped[int] = mapped_column(ForeignKey('shipping_warehouses.id'), nullable=True)
 | 
				
			||||||
    shipping_warehouse: Mapped["ShippingWarehouse"] = relationship()
 | 
					    shipping_warehouse: Mapped["ShippingWarehouse"] = relationship()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -88,6 +87,12 @@ class Deal(BaseModel):
 | 
				
			|||||||
        order_by="desc(DealProduct.product_id)"
 | 
					        order_by="desc(DealProduct.product_id)"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    board_id: Mapped[int] = mapped_column(ForeignKey('boards.id'), nullable=True, server_default='1')
 | 
				
			||||||
 | 
					    board: Mapped[Board] = relationship(
 | 
				
			||||||
 | 
					        "Board",
 | 
				
			||||||
 | 
					        back_populates="deals",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # TODO remake with sequence
 | 
					    # TODO remake with sequence
 | 
				
			||||||
    lexorank = Column(String, nullable=False, comment='Lexorank', index=True)
 | 
					    lexorank = Column(String, nullable=False, comment='Lexorank', index=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -112,25 +117,6 @@ class Deal(BaseModel):
 | 
				
			|||||||
    employees: Mapped[list['DealEmployees']] = relationship(back_populates='deal', lazy='selectin')
 | 
					    employees: Mapped[list['DealEmployees']] = relationship(back_populates='deal', lazy='selectin')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealStatusHistory(BaseModel):
 | 
					 | 
				
			||||||
    __tablename__ = 'deals_status_history'
 | 
					 | 
				
			||||||
    id = Column(Integer, autoincrement=True, primary_key=True, index=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    deal_id = Column(Integer, ForeignKey('deals.id'), nullable=False, comment='ID сделки')
 | 
					 | 
				
			||||||
    deal = relationship('Deal', back_populates='status_history')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
 | 
					 | 
				
			||||||
    user = relationship('User')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    changed_at = Column(DateTime, nullable=False, comment='Дата и время когда произошла смена статуса')
 | 
					 | 
				
			||||||
    from_status = Column(Integer, nullable=False, comment='Предыдущий статус')
 | 
					 | 
				
			||||||
    to_status = Column(Integer, nullable=False, comment='Новый статус')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    next_status_deadline = Column(DateTime,
 | 
					 | 
				
			||||||
                                  comment='Дедлайн до которого сделку нужно перевести на следующий этап')
 | 
					 | 
				
			||||||
    comment = Column(String, nullable=False, comment='Коментарий', server_default='')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DealEmployees(BaseModel):
 | 
					class DealEmployees(BaseModel):
 | 
				
			||||||
    __tablename__ = 'deal_employees'
 | 
					    __tablename__ = 'deal_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)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										24
									
								
								models/project.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								models/project.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					from typing import TYPE_CHECKING
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sqlalchemy.orm import mapped_column, Mapped, relationship
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from models import BaseModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
 | 
					    from board import Board
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Project(BaseModel):
 | 
				
			||||||
 | 
					    __tablename__ = "projects"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    id: Mapped[int] = mapped_column(primary_key=True)
 | 
				
			||||||
 | 
					    name: Mapped[str] = mapped_column(nullable=False)
 | 
				
			||||||
 | 
					    created_at: Mapped[datetime] = mapped_column(nullable=False)
 | 
				
			||||||
 | 
					    is_deleted: Mapped[bool] = mapped_column(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    boards: Mapped[list["Board"]] = relationship(
 | 
				
			||||||
 | 
					        "Board",
 | 
				
			||||||
 | 
					        back_populates="project",
 | 
				
			||||||
 | 
					        lazy="noload",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
							
								
								
									
										61
									
								
								models/status.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								models/status.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					from typing import TYPE_CHECKING
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sqlalchemy import ForeignKey, Column, Integer, DateTime, String
 | 
				
			||||||
 | 
					from sqlalchemy.orm import Mapped, mapped_column, relationship
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from models import BaseModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
 | 
					    from models import Board
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealStatus(BaseModel):
 | 
				
			||||||
 | 
					    __tablename__ = "deal_statuses"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    id: Mapped[int] = mapped_column(primary_key=True)
 | 
				
			||||||
 | 
					    name: Mapped[str] = mapped_column(nullable=False)
 | 
				
			||||||
 | 
					    ordinal_number: Mapped[int] = mapped_column(nullable=False)
 | 
				
			||||||
 | 
					    is_finishing: Mapped[bool] = mapped_column(default=False, nullable=False)
 | 
				
			||||||
 | 
					    is_deleted: Mapped[bool] = mapped_column(default=False, nullable=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    board_id: Mapped[int] = mapped_column(ForeignKey('boards.id'), nullable=False)
 | 
				
			||||||
 | 
					    board: Mapped["Board"] = relationship("Board", back_populates="deal_statuses")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealStatusHistory(BaseModel):
 | 
				
			||||||
 | 
					    __tablename__ = 'deals_status_history'
 | 
				
			||||||
 | 
					    id = Column(Integer, autoincrement=True, primary_key=True, index=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    deal_id = Column(Integer, ForeignKey('deals.id'), nullable=False, comment='ID сделки')
 | 
				
			||||||
 | 
					    deal = relationship('Deal', back_populates='status_history')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
 | 
				
			||||||
 | 
					    user = relationship('User')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    changed_at = Column(DateTime, nullable=False, comment='Дата и время когда произошла смена статуса')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    from_status_id: Mapped[int] = mapped_column(
 | 
				
			||||||
 | 
					        ForeignKey('deal_statuses.id'),
 | 
				
			||||||
 | 
					        nullable=False,
 | 
				
			||||||
 | 
					        comment='Предыдущий статус',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    from_status: Mapped[DealStatus] = relationship(
 | 
				
			||||||
 | 
					        'DealStatus',
 | 
				
			||||||
 | 
					        foreign_keys=[from_status_id],
 | 
				
			||||||
 | 
					        lazy='joined',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    to_status_id: Mapped[int] = mapped_column(
 | 
				
			||||||
 | 
					        ForeignKey('deal_statuses.id'),
 | 
				
			||||||
 | 
					        nullable=False,
 | 
				
			||||||
 | 
					        comment='Новый статус',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    to_status: Mapped[DealStatus] = relationship(
 | 
				
			||||||
 | 
					        'DealStatus',
 | 
				
			||||||
 | 
					        foreign_keys=[to_status_id],
 | 
				
			||||||
 | 
					        lazy='joined',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    next_status_deadline = Column(DateTime,
 | 
				
			||||||
 | 
					                                  comment='Дедлайн до которого сделку нужно перевести на следующий этап')
 | 
				
			||||||
 | 
					    comment = Column(String, nullable=False, comment='Коментарий', server_default='')
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
from .auth import auth_router
 | 
					from .auth import auth_router
 | 
				
			||||||
from .deal import deal_router
 | 
					from .deal import deal_router
 | 
				
			||||||
 | 
					from .group import deal_group_router
 | 
				
			||||||
from .client import client_router
 | 
					from .client import client_router
 | 
				
			||||||
from .service import service_router
 | 
					from .service import service_router
 | 
				
			||||||
from .product import product_router
 | 
					from .product import product_router
 | 
				
			||||||
@@ -20,3 +21,6 @@ from .transaction import transaction_router
 | 
				
			|||||||
from .shipping import shipping_router
 | 
					from .shipping import shipping_router
 | 
				
			||||||
from .department import department_router
 | 
					from .department import department_router
 | 
				
			||||||
from .residues import residues_router
 | 
					from .residues import residues_router
 | 
				
			||||||
 | 
					from .project import project_router
 | 
				
			||||||
 | 
					from .board import board_router
 | 
				
			||||||
 | 
					from .status import status_router
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										79
									
								
								routers/board.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								routers/board.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					from typing import Annotated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from fastapi import APIRouter, Depends
 | 
				
			||||||
 | 
					from sqlalchemy.ext.asyncio import AsyncSession
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from backend.session import get_session
 | 
				
			||||||
 | 
					from schemas.board import *
 | 
				
			||||||
 | 
					from services.auth import guest_user, authorized_user
 | 
				
			||||||
 | 
					from services.board import BoardService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					board_router = APIRouter(
 | 
				
			||||||
 | 
					    prefix="/board",
 | 
				
			||||||
 | 
					    tags=["board"],
 | 
				
			||||||
 | 
					    dependencies=[Depends(guest_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@board_router.get(
 | 
				
			||||||
 | 
					    "/{project_id}",
 | 
				
			||||||
 | 
					    response_model=GetBoardsResponse,
 | 
				
			||||||
 | 
					    operation_id="get_boards",
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def get_boards(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        project_id: int,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await BoardService(session).get_boards(project_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@board_router.post(
 | 
				
			||||||
 | 
					    '/',
 | 
				
			||||||
 | 
					    response_model=CreateBoardResponse,
 | 
				
			||||||
 | 
					    operation_id="create_board",
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def create_board(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        request: CreateBoardRequest,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await BoardService(session).create_board(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@board_router.patch(
 | 
				
			||||||
 | 
					    '/',
 | 
				
			||||||
 | 
					    response_model=UpdateBoardResponse,
 | 
				
			||||||
 | 
					    operation_id="update_board",
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def update_board(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        request: UpdateBoardRequest,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await BoardService(session).update_board(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@board_router.patch(
 | 
				
			||||||
 | 
					    '/order',
 | 
				
			||||||
 | 
					    response_model=UpdateBoardOrderResponse,
 | 
				
			||||||
 | 
					    operation_id="update_board_order",
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def update_board_order(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        request: UpdateBoardOrderRequest,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await BoardService(session).update_board_order(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@board_router.delete(
 | 
				
			||||||
 | 
					    '/{board_id}',
 | 
				
			||||||
 | 
					    response_model=DeleteBoardResponse,
 | 
				
			||||||
 | 
					    operation_id="delete_board",
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def delete_board(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        board_id: int,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await BoardService(session).delete_board(board_id)
 | 
				
			||||||
@@ -24,17 +24,6 @@ deal_router = APIRouter(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# region Deal
 | 
					# region Deal
 | 
				
			||||||
@deal_router.post(
 | 
					 | 
				
			||||||
    '/create',
 | 
					 | 
				
			||||||
    dependencies=[Depends(authorized_user)]
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
async def create(
 | 
					 | 
				
			||||||
        request: DealCreateRequest,
 | 
					 | 
				
			||||||
        session: Annotated[AsyncSession, Depends(get_session)],
 | 
					 | 
				
			||||||
        user: Annotated[User, Depends(get_current_user)]
 | 
					 | 
				
			||||||
):
 | 
					 | 
				
			||||||
    return await DealService(session).create(request, user)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@deal_router.post(
 | 
					@deal_router.post(
 | 
				
			||||||
    '/delete',
 | 
					    '/delete',
 | 
				
			||||||
@@ -151,9 +140,10 @@ async def get_deal_by_id(
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
async def update_general_info(
 | 
					async def update_general_info(
 | 
				
			||||||
        request: DealUpdateGeneralInfoRequest,
 | 
					        request: DealUpdateGeneralInfoRequest,
 | 
				
			||||||
        session: Annotated[AsyncSession, Depends(get_session)]
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        user: CurrentUserDependency,
 | 
				
			||||||
):
 | 
					):
 | 
				
			||||||
    return await DealService(session).update_general_info(request)
 | 
					    return await DealService(session).update_general_info(request, user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@deal_router.post(
 | 
					@deal_router.post(
 | 
				
			||||||
@@ -494,71 +484,3 @@ async def get_deal_products_barcodes_pdf(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# endregion
 | 
					# endregion
 | 
				
			||||||
 | 
					 | 
				
			||||||
# region Deal groups
 | 
					 | 
				
			||||||
@deal_router.post(
 | 
					 | 
				
			||||||
    '/add-to-group',
 | 
					 | 
				
			||||||
    response_model=DealAddToGroupResponse,
 | 
					 | 
				
			||||||
    operation_id='add_deal_to_group',
 | 
					 | 
				
			||||||
    dependencies=[Depends(authorized_user)]
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
async def add_to_group(
 | 
					 | 
				
			||||||
        request: DealAddToGroupRequest,
 | 
					 | 
				
			||||||
        session: SessionDependency,
 | 
					 | 
				
			||||||
        user: CurrentUserDependency
 | 
					 | 
				
			||||||
):
 | 
					 | 
				
			||||||
    return await DealService(session).add_to_group(user, request)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@deal_router.post(
 | 
					 | 
				
			||||||
    '/create-group',
 | 
					 | 
				
			||||||
    response_model=DealCreateGroupResponse,
 | 
					 | 
				
			||||||
    operation_id='create_deal_group',
 | 
					 | 
				
			||||||
    dependencies=[Depends(authorized_user)]
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
async def create_group(
 | 
					 | 
				
			||||||
        request: DealCreateGroupRequest,
 | 
					 | 
				
			||||||
        session: SessionDependency,
 | 
					 | 
				
			||||||
        user: CurrentUserDependency
 | 
					 | 
				
			||||||
):
 | 
					 | 
				
			||||||
    return await DealService(session).create_group(user, request)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@deal_router.post(
 | 
					 | 
				
			||||||
    '/remove-from-group',
 | 
					 | 
				
			||||||
    response_model=DealRemoveFromGroupResponse,
 | 
					 | 
				
			||||||
    operation_id='remove_deal_from_group',
 | 
					 | 
				
			||||||
    dependencies=[Depends(authorized_user)]
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
async def remove_from_group(
 | 
					 | 
				
			||||||
        request: DealRemoveFromGroupRequest,
 | 
					 | 
				
			||||||
        session: SessionDependency,
 | 
					 | 
				
			||||||
):
 | 
					 | 
				
			||||||
    return await DealService(session).remove_from_group( request)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# route to update group name
 | 
					 | 
				
			||||||
@deal_router.post(
 | 
					 | 
				
			||||||
    '/group/update',
 | 
					 | 
				
			||||||
    response_model=DealGroupUpdateResponse,
 | 
					 | 
				
			||||||
    operation_id='update_deal_group',
 | 
					 | 
				
			||||||
    dependencies=[Depends(authorized_user)]
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
async def update_group(
 | 
					 | 
				
			||||||
        request: DealGroupUpdateRequest,
 | 
					 | 
				
			||||||
        session: SessionDependency,
 | 
					 | 
				
			||||||
):
 | 
					 | 
				
			||||||
    return await DealService(session).update_group(request)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# route to change group status
 | 
					 | 
				
			||||||
@deal_router.post(
 | 
					 | 
				
			||||||
    '/group/change-status',
 | 
					 | 
				
			||||||
    response_model=DealGroupChangeStatusResponse,
 | 
					 | 
				
			||||||
    operation_id='change_deal_group_status',
 | 
					 | 
				
			||||||
    dependencies=[Depends(authorized_user)]
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
async def change_group_status(
 | 
					 | 
				
			||||||
        request: DealGroupChangeStatusRequest,
 | 
					 | 
				
			||||||
        session: SessionDependency,
 | 
					 | 
				
			||||||
        user: CurrentUserDependency
 | 
					 | 
				
			||||||
):
 | 
					 | 
				
			||||||
    return await DealService(session).change_group_status(user,request)
 | 
					 | 
				
			||||||
# endregion
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										79
									
								
								routers/group.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								routers/group.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					from fastapi import APIRouter, Depends
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from backend.dependecies import SessionDependency, CurrentUserDependency
 | 
				
			||||||
 | 
					from schemas.group import *
 | 
				
			||||||
 | 
					from services.auth import authorized_user
 | 
				
			||||||
 | 
					from services.deal_group import DealGroupService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					deal_group_router = APIRouter(
 | 
				
			||||||
 | 
					    prefix='/deal-group',
 | 
				
			||||||
 | 
					    tags=['deal-group'],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@deal_group_router.patch(
 | 
				
			||||||
 | 
					    '/',
 | 
				
			||||||
 | 
					    response_model=DealGroupUpdateResponse,
 | 
				
			||||||
 | 
					    operation_id='update_deal_group',
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def update_group(
 | 
				
			||||||
 | 
					        request: DealGroupUpdateRequest,
 | 
				
			||||||
 | 
					        session: SessionDependency,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await DealGroupService(session).update_group(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@deal_group_router.post(
 | 
				
			||||||
 | 
					    '/',
 | 
				
			||||||
 | 
					    response_model=DealCreateGroupResponse,
 | 
				
			||||||
 | 
					    operation_id='create_deal_group',
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def create_group(
 | 
				
			||||||
 | 
					        request: DealCreateGroupRequest,
 | 
				
			||||||
 | 
					        session: SessionDependency,
 | 
				
			||||||
 | 
					        user: CurrentUserDependency
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await DealGroupService(session).create_group(user, request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@deal_group_router.patch(
 | 
				
			||||||
 | 
					    '/change-status',
 | 
				
			||||||
 | 
					    response_model=DealGroupChangeStatusResponse,
 | 
				
			||||||
 | 
					    operation_id='change_status',
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def change_status(
 | 
				
			||||||
 | 
					        request: DealGroupChangeStatusRequest,
 | 
				
			||||||
 | 
					        session: SessionDependency,
 | 
				
			||||||
 | 
					        user: CurrentUserDependency
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await DealGroupService(session).change_group_status(user,request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@deal_group_router.post(
 | 
				
			||||||
 | 
					    '/deal',
 | 
				
			||||||
 | 
					    response_model=DealAddToGroupResponse,
 | 
				
			||||||
 | 
					    operation_id='add_deal',
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def add_deal(
 | 
				
			||||||
 | 
					        request: DealAddToGroupRequest,
 | 
				
			||||||
 | 
					        session: SessionDependency,
 | 
				
			||||||
 | 
					        user: CurrentUserDependency
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await DealGroupService(session).add_deal(user, request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@deal_group_router.delete(
 | 
				
			||||||
 | 
					    '/deal',
 | 
				
			||||||
 | 
					    response_model=DealRemoveFromGroupResponse,
 | 
				
			||||||
 | 
					    operation_id='remove_deal',
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def remove_deal(
 | 
				
			||||||
 | 
					        request: DealRemoveFromGroupRequest,
 | 
				
			||||||
 | 
					        session: SessionDependency,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await DealGroupService(session).remove_deal(request)
 | 
				
			||||||
							
								
								
									
										65
									
								
								routers/project.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								routers/project.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					from typing import Annotated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from fastapi import APIRouter, Depends
 | 
				
			||||||
 | 
					from sqlalchemy.ext.asyncio import AsyncSession
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from backend.session import get_session
 | 
				
			||||||
 | 
					from schemas.project import *
 | 
				
			||||||
 | 
					from services.auth import guest_user, authorized_user
 | 
				
			||||||
 | 
					from services.project import ProjectService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					project_router = APIRouter(
 | 
				
			||||||
 | 
					    prefix="/project",
 | 
				
			||||||
 | 
					    tags=["project"],
 | 
				
			||||||
 | 
					    dependencies=[Depends(guest_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@project_router.get(
 | 
				
			||||||
 | 
					    "/",
 | 
				
			||||||
 | 
					    response_model=GetProjectsResponse,
 | 
				
			||||||
 | 
					    operation_id="get_projects",
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def get_projects(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await ProjectService(session).get_projects()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@project_router.post(
 | 
				
			||||||
 | 
					    '/',
 | 
				
			||||||
 | 
					    response_model=CreateProjectResponse,
 | 
				
			||||||
 | 
					    operation_id="create_project",
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def create_project(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        request: CreateProjectRequest,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await ProjectService(session).create_project(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@project_router.patch(
 | 
				
			||||||
 | 
					    '/',
 | 
				
			||||||
 | 
					    response_model=UpdateProjectResponse,
 | 
				
			||||||
 | 
					    operation_id="update_project",
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def update_project(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        request: UpdateProjectRequest,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await ProjectService(session).update_project(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@project_router.delete(
 | 
				
			||||||
 | 
					    '/{project_id}',
 | 
				
			||||||
 | 
					    response_model=DeleteProjectResponse,
 | 
				
			||||||
 | 
					    operation_id="delete_project",
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def delete_project(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        project_id: int,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await ProjectService(session).delete_project(project_id)
 | 
				
			||||||
							
								
								
									
										67
									
								
								routers/status.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								routers/status.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					from typing import Annotated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from fastapi import APIRouter, Depends
 | 
				
			||||||
 | 
					from sqlalchemy.ext.asyncio import AsyncSession
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from backend.session import get_session
 | 
				
			||||||
 | 
					from schemas.status import *
 | 
				
			||||||
 | 
					from services.auth import guest_user, authorized_user
 | 
				
			||||||
 | 
					from services.status import StatusService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					status_router = APIRouter(
 | 
				
			||||||
 | 
					    prefix="/status",
 | 
				
			||||||
 | 
					    tags=["status"],
 | 
				
			||||||
 | 
					    dependencies=[Depends(guest_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@status_router.post(
 | 
				
			||||||
 | 
					    '/',
 | 
				
			||||||
 | 
					    response_model=CreateStatusResponse,
 | 
				
			||||||
 | 
					    operation_id="create_status",
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def create_status(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        request: CreateStatusRequest,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await StatusService(session).create_status(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@status_router.patch(
 | 
				
			||||||
 | 
					    '/',
 | 
				
			||||||
 | 
					    response_model=UpdateStatusResponse,
 | 
				
			||||||
 | 
					    operation_id="update_status",
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def update_status(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        request: UpdateStatusRequest,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await StatusService(session).update_status(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@status_router.patch(
 | 
				
			||||||
 | 
					    '/order',
 | 
				
			||||||
 | 
					    response_model=UpdateStatusOrderResponse,
 | 
				
			||||||
 | 
					    operation_id="update_status_order",
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def update_status_order(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        request: UpdateStatusOrderRequest,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await StatusService(session).update_status_order(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@status_router.delete(
 | 
				
			||||||
 | 
					    '/{status_id}',
 | 
				
			||||||
 | 
					    response_model=DeleteStatusResponse,
 | 
				
			||||||
 | 
					    operation_id="delete_status",
 | 
				
			||||||
 | 
					    dependencies=[Depends(authorized_user)]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def delete_status(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        status_id: int,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await StatusService(session).delete_status(status_id)
 | 
				
			||||||
							
								
								
									
										61
									
								
								schemas/board.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								schemas/board.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					from schemas.base import BaseSchema, OkMessageSchema
 | 
				
			||||||
 | 
					from schemas.project import ProjectSchema
 | 
				
			||||||
 | 
					from schemas.status import StatusSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# region Entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BaseBoardSchema(BaseSchema):
 | 
				
			||||||
 | 
					    name: str
 | 
				
			||||||
 | 
					    project_id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoardSchema(BaseBoardSchema):
 | 
				
			||||||
 | 
					    id: int
 | 
				
			||||||
 | 
					    ordinal_number: int
 | 
				
			||||||
 | 
					    deal_statuses: list[StatusSchema]
 | 
				
			||||||
 | 
					    project: ProjectSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# region Requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateBoardRequest(BaseSchema):
 | 
				
			||||||
 | 
					    board: BaseBoardSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UpdateBoardRequest(BaseSchema):
 | 
				
			||||||
 | 
					    board: BoardSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UpdateBoardOrderRequest(BaseSchema):
 | 
				
			||||||
 | 
					    project_id: int
 | 
				
			||||||
 | 
					    board_id: int
 | 
				
			||||||
 | 
					    new_ordinal_number: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# region Responses
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GetBoardsResponse(BaseSchema):
 | 
				
			||||||
 | 
					    boards: list[BoardSchema]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateBoardResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UpdateBoardResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UpdateBoardOrderResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DeleteBoardResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# endregion
 | 
				
			||||||
@@ -1,18 +1,19 @@
 | 
				
			|||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
from collections import defaultdict
 | 
					from typing import List, Optional, Union
 | 
				
			||||||
from typing import List, Optional, Union, Dict, Tuple, TypedDict
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from pydantic import constr, field_validator
 | 
					from pydantic import constr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from models import ServiceCategoryPrice, ServicePriceCategory, Deal, Product, DealProduct, DealStatusHistory
 | 
					 | 
				
			||||||
from schemas.base import BaseSchema, OkMessageSchema
 | 
					from schemas.base import BaseSchema, OkMessageSchema
 | 
				
			||||||
from schemas.billing import DealBillRequestSchema,GroupBillRequestSchema
 | 
					from schemas.billing import DealBillRequestSchema
 | 
				
			||||||
 | 
					from schemas.board import BoardSchema
 | 
				
			||||||
from schemas.client import ClientSchema
 | 
					from schemas.client import ClientSchema
 | 
				
			||||||
 | 
					from schemas.group import DealGroupSchema
 | 
				
			||||||
from schemas.marketplace import BaseMarketplaceSchema
 | 
					from schemas.marketplace import BaseMarketplaceSchema
 | 
				
			||||||
from schemas.product import ProductSchema
 | 
					from schemas.product import ProductSchema
 | 
				
			||||||
from schemas.service import ServiceSchema, ServicePriceCategorySchema
 | 
					from schemas.service import ServiceSchema, ServicePriceCategorySchema
 | 
				
			||||||
from schemas.shipping import PalletSchema, BoxSchema
 | 
					from schemas.shipping import PalletSchema, BoxSchema
 | 
				
			||||||
from schemas.shipping_warehouse import ShippingWarehouseSchema, BaseShippingWarehouseSchema
 | 
					from schemas.shipping_warehouse import ShippingWarehouseSchema, BaseShippingWarehouseSchema
 | 
				
			||||||
 | 
					from schemas.status import StatusSchema, DealStatusHistorySchema
 | 
				
			||||||
from schemas.user import UserSchema
 | 
					from schemas.user import UserSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,21 +25,13 @@ class FastDeal(BaseSchema):
 | 
				
			|||||||
    acceptance_date: datetime.datetime
 | 
					    acceptance_date: datetime.datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealGroupSchema(BaseSchema):
 | 
					 | 
				
			||||||
    id: int
 | 
					 | 
				
			||||||
    name: Optional[str] = None
 | 
					 | 
				
			||||||
    lexorank: str
 | 
					 | 
				
			||||||
    bill_request: Optional[GroupBillRequestSchema] = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DealSummary(BaseSchema):
 | 
					class DealSummary(BaseSchema):
 | 
				
			||||||
    id: int
 | 
					    id: int
 | 
				
			||||||
    name: str
 | 
					    name: str
 | 
				
			||||||
    client_name: str
 | 
					    client_name: str
 | 
				
			||||||
    changed_at: datetime.datetime
 | 
					 | 
				
			||||||
    created_at: datetime.datetime
 | 
					    created_at: datetime.datetime
 | 
				
			||||||
    deadline: Optional[datetime.datetime] = None
 | 
					    status: StatusSchema
 | 
				
			||||||
    status: int
 | 
					    board: BoardSchema
 | 
				
			||||||
    total_price: int
 | 
					    total_price: int
 | 
				
			||||||
    rank: int
 | 
					    rank: int
 | 
				
			||||||
    base_marketplace: Optional[BaseMarketplaceSchema] = None
 | 
					    base_marketplace: Optional[BaseMarketplaceSchema] = None
 | 
				
			||||||
@@ -75,15 +68,6 @@ class DealProductSchema(BaseSchema):
 | 
				
			|||||||
    comment: str = ""
 | 
					    comment: str = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealStatusHistorySchema(BaseSchema):
 | 
					 | 
				
			||||||
    user: UserSchema
 | 
					 | 
				
			||||||
    changed_at: datetime.datetime
 | 
					 | 
				
			||||||
    from_status: int
 | 
					 | 
				
			||||||
    to_status: int
 | 
					 | 
				
			||||||
    next_status_deadline: datetime.datetime | None
 | 
					 | 
				
			||||||
    comment: str | None = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DealEmployeesSchema(BaseSchema):
 | 
					class DealEmployeesSchema(BaseSchema):
 | 
				
			||||||
    user: UserSchema
 | 
					    user: UserSchema
 | 
				
			||||||
    created_at: datetime.datetime
 | 
					    created_at: datetime.datetime
 | 
				
			||||||
@@ -94,7 +78,8 @@ class DealSchema(BaseSchema):
 | 
				
			|||||||
    name: str
 | 
					    name: str
 | 
				
			||||||
    client_id: int
 | 
					    client_id: int
 | 
				
			||||||
    created_at: datetime.datetime
 | 
					    created_at: datetime.datetime
 | 
				
			||||||
    current_status: int
 | 
					    status: StatusSchema
 | 
				
			||||||
 | 
					    board: BoardSchema
 | 
				
			||||||
    services: List[DealServiceSchema]
 | 
					    services: List[DealServiceSchema]
 | 
				
			||||||
    products: List[DealProductSchema]
 | 
					    products: List[DealProductSchema]
 | 
				
			||||||
    status_history: List[DealStatusHistorySchema]
 | 
					    status_history: List[DealStatusHistorySchema]
 | 
				
			||||||
@@ -127,6 +112,8 @@ class DealGeneralInfoSchema(BaseSchema):
 | 
				
			|||||||
    delivery_date: Optional[datetime.datetime] = None
 | 
					    delivery_date: Optional[datetime.datetime] = None
 | 
				
			||||||
    receiving_slot_date: Optional[datetime.datetime] = None
 | 
					    receiving_slot_date: Optional[datetime.datetime] = None
 | 
				
			||||||
    manager: Optional[UserSchema] = None
 | 
					    manager: Optional[UserSchema] = None
 | 
				
			||||||
 | 
					    board: BoardSchema
 | 
				
			||||||
 | 
					    status: StatusSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OptionalShippingWarehouseSchema(BaseShippingWarehouseSchema):
 | 
					class OptionalShippingWarehouseSchema(BaseShippingWarehouseSchema):
 | 
				
			||||||
@@ -166,6 +153,7 @@ class DealChangeStatusRequest(BaseSchema):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class DealCreateRequest(BaseSchema):
 | 
					class DealCreateRequest(BaseSchema):
 | 
				
			||||||
    name: str
 | 
					    name: str
 | 
				
			||||||
 | 
					    status_id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealQuickCreateRequest(BaseSchema):
 | 
					class DealQuickCreateRequest(BaseSchema):
 | 
				
			||||||
@@ -176,6 +164,7 @@ class DealQuickCreateRequest(BaseSchema):
 | 
				
			|||||||
    shipping_warehouse: constr(strip_whitespace=True)
 | 
					    shipping_warehouse: constr(strip_whitespace=True)
 | 
				
			||||||
    base_marketplace: BaseMarketplaceSchema
 | 
					    base_marketplace: BaseMarketplaceSchema
 | 
				
			||||||
    category: Optional[ServicePriceCategorySchema] = None
 | 
					    category: Optional[ServicePriceCategorySchema] = None
 | 
				
			||||||
 | 
					    status_id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealSummaryRequest(BaseSchema):
 | 
					class DealSummaryRequest(BaseSchema):
 | 
				
			||||||
@@ -243,7 +232,7 @@ class DealUpdateGeneralInfoRequest(BaseSchema):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class DealSummaryReorderRequest(BaseSchema):
 | 
					class DealSummaryReorderRequest(BaseSchema):
 | 
				
			||||||
    deal_id: int
 | 
					    deal_id: int
 | 
				
			||||||
    status: int
 | 
					    status_id: int
 | 
				
			||||||
    index: int
 | 
					    index: int
 | 
				
			||||||
    deadline: datetime.datetime | None = None
 | 
					    deadline: datetime.datetime | None = None
 | 
				
			||||||
    comment: str | None = None
 | 
					    comment: str | None = None
 | 
				
			||||||
@@ -300,32 +289,10 @@ class ManageEmployeeRequest(BaseSchema):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class CreateDealsFromExcelRequest(BaseSchema):
 | 
					class CreateDealsFromExcelRequest(BaseSchema):
 | 
				
			||||||
    client_id: int
 | 
					    client_id: int
 | 
				
			||||||
 | 
					    status_id: int
 | 
				
			||||||
    products: list[ProductFromExcelSchema]
 | 
					    products: list[ProductFromExcelSchema]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealAddToGroupRequest(BaseSchema):
 | 
					 | 
				
			||||||
    deal_id: int
 | 
					 | 
				
			||||||
    group_id: int
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DealCreateGroupRequest(BaseSchema):
 | 
					 | 
				
			||||||
    dragging_deal_id: int
 | 
					 | 
				
			||||||
    hovered_deal_id: int
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DealRemoveFromGroupRequest(BaseSchema):
 | 
					 | 
				
			||||||
    deal_id: int
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DealGroupUpdateRequest(BaseSchema):
 | 
					 | 
				
			||||||
    data: DealGroupSchema
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DealGroupChangeStatusRequest(BaseSchema):
 | 
					 | 
				
			||||||
    group_id: int
 | 
					 | 
				
			||||||
    new_status: int
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# endregion Requests
 | 
					# endregion Requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# region Responses
 | 
					# region Responses
 | 
				
			||||||
@@ -452,23 +419,4 @@ class CreateDealsFromExcelResponse(OkMessageSchema):
 | 
				
			|||||||
    pass
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealAddToGroupResponse(OkMessageSchema):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DealCreateGroupResponse(OkMessageSchema):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DealRemoveFromGroupResponse(OkMessageSchema):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DealGroupUpdateResponse(OkMessageSchema):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DealGroupChangeStatusResponse(OkMessageSchema):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# endregion Responses
 | 
					# endregion Responses
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										66
									
								
								schemas/group.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								schemas/group.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from schemas.base import BaseSchema, OkMessageSchema
 | 
				
			||||||
 | 
					from schemas.billing import GroupBillRequestSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# region Entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealGroupSchema(BaseSchema):
 | 
				
			||||||
 | 
					    id: int
 | 
				
			||||||
 | 
					    name: Optional[str] = None
 | 
				
			||||||
 | 
					    lexorank: str
 | 
				
			||||||
 | 
					    bill_request: Optional[GroupBillRequestSchema] = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# region Requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealGroupUpdateRequest(BaseSchema):
 | 
				
			||||||
 | 
					    data: DealGroupSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealCreateGroupRequest(BaseSchema):
 | 
				
			||||||
 | 
					    dragging_deal_id: int
 | 
				
			||||||
 | 
					    hovered_deal_id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealGroupChangeStatusRequest(BaseSchema):
 | 
				
			||||||
 | 
					    group_id: int
 | 
				
			||||||
 | 
					    new_status: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealAddToGroupRequest(BaseSchema):
 | 
				
			||||||
 | 
					    deal_id: int
 | 
				
			||||||
 | 
					    group_id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealRemoveFromGroupRequest(BaseSchema):
 | 
				
			||||||
 | 
					    deal_id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# region Responses
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealCreateGroupResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealGroupUpdateResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealGroupChangeStatusResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealAddToGroupResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealRemoveFromGroupResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# endregion
 | 
				
			||||||
							
								
								
									
										50
									
								
								schemas/project.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								schemas/project.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					from schemas.base import BaseSchema, OkMessageSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# region Entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BaseProjectSchema(BaseSchema):
 | 
				
			||||||
 | 
					    name: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProjectSchema(BaseProjectSchema):
 | 
				
			||||||
 | 
					    id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProjectSchemaWithCount(ProjectSchema):
 | 
				
			||||||
 | 
					    boards_count: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# region Requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateProjectRequest(BaseSchema):
 | 
				
			||||||
 | 
					    project: BaseProjectSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UpdateProjectRequest(BaseSchema):
 | 
				
			||||||
 | 
					    project: ProjectSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# region Responses
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GetProjectsResponse(BaseSchema):
 | 
				
			||||||
 | 
					    projects: list[ProjectSchemaWithCount]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateProjectResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UpdateProjectResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DeleteProjectResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# endregion
 | 
				
			||||||
@@ -29,10 +29,13 @@ class CommonProfitFilters(BaseSchema):
 | 
				
			|||||||
    date_range: Tuple[datetime.date, datetime.date]
 | 
					    date_range: Tuple[datetime.date, datetime.date]
 | 
				
			||||||
    client_id: int
 | 
					    client_id: int
 | 
				
			||||||
    base_marketplace_key: str
 | 
					    base_marketplace_key: str
 | 
				
			||||||
 | 
					    project_id: int
 | 
				
			||||||
 | 
					    board_id: int
 | 
				
			||||||
    deal_status_id: int
 | 
					    deal_status_id: int
 | 
				
			||||||
    manager_id: int
 | 
					    manager_id: int
 | 
				
			||||||
    expense_tag_id: int
 | 
					    expense_tag_id: int
 | 
				
			||||||
    income_tag_id: int
 | 
					    income_tag_id: int
 | 
				
			||||||
 | 
					    is_completed_only: bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GetProfitChartDataRequest(CommonProfitFilters):
 | 
					class GetProfitChartDataRequest(CommonProfitFilters):
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										65
									
								
								schemas/status.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								schemas/status.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from schemas.base import BaseSchema, OkMessageSchema
 | 
				
			||||||
 | 
					from schemas.user import UserSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# region Entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BaseStatusSchema(BaseSchema):
 | 
				
			||||||
 | 
					    name: str
 | 
				
			||||||
 | 
					    board_id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StatusSchema(BaseStatusSchema):
 | 
				
			||||||
 | 
					    id: int
 | 
				
			||||||
 | 
					    ordinal_number: int
 | 
				
			||||||
 | 
					    is_deleted: bool = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DealStatusHistorySchema(BaseSchema):
 | 
				
			||||||
 | 
					    user: UserSchema
 | 
				
			||||||
 | 
					    changed_at: datetime
 | 
				
			||||||
 | 
					    from_status: StatusSchema
 | 
				
			||||||
 | 
					    to_status: StatusSchema
 | 
				
			||||||
 | 
					    next_status_deadline: datetime | None
 | 
				
			||||||
 | 
					    comment: str | None = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# region Requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateStatusRequest(BaseSchema):
 | 
				
			||||||
 | 
					    status: BaseStatusSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UpdateStatusRequest(BaseSchema):
 | 
				
			||||||
 | 
					    status: StatusSchema
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UpdateStatusOrderRequest(BaseSchema):
 | 
				
			||||||
 | 
					    board_id: int
 | 
				
			||||||
 | 
					    status_id: int
 | 
				
			||||||
 | 
					    new_ordinal_number: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# region Responses
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateStatusResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UpdateStatusResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UpdateStatusOrderResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DeleteStatusResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# endregion
 | 
				
			||||||
							
								
								
									
										119
									
								
								services/board.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								services/board.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
				
			|||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sqlalchemy import select, and_, func
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from models import Board, Deal
 | 
				
			||||||
 | 
					from schemas.board import *
 | 
				
			||||||
 | 
					from services.base import BaseService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BoardService(BaseService):
 | 
				
			||||||
 | 
					    async def _get_boards_for_project(self, project_id: int) -> list[Board]:
 | 
				
			||||||
 | 
					        stmt = (
 | 
				
			||||||
 | 
					            select(Board)
 | 
				
			||||||
 | 
					            .where(
 | 
				
			||||||
 | 
					                and_(
 | 
				
			||||||
 | 
					                    Board.is_deleted == False,
 | 
				
			||||||
 | 
					                    Board.project_id == project_id,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .order_by(Board.ordinal_number)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        boards = (await self.session.scalars(stmt)).all()
 | 
				
			||||||
 | 
					        return list(boards)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def get_boards(self, project_id: int) -> GetBoardsResponse:
 | 
				
			||||||
 | 
					        boards = await self._get_boards_for_project(project_id)
 | 
				
			||||||
 | 
					        return GetBoardsResponse(boards=boards)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def create_board(self, request: CreateBoardRequest) -> CreateBoardResponse:
 | 
				
			||||||
 | 
					        boards = await self._get_boards_for_project(request.board.project_id)
 | 
				
			||||||
 | 
					        if len(boards) == 0:
 | 
				
			||||||
 | 
					            ordinal_number = 1
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            ordinal_number = boards[-1].ordinal_number + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        board = Board(
 | 
				
			||||||
 | 
					            **request.board.model_dump(),
 | 
				
			||||||
 | 
					            created_at=datetime.now(),
 | 
				
			||||||
 | 
					            ordinal_number=ordinal_number,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.session.add(board)
 | 
				
			||||||
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return CreateBoardResponse(ok=True, message="Доска успешно создана")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _get_board_by_id(self, board_id: int) -> Optional[Board]:
 | 
				
			||||||
 | 
					        return await self.session.get(Board, board_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def update_board(self, request: UpdateBoardRequest) -> UpdateBoardResponse:
 | 
				
			||||||
 | 
					        board = await self._get_board_by_id(request.board.id)
 | 
				
			||||||
 | 
					        if not board:
 | 
				
			||||||
 | 
					            return UpdateBoardResponse(ok=False, message=f"Доска с ID {request.board.id} не найдена")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        board.name = request.board.name
 | 
				
			||||||
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					        return UpdateBoardResponse(ok=True, message="Доска успешно обновлена")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def update_board_order(self, request: UpdateBoardOrderRequest) -> UpdateBoardOrderResponse:
 | 
				
			||||||
 | 
					        boards = await self._get_boards_for_project(request.project_id)
 | 
				
			||||||
 | 
					        board_idx = 0
 | 
				
			||||||
 | 
					        while board_idx < len(boards) and boards[board_idx].id != request.board_id:
 | 
				
			||||||
 | 
					            board_idx += 1
 | 
				
			||||||
 | 
					        if board_idx == len(boards):
 | 
				
			||||||
 | 
					            return UpdateBoardOrderResponse(ok=False, message=f"Доска с ID {request.board_id} не найдена в проекте")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        board = boards.pop(board_idx)
 | 
				
			||||||
 | 
					        boards.insert(request.new_ordinal_number - 1, board)
 | 
				
			||||||
 | 
					        new_ordinal_number = 1
 | 
				
			||||||
 | 
					        for board in boards:
 | 
				
			||||||
 | 
					            board.ordinal_number = new_ordinal_number
 | 
				
			||||||
 | 
					            new_ordinal_number += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					        return UpdateBoardOrderResponse(ok=True, message="Порядок досок изменен")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _count_deals_in_progress(self, board_id: int) -> int:
 | 
				
			||||||
 | 
					        stmt = (
 | 
				
			||||||
 | 
					            select(func.count(Deal.id))
 | 
				
			||||||
 | 
					            .where(
 | 
				
			||||||
 | 
					                and_(
 | 
				
			||||||
 | 
					                    Deal.board_id == board_id,
 | 
				
			||||||
 | 
					                    Deal.is_deleted == False,
 | 
				
			||||||
 | 
					                    Deal.is_completed == False,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return (await self.session.scalars(stmt)).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _count_deals(self, board_id: int) -> int:
 | 
				
			||||||
 | 
					        stmt = (
 | 
				
			||||||
 | 
					            select(func.count(Deal.id))
 | 
				
			||||||
 | 
					            .where(Deal.board_id == board_id)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return (await self.session.scalars(stmt)).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def delete_board(self, board_id: int) -> DeleteBoardResponse:
 | 
				
			||||||
 | 
					        board = await self._get_board_by_id(board_id)
 | 
				
			||||||
 | 
					        if not board:
 | 
				
			||||||
 | 
					            return DeleteBoardResponse(ok=False, message=f"Доска с ID {board_id} не найдена")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        count_deals_in_progress = await self._count_deals_in_progress(board_id)
 | 
				
			||||||
 | 
					        if count_deals_in_progress != 0:
 | 
				
			||||||
 | 
					            return DeleteBoardResponse(
 | 
				
			||||||
 | 
					                ok=False,
 | 
				
			||||||
 | 
					                message=f"Нельзя удалить доску с активными сделками",
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        count_deals = await self._count_deals(board_id)
 | 
				
			||||||
 | 
					        if count_deals == 0:
 | 
				
			||||||
 | 
					            await self.session.delete(board)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            board.is_deleted = True
 | 
				
			||||||
 | 
					            for status in board.deal_statuses:
 | 
				
			||||||
 | 
					                status.is_deleted = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return DeleteBoardResponse(ok=True, message="Доска успешно удалена")
 | 
				
			||||||
							
								
								
									
										244
									
								
								services/deal.py
									
									
									
									
									
								
							
							
						
						
									
										244
									
								
								services/deal.py
									
									
									
									
									
								
							@@ -1,13 +1,14 @@
 | 
				
			|||||||
 | 
					from collections import defaultdict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import lexorank
 | 
					import lexorank
 | 
				
			||||||
from attr import dataclass
 | 
					 | 
				
			||||||
from fastapi import HTTPException
 | 
					from fastapi import HTTPException
 | 
				
			||||||
from sqlalchemy import select, func, update, delete, insert, and_
 | 
					from sqlalchemy import select, func, update, delete, insert, and_
 | 
				
			||||||
from sqlalchemy.orm import joinedload, selectinload
 | 
					from sqlalchemy.orm import joinedload, selectinload
 | 
				
			||||||
from starlette import status
 | 
					from starlette import status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from models import User, Service, Client, DealProductService, deal_relations, DealStatusHistory, Product, DealProduct
 | 
				
			||||||
import models.deal
 | 
					import models.deal
 | 
				
			||||||
import models.secondary
 | 
					import models.secondary
 | 
				
			||||||
from models import User, Service, Client, DealProductService, deal_relations, GroupBillRequest
 | 
					 | 
				
			||||||
from models.deal import *
 | 
					from models.deal import *
 | 
				
			||||||
from models.deal_group import DealGroup
 | 
					from models.deal_group import DealGroup
 | 
				
			||||||
from models.shipping import ShippingProduct
 | 
					from models.shipping import ShippingProduct
 | 
				
			||||||
@@ -16,9 +17,9 @@ from schemas.deal import *
 | 
				
			|||||||
from services.auth import AuthService
 | 
					from services.auth import AuthService
 | 
				
			||||||
from services.base import BaseService
 | 
					from services.base import BaseService
 | 
				
			||||||
from services.client import ClientService
 | 
					from services.client import ClientService
 | 
				
			||||||
from services.deal_group import DealGroupService
 | 
					 | 
				
			||||||
from services.service import ServiceService
 | 
					from services.service import ServiceService
 | 
				
			||||||
from services.shipping_warehouse import ShippingWarehouseService
 | 
					from services.shipping_warehouse import ShippingWarehouseService
 | 
				
			||||||
 | 
					from services import deal_group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealService(BaseService):
 | 
					class DealService(BaseService):
 | 
				
			||||||
@@ -36,9 +37,13 @@ class DealService(BaseService):
 | 
				
			|||||||
    async def _get_deal_by_id(self, deal_id) -> Union[Deal, None]:
 | 
					    async def _get_deal_by_id(self, deal_id) -> Union[Deal, None]:
 | 
				
			||||||
        return await self.session.get(Deal, deal_id)
 | 
					        return await self.session.get(Deal, deal_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def _get_rank_for_deal(self, deal_status: DealStatus) -> str:
 | 
					    async def _get_rank_for_deal(self, status_id: int) -> str:
 | 
				
			||||||
        deal_query = await self.session.execute(
 | 
					        deal_query = await self.session.execute(
 | 
				
			||||||
            select(Deal).where(Deal.current_status == deal_status).order_by(Deal.lexorank.desc()).limit(1))
 | 
					            select(Deal)
 | 
				
			||||||
 | 
					            .where(Deal.current_status_id == status_id)
 | 
				
			||||||
 | 
					            .order_by(Deal.lexorank.desc())
 | 
				
			||||||
 | 
					            .limit(1)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        deal = deal_query.scalar_one_or_none()
 | 
					        deal = deal_query.scalar_one_or_none()
 | 
				
			||||||
        if not deal:
 | 
					        if not deal:
 | 
				
			||||||
            prev = lexorank.middle(lexorank.Bucket.BUCEKT_0)
 | 
					            prev = lexorank.middle(lexorank.Bucket.BUCEKT_0)
 | 
				
			||||||
@@ -46,59 +51,46 @@ class DealService(BaseService):
 | 
				
			|||||||
        return str(lexorank.parse(deal.lexorank).next())
 | 
					        return str(lexorank.parse(deal.lexorank).next())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def change_status(self, deal: Deal,
 | 
					    async def change_status(self, deal: Deal,
 | 
				
			||||||
                            status: DealStatus,
 | 
					                            status_id: int,
 | 
				
			||||||
                            user: User,
 | 
					                            user: models.User,
 | 
				
			||||||
                            deadline: datetime.datetime = None,
 | 
					                            deadline: datetime.datetime = None,
 | 
				
			||||||
                            rank=None,
 | 
					                            rank=None,
 | 
				
			||||||
                            comment: str = ''):
 | 
					                            comment: str = ''):
 | 
				
			||||||
        if not deal.current_status == status:
 | 
					        if not deal.current_status_id == status_id:
 | 
				
			||||||
            deadline = deadline
 | 
					            deadline = deadline
 | 
				
			||||||
            status_change = DealStatusHistory(
 | 
					            status_change = DealStatusHistory(
 | 
				
			||||||
                deal_id=deal.id,
 | 
					                deal_id=deal.id,
 | 
				
			||||||
                user_id=user.id,
 | 
					                user_id=user.id,
 | 
				
			||||||
                changed_at=datetime.datetime.now(),
 | 
					                changed_at=datetime.datetime.now(),
 | 
				
			||||||
                from_status=deal.current_status,
 | 
					                from_status_id=deal.current_status_id,
 | 
				
			||||||
                to_status=status,
 | 
					                to_status_id=status_id,
 | 
				
			||||||
                next_status_deadline=deadline,
 | 
					                next_status_deadline=deadline,
 | 
				
			||||||
                comment=comment
 | 
					                comment=comment
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            self.session.add(status_change)
 | 
					            self.session.add(status_change)
 | 
				
			||||||
            deal.current_status = status
 | 
					            deal.current_status_id = status_id
 | 
				
			||||||
            if not rank:
 | 
					            if not rank:
 | 
				
			||||||
                rank = await self._get_rank_for_deal(status)
 | 
					                rank = await self._get_rank_for_deal(status_id)
 | 
				
			||||||
        if rank:
 | 
					        if rank:
 | 
				
			||||||
            deal.lexorank = rank
 | 
					            deal.lexorank = rank
 | 
				
			||||||
        await self.session.flush()
 | 
					        await self.session.flush()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def create(self, request: DealCreateRequest, user: User) -> DealCreateResponse:
 | 
					 | 
				
			||||||
        rank = self._get_rank_for_deal(DealStatus.CREATED)
 | 
					 | 
				
			||||||
        deal = Deal(
 | 
					 | 
				
			||||||
            name=request.name,
 | 
					 | 
				
			||||||
            created_at=datetime.datetime.now(),
 | 
					 | 
				
			||||||
            current_status=DealStatus.CREATED,
 | 
					 | 
				
			||||||
            lexorank=rank
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self.session.add(deal)
 | 
					 | 
				
			||||||
        await self.session.flush()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Append status history
 | 
					 | 
				
			||||||
        await self.change_status(deal, DealStatus.AWAITING_ACCEPTANCE, user)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await self.session.commit()
 | 
					 | 
				
			||||||
        return DealCreateResponse(ok=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def delete(self, request: DealDeleteRequest) -> DealDeleteResponse:
 | 
					    async def delete(self, request: DealDeleteRequest) -> DealDeleteResponse:
 | 
				
			||||||
        deal = await self._get_deal_by_id(request.deal_id)
 | 
					        deal = await self._get_deal_by_id(request.deal_id)
 | 
				
			||||||
        if not deal:
 | 
					        if not deal:
 | 
				
			||||||
            return DealDeleteResponse(ok=False, message="Сделка не найдена")
 | 
					            return DealDeleteResponse(ok=False, message="Сделка не найдена")
 | 
				
			||||||
        if deal.group:
 | 
					        if deal.group:
 | 
				
			||||||
            await DealGroupService(self.session).delete_group(deal.group.id)
 | 
					            await deal_group.DealGroupService(self.session).delete_group(deal.group.id)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            deal.is_deleted = True
 | 
					            deal.is_deleted = True
 | 
				
			||||||
        await self.session.commit()
 | 
					        await self.session.commit()
 | 
				
			||||||
        return DealDeleteResponse(ok=True, message="Сделка успешно удалена")
 | 
					        return DealDeleteResponse(ok=True, message="Сделка успешно удалена")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def quick_create(self, request: DealQuickCreateRequest, user: User) -> DealQuickCreateResponse:
 | 
					    async def quick_create(self, request: DealQuickCreateRequest, user: models.User) -> DealQuickCreateResponse:
 | 
				
			||||||
 | 
					        deal_status = await self.session.get(DealStatus, request.status_id)
 | 
				
			||||||
 | 
					        if not deal_status:
 | 
				
			||||||
 | 
					            raise HTTPException(status_code=400, detail="Указан некорректный статус")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        client_service = ClientService(self.session)
 | 
					        client_service = ClientService(self.session)
 | 
				
			||||||
        client = await client_service.get_by_name(request.client_name)
 | 
					        client = await client_service.get_by_name(request.client_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -108,28 +100,25 @@ class DealService(BaseService):
 | 
				
			|||||||
                request.client_name,
 | 
					                request.client_name,
 | 
				
			||||||
                ClientDetailsSchema()
 | 
					                ClientDetailsSchema()
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        shipping_warehouse_service = ShippingWarehouseService(self.session)
 | 
					        shipping_warehouse_service = ShippingWarehouseService(self.session)
 | 
				
			||||||
        shipping_warehouse = await shipping_warehouse_service.get_by_name(name=request.shipping_warehouse)
 | 
					        shipping_warehouse = await shipping_warehouse_service.get_by_name(name=request.shipping_warehouse)
 | 
				
			||||||
        if not shipping_warehouse:
 | 
					        if not shipping_warehouse:
 | 
				
			||||||
            shipping_warehouse = await shipping_warehouse_service.create_by_name(name=request.shipping_warehouse)
 | 
					            shipping_warehouse = await shipping_warehouse_service.create_by_name(name=request.shipping_warehouse)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        rank = await self._get_rank_for_deal(DealStatus.CREATED)
 | 
					        rank = await self._get_rank_for_deal(request.status_id)
 | 
				
			||||||
        deal = Deal(
 | 
					        deal = Deal(
 | 
				
			||||||
            name=request.name,
 | 
					            name=request.name,
 | 
				
			||||||
            created_at=datetime.datetime.now(),
 | 
					            created_at=datetime.datetime.now(),
 | 
				
			||||||
            client_id=client.id,
 | 
					            client_id=client.id,
 | 
				
			||||||
            current_status=DealStatus.CREATED,
 | 
					            current_status_id=request.status_id,
 | 
				
			||||||
 | 
					            board_id=deal_status.board_id,
 | 
				
			||||||
            lexorank=rank,
 | 
					            lexorank=rank,
 | 
				
			||||||
            shipping_warehouse_id=shipping_warehouse.id,
 | 
					            shipping_warehouse_id=shipping_warehouse.id,
 | 
				
			||||||
            base_marketplace_key=request.base_marketplace.key
 | 
					            base_marketplace_key=request.base_marketplace.key
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.session.add(deal)
 | 
					        self.session.add(deal)
 | 
				
			||||||
        await self.session.flush()
 | 
					        await self.session.flush()
 | 
				
			||||||
        await self.change_status(deal,
 | 
					 | 
				
			||||||
                                 DealStatus.AWAITING_ACCEPTANCE,
 | 
					 | 
				
			||||||
                                 user,
 | 
					 | 
				
			||||||
                                 deadline=request.acceptance_date,
 | 
					 | 
				
			||||||
                                 comment=request.comment)
 | 
					 | 
				
			||||||
        # add category if specified
 | 
					        # add category if specified
 | 
				
			||||||
        if request.category:
 | 
					        if request.category:
 | 
				
			||||||
            deal_category = DealPriceCategory(
 | 
					            deal_category = DealPriceCategory(
 | 
				
			||||||
@@ -141,12 +130,12 @@ class DealService(BaseService):
 | 
				
			|||||||
        await self.session.commit()
 | 
					        await self.session.commit()
 | 
				
			||||||
        return DealQuickCreateResponse(deal_id=deal.id)
 | 
					        return DealQuickCreateResponse(deal_id=deal.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def change_status_manual(self, request: DealChangeStatusRequest, user: User) -> DealChangeStatusResponse:
 | 
					    async def change_status_manual(self, request: DealChangeStatusRequest, user: models.User) -> DealChangeStatusResponse:
 | 
				
			||||||
        # Changing current status
 | 
					        # Changing current status
 | 
				
			||||||
        deal = await self._get_deal_by_id(request.deal_id)
 | 
					        deal = await self._get_deal_by_id(request.deal_id)
 | 
				
			||||||
        if not deal:
 | 
					        if not deal:
 | 
				
			||||||
            return DealChangeStatusResponse(ok=False)
 | 
					            return DealChangeStatusResponse(ok=False)
 | 
				
			||||||
        await self.change_status(deal, DealStatus(request.new_status), user)
 | 
					        await self.change_status(deal, request.new_status, user)
 | 
				
			||||||
        await self.session.commit()
 | 
					        await self.session.commit()
 | 
				
			||||||
        return DealChangeStatusResponse(ok=True)
 | 
					        return DealChangeStatusResponse(ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -199,7 +188,7 @@ class DealService(BaseService):
 | 
				
			|||||||
                Deal,
 | 
					                Deal,
 | 
				
			||||||
                func.coalesce(price_subquery.c.total_price, 0),
 | 
					                func.coalesce(price_subquery.c.total_price, 0),
 | 
				
			||||||
                func.row_number().over(
 | 
					                func.row_number().over(
 | 
				
			||||||
                    partition_by=Deal.current_status,
 | 
					                    partition_by=Deal.current_status_id,
 | 
				
			||||||
                    order_by=Deal.lexorank
 | 
					                    order_by=Deal.lexorank
 | 
				
			||||||
                ).label('rank'),
 | 
					                ).label('rank'),
 | 
				
			||||||
                func.coalesce(products_quantity_subquery.c.total_quantity, 0)
 | 
					                func.coalesce(products_quantity_subquery.c.total_quantity, 0)
 | 
				
			||||||
@@ -208,7 +197,9 @@ class DealService(BaseService):
 | 
				
			|||||||
                selectinload(Deal.status_history),
 | 
					                selectinload(Deal.status_history),
 | 
				
			||||||
                joinedload(Deal.client),
 | 
					                joinedload(Deal.client),
 | 
				
			||||||
                joinedload(Deal.shipping_warehouse),
 | 
					                joinedload(Deal.shipping_warehouse),
 | 
				
			||||||
                joinedload(Deal.bill_request)
 | 
					                joinedload(Deal.bill_request),
 | 
				
			||||||
 | 
					                joinedload(Deal.status),
 | 
				
			||||||
 | 
					                joinedload(Deal.board),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .outerjoin(
 | 
					            .outerjoin(
 | 
				
			||||||
                price_subquery, Deal.id == price_subquery.c.deal_id,
 | 
					                price_subquery, Deal.id == price_subquery.c.deal_id,
 | 
				
			||||||
@@ -231,8 +222,6 @@ class DealService(BaseService):
 | 
				
			|||||||
        summaries = []
 | 
					        summaries = []
 | 
				
			||||||
        for deal, total_price, rank, products_count in deals_query.all():
 | 
					        for deal, total_price, rank, products_count in deals_query.all():
 | 
				
			||||||
            deal: Deal
 | 
					            deal: Deal
 | 
				
			||||||
            last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at)
 | 
					 | 
				
			||||||
            deadline = last_status.next_status_deadline
 | 
					 | 
				
			||||||
            base_marketplace = None
 | 
					            base_marketplace = None
 | 
				
			||||||
            if deal.base_marketplace:
 | 
					            if deal.base_marketplace:
 | 
				
			||||||
                base_marketplace = BaseMarketplaceSchema.model_validate(deal.base_marketplace)
 | 
					                base_marketplace = BaseMarketplaceSchema.model_validate(deal.base_marketplace)
 | 
				
			||||||
@@ -242,9 +231,8 @@ class DealService(BaseService):
 | 
				
			|||||||
                    id=deal.id,
 | 
					                    id=deal.id,
 | 
				
			||||||
                    client_name=deal.client.name,
 | 
					                    client_name=deal.client.name,
 | 
				
			||||||
                    name=deal.name,
 | 
					                    name=deal.name,
 | 
				
			||||||
                    changed_at=last_status.changed_at,
 | 
					                    status=deal.status,
 | 
				
			||||||
                    deadline=deadline,
 | 
					                    board=deal.board,
 | 
				
			||||||
                    status=last_status.to_status,
 | 
					 | 
				
			||||||
                    total_price=total_price,
 | 
					                    total_price=total_price,
 | 
				
			||||||
                    rank=rank,
 | 
					                    rank=rank,
 | 
				
			||||||
                    base_marketplace=base_marketplace,
 | 
					                    base_marketplace=base_marketplace,
 | 
				
			||||||
@@ -308,6 +296,9 @@ class DealService(BaseService):
 | 
				
			|||||||
        deal = await self.session.scalar(
 | 
					        deal = await self.session.scalar(
 | 
				
			||||||
            select(Deal)
 | 
					            select(Deal)
 | 
				
			||||||
            .options(
 | 
					            .options(
 | 
				
			||||||
 | 
					                joinedload(Deal.status),
 | 
				
			||||||
 | 
					                joinedload(Deal.board)
 | 
				
			||||||
 | 
					                .joinedload(Board.project),
 | 
				
			||||||
                joinedload(Deal.shipping_warehouse),
 | 
					                joinedload(Deal.shipping_warehouse),
 | 
				
			||||||
                joinedload(Deal.client)
 | 
					                joinedload(Deal.client)
 | 
				
			||||||
                .joinedload(Client.details),
 | 
					                .joinedload(Client.details),
 | 
				
			||||||
@@ -350,7 +341,7 @@ class DealService(BaseService):
 | 
				
			|||||||
            raise HTTPException(status_code=404, detail="Сделка не найдена")
 | 
					            raise HTTPException(status_code=404, detail="Сделка не найдена")
 | 
				
			||||||
        return DealSchema.model_validate(deal)
 | 
					        return DealSchema.model_validate(deal)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def update_general_info(self, request: DealUpdateGeneralInfoRequest) -> DealUpdateGeneralInfoResponse:
 | 
					    async def update_general_info(self, request: DealUpdateGeneralInfoRequest, user: User) -> DealUpdateGeneralInfoResponse:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            deal: Deal = await self.session.scalar(
 | 
					            deal: Deal = await self.session.scalar(
 | 
				
			||||||
                select(Deal)
 | 
					                select(Deal)
 | 
				
			||||||
@@ -369,6 +360,15 @@ class DealService(BaseService):
 | 
				
			|||||||
            deal.delivery_date = request.data.delivery_date
 | 
					            deal.delivery_date = request.data.delivery_date
 | 
				
			||||||
            deal.receiving_slot_date = request.data.receiving_slot_date
 | 
					            deal.receiving_slot_date = request.data.receiving_slot_date
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if deal.board_id != request.data.board.id or deal.current_status_id != request.data.status.id:
 | 
				
			||||||
 | 
					                if deal.group:
 | 
				
			||||||
 | 
					                    for deal in deal.group.deals:
 | 
				
			||||||
 | 
					                        deal.board_id = request.data.board.id
 | 
				
			||||||
 | 
					                        await self.change_status(deal, request.data.status.id, user)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    deal.board_id = request.data.board.id
 | 
				
			||||||
 | 
					                    await self.change_status(deal, request.data.status.id, user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if deal.group:
 | 
					            if deal.group:
 | 
				
			||||||
                for deal in deal.group.deals:
 | 
					                for deal in deal.group.deals:
 | 
				
			||||||
                    deal.is_accounted = request.data.is_accounted
 | 
					                    deal.is_accounted = request.data.is_accounted
 | 
				
			||||||
@@ -402,7 +402,7 @@ class DealService(BaseService):
 | 
				
			|||||||
        stmt = (
 | 
					        stmt = (
 | 
				
			||||||
            select(Deal)
 | 
					            select(Deal)
 | 
				
			||||||
            .where(
 | 
					            .where(
 | 
				
			||||||
                Deal.current_status == request.status,
 | 
					                Deal.current_status_id == request.status_id,
 | 
				
			||||||
                Deal.id != request.deal_id,
 | 
					                Deal.id != request.deal_id,
 | 
				
			||||||
                Deal.is_deleted == False,
 | 
					                Deal.is_deleted == False,
 | 
				
			||||||
                Deal.is_completed == False
 | 
					                Deal.is_completed == False
 | 
				
			||||||
@@ -433,7 +433,7 @@ class DealService(BaseService):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            new_rank = lexorank.middle(lexorank.Bucket.BUCEKT_0)
 | 
					            new_rank = lexorank.middle(lexorank.Bucket.BUCEKT_0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await self.change_status(deal, request.status, user,
 | 
					        await self.change_status(deal, request.status_id, user,
 | 
				
			||||||
                                 deadline=request.deadline,
 | 
					                                 deadline=request.deadline,
 | 
				
			||||||
                                 comment=request.comment,
 | 
					                                 comment=request.comment,
 | 
				
			||||||
                                 rank=str(new_rank))
 | 
					                                 rank=str(new_rank))
 | 
				
			||||||
@@ -1079,12 +1079,9 @@ class DealService(BaseService):
 | 
				
			|||||||
                return DealCompleteResponse(ok=False, message="Сделка не найдена")
 | 
					                return DealCompleteResponse(ok=False, message="Сделка не найдена")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if deal.group:
 | 
					            if deal.group:
 | 
				
			||||||
                deals = await DealGroupService(self.session).complete_group(deal.group.id)
 | 
					                await deal_group.DealGroupService(self.session).complete_group(deal.group.id)
 | 
				
			||||||
                for completed_deal in deals:
 | 
					 | 
				
			||||||
                    await self.change_status(completed_deal, DealStatus.COMPLETED, user)
 | 
					 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                deal.is_completed = True
 | 
					                deal.is_completed = True
 | 
				
			||||||
                await self.change_status(deal, DealStatus.COMPLETED, user)
 | 
					 | 
				
			||||||
            await self.session.commit()
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return DealCompleteResponse(ok=True, message="Сделка успешно завершена")
 | 
					            return DealCompleteResponse(ok=True, message="Сделка успешно завершена")
 | 
				
			||||||
@@ -1234,14 +1231,15 @@ class DealService(BaseService):
 | 
				
			|||||||
    async def _create_deal_from_excel(
 | 
					    async def _create_deal_from_excel(
 | 
				
			||||||
            self,
 | 
					            self,
 | 
				
			||||||
            client: Client,
 | 
					            client: Client,
 | 
				
			||||||
 | 
					            deal_status: DealStatus,
 | 
				
			||||||
            breakdown: CityBreakdownFromExcelSchema,
 | 
					            breakdown: CityBreakdownFromExcelSchema,
 | 
				
			||||||
            user: User,
 | 
					            user: User,
 | 
				
			||||||
    ) -> Deal:
 | 
					    ) -> Deal:
 | 
				
			||||||
        rank = await self._get_rank_for_deal(DealStatus.CREATED)
 | 
					        rank = await self._get_rank_for_deal(deal_status.id)
 | 
				
			||||||
        deal = Deal(
 | 
					        deal = Deal(
 | 
				
			||||||
            name=f"{client.name} - {breakdown.base_marketplace.key.upper()} - {breakdown.shipping_warehouse.name}",
 | 
					            name=f"{client.name} - {breakdown.base_marketplace.key.upper()} - {breakdown.shipping_warehouse.name}",
 | 
				
			||||||
            created_at=datetime.datetime.now(),
 | 
					            created_at=datetime.datetime.now(),
 | 
				
			||||||
            current_status=DealStatus.CREATED,
 | 
					            current_status_id=deal_status.id,
 | 
				
			||||||
            lexorank=rank,
 | 
					            lexorank=rank,
 | 
				
			||||||
            client_id=client.id,
 | 
					            client_id=client.id,
 | 
				
			||||||
            base_marketplace_key=breakdown.base_marketplace.key,
 | 
					            base_marketplace_key=breakdown.base_marketplace.key,
 | 
				
			||||||
@@ -1250,19 +1248,8 @@ class DealService(BaseService):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.session.add(deal)
 | 
					        self.session.add(deal)
 | 
				
			||||||
        await self.session.flush()
 | 
					        await self.session.flush()
 | 
				
			||||||
        await self.change_status(deal, DealStatus.AWAITING_ACCEPTANCE, user)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return deal
 | 
					        return deal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def _create_group(self) -> DealGroup:
 | 
					 | 
				
			||||||
        group = models.DealGroup(
 | 
					 | 
				
			||||||
            name='',
 | 
					 | 
				
			||||||
            lexorank=lexorank.middle(lexorank.Bucket.BUCEKT_0).__str__(),
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self.session.add(group)
 | 
					 | 
				
			||||||
        await self.session.flush()
 | 
					 | 
				
			||||||
        return group
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def _get_or_create_warehouse(
 | 
					    async def _get_or_create_warehouse(
 | 
				
			||||||
            self,
 | 
					            self,
 | 
				
			||||||
            shipping_warehouse: OptionalShippingWarehouseSchema,
 | 
					            shipping_warehouse: OptionalShippingWarehouseSchema,
 | 
				
			||||||
@@ -1288,8 +1275,12 @@ class DealService(BaseService):
 | 
				
			|||||||
        if not client:
 | 
					        if not client:
 | 
				
			||||||
            return CreateDealsFromExcelResponse(ok=False, message=f"Клиент с ID {request.client_id} не найден")
 | 
					            return CreateDealsFromExcelResponse(ok=False, message=f"Клиент с ID {request.client_id} не найден")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        deal_status: Optional[DealStatus] = await self.session.get(DealStatus, request.status_id)
 | 
				
			||||||
 | 
					        if not deal_status:
 | 
				
			||||||
 | 
					            return CreateDealsFromExcelResponse(ok=False, message=f"Статус с ID {request.status_id} не найден")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        deals_dict: dict[str, Deal] = {}
 | 
					        deals_dict: dict[str, Deal] = {}
 | 
				
			||||||
        group = await self._create_group()
 | 
					        group = await deal_group.DealGroupService(self.session).create_group_model()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for product_data in request.products:
 | 
					        for product_data in request.products:
 | 
				
			||||||
            for breakdown in product_data.cities_breakdown:
 | 
					            for breakdown in product_data.cities_breakdown:
 | 
				
			||||||
@@ -1298,7 +1289,7 @@ class DealService(BaseService):
 | 
				
			|||||||
                key = f"{breakdown.shipping_warehouse.id} - {breakdown.base_marketplace.key}"
 | 
					                key = f"{breakdown.shipping_warehouse.id} - {breakdown.base_marketplace.key}"
 | 
				
			||||||
                deal = deals_dict.get(key)
 | 
					                deal = deals_dict.get(key)
 | 
				
			||||||
                if not deal:
 | 
					                if not deal:
 | 
				
			||||||
                    deal = await self._create_deal_from_excel(client, breakdown, user)
 | 
					                    deal = await self._create_deal_from_excel(client, deal_status, breakdown, user)
 | 
				
			||||||
                    deals_dict[key] = deal
 | 
					                    deals_dict[key] = deal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    insert_stmt = insert(deal_relations).values({
 | 
					                    insert_stmt = insert(deal_relations).values({
 | 
				
			||||||
@@ -1317,121 +1308,6 @@ class DealService(BaseService):
 | 
				
			|||||||
        await self.session.commit()
 | 
					        await self.session.commit()
 | 
				
			||||||
        return CreateDealsFromExcelResponse(ok=True, message="Сделки успешно созданы")
 | 
					        return CreateDealsFromExcelResponse(ok=True, message="Сделки успешно созданы")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def add_to_group(self, user: User, request: DealAddToGroupRequest) -> DealAddToGroupResponse:
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            group_bill_request = await self.session.get(GroupBillRequest, request.group_id)
 | 
					 | 
				
			||||||
            if group_bill_request:
 | 
					 | 
				
			||||||
                raise Exception("Нельзя добавить сделку, так как на группу выставлен счёт.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # changing status if needed
 | 
					 | 
				
			||||||
            deal_id = await self.session.scalar(
 | 
					 | 
				
			||||||
                select(deal_relations.c.deal_id)
 | 
					 | 
				
			||||||
                .where(deal_relations.c.group_id == request.group_id)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            group_deal_status = await self.session.scalar(
 | 
					 | 
				
			||||||
                select(Deal.current_status)
 | 
					 | 
				
			||||||
                .where(Deal.id == deal_id)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            request_deal = await self.session.scalar(
 | 
					 | 
				
			||||||
                select(Deal).where(Deal.id == request.deal_id)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            if group_deal_status != request_deal.current_status:
 | 
					 | 
				
			||||||
                await self.change_status(request_deal, group_deal_status, user)
 | 
					 | 
				
			||||||
            insert_stmt = insert(deal_relations).values({
 | 
					 | 
				
			||||||
                'deal_id': request.deal_id,
 | 
					 | 
				
			||||||
                'group_id': request.group_id
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            await self.session.execute(insert_stmt)
 | 
					 | 
				
			||||||
            await self.session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return DealAddToGroupResponse(ok=True, message="Сделка успешно добавлена в группу")
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            await self.session.rollback()
 | 
					 | 
				
			||||||
            return DealAddToGroupResponse(ok=False, message=str(e))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def create_group(self, user: User, request: DealCreateGroupRequest) -> DealCreateGroupResponse:
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            # getting lexorank for grop
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            group = await self._create_group()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for deal_id in [request.dragging_deal_id, request.hovered_deal_id]:
 | 
					 | 
				
			||||||
                insert_stmt = insert(deal_relations).values({
 | 
					 | 
				
			||||||
                    'deal_id': deal_id,
 | 
					 | 
				
			||||||
                    'group_id': group.id
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                await self.session.execute(insert_stmt)
 | 
					 | 
				
			||||||
            # changing status if needed on draggable deal
 | 
					 | 
				
			||||||
            dragging_deal = await self.session.scalar(
 | 
					 | 
				
			||||||
                select(Deal).where(Deal.id == request.dragging_deal_id)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            dropped_deal = await self.session.scalar(
 | 
					 | 
				
			||||||
                select(Deal).where(Deal.id == request.hovered_deal_id)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            if dragging_deal.current_status != dropped_deal.current_status:
 | 
					 | 
				
			||||||
                await self.change_status(dragging_deal, DealStatus(dropped_deal.current_status), user)
 | 
					 | 
				
			||||||
            await self.session.commit()
 | 
					 | 
				
			||||||
            return DealCreateGroupResponse(ok=True, message="Группа успешно создана")
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            return DealCreateGroupResponse(ok=False, message=str(e))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def remove_from_group(self, request: DealRemoveFromGroupRequest) -> DealRemoveFromGroupResponse:
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            delete_stmt = (
 | 
					 | 
				
			||||||
                delete(deal_relations)
 | 
					 | 
				
			||||||
                .where(
 | 
					 | 
				
			||||||
                    deal_relations.c.deal_id == request.deal_id,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            await self.session.execute(delete_stmt)
 | 
					 | 
				
			||||||
            await self.session.commit()
 | 
					 | 
				
			||||||
            return DealRemoveFromGroupResponse(ok=True, message="Сделка успешно удалена из группы")
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            await self.session.rollback()
 | 
					 | 
				
			||||||
            return DealRemoveFromGroupResponse(ok=False, message=str(e))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def update_group(self, request: DealGroupUpdateRequest) -> DealGroupUpdateResponse:
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            group = await self.session.scalar(
 | 
					 | 
				
			||||||
                select(models.DealGroup).where(models.DealGroup.id == request.data.id)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            if not group:
 | 
					 | 
				
			||||||
                return DealGroupUpdateResponse(ok=False, message="Группа не найдена")
 | 
					 | 
				
			||||||
            # update by dictionary
 | 
					 | 
				
			||||||
            request_dict = request.data.dict()
 | 
					 | 
				
			||||||
            update_stmt = (
 | 
					 | 
				
			||||||
                update(
 | 
					 | 
				
			||||||
                    models.DealGroup
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .where(models.DealGroup.id == request.data.id)
 | 
					 | 
				
			||||||
                .values(**request_dict)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            await self.session.execute(update_stmt)
 | 
					 | 
				
			||||||
            await self.session.commit()
 | 
					 | 
				
			||||||
            return DealGroupUpdateResponse(ok=True, message="Группа успешно обновлена")
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            await self.session.rollback()
 | 
					 | 
				
			||||||
            return DealGroupUpdateResponse(ok=False, message=str(e))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def change_group_status(self, user: User,
 | 
					 | 
				
			||||||
                                  request: DealGroupChangeStatusRequest) -> DealGroupChangeStatusResponse:
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            # getting all deals in group
 | 
					 | 
				
			||||||
            deals = await self.session.scalars(
 | 
					 | 
				
			||||||
                select(deal_relations.c.deal_id)
 | 
					 | 
				
			||||||
                .where(deal_relations.c.group_id == request.group_id)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            for deal_id in deals:
 | 
					 | 
				
			||||||
                deal = await self.session.scalar(
 | 
					 | 
				
			||||||
                    select(Deal).where(Deal.id == deal_id)
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                await self.change_status(deal, DealStatus(request.new_status), user)
 | 
					 | 
				
			||||||
            await self.session.commit()
 | 
					 | 
				
			||||||
            return DealGroupChangeStatusResponse(ok=True, message="Статус группы успешно изменен")
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            await self.session.rollback()
 | 
					 | 
				
			||||||
            return DealGroupChangeStatusResponse(ok=False, message=str(e))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def get_deals_grouped(self, deal: models.Deal) -> List[models.Deal]:
 | 
					    async def get_deals_grouped(self, deal: models.Deal) -> List[models.Deal]:
 | 
				
			||||||
        if not deal.group:
 | 
					        if not deal.group:
 | 
				
			||||||
            return [deal]
 | 
					            return [deal]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,12 @@
 | 
				
			|||||||
from sqlalchemy import select
 | 
					from lexorank import lexorank
 | 
				
			||||||
 | 
					from sqlalchemy import select, insert, update, delete
 | 
				
			||||||
from sqlalchemy.orm import selectinload
 | 
					from sqlalchemy.orm import selectinload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from models import DealService as DealServiceModel
 | 
					from models import DealService as DealServiceModel, User, Deal, DealProduct, Product, GroupBillRequest
 | 
				
			||||||
from models.deal_group import DealGroup
 | 
					from models.deal_group import DealGroup, deal_relations
 | 
				
			||||||
from schemas.deal import *
 | 
					from schemas.group import *
 | 
				
			||||||
from services.base import BaseService
 | 
					from services.base import BaseService
 | 
				
			||||||
 | 
					from services.deal import DealService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealGroupService(BaseService):
 | 
					class DealGroupService(BaseService):
 | 
				
			||||||
@@ -25,6 +27,63 @@ class DealGroupService(BaseService):
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
        return group.deals if group else []
 | 
					        return group.deals if group else []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def create_group_model(self) -> DealGroup:
 | 
				
			||||||
 | 
					        group = DealGroup(
 | 
				
			||||||
 | 
					            name='',
 | 
				
			||||||
 | 
					            lexorank=lexorank.middle(lexorank.Bucket.BUCEKT_0).__str__(),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.session.add(group)
 | 
				
			||||||
 | 
					        await self.session.flush()
 | 
				
			||||||
 | 
					        return group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def create_group(self, user: User, request: DealCreateGroupRequest) -> DealCreateGroupResponse:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            group = await self.create_group_model()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for deal_id in [request.dragging_deal_id, request.hovered_deal_id]:
 | 
				
			||||||
 | 
					                insert_stmt = insert(deal_relations).values({
 | 
				
			||||||
 | 
					                    'deal_id': deal_id,
 | 
				
			||||||
 | 
					                    'group_id': group.id
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                await self.session.execute(insert_stmt)
 | 
				
			||||||
 | 
					            # changing status if needed on draggable deal
 | 
				
			||||||
 | 
					            dragging_deal = await self.session.scalar(
 | 
				
			||||||
 | 
					                select(Deal).where(Deal.id == request.dragging_deal_id)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            dropped_deal = await self.session.scalar(
 | 
				
			||||||
 | 
					                select(Deal).where(Deal.id == request.hovered_deal_id)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            if dragging_deal.current_status_id != dropped_deal.current_status_id:
 | 
				
			||||||
 | 
					                deal_service = DealService(self.session)
 | 
				
			||||||
 | 
					                await deal_service.change_status(dragging_deal, dropped_deal.current_status_id, user)
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					            return DealCreateGroupResponse(ok=True, message="Группа успешно создана")
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            return DealCreateGroupResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def update_group(self, request: DealGroupUpdateRequest) -> DealGroupUpdateResponse:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            group = await self.session.scalar(
 | 
				
			||||||
 | 
					                select(DealGroup).where(DealGroup.id == request.data.id)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            if not group:
 | 
				
			||||||
 | 
					                return DealGroupUpdateResponse(ok=False, message="Группа не найдена")
 | 
				
			||||||
 | 
					            # update by dictionary
 | 
				
			||||||
 | 
					            request_dict = request.data.model_dump()
 | 
				
			||||||
 | 
					            request_dict.pop("bill_request", None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            update_stmt = (
 | 
				
			||||||
 | 
					                update(DealGroup)
 | 
				
			||||||
 | 
					                .where(DealGroup.id == request.data.id)
 | 
				
			||||||
 | 
					                .values(**request_dict)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            await self.session.execute(update_stmt)
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					            return DealGroupUpdateResponse(ok=True, message="Группа успешно обновлена")
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            await self.session.rollback()
 | 
				
			||||||
 | 
					            return DealGroupUpdateResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def complete_group(self, group_id: int) -> list[Deal]:
 | 
					    async def complete_group(self, group_id: int) -> list[Deal]:
 | 
				
			||||||
        deals = await self.get_deals_by_group_id(group_id)
 | 
					        deals = await self.get_deals_by_group_id(group_id)
 | 
				
			||||||
        for deal in deals:
 | 
					        for deal in deals:
 | 
				
			||||||
@@ -36,3 +95,74 @@ class DealGroupService(BaseService):
 | 
				
			|||||||
        for deal in deals:
 | 
					        for deal in deals:
 | 
				
			||||||
            deal.is_deleted = True
 | 
					            deal.is_deleted = True
 | 
				
			||||||
        await self.session.commit()
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def change_group_status(
 | 
				
			||||||
 | 
					            self,
 | 
				
			||||||
 | 
					            user: User,
 | 
				
			||||||
 | 
					            request: DealGroupChangeStatusRequest,
 | 
				
			||||||
 | 
					    ) -> DealGroupChangeStatusResponse:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # getting all deals in group
 | 
				
			||||||
 | 
					            deals = await self.session.scalars(
 | 
				
			||||||
 | 
					                select(deal_relations.c.deal_id)
 | 
				
			||||||
 | 
					                .where(deal_relations.c.group_id == request.group_id)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            deal_service = DealService(self.session)
 | 
				
			||||||
 | 
					            for deal_id in deals:
 | 
				
			||||||
 | 
					                deal = await self.session.scalar(
 | 
				
			||||||
 | 
					                    select(Deal).where(Deal.id == deal_id)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                await deal_service.change_status(deal, request.new_status, user)
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					            return DealGroupChangeStatusResponse(ok=True, message="Статус группы успешно изменен")
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            await self.session.rollback()
 | 
				
			||||||
 | 
					            return DealGroupChangeStatusResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def add_deal(self, user: User, request: DealAddToGroupRequest) -> DealAddToGroupResponse:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            group_bill_request = await self.session.get(GroupBillRequest, request.group_id)
 | 
				
			||||||
 | 
					            if group_bill_request:
 | 
				
			||||||
 | 
					                raise Exception("Нельзя добавить сделку, так как на группу выставлен счёт.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # changing status if needed
 | 
				
			||||||
 | 
					            deal_id = await self.session.scalar(
 | 
				
			||||||
 | 
					                select(deal_relations.c.deal_id)
 | 
				
			||||||
 | 
					                .where(deal_relations.c.group_id == request.group_id)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            group_deal_status = await self.session.scalar(
 | 
				
			||||||
 | 
					                select(Deal.current_status_id)
 | 
				
			||||||
 | 
					                .where(Deal.id == deal_id)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            request_deal = await self.session.scalar(
 | 
				
			||||||
 | 
					                select(Deal).where(Deal.id == request.deal_id)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            if group_deal_status != request_deal.current_status_id:
 | 
				
			||||||
 | 
					                await DealService(self.session).change_status(request_deal, group_deal_status, user)
 | 
				
			||||||
 | 
					            insert_stmt = insert(deal_relations).values({
 | 
				
			||||||
 | 
					                'deal_id': request.deal_id,
 | 
				
			||||||
 | 
					                'group_id': request.group_id
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            await self.session.execute(insert_stmt)
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return DealAddToGroupResponse(ok=True, message="Сделка успешно добавлена в группу")
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            await self.session.rollback()
 | 
				
			||||||
 | 
					            return DealAddToGroupResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def remove_deal(self, request: DealRemoveFromGroupRequest) -> DealRemoveFromGroupResponse:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            delete_stmt = (
 | 
				
			||||||
 | 
					                delete(deal_relations)
 | 
				
			||||||
 | 
					                .where(
 | 
				
			||||||
 | 
					                    deal_relations.c.deal_id == request.deal_id,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            await self.session.execute(delete_stmt)
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					            return DealRemoveFromGroupResponse(ok=True, message="Сделка успешно удалена из группы")
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            await self.session.rollback()
 | 
				
			||||||
 | 
					            return DealRemoveFromGroupResponse(ok=False, message=str(e))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										73
									
								
								services/project.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								services/project.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
				
			|||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sqlalchemy import select, update, func, delete, and_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from models import Project, Board
 | 
				
			||||||
 | 
					from schemas.project import *
 | 
				
			||||||
 | 
					from services.base import BaseService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProjectService(BaseService):
 | 
				
			||||||
 | 
					    async def get_projects(self) -> GetProjectsResponse:
 | 
				
			||||||
 | 
					        boards_sub = (
 | 
				
			||||||
 | 
					            select(Board)
 | 
				
			||||||
 | 
					            .where(Board.is_deleted == False)
 | 
				
			||||||
 | 
					            .subquery()
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        stmt = (
 | 
				
			||||||
 | 
					            select(
 | 
				
			||||||
 | 
					                Project.id,
 | 
				
			||||||
 | 
					                Project.name,
 | 
				
			||||||
 | 
					                func.count(boards_sub.c.id)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .join(boards_sub, Project.id == boards_sub.c.project_id, isouter=True)
 | 
				
			||||||
 | 
					            .where(Project.is_deleted == False)
 | 
				
			||||||
 | 
					            .group_by(Project.id, Project.name)
 | 
				
			||||||
 | 
					            .order_by(Project.name)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        project_data = (await self.session.execute(stmt)).all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        projects = []
 | 
				
			||||||
 | 
					        for project_id, name, boards_count in project_data:
 | 
				
			||||||
 | 
					            project = ProjectSchemaWithCount(id=project_id, name=name, boards_count=boards_count)
 | 
				
			||||||
 | 
					            projects.append(project)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return GetProjectsResponse(projects=projects)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def create_project(self, request: CreateProjectRequest) -> CreateProjectResponse:
 | 
				
			||||||
 | 
					        project = Project(
 | 
				
			||||||
 | 
					            name=request.project.name,
 | 
				
			||||||
 | 
					            created_at=datetime.now(),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.session.add(project)
 | 
				
			||||||
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					        return UpdateProjectResponse(ok=True, message="Проект успешно создан")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def update_project(self, request: UpdateProjectRequest) -> UpdateProjectResponse:
 | 
				
			||||||
 | 
					        stmt = (
 | 
				
			||||||
 | 
					            update(Project)
 | 
				
			||||||
 | 
					            .where(Project.id == request.project.id)
 | 
				
			||||||
 | 
					            .values(name=request.project.name)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await self.session.execute(stmt)
 | 
				
			||||||
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return UpdateProjectResponse(ok=True, message="Проект успешно изменен")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def delete_project(self, project_id: int) -> DeleteProjectResponse:
 | 
				
			||||||
 | 
					        stmt_boards = select(Board).where(Board.project_id == project_id)
 | 
				
			||||||
 | 
					        boards = (await self.session.scalars(stmt_boards)).all()
 | 
				
			||||||
 | 
					        if len(boards) == 0:
 | 
				
			||||||
 | 
					            stmt = (
 | 
				
			||||||
 | 
					                delete(Project)
 | 
				
			||||||
 | 
					                .where(Project.id == project_id)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            stmt = (
 | 
				
			||||||
 | 
					                update(Project)
 | 
				
			||||||
 | 
					                .where(Project.id == project_id)
 | 
				
			||||||
 | 
					                .values(is_deleted=True)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        await self.session.execute(stmt)
 | 
				
			||||||
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					        return DeleteProjectResponse(ok=True, message="Проект успешно удален")
 | 
				
			||||||
@@ -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 DealService, Deal, DealStatusHistory, DealProductService, DealProduct, Service, Client, \
 | 
					from models import DealService, Deal, DealStatusHistory, DealProductService, DealProduct, Service, Client, \
 | 
				
			||||||
    ShippingWarehouse, BaseMarketplace, User
 | 
					    ShippingWarehouse, BaseMarketplace, User, Project, Board
 | 
				
			||||||
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
 | 
				
			||||||
@@ -24,7 +24,7 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
                    'day',
 | 
					                    'day',
 | 
				
			||||||
                    Deal.created_at,
 | 
					                    Deal.created_at,
 | 
				
			||||||
                ).label('date'),
 | 
					                ).label('date'),
 | 
				
			||||||
                Deal.current_status,
 | 
					                Deal.current_status_id,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .subquery()
 | 
					            .subquery()
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@@ -51,7 +51,7 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
                    'day',
 | 
					                    'day',
 | 
				
			||||||
                    last_statuses.c.changed_at,
 | 
					                    last_statuses.c.changed_at,
 | 
				
			||||||
                ).label('date'),
 | 
					                ).label('date'),
 | 
				
			||||||
                Deal.current_status,
 | 
					                Deal.current_status_id,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .join(last_statuses, last_statuses.c.deal_id == Deal.id)
 | 
					            .join(last_statuses, last_statuses.c.deal_id == Deal.id)
 | 
				
			||||||
            .subquery()
 | 
					            .subquery()
 | 
				
			||||||
@@ -97,8 +97,7 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return data
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    def _get_stmt_deal_services(self, sub_filtered_status_history: Subquery):
 | 
				
			||||||
    def _get_stmt_deal_services(sub_filtered_status_history: Subquery):
 | 
					 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            select(
 | 
					            select(
 | 
				
			||||||
                Deal.id.label("deal_id"),
 | 
					                Deal.id.label("deal_id"),
 | 
				
			||||||
@@ -112,10 +111,24 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
            .join(DealService, Deal.id == DealService.deal_id)
 | 
					            .join(DealService, Deal.id == DealService.deal_id)
 | 
				
			||||||
            .join(Service, DealService.service_id == Service.id)
 | 
					            .join(Service, DealService.service_id == Service.id)
 | 
				
			||||||
            .join(sub_filtered_status_history, Deal.id == sub_filtered_status_history.c.deal_id)
 | 
					            .join(sub_filtered_status_history, Deal.id == sub_filtered_status_history.c.deal_id)
 | 
				
			||||||
            .where(and_(Deal.is_deleted == False, Deal.is_accounted == True))
 | 
					            .where(
 | 
				
			||||||
 | 
					                and_(
 | 
				
			||||||
 | 
					                    Deal.is_deleted == False,
 | 
				
			||||||
 | 
					                    Deal.is_accounted == True,
 | 
				
			||||||
 | 
					                    Deal.is_completed == True if self.is_completed_only else True
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            .group_by(Deal.id, "date")
 | 
					            .group_by(Deal.id, "date")
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def _board_ids_for_project(project_id: int, stmt):
 | 
				
			||||||
 | 
					        board_ids_stmt = (
 | 
				
			||||||
 | 
					            select(Board.id)
 | 
				
			||||||
 | 
					            .where(Board.project_id == project_id)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return stmt.where(Deal.board_id.in_(board_ids_stmt))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def _apply_filters(request: CommonProfitFilters, stmt_deal_services, stmt_deal_product_services):
 | 
					    def _apply_filters(request: CommonProfitFilters, stmt_deal_services, stmt_deal_product_services):
 | 
				
			||||||
        if request.client_id != -1:
 | 
					        if request.client_id != -1:
 | 
				
			||||||
@@ -127,9 +140,21 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
            stmt_deal_product_services = stmt_deal_product_services.where(
 | 
					            stmt_deal_product_services = stmt_deal_product_services.where(
 | 
				
			||||||
                Deal.base_marketplace_key == request.base_marketplace_key)
 | 
					                Deal.base_marketplace_key == request.base_marketplace_key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if request.deal_status_id != -1:
 | 
					        if request.project_id != -1:
 | 
				
			||||||
            stmt_deal_services = stmt_deal_services.where(Deal.current_status == request.deal_status_id)
 | 
					            stmt_deal_services = ProfitStatisticsService._board_ids_for_project(request.project_id, stmt_deal_services)
 | 
				
			||||||
            stmt_deal_product_services = stmt_deal_product_services.where(Deal.current_status == request.deal_status_id)
 | 
					            stmt_deal_product_services = ProfitStatisticsService._board_ids_for_project(
 | 
				
			||||||
 | 
					                request.project_id,
 | 
				
			||||||
 | 
					                stmt_deal_product_services,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if request.board_id != -1:
 | 
				
			||||||
 | 
					                stmt_deal_services = stmt_deal_services.where(Deal.board_id == request.board_id)
 | 
				
			||||||
 | 
					                stmt_deal_product_services = stmt_deal_product_services.where(Deal.board_id == request.board_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if request.deal_status_id != -1:
 | 
				
			||||||
 | 
					                    stmt_deal_services = stmt_deal_services.where(Deal.current_status_id == request.deal_status_id)
 | 
				
			||||||
 | 
					                    stmt_deal_product_services = stmt_deal_product_services.where(
 | 
				
			||||||
 | 
					                        Deal.current_status_id == request.deal_status_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if request.manager_id != -1:
 | 
					        if request.manager_id != -1:
 | 
				
			||||||
            stmt_deal_services = stmt_deal_services.where(Deal.manager_id == request.manager_id)
 | 
					            stmt_deal_services = stmt_deal_services.where(Deal.manager_id == request.manager_id)
 | 
				
			||||||
@@ -137,8 +162,7 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return stmt_deal_services, stmt_deal_product_services
 | 
					        return stmt_deal_services, stmt_deal_product_services
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    def _get_stmt_product_services(self):
 | 
				
			||||||
    def _get_stmt_product_services():
 | 
					 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            select(
 | 
					            select(
 | 
				
			||||||
                Deal.id.label("deal_id"),
 | 
					                Deal.id.label("deal_id"),
 | 
				
			||||||
@@ -154,7 +178,13 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
                )
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .join(Service, DealProductService.service_id == Service.id)
 | 
					            .join(Service, DealProductService.service_id == Service.id)
 | 
				
			||||||
            .where(and_(Deal.is_deleted == False, Deal.is_accounted == True))
 | 
					            .where(
 | 
				
			||||||
 | 
					                and_(
 | 
				
			||||||
 | 
					                    Deal.is_deleted == False,
 | 
				
			||||||
 | 
					                    Deal.is_accounted == True,
 | 
				
			||||||
 | 
					                    Deal.is_completed == True if self.is_completed_only else True,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            .group_by(Deal.id)
 | 
					            .group_by(Deal.id)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -226,16 +256,47 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def _join_and_group_by_statuses(stmt):
 | 
					    def _join_and_group_by_projects(stmt):
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            select(
 | 
					            select(
 | 
				
			||||||
                Deal.current_status.label("grouped_value"),
 | 
					                Project.id,
 | 
				
			||||||
 | 
					                Project.name.label("grouped_value"),
 | 
				
			||||||
                func.count(stmt.c.deal_id).label("deals_count"),
 | 
					                func.count(stmt.c.deal_id).label("deals_count"),
 | 
				
			||||||
                func.sum(stmt.c.revenue).label("revenue"),
 | 
					                func.sum(stmt.c.revenue).label("revenue"),
 | 
				
			||||||
                func.sum(stmt.c.profit).label("profit"),
 | 
					                func.sum(stmt.c.profit).label("profit"),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .join(Deal, Deal.id == stmt.c.deal_id)
 | 
					            .join(Deal, Deal.id == stmt.c.deal_id)
 | 
				
			||||||
            .group_by(Deal.current_status)
 | 
					            .join(Board, Board.id == Deal.board_id)
 | 
				
			||||||
 | 
					            .join(Project, Project.id == Board.project_id)
 | 
				
			||||||
 | 
					            .group_by(Project.id, Project.name)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def _join_and_group_by_boards(stmt):
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            select(
 | 
				
			||||||
 | 
					                Board.id,
 | 
				
			||||||
 | 
					                Board.name.label("grouped_value"),
 | 
				
			||||||
 | 
					                func.count(stmt.c.deal_id).label("deals_count"),
 | 
				
			||||||
 | 
					                func.sum(stmt.c.revenue).label("revenue"),
 | 
				
			||||||
 | 
					                func.sum(stmt.c.profit).label("profit"),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .join(Deal, Deal.id == stmt.c.deal_id)
 | 
				
			||||||
 | 
					            .join(Board, Board.id == Deal.board_id)
 | 
				
			||||||
 | 
					            .group_by(Board.id, Board.name)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def _join_and_group_by_statuses(stmt):
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            select(
 | 
				
			||||||
 | 
					                Deal.current_status_id.label("grouped_value"),
 | 
				
			||||||
 | 
					                func.count(stmt.c.deal_id).label("deals_count"),
 | 
				
			||||||
 | 
					                func.sum(stmt.c.revenue).label("revenue"),
 | 
				
			||||||
 | 
					                func.sum(stmt.c.profit).label("profit"),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .join(Deal, Deal.id == stmt.c.deal_id)
 | 
				
			||||||
 | 
					            .group_by(Deal.current_status_id)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
@@ -320,6 +381,7 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    async def _get_data_grouped_by_date(self, request: CommonProfitFilters, is_chart: bool = True):
 | 
					    async def _get_data_grouped_by_date(self, request: CommonProfitFilters, is_chart: bool = True):
 | 
				
			||||||
        self.date_from, self.date_to = request.date_range
 | 
					        self.date_from, self.date_to = request.date_range
 | 
				
			||||||
 | 
					        self.is_completed_only = request.is_completed_only
 | 
				
			||||||
        self.filters = request
 | 
					        self.filters = request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sub_deals_dates = self._get_deals_dates(request.deal_status_id)
 | 
					        sub_deals_dates = self._get_deals_dates(request.deal_status_id)
 | 
				
			||||||
@@ -399,6 +461,18 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return await self._table_data_from_stmt(stmt_grouped_by_clients)
 | 
					        return await self._table_data_from_stmt(stmt_grouped_by_clients)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _get_table_grouped_by_projects(self, request: GetProfitTableDataRequest) -> GetProfitTableDataResponse:
 | 
				
			||||||
 | 
					        sub_grouped_by_deals = self._get_common_table_grouped(request)
 | 
				
			||||||
 | 
					        stmt_grouped_by_projects = self._join_and_group_by_projects(sub_grouped_by_deals)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return await self._table_data_from_stmt(stmt_grouped_by_projects)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _get_table_grouped_by_boards(self, request: GetProfitTableDataRequest) -> GetProfitTableDataResponse:
 | 
				
			||||||
 | 
					        sub_grouped_by_deals = self._get_common_table_grouped(request)
 | 
				
			||||||
 | 
					        stmt_grouped_by_boards = self._join_and_group_by_boards(sub_grouped_by_deals)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return await self._table_data_from_stmt(stmt_grouped_by_boards)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -449,6 +523,10 @@ class ProfitStatisticsService(BaseService):
 | 
				
			|||||||
                return await self._get_table_grouped_by_dates(request)
 | 
					                return await self._get_table_grouped_by_dates(request)
 | 
				
			||||||
            case ProfitTableGroupBy.BY_CLIENTS:
 | 
					            case ProfitTableGroupBy.BY_CLIENTS:
 | 
				
			||||||
                return await self._get_table_grouped_by_clients(request)
 | 
					                return await self._get_table_grouped_by_clients(request)
 | 
				
			||||||
 | 
					            case ProfitTableGroupBy.BY_PROJECTS:
 | 
				
			||||||
 | 
					                return await self._get_table_grouped_by_projects(request)
 | 
				
			||||||
 | 
					            case ProfitTableGroupBy.BY_BOARDS:
 | 
				
			||||||
 | 
					                return await self._get_table_grouped_by_boards(request)
 | 
				
			||||||
            case ProfitTableGroupBy.BY_STATUSES:
 | 
					            case ProfitTableGroupBy.BY_STATUSES:
 | 
				
			||||||
                return await self._get_table_grouped_by_statuses(request)
 | 
					                return await self._get_table_grouped_by_statuses(request)
 | 
				
			||||||
            case ProfitTableGroupBy.BY_WAREHOUSES:
 | 
					            case ProfitTableGroupBy.BY_WAREHOUSES:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										128
									
								
								services/status.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								services/status.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
				
			|||||||
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sqlalchemy import select, and_, func
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from models import DealStatus, Deal
 | 
				
			||||||
 | 
					from schemas.status import *
 | 
				
			||||||
 | 
					from services.base import BaseService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StatusService(BaseService):
 | 
				
			||||||
 | 
					    async def _get_statuses_for_board(self, board_id: int) -> list[DealStatus]:
 | 
				
			||||||
 | 
					        stmt = (
 | 
				
			||||||
 | 
					            select(DealStatus)
 | 
				
			||||||
 | 
					            .where(
 | 
				
			||||||
 | 
					                and_(
 | 
				
			||||||
 | 
					                    DealStatus.board_id == board_id,
 | 
				
			||||||
 | 
					                    DealStatus.is_deleted == False,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .order_by(DealStatus.ordinal_number)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        statuses = (await self.session.scalars(stmt)).all()
 | 
				
			||||||
 | 
					        return list(statuses)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _get_status_by_id(self, status_id: int) -> Optional[DealStatus]:
 | 
				
			||||||
 | 
					        stmt = (
 | 
				
			||||||
 | 
					            select(DealStatus)
 | 
				
			||||||
 | 
					            .where(DealStatus.id == status_id)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        status = await self.session.scalar(stmt)
 | 
				
			||||||
 | 
					        return status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def create_status(self, request: CreateStatusRequest) -> CreateStatusResponse:
 | 
				
			||||||
 | 
					        statuses = await self._get_statuses_for_board(request.status.board_id)
 | 
				
			||||||
 | 
					        if len(statuses) == 0:
 | 
				
			||||||
 | 
					            ordinal_number = 1
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            statuses[-1].is_finishing = False
 | 
				
			||||||
 | 
					            ordinal_number = statuses[-1].ordinal_number + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        status = DealStatus(
 | 
				
			||||||
 | 
					            **request.status.model_dump(),
 | 
				
			||||||
 | 
					            ordinal_number=ordinal_number,
 | 
				
			||||||
 | 
					            is_finishing=True,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.session.add(status)
 | 
				
			||||||
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return CreateStatusResponse(ok=True, message="Статус успешно создан")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def update_status(self, request: UpdateStatusRequest) -> UpdateStatusResponse:
 | 
				
			||||||
 | 
					        status = await self._get_status_by_id(request.status.id)
 | 
				
			||||||
 | 
					        if not status:
 | 
				
			||||||
 | 
					            return UpdateStatusResponse(ok=False, message=f"Статус с ID {request.status.id} не найден")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        status.name = request.status.name
 | 
				
			||||||
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return UpdateStatusResponse(ok=True, message="Статус успешно изменен")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def update_status_order(self, request: UpdateStatusOrderRequest) -> UpdateStatusOrderResponse:
 | 
				
			||||||
 | 
					        statuses = await self._get_statuses_for_board(request.board_id)
 | 
				
			||||||
 | 
					        status_idx = 0
 | 
				
			||||||
 | 
					        while status_idx < len(statuses) and statuses[status_idx].id != request.status_id:
 | 
				
			||||||
 | 
					            status_idx += 1
 | 
				
			||||||
 | 
					        if status_idx == len(statuses):
 | 
				
			||||||
 | 
					            return UpdateStatusOrderResponse(ok=False, message=f"Статус с ID {request.status_id} не найден")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        status = statuses.pop(status_idx)
 | 
				
			||||||
 | 
					        statuses.insert(request.new_ordinal_number - 1, status)
 | 
				
			||||||
 | 
					        new_ordinal_number = 1
 | 
				
			||||||
 | 
					        for status in statuses:
 | 
				
			||||||
 | 
					            status.ordinal_number = new_ordinal_number
 | 
				
			||||||
 | 
					            status.is_finishing = False
 | 
				
			||||||
 | 
					            new_ordinal_number += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        statuses[-1].is_finishing = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					        return UpdateStatusOrderResponse(ok=True, message="Порядок статусов изменен")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _count_deals_in_progress(self, status_id: int) -> int:
 | 
				
			||||||
 | 
					        stmt = (
 | 
				
			||||||
 | 
					            select(func.count(Deal.id))
 | 
				
			||||||
 | 
					            .where(
 | 
				
			||||||
 | 
					                and_(
 | 
				
			||||||
 | 
					                    Deal.current_status_id == status_id,
 | 
				
			||||||
 | 
					                    Deal.is_deleted == False,
 | 
				
			||||||
 | 
					                    Deal.is_completed == False,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return (await self.session.scalars(stmt)).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _count_deals(self, status_id: int) -> int:
 | 
				
			||||||
 | 
					        stmt = (
 | 
				
			||||||
 | 
					            select(func.count(Deal.id))
 | 
				
			||||||
 | 
					            .where(Deal.current_status_id == status_id)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return (await self.session.scalars(stmt)).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _set_finishing_flag_to_prev_status(self, status: DealStatus):
 | 
				
			||||||
 | 
					        statuses = await self._get_statuses_for_board(status.board_id)
 | 
				
			||||||
 | 
					        if len(statuses) < 2:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        statuses[-2].is_finishing = True
 | 
				
			||||||
 | 
					        statuses[-1].is_finishing = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def delete_status(self, status_id: int) -> DeleteStatusResponse:
 | 
				
			||||||
 | 
					        status = await self._get_status_by_id(status_id)
 | 
				
			||||||
 | 
					        if not status:
 | 
				
			||||||
 | 
					            return DeleteStatusResponse(ok=False, message=f"Статус с ID {status_id} не найден")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        count_deals_in_progress = await self._count_deals_in_progress(status_id)
 | 
				
			||||||
 | 
					        if count_deals_in_progress != 0:
 | 
				
			||||||
 | 
					            return DeleteStatusResponse(ok=False, message="Нельзя удалить статус с активными сделками")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if status.is_finishing:
 | 
				
			||||||
 | 
					            await self._set_finishing_flag_to_prev_status(status)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        count_deals = await self._count_deals(status_id)
 | 
				
			||||||
 | 
					        if count_deals == 0:
 | 
				
			||||||
 | 
					            await self.session.delete(status)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            status.is_deleted = True
 | 
				
			||||||
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return DeleteStatusResponse(ok=True, message="Статус успешно удален")
 | 
				
			||||||
		Reference in New Issue
	
	Block a user