diff --git a/auth/__init__.py b/auth/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/auth/jwt.py b/auth/jwt.py deleted file mode 100644 index 3e7a172..0000000 --- a/auth/jwt.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Annotated - -from fastapi import HTTPException, Depends -from fastapi.security import OAuth2PasswordBearer -from sqlalchemy import select -from starlette import status - -from backend import config -from database import User -from jose import jwt - -from database.base import DatabaseDependency - -oauth2_scheme = OAuth2PasswordBearer("") -ALGORITHM = "HS256" - - -def generate_jwt_token(user: User): - return jwt.encode({'sub': user.id}, settings.SECRET_KEY, algorithm=ALGORITHM) - - -def require_jwt_sub(token: Annotated[str, Depends(oauth2_scheme)]): - payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM]) - user_id = payload.get("sub") - if not user_id: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid authorization credentials') - return payload - - -async def get_current_user(db_session: DatabaseDependency, user_id: Annotated[int, Depends(require_jwt_sub)]) -> User: - user = await db_session.scalar(select(User).where(User.id == user_id)) - if user: - return user diff --git a/auth/telegram.py b/auth/telegram.py deleted file mode 100644 index cbf2b1b..0000000 --- a/auth/telegram.py +++ /dev/null @@ -1,27 +0,0 @@ -import hmac -import hashlib - -from backend import config - - -def _generate_hash(telegram_data: dict): - data = telegram_data.copy() - del data['hash'] - keys = sorted(data.keys()) - string_arr = [] - for key in keys: - if data[key] is not None: - string_arr.append(key + '=' + str(data[key])) - string_cat = '\n'.join(string_arr) - - secret_key = hashlib.sha256(settings.TELEGRAM_BOT_TOKEN.encode('utf-8')).digest() - hash_bytes = bytes(string_cat, 'utf-8') - hmac_hash = hmac.new(secret_key, hash_bytes, hashlib.sha256).hexdigest() - return hmac_hash - - -def telegram_authorize(telegram_data: dict): - generated_hash = _generate_hash(telegram_data) - user_hash = telegram_data['hash'] - return generated_hash == user_hash - diff --git a/models/deal.py b/models/deal.py index 08d884f..f3a1dfe 100644 --- a/models/deal.py +++ b/models/deal.py @@ -9,7 +9,7 @@ from .marketplace import BaseMarketplace from .shipping_warehouse import ShippingWarehouse if TYPE_CHECKING: - from . import DealBillRequest + from . import DealBillRequest, ServicePriceCategory @unique @@ -23,6 +23,12 @@ class DealStatus(IntEnum): CANCELLED = 6 +class DealPriceCategory(BaseModel): + __tablename__ = 'deal_price_category' + deal_id: Mapped[int] = mapped_column(ForeignKey('deals.id'), primary_key=True, unique=True) + category_id: Mapped[int] = mapped_column(ForeignKey('service_price_category.id'), primary_key=True) + + class Deal(BaseModel): __tablename__ = 'deals' id = Column(Integer, autoincrement=True, primary_key=True, index=True) @@ -64,6 +70,9 @@ class Deal(BaseModel): comment = Column(String, nullable=False, server_default='', comment='Коментарий к заданию') bill_request: Mapped[Optional['DealBillRequest']] = relationship(back_populates='deal', lazy='joined') + category: Mapped[Optional["ServicePriceCategory"]] = relationship('ServicePriceCategory', + secondary=DealPriceCategory.__table__, + lazy='joined') class DealStatusHistory(BaseModel): diff --git a/models/service.py b/models/service.py index 879c14e..324846f 100644 --- a/models/service.py +++ b/models/service.py @@ -42,6 +42,10 @@ class Service(BaseModel): lazy='selectin', order_by="asc(ServicePriceRange.from_quantity)", cascade="all, delete-orphan") + category_prices = relationship('ServiceCategoryPrice', + back_populates='service', + lazy='selectin', + cascade="all, delete-orphan") class ServicePriceRange(BaseModel): @@ -54,6 +58,23 @@ class ServicePriceRange(BaseModel): price = Column(Double, nullable=False, comment='Цена') +class ServiceCategoryPrice(BaseModel): + __tablename__ = 'service_category_prices' + service_id: Mapped[int] = mapped_column(ForeignKey('services.id'), primary_key=True) + category_id: Mapped[int] = mapped_column(ForeignKey('service_price_category.id'), primary_key=True) + + price: Mapped[float] = mapped_column(Double, nullable=False, comment='Цена') + + service: Mapped["Service"] = relationship('Service', lazy='joined', back_populates='category_prices') + category: Mapped["ServicePriceCategory"] = relationship('ServicePriceCategory', lazy='joined') + + +class ServicePriceCategory(BaseModel): + __tablename__ = 'service_price_category' + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(nullable=False) + + class ServiceCategory(BaseModel): __tablename__ = 'service_categories' id = Column(Integer, autoincrement=True, primary_key=True, index=True) diff --git a/routers/service.py b/routers/service.py index 123bac5..1b5cad0 100644 --- a/routers/service.py +++ b/routers/service.py @@ -17,6 +17,7 @@ service_router = APIRouter( ) +# region Services @service_router.get( '/get-all', response_model=ServiceGetAllResponse, @@ -69,6 +70,10 @@ async def delete( return await ServiceService(session).delete(request) +# endregion + +# region Categories + @service_router.get( '/categories/get-all', response_model=ServiceGetAllCategoriesResponse, @@ -94,6 +99,9 @@ async def create_category( return await ServiceService(session).create_category(request) +# endregion + +# region Types @service_router.get( '/types/get-all', response_model=BaseEnumListSchema, @@ -108,6 +116,10 @@ async def get_all_service_types( return BaseEnumListSchema(items=result) +# endregion + +# region Kits + @service_router.get( '/kits/get-all', response_model=GetAllServicesKitsResponse, @@ -144,3 +156,60 @@ async def update_services_kit( request: UpdateServicesKitRequest ): return await ServiceService(session).update_kit(request) + + +# endregion + +# region Price Categories +# crud price categories +@service_router.get( + '/price-categories/get-all', + response_model=GetAllPriceCategoriesResponse, + operation_id='get_all_price_categories', + dependencies=[Depends(guest_user)] +) +async def get_all_price_categories( + session: SessionDependency +): + return await ServiceService(session).get_all_price_categories() + + +@service_router.post( + '/price-categories/create', + response_model=CreatePriceCategoryResponse, + operation_id='create_price_category', + dependencies=[Depends(authorized_user)] +) +async def create_price_category( + session: SessionDependency, + request: CreatePriceCategoryRequest +): + return await ServiceService(session).create_price_category(request) + + +@service_router.post( + '/price-categories/update', + response_model=UpdatePriceCategoryResponse, + operation_id='update_price_category', + dependencies=[Depends(authorized_user)] +) +async def update_price_category( + session: SessionDependency, + request: UpdatePriceCategoryRequest +): + return await ServiceService(session).update_price_category(request) + + +@service_router.post( + '/price-categories/delete', + response_model=DeletePriceCategoryResponse, + operation_id='delete_price_category', + dependencies=[Depends(authorized_user)] +) +async def delete_price_category( + session: SessionDependency, + request: DeletePriceCategoryRequest +): + return await ServiceService(session).delete_price_category(request) + +# endregion diff --git a/schemas/deal.py b/schemas/deal.py index 15c7f40..f09b845 100644 --- a/schemas/deal.py +++ b/schemas/deal.py @@ -3,12 +3,13 @@ from typing import List, Optional, Union from pydantic import constr, field_validator +from models import ServiceCategoryPrice, ServicePriceCategory from schemas.base import BaseSchema, OkMessageSchema from schemas.billing import DealBillRequestSchema from schemas.client import ClientSchema from schemas.marketplace import BaseMarketplaceSchema from schemas.product import ProductSchema -from schemas.service import ServiceSchema +from schemas.service import ServiceSchema, ServicePriceCategorySchema from schemas.shipping_warehouse import ShippingWarehouseSchema from schemas.user import UserSchema @@ -37,6 +38,7 @@ class DealSummary(BaseSchema): shipment_warehouse_id: Optional[int] shipment_warehouse_name: Optional[str] + class DealServiceSchema(BaseSchema): service: ServiceSchema quantity: int @@ -81,6 +83,7 @@ class DealSchema(BaseSchema): comment: str shipping_warehouse: Optional[Union[ShippingWarehouseSchema, str]] = None bill_request: Optional[DealBillRequestSchema] = None + category: Optional[ServicePriceCategorySchema] = None class DealGeneralInfoSchema(BaseSchema): @@ -110,6 +113,7 @@ class DealQuickCreateRequest(BaseSchema): acceptance_date: datetime.datetime shipping_warehouse: constr(strip_whitespace=True) base_marketplace: BaseMarketplaceSchema + category: Optional[ServicePriceCategorySchema] = None class DealSummaryRequest(BaseSchema): @@ -212,9 +216,11 @@ class DealAddKitRequest(BaseSchema): class DealCreateGuestUrlRequest(BaseSchema): deal_id: int + class DealCompleteRequest(BaseSchema): deal_id: int + # endregion Requests # region Responses @@ -311,6 +317,7 @@ class DealAddKitResponse(OkMessageSchema): class DealCreateGuestUrlResponse(OkMessageSchema): url: str + class DealCompleteResponse(OkMessageSchema): pass diff --git a/schemas/service.py b/schemas/service.py index 12be97e..f854cdd 100644 --- a/schemas/service.py +++ b/schemas/service.py @@ -4,6 +4,8 @@ from schemas.base import BaseSchema, OkMessageSchema, BaseEnumSchema # region Entities + +# region Services class ServicePriceRangeSchema(BaseSchema): id: int | None from_quantity: int @@ -16,6 +18,16 @@ class ServiceCategorySchema(BaseSchema): name: str +class ServicePriceCategorySchema(BaseSchema): + id: int + name: str + + +class ServiceCategoryPriceSchema(BaseSchema): + category: ServicePriceCategorySchema + price: float + + class ServiceSchema(BaseSchema): id: int name: str @@ -23,9 +35,13 @@ class ServiceSchema(BaseSchema): price: float service_type: int price_ranges: List[ServicePriceRangeSchema] + category_prices: List[ServiceCategoryPriceSchema] cost: Optional[int] +# endregion + +# region Kits class BaseServiceKitSchema(BaseSchema): name: str service_type: int @@ -47,6 +63,12 @@ class UpdateServiceKitSchema(BaseServiceKitSchema): # endregion +# region Category prices + +# endregion + +# endregion + # region Requests class ServiceCreateRequest(BaseSchema): @@ -73,6 +95,19 @@ class UpdateServicesKitRequest(BaseSchema): data: UpdateServiceKitSchema +class CreatePriceCategoryRequest(BaseSchema): + name: str + + +class UpdatePriceCategoryRequest(BaseSchema): + id: int + name: str + + +class DeletePriceCategoryRequest(BaseSchema): + id: int + + # endregion @@ -111,4 +146,21 @@ class UpdateServicesKitResponse(OkMessageSchema): class GetAllServicesKitsResponse(BaseSchema): services_kits: List[GetServiceKitSchema] + + +class GetAllPriceCategoriesResponse(BaseSchema): + price_categories: List[ServicePriceCategorySchema] + + +class CreatePriceCategoryResponse(OkMessageSchema): + pass + + +class UpdatePriceCategoryResponse(OkMessageSchema): + pass + + +class DeletePriceCategoryResponse(OkMessageSchema): + pass + # endregion diff --git a/services/deal.py b/services/deal.py index 067e418..5ddf4b9 100644 --- a/services/deal.py +++ b/services/deal.py @@ -130,6 +130,14 @@ class DealService(BaseService): user, deadline=request.acceptance_date, comment=request.comment) + # add category if specified + if request.category: + deal_category = DealPriceCategory( + deal_id=deal.id, + category_id=request.category.id + ) + self.session.add(deal_category) + await self.session.commit() return DealQuickCreateResponse(deal_id=deal.id) @@ -1004,7 +1012,7 @@ class DealService(BaseService): for product in deal.products: total_one_product = sum((service.price for service in product.services)) total = total_one_product * product.quantity - totals.append({ "total_one_product": total_one_product, "total": total }) + totals.append({"total_one_product": total_one_product, "total": total}) return totals diff --git a/services/service.py b/services/service.py index d8c1859..4c9a476 100644 --- a/services/service.py +++ b/services/service.py @@ -3,7 +3,8 @@ from typing import Union from sqlalchemy import select, update, insert, delete from sqlalchemy.orm import joinedload -from models import Service, ServiceCategory, ServicePriceRange, ServicesKit, services_kit_services +from models import Service, ServiceCategory, ServicePriceRange, ServicesKit, services_kit_services, \ + ServiceCategoryPrice, ServicePriceCategory from services.base import BaseService from schemas.service import * @@ -27,6 +28,7 @@ class ServiceService(BaseService): del service_dict['id'] del service_dict['category'] del service_dict['price_ranges'] + del service_dict['category_prices'] service = Service(**service_dict) self.session.add(service) await self.session.flush() @@ -37,7 +39,15 @@ class ServiceService(BaseService): del price_range_dict['id'] price_range_obj = ServicePriceRange(**price_range_dict) self.session.add(price_range_obj) + category_prices = request.service.category_prices + for category_price in category_prices: + category_price_dict = category_price.model_dump() + category_price_dict['service_id'] = service.id + category_price_dict['category_id'] = category_price.category.id + del category_price_dict['category'] + category_price_obj = ServiceCategoryPrice(**category_price_dict) + self.session.add(category_price_obj) await self.session.commit() return ServiceCreateResponse(ok=True, message="Услуга успешно создана") except Exception as e: @@ -54,6 +64,7 @@ class ServiceService(BaseService): service_dict['category_id'] = raw_service.category.id del service_dict['category'] del service_dict['price_ranges'] + del service_dict['category_prices'] await self.session.execute( update(Service) .where(Service.id == raw_service.id) @@ -81,6 +92,26 @@ class ServiceService(BaseService): del price_range_dict['id'] price_range_obj = ServicePriceRange(**price_range_dict) self.session.add(price_range_obj) + + # deleting previouse category prices + stmt = ( + delete( + ServiceCategoryPrice + ).where( + ServiceCategoryPrice.service_id == service.id + ) + ) + await self.session.execute(stmt) + await self.session.flush() + # inserting new category prices + for category_price in raw_service.category_prices: + category_price_dict = category_price.dict() + category_price_dict['service_id'] = raw_service.id + category_price_dict['category_id'] = category_price.category.id + del category_price_dict['category'] + category_price_obj = ServiceCategoryPrice(**category_price_dict) + self.session.add(category_price_obj) + await self.session.commit() return ServiceUpdateResponse(ok=True, message="Услуга успешно обновлена") except Exception as e: @@ -205,3 +236,60 @@ class ServiceService(BaseService): except Exception as e: return UpdateServicesKitResponse(ok=False, message=str(e)) + + async def get_all_price_categories(self) -> GetAllPriceCategoriesResponse: + query = await (self.session + .scalars(select(ServicePriceCategory) + .order_by(ServicePriceCategory.id))) + price_categories = [] + for category in query.all(): + price_categories.append(ServicePriceCategorySchema.model_validate(category)) + return GetAllPriceCategoriesResponse(price_categories=price_categories) + + async def create_price_category(self, request: CreatePriceCategoryRequest) -> ServiceCreateCategoryResponse: + try: + raw_category = request.name + category = ServicePriceCategory(name=raw_category) + self.session.add(category) + await self.session.commit() + return ServiceCreateCategoryResponse(ok=True, message="Категория цен успешно создана") + except Exception as e: + return ServiceCreateCategoryResponse(ok=False, message=f"Неудалось создать категорию цен, ошибка: {e}") + + async def update_price_category(self, request: UpdatePriceCategoryRequest) -> ServiceUpdateResponse: + try: + raw_category = request.name + category = await (self.session.get(ServicePriceCategory, request.id)) + if not category: + return ServiceUpdateResponse(ok=False, message="Категория цен не найдена") + await self.session.execute( + update(ServicePriceCategory) + .where(ServicePriceCategory.id == request.id) + .values(name=raw_category) + ) + await self.session.commit() + return ServiceUpdateResponse(ok=True, message="Категория цен успешно обновлена") + except Exception as e: + return ServiceUpdateResponse(ok=False, message=f"Неудалось обновить категорию цен, ошибка: {e}") + + async def delete_price_category(self, request: DeletePriceCategoryRequest) -> ServiceDeleteResponse: + try: + category = await ( + self.session + .scalar( + select( + ServicePriceCategory + ) + .filter( + ServicePriceCategory.id == request.id + ) + ) + ) + if not category: + return ServiceDeleteResponse(ok=False, message="Категория цен не найдена") + + await self.session.delete(category) + await self.session.commit() + return ServiceDeleteResponse(ok=True, message="Категория цен успешно удалена") + except Exception as e: + return ServiceDeleteResponse(ok=False, message=f"Неудалось удалить категорию цен, ошибка: {e}")