feat: barcode templates
This commit is contained in:
		@@ -1,17 +1,8 @@
 | 
			
		||||
from abc import abstractmethod, ABC
 | 
			
		||||
 | 
			
		||||
from models import Product
 | 
			
		||||
from .article_attribute_getter import ArticleAttributeGetter
 | 
			
		||||
from .client_name_attribute_getter import ClientNameAttributeGetter
 | 
			
		||||
from .name_attribute_getter import NameAttributeGetter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseAttributeGetter(ABC):
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def get_value(self, product: Product):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AttributeWriterFactory:
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_writer(key: str):
 | 
			
		||||
@@ -22,3 +13,5 @@ class AttributeWriterFactory:
 | 
			
		||||
                return ArticleAttributeGetter()
 | 
			
		||||
            case 'client.name':
 | 
			
		||||
                return ClientNameAttributeGetter()
 | 
			
		||||
            case _:
 | 
			
		||||
                return None
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
from barcodes.attributes import BaseAttributeGetter
 | 
			
		||||
from barcodes.attributes.base import BaseAttributeGetter
 | 
			
		||||
from models import Product
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								barcodes/attributes/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								barcodes/attributes/base.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
from abc import ABC, abstractmethod
 | 
			
		||||
 | 
			
		||||
from models import Product
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseAttributeGetter(ABC):
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def get_value(self, product: Product):
 | 
			
		||||
        pass
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
from barcodes.attributes import BaseAttributeGetter
 | 
			
		||||
from barcodes.attributes.base import BaseAttributeGetter
 | 
			
		||||
from models import Product, Client
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
from barcodes.attributes import BaseAttributeGetter
 | 
			
		||||
from barcodes.attributes.base import BaseAttributeGetter
 | 
			
		||||
from models import Product
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								barcodes/generator/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								barcodes/generator/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										13
									
								
								barcodes/generator/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								barcodes/generator/base.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
from abc import abstractmethod
 | 
			
		||||
 | 
			
		||||
from models import ProductBarcode, Product, BarcodeTemplate
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseBarcodeGenerator:
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def generate(self,
 | 
			
		||||
                 barcode: ProductBarcode,
 | 
			
		||||
                 product: Product,
 | 
			
		||||
                 template: BarcodeTemplate):
 | 
			
		||||
        pass
 | 
			
		||||
							
								
								
									
										10
									
								
								barcodes/generator/default_generator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								barcodes/generator/default_generator.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
from barcodes.generator.base import BaseBarcodeGenerator
 | 
			
		||||
from models import ProductBarcode, Product, BarcodeTemplate
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DefaultBarcodeGenerator(BaseBarcodeGenerator):
 | 
			
		||||
    def generate(self,
 | 
			
		||||
                 barcode: ProductBarcode,
 | 
			
		||||
                 product: Product,
 | 
			
		||||
                 template: BarcodeTemplate):
 | 
			
		||||
        pass
 | 
			
		||||
