feat: time tracking

This commit is contained in:
2024-08-03 05:39:05 +03:00
parent 82c9126d87
commit 58220a246b
7 changed files with 245 additions and 63 deletions

View File

@@ -41,6 +41,7 @@ routers_list = [
routers.role_router, routers.role_router,
routers.marketplace_router, routers.marketplace_router,
routers.payroll_router, routers.payroll_router,
routers.time_tracking_router,
] ]
for router in routers_list: for router in routers_list:
app.include_router(router) app.include_router(router)

View File

@@ -10,3 +10,4 @@ from .user import user_router
from .role import role_router from .role import role_router
from .marketplace import marketplace_router from .marketplace import marketplace_router
from .payroll import payroll_router from .payroll import payroll_router
from .time_tracking import time_tracking_router

36
routers/time_tracking.py Normal file
View File

@@ -0,0 +1,36 @@
from fastapi import APIRouter
from backend.dependecies import SessionDependency, CurrentUserDependency
from schemas.time_tracking import *
from services.time_tracking import TimeTrackingService
time_tracking_router = APIRouter(
prefix="/time-tracking",
tags=["time-tracking"]
)
@time_tracking_router.post(
'/get-records',
operation_id='get_time_tracking_records',
response_model=GetTimeTrackingRecordsResponse
)
async def get_data(
session: SessionDependency,
request: GetTimeTrackingRecordsRequest
):
return await TimeTrackingService(session).get_records(request)
@time_tracking_router.post(
'/update-record',
operation_id='update_time_tracking_record',
response_model=UpdateTimeTrackingRecordResponse
)
async def get_data(
session: SessionDependency,
request: UpdateTimeTrackingRecordRequest,
user: CurrentUserDependency
):
return await TimeTrackingService(session).update_record(user, request)

45
schemas/time_tracking.py Normal file
View File

@@ -0,0 +1,45 @@
import datetime
from typing import List
from schemas.base import BaseSchema, OkMessageSchema
from schemas.user import UserSchema
# region Entities
class TimeTrackingData(BaseSchema):
date: datetime.date
hours: int
amount: int
class TimeTrackingRecord(BaseSchema):
user: UserSchema
total_amount: int
data: List[TimeTrackingData]
# endregion
# region Requests
class GetTimeTrackingRecordsRequest(BaseSchema):
date: datetime.date
user_ids: list[int]
class UpdateTimeTrackingRecordRequest(BaseSchema):
user_id: int
date: datetime.date
hours: int
# endregion
# region Responses
class GetTimeTrackingRecordsResponse(BaseSchema):
records: List[TimeTrackingRecord]
class UpdateTimeTrackingRecordResponse(OkMessageSchema):
pass
# endregion

View File

