feat: вфыв
This commit is contained in:
@@ -4,5 +4,11 @@ from fastapi import Depends
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from backend.session import get_session
|
from backend.session import get_session
|
||||||
|
from models import User
|
||||||
|
from schemas.base import PaginationSchema
|
||||||
|
from services.auth import get_current_user
|
||||||
|
from utils.dependecies import pagination_parameters
|
||||||
|
|
||||||
SessionDependency = Annotated[AsyncSession, Depends(get_session)]
|
SessionDependency = Annotated[AsyncSession, Depends(get_session)]
|
||||||
|
PaginationDependency = Annotated[PaginationSchema, Depends(pagination_parameters)]
|
||||||
|
CurrentUserDependency = Annotated[User, Depends(get_current_user)]
|
||||||
|
|||||||
7
enums/payroll.py
Normal file
7
enums/payroll.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from enum import StrEnum
|
||||||
|
|
||||||
|
|
||||||
|
class PaySchemeType(StrEnum):
|
||||||
|
hourly = 'hourly'
|
||||||
|
daily = 'daily'
|
||||||
|
monthly = 'monthly'
|
||||||
1
main.py
1
main.py
@@ -40,6 +40,7 @@ routers_list = [
|
|||||||
routers.user_router,
|
routers.user_router,
|
||||||
routers.role_router,
|
routers.role_router,
|
||||||
routers.marketplace_router,
|
routers.marketplace_router,
|
||||||
|
routers.payroll_router,
|
||||||
]
|
]
|
||||||
for router in routers_list:
|
for router in routers_list:
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ from .secondary import *
|
|||||||
from .barcode import *
|
from .barcode import *
|
||||||
from .shipping_warehouse import *
|
from .shipping_warehouse import *
|
||||||
from .marketplace import *
|
from .marketplace import *
|
||||||
|
from .payroll import *
|
||||||
|
|
||||||
configure_mappers()
|
configure_mappers()
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from sqlalchemy import BigInteger, Table, ForeignKey, Column
|
from sqlalchemy import BigInteger, Table, ForeignKey, Column
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from enums.user import UserRole
|
from enums.user import UserRole
|
||||||
from models.base import BaseModel
|
from models.base import BaseModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from models.payroll import PayRate, PaymentRecord
|
||||||
|
|
||||||
role_permissions = Table(
|
role_permissions = Table(
|
||||||
'role_permissions',
|
'role_permissions',
|
||||||
BaseModel.metadata,
|
BaseModel.metadata,
|
||||||
@@ -16,7 +21,12 @@ user_position = Table(
|
|||||||
BaseModel.metadata,
|
BaseModel.metadata,
|
||||||
Column('position_key', ForeignKey('positions.key'), primary_key=True),
|
Column('position_key', ForeignKey('positions.key'), primary_key=True),
|
||||||
Column('user_id', ForeignKey('users.id'), primary_key=True, unique=True)
|
Column('user_id', ForeignKey('users.id'), primary_key=True, unique=True)
|
||||||
|
)
|
||||||
|
user_pay_rate = Table(
|
||||||
|
'user_pay_rate',
|
||||||
|
BaseModel.metadata,
|
||||||
|
Column('pay_rate_id', ForeignKey('pay_rates.id'), primary_key=True),
|
||||||
|
Column('user_id', ForeignKey('users.id'), primary_key=True, unique=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -69,6 +79,18 @@ class User(BaseModel):
|
|||||||
back_populates='users',
|
back_populates='users',
|
||||||
lazy="joined"
|
lazy="joined"
|
||||||
)
|
)
|
||||||
|
pay_rate: Mapped["PayRate"] = relationship(
|
||||||
|
"PayRate",
|
||||||
|
secondary=user_pay_rate,
|
||||||
|
uselist=False,
|
||||||
|
lazy="joined"
|
||||||
|
)
|
||||||
|
payment_records: Mapped[list["PaymentRecord"]] = relationship(
|
||||||
|
"PaymentRecord",
|
||||||
|
back_populates="user",
|
||||||
|
uselist=True,
|
||||||
|
foreign_keys="PaymentRecord.user_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Position(BaseModel):
|
class Position(BaseModel):
|
||||||
|
|||||||
51
models/payroll.py
Normal file
51
models/payroll.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import datetime
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from sqlalchemy import ForeignKey, Double
|
||||||
|
from sqlalchemy.orm import mapped_column, Mapped, relationship
|
||||||
|
|
||||||
|
from models.base import BaseModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from models.auth import User
|
||||||
|
|
||||||
|
|
||||||
|
class PayrollScheme(BaseModel):
|
||||||
|
__tablename__ = 'payroll_schemas'
|
||||||
|
key: Mapped[str] = mapped_column(primary_key=True)
|
||||||
|
name: Mapped[str] = mapped_column(nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class PayRate(BaseModel):
|
||||||
|
__tablename__ = 'pay_rates'
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
name: Mapped[str] = mapped_column(nullable=False)
|
||||||
|
|
||||||
|
payroll_scheme_key: Mapped[int] = mapped_column(ForeignKey("payroll_schemas.key"), nullable=False)
|
||||||
|
payroll_scheme: Mapped["PayrollScheme"] = relationship(lazy="joined")
|
||||||
|
|
||||||
|
base_rate: Mapped[float] = mapped_column(Double, nullable=False)
|
||||||
|
|
||||||
|
overtime_rate: Mapped[float] = mapped_column(Double, nullable=True)
|
||||||
|
overtime_threshold: Mapped[int] = mapped_column(nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentRecord(BaseModel):
|
||||||
|
__tablename__ = 'payment_records'
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
|
||||||
|
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
|
||||||
|
user: Mapped["User"] = relationship(back_populates="payment_records", foreign_keys=[user_id])
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
start_date: Mapped[datetime.date] = mapped_column(nullable=False)
|
||||||
|
end_date: Mapped[datetime.date] = mapped_column(nullable=False)
|
||||||
|
created_at: Mapped[datetime.datetime] = mapped_column(nullable=False)
|
||||||
|
|
||||||
|
payroll_scheme_key: Mapped[int] = mapped_column(ForeignKey("payroll_schemas.key"), nullable=False)
|
||||||
|
payroll_scheme: Mapped["PayrollScheme"] = relationship()
|
||||||
|
|
||||||
|
work_units: Mapped[int] = mapped_column(nullable=False)
|
||||||
|
amount: Mapped[float] = mapped_column(Double, nullable=False)
|
||||||
@@ -9,3 +9,4 @@ from .position import position_router
|
|||||||
from .user import user_router
|
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
|
||||||
|
|||||||
117
routers/payroll.py
Normal file
117
routers/payroll.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from backend.dependecies import SessionDependency, PaginationDependency, CurrentUserDependency
|
||||||
|
from schemas.finances import CreatePayRateRequest, UpdatePayRateRequest, DeletePayRateRequest, \
|
||||||
|
GetAllPayrollSchemeResponse, GetAllPayRatesResponse, CreatePayRateResponse, UpdatePayRateResponse, \
|
||||||
|
DeletePayRateResponse
|
||||||
|
from schemas.payment_record import GetPaymentRecordsResponse, CreatePaymentRecordResponse, CreatePaymentRecordRequest, \
|
||||||
|
DeletePaymentRecordResponse, DeletePaymentRecordRequest
|
||||||
|
from services.payroll import PayrollService
|
||||||
|
|
||||||
|
payroll_router = APIRouter(
|
||||||
|
prefix="/payroll",
|
||||||
|
tags=["payroll"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# region PayScheme
|
||||||
|
@payroll_router.get(
|
||||||
|
'/scheme/get-all',
|
||||||
|
operation_id='get_all_payroll_schemas',
|
||||||
|
response_model=GetAllPayrollSchemeResponse
|
||||||
|
)
|
||||||
|
async def get_all_schemas(
|
||||||
|
session: SessionDependency
|
||||||
|
):
|
||||||
|
return await PayrollService(session).get_all_schemas()
|
||||||
|
|
||||||
|
|
||||||
|
@payroll_router.get(
|
||||||
|
'/pay-rate/get-all',
|
||||||
|
operation_id='get_all_pay_rates',
|
||||||
|
response_model=GetAllPayRatesResponse
|
||||||
|
)
|
||||||
|
async def get_all_pay_rates(
|
||||||
|
session: SessionDependency
|
||||||
|
):
|
||||||
|
return await PayrollService(session).get_all_pay_rates()
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region PayRate
|
||||||
|
@payroll_router.post(
|
||||||
|
'/pay-rate/create',
|
||||||
|
operation_id='create_pay_rate',
|
||||||
|
response_model=CreatePayRateResponse
|
||||||
|
)
|
||||||
|
async def create_pay_rate(
|
||||||
|
session: SessionDependency,
|
||||||
|
request: CreatePayRateRequest
|
||||||
|
):
|
||||||
|
return await PayrollService(session).create_pay_rate(request)
|
||||||
|
|
||||||
|
|
||||||
|
@payroll_router.post(
|
||||||
|
'/pay-rate/update',
|
||||||
|
operation_id='update_pay_rate',
|
||||||
|
response_model=UpdatePayRateResponse
|
||||||
|
)
|
||||||
|
async def update_pay_rate(
|
||||||
|
session: SessionDependency,
|
||||||
|
request: UpdatePayRateRequest
|
||||||
|
):
|
||||||
|
return await PayrollService(session).update_pay_rate(request)
|
||||||
|
|
||||||
|
|
||||||
|
@payroll_router.post(
|
||||||
|
'/pay-rate/delete',
|
||||||
|
operation_id='delete_pay_rate',
|
||||||
|
response_model=DeletePayRateResponse
|
||||||
|
)
|
||||||
|
async def update_pay_rate(
|
||||||
|
session: SessionDependency,
|
||||||
|
request: DeletePayRateRequest
|
||||||
|
):
|
||||||
|
return await PayrollService(session).delete_pay_rate(request)
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region PaymentRecord
|
||||||
|
@payroll_router.get(
|
||||||
|
'/payment-record/get',
|
||||||
|
operation_id='get_payment_records',
|
||||||
|
response_model=GetPaymentRecordsResponse
|
||||||
|
)
|
||||||
|
async def get_payment_records(
|
||||||
|
session: SessionDependency,
|
||||||
|
pagination: PaginationDependency
|
||||||
|
):
|
||||||
|
return await PayrollService(session).get_payment_records(pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@payroll_router.post(
|
||||||
|
'/payment-record/create',
|
||||||
|
operation_id='create_payment_record',
|
||||||
|
response_model=CreatePaymentRecordResponse
|
||||||
|
)
|
||||||
|
async def create_payment_records(
|
||||||
|
session: SessionDependency,
|
||||||
|
request: CreatePaymentRecordRequest,
|
||||||
|
user: CurrentUserDependency
|
||||||
|
):
|
||||||
|
return await PayrollService(session).create_payment_record(request, user)
|
||||||
|
|
||||||
|
|
||||||
|
@payroll_router.post(
|
||||||
|
'/payment-record/delete',
|
||||||
|
operation_id='delete_payment_record',
|
||||||
|
response_model=DeletePaymentRecordResponse
|
||||||
|
)
|
||||||
|
async def delete_payment_record(
|
||||||
|
session: SessionDependency,
|
||||||
|
request: DeletePaymentRecordRequest,
|
||||||
|
):
|
||||||
|
return await PayrollService(session).delete_payment_record(request)
|
||||||
|
# endregion
|
||||||
44
schemas/finances.py
Normal file
44
schemas/finances.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from schemas.base import BaseSchema, OkMessageSchema
|
||||||
|
from schemas.payrate import PayRateSchemaBase, PayRateSchema
|
||||||
|
from schemas.payroll import PayrollSchemeSchema
|
||||||
|
|
||||||
|
|
||||||
|
# region Requests
|
||||||
|
|
||||||
|
class CreatePayRateRequest(BaseSchema):
|
||||||
|
data: PayRateSchemaBase
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatePayRateRequest(BaseSchema):
|
||||||
|
data: PayRateSchema
|
||||||
|
|
||||||
|
|
||||||
|
class DeletePayRateRequest(BaseSchema):
|
||||||
|
pay_rate_id: int
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region Responses
|
||||||
|
|
||||||
|
class GetAllPayrollSchemeResponse(BaseSchema):
|
||||||
|
payroll_schemas: List[PayrollSchemeSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class GetAllPayRatesResponse(BaseSchema):
|
||||||
|
pay_rates: List[PayRateSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePayRateResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatePayRateResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DeletePayRateResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
# endregion
|
||||||
55
schemas/payment_record.py
Normal file
55
schemas/payment_record.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import datetime
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from schemas.base import BaseSchema, PaginationInfoSchema, OkMessageSchema
|
||||||
|
from schemas.payroll import PayrollSchemeSchema
|
||||||
|
from schemas.user import UserSchema
|
||||||
|
|
||||||
|
|
||||||
|
# region Entities
|
||||||
|
class PaymentRecordSchemaBase(BaseSchema):
|
||||||
|
start_date: datetime.date
|
||||||
|
end_date: datetime.date
|
||||||
|
|
||||||
|
work_units: int
|
||||||
|
user: UserSchema
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentRecordCreateSchema(PaymentRecordSchemaBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentRecordGetSchema(PaymentRecordSchemaBase):
|
||||||
|
id: int
|
||||||
|
created_by_user: UserSchema
|
||||||
|
payroll_scheme: PayrollSchemeSchema
|
||||||
|
amount: int
|
||||||
|
created_at: datetime.datetime
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region Requests
|
||||||
|
class CreatePaymentRecordRequest(BaseSchema):
|
||||||
|
data: PaymentRecordCreateSchema
|
||||||
|
|
||||||
|
|
||||||
|
class DeletePaymentRecordRequest(BaseSchema):
|
||||||
|
payment_record_id: int
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region Responses
|
||||||
|
class GetPaymentRecordsResponse(BaseSchema):
|
||||||
|
payment_records: List[PaymentRecordGetSchema]
|
||||||
|
pagination_info: PaginationInfoSchema
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePaymentRecordResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DeletePaymentRecordResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
# endregion
|
||||||
16
schemas/payrate.py
Normal file
16
schemas/payrate.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from schemas.base import BaseSchema
|
||||||
|
from schemas.payroll import PayrollSchemeSchema
|
||||||
|
|
||||||
|
|
||||||
|
class PayRateSchemaBase(BaseSchema):
|
||||||
|
name: str
|
||||||
|
payroll_scheme: PayrollSchemeSchema
|
||||||
|
base_rate: float
|
||||||
|
overtime_rate: Optional[float] = None
|
||||||
|
overtime_threshold: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
|
class PayRateSchema(PayRateSchemaBase):
|
||||||
|
id: int
|
||||||
20
schemas/payroll.py
Normal file
20
schemas/payroll.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from schemas.base import BaseSchema
|
||||||
|
|
||||||
|
|
||||||
|
# region Entities
|
||||||
|
class PayrollSchemeSchema(BaseSchema):
|
||||||
|
key: str
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region Requests
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region Responses
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from schemas.base import BaseSchema, OkMessageSchema
|
from schemas.base import BaseSchema, OkMessageSchema
|
||||||
|
from schemas.payrate import PayRateSchema
|
||||||
from schemas.position import PositionSchema
|
from schemas.position import PositionSchema
|
||||||
from schemas.role import RoleSchema
|
from schemas.role import RoleSchema
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ class BaseUser(BaseSchema):
|
|||||||
is_blocked: bool
|
is_blocked: bool
|
||||||
is_deleted: bool
|
is_deleted: bool
|
||||||
role_key: str
|
role_key: str
|
||||||
|
pay_rate: Optional[PayRateSchema] = None
|
||||||
|
|
||||||
|
|
||||||
class UserSchema(BaseUser):
|
class UserSchema(BaseUser):
|
||||||
|
|||||||
293
services/payroll.py
Normal file
293
services/payroll.py
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
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))
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from sqlalchemy import select, update, delete, insert
|
from sqlalchemy import select, update, delete, insert
|
||||||
|
|
||||||
from models import User, user_position
|
from models import User, user_position, user_pay_rate
|
||||||
from services.base import BaseService
|
from services.base import BaseService
|
||||||
from schemas.user import *
|
from schemas.user import *
|
||||||
|
|
||||||
@@ -21,28 +21,38 @@ class UserService(BaseService):
|
|||||||
|
|
||||||
async def update(self, request: UpdateUserRequest) -> UpdateUserResponse:
|
async def update(self, request: UpdateUserRequest) -> UpdateUserResponse:
|
||||||
try:
|
try:
|
||||||
if not self.get_by_id(request.data.id):
|
if not await self.get_by_id(request.data.id):
|
||||||
return UpdateUserResponse(ok=False, message='Указанный пользователь не найден')
|
return UpdateUserResponse(ok=False, message='Указанный пользователь не найден')
|
||||||
base_fields = request.data.model_dump_parent()
|
base_fields = request.data.model_dump_parent()
|
||||||
|
del base_fields['pay_rate']
|
||||||
stmt = update(User).values(**base_fields).where(User.id == request.data.id)
|
stmt = update(User).values(**base_fields).where(User.id == request.data.id)
|
||||||
print(stmt)
|
|
||||||
await self.session.execute(stmt)
|
await self.session.execute(stmt)
|
||||||
await self.session.flush()
|
await self.session.flush()
|
||||||
|
|
||||||
# Updating position
|
# Deleting previous position
|
||||||
stmt = delete(user_position).where(user_position.c.user_id == request.data.id)
|
stmt = delete(user_position).where(user_position.c.user_id == request.data.id)
|
||||||
await self.session.execute(stmt)
|
await self.session.execute(stmt)
|
||||||
await self.session.flush()
|
await self.session.flush()
|
||||||
|
|
||||||
if not request.data.position_key:
|
# Deleting previous pay rate
|
||||||
await self.session.commit()
|
stmt = delete(user_pay_rate).where(user_pay_rate.c.user_id == request.data.id)
|
||||||
return UpdateUserResponse(ok=True, message='Пользователь успешно обновлен')
|
|
||||||
stmt = insert(user_position).values(**{
|
|
||||||
'user_id': request.data.id,
|
|
||||||
'position_key': request.data.position_key
|
|
||||||
})
|
|
||||||
await self.session.execute(stmt)
|
await self.session.execute(stmt)
|
||||||
|
await self.session.flush()
|
||||||
|
|
||||||
|
if request.data.position_key:
|
||||||
|
stmt = insert(user_position).values(**{
|
||||||
|
'user_id': request.data.id,
|
||||||
|
'position_key': request.data.position_key
|
||||||
|
})
|
||||||
|
await self.session.execute(stmt)
|
||||||
|
if request.data.pay_rate:
|
||||||
|
stmt = insert(user_pay_rate).values(**{
|
||||||
|
'user_id': request.data.id,
|
||||||
|
'pay_rate_id': request.data.pay_rate.id
|
||||||
|
})
|
||||||
|
await self.session.execute(stmt)
|
||||||
|
|
||||||
await self.session.commit()
|
await self.session.commit()
|
||||||
return UpdateUserResponse(ok=True, message='Пользователь успешно обновлен')
|
return UpdateUserResponse(ok=True, message='Пользователь успешно обновлен')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
return UpdateUserResponse(ok=False, message=str(e))
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 4.3 KiB |
31
test.py
31
test.py
@@ -1,3 +1,28 @@
|
|||||||
a = {1, 2, 3}
|
import asyncio
|
||||||
b = {4, 5, 6}
|
|
||||||
print(a.union(b))
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from backend.session import session_maker
|
||||||
|
from models import User
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
a = await session.scalar(
|
||||||
|
select(User).where(User.first_name == "Абид")
|
||||||
|
|
||||||
|
)
|
||||||
|
print(a)
|
||||||
|
await session.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ async def main():
|
|||||||
{
|
{
|
||||||
'key': BaseMarketplace.OZON,
|
'key': BaseMarketplace.OZON,
|
||||||
'name': 'OZON',
|
'name': 'OZON',
|
||||||
'icon_url': '/api/static/icons/ozon.svg'
|
'icon_url': '/api/static/icons/ozon.png'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'key': BaseMarketplace.YANDEX_MARKET,
|
'key': BaseMarketplace.YANDEX_MARKET,
|
||||||
'name': 'Яндекс Маркет',
|
'name': 'Яндекс Маркет',
|
||||||
'icon_url': '/api/static/icons/ym.svg'
|
'icon_url': '/api/static/icons/ym.png'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
await session.execute(insert(models.BaseMarketplace), marketplaces)
|
await session.execute(insert(models.BaseMarketplace), marketplaces)
|
||||||
|
|||||||
36
utils/init_payroll_schemas.py
Normal file
36
utils/init_payroll_schemas.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
from sqlalchemy import insert
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
import models
|
||||||
|
from backend.session import session_maker
|
||||||
|
from enums.base_marketplace import BaseMarketplace
|
||||||
|
from enums.payroll import PaySchemeType
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
session: AsyncSession = session_maker()
|
||||||
|
schemas = [
|
||||||
|
{
|
||||||
|
'key': PaySchemeType.hourly,
|
||||||
|
'name': 'Почасовая',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'key': PaySchemeType.daily,
|
||||||
|
'name': 'Подневная',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'key': PaySchemeType.monthly,
|
||||||
|
'name': 'Помесячная',
|
||||||
|
|
||||||
|
}
|
||||||
|
]
|
||||||
|
await session.execute(insert(models.PayrollScheme), schemas)
|
||||||
|
await session.commit()
|
||||||
|
await session.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
Reference in New Issue
Block a user