feat: work shifts planning
This commit is contained in:
1
main.py
1
main.py
@@ -47,6 +47,7 @@ routers_list = [
|
|||||||
routers.task_router,
|
routers.task_router,
|
||||||
routers.statistics_router,
|
routers.statistics_router,
|
||||||
routers.work_shifts_router,
|
routers.work_shifts_router,
|
||||||
|
routers.work_shifts_planning_router,
|
||||||
routers.transaction_router,
|
routers.transaction_router,
|
||||||
routers.shipping_router,
|
routers.shipping_router,
|
||||||
routers.department_router,
|
routers.department_router,
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime, date
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from sqlalchemy import ForeignKey
|
from sqlalchemy import ForeignKey, Table, Column
|
||||||
from sqlalchemy.sql import expression
|
from sqlalchemy.sql import expression
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from models.base import BaseModel
|
from models.base import BaseModel
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from models import User
|
from models import User, Position
|
||||||
|
|
||||||
|
|
||||||
class WorkShift(BaseModel):
|
class WorkShift(BaseModel):
|
||||||
@@ -63,3 +63,29 @@ class WorkShiftPause(BaseModel):
|
|||||||
back_populates="pauses",
|
back_populates="pauses",
|
||||||
lazy="selectin",
|
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",
|
||||||
|
)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from .time_tracking import time_tracking_router
|
|||||||
from .billing import billing_router
|
from .billing import billing_router
|
||||||
from .task import task_router
|
from .task import task_router
|
||||||
from .work_shifts import work_shifts_router
|
from .work_shifts import work_shifts_router
|
||||||
|
from .work_shifts_planning import work_shifts_planning_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
|
||||||
|
|||||||
34
routers/work_shifts_planning.py
Normal file
34
routers/work_shifts_planning.py
Normal file
@@ -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)
|
||||||
47
schemas/work_shifts_planning.py
Normal file
47
schemas/work_shifts_planning.py
Normal file
@@ -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
|
||||||
81
services/work_shifts_planning.py
Normal file
81
services/work_shifts_planning.py
Normal file
@@ -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="Данные о смене сохранены")
|
||||||
Reference in New Issue
Block a user