@@ -20,22 +20,31 @@ class BarcodeTemplateAdditionalField(BaseModel):
 | 
			
		||||
    barcode_template = relationship('BarcodeTemplate')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BarcodeTemplateSize(BaseModel):
 | 
			
		||||
    __tablename__ = 'barcode_template_sizes'
 | 
			
		||||
    id = Column(Integer, autoincrement=True, primary_key=True, index=True)
 | 
			
		||||
 | 
			
		||||
    name = Column(String, nullable=False, index=True, comment='Название размера')
 | 
			
		||||
    key = Column(String, nullable=False, index=True, comment='Ключ размера')
 | 
			
		||||
 | 
			
		||||
    width = Column(Integer, nullable=False, comment='Ширина в мм')
 | 
			
		||||
    height = Column(Integer, nullable=False, 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('BarcodeTemplateAttribute',
 | 
			
		||||
                              secondary=barcode_template_attribute_link,
 | 
			
		||||
                              cascade="all",
 | 
			
		||||
                              lazy='selectin'
 | 
			
		||||
                              )
 | 
			
		||||
    additional_fields = relationship('BarcodeTemplateAdditionalField',
 | 
			
		||||
                                     lazy='selectin',
 | 
			
		||||
                                     back_populates='barcode_template',
 | 
			
		||||
                                     cascade="all")
 | 
			
		||||
    additional_attributes = relationship('BarcodeTemplateAdditionalField',
 | 
			
		||||
                                         lazy='selectin',
 | 
			
		||||
                                         back_populates='barcode_template',
 | 
			
		||||
                                         cascade="all, delete")
 | 
			
		||||
 | 
			
		||||
    is_default = Column(Boolean, nullable=False, default=False, comment='По умолчанию')
 | 
			
		||||
 | 
			
		||||
    # size
 | 
			
		||||
    width = Column(Integer, nullable=False, comment='Ширина в мм')
 | 
			
		||||
    height = Column(Integer, nullable=False, comment='Высота в мм')
 | 
			
		||||
    size_id = Column(Integer, ForeignKey('barcode_template_sizes.id'), nullable=False)
 | 
			
		||||
    size = relationship('BarcodeTemplateSize', lazy='joined')
 | 
			
		||||
 
 | 
			
		||||
@@ -33,23 +33,6 @@ class DealProduct(BaseModel):
 | 
			
		||||
    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')
 | 
			
		||||
barcode_template_attribute_link = Table(
 | 
			
		||||
    'barcode_template_attribute_links',
 | 
			
		||||
    BaseModel.metadata,
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ from schemas.barcode import (GetBarcodeTemplateByIdResponse,
 | 
			
		||||
                             CreateBarcodeTemplateAttributeResponse, CreateBarcodeTemplateAttributeRequest,
 | 
			
		||||
                             BarcodeTemplateUpdateResponse, BarcodeTemplateUpdateRequest,
 | 
			
		||||
                             GetAllBarcodeTemplatesResponse, BarcodeTemplateDeleteRequest,
 | 
			
		||||
                             BarcodeTemplateDeleteResponse)
 | 
			
		||||
                             BarcodeTemplateDeleteResponse, GetAllBarcodeTemplateSizesResponse)
 | 
			
		||||
from services.barcode import BarcodeService
 | 
			
		||||
 | 
			
		||||
barcode_router = APIRouter(
 | 
			
		||||
@@ -104,4 +104,18 @@ async def create_barcode_template_attribute(
 | 
			
		||||
        session: Annotated[AsyncSession, Depends(get_session)]
 | 
			
		||||
):
 | 
			
		||||
    return await BarcodeService(session).create_barcode_template_attribute(request)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# endregion
 | 
			
		||||
 | 
			
		||||
# region Template size
 | 
			
		||||
@barcode_router.get(
 | 
			
		||||
    '/template/size/get-all',
 | 
			
		||||
    response_model=GetAllBarcodeTemplateSizesResponse,
 | 
			
		||||
    operation_id='get_all_barcode_template_sizes'
 | 
			
		||||
)
 | 
			
		||||
async def get_all_barcode_template_sizes(
 | 
			
		||||
        session: Annotated[AsyncSession, Depends(get_session)]
 | 
			
		||||
):
 | 
			
		||||
    return await BarcodeService(session).get_all_barcode_template_sizes()
 | 
			
		||||
# endregion
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
 | 
			
		||||
 | 
			
		||||
import utils.dependecies
 | 
			
		||||
from backend.session import get_session
 | 
			
		||||
from schemas.barcode import GetProductBarcodeResponse, GetProductBarcodeRequest
 | 
			
		||||
from schemas.base import PaginationSchema
 | 
			
		||||
from schemas.product import *
 | 
			
		||||
from services.auth import get_current_user
 | 
			
		||||
from services.barcode import BarcodeService
 | 
			
		||||
from services.product import ProductService
 | 
			
		||||
 | 
			
		||||
product_router = APIRouter(
 | 
			
		||||
@@ -111,3 +113,15 @@ async def generate_product_barcode(
 | 
			
		||||
        session: Annotated[AsyncSession, Depends(get_session)]
 | 
			
		||||
):
 | 
			
		||||
    return await ProductService(session).generate_barcode(request)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@product_router.post(
 | 
			
		||||
    '/barcode/get',
 | 
			
		||||
    response_model=GetProductBarcodeResponse,
 | 
			
		||||
    operation_id='get_product_barcode'
 | 
			
		||||
)
 | 
			
		||||
async def get_product_barcode(
 | 
			
		||||
        request: GetProductBarcodeRequest,
 | 
			
		||||
        session: Annotated[AsyncSession, Depends(get_session)]
 | 
			
		||||
):
 | 
			
		||||
    return await BarcodeService(session).get_barcode(request)
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,14 @@ class BarcodeTemplateAttributeSchema(CustomModelCamel):
 | 
			
		||||
    name: str
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BarcodeTemplateSizeSchema(CustomModelCamel):
 | 
			
		||||
    id: int
 | 
			
		||||
    name: str
 | 
			
		||||
    key: str
 | 
			
		||||
    width: int
 | 
			
		||||
    height: int
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BarcodeTemplateAdditionalAttributeSchema(CustomModelCamel):
 | 
			
		||||
    name: str
 | 
			
		||||
    value: str
 | 
			
		||||
@@ -18,8 +26,7 @@ class BarcodeTemplateAdditionalAttributeSchema(CustomModelCamel):
 | 
			
		||||
class BaseBarcodeTemplateSchema(CustomModelCamel):
 | 
			
		||||
    name: str
 | 
			
		||||
    is_default: bool
 | 
			
		||||
    width: int
 | 
			
		||||
    height: int
 | 
			
		||||
    size: BarcodeTemplateSizeSchema
 | 
			
		||||
    additional_attributes: list[BarcodeTemplateAdditionalAttributeSchema]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -102,4 +109,8 @@ class BarcodeTemplateDeleteResponse(OkMessageSchema):
 | 
			
		||||
 | 
			
		||||
class GetProductBarcodeResponse(CustomModelCamel):
 | 
			
		||||
    barcode: BarcodeSchema
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GetAllBarcodeTemplateSizesResponse(CustomModelCamel):
 | 
			
		||||
    sizes: list[BarcodeTemplateSizeSchema]
 | 
			
		||||
# endregion
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
from sqlalchemy import select, update, insert
 | 
			
		||||
from sqlalchemy.orm import selectinload, joinedload
 | 
			
		||||
 | 
			
		||||
from models import BarcodeTemplate, BarcodeTemplateAttribute, barcode_template_attribute_link, Product
 | 
			
		||||
from barcodes.attributes import AttributeWriterFactory
 | 
			
		||||
from models import BarcodeTemplate, BarcodeTemplateAttribute, barcode_template_attribute_link, Product, \
 | 
			
		||||
    BarcodeTemplateAdditionalField, BarcodeTemplateSize
 | 
			
		||||
from schemas.barcode import *
 | 
			
		||||
from services.base import BaseService
 | 
			
		||||
 | 
			
		||||
@@ -9,21 +11,7 @@ from services.base import BaseService
 | 
			
		||||
class BarcodeService(BaseService):
 | 
			
		||||
 | 
			
		||||
    # region Barcode
 | 
			
		||||
    async def get_barcode(self, request: GetProductBarcodeRequest) -> GetProductBarcodeResponse:
 | 
			
		||||
        # get product by id
 | 
			
		||||
        stmt = (
 | 
			
		||||
            select(Product)
 | 
			
		||||
            .options(
 | 
			
		||||
                joinedload(Product.client)
 | 
			
		||||
            )
 | 
			
		||||
            .filter(Product.id == request.product_id)
 | 
			
		||||
        )
 | 
			
		||||
        query = await self.session.execute(stmt)
 | 
			
		||||
        product: Product = query.scalar()
 | 
			
		||||
        if not product:
 | 
			
		||||
            raise ValueError('Товар не найден')
 | 
			
		||||
 | 
			
		||||
        # get barcode template by id
 | 
			
		||||
    async def _get_barcode_template(self, request: GetProductBarcodeRequest, product: Product) -> BarcodeTemplate:
 | 
			
		||||
        if request.barcode_template_id:
 | 
			
		||||
            stmt = select(BarcodeTemplate).filter(BarcodeTemplate.id == request.barcode_template_id)
 | 
			
		||||
            query = await self.session.execute(stmt)
 | 
			
		||||
@@ -41,9 +29,46 @@ class BarcodeService(BaseService):
 | 
			
		||||
            if not barcode_template:
 | 
			
		||||
                raise ValueError('Стандартный шаблон не найден')
 | 
			
		||||
        barcode_template: BarcodeTemplate
 | 
			
		||||
        return barcode_template
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        # endregion
 | 
			
		||||
    async def get_barcode(self, request: GetProductBarcodeRequest) -> GetProductBarcodeResponse:
 | 
			
		||||
        # get product by id
 | 
			
		||||
        stmt = (
 | 
			
		||||
            select(Product)
 | 
			
		||||
            .options(
 | 
			
		||||
                joinedload(Product.client)
 | 
			
		||||
            )
 | 
			
		||||
            .filter(Product.id == request.product_id)
 | 
			
		||||
        )
 | 
			
		||||
        query = await self.session.execute(stmt)
 | 
			
		||||
        product: Product = query.scalar()
 | 
			
		||||
        if not product:
 | 
			
		||||
            raise ValueError('Товар не найден')
 | 
			
		||||
        barcode_template = await self._get_barcode_template(request, product)
 | 
			
		||||
        attributes_list = []
 | 
			
		||||
        for attribute in barcode_template.attributes:
 | 
			
		||||
            attribute_getter = AttributeWriterFactory.get_writer(attribute.key)
 | 
			
		||||
            if not attribute_getter:
 | 
			
		||||
                continue
 | 
			
		||||
            value = attribute_getter.get_value(product)
 | 
			
		||||
            attributes_list.append(
 | 
			
		||||
                BarcodeAttributeSchema(
 | 
			
		||||
                    name=attribute.name,
 | 
			
		||||
                    value=value
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        for additional_attribute in barcode_template.additional_attributes:
 | 
			
		||||
            attributes_list.append(
 | 
			
		||||
                BarcodeAttributeSchema(
 | 
			
		||||
                    name=additional_attribute.name,
 | 
			
		||||
                    value=additional_attribute.value
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        barcode = BarcodeSchema(
 | 
			
		||||
            barcode=request.barcode,
 | 
			
		||||
            attributes=attributes_list
 | 
			
		||||
        )
 | 
			
		||||
        return GetProductBarcodeResponse(barcode=barcode)
 | 
			
		||||
 | 
			
		||||
    # endregion
 | 
			
		||||
 | 
			
		||||
@@ -84,6 +109,9 @@ class BarcodeService(BaseService):
 | 
			
		||||
            # create template then add attributes
 | 
			
		||||
            request_dict = request.dict()
 | 
			
		||||
            del request_dict['attribute_ids']
 | 
			
		||||
            del request_dict['additional_attributes']
 | 
			
		||||
            del request_dict['size']
 | 
			
		||||
            request_dict['size_id'] = request.size.id
 | 
			
		||||
            template = BarcodeTemplate(**request_dict)
 | 
			
		||||
            self.session.add(template)
 | 
			
		||||
            await self.session.flush()
 | 
			
		||||
@@ -97,6 +125,11 @@ class BarcodeService(BaseService):
 | 
			
		||||
            # add attributes to template
 | 
			
		||||
            for attribute in attributes:
 | 
			
		||||
                template.attributes.append(attribute)
 | 
			
		||||
            for additional_attribute in request.additional_attributes:
 | 
			
		||||
                template.additional_attributes.append(
 | 
			
		||||
                    BarcodeTemplateAdditionalField(**additional_attribute.dict(),
 | 
			
		||||
                                                   barcode_template_id=template.id)
 | 
			
		||||
                )
 | 
			
		||||
            await self.session.commit()
 | 
			
		||||
            return BarcodeTemplateCreateResponse(message='Шаблон успешно создан',
 | 
			
		||||
                                                 ok=True,
 | 
			
		||||
@@ -126,6 +159,10 @@ class BarcodeService(BaseService):
 | 
			
		||||
            request_dict = request.dict()
 | 
			
		||||
            del request_dict['id']
 | 
			
		||||
            del request_dict['attribute_ids']
 | 
			
		||||
            del request_dict['additional_attributes']
 | 
			
		||||
            del request_dict['size']
 | 
			
		||||
            request_dict['size_id'] = request.size.id
 | 
			
		||||
 | 
			
		||||
            await self.session.execute(
 | 
			
		||||
                update(BarcodeTemplate)
 | 
			
		||||
                .where(BarcodeTemplate.id == request.id)
 | 
			
		||||
@@ -151,6 +188,24 @@ class BarcodeService(BaseService):
 | 
			
		||||
                await self.session.execute(stmt)
 | 
			
		||||
                await self.session.flush()
 | 
			
		||||
 | 
			
		||||
            # working with additional attributes
 | 
			
		||||
            # deleting additioanl attributes that is deleted
 | 
			
		||||
            template_additional_attributes = set([attribute.name for attribute in template.additional_attributes])
 | 
			
		||||
            request_additional_attributes = set([attribute.name for attribute in request.additional_attributes])
 | 
			
		||||
            new_additional_attributes = request_additional_attributes.difference(template_additional_attributes)
 | 
			
		||||
            deleted_additional_attributes = template_additional_attributes.difference(request_additional_attributes)
 | 
			
		||||
            for attribute in template.additional_attributes:
 | 
			
		||||
                if attribute.name not in deleted_additional_attributes:
 | 
			
		||||
                    continue
 | 
			
		||||
                await self.session.delete(attribute)
 | 
			
		||||
            for new_attribute in request.additional_attributes:
 | 
			
		||||
                if new_attribute.name not in new_additional_attributes:
 | 
			
		||||
                    continue
 | 
			
		||||
                template.additional_attributes.append(
 | 
			
		||||
                    BarcodeTemplateAdditionalField(**new_attribute.dict(),
 | 
			
		||||
                                                   barcode_template_id=template.id)
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            await self.session.commit()
 | 
			
		||||
            return BarcodeTemplateUpdateResponse(message='Шаблон успешно обновлен',
 | 
			
		||||
                                                 ok=True)
 | 
			
		||||
@@ -196,4 +251,13 @@ class BarcodeService(BaseService):
 | 
			
		||||
            return BarcodeTemplateCreateResponse(message=str(e),
 | 
			
		||||
                                                 ok=False,
 | 
			
		||||
                                                 id=-1)
 | 
			
		||||
 | 
			
		||||
    # endregion
 | 
			
		||||
 | 
			
		||||
    # region Template sizes
 | 
			
		||||
    async def get_all_barcode_template_sizes(self) -> GetAllBarcodeTemplateSizesResponse:
 | 
			
		||||
        stmt = select(BarcodeTemplateSize).order_by(BarcodeTemplateSize.id)
 | 
			
		||||
        query = await self.session.execute(stmt)
 | 
			
		||||
        sizes = query.scalars().all()
 | 
			
		||||
        return GetAllBarcodeTemplateSizesResponse(sizes=sizes)
 | 
			
		||||
    # endregion
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user