feat: barcode templates

This commit is contained in:
2024-05-09 21:30:46 +03:00
parent 87085379ed
commit bde69f9db8
14 changed files with 178 additions and 58 deletions

View File

@@ -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

View File

@@ -1,4 +1,4 @@
from barcodes.attributes import BaseAttributeGetter
from barcodes.attributes.base import BaseAttributeGetter
from models import Product

View 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

View File

@@ -1,4 +1,4 @@
from barcodes.attributes import BaseAttributeGetter
from barcodes.attributes.base import BaseAttributeGetter
from models import Product, Client

View File

@@ -1,4 +1,4 @@
from barcodes.attributes import BaseAttributeGetter
from barcodes.attributes.base import BaseAttributeGetter
from models import Product

View File

View 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

View 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

View File

@@ -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',
additional_attributes = relationship('BarcodeTemplateAdditionalField',
lazy='selectin',
back_populates='barcode_template',
cascade="all")
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')

View File

@@ -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,

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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