feat: departments and department sections
This commit is contained in:
1
main.py
1
main.py
@@ -49,6 +49,7 @@ routers_list = [
|
|||||||
routers.work_shifts_router,
|
routers.work_shifts_router,
|
||||||
routers.transaction_router,
|
routers.transaction_router,
|
||||||
routers.shipping_router,
|
routers.shipping_router,
|
||||||
|
routers.department_router,
|
||||||
]
|
]
|
||||||
for router in routers_list:
|
for router in routers_list:
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
|
|||||||
@@ -31,6 +31,13 @@ user_pay_rate = Table(
|
|||||||
Column('user_id', ForeignKey('users.id'), primary_key=True, unique=True)
|
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):
|
class Permission(BaseModel):
|
||||||
__tablename__ = 'permissions'
|
__tablename__ = 'permissions'
|
||||||
@@ -135,3 +142,33 @@ class PassportImage(BaseModel):
|
|||||||
user: Mapped["User"] = relationship(back_populates='passport_images')
|
user: Mapped["User"] = relationship(back_populates='passport_images')
|
||||||
|
|
||||||
image_url: Mapped[str] = mapped_column(nullable=False)
|
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,
|
||||||
|
)
|
||||||
|
|||||||
@@ -17,3 +17,4 @@ from .work_shifts import work_shifts_router
|
|||||||
from .statistics import statistics_router
|
from .statistics import statistics_router
|
||||||
from .transaction import transaction_router
|
from .transaction import transaction_router
|
||||||
from .shipping import shipping_router
|
from .shipping import shipping_router
|
||||||
|
from .department import department_router
|
||||||
|
|||||||
131
routers/department.py
Normal file
131
routers/department.py
Normal file
@@ -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)
|
||||||
99
schemas/department.py
Normal file
99
schemas/department.py
Normal file
@@ -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
|
||||||
159
services/department.py
Normal file
159
services/department.py
Normal file
@@ -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)
|
||||||
Reference in New Issue
Block a user