@@ -167,11 +167,10 @@ class PayrollService(BaseService):
) )
return response return response
async def _create_payment_record_hourly( def get_amount(
self, self,
creator: User,
user: User, user: User,
record_schema: PaymentRecordCreateSchema work_units: int
): ):
pay_rate: PayRate = user.pay_rate pay_rate: PayRate = user.pay_rate
overtime_threshold = 0 overtime_threshold = 0
@@ -183,13 +182,22 @@ class PayrollService(BaseService):
overtime_rate = pay_rate.overtime_rate overtime_rate = pay_rate.overtime_rate
if overtime_threshold == 0 or overtime_rate == 0: if overtime_threshold == 0 or overtime_rate == 0:
base_units = record_schema.work_units base_units = work_units
overtime_units = 0 overtime_units = 0
else: else:
overtime_units = max(0, record_schema.work_units - overtime_threshold) overtime_units = max(0, work_units - overtime_threshold)
base_units = record_schema.work_units - overtime_units base_units = work_units - overtime_units
amount = pay_rate.base_rate * base_units + overtime_rate * overtime_units return pay_rate.base_rate * base_units + overtime_rate * overtime_units
async def _create_payment_record_hourly(
self,
creator: User,
user: User,
record_schema: PaymentRecordCreateSchema
):
pay_rate: PayRate = user.pay_rate
amount = self.get_amount(user, record_schema.work_units)
payment_record_dict = record_schema.model_dump() payment_record_dict = record_schema.model_dump()
del payment_record_dict['user'] del payment_record_dict['user']
payment_record_dict.update({ payment_record_dict.update({
@@ -202,54 +210,14 @@ class PayrollService(BaseService):
stmt = ( stmt = (
insert( insert(
PaymentRecord PaymentRecord
).values( )
.values(
**payment_record_dict **payment_record_dict
) )
) )
await self.session.execute(stmt) await self.session.execute(stmt)
await self.session.commit() await self.session.commit()
async def _create_payment_record_daily(
self,
creator: User,
user: User,
record_schema: PaymentRecordCreateSchema
):
pay_rate: PayRate = user.pay_rate
amount = pay_rate.base_rate * record_schema.work_units
payment_record_dict = record_schema.model_dump()
del payment_record_dict['user']
payment_record_dict.update({
'created_by_user_id': creator.id,
'created_at': datetime.datetime.now(),
'payroll_scheme_key': pay_rate.payroll_scheme.key,
'amount': amount,
'user_id': record_schema.user.id
})
async def _create_payment_record_monthly(
self,
creator: User,
user: User,
record_schema: PaymentRecordCreateSchema
) -> CreatePaymentRecordResponse:
pay_rate: PayRate = user.pay_rate
amount = pay_rate.base_rate * record_schema.work_units
payment_record_dict = record_schema.model_dump()
del payment_record_dict['user']
payment_record_dict.update({
'created_by_user_id': creator.id,
'created_at': datetime.datetime.now(),
'payroll_scheme_key': pay_rate.payroll_scheme.key,
'amount': amount,
'user_id': record_schema.user.id
})
async def create_payment_record( async def create_payment_record(
self, self,
request: CreatePaymentRecordRequest, request: CreatePaymentRecordRequest,
@@ -282,7 +250,8 @@ class PayrollService(BaseService):
stmt = ( stmt = (
delete( delete(
PaymentRecord PaymentRecord
).where( )
.where(
PaymentRecord.id == request.payment_record_id PaymentRecord.id == request.payment_record_id
) )
) )

111
services/time_tracking.py Normal file
View File

@@ -0,0 +1,111 @@
from collections import defaultdict
from sqlalchemy import select, func
from sqlalchemy.orm import joinedload
from models import PaymentRecord, User
from schemas.time_tracking import *
from services.base import BaseService
from services.payroll import PayrollService
from services.user import UserService
class TimeTrackingService(BaseService):
async def get_records(self, request: GetTimeTrackingRecordsRequest) -> GetTimeTrackingRecordsResponse:
stmt = (
select(
PaymentRecord,
)
.options(
joinedload(
PaymentRecord.user
)
)
.where(
func.date(func.date_trunc('month', PaymentRecord.start_date)) == request.date,
func.date(func.date_trunc('month', PaymentRecord.end_date)) == request.date,
PaymentRecord.start_date == PaymentRecord.end_date,
# PaymentRecord.user_id.in_(request.user_ids)
)
)
query_result = (await self.session.scalars(stmt)).all()
records_dict = defaultdict(list)
users_dict = {}
amount_dict = defaultdict(list)
for payment_record in query_result:
user = UserSchema.model_validate(payment_record.user)
data = TimeTrackingData(
date=payment_record.start_date,
hours=payment_record.work_units,
amount=payment_record.amount
)
users_dict[user.id] = user
records_dict[user.id].append(data)
amount_dict[user.id].append(payment_record.amount)
records = []
for user_id, data_list in records_dict.items():
amount = sum(amount_dict[user_id])
user = users_dict[user_id]
record = TimeTrackingRecord(
user=user,
data=data_list,
total_amount=amount
)
records.append(
record
)
return GetTimeTrackingRecordsResponse(
records=records
)
async def update_record(self,
user: User,
request: UpdateTimeTrackingRecordRequest
) -> UpdateTimeTrackingRecordResponse:
try:
record_user = await UserService(self.session).get_by_id(user_id=request.user_id)
if not record_user:
return UpdateTimeTrackingRecordResponse(ok=False, message="Указанный пользователь не найден!")
if not record_user.pay_rate:
return UpdateTimeTrackingRecordResponse(ok=False, message="У пользователя не указана схема оплаты!")
existing_record_stmt = (
select(
PaymentRecord
)
.where(
PaymentRecord.user_id == request.user_id,
PaymentRecord.start_date == request.date,
PaymentRecord.end_date == request.date,
)
)
amount = (
PayrollService(
self.session
)
.get_amount(
user=record_user,
work_units=request.hours
)
)
existing_record = await self.session.scalar(existing_record_stmt)
if existing_record:
existing_record: PaymentRecord
existing_record.work_units = request.hours
existing_record.amount = amount
else:
new_record = PaymentRecord(
user_id=request.user_id,
created_by_user_id=user.id,
start_date=request.date,
end_date=request.date,
created_at=datetime.datetime.now(),
payroll_scheme_key=record_user.pay_rate.payroll_scheme_key,
amount=amount,
work_units=request.hours
)
self.session.add(new_record)
await self.session.commit()
return UpdateTimeTrackingRecordResponse(ok=True, message="Запись успешно обновлена")
except Exception as e:
return UpdateTimeTrackingRecordResponse(ok=False, message=str(e))

43
test.py
View File

@@ -1,25 +1,44 @@
import asyncio import asyncio
import datetime
from sqlalchemy import select from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload
from backend.session import session_maker from backend.session import session_maker
from models import User from models import User, PaymentRecord
async def main(): async def main():
work_units = 15
base_rate = 0
overtime_units = max([0, work_units - base_rate])
base_units = work_units - overtime_units
print(overtime_units, base_units)
return
session: AsyncSession = session_maker() session: AsyncSession = session_maker()
a = await session.scalar( try:
select(User).where(User.first_name == "Абид") d = datetime.date.today()
d = d.replace(day=1)
print(d)
stmt = (
select(
PaymentRecord
) )
print(a) .select_from(PaymentRecord)
.options(
joinedload(
PaymentRecord.user
)
)
.where(
func.date(func.date_trunc('month', PaymentRecord.start_date)) == d,
func.date(func.date_trunc('month', PaymentRecord.end_date)) == d,
PaymentRecord.start_date == PaymentRecord.end_date,
# PaymentRecord.user_id.in_(request.user_ids)
)
)
print(stmt.compile(compile_kwargs={
'literal_binds': True
}))
query_result = (await session.scalars(stmt)).all()
print(query_result)
except Exception as e:
print(e)
await session.close() await session.close()