feat: assignment of employees to deals
This commit is contained in:
		@@ -9,7 +9,7 @@ from models.work_shifts import WorkShift
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
if TYPE_CHECKING:
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
    from models.payroll import PayRate, PaymentRecord
 | 
					    from models.payroll import PayRate, PaymentRecord
 | 
				
			||||||
    from models import Deal
 | 
					    from models import Deal, Assignment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
role_permissions = Table(
 | 
					role_permissions = Table(
 | 
				
			||||||
    'role_permissions',
 | 
					    'role_permissions',
 | 
				
			||||||
@@ -121,6 +121,11 @@ class User(BaseModel):
 | 
				
			|||||||
        cascade="all, delete-orphan"
 | 
					        cascade="all, delete-orphan"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assignments: Mapped[list['Assignment']] = relationship(
 | 
				
			||||||
 | 
					        back_populates='user',
 | 
				
			||||||
 | 
					        lazy='selectin'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Position(BaseModel):
 | 
					class Position(BaseModel):
 | 
				
			||||||
    __tablename__ = 'positions'
 | 
					    __tablename__ = 'positions'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -108,6 +108,8 @@ class Deal(BaseModel):
 | 
				
			|||||||
    pallets: Mapped[list[Pallet]] = relationship(back_populates='deal', lazy='selectin')
 | 
					    pallets: Mapped[list[Pallet]] = relationship(back_populates='deal', lazy='selectin')
 | 
				
			||||||
    boxes: Mapped[list[Box]] = relationship(back_populates='deal', lazy='selectin')
 | 
					    boxes: Mapped[list[Box]] = relationship(back_populates='deal', lazy='selectin')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assignments: Mapped[list['Assignment']] = relationship(back_populates='deal', lazy='selectin')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealStatusHistory(BaseModel):
 | 
					class DealStatusHistory(BaseModel):
 | 
				
			||||||
    __tablename__ = 'deals_status_history'
 | 
					    __tablename__ = 'deals_status_history'
 | 
				
			||||||
@@ -126,3 +128,14 @@ class DealStatusHistory(BaseModel):
 | 
				
			|||||||
    next_status_deadline = Column(DateTime,
 | 
					    next_status_deadline = Column(DateTime,
 | 
				
			||||||
                                  comment='Дедлайн до которого сделку нужно перевести на следующий этап')
 | 
					                                  comment='Дедлайн до которого сделку нужно перевести на следующий этап')
 | 
				
			||||||
    comment = Column(String, nullable=False, comment='Коментарий', server_default='')
 | 
					    comment = Column(String, nullable=False, comment='Коментарий', server_default='')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Assignment(BaseModel):
 | 
				
			||||||
 | 
					    __tablename__ = 'assignments'
 | 
				
			||||||
 | 
					    user_id: Mapped[int] = mapped_column(ForeignKey('users.id'), primary_key=True)
 | 
				
			||||||
 | 
					    user: Mapped['User'] = relationship('User', back_populates='assignments', lazy='selectin')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    deal_id: Mapped[int] = mapped_column(ForeignKey('deals.id'), primary_key=True)
 | 
				
			||||||
 | 
					    deal: Mapped[Deal] = relationship('Deal', back_populates='assignments', lazy='selectin')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    created_at: Mapped[datetime] = mapped_column()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -234,6 +234,30 @@ async def recalculate_deal_price(
 | 
				
			|||||||
    return await DealService(session).recalculate_price(request)
 | 
					    return await DealService(session).recalculate_price(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@deal_router.post(
 | 
				
			||||||
 | 
					    '/employee',
 | 
				
			||||||
 | 
					    response_model=ManageEmployeeResponse,
 | 
				
			||||||
 | 
					    operation_id='manage_employee',
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def manage_employee(
 | 
				
			||||||
 | 
					        session: SessionDependency,
 | 
				
			||||||
 | 
					        request: ManageEmployeeRequest,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await DealService(session).manage_employee(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@deal_router.get(
 | 
				
			||||||
 | 
					    '/employee/available/{deal_id}',
 | 
				
			||||||
 | 
					    response_model=GetAvailableEmployeesToAssignResponse,
 | 
				
			||||||
 | 
					    operation_id='get_available_employees_to_assign',
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def get_available_employees_to_assign(
 | 
				
			||||||
 | 
					        session: Annotated[AsyncSession, Depends(get_session)],
 | 
				
			||||||
 | 
					        deal_id: int,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return await DealService(session).get_available_employees_to_assign(deal_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# endregion
 | 
					# endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# region Deal services
 | 
					# region Deal services
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -84,6 +84,11 @@ class DealStatusHistorySchema(BaseSchema):
 | 
				
			|||||||
    comment: str | None = None
 | 
					    comment: str | None = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AssignmentSchema(BaseSchema):
 | 
				
			||||||
 | 
					    user: UserSchema
 | 
				
			||||||
 | 
					    created_at: datetime.datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealSchema(BaseSchema):
 | 
					class DealSchema(BaseSchema):
 | 
				
			||||||
    id: int
 | 
					    id: int
 | 
				
			||||||
    name: str
 | 
					    name: str
 | 
				
			||||||
@@ -105,6 +110,7 @@ class DealSchema(BaseSchema):
 | 
				
			|||||||
    manager: Optional[UserSchema] = None
 | 
					    manager: Optional[UserSchema] = None
 | 
				
			||||||
    pallets: List[PalletSchema] = []
 | 
					    pallets: List[PalletSchema] = []
 | 
				
			||||||
    boxes: List[BoxSchema] = []
 | 
					    boxes: List[BoxSchema] = []
 | 
				
			||||||
 | 
					    assignments: List[AssignmentSchema] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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
 | 
				
			||||||
@@ -257,6 +263,12 @@ class DealRecalculatePriceRequest(BaseSchema):
 | 
				
			|||||||
    deal_id: int
 | 
					    deal_id: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ManageEmployeeRequest(BaseSchema):
 | 
				
			||||||
 | 
					    deal_id: int
 | 
				
			||||||
 | 
					    user_id: int
 | 
				
			||||||
 | 
					    is_assign: bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealAddToGroupRequest(BaseSchema):
 | 
					class DealAddToGroupRequest(BaseSchema):
 | 
				
			||||||
    deal_id: int
 | 
					    deal_id: int
 | 
				
			||||||
    group_id: int
 | 
					    group_id: int
 | 
				
			||||||
@@ -389,6 +401,14 @@ class DealRecalculatePriceResponse(OkMessageSchema):
 | 
				
			|||||||
    pass
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ManageEmployeeResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GetAvailableEmployeesToAssignResponse(BaseSchema):
 | 
				
			||||||
 | 
					    employees: list[UserSchema]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DealAddToGroupResponse(OkMessageSchema):
 | 
					class DealAddToGroupResponse(OkMessageSchema):
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import lexorank
 | 
					import lexorank
 | 
				
			||||||
from attr import dataclass
 | 
					from attr import dataclass
 | 
				
			||||||
from fastapi import HTTPException
 | 
					from fastapi import HTTPException
 | 
				
			||||||
from sqlalchemy import select, func, update, delete, insert
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -339,6 +339,8 @@ class DealService(BaseService):
 | 
				
			|||||||
                selectinload(Deal.boxes)
 | 
					                selectinload(Deal.boxes)
 | 
				
			||||||
                .selectinload(Box.product)
 | 
					                .selectinload(Box.product)
 | 
				
			||||||
                .noload(Product.barcodes),
 | 
					                .noload(Product.barcodes),
 | 
				
			||||||
 | 
					                selectinload(Deal.assignments)
 | 
				
			||||||
 | 
					                .joinedload(Assignment.user),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .where(Deal.id == deal_id)
 | 
					            .where(Deal.id == deal_id)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@@ -1136,7 +1138,6 @@ class DealService(BaseService):
 | 
				
			|||||||
        for deal in deals:
 | 
					        for deal in deals:
 | 
				
			||||||
            await self._recalculate_price_single(deal, services_quantity)
 | 
					            await self._recalculate_price_single(deal, services_quantity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def recalculate_price(self, request: DealRecalculatePriceRequest) -> DealRecalculatePriceResponse:
 | 
					    async def recalculate_price(self, request: DealRecalculatePriceRequest) -> DealRecalculatePriceResponse:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            deal_stmt = (
 | 
					            deal_stmt = (
 | 
				
			||||||
@@ -1164,6 +1165,59 @@ class DealService(BaseService):
 | 
				
			|||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            return DealRecalculatePriceResponse(ok=False, message=str(e))
 | 
					            return DealRecalculatePriceResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _assign_employee(self, deal: Deal, user: User) -> tuple[bool, str]:
 | 
				
			||||||
 | 
					        assigned_employee_ids = [assignment.user_id for assignment in deal.assignments]
 | 
				
			||||||
 | 
					        if user.id in assigned_employee_ids:
 | 
				
			||||||
 | 
					            return False, "Работник уже назначен"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assignment = Assignment(user_id=user.id, deal_id=deal.id, created_at=datetime.datetime.now())
 | 
				
			||||||
 | 
					        self.session.add(assignment)
 | 
				
			||||||
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return True, "Работник успешно назначен"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _unassign_employee(self, deal: Deal, user: User) -> tuple[bool, str]:
 | 
				
			||||||
 | 
					        assigned_employee_ids = [assignment.user_id for assignment in deal.assignments]
 | 
				
			||||||
 | 
					        if user.id not in assigned_employee_ids:
 | 
				
			||||||
 | 
					            return False, "Работник еще не назначен"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stmt = delete(Assignment).where(and_(Assignment.user_id == user.id, Assignment.deal_id == deal.id))
 | 
				
			||||||
 | 
					        await self.session.execute(stmt)
 | 
				
			||||||
 | 
					        await self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return True, "Работник успешно удален"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def manage_employee(self, request: ManageEmployeeRequest) -> ManageEmployeeResponse:
 | 
				
			||||||
 | 
					        deal: Optional[Deal] = await self._get_deal_by_id(request.deal_id)
 | 
				
			||||||
 | 
					        if not deal:
 | 
				
			||||||
 | 
					            return ManageEmployeeResponse(ok=False, message=f"Сделка с ID {request.deal_id} не найдена")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        user: Optional[User] = await self.session.get(User, request.user_id)
 | 
				
			||||||
 | 
					        if not user:
 | 
				
			||||||
 | 
					            return ManageEmployeeResponse(ok=False, message=f"Пользователь с ID {request.user_id} не найден")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if request.is_assign:
 | 
				
			||||||
 | 
					            ok, message = await self._assign_employee(deal, user)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            ok, message = await self._unassign_employee(deal, user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ManageEmployeeResponse(ok=ok, message=message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def get_available_employees_to_assign(self, deal_id: int) -> GetAvailableEmployeesToAssignResponse:
 | 
				
			||||||
 | 
					        assigned_users = select(Assignment.user_id).where(Assignment.deal_id == deal_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stmt_free_employees = (
 | 
				
			||||||
 | 
					            select(User)
 | 
				
			||||||
 | 
					            .where(and_(
 | 
				
			||||||
 | 
					                User.is_deleted == False,
 | 
				
			||||||
 | 
					                User.role_key == "employee",
 | 
				
			||||||
 | 
					                User.id.not_in(assigned_users),
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        free_employees = (await self.session.execute(stmt_free_employees)).scalars().all()
 | 
				
			||||||
 | 
					        return GetAvailableEmployeesToAssignResponse(employees=free_employees)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def add_to_group(self, user: User, request: DealAddToGroupRequest) -> DealAddToGroupResponse:
 | 
					    async def add_to_group(self, user: User, request: DealAddToGroupRequest) -> DealAddToGroupResponse:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            group_bill_request = await self.session.get(GroupBillRequest, request.group_id)
 | 
					            group_bill_request = await self.session.get(GroupBillRequest, request.group_id)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user