From 99f0308a8adfb33218a8ced39c6d5f9bdcb87d94 Mon Sep 17 00:00:00 2001 From: fakz9 Date: Fri, 3 May 2024 17:07:36 +0300 Subject: [PATCH] feat: temp barcode templates --- main.py | 1 + models/__init__.py | 1 + models/barcode.py | 22 +++++++ models/client.py | 4 +- models/product.py | 3 + models/secondary.py | 17 +++++ routers/__init__.py | 1 + routers/barcode.py | 82 ++++++++++++++++++++++++ schemas/barcode.py | 64 +++++++++++++++++++ services/barcode.py | 147 ++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 models/barcode.py create mode 100644 routers/barcode.py create mode 100644 schemas/barcode.py create mode 100644 services/barcode.py diff --git a/main.py b/main.py index 493586b..52cb0ea 100644 --- a/main.py +++ b/main.py @@ -31,6 +31,7 @@ routers_list = [ routers.client_router, routers.service_router, routers.product_router, + routers.barcode_router ] for router in routers_list: app.include_router(router) diff --git a/models/__init__.py b/models/__init__.py index cc7220d..c2803b4 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -6,4 +6,5 @@ from .client import * from .service import * from .product import * from .secondary import * +from .barcode import * configure_mappers() diff --git a/models/barcode.py b/models/barcode.py new file mode 100644 index 0000000..5b0f25e --- /dev/null +++ b/models/barcode.py @@ -0,0 +1,22 @@ +from sqlalchemy import Integer, Column, String, Boolean +from sqlalchemy.orm import relationship + +from models import BaseModel + + +class BarcodeTemplateAttribute(BaseModel): + __tablename__ = 'barcode_template_attributes' + id = Column(Integer, autoincrement=True, primary_key=True, index=True) + name = Column(String, nullable=False, index=True, comment='Название атрибута') + label = Column(String, nullable=False, index=True, comment='Метка атрибута') + + +class BarcodeTemplate(BaseModel): + __tablename__ = 'barcode_templates' + id = Column(Integer, autoincrement=True, primary_key=True, index=True) + name = Column(String, nullable=False, index=True, comment='Название шаблона') + attributes = relationship('BarcodeTemplateAttributeLink', + back_populates='barcode_template', + cascade="all, delete-orphan", + lazy='joined') + is_default = Column(Boolean, nullable=False, default=False, comment='По умолчанию') diff --git a/models/client.py b/models/client.py index a3d5fbf..8be03ff 100644 --- a/models/client.py +++ b/models/client.py @@ -11,9 +11,11 @@ class Client(BaseModel): created_at = Column(DateTime, nullable=False, comment='Дата создания') products = relationship('Product', back_populates='client') - details = relationship('ClientDetails', uselist=False, back_populates='client', cascade='all, delete') + barcode_template_id = Column(Integer, ForeignKey('barcode_templates.id'), nullable=True) + barcode_template = relationship('BarcodeTemplate') + class ClientDetails(BaseModel): __tablename__ = 'client_details' diff --git a/models/product.py b/models/product.py index 24ba807..52f9163 100644 --- a/models/product.py +++ b/models/product.py @@ -18,6 +18,9 @@ class Product(BaseModel): client = relationship('Client', back_populates='products') barcodes = relationship('ProductBarcode', back_populates='product', cascade="all, delete-orphan") + barcode_template_id = Column(Integer, ForeignKey('barcode_templates.id'), nullable=True) + barcode_template = relationship('BarcodeTemplate') + class ProductBarcode(BaseModel): __tablename__ = 'product_barcodes' diff --git a/models/secondary.py b/models/secondary.py index fd1ced3..a971056 100644 --- a/models/secondary.py +++ b/models/secondary.py @@ -31,3 +31,20 @@ class DealProduct(BaseModel): product = relationship('Product') quantity = Column(Integer, nullable=False, comment='Кол-во продукта') + + +class BarcodeTemplateAttributeLink(BaseModel): + __tablename__ = 'barcode_template_attributes_links' + barcode_template_id = Column(Integer, + ForeignKey('barcode_templates.id'), + nullable=False, + comment='ID Шаблона ШК', + primary_key=True) + barcode_template = relationship('BarcodeTemplate', back_populates='attributes') + + attribute_id = Column(Integer, + ForeignKey('barcode_template_attributes.id'), + nullable=False, + comment='ID Атрибута', + primary_key=True) + attribute = relationship('BarcodeTemplateAttribute') diff --git a/routers/__init__.py b/routers/__init__.py index e220ddc..a22495c 100644 --- a/routers/__init__.py +++ b/routers/__init__.py @@ -3,3 +3,4 @@ from .deal import deal_router from .client import client_router from .service import service_router from .product import product_router +from .barcode import barcode_router diff --git a/routers/barcode.py b/routers/barcode.py new file mode 100644 index 0000000..d7ece7e --- /dev/null +++ b/routers/barcode.py @@ -0,0 +1,82 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from backend.session import get_session +from schemas.barcode import (GetBarcodeTemplateByIdResponse, + GetBarcodeTemplateByIdRequest, + BarcodeTemplateCreateResponse, + BarcodeTemplateCreateRequest, GetAllBarcodeTemplateAttributesResponse, + CreateBarcodeTemplateAttributeResponse, CreateBarcodeTemplateAttributeRequest, + BarcodeTemplateUpdateResponse, BarcodeTemplateUpdateRequest) +from services.barcode import BarcodeService + +barcode_router = APIRouter( + prefix='/barcode', + tags=['barcode'], +) + + +# region Templates +@barcode_router.get( + '/template/get', + response_model=GetBarcodeTemplateByIdResponse, + operation_id='get_barcode_template_by_id' +) +async def get_barcode_template_by_id( + request: GetBarcodeTemplateByIdRequest, + session: Annotated[AsyncSession, Depends(get_session)] +): + return await BarcodeService(session).get_barcode_template_by_id(request) + + +@barcode_router.post( + '/template/create', + response_model=BarcodeTemplateCreateResponse, + operation_id='create_barcode_template' +) +async def create_barcode_template( + request: BarcodeTemplateCreateRequest, + session: Annotated[AsyncSession, Depends(get_session)] +): + return await BarcodeService(session).create_barcode_template(request) + + +@barcode_router.post( + '/template/update', + response_model=BarcodeTemplateUpdateResponse, + operation_id='update_barcode_template' +) +async def update_barcode_template( + request: BarcodeTemplateUpdateRequest, + session: Annotated[AsyncSession, Depends(get_session)] +): + return await BarcodeService(session).update_barcode_template(request) + + +# endregion + +# region Template attributes +@barcode_router.get( + '/template/attribute/get-all', + response_model=GetAllBarcodeTemplateAttributesResponse, + operation_id='get_all_barcode_template_attributes' +) +async def get_all_barcode_template_attributes( + session: Annotated[AsyncSession, Depends(get_session)] +): + return await BarcodeService(session).get_all_barcode_template_attributes() + + +@barcode_router.post( + '/template/attribute/create', + response_model=CreateBarcodeTemplateAttributeResponse, + operation_id='create_barcode_template_attribute' +) +async def create_barcode_template_attribute( + request: CreateBarcodeTemplateAttributeRequest, + session: Annotated[AsyncSession, Depends(get_session)] +): + return await BarcodeService(session).create_barcode_template_attribute(request) +# endregion diff --git a/schemas/barcode.py b/schemas/barcode.py new file mode 100644 index 0000000..261a841 --- /dev/null +++ b/schemas/barcode.py @@ -0,0 +1,64 @@ +from schemas.base import CustomModelCamel, OkMessageSchema + + +# region Entities +class BarcodeTemplateAttribute(CustomModelCamel): + id: int + label: str + name: str + + +class BarcodeTemplate(CustomModelCamel): + id: int + name: str + is_default: bool + attributes: list[BarcodeTemplateAttribute] + + +# endregion + +# region Requests +class GetBarcodeTemplateByIdRequest(CustomModelCamel): + id: int + + +class BarcodeTemplateCreateResponse(OkMessageSchema): + id: int + + +class BarcodeTemplateUpdateResponse(OkMessageSchema): + pass + + +class GetAllBarcodeTemplateAttributesResponse(CustomModelCamel): + attributes: list[BarcodeTemplateAttribute] + + +class CreateBarcodeTemplateAttributeRequest(CustomModelCamel): + name: str + label: str + + +# endregion + +# region Responses +class GetBarcodeTemplateByIdResponse(CustomModelCamel): + barcode_template: BarcodeTemplate + + +class BarcodeTemplateCreateRequest(CustomModelCamel): + name: str + attribute_ids: list[int] + is_default: bool + + +class BarcodeTemplateUpdateRequest(CustomModelCamel): + id: int + name: str + is_default: bool + attribute_ids: list[int] + + +class CreateBarcodeTemplateAttributeResponse(OkMessageSchema): + id: int +# endregion diff --git a/services/barcode.py b/services/barcode.py new file mode 100644 index 0000000..3d1a586 --- /dev/null +++ b/services/barcode.py @@ -0,0 +1,147 @@ +from sqlalchemy import select, update + +from models import BarcodeTemplate, BarcodeTemplateAttribute, BarcodeTemplateAttributeLink +from schemas.barcode import (GetBarcodeTemplateByIdRequest, + GetBarcodeTemplateByIdResponse, + BarcodeTemplateCreateResponse, + BarcodeTemplateCreateRequest, CreateBarcodeTemplateAttributeRequest, + BarcodeTemplateUpdateResponse, BarcodeTemplateUpdateRequest) +from services.base import BaseService + + +class BarcodeService(BaseService): + + # region Template + async def get_barcode_template_by_id(self, + request: GetBarcodeTemplateByIdRequest) -> GetBarcodeTemplateByIdResponse: + stmt = select(BarcodeTemplate).filter(BarcodeTemplate.id == request.id) + query = await self.session.execute( + stmt + ) + return query.scalar() + + async def create_barcode_template(self, request: BarcodeTemplateCreateRequest) -> BarcodeTemplateCreateResponse: + try: + if request.is_default: + stmt = select(BarcodeTemplate).filter(BarcodeTemplate.is_default == True) + query = await self.session.execute(stmt) + if query.scalar(): + raise ValueError('Стандартный шаблон уже существует') + + # prevent duplicate template + stmt = select(BarcodeTemplate).filter(BarcodeTemplate.name == request.name) + query = await self.session.execute(stmt) + if query.scalar(): + raise ValueError('Шаблон с таким именем уже существует') + + # create template then add attributes + template = BarcodeTemplate(name=request.name, is_default=request.is_default) + self.session.add(template) + await self.session.flush() + + # get all attributes from database + stmt = select(BarcodeTemplateAttribute).filter( + BarcodeTemplateAttribute.id.in_(request.attribute_ids)) + query = await self.session.execute(stmt) + attributes = query.scalars().all() + + # add attributes to template + for attribute in attributes: + template_attribute_link = BarcodeTemplateAttributeLink( + barcode_template_id=template.id, + attribute_id=attribute.id + ) + self.session.add(template_attribute_link) + await self.session.flush() + await self.session.commit() + return BarcodeTemplateCreateResponse(message='Шаблон успешно создан', + ok=True, + id=template.id) + except Exception as e: + await self.session.rollback() + return BarcodeTemplateCreateResponse(message=str(e), + ok=False, + id=-1) + + async def update_barcode_template(self, request: BarcodeTemplateUpdateRequest) -> BarcodeTemplateUpdateResponse: + try: + stmt = select(BarcodeTemplate).filter(BarcodeTemplate.id == request.id) + query = await self.session.execute(stmt) + template = query.scalar() + if not template: + raise ValueError('Шаблон не найден') + + if request.is_default: + stmt = select(BarcodeTemplate).filter(BarcodeTemplate.is_default == True) + query = await self.session.execute(stmt) + default_template = query.scalar() + if default_template and default_template.id != request.id: + raise ValueError('Стандартный шаблон уже существует') + + # update template then add attributes with dict and value syntax + request_dict = request.dict() + del request_dict['id'] + del request_dict['attribute_ids'] + await self.session.execute( + update(BarcodeTemplate) + .where(BarcodeTemplate.id == request.id) + .values(**request_dict) + ) + + # difference deleted and new + template_attributes = set([attribute.attribute_id for attribute in template.attributes]) + request_attributes = set(request.attribute_ids) + new_attributes = request_attributes.difference(template_attributes) + deleted_attributes = template_attributes.difference(request_attributes) + + # delete attributes + for attribute in template.attributes: + if attribute.attribute_id not in deleted_attributes: + continue + await self.session.delete(attribute) + + # add new attributes + for new_attribute in new_attributes: + template_attribute_link = BarcodeTemplateAttributeLink( + barcode_template_id=template.id, + attribute_id=new_attribute + ) + self.session.add(template_attribute_link) + await self.session.flush() + + await self.session.commit() + return BarcodeTemplateUpdateResponse(message='Шаблон успешно обновлен', + ok=True) + except Exception as e: + await self.session.rollback() + return BarcodeTemplateUpdateResponse(message=str(e), + ok=False) + + # endregion + + # region Template attributes + async def get_all_barcode_template_attributes(self): + stmt = select(BarcodeTemplateAttribute) + query = await self.session.execute(stmt) + return query.scalars().all() + + async def create_barcode_template_attribute(self, request: CreateBarcodeTemplateAttributeRequest): + try: + # prevent duplicate attribute + stmt = select(BarcodeTemplateAttribute).filter(BarcodeTemplateAttribute.name == request.name) + query = await self.session.execute(stmt) + if query.scalar(): + raise ValueError('Атрибут с таким именем уже существует') + + attribute = BarcodeTemplateAttribute(**request.dict()) + self.session.add(attribute) + await self.session.commit() + return BarcodeTemplateCreateResponse(message='Атрибут успешно создан', + ok=True, + id=attribute.id) + except Exception as e: + await self.session.rollback() + return BarcodeTemplateCreateResponse(message=str(e), + ok=False, + id=-1) + # endregion