diff --git a/models/product.py b/models/product.py index 6e69ca7..1994849 100644 --- a/models/product.py +++ b/models/product.py @@ -12,7 +12,7 @@ class Product(BaseModel): client_id = Column(Integer, ForeignKey('clients.id'), nullable=False, comment='ID сделки') client = relationship('Client', back_populates='products') - barcodes = relationship('ProductBarcode', back_populates='product') + barcodes = relationship('ProductBarcode', back_populates='product', cascade="all, delete-orphan") class ProductBarcode(BaseModel): @@ -20,4 +20,4 @@ class ProductBarcode(BaseModel): product_id = Column(Integer, ForeignKey('products.id'), nullable=False, comment='ID товара', primary_key=True) product = relationship('Product', back_populates='barcodes') - barcode = Column(String, nullable=False, index=True, comment='ШК товара') + barcode = Column(String, nullable=False, index=True, comment='ШК товара', primary_key=True) diff --git a/routers/product.py b/routers/product.py index 70b6ed9..70dae9f 100644 --- a/routers/product.py +++ b/routers/product.py @@ -17,7 +17,8 @@ product_router = APIRouter( @product_router.post( '/create', - response_model=ProductCreateResponse + response_model=ProductCreateResponse, + operation_id='create_product' ) async def create_product( request: ProductCreateRequest, @@ -26,6 +27,28 @@ async def create_product( return await ProductService(session).create(request) +@product_router.post( + '/delete', + response_model=ProductDeleteResponse, + operation_id='delete_product' +) +async def delete_product( + request: ProductDeleteRequest, + session: Annotated[AsyncSession, Depends(get_session)] +): + return await ProductService(session).delete(request) + +@product_router.post( + '/update', + response_model=ProductUpdateResponse, + operation_id='update_product' +) +async def delete_product( + request: ProductUpdateRequest, + session: Annotated[AsyncSession, Depends(get_session)] +): + return await ProductService(session).update(request) + @product_router.get( '/get', response_model=ProductGetResponse, diff --git a/schemas/auth.py b/schemas/auth.py index 69e62f6..5431454 100644 --- a/schemas/auth.py +++ b/schemas/auth.py @@ -1,7 +1,7 @@ -from schemas.base import CustomModel +from schemas.base import CustomModelCamel, CustomModelSnake -class AuthLoginRequest(CustomModel): +class AuthLoginRequest(CustomModelSnake): auth_date: int first_name: str hash: str @@ -9,5 +9,5 @@ class AuthLoginRequest(CustomModel): photo_url: str -class AuthLoginResponse(CustomModel): +class AuthLoginResponse(CustomModelCamel): access_token: str diff --git a/schemas/base.py b/schemas/base.py index 9a8b0ed..584bb48 100644 --- a/schemas/base.py +++ b/schemas/base.py @@ -1,4 +1,5 @@ from pydantic import BaseModel +from pydantic.alias_generators import to_camel class CustomConfig: @@ -6,21 +7,34 @@ class CustomConfig: from_attributes = True -class CustomModel(BaseModel): +class CustomModelCamel(BaseModel): + class Config: + from_attributes = True + alias_generator = to_camel + populate_by_name = True + + @classmethod + def from_sql_model(cls, model, fields: dict): + model_dict = {c.name: getattr(model, c.name) for c in model.__table__.columns} + model_dict.update(fields) + return cls(**model_dict) + + +class CustomModelSnake(BaseModel): class Config: from_attributes = True -class OkMessageSchema(BaseModel): +class OkMessageSchema(CustomModelCamel): ok: bool message: str -class PaginationSchema(CustomModel): +class PaginationSchema(CustomModelCamel): page: int items_per_page: int -class PaginationInfoSchema(CustomModel): +class PaginationInfoSchema(CustomModelCamel): total_pages: int total_items: int diff --git a/schemas/client.py b/schemas/client.py index 070f6bc..94ebdc4 100644 --- a/schemas/client.py +++ b/schemas/client.py @@ -1,42 +1,42 @@ from typing import List -from schemas.base import CustomModel +from schemas.base import CustomModelCamel -class ClientDetailsSchema(CustomModel): +class ClientDetailsSchema(CustomModelCamel): address: str | None = None phone_number: str | None = None inn: int | None = None email: str | None = None -class ClientSchema(CustomModel): +class ClientSchema(CustomModelCamel): id: int name: str details: ClientDetailsSchema | None = None -class ClientSearchRequest(CustomModel): +class ClientSearchRequest(CustomModelCamel): name: str -class ClientCreateRequest(CustomModel): +class ClientCreateRequest(CustomModelCamel): name: str address: str -class ClientSearchResponse(CustomModel): +class ClientSearchResponse(CustomModelCamel): clients: List[ClientSchema] -class ClientUpdateDetailsRequest(CustomModel): +class ClientUpdateDetailsRequest(CustomModelCamel): client_id: int details: ClientDetailsSchema -class ClientUpdateDetailsResponse(CustomModel): +class ClientUpdateDetailsResponse(CustomModelCamel): ok: bool -class ClientGetAllResponse(CustomModel): +class ClientGetAllResponse(CustomModelCamel): clients: List[ClientSchema] diff --git a/schemas/deal.py b/schemas/deal.py index 31bd0da..a304a9d 100644 --- a/schemas/deal.py +++ b/schemas/deal.py @@ -1,19 +1,19 @@ import datetime from typing import List -from schemas.base import CustomModel +from schemas.base import CustomModelCamel from schemas.client import ClientSchema # region Entities -class FastDeal(CustomModel): +class FastDeal(CustomModelCamel): name: str client: ClientSchema comment: str acceptance_date: datetime.datetime -class DealSummary(CustomModel): +class DealSummary(CustomModelCamel): id: int name: str client_name: str @@ -22,7 +22,7 @@ class DealSummary(CustomModel): total_price: int -class DealServiceSchema(CustomModel): +class DealServiceSchema(CustomModelCamel): id: int quantity: int @@ -30,16 +30,16 @@ class DealServiceSchema(CustomModel): # endregion Entities # region Requests -class DealChangeStatusRequest(CustomModel): +class DealChangeStatusRequest(CustomModelCamel): deal_id: int new_status: int -class DealCreateRequest(CustomModel): +class DealCreateRequest(CustomModelCamel): name: str -class DealQuickCreateRequest(CustomModel): +class DealQuickCreateRequest(CustomModelCamel): name: str client_name: str client_address: str @@ -47,11 +47,11 @@ class DealQuickCreateRequest(CustomModel): acceptance_date: datetime.datetime -class DealSummaryRequest(CustomModel): +class DealSummaryRequest(CustomModelCamel): pass -class DealAddServicesRequest(CustomModel): +class DealAddServicesRequest(CustomModelCamel): deal_id: int services: list[DealServiceSchema] @@ -60,23 +60,23 @@ class DealAddServicesRequest(CustomModel): # region Responses -class DealChangeStatusResponse(CustomModel): +class DealChangeStatusResponse(CustomModelCamel): ok: bool -class DealCreateResponse(CustomModel): +class DealCreateResponse(CustomModelCamel): ok: bool -class DealQuickCreateResponse(CustomModel): +class DealQuickCreateResponse(CustomModelCamel): deal_id: int -class DealSummaryResponse(CustomModel): +class DealSummaryResponse(CustomModelCamel): summaries: List[DealSummary] -class DealAddServicesResponse(CustomModel): +class DealAddServicesResponse(CustomModelCamel): ok: bool message: str # endregion Responses diff --git a/schemas/product.py b/schemas/product.py index 615df80..7913d72 100644 --- a/schemas/product.py +++ b/schemas/product.py @@ -1,33 +1,51 @@ from typing import List -from schemas.base import CustomModel, PaginationInfoSchema +from schemas.base import CustomModelCamel, PaginationInfoSchema, OkMessageSchema # region Entities -class ProductSchema(CustomModel): +class ProductSchema(CustomModelCamel): id: int name: str article: str client_id: int + barcodes: list[str] # endregion # region Requests -class ProductCreateRequest(CustomModel): +class ProductCreateRequest(CustomModelCamel): name: str article: str client_id: int + barcodes: List[str] + + +class ProductDeleteRequest(CustomModelCamel): + product_id: int + + +class ProductUpdateRequest(CustomModelCamel): + product: ProductSchema # endregion # region Responses -class ProductCreateResponse(CustomModel): - product_id: int +class ProductCreateResponse(OkMessageSchema): + product_id: int | None = None -class ProductGetResponse(CustomModel): +class ProductGetResponse(CustomModelCamel): products: List[ProductSchema] pagination_info: PaginationInfoSchema + + +class ProductDeleteResponse(OkMessageSchema): + pass + + +class ProductUpdateResponse(OkMessageSchema): + pass # endregion diff --git a/schemas/service.py b/schemas/service.py index 39b98c0..4b6c3d2 100644 --- a/schemas/service.py +++ b/schemas/service.py @@ -1,15 +1,15 @@ from typing import List -from schemas.base import CustomModel, OkMessageSchema +from schemas.base import CustomModelCamel, OkMessageSchema # region Entities -class ServiceCategorySchema(CustomModel): +class ServiceCategorySchema(CustomModelCamel): id: int name: str -class ServiceSchema(CustomModel): +class ServiceSchema(CustomModelCamel): id: int name: str category: ServiceCategorySchema @@ -20,11 +20,11 @@ class ServiceSchema(CustomModel): # region Requests -class ServiceCreateRequest(CustomModel): +class ServiceCreateRequest(CustomModelCamel): service: ServiceSchema -class ServiceCreateCategoryRequest(CustomModel): +class ServiceCreateCategoryRequest(CustomModelCamel): category: ServiceCategorySchema @@ -32,11 +32,11 @@ class ServiceCreateCategoryRequest(CustomModel): # region Responses -class ServiceGetAllResponse(CustomModel): +class ServiceGetAllResponse(CustomModelCamel): services: List[ServiceSchema] -class ServiceGetAllCategoriesResponse(CustomModel): +class ServiceGetAllCategoriesResponse(CustomModelCamel): categories: List[ServiceCategorySchema] diff --git a/services/auth.py b/services/auth.py index d794363..aebf5e2 100644 --- a/services/auth.py +++ b/services/auth.py @@ -1,7 +1,7 @@ from typing import Union, Annotated from fastapi import Depends, HTTPException -from fastapi.security import OAuth2PasswordBearer, HTTPBearer, HTTPAuthorizationCredentials +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from jose import jwt, JWTError from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -10,8 +10,8 @@ from starlette import status import backend.config from backend.session import get_session from models import User -from services.base import BaseService from schemas.auth import * +from services.base import BaseService oauth2_schema = HTTPBearer() algorithm = 'HS256' diff --git a/services/product.py b/services/product.py index e1f1e19..928b224 100644 --- a/services/product.py +++ b/services/product.py @@ -1,7 +1,8 @@ from fastapi import HTTPException -from sqlalchemy import select +from sqlalchemy import select, func, Integer, update +from sqlalchemy.orm import selectinload -from models.product import Product +from models.product import Product, ProductBarcode from schemas.base import PaginationSchema from services.base import BaseService from schemas.product import * @@ -9,6 +10,7 @@ from schemas.product import * class ProductService(BaseService): async def create(self, request: ProductCreateRequest) -> ProductCreateResponse: + # Unique article validation existing_product_query = await self.session.execute( select(Product) .where(Product.client_id == request.client_id, @@ -16,22 +18,101 @@ class ProductService(BaseService): ) existing_product = existing_product_query.first() if existing_product: - raise HTTPException(status_code=403, detail="Product already exists") - product = Product(**request.dict()) + return ProductCreateResponse(ok=False, message='Товар с таким артикулом уже существует у клиента') + + # Creating product + product_dict = request.dict() + del product_dict['barcodes'] + product = Product(**product_dict) self.session.add(product) + + # Creating barcodes + await self.session.flush() + for barcode in request.barcodes: + product_barcode = ProductBarcode(product_id=product.id, + barcode=barcode) + self.session.add(product_barcode) + await self.session.flush() await self.session.commit() - return ProductCreateResponse(product_id=product.id) + return ProductCreateResponse(ok=True, message='Товар успешно создан', product_id=product.id) + + async def delete(self, request: ProductDeleteRequest): + product = await self.session.get(Product, request.product_id) + if not product: + return ProductDeleteResponse(ok=False, message='Указанного товара не существует') + await self.session.delete(product) + await self.session.commit() + return ProductDeleteResponse(ok=True, message="Товар успешно удален!") + + async def update(self, request: ProductUpdateRequest): + stmt = ( + select(Product) + .where(Product.id == request.product.id) + .options(selectinload(Product.barcodes)) + ) + product_query = await self.session.execute(stmt) + product = product_query.scalar() + if not product: + return ProductUpdateResponse(ok=False, message='Указанного товара не существует') + product_dict = request.product.dict() + del product_dict['id'] + del product_dict['barcodes'] + await self.session.execute( + update(Product) + .where(Product.id == request.product.id) + .values(**product_dict) + ) + # Updating barcodes + product_barcodes = set([barcode for barcode in product.barcodes]) + request_barcodes = set(request.product.barcodes) + new_barcodes = request_barcodes.difference(product_barcodes) + deleted_barcodes = product_barcodes.difference(request_barcodes) + for product_barcode in product.barcodes: + if product_barcode not in deleted_barcodes: + continue + await self.session.delete(product_barcode) + for new_barcode in new_barcodes: + product_barcode = ProductBarcode( + product_id=product.id, + barcode=new_barcode + ) + self.session.add(product_barcode) + await self.session.flush() + + await self.session.commit() + return ProductUpdateResponse(ok=True, message='Товар успешно обновлен') async def get_by_client_id(self, client_id: int, pagination: PaginationSchema) -> ProductGetResponse: - query = await self.session.execute( + stmt = ( select(Product) + .options(selectinload(Product.barcodes)) .where(Product.client_id == client_id) + .order_by(Product.id) + ) + total_products_query = await self.session.execute( + select( + func.cast(func.ceil(func.count() / pagination.items_per_page), Integer), + func.count() + ) + .select_from(stmt.subquery()) + ) + total_pages, total_items = total_products_query.first() + pagination_info = PaginationInfoSchema(total_pages=total_pages, total_items=total_items) + query = await self.session.execute( + stmt .offset(pagination.page * pagination.items_per_page) .limit(pagination.items_per_page) ) products: list[ProductSchema] = [] for product in query.scalars().all(): + product: Product + + barcodes = [] + for barcode_obj in product.barcodes: + barcode_obj: ProductBarcode + barcodes.append(barcode_obj.barcode) + products.append( - ProductSchema.model_validate(product) + ProductSchema.from_sql_model(product, {'barcodes': barcodes}) ) - return ProductGetResponse(products=products) + return ProductGetResponse(products=products, pagination_info=pagination_info) diff --git a/test/test.json b/test/test.json new file mode 100644 index 0000000..858b580 --- /dev/null +++ b/test/test.json @@ -0,0 +1,1241 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/auth/login": { + "post": { + "tags": [ + "auth" + ], + "summary": "Login", + "operationId": "login_auth_login_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthLoginRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthLoginResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/deal/create": { + "post": { + "tags": [ + "deal" + ], + "summary": "Create", + "operationId": "create_deal_create_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DealCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBearer": [] + } + ] + } + }, + "/deal/quickCreate": { + "post": { + "tags": [ + "deal" + ], + "summary": "Quick Create", + "operationId": "quick_create_deal_quickCreate_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DealQuickCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DealQuickCreateResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBearer": [] + } + ] + } + }, + "/deal/changeStatus": { + "post": { + "tags": [ + "deal" + ], + "summary": "Change Status", + "operationId": "change_status_deal_changeStatus_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DealChangeStatusRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DealChangeStatusResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBearer": [] + } + ] + } + }, + "/deal/summaries": { + "get": { + "tags": [ + "deal" + ], + "summary": "Get Summary", + "operationId": "getDealSummaries", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DealSummaryResponse" + } + } + } + } + } + } + }, + "/deal/services/add": { + "post": { + "tags": [ + "deal" + ], + "summary": "Services Add", + "operationId": "services_add_deal_services_add_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DealAddServicesRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DealAddServicesResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/client/search": { + "get": { + "tags": [ + "client" + ], + "summary": "Search Clients", + "operationId": "search_clients", + "parameters": [ + { + "name": "name", + "in": "query", + "required": true, + "schema": { + "type": "string", + "title": "Name" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/client/update-details": { + "post": { + "tags": [ + "client" + ], + "summary": "Update Client Details", + "operationId": "update_client_details_client_update_details_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientUpdateDetailsRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBearer": [] + } + ] + } + }, + "/client/get-all": { + "get": { + "tags": [ + "client" + ], + "summary": "Get All Clients", + "operationId": "get_all_clients", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientGetAllResponse" + } + } + } + } + } + } + }, + "/service/get-all": { + "get": { + "tags": [ + "service" + ], + "summary": "Get All", + "operationId": "get_all_services", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceGetAllResponse" + } + } + } + } + } + } + }, + "/service/create": { + "post": { + "tags": [ + "service" + ], + "summary": "Create", + "operationId": "create_service", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceCreateResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/service/categories/get-all": { + "get": { + "tags": [ + "service" + ], + "summary": "Get All Categories", + "operationId": "get_all_service_categories", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceGetAllCategoriesResponse" + } + } + } + } + } + } + }, + "/service/categories/create": { + "post": { + "tags": [ + "service" + ], + "summary": "Create Category", + "operationId": "create_service_category", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceCreateCategoryRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceCreateCategoryResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/product/create": { + "post": { + "tags": [ + "product" + ], + "summary": "Create Product", + "operationId": "create_product", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductCreateResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/product/get": { + "get": { + "tags": [ + "product" + ], + "summary": "Get Product", + "operationId": "get_products_by_client_id", + "parameters": [ + { + "name": "client_id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "title": "Client Id" + } + }, + { + "name": "page", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "title": "Page" + } + }, + { + "name": "items_per_page", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "title": "Items Per Page" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductGetResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AuthLoginRequest": { + "properties": { + "auth_date": { + "type": "integer", + "title": "Auth Date" + }, + "first_name": { + "type": "string", + "title": "First Name" + }, + "hash": { + "type": "string", + "title": "Hash" + }, + "id": { + "type": "integer", + "title": "Id" + }, + "photo_url": { + "type": "string", + "title": "Photo Url" + } + }, + "type": "object", + "required": [ + "auth_date", + "first_name", + "hash", + "id", + "photo_url" + ], + "title": "AuthLoginRequest" + }, + "AuthLoginResponse": { + "properties": { + "access_token": { + "type": "string", + "title": "Access Token" + } + }, + "type": "object", + "required": [ + "access_token" + ], + "title": "AuthLoginResponse" + }, + "ClientDetailsSchema": { + "properties": { + "address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Address" + }, + "phone_number": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Phone Number" + }, + "inn": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Inn" + }, + "email": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Email" + } + }, + "type": "object", + "title": "ClientDetailsSchema" + }, + "ClientGetAllResponse": { + "properties": { + "clients": { + "items": { + "$ref": "#/components/schemas/ClientSchema" + }, + "type": "array", + "title": "Clients" + } + }, + "type": "object", + "required": [ + "clients" + ], + "title": "ClientGetAllResponse" + }, + "ClientSchema": { + "properties": { + "id": { + "type": "integer", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "details": { + "anyOf": [ + { + "$ref": "#/components/schemas/ClientDetailsSchema" + }, + { + "type": "null" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "name" + ], + "title": "ClientSchema" + }, + "ClientUpdateDetailsRequest": { + "properties": { + "client_id": { + "type": "integer", + "title": "Client Id" + }, + "details": { + "$ref": "#/components/schemas/ClientDetailsSchema" + } + }, + "type": "object", + "required": [ + "client_id", + "details" + ], + "title": "ClientUpdateDetailsRequest" + }, + "DealAddServicesRequest": { + "properties": { + "deal_id": { + "type": "integer", + "title": "Deal Id" + }, + "services": { + "items": { + "$ref": "#/components/schemas/DealServiceSchema" + }, + "type": "array", + "title": "Services" + } + }, + "type": "object", + "required": [ + "deal_id", + "services" + ], + "title": "DealAddServicesRequest" + }, + "DealAddServicesResponse": { + "properties": { + "ok": { + "type": "boolean", + "title": "Ok" + }, + "message": { + "type": "string", + "title": "Message" + } + }, + "type": "object", + "required": [ + "ok", + "message" + ], + "title": "DealAddServicesResponse" + }, + "DealChangeStatusRequest": { + "properties": { + "deal_id": { + "type": "integer", + "title": "Deal Id" + }, + "new_status": { + "type": "integer", + "title": "New Status" + } + }, + "type": "object", + "required": [ + "deal_id", + "new_status" + ], + "title": "DealChangeStatusRequest" + }, + "DealChangeStatusResponse": { + "properties": { + "ok": { + "type": "boolean", + "title": "Ok" + } + }, + "type": "object", + "required": [ + "ok" + ], + "title": "DealChangeStatusResponse" + }, + "DealCreateRequest": { + "properties": { + "name": { + "type": "string", + "title": "Name" + } + }, + "type": "object", + "required": [ + "name" + ], + "title": "DealCreateRequest" + }, + "DealQuickCreateRequest": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "client_name": { + "type": "string", + "title": "Client Name" + }, + "client_address": { + "type": "string", + "title": "Client Address" + }, + "comment": { + "type": "string", + "title": "Comment" + }, + "acceptance_date": { + "type": "string", + "format": "date-time", + "title": "Acceptance Date" + } + }, + "type": "object", + "required": [ + "name", + "client_name", + "client_address", + "comment", + "acceptance_date" + ], + "title": "DealQuickCreateRequest" + }, + "DealQuickCreateResponse": { + "properties": { + "deal_id": { + "type": "integer", + "title": "Deal Id" + } + }, + "type": "object", + "required": [ + "deal_id" + ], + "title": "DealQuickCreateResponse" + }, + "DealServiceSchema": { + "properties": { + "id": { + "type": "integer", + "title": "Id" + }, + "quantity": { + "type": "integer", + "title": "Quantity" + } + }, + "type": "object", + "required": [ + "id", + "quantity" + ], + "title": "DealServiceSchema" + }, + "DealSummary": { + "properties": { + "id": { + "type": "integer", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "client_name": { + "type": "string", + "title": "Client Name" + }, + "changed_at": { + "type": "string", + "format": "date-time", + "title": "Changed At" + }, + "status": { + "type": "integer", + "title": "Status" + }, + "total_price": { + "type": "integer", + "title": "Total Price" + } + }, + "type": "object", + "required": [ + "id", + "name", + "client_name", + "changed_at", + "status", + "total_price" + ], + "title": "DealSummary" + }, + "DealSummaryResponse": { + "properties": { + "summaries": { + "items": { + "$ref": "#/components/schemas/DealSummary" + }, + "type": "array", + "title": "Summaries" + } + }, + "type": "object", + "required": [ + "summaries" + ], + "title": "DealSummaryResponse" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "PaginationInfoSchema": { + "properties": { + "total_pages": { + "type": "integer", + "title": "Total Pages" + }, + "total_items": { + "type": "integer", + "title": "Total Items" + } + }, + "type": "object", + "required": [ + "total_pages", + "total_items" + ], + "title": "PaginationInfoSchema" + }, + "ProductCreateRequest": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "article": { + "type": "string", + "title": "Article" + }, + "client_id": { + "type": "integer", + "title": "Client Id" + }, + "barcodes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Barcodes" + } + }, + "type": "object", + "required": [ + "name", + "article", + "client_id", + "barcodes" + ], + "title": "ProductCreateRequest" + }, + "ProductCreateResponse": { + "properties": { + "product_id": { + "type": "integer", + "title": "Product Id" + } + }, + "type": "object", + "required": [ + "product_id" + ], + "title": "ProductCreateResponse" + }, + "ProductGetResponse": { + "properties": { + "products": { + "items": { + "$ref": "#/components/schemas/ProductSchema" + }, + "type": "array", + "title": "Products" + }, + "pagination_info": { + "$ref": "#/components/schemas/PaginationInfoSchema" + } + }, + "type": "object", + "required": [ + "products", + "pagination_info" + ], + "title": "ProductGetResponse" + }, + "ProductSchema": { + "properties": { + "id": { + "type": "integer", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "article": { + "type": "string", + "title": "Article" + }, + "client_id": { + "type": "integer", + "title": "Client Id" + } + }, + "type": "object", + "required": [ + "id", + "name", + "article", + "client_id" + ], + "title": "ProductSchema" + }, + "ServiceCategorySchema": { + "properties": { + "id": { + "type": "integer", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + } + }, + "type": "object", + "required": [ + "id", + "name" + ], + "title": "ServiceCategorySchema" + }, + "ServiceCreateCategoryRequest": { + "properties": { + "category": { + "$ref": "#/components/schemas/ServiceCategorySchema" + } + }, + "type": "object", + "required": [ + "category" + ], + "title": "ServiceCreateCategoryRequest" + }, + "ServiceCreateCategoryResponse": { + "properties": { + "ok": { + "type": "boolean", + "title": "Ok" + }, + "message": { + "type": "string", + "title": "Message" + } + }, + "type": "object", + "required": [ + "ok", + "message" + ], + "title": "ServiceCreateCategoryResponse" + }, + "ServiceCreateRequest": { + "properties": { + "service": { + "$ref": "#/components/schemas/ServiceSchema" + } + }, + "type": "object", + "required": [ + "service" + ], + "title": "ServiceCreateRequest" + }, + "ServiceCreateResponse": { + "properties": { + "ok": { + "type": "boolean", + "title": "Ok" + }, + "message": { + "type": "string", + "title": "Message" + } + }, + "type": "object", + "required": [ + "ok", + "message" + ], + "title": "ServiceCreateResponse" + }, + "ServiceGetAllCategoriesResponse": { + "properties": { + "categories": { + "items": { + "$ref": "#/components/schemas/ServiceCategorySchema" + }, + "type": "array", + "title": "Categories" + } + }, + "type": "object", + "required": [ + "categories" + ], + "title": "ServiceGetAllCategoriesResponse" + }, + "ServiceGetAllResponse": { + "properties": { + "services": { + "items": { + "$ref": "#/components/schemas/ServiceSchema" + }, + "type": "array", + "title": "Services" + } + }, + "type": "object", + "required": [ + "services" + ], + "title": "ServiceGetAllResponse" + }, + "ServiceSchema": { + "properties": { + "id": { + "type": "integer", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "category": { + "$ref": "#/components/schemas/ServiceCategorySchema" + }, + "price": { + "type": "number", + "title": "Price" + } + }, + "type": "object", + "required": [ + "id", + "name", + "category", + "price" + ], + "title": "ServiceSchema" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + } + }, + "securitySchemes": { + "HTTPBearer": { + "type": "http", + "scheme": "bearer" + } + } + } +} \ No newline at end of file diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..528a0ac --- /dev/null +++ b/test/test.py @@ -0,0 +1,36 @@ +import asyncio + +from sqlalchemy.ext.asyncio import AsyncSession + +from backend.session import session_maker +from models import Product, ProductBarcode + + +async def main(session: AsyncSession): + client_ids = [2, 4] + for client_id in client_ids: + for i in range(1, 500 + 1): + product = Product( + name=f"Товар №{i}", + article=f"Ариткул товара №{i}", + client_id=client_id + ) + session.add(product) + await session.flush() + for j in range(1, 5 + 1): + barcode = ProductBarcode( + barcode=f"Штрихкод №{j} для товара №{i}", + product_id=product.id + ) + session.add(barcode) + await session.flush() + await session.commit() + + +async def preload(): + async with session_maker() as session: + await main(session) + + +if __name__ == '__main__': + asyncio.run(preload())