feat: temp barcode templates
This commit is contained in:
1
main.py
1
main.py
@@ -31,6 +31,7 @@ routers_list = [
|
|||||||
routers.client_router,
|
routers.client_router,
|
||||||
routers.service_router,
|
routers.service_router,
|
||||||
routers.product_router,
|
routers.product_router,
|
||||||
|
routers.barcode_router
|
||||||
]
|
]
|
||||||
for router in routers_list:
|
for router in routers_list:
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ from .client import *
|
|||||||
from .service import *
|
from .service import *
|
||||||
from .product import *
|
from .product import *
|
||||||
from .secondary import *
|
from .secondary import *
|
||||||
|
from .barcode import *
|
||||||
configure_mappers()
|
configure_mappers()
|
||||||
|
|||||||
22
models/barcode.py
Normal file
22
models/barcode.py
Normal file
@@ -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='По умолчанию')
|
||||||
@@ -11,9 +11,11 @@ class Client(BaseModel):
|
|||||||
created_at = Column(DateTime, nullable=False, comment='Дата создания')
|
created_at = Column(DateTime, nullable=False, comment='Дата создания')
|
||||||
|
|
||||||
products = relationship('Product', back_populates='client')
|
products = relationship('Product', back_populates='client')
|
||||||
|
|
||||||
details = relationship('ClientDetails', uselist=False, back_populates='client', cascade='all, delete')
|
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):
|
class ClientDetails(BaseModel):
|
||||||
__tablename__ = 'client_details'
|
__tablename__ = 'client_details'
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ class Product(BaseModel):
|
|||||||
client = relationship('Client', back_populates='products')
|
client = relationship('Client', back_populates='products')
|
||||||
barcodes = relationship('ProductBarcode', back_populates='product', cascade="all, delete-orphan")
|
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):
|
class ProductBarcode(BaseModel):
|
||||||
__tablename__ = 'product_barcodes'
|
__tablename__ = 'product_barcodes'
|
||||||
|
|||||||
@@ -31,3 +31,20 @@ class DealProduct(BaseModel):
|
|||||||
product = relationship('Product')
|
product = relationship('Product')
|
||||||
|
|
||||||
quantity = Column(Integer, nullable=False, comment='Кол-во продукта')
|
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')
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ from .deal import deal_router
|
|||||||
from .client import client_router
|
from .client import client_router
|
||||||
from .service import service_router
|
from .service import service_router
|
||||||
from .product import product_router
|
from .product import product_router
|
||||||
|
from .barcode import barcode_router
|
||||||
|
|||||||
82
routers/barcode.py
Normal file
82
routers/barcode.py
Normal file
@@ -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
|
||||||
64
schemas/barcode.py
Normal file
64
schemas/barcode.py
Normal file
@@ -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
|
||||||
147
services/barcode.py
Normal file
147
services/barcode.py
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user