feat: work shifts history
This commit is contained in:
		@@ -29,7 +29,7 @@ class Expense(BaseModel):
 | 
			
		||||
    amount: Mapped[float] = mapped_column()
 | 
			
		||||
 | 
			
		||||
    created_by_user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
 | 
			
		||||
    created_by_user: Mapped["User"] = relationship(foreign_keys=[created_by_user_id])
 | 
			
		||||
    created_by_user: Mapped["User"] = relationship(foreign_keys=[created_by_user_id], lazy="selectin")
 | 
			
		||||
 | 
			
		||||
    tags: Mapped[list["ExpenseTag"]] = relationship(
 | 
			
		||||
        secondary=expenses_expense_tags,
 | 
			
		||||
 
 | 
			
		||||
@@ -27,4 +27,5 @@ class WorkShift(BaseModel):
 | 
			
		||||
    user: Mapped["User"] = relationship(
 | 
			
		||||
        "User",
 | 
			
		||||
        back_populates="work_shifts",
 | 
			
		||||
        lazy="selectin",
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,9 @@ from io import BytesIO
 | 
			
		||||
 | 
			
		||||
from fastapi import APIRouter, Response
 | 
			
		||||
 | 
			
		||||
from backend.dependecies import SessionDependency, CurrentUserDependency
 | 
			
		||||
from backend.dependecies import SessionDependency, CurrentUserDependency, PaginationDependency
 | 
			
		||||
from generators.work_shifts_qr_code_generator import WorkShiftsQRCodeGenerator
 | 
			
		||||
from schemas.work_shifts import StartShiftResponse, FinishShiftResponse, ActiveWorkShiftsResponse, DeleteShiftResponse, \
 | 
			
		||||
    FinishShiftByIdResponse
 | 
			
		||||
from schemas.work_shifts import *
 | 
			
		||||
from services.work_shifts import WorkShiftsService
 | 
			
		||||
 | 
			
		||||
work_shifts_router = APIRouter(
 | 
			
		||||
@@ -65,14 +64,16 @@ async def finish_work_shift_by_id(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@work_shifts_router.get(
 | 
			
		||||
    "/get-active-shifts",
 | 
			
		||||
    response_model=ActiveWorkShiftsResponse,
 | 
			
		||||
    operation_id="get_active_shifts",
 | 
			
		||||
    "/get-shifts/{is_active}",
 | 
			
		||||
    response_model=GetWorkShiftsResponse,
 | 
			
		||||
    operation_id="get_shifts",
 | 
			
		||||
)
 | 
			
		||||
async def get_active_shifts(
 | 
			
		||||
async def get_shifts(
 | 
			
		||||
        session: SessionDependency,
 | 
			
		||||
        pagination: PaginationDependency,
 | 
			
		||||
        is_active: bool,
 | 
			
		||||
):
 | 
			
		||||
    return await WorkShiftsService(session).get_active_shifts()
 | 
			
		||||
    return await WorkShiftsService(session).get_shifts(is_active, pagination)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@work_shifts_router.delete(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,17 @@
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from typing import List
 | 
			
		||||
from typing import List, Optional
 | 
			
		||||
 | 
			
		||||
from schemas.base import OkMessageSchema, BaseSchema
 | 
			
		||||
from schemas.base import OkMessageSchema, BaseSchema, PaginationInfoSchema
 | 
			
		||||
from schemas.user import UserSchema
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# region Entities
 | 
			
		||||
 | 
			
		||||
class ActiveWorkShiftSchema(BaseSchema):
 | 
			
		||||
class WorkShiftSchema(BaseSchema):
 | 
			
		||||
    id: int
 | 
			
		||||
    started_at: datetime
 | 
			
		||||
    finished_at: Optional[datetime] = None
 | 
			
		||||
    hours: Optional[float] = None
 | 
			
		||||
    user: UserSchema
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -29,8 +31,9 @@ class FinishShiftByIdResponse(OkMessageSchema):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActiveWorkShiftsResponse(BaseSchema):
 | 
			
		||||
    shifts: List[ActiveWorkShiftSchema]
 | 
			
		||||
class GetWorkShiftsResponse(BaseSchema):
 | 
			
		||||
    shifts: List[WorkShiftSchema]
 | 
			
		||||
    pagination_info: PaginationInfoSchema
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DeleteShiftResponse(OkMessageSchema):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,17 @@
 | 
			
		||||
from datetime import datetime, date, timedelta
 | 
			
		||||
from datetime import date, timedelta
 | 
			
		||||
 | 
			
		||||
from sqlalchemy import select, delete
 | 
			
		||||
import math
 | 
			
		||||
from fastapi import HTTPException, status
 | 
			
		||||
from sqlalchemy import select, delete, func
 | 
			
		||||
from sqlalchemy.orm import joinedload
 | 
			
		||||
 | 
			
		||||
from models import WorkShift, User
 | 
			
		||||
from schemas.base import PaginationSchema
 | 
			
		||||
from schemas.time_tracking import UpdateTimeTrackingRecordRequest
 | 
			
		||||
from schemas.user import *
 | 
			
		||||
from schemas.work_shifts import StartShiftResponse, FinishShiftResponse, ActiveWorkShiftsResponse, DeleteShiftResponse, \
 | 
			
		||||
    FinishShiftByIdResponse
 | 
			
		||||
from schemas.work_shifts import *
 | 
			
		||||
from services.base import BaseService
 | 
			
		||||
from services.time_tracking import TimeTrackingService
 | 
			
		||||
from utils.dependecies import is_valid_pagination
 | 
			
		||||
from utils.work_time import hours_to_hours_and_minutes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -100,18 +102,44 @@ class WorkShiftsService(BaseService):
 | 
			
		||||
        hours, minutes = hours_to_hours_and_minutes(hours)
 | 
			
		||||
        return FinishShiftByIdResponse(ok=True, message=f"Смена закончена. Отработано {hours} ч. {minutes} мин.")
 | 
			
		||||
 | 
			
		||||
    async def get_active_shifts(self) -> ActiveWorkShiftsResponse:
 | 
			
		||||
    async def get_shifts(self, is_active: bool, pagination: PaginationSchema) -> GetWorkShiftsResponse:
 | 
			
		||||
        if not is_valid_pagination(pagination):
 | 
			
		||||
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid pagination')
 | 
			
		||||
        page = max(0, pagination.page - 1)
 | 
			
		||||
 | 
			
		||||
        total_shifts = await self.session.scalar(
 | 
			
		||||
            select(func.count())
 | 
			
		||||
            .select_from(WorkShift)
 | 
			
		||||
            .where(WorkShift.finished_at.is_(None) if is_active else WorkShift.finished_at.is_not(None))
 | 
			
		||||
        )
 | 
			
		||||
        if not total_shifts:
 | 
			
		||||
            return GetWorkShiftsResponse(
 | 
			
		||||
                shifts=[],
 | 
			
		||||
                pagination_info=PaginationInfoSchema(
 | 
			
		||||
                    total_pages=0,
 | 
			
		||||
                    total_items=0
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        total_pages = math.ceil(total_shifts / pagination.items_per_page)
 | 
			
		||||
 | 
			
		||||
        stmt = (
 | 
			
		||||
            select(WorkShift)
 | 
			
		||||
            .options(joinedload(WorkShift.user))
 | 
			
		||||
            .where(WorkShift.finished_at == None)
 | 
			
		||||
            .where(WorkShift.finished_at.is_(None) if is_active else WorkShift.finished_at.is_not(None))
 | 
			
		||||
            .order_by(WorkShift.started_at.desc())
 | 
			
		||||
            .offset(page * pagination.items_per_page)
 | 
			
		||||
            .limit(pagination.items_per_page)
 | 
			
		||||
        )
 | 
			
		||||
        shifts = (await self.session.execute(stmt)).scalars().all()
 | 
			
		||||
 | 
			
		||||
        shifts = await self.session.execute(stmt)
 | 
			
		||||
        shifts = shifts.scalars().all()
 | 
			
		||||
        response = ActiveWorkShiftsResponse(shifts=shifts)
 | 
			
		||||
        return response
 | 
			
		||||
        return GetWorkShiftsResponse(
 | 
			
		||||
            shifts=shifts,
 | 
			
		||||
            pagination_info=PaginationInfoSchema(
 | 
			
		||||
                total_pages=total_pages,
 | 
			
		||||
                total_items=total_shifts,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    async def delete_shift(self, shift_id: int) -> DeleteShiftResponse:
 | 
			
		||||
        stmt = (
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user