294 lines
11 KiB
Python
294 lines
11 KiB
Python
import datetime
|
||
import math
|
||
from typing import Optional
|
||
|
||
from fastapi import HTTPException
|
||
from sqlalchemy import select, insert, update, delete, func
|
||
from sqlalchemy.orm import joinedload
|
||
from starlette import status
|
||
|
||
import enums.payroll
|
||
from models import PayrollScheme, PayRate, user_pay_rate, PaymentRecord, User
|
||
from schemas.base import PaginationSchema, PaginationInfoSchema
|
||
from schemas.finances import CreatePayRateRequest, UpdatePayRateRequest, DeletePayRateRequest, \
|
||
GetAllPayrollSchemeResponse, GetAllPayRatesResponse, CreatePayRateResponse, UpdatePayRateResponse, \
|
||
DeletePayRateResponse
|
||
from schemas.payment_record import GetPaymentRecordsResponse, PaymentRecordGetSchema, CreatePaymentRecordRequest, \
|
||
CreatePaymentRecordResponse, PaymentRecordCreateSchema, DeletePaymentRecordRequest, DeletePaymentRecordResponse
|
||
from services.base import BaseService
|
||
from schemas.payroll import *
|
||
from utils.dependecies import is_valid_pagination
|
||
|
||
|
||
class PayrollService(BaseService):
|
||
async def get_all_schemas(self) -> GetAllPayrollSchemeResponse:
|
||
stmt = (select(PayrollScheme).order_by(PayrollScheme.key))
|
||
payroll_schemas = (await self.session.scalars(stmt)).all()
|
||
return GetAllPayrollSchemeResponse(
|
||
payroll_schemas=PayrollSchemeSchema.from_orm_list(payroll_schemas)
|
||
)
|
||
|
||
async def get_all_pay_rates(self) -> GetAllPayRatesResponse:
|
||
stmt = (
|
||
select(
|
||
PayRate
|
||
).order_by(
|
||
PayRate.id
|
||
)
|
||
.options(
|
||
joinedload(PayRate.payroll_scheme)
|
||
)
|
||
)
|
||
pay_rates = (await self.session.scalars(stmt)).all()
|
||
return GetAllPayRatesResponse(
|
||
pay_rates=pay_rates
|
||
)
|
||
|
||
async def create_pay_rate(self, request: CreatePayRateRequest) -> CreatePayRateResponse:
|
||
try:
|
||
# Preventing duplicate by name
|
||
if await self.session.scalar(select(PayRate).where(PayRate.name == request.data.name)):
|
||
return CreatePayRateResponse(ok=False, message="Тариф с таким названием уже существует")
|
||
pay_rate_dict = request.data.model_dump()
|
||
del pay_rate_dict['payroll_scheme']
|
||
pay_rate_dict['payroll_scheme_key'] = request.data.payroll_scheme.key
|
||
|
||
stmt = (
|
||
insert(PayRate)
|
||
.values(**pay_rate_dict)
|
||
)
|
||
await self.session.execute(stmt)
|
||
await self.session.commit()
|
||
|
||
return CreatePayRateResponse(ok=True, message='Тариф успешно создан')
|
||
except Exception as e:
|
||
return CreatePayRateResponse(ok=False, message=str(e))
|
||
|
||
async def update_pay_rate(self, request: UpdatePayRateRequest) -> UpdatePayRateResponse:
|
||
try:
|
||
# Preventing duplicate by name
|
||
stmt = (
|
||
select(
|
||
PayRate
|
||
).where(
|
||
PayRate.id == request.data.id
|
||
)
|
||
)
|
||
pay_rate = await self.session.scalar(stmt)
|
||
if not pay_rate:
|
||
return CreatePayRateResponse(ok=False, message="Указанный тариф несуществует")
|
||
pay_rate_dict = request.data.model_dump()
|
||
del pay_rate_dict['payroll_scheme']
|
||
pay_rate_dict['payroll_scheme_key'] = request.data.payroll_scheme.key
|
||
|
||
stmt = (
|
||
update(PayRate)
|
||
.values(**pay_rate_dict).where(
|
||
PayRate.id == request.data.id
|
||
)
|
||
)
|
||
await self.session.execute(stmt)
|
||
await self.session.commit()
|
||
|
||
return CreatePayRateResponse(ok=True, message='Тариф успешно обновлен')
|
||
except Exception as e:
|
||
return CreatePayRateResponse(ok=False, message=str(e))
|
||
|
||
async def delete_pay_rate(self, request: DeletePayRateRequest) -> DeletePayRateResponse:
|
||
try:
|
||
user_pay_rate_record = await (
|
||
self.session.scalar(
|
||
select(
|
||
user_pay_rate
|
||
)
|
||
.where(
|
||
user_pay_rate.c.pay_rate_id == request.pay_rate_id
|
||
)
|
||
)
|
||
)
|
||
if user_pay_rate_record:
|
||
return DeletePayRateResponse(ok=False, message="Указанный тариф привязан к пользователю")
|
||
stmt = (
|
||
delete(
|
||
PayRate
|
||
)
|
||
.where(
|
||
PayRate.id == request.pay_rate_id
|
||
)
|
||
)
|
||
await self.session.execute(stmt)
|
||
await self.session.commit()
|
||
return DeletePayRateResponse(ok=True, message="Тариф успешно удален")
|
||
except Exception as e:
|
||
return DeletePayRateResponse(ok=False, message=str(e))
|
||
|
||
async def get_payment_records(self, pagination: PaginationSchema) -> GetPaymentRecordsResponse:
|
||
if not is_valid_pagination(pagination):
|
||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid pagination')
|
||
page = max(0, pagination.page - 1)
|
||
stmt = (
|
||
select(
|
||
PaymentRecord
|
||
)
|
||
.options(
|
||
joinedload(PaymentRecord.payroll_scheme),
|
||
joinedload(PaymentRecord.user).noload(User.payment_records),
|
||
joinedload(PaymentRecord.created_by_user),
|
||
)
|
||
.order_by(
|
||
PaymentRecord.created_at.desc()
|
||
)
|
||
.offset(
|
||
page * pagination.items_per_page
|
||
)
|
||
.limit(
|
||
pagination.items_per_page
|
||
)
|
||
)
|
||
total_records = await self.session.scalar(select(func.count()).select_from(PaymentRecord))
|
||
if not total_records:
|
||
return GetPaymentRecordsResponse(
|
||
payment_records=[],
|
||
pagination_info=PaginationInfoSchema(
|
||
total_pages=0,
|
||
total_items=0
|
||
)
|
||
)
|
||
total_items = total_records
|
||
total_pages = math.ceil(total_records / pagination.items_per_page)
|
||
|
||
payment_records = (await self.session.scalars(stmt)).all()
|
||
response = GetPaymentRecordsResponse(
|
||
payment_records=PaymentRecordGetSchema.from_orm_list(payment_records),
|
||
pagination_info=PaginationInfoSchema(
|
||
total_items=total_items,
|
||
total_pages=total_pages
|
||
)
|
||
)
|
||
return response
|
||
|
||
async def _create_payment_record_hourly(
|
||
self,
|
||
creator: User,
|
||
user: User,
|
||
record_schema: PaymentRecordCreateSchema
|
||
):
|
||
pay_rate: PayRate = user.pay_rate
|
||
overtime_threshold = 0
|
||
overtime_rate = 0
|
||
|
||
if pay_rate.overtime_threshold:
|
||
overtime_threshold = pay_rate.overtime_threshold
|
||
if pay_rate.overtime_rate:
|
||
overtime_rate = pay_rate.overtime_rate
|
||
|
||
if overtime_threshold == 0 or overtime_rate == 0:
|
||
base_units = record_schema.work_units
|
||
overtime_units = 0
|
||
else:
|
||
overtime_units = max(0, record_schema.work_units - overtime_threshold)
|
||
base_units = record_schema.work_units - overtime_units
|
||
|
||
amount = pay_rate.base_rate * base_units + overtime_rate * overtime_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
|
||
})
|
||
stmt = (
|
||
insert(
|
||
PaymentRecord
|
||
).values(
|
||
**payment_record_dict
|
||
)
|
||
)
|
||
await self.session.execute(stmt)
|
||
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(
|
||
self,
|
||
request: CreatePaymentRecordRequest,
|
||
creator: User
|
||
) -> CreatePaymentRecordResponse:
|
||
try:
|
||
user: Optional[User] = await self.session.scalar(select(User).where(User.id == request.data.user.id))
|
||
if not user:
|
||
return CreatePaymentRecordResponse(ok=False, message='Указанный пользователь не найден')
|
||
if not user.pay_rate:
|
||
return CreatePaymentRecordResponse(ok=False, message='У пользователя не указан тариф')
|
||
user_payroll_scheme = user.pay_rate.payroll_scheme.key
|
||
|
||
if user_payroll_scheme == enums.payroll.PaySchemeType.hourly:
|
||
await self._create_payment_record_hourly(creator, user, request.data)
|
||
if user_payroll_scheme == enums.payroll.PaySchemeType.daily:
|
||
await self._create_payment_record_hourly(creator, user, request.data)
|
||
if user_payroll_scheme == enums.payroll.PaySchemeType.monthly:
|
||
await self._create_payment_record_hourly(creator, user, request.data)
|
||
|
||
return CreatePaymentRecordResponse(ok=True, message='Запись успешно добавлена')
|
||
except Exception as e:
|
||
return CreatePaymentRecordResponse(ok=False, message=str(e))
|
||
|
||
async def delete_payment_record(
|
||
self,
|
||
request: DeletePaymentRecordRequest
|
||
) -> DeletePaymentRecordResponse:
|
||
try:
|
||
stmt = (
|
||
delete(
|
||
PaymentRecord
|
||
).where(
|
||
PaymentRecord.id == request.payment_record_id
|
||
)
|
||
)
|
||
await self.session.execute(stmt)
|
||
await self.session.commit()
|
||
return DeletePayRateResponse(ok=True, message="Начисление успешно удалено")
|
||
except Exception as e:
|
||
return DeletePayRateResponse(ok=False, message=str(e))
|