From c65ca39d08fa3932ee58b7c47059d9f832f396f1 Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Fri, 20 Dec 2024 00:27:26 +0400 Subject: [PATCH] feat: assignment of employees to deals --- models/auth.py | 7 +++++- models/deal.py | 13 +++++++++++ routers/deal.py | 24 ++++++++++++++++++++ schemas/deal.py | 20 +++++++++++++++++ services/deal.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 119 insertions(+), 3 deletions(-) diff --git a/models/auth.py b/models/auth.py index 8952636..1c8e89e 100644 --- a/models/auth.py +++ b/models/auth.py @@ -9,7 +9,7 @@ from models.work_shifts import WorkShift if TYPE_CHECKING: from models.payroll import PayRate, PaymentRecord - from models import Deal + from models import Deal, Assignment role_permissions = Table( 'role_permissions', @@ -121,6 +121,11 @@ class User(BaseModel): cascade="all, delete-orphan" ) + assignments: Mapped[list['Assignment']] = relationship( + back_populates='user', + lazy='selectin' + ) + class Position(BaseModel): __tablename__ = 'positions' diff --git a/models/deal.py b/models/deal.py index 4c12f2a..f72068c 100644 --- a/models/deal.py +++ b/models/deal.py @@ -108,6 +108,8 @@ class Deal(BaseModel): pallets: Mapped[list[Pallet]] = 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): __tablename__ = 'deals_status_history' @@ -126,3 +128,14 @@ class DealStatusHistory(BaseModel): next_status_deadline = Column(DateTime, comment='Дедлайн до которого сделку нужно перевести на следующий этап') 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() diff --git a/routers/deal.py b/routers/deal.py index c2ca119..4744d25 100644 --- a/routers/deal.py +++ b/routers/deal.py @@ -234,6 +234,30 @@ async def recalculate_deal_price( 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 # region Deal services diff --git a/schemas/deal.py b/schemas/deal.py index 5800c27..005048e 100644 --- a/schemas/deal.py +++ b/schemas/deal.py @@ -84,6 +84,11 @@ class DealStatusHistorySchema(BaseSchema): comment: str | None = None +class AssignmentSchema(BaseSchema): + user: UserSchema + created_at: datetime.datetime + + class DealSchema(BaseSchema): id: int name: str @@ -105,6 +110,7 @@ class DealSchema(BaseSchema): manager: Optional[UserSchema] = None pallets: List[PalletSchema] = [] boxes: List[BoxSchema] = [] + assignments: List[AssignmentSchema] = [] delivery_date: Optional[datetime.datetime] = None receiving_slot_date: Optional[datetime.datetime] = None @@ -257,6 +263,12 @@ class DealRecalculatePriceRequest(BaseSchema): deal_id: int +class ManageEmployeeRequest(BaseSchema): + deal_id: int + user_id: int + is_assign: bool + + class DealAddToGroupRequest(BaseSchema): deal_id: int group_id: int @@ -389,6 +401,14 @@ class DealRecalculatePriceResponse(OkMessageSchema): pass +class ManageEmployeeResponse(OkMessageSchema): + pass + + +class GetAvailableEmployeesToAssignResponse(BaseSchema): + employees: list[UserSchema] + + class DealAddToGroupResponse(OkMessageSchema): pass diff --git a/services/deal.py b/services/deal.py index 8d01472..08bcad1 100644 --- a/services/deal.py +++ b/services/deal.py @@ -1,7 +1,7 @@ import lexorank from attr import dataclass 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 starlette import status @@ -339,6 +339,8 @@ class DealService(BaseService): selectinload(Deal.boxes) .selectinload(Box.product) .noload(Product.barcodes), + selectinload(Deal.assignments) + .joinedload(Assignment.user), ) .where(Deal.id == deal_id) ) @@ -1136,7 +1138,6 @@ class DealService(BaseService): for deal in deals: await self._recalculate_price_single(deal, services_quantity) - async def recalculate_price(self, request: DealRecalculatePriceRequest) -> DealRecalculatePriceResponse: try: deal_stmt = ( @@ -1164,6 +1165,59 @@ class DealService(BaseService): except Exception as 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: try: group_bill_request = await self.session.get(GroupBillRequest, request.group_id)