From 82c9126d879fd4eba46b3ad627ab2f942d68581a Mon Sep 17 00:00:00 2001 From: fakz9 Date: Mon, 22 Jul 2024 12:45:21 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B2=D1=84=D1=8B=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dependecies.py | 6 + enums/payroll.py | 7 + main.py | 1 + models/__init__.py | 1 + models/auth.py | 24 ++- models/payroll.py | 51 ++++++ routers/__init__.py | 1 + routers/payroll.py | 117 ++++++++++++++ schemas/finances.py | 44 +++++ schemas/payment_record.py | 55 +++++++ schemas/payrate.py | 16 ++ schemas/payroll.py | 20 +++ schemas/user.py | 2 + services/payroll.py | 293 ++++++++++++++++++++++++++++++++++ services/user.py | 34 ++-- static/icons/ozon.png | Bin 23589 -> 4453 bytes test.py | 31 +++- utils/init_marketplaces.py | 4 +- utils/init_payroll_schemas.py | 36 +++++ 19 files changed, 725 insertions(+), 18 deletions(-) create mode 100644 enums/payroll.py create mode 100644 models/payroll.py create mode 100644 routers/payroll.py create mode 100644 schemas/finances.py create mode 100644 schemas/payment_record.py create mode 100644 schemas/payrate.py create mode 100644 schemas/payroll.py create mode 100644 services/payroll.py create mode 100644 utils/init_payroll_schemas.py diff --git a/backend/dependecies.py b/backend/dependecies.py index e5acce2..f85b408 100644 --- a/backend/dependecies.py +++ b/backend/dependecies.py @@ -4,5 +4,11 @@ from fastapi import Depends from sqlalchemy.ext.asyncio import AsyncSession 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)] +PaginationDependency = Annotated[PaginationSchema, Depends(pagination_parameters)] +CurrentUserDependency = Annotated[User, Depends(get_current_user)] diff --git a/enums/payroll.py b/enums/payroll.py new file mode 100644 index 0000000..7000af9 --- /dev/null +++ b/enums/payroll.py @@ -0,0 +1,7 @@ +from enum import StrEnum + + +class PaySchemeType(StrEnum): + hourly = 'hourly' + daily = 'daily' + monthly = 'monthly' diff --git a/main.py b/main.py index c863c79..1171da9 100644 --- a/main.py +++ b/main.py @@ -40,6 +40,7 @@ routers_list = [ routers.user_router, routers.role_router, routers.marketplace_router, + routers.payroll_router, ] for router in routers_list: app.include_router(router) diff --git a/models/__init__.py b/models/__init__.py index e9e05d3..2b913c8 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -9,5 +9,6 @@ from .secondary import * from .barcode import * from .shipping_warehouse import * from .marketplace import * +from .payroll import * configure_mappers() diff --git a/models/auth.py b/models/auth.py index 84c1b60..1456c81 100644 --- a/models/auth.py +++ b/models/auth.py @@ -1,9 +1,14 @@ +from typing import TYPE_CHECKING + from sqlalchemy import BigInteger, Table, ForeignKey, Column from sqlalchemy.orm import Mapped, mapped_column, relationship from enums.user import UserRole from models.base import BaseModel +if TYPE_CHECKING: + from models.payroll import PayRate, PaymentRecord + role_permissions = Table( 'role_permissions', BaseModel.metadata, @@ -16,7 +21,12 @@ user_position = Table( BaseModel.metadata, Column('position_key', ForeignKey('positions.key'), primary_key=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', 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): diff --git a/models/payroll.py b/models/payroll.py new file mode 100644 index 0000000..e059896 --- /dev/null +++ b/models/payroll.py @@ -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) diff --git a/routers/__init__.py b/routers/__init__.py index 2c8fa1d..b3009b4 100644 --- a/routers/__init__.py +++ b/routers/__init__.py @@ -9,3 +9,4 @@ from .position import position_router from .user import user_router from .role import role_router from .marketplace import marketplace_router +from .payroll import payroll_router diff --git a/routers/payroll.py b/routers/payroll.py new file mode 100644 index 0000000..3d86b99 --- /dev/null +++ b/routers/payroll.py @@ -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 diff --git a/schemas/finances.py b/schemas/finances.py new file mode 100644 index 0000000..762c3bb --- /dev/null +++ b/schemas/finances.py @@ -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 diff --git a/schemas/payment_record.py b/schemas/payment_record.py new file mode 100644 index 0000000..4b8576d --- /dev/null +++ b/schemas/payment_record.py @@ -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 diff --git a/schemas/payrate.py b/schemas/payrate.py new file mode 100644 index 0000000..fe3538f --- /dev/null +++ b/schemas/payrate.py @@ -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 diff --git a/schemas/payroll.py b/schemas/payroll.py new file mode 100644 index 0000000..f244d1e --- /dev/null +++ b/schemas/payroll.py @@ -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 diff --git a/schemas/user.py b/schemas/user.py index 0f3b75b..a48a170 100644 --- a/schemas/user.py +++ b/schemas/user.py @@ -1,6 +1,7 @@ from typing import List, Optional from schemas.base import BaseSchema, OkMessageSchema +from schemas.payrate import PayRateSchema from schemas.position import PositionSchema from schemas.role import RoleSchema @@ -20,6 +21,7 @@ class BaseUser(BaseSchema): is_blocked: bool is_deleted: bool role_key: str + pay_rate: Optional[PayRateSchema] = None class UserSchema(BaseUser): diff --git a/services/payroll.py b/services/payroll.py new file mode 100644 index 0000000..6d18596 --- /dev/null +++ b/services/payroll.py @@ -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)) diff --git a/services/user.py b/services/user.py index 1d84de9..22d4d07 100644 --- a/services/user.py +++ b/services/user.py @@ -1,6 +1,6 @@ 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 schemas.user import * @@ -21,28 +21,38 @@ class UserService(BaseService): async def update(self, request: UpdateUserRequest) -> UpdateUserResponse: 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='Указанный пользователь не найден') base_fields = request.data.model_dump_parent() + del base_fields['pay_rate'] stmt = update(User).values(**base_fields).where(User.id == request.data.id) - print(stmt) await self.session.execute(stmt) await self.session.flush() - # Updating position + # Deleting previous position stmt = delete(user_position).where(user_position.c.user_id == request.data.id) await self.session.execute(stmt) await self.session.flush() - if not request.data.position_key: - await self.session.commit() - return UpdateUserResponse(ok=True, message='Пользователь успешно обновлен') - stmt = insert(user_position).values(**{ - 'user_id': request.data.id, - 'position_key': request.data.position_key - }) + # Deleting previous pay rate + stmt = delete(user_pay_rate).where(user_pay_rate.c.user_id == request.data.id) 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() return UpdateUserResponse(ok=True, message='Пользователь успешно обновлен') except Exception as e: - pass + return UpdateUserResponse(ok=False, message=str(e)) diff --git a/static/icons/ozon.png b/static/icons/ozon.png index 52293bfee7e9bb2a166575407b35f7fbb851fa42..1effefc0b5c8ddde8e5e37ebb6b302091726f92c 100644 GIT binary patch literal 4453 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 R$II?{|7W~CxZY0 literal 23589 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 G-2VU(Ob+t^ diff --git a/test.py b/test.py index b262575..0d6471d 100644 --- a/test.py +++ b/test.py @@ -1,3 +1,28 @@ -a = {1, 2, 3} -b = {4, 5, 6} -print(a.union(b)) +import asyncio + +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()) diff --git a/utils/init_marketplaces.py b/utils/init_marketplaces.py index 0b6769c..51f2872 100644 --- a/utils/init_marketplaces.py +++ b/utils/init_marketplaces.py @@ -19,12 +19,12 @@ async def main(): { 'key': BaseMarketplace.OZON, 'name': 'OZON', - 'icon_url': '/api/static/icons/ozon.svg' + 'icon_url': '/api/static/icons/ozon.png' }, { 'key': BaseMarketplace.YANDEX_MARKET, 'name': 'Яндекс Маркет', - 'icon_url': '/api/static/icons/ym.svg' + 'icon_url': '/api/static/icons/ym.png' } ] await session.execute(insert(models.BaseMarketplace), marketplaces) diff --git a/utils/init_payroll_schemas.py b/utils/init_payroll_schemas.py new file mode 100644 index 0000000..a45d002 --- /dev/null +++ b/utils/init_payroll_schemas.py @@ -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())