From 144811ec87ead77a2eccd6bca4de561fd2eb1c9a Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Tue, 17 Dec 2024 12:42:41 +0400 Subject: [PATCH] feat: departments and department sections --- main.py | 1 + models/auth.py | 37 ++++++++++ routers/__init__.py | 1 + routers/department.py | 131 +++++++++++++++++++++++++++++++++ schemas/department.py | 99 +++++++++++++++++++++++++ services/department.py | 159 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 428 insertions(+) create mode 100644 routers/department.py create mode 100644 schemas/department.py create mode 100644 services/department.py diff --git a/main.py b/main.py index 4af0e18..520d333 100644 --- a/main.py +++ b/main.py @@ -49,6 +49,7 @@ routers_list = [ routers.work_shifts_router, routers.transaction_router, routers.shipping_router, + routers.department_router, ] for router in routers_list: app.include_router(router) diff --git a/models/auth.py b/models/auth.py index 8a56d67..8952636 100644 --- a/models/auth.py +++ b/models/auth.py @@ -31,6 +31,13 @@ user_pay_rate = Table( Column('user_id', ForeignKey('users.id'), primary_key=True, unique=True) ) +user_department_section = Table( + 'user_department_section', + BaseModel.metadata, + Column('department_section_id', ForeignKey('department_sections.id'), primary_key=True), + Column('user_id', ForeignKey('users.id'), primary_key=True) +) + class Permission(BaseModel): __tablename__ = 'permissions' @@ -135,3 +142,33 @@ class PassportImage(BaseModel): user: Mapped["User"] = relationship(back_populates='passport_images') image_url: Mapped[str] = mapped_column(nullable=False) + + +class Department(BaseModel): + __tablename__ = 'departments' + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(unique=True) + + sections: Mapped[list['DepartmentSection']] = relationship( + back_populates='department', + lazy='selectin', + cascade='all, delete', + ) + + +class DepartmentSection(BaseModel): + __tablename__ = 'department_sections' + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(index=True) + + department_id: Mapped[int] = mapped_column(ForeignKey('departments.id')) + department: Mapped["Department"] = relationship( + back_populates='sections', + lazy='selectin', + ) + + users: Mapped[list[User]] = relationship( + 'User', + secondary=user_department_section, + uselist=True, + ) diff --git a/routers/__init__.py b/routers/__init__.py index b3684b7..c6ceeed 100644 --- a/routers/__init__.py +++ b/routers/__init__.py @@ -17,3 +17,4 @@ from .work_shifts import work_shifts_router from .statistics import statistics_router from .transaction import transaction_router from .shipping import shipping_router +from .department import department_router diff --git a/routers/department.py b/routers/department.py new file mode 100644 index 0000000..f56fb86 --- /dev/null +++ b/routers/department.py @@ -0,0 +1,131 @@ +from fastapi import APIRouter, Depends + +from backend.dependecies import SessionDependency +from schemas.department import * +from services.auth import authorized_user +from services.department import DepartmentService + +department_router = APIRouter( + prefix="/department", + tags=["department"], + dependencies=[Depends(authorized_user)] +) + + +@department_router.get( + "/", + operation_id="get_departments", + response_model=GetDepartmentsResponse, +) +async def get_departments( + session: SessionDependency, +): + return await DepartmentService(session).get_departments() + + +@department_router.post( + "/", + operation_id="create_department", + response_model=CreateDepartmentResponse, +) +async def create_department( + session: SessionDependency, + request: CreateDepartmentRequest, +): + return await DepartmentService(session).create_department(request) + + +@department_router.patch( + "/", + operation_id="update_department", + response_model=UpdateDepartmentResponse, +) +async def update_department( + session: SessionDependency, + request: UpdateDepartmentRequest, +): + return await DepartmentService(session).update_department(request) + + +@department_router.delete( + "/{department_id}", + operation_id="delete_department", + response_model=DeleteDepartmentResponse, +) +async def delete_department( + session: SessionDependency, + department_id: int, +): + return await DepartmentService(session).delete_department(department_id) + + +@department_router.post( + "/section", + operation_id="create_section", + response_model=CreateDepartmentSectionResponse, +) +async def create_section( + session: SessionDependency, + request: CreateDepartmentSectionRequest, +): + return await DepartmentService(session).create_department_section(request) + + +@department_router.patch( + "/section", + operation_id="update_section", + response_model=UpdateDepartmentSectionResponse, +) +async def update_section( + session: SessionDependency, + request: UpdateDepartmentSectionRequest, +): + return await DepartmentService(session).update_department_section(request) + + +@department_router.delete( + "/section/{section_id}", + operation_id="delete_section", + response_model=DeleteDepartmentSectionResponse, +) +async def delete_section( + session: SessionDependency, + section_id: int, +): + return await DepartmentService(session).delete_department_section(section_id) + + +@department_router.get( + "/users/{section_id}", + operation_id="get_available_users_for_section", + response_model=GetAvailableUsersForDepartmentSectionResponse, +) +async def get_available_users_for_department_section( + session: SessionDependency, + section_id: int, +): + return await DepartmentService(session).get_available_users_for_section(section_id) + + +@department_router.post( + "/users", + operation_id="add_user", + response_model=AddUserResponse, +) +async def add_user( + session: SessionDependency, + request: AddUserRequest, +): + return await DepartmentService(session).add_user(request) + + +@department_router.post( + "/users/delete", + operation_id="delete_user", + response_model=DeleteUserResponse, +) +async def delete_user( + session: SessionDependency, + request: DeleteUserRequest, +): + return await DepartmentService(session).delete_user(request) diff --git a/schemas/department.py b/schemas/department.py new file mode 100644 index 0000000..218530b --- /dev/null +++ b/schemas/department.py @@ -0,0 +1,99 @@ +from schemas.base import BaseSchema, OkMessageSchema +from schemas.user import UserSchema + + +# region Entities + +class DepartmentSectionBaseSchema(BaseSchema): + name: str + department_id: int + + +class DepartmentSectionSchema(DepartmentSectionBaseSchema): + id: int + users: list[UserSchema] = [] + + +class DepartmentBaseSchema(BaseSchema): + name: str + + +class DepartmentSchema(DepartmentBaseSchema): + id: int + sections: list[DepartmentSectionSchema] = [] + + +# endregion + +# region Requests + +class CreateDepartmentRequest(BaseSchema): + department: DepartmentBaseSchema + + +class UpdateDepartmentRequest(BaseSchema): + department: DepartmentSchema + + +class CreateDepartmentSectionRequest(BaseSchema): + section: DepartmentSectionBaseSchema + + +class UpdateDepartmentSectionRequest(BaseSchema): + section: DepartmentSectionSchema + + +class AddUserRequest(BaseSchema): + user_id: int + section_id: int + + +class DeleteUserRequest(BaseSchema): + user_id: int + section_id: int + + +# endregion + +# region Responses + +class GetDepartmentsResponse(BaseSchema): + departments: list[DepartmentSchema] + + +class CreateDepartmentResponse(OkMessageSchema): + pass + + +class UpdateDepartmentResponse(OkMessageSchema): + pass + + +class DeleteDepartmentResponse(OkMessageSchema): + pass + + +class CreateDepartmentSectionResponse(OkMessageSchema): + pass + + +class UpdateDepartmentSectionResponse(OkMessageSchema): + pass + + +class DeleteDepartmentSectionResponse(OkMessageSchema): + pass + + +class GetAvailableUsersForDepartmentSectionResponse(BaseSchema): + users: list[UserSchema] + + +class AddUserResponse(OkMessageSchema): + pass + + +class DeleteUserResponse(OkMessageSchema): + pass + +# endregion diff --git a/services/department.py b/services/department.py new file mode 100644 index 0000000..1df6237 --- /dev/null +++ b/services/department.py @@ -0,0 +1,159 @@ +from typing import Optional + +from sqlalchemy import select, and_ +from sqlalchemy.orm import selectinload + +from models import Department, DepartmentSection, User, user_department_section +from schemas.department import * +from services.base import BaseService + + +class DepartmentService(BaseService): + async def get_departments(self) -> GetDepartmentsResponse: + stmt = ( + select(Department) + .options( + selectinload(Department.sections).selectinload(DepartmentSection.users) + ) + .order_by(Department.name) + ) + departments = (await self.session.execute(stmt)).scalars().all() + return GetDepartmentsResponse(departments=departments) + + async def _get_department(self, filter_condition) -> Optional[Department]: + stmt = ( + select(Department) + .where(filter_condition) + .options(selectinload(Department.sections)) + ) + departments = (await self.session.execute(stmt)).one_or_none() + return departments[0] if departments else None + + async def _get_department_by_name(self, name: str) -> Optional[Department]: + return await self._get_department(Department.name == name) + + async def _get_department_by_id(self, department_id: int) -> Optional[Department]: + return await self._get_department(Department.id == department_id) + + async def create_department(self, request: CreateDepartmentRequest) -> CreateDepartmentResponse: + department = await self._get_department_by_name(request.department.name) + if department: + return CreateDepartmentResponse(ok=False, message="Департамент с таким названием уже существует") + + department = Department(name=request.department.name) + self.session.add(department) + await self.session.commit() + return CreateDepartmentResponse(ok=True, message="Департамент успешно создан") + + async def update_department(self, request: UpdateDepartmentRequest) -> UpdateDepartmentResponse: + department_same_name = await self._get_department_by_name(request.department.name) + if department_same_name and department_same_name.id != request.department.id: + return UpdateDepartmentResponse(ok=False, message="Департамент с данным именем уже существует") + + department = await self._get_department_by_id(request.department.id) + if not department: + return UpdateDepartmentResponse(ok=False, message=f"Департамент с ID {request.department.id} не найден") + + department.name = request.department.name + await self.session.commit() + return UpdateDepartmentResponse(ok=True, message="Департамент успешно изменен") + + async def delete_department(self, department_id: int) -> DeleteDepartmentResponse: + department = await self.session.get(Department, department_id) + if not department: + return CreateDepartmentResponse(ok=False, message=f"Департамент с ID {department_id} не найден") + + await self.session.delete(department) + await self.session.commit() + return DeleteDepartmentResponse(ok=True, message="Департамент успешно удален") + + async def _get_section(self, filter_condition) -> Optional[DepartmentSection]: + stmt = ( + select(DepartmentSection) + .where(filter_condition) + .options(selectinload(DepartmentSection.users)) + ) + departments = (await self.session.execute(stmt)).one_or_none() + return departments[0] if departments else None + + async def _get_section_by_name(self, name: str, department_id: int) -> Optional[DepartmentSection]: + return await self._get_section(and_( + DepartmentSection.name == name, + DepartmentSection.department_id == department_id, + )) + + async def _get_section_by_id(self, section_id: int) -> Optional[DepartmentSection]: + return await self._get_section(DepartmentSection.id == section_id) + + async def create_department_section(self, request: CreateDepartmentSectionRequest) -> CreateDepartmentSectionResponse: + section = await self._get_section_by_name(request.section.name, request.section.department_id) + if section: + return CreateDepartmentSectionResponse(ok=False, message="Отдел с таким названием уже существует в департаменте") + + section = DepartmentSection(name=request.section.name, department_id=request.section.department_id) + self.session.add(section) + await self.session.commit() + return CreateDepartmentSectionResponse(ok=True, message="Отдел успешно создан") + + async def update_department_section(self, request: UpdateDepartmentSectionRequest) -> UpdateDepartmentSectionResponse: + section = await self._get_section_by_id(request.section.id) + if not section: + return UpdateDepartmentSectionResponse(ok=False, message=f"Отдел с ID {request.section.id} не найден") + + section.name = request.section.name + await self.session.commit() + return UpdateDepartmentSectionResponse(ok=True, message="Отдел успешно изменен") + + async def get_available_users_for_section(self, section_id: int) -> GetAvailableUsersForDepartmentSectionResponse: + sub_user_ids_in_section = ( + select(user_department_section.c.user_id.label("user_id")) + .where(user_department_section.c.department_section_id == section_id) + .group_by(user_department_section.c.user_id) + .subquery() + ) + stmt = ( + select(User) + .join(sub_user_ids_in_section, sub_user_ids_in_section.c.user_id == User.id, isouter=True) + .where(and_( + sub_user_ids_in_section.c.user_id == None, + User.is_deleted == False, + )) + ) + users = (await self.session.execute(stmt)).scalars().all() + return GetAvailableUsersForDepartmentSectionResponse(users=users) + + async def delete_department_section(self, section_id: int) -> DeleteDepartmentSectionResponse: + section = await self._get_section_by_id(section_id) + if not section: + return DeleteDepartmentSectionResponse(ok=False, message=f"Отдел с ID {section_id} не найден") + + await self.session.delete(section) + await self.session.commit() + return DeleteDepartmentSectionResponse(ok=True, message="Отдел успешно удален") + + async def _modify_user(self, request: AddUserRequest | DeleteUserRequest, is_adding: bool) -> tuple[bool, str]: + section = await self._get_section_by_id(request.section_id) + if not section: + return False, f"Отдел с ID {request.section_id} не найден" + + user = await self.session.get(User, request.user_id) + if not user: + return False, f"Пользователь с ID {request.user_id} не найден" + + if is_adding: + section.users.append(user) + message = f"Пользователь успешно добавлен в отдел" + else: + section.users.remove(user) + message = f"Пользователь успешно удален из отдела" + + await self.session.commit() + return True, message + + async def add_user(self, request: AddUserRequest) -> AddUserResponse: + ok, message = await self._modify_user(request, True) + return AddUserResponse(ok=ok, message=message) + + async def delete_user(self, request: DeleteUserRequest) -> DeleteUserResponse: + ok, message = await self._modify_user(request, False) + return DeleteUserResponse(ok=ok, message=message)