feat: work shifts history
This commit is contained in:
@@ -29,7 +29,7 @@ class Expense(BaseModel):
|
|||||||
amount: Mapped[float] = mapped_column()
|
amount: Mapped[float] = mapped_column()
|
||||||
|
|
||||||
created_by_user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
|
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(
|
tags: Mapped[list["ExpenseTag"]] = relationship(
|
||||||
secondary=expenses_expense_tags,
|
secondary=expenses_expense_tags,
|
||||||
|
|||||||
@@ -27,4 +27,5 @@ class WorkShift(BaseModel):
|
|||||||
user: Mapped["User"] = relationship(
|
user: Mapped["User"] = relationship(
|
||||||
"User",
|
"User",
|
||||||
back_populates="work_shifts",
|
back_populates="work_shifts",
|
||||||
|
lazy="selectin",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ from io import BytesIO
|
|||||||
|
|
||||||
from fastapi import APIRouter, Response
|
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 generators.work_shifts_qr_code_generator import WorkShiftsQRCodeGenerator
|
||||||
from schemas.work_shifts import StartShiftResponse, FinishShiftResponse, ActiveWorkShiftsResponse, DeleteShiftResponse, \
|
from schemas.work_shifts import *
|
||||||
FinishShiftByIdResponse
|
|
||||||
from services.work_shifts import WorkShiftsService
|
from services.work_shifts import WorkShiftsService
|
||||||
|
|
||||||
work_shifts_router = APIRouter(
|
work_shifts_router = APIRouter(
|
||||||
@@ -65,14 +64,16 @@ async def finish_work_shift_by_id(
|
|||||||
|
|
||||||
|
|
||||||
@work_shifts_router.get(
|
@work_shifts_router.get(
|
||||||
"/get-active-shifts",
|
"/get-shifts/{is_active}",
|
||||||
response_model=ActiveWorkShiftsResponse,
|
response_model=GetWorkShiftsResponse,
|
||||||
operation_id="get_active_shifts",
|
operation_id="get_shifts",
|
||||||
)
|
)
|
||||||
async def get_active_shifts(
|
async def get_shifts(
|
||||||
session: SessionDependency,
|
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(
|
@work_shifts_router.delete(
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
from datetime import datetime
|
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
|
from schemas.user import UserSchema
|
||||||
|
|
||||||
|
|
||||||
# region Entities
|
# region Entities
|
||||||
|
|
||||||
class ActiveWorkShiftSchema(BaseSchema):
|
class WorkShiftSchema(BaseSchema):
|
||||||
id: int
|
id: int
|
||||||
started_at: datetime
|
started_at: datetime
|
||||||
|
finished_at: Optional[datetime] = None
|
||||||
|
hours: Optional[float] = None
|
||||||
user: UserSchema
|
user: UserSchema
|
||||||
|
|
||||||
|
|
||||||
@@ -29,8 +31,9 @@ class FinishShiftByIdResponse(OkMessageSchema):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ActiveWorkShiftsResponse(BaseSchema):
|
class GetWorkShiftsResponse(BaseSchema):
|
||||||
shifts: List[ActiveWorkShiftSchema]
|
shifts: List[WorkShiftSchema]
|
||||||
|
pagination_info: PaginationInfoSchema
|
||||||
|
|
||||||
|
|
||||||
class DeleteShiftResponse(OkMessageSchema):
|
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 sqlalchemy.orm import joinedload
|
||||||
|
|
||||||
from models import WorkShift, User
|
from models import WorkShift, User
|
||||||
|
from schemas.base import PaginationSchema
|
||||||
from schemas.time_tracking import UpdateTimeTrackingRecordRequest
|
from schemas.time_tracking import UpdateTimeTrackingRecordRequest
|
||||||
from schemas.user import *
|
from schemas.work_shifts import *
|
||||||
from schemas.work_shifts import StartShiftResponse, FinishShiftResponse, ActiveWorkShiftsResponse, DeleteShiftResponse, \
|
|
||||||
FinishShiftByIdResponse
|
|
||||||
from services.base import BaseService
|
from services.base import BaseService
|
||||||
from services.time_tracking import TimeTrackingService
|
from services.time_tracking import TimeTrackingService
|
||||||
|
from utils.dependecies import is_valid_pagination
|
||||||
from utils.work_time import hours_to_hours_and_minutes
|
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)
|
hours, minutes = hours_to_hours_and_minutes(hours)
|
||||||
return FinishShiftByIdResponse(ok=True, message=f"Смена закончена. Отработано {hours} ч. {minutes} мин.")
|
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 = (
|
stmt = (
|
||||||
select(WorkShift)
|
select(WorkShift)
|
||||||
.options(joinedload(WorkShift.user))
|
.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())
|
.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)
|
return GetWorkShiftsResponse(
|
||||||
shifts = shifts.scalars().all()
|
shifts=shifts,
|
||||||
response = ActiveWorkShiftsResponse(shifts=shifts)
|
pagination_info=PaginationInfoSchema(
|
||||||
return response
|
total_pages=total_pages,
|
||||||
|
total_items=total_shifts,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
async def delete_shift(self, shift_id: int) -> DeleteShiftResponse:
|
async def delete_shift(self, shift_id: int) -> DeleteShiftResponse:
|
||||||
stmt = (
|
stmt = (
|
||||||
|
|||||||
Reference in New Issue
Block a user