diff --git a/main.py b/main.py index ab0e46d..5003f71 100644 --- a/main.py +++ b/main.py @@ -47,6 +47,7 @@ routers_list = [ routers.task_router, routers.statistics_router, routers.work_shifts_router, + routers.work_shifts_planning_router, routers.transaction_router, routers.shipping_router, routers.department_router, diff --git a/models/work_shifts.py b/models/work_shifts.py index 6664b93..4feded3 100644 --- a/models/work_shifts.py +++ b/models/work_shifts.py @@ -1,14 +1,14 @@ -from datetime import datetime +from datetime import datetime, date from typing import TYPE_CHECKING -from sqlalchemy import ForeignKey +from sqlalchemy import ForeignKey, Table, Column from sqlalchemy.sql import expression from sqlalchemy.orm import Mapped, mapped_column, relationship from models.base import BaseModel if TYPE_CHECKING: - from models import User + from models import User, Position class WorkShift(BaseModel): @@ -63,3 +63,29 @@ class WorkShiftPause(BaseModel): back_populates="pauses", lazy="selectin", ) + + +work_shifts_positions = Table( + 'work_shifts_positions', + BaseModel.metadata, + Column('position_key', ForeignKey('positions.key'), primary_key=True), + Column('work_shift_id', ForeignKey('planned_work_shifts.id'), primary_key=True), +) + + +class PlannedWorkShift(BaseModel): + __tablename__ = "planned_work_shifts" + + id: Mapped[int] = mapped_column(primary_key=True) + shift_date: Mapped[date] = mapped_column(nullable=False, index=True) + created_at: Mapped[datetime] = mapped_column(nullable=False) + + user_id: Mapped[int] = mapped_column(ForeignKey('users.id'), nullable=False, index=True) + user: Mapped["User"] = relationship(lazy="selectin", backref="planned_work_shifts") + + positions: Mapped[list["Position"]] = relationship( + "Position", + uselist=True, + secondary=work_shifts_positions, + lazy="selectin", + ) diff --git a/routers/__init__.py b/routers/__init__.py index b425640..5f2cd19 100644 --- a/routers/__init__.py +++ b/routers/__init__.py @@ -14,6 +14,7 @@ from .time_tracking import time_tracking_router from .billing import billing_router from .task import task_router from .work_shifts import work_shifts_router +from .work_shifts_planning import work_shifts_planning_router from .statistics import statistics_router from .transaction import transaction_router from .shipping import shipping_router diff --git a/routers/work_shifts_planning.py b/routers/work_shifts_planning.py new file mode 100644 index 0000000..17da50f --- /dev/null +++ b/routers/work_shifts_planning.py @@ -0,0 +1,34 @@ +from fastapi import APIRouter + +from backend.dependecies import SessionDependency +from schemas.work_shifts_planning import * +from services.work_shifts_planning import WorkShiftsPlanningService + +work_shifts_planning_router = APIRouter( + prefix="/work-shifts-planning", + tags=["work-shifts-planning"], +) + + +@work_shifts_planning_router.post( + "/", + response_model=GetPlannedWorkShiftsResponse, + operation_id="get_work_shifts", +) +async def get_work_shifts( + session: SessionDependency, + request: GetWorkShiftsPlanningDataRequest, +): + return await WorkShiftsPlanningService(session).get_work_shifts(request) + + +@work_shifts_planning_router.post( + "/update", + response_model=UpdatePlanningWorkShiftResponse, + operation_id="update_work_shift", +) +async def update_work_shift( + session: SessionDependency, + request: UpdatePlanningWorkShiftRequest, +): + return await WorkShiftsPlanningService(session).update_work_shift(request) diff --git a/schemas/work_shifts_planning.py b/schemas/work_shifts_planning.py new file mode 100644 index 0000000..b17a7eb --- /dev/null +++ b/schemas/work_shifts_planning.py @@ -0,0 +1,47 @@ +from datetime import datetime, date + +from schemas.base import BaseSchema, OkMessageSchema +from schemas.position import PositionSchema +from schemas.user import UserSchema + + +# region Entities + +class PlannedWorkShiftSchema(BaseSchema): + id: int + shift_date: datetime + positions: list[PositionSchema] + + +class PlanningTableRow(BaseSchema): + user: UserSchema + shifts: list[PlannedWorkShiftSchema] + + +# endregion + +# region Requests + +class GetWorkShiftsPlanningDataRequest(BaseSchema): + date_from: date + date_to: date + + +class UpdatePlanningWorkShiftRequest(BaseSchema): + shift_date: date + position_keys: list[str] + user_id: int + + +# endregion + +# region Responses + +class GetPlannedWorkShiftsResponse(BaseSchema): + shifts: list[PlanningTableRow] + + +class UpdatePlanningWorkShiftResponse(OkMessageSchema): + pass + +# endregion diff --git a/services/work_shifts_planning.py b/services/work_shifts_planning.py new file mode 100644 index 0000000..2ba5501 --- /dev/null +++ b/services/work_shifts_planning.py @@ -0,0 +1,81 @@ +from collections import defaultdict +from typing import Optional + +from sqlalchemy import select, and_ +from sqlalchemy.orm import joinedload + +from models import Position +from models.work_shifts import PlannedWorkShift +from schemas.work_shifts_planning import * +from services.base import BaseService + + +class WorkShiftsPlanningService(BaseService): + async def get_work_shifts(self, request: GetWorkShiftsPlanningDataRequest) -> GetPlannedWorkShiftsResponse: + users_shifts_ids = ( + select(PlannedWorkShift) + .options( + joinedload(PlannedWorkShift.user), + ) + .where(PlannedWorkShift.shift_date.between(request.date_from, request.date_to)) + .order_by(PlannedWorkShift.shift_date) + ) + shifts = (await self.session.scalars(users_shifts_ids)).all() + + user_shifts_dict = defaultdict(list) + for shift in shifts: + user_shifts_dict[shift.user].append(shift) + + result_shifts = [] + for user, shifts in user_shifts_dict.items(): + user_schema = UserSchema.model_validate(user) + shifts_schema = [PlannedWorkShiftSchema.model_validate(shift) for shift in shifts] + result_shifts.append( + PlanningTableRow( + user=user_schema, + shifts=shifts_schema, + ) + ) + + return GetPlannedWorkShiftsResponse(shifts=result_shifts) + + async def _get_work_shift(self, user_id: int, shift_date: date) -> Optional[PlannedWorkShift]: + stmt = ( + select(PlannedWorkShift) + .where( + and_( + PlannedWorkShift.user_id == user_id, + PlannedWorkShift.shift_date == shift_date, + ) + ) + ) + + work_shift = await self.session.execute(stmt) + work_shift = work_shift.one_or_none() + return work_shift[0] if work_shift else None + + async def update_work_shift(self, request: UpdatePlanningWorkShiftRequest) -> UpdatePlanningWorkShiftResponse: + work_shift = await self._get_work_shift(request.user_id, request.shift_date) + + positions: list[Position] = [] + for position_key in request.position_keys: + position: Optional[Position] = await self.session.get(Position, position_key) + if position: + positions.append(position) + else: + return UpdatePlanningWorkShiftResponse(ok=False, message=f"Должность с ID: {position_key} не найдена") + + if not work_shift: + work_shift = PlannedWorkShift( + user_id=request.user_id, + shift_date=request.shift_date, + positions=positions, + created_at=datetime.now(), + ) + self.session.add(work_shift) + else: + work_shift.positions = positions + + await self.session.commit() + + return UpdatePlanningWorkShiftResponse(ok=True, message="Данные о смене сохранены")