feat: work shift pauses
This commit is contained in:
@@ -2,6 +2,7 @@ from datetime import datetime
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from sqlalchemy import ForeignKey
|
from sqlalchemy import ForeignKey
|
||||||
|
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
|
||||||
@@ -20,6 +21,10 @@ class WorkShift(BaseModel):
|
|||||||
finished_at: Mapped[datetime] = mapped_column(
|
finished_at: Mapped[datetime] = mapped_column(
|
||||||
nullable=True,
|
nullable=True,
|
||||||
)
|
)
|
||||||
|
is_paused: Mapped[bool] = mapped_column(
|
||||||
|
default=False,
|
||||||
|
server_default=expression.false(),
|
||||||
|
)
|
||||||
|
|
||||||
user_id: Mapped[int] = mapped_column(
|
user_id: Mapped[int] = mapped_column(
|
||||||
ForeignKey("users.id"),
|
ForeignKey("users.id"),
|
||||||
@@ -29,3 +34,32 @@ class WorkShift(BaseModel):
|
|||||||
back_populates="work_shifts",
|
back_populates="work_shifts",
|
||||||
lazy="selectin",
|
lazy="selectin",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pauses: Mapped[list["WorkShiftPause"]] = relationship(
|
||||||
|
"WorkShiftPause",
|
||||||
|
back_populates="work_shift",
|
||||||
|
uselist=True,
|
||||||
|
foreign_keys="[WorkShiftPause.work_shift_id]",
|
||||||
|
cascade="all, delete",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkShiftPause(BaseModel):
|
||||||
|
__tablename__ = "work_shifts_pauses"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
started_at: Mapped[datetime] = mapped_column(
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
finished_at: Mapped[datetime] = mapped_column(
|
||||||
|
nullable=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
work_shift_id: Mapped[int] = mapped_column(
|
||||||
|
ForeignKey("work_shifts.id"),
|
||||||
|
)
|
||||||
|
work_shift: Mapped[WorkShift] = relationship(
|
||||||
|
"WorkShift",
|
||||||
|
back_populates="pauses",
|
||||||
|
lazy="selectin",
|
||||||
|
)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ async def finish_shift(
|
|||||||
user_id: int,
|
user_id: int,
|
||||||
user: CurrentUserDependency,
|
user: CurrentUserDependency,
|
||||||
):
|
):
|
||||||
return await WorkShiftsService(session).finish_shift(user, user_id)
|
return await WorkShiftsService(session).finish_shift_by_user_id(user, user_id)
|
||||||
|
|
||||||
|
|
||||||
@work_shifts_router.post(
|
@work_shifts_router.post(
|
||||||
@@ -86,3 +86,51 @@ async def delete_work_shift(
|
|||||||
shift_id: int,
|
shift_id: int,
|
||||||
):
|
):
|
||||||
return await WorkShiftsService(session).delete_shift(shift_id)
|
return await WorkShiftsService(session).delete_shift(shift_id)
|
||||||
|
|
||||||
|
|
||||||
|
@work_shifts_router.post(
|
||||||
|
"/pause/start/{shift_id}",
|
||||||
|
response_model=StartPauseByShiftIdResponse,
|
||||||
|
operation_id="start_pause_by_shift_id",
|
||||||
|
)
|
||||||
|
async def start_pause_by_shift_id(
|
||||||
|
session: SessionDependency,
|
||||||
|
shift_id: int,
|
||||||
|
):
|
||||||
|
return await WorkShiftsService(session).start_pause_by_shift_id(shift_id)
|
||||||
|
|
||||||
|
|
||||||
|
@work_shifts_router.post(
|
||||||
|
"/pause/start/for-user/{user_id}",
|
||||||
|
response_model=StartPauseByUserIdResponse,
|
||||||
|
operation_id="start_pause_by_user_id",
|
||||||
|
)
|
||||||
|
async def start_pause_by_user_id(
|
||||||
|
session: SessionDependency,
|
||||||
|
user_id: int,
|
||||||
|
):
|
||||||
|
return await WorkShiftsService(session).start_pause_by_user_id(user_id)
|
||||||
|
|
||||||
|
|
||||||
|
@work_shifts_router.post(
|
||||||
|
"/pause/finish/{shift_id}",
|
||||||
|
response_model=FinishPauseByShiftIdResponse,
|
||||||
|
operation_id="finish_pause_by_shift_id",
|
||||||
|
)
|
||||||
|
async def finish_pause_by_shift_id(
|
||||||
|
session: SessionDependency,
|
||||||
|
shift_id: int,
|
||||||
|
):
|
||||||
|
return await WorkShiftsService(session).finish_pause_by_shift_id(shift_id)
|
||||||
|
|
||||||
|
|
||||||
|
@work_shifts_router.post(
|
||||||
|
"/pause/finish/for-user/{shift_id}",
|
||||||
|
response_model=FinishPauseByUserIdResponse,
|
||||||
|
operation_id="finish_pause_by_user_id",
|
||||||
|
)
|
||||||
|
async def finish_pause_by_user_id(
|
||||||
|
session: SessionDependency,
|
||||||
|
user_id: int,
|
||||||
|
):
|
||||||
|
return await WorkShiftsService(session).finish_pause_by_user_id(user_id)
|
||||||
|
|||||||
@@ -11,10 +11,16 @@ class WorkShiftSchema(BaseSchema):
|
|||||||
id: int
|
id: int
|
||||||
started_at: datetime
|
started_at: datetime
|
||||||
finished_at: Optional[datetime] = None
|
finished_at: Optional[datetime] = None
|
||||||
hours: Optional[float] = None
|
is_paused: Optional[bool] = None
|
||||||
user: UserSchema
|
user: UserSchema
|
||||||
|
|
||||||
|
|
||||||
|
class WorkShiftRowSchema(BaseSchema):
|
||||||
|
work_shift: WorkShiftSchema
|
||||||
|
total_hours: Optional[float] = None
|
||||||
|
pause_hours: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
# region Responses
|
# region Responses
|
||||||
@@ -32,11 +38,28 @@ class FinishShiftByIdResponse(OkMessageSchema):
|
|||||||
|
|
||||||
|
|
||||||
class GetWorkShiftsResponse(BaseSchema):
|
class GetWorkShiftsResponse(BaseSchema):
|
||||||
shifts: List[WorkShiftSchema]
|
shifts: List[WorkShiftRowSchema]
|
||||||
pagination_info: PaginationInfoSchema
|
pagination_info: PaginationInfoSchema
|
||||||
|
|
||||||
|
|
||||||
class DeleteShiftResponse(OkMessageSchema):
|
class DeleteShiftResponse(OkMessageSchema):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class StartPauseByUserIdResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FinishPauseByUserIdResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class StartPauseByShiftIdResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FinishPauseByShiftIdResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ from datetime import date, timedelta
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from sqlalchemy import select, delete, func
|
from sqlalchemy import select, func, extract, literal, label, Select
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload, selectinload
|
||||||
|
|
||||||
from models import WorkShift, User
|
from models import WorkShift, User
|
||||||
|
from models.work_shifts import WorkShiftPause
|
||||||
from schemas.base import PaginationSchema
|
from schemas.base import PaginationSchema
|
||||||
from schemas.time_tracking import UpdateTimeTrackingRecordRequest
|
from schemas.time_tracking import UpdateTimeTrackingRecordRequest
|
||||||
from schemas.work_shifts import *
|
from schemas.work_shifts import *
|
||||||
@@ -16,9 +17,10 @@ from utils.work_time import hours_to_hours_and_minutes
|
|||||||
|
|
||||||
|
|
||||||
class WorkShiftsService(BaseService):
|
class WorkShiftsService(BaseService):
|
||||||
async def _get_last_work_shift_for_today(self, user_id: int) -> Optional[WorkShift]:
|
async def _get_last_work_shift(self, user_id: int, for_today: bool = False) -> Optional[WorkShift]:
|
||||||
stmt = (
|
stmt = (
|
||||||
select(WorkShift)
|
select(WorkShift)
|
||||||
|
.options(selectinload(WorkShift.pauses))
|
||||||
.where(WorkShift.user_id == user_id)
|
.where(WorkShift.user_id == user_id)
|
||||||
.order_by(WorkShift.started_at.desc())
|
.order_by(WorkShift.started_at.desc())
|
||||||
.limit(1)
|
.limit(1)
|
||||||
@@ -28,16 +30,17 @@ class WorkShiftsService(BaseService):
|
|||||||
work_shift = work_shift.one_or_none()
|
work_shift = work_shift.one_or_none()
|
||||||
work_shift = work_shift[0] if work_shift else None
|
work_shift = work_shift[0] if work_shift else None
|
||||||
|
|
||||||
if work_shift and work_shift.started_at.date() == date.today():
|
if for_today:
|
||||||
return work_shift
|
return work_shift if work_shift and work_shift.started_at.date() == date.today() else None
|
||||||
return None
|
|
||||||
|
return work_shift
|
||||||
|
|
||||||
async def start_shift(self, user_id: int) -> StartShiftResponse:
|
async def start_shift(self, user_id: int) -> StartShiftResponse:
|
||||||
employee = await self.session.get(User, user_id)
|
employee = await self.session.get(User, user_id)
|
||||||
if not employee or employee.is_deleted:
|
if not employee or employee.is_deleted:
|
||||||
return StartShiftResponse(ok=False, message=f"Пользователь с ID {user_id} не найден")
|
return StartShiftResponse(ok=False, message=f"Пользователь с ID {user_id} не найден")
|
||||||
|
|
||||||
work_shift = await self._get_last_work_shift_for_today(user_id)
|
work_shift = await self._get_last_work_shift(user_id, for_today=True)
|
||||||
if work_shift:
|
if work_shift:
|
||||||
if not work_shift.finished_at:
|
if not work_shift.finished_at:
|
||||||
return StartShiftResponse(ok=False, message="Предыдущая смена еще не закончена")
|
return StartShiftResponse(ok=False, message="Предыдущая смена еще не закончена")
|
||||||
@@ -48,90 +51,153 @@ class WorkShiftsService(BaseService):
|
|||||||
await self.session.commit()
|
await self.session.commit()
|
||||||
return StartShiftResponse(ok=True, message="Смена начата")
|
return StartShiftResponse(ok=True, message="Смена начата")
|
||||||
|
|
||||||
async def finish_shift(self, user: User, user_id: int) -> FinishShiftResponse:
|
async def finish_shift_by_user_id(self, user: User, user_id: int) -> FinishShiftResponse:
|
||||||
employee = await self.session.get(User, user_id)
|
employee = await self.session.get(User, user_id)
|
||||||
if not employee or employee.is_deleted:
|
if not employee or employee.is_deleted:
|
||||||
return FinishShiftResponse(ok=False, message=f"Пользователь с ID {user_id} не найден")
|
return FinishShiftResponse(ok=False, message=f"Пользователь с ID {user_id} не найден")
|
||||||
|
|
||||||
work_shift = await self._get_last_work_shift_for_today(user_id)
|
work_shift = await self._get_last_work_shift(user_id)
|
||||||
|
|
||||||
if not work_shift or work_shift.finished_at:
|
ok, message = await self._finish_shift_common(user, work_shift)
|
||||||
return FinishShiftResponse(ok=False, message="Смена для сотрудника еще не начата")
|
return FinishShiftResponse(ok=ok, message=message)
|
||||||
|
|
||||||
work_shift.finished_at = datetime.now()
|
|
||||||
await self.session.commit()
|
|
||||||
|
|
||||||
diff: timedelta = work_shift.finished_at - work_shift.started_at
|
|
||||||
hours = diff.total_seconds() / 3600
|
|
||||||
|
|
||||||
if diff.total_seconds() < 60:
|
|
||||||
return FinishShiftResponse(ok=True, message=f"Смена закончена. Отработано 0 ч. 0 мин.")
|
|
||||||
|
|
||||||
data = UpdateTimeTrackingRecordRequest(
|
|
||||||
user_id=user_id,
|
|
||||||
date=work_shift.started_at.date(),
|
|
||||||
hours=hours,
|
|
||||||
)
|
|
||||||
await TimeTrackingService(self.session).update_record(user, data)
|
|
||||||
|
|
||||||
hours, minutes = hours_to_hours_and_minutes(hours)
|
|
||||||
return FinishShiftResponse(ok=True, message=f"Смена закончена. Отработано {hours} ч. {minutes} мин.")
|
|
||||||
|
|
||||||
async def finish_shift_by_id(self, user: User, shift_id: int) -> FinishShiftByIdResponse:
|
async def finish_shift_by_id(self, user: User, shift_id: int) -> FinishShiftByIdResponse:
|
||||||
work_shift = await self.session.get(WorkShift, shift_id)
|
stmt = (
|
||||||
|
select(WorkShift)
|
||||||
|
.options(selectinload(WorkShift.pauses))
|
||||||
|
.where(WorkShift.id == shift_id)
|
||||||
|
)
|
||||||
|
work_shift = await self.session.execute(stmt)
|
||||||
|
work_shift = work_shift.scalars().one_or_none()
|
||||||
|
|
||||||
|
ok, message = await self._finish_shift_common(user, work_shift)
|
||||||
|
return FinishShiftByIdResponse(ok=ok, message=message)
|
||||||
|
|
||||||
|
async def _finish_shift_common(self, user: User, work_shift: Optional[WorkShift]) -> tuple[bool, str]:
|
||||||
if not work_shift or work_shift.finished_at:
|
if not work_shift or work_shift.finished_at:
|
||||||
return FinishShiftByIdResponse(ok=False, message="Смена для сотрудника еще не начата")
|
return False, "Смена для сотрудника еще не начата"
|
||||||
|
|
||||||
|
if work_shift.is_paused:
|
||||||
|
await self.finish_pause_by_shift_id(work_shift.id)
|
||||||
|
|
||||||
work_shift.finished_at = datetime.now()
|
work_shift.finished_at = datetime.now()
|
||||||
await self.session.commit()
|
await self.session.commit()
|
||||||
|
|
||||||
diff: timedelta = work_shift.finished_at - work_shift.started_at
|
pause_time = timedelta()
|
||||||
hours = diff.total_seconds() / 3600
|
for pause in work_shift.pauses:
|
||||||
|
pause_time += pause.finished_at - pause.started_at
|
||||||
|
|
||||||
if diff.total_seconds() < 60:
|
total_work_time: timedelta = work_shift.finished_at - work_shift.started_at
|
||||||
return FinishShiftByIdResponse(ok=True, message=f"Смена закончена. Отработано 0 ч. 0 мин.")
|
pure_work_seconds = total_work_time.total_seconds() - pause_time.total_seconds()
|
||||||
|
hours = pure_work_seconds / 3600
|
||||||
|
|
||||||
data = UpdateTimeTrackingRecordRequest(
|
if pure_work_seconds >= 60:
|
||||||
user_id=work_shift.user_id,
|
data = UpdateTimeTrackingRecordRequest(
|
||||||
date=work_shift.started_at.date(),
|
user_id=work_shift.user_id,
|
||||||
hours=hours,
|
date=work_shift.started_at.date(),
|
||||||
|
hours=hours,
|
||||||
|
)
|
||||||
|
await TimeTrackingService(self.session).update_record(user, data)
|
||||||
|
|
||||||
|
hours, minutes = hours_to_hours_and_minutes(total_work_time)
|
||||||
|
return True, f"Смена закончена. Отработано {hours} ч. {minutes} мин."
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_work_shifts_history_stmt() -> Select:
|
||||||
|
sub_hours = (
|
||||||
|
select(
|
||||||
|
WorkShift.id,
|
||||||
|
label(
|
||||||
|
"total_hours",
|
||||||
|
extract('epoch', WorkShift.finished_at) - extract('epoch', WorkShift.started_at)
|
||||||
|
),
|
||||||
|
func.sum(
|
||||||
|
extract('epoch', WorkShiftPause.finished_at) - extract('epoch', WorkShiftPause.started_at)
|
||||||
|
).label("pause_hours"),
|
||||||
|
)
|
||||||
|
.join(WorkShiftPause, isouter=True)
|
||||||
|
.where(WorkShift.finished_at.is_not(None))
|
||||||
|
.group_by(WorkShift.id)
|
||||||
|
.subquery()
|
||||||
)
|
)
|
||||||
await TimeTrackingService(self.session).update_record(user, data)
|
|
||||||
|
|
||||||
hours, minutes = hours_to_hours_and_minutes(hours)
|
return (
|
||||||
return FinishShiftByIdResponse(ok=True, message=f"Смена закончена. Отработано {hours} ч. {minutes} мин.")
|
select(
|
||||||
|
WorkShift,
|
||||||
|
sub_hours.c.total_hours,
|
||||||
|
sub_hours.c.pause_hours,
|
||||||
|
)
|
||||||
|
.join(sub_hours, sub_hours.c.id == WorkShift.id)
|
||||||
|
.options(joinedload(WorkShift.user))
|
||||||
|
.order_by(WorkShift.started_at.desc())
|
||||||
|
)
|
||||||
|
|
||||||
async def get_shifts(self, is_active: bool, pagination: PaginationSchema) -> GetWorkShiftsResponse:
|
@staticmethod
|
||||||
|
def get_active_work_shifts_stmt() -> Select:
|
||||||
|
return (
|
||||||
|
select(
|
||||||
|
WorkShift,
|
||||||
|
literal(0),
|
||||||
|
literal(0),
|
||||||
|
)
|
||||||
|
.options(joinedload(WorkShift.user))
|
||||||
|
.where(WorkShift.finished_at.is_(None))
|
||||||
|
.order_by(WorkShift.started_at.desc())
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate_pagination(pagination: PaginationSchema):
|
||||||
if not is_valid_pagination(pagination):
|
if not is_valid_pagination(pagination):
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid 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(
|
async def get_total_shifts_count(self, is_active: bool) -> int:
|
||||||
|
count_query = (
|
||||||
select(func.count())
|
select(func.count())
|
||||||
.select_from(WorkShift)
|
.select_from(WorkShift)
|
||||||
.where(WorkShift.finished_at.is_(None) if is_active else WorkShift.finished_at.is_not(None))
|
.where(WorkShift.finished_at.is_(None) if is_active else WorkShift.finished_at.is_not(None))
|
||||||
)
|
)
|
||||||
if not total_shifts:
|
return await self.session.scalar(count_query)
|
||||||
return GetWorkShiftsResponse(
|
|
||||||
shifts=[],
|
|
||||||
pagination_info=PaginationInfoSchema(
|
|
||||||
total_pages=0,
|
|
||||||
total_items=0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def empty_shifts_response() -> GetWorkShiftsResponse:
|
||||||
|
return GetWorkShiftsResponse(
|
||||||
|
shifts=[],
|
||||||
|
pagination_info=PaginationInfoSchema(
|
||||||
|
total_pages=0,
|
||||||
|
total_items=0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_shifts(self, is_active: bool, pagination: PaginationSchema) -> GetWorkShiftsResponse:
|
||||||
|
self.validate_pagination(pagination)
|
||||||
|
|
||||||
|
page = max(0, pagination.page - 1)
|
||||||
|
|
||||||
|
total_shifts = await self.get_total_shifts_count(is_active)
|
||||||
|
if not total_shifts:
|
||||||
|
return self.empty_shifts_response()
|
||||||
total_pages = math.ceil(total_shifts / pagination.items_per_page)
|
total_pages = math.ceil(total_shifts / pagination.items_per_page)
|
||||||
|
|
||||||
stmt = (
|
if is_active:
|
||||||
select(WorkShift)
|
stmt = self.get_active_work_shifts_stmt()
|
||||||
.options(joinedload(WorkShift.user))
|
else:
|
||||||
.where(WorkShift.finished_at.is_(None) if is_active else WorkShift.finished_at.is_not(None))
|
stmt = self.get_work_shifts_history_stmt()
|
||||||
.order_by(WorkShift.started_at.desc())
|
|
||||||
|
stmt_with_pagination = (
|
||||||
|
stmt
|
||||||
.offset(page * pagination.items_per_page)
|
.offset(page * pagination.items_per_page)
|
||||||
.limit(pagination.items_per_page)
|
.limit(pagination.items_per_page)
|
||||||
)
|
)
|
||||||
shifts = (await self.session.execute(stmt)).scalars().all()
|
|
||||||
|
shifts_rows = await self.session.execute(stmt_with_pagination)
|
||||||
|
shifts = []
|
||||||
|
for shift, total_hours, pause_hours in shifts_rows:
|
||||||
|
shift = WorkShiftRowSchema(
|
||||||
|
work_shift=shift,
|
||||||
|
total_hours=total_hours,
|
||||||
|
pause_hours=pause_hours,
|
||||||
|
)
|
||||||
|
shifts.append(shift)
|
||||||
|
|
||||||
return GetWorkShiftsResponse(
|
return GetWorkShiftsResponse(
|
||||||
shifts=shifts,
|
shifts=shifts,
|
||||||
@@ -142,10 +208,77 @@ class WorkShiftsService(BaseService):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def delete_shift(self, shift_id: int) -> DeleteShiftResponse:
|
async def delete_shift(self, shift_id: int) -> DeleteShiftResponse:
|
||||||
stmt = (
|
work_shift = await self.session.get(WorkShift, shift_id)
|
||||||
delete(WorkShift)
|
if work_shift:
|
||||||
.where(WorkShift.id == shift_id)
|
await self.session.delete(work_shift)
|
||||||
)
|
await self.session.commit()
|
||||||
await self.session.execute(stmt)
|
|
||||||
await self.session.commit()
|
|
||||||
return DeleteShiftResponse(ok=True, message="Запись о смене успешно удалена")
|
return DeleteShiftResponse(ok=True, message="Запись о смене успешно удалена")
|
||||||
|
|
||||||
|
async def _get_last_work_shift_pause(self, work_shift_id: int) -> Optional[WorkShiftPause]:
|
||||||
|
stmt = (
|
||||||
|
select(WorkShiftPause)
|
||||||
|
.where(WorkShiftPause.work_shift_id == work_shift_id)
|
||||||
|
.order_by(WorkShiftPause.started_at.desc())
|
||||||
|
.limit(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
work_shift_pause = await self.session.execute(stmt)
|
||||||
|
work_shift_pause = work_shift_pause.one_or_none()
|
||||||
|
work_shift_pause = work_shift_pause[0] if work_shift_pause else None
|
||||||
|
|
||||||
|
return work_shift_pause if work_shift_pause and work_shift_pause.started_at.date() == date.today() else None
|
||||||
|
|
||||||
|
async def start_pause(self, work_shift: Optional[WorkShift]) -> tuple[bool, str]:
|
||||||
|
if not work_shift:
|
||||||
|
return False, "Смена не найдена"
|
||||||
|
if work_shift.finished_at:
|
||||||
|
return False, "Смена уже завершена"
|
||||||
|
|
||||||
|
work_shift_pause = await self._get_last_work_shift_pause(work_shift.id)
|
||||||
|
if work_shift_pause and not work_shift_pause.finished_at:
|
||||||
|
return False, "Перерыв для смены уже начат"
|
||||||
|
|
||||||
|
work_shift.is_paused = True
|
||||||
|
work_shift_pause = WorkShiftPause(work_shift_id=work_shift.id, started_at=datetime.now())
|
||||||
|
self.session.add(work_shift_pause)
|
||||||
|
await self.session.commit()
|
||||||
|
return True, "Перерыв начат"
|
||||||
|
|
||||||
|
async def start_pause_by_user_id(self, user_id: int) -> StartPauseByUserIdResponse:
|
||||||
|
last_shift = await self._get_last_work_shift(user_id)
|
||||||
|
ok, message = await self.start_pause(last_shift)
|
||||||
|
return StartPauseByUserIdResponse(ok=ok, message=message)
|
||||||
|
|
||||||
|
async def start_pause_by_shift_id(self, shift_id: int) -> StartPauseByShiftIdResponse:
|
||||||
|
work_shift = await self.session.get(WorkShift, shift_id)
|
||||||
|
ok, message = await self.start_pause(work_shift)
|
||||||
|
return StartPauseByShiftIdResponse(ok=ok, message=message)
|
||||||
|
|
||||||
|
async def finish_pause(self, work_shift: Optional[WorkShift]) -> tuple[bool, str]:
|
||||||
|
if not work_shift:
|
||||||
|
return False, "Смена не найдена"
|
||||||
|
if work_shift.finished_at:
|
||||||
|
return False, "Смена уже завершена"
|
||||||
|
|
||||||
|
work_shift_pause = await self._get_last_work_shift_pause(work_shift.id)
|
||||||
|
if not work_shift_pause or work_shift_pause.finished_at:
|
||||||
|
return False, "Перерыв еще не начат"
|
||||||
|
|
||||||
|
work_shift.is_paused = False
|
||||||
|
work_shift_pause.finished_at = datetime.now()
|
||||||
|
await self.session.commit()
|
||||||
|
|
||||||
|
diff: timedelta = work_shift_pause.finished_at - work_shift_pause.started_at
|
||||||
|
|
||||||
|
hours, minutes = hours_to_hours_and_minutes(diff)
|
||||||
|
return True, f"Перерыв закончен: {hours} ч. {minutes} мин."
|
||||||
|
|
||||||
|
async def finish_pause_by_user_id(self, user_id: int) -> FinishPauseByUserIdResponse:
|
||||||
|
last_shift = await self._get_last_work_shift(user_id)
|
||||||
|
ok, message = await self.finish_pause(last_shift)
|
||||||
|
return FinishPauseByUserIdResponse(ok=ok, message=message)
|
||||||
|
|
||||||
|
async def finish_pause_by_shift_id(self, shift_id: int) -> FinishPauseByShiftIdResponse:
|
||||||
|
work_shift = await self.session.get(WorkShift, shift_id)
|
||||||
|
ok, message = await self.finish_pause(work_shift)
|
||||||
|
return FinishPauseByShiftIdResponse(ok=ok, message=message)
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from math import floor
|
from math import floor
|
||||||
|
|
||||||
|
|
||||||
def hours_to_hours_and_minutes(hours: float) -> tuple[int, int]:
|
def hours_to_hours_and_minutes(time: timedelta) -> tuple[int, int]:
|
||||||
|
if time.total_seconds() < 60:
|
||||||
|
return 0, 0
|
||||||
|
|
||||||
|
hours = time.total_seconds() / 3600
|
||||||
res_hours = int(floor(hours))
|
res_hours = int(floor(hours))
|
||||||
minutes = int(round((hours - res_hours) * 60))
|
minutes = int(floor((hours - res_hours) * 60))
|
||||||
return res_hours, minutes
|
return res_hours, minutes
|
||||||
|
|||||||
Reference in New Issue
Block a user