feat: barcode templates
This commit is contained in:
@ -1,6 +1,16 @@
|
||||
from .deal_product import DealProduct as DealProduct, DealProductService as DealProductService
|
||||
from .deal_service import (
|
||||
DealService as DealService,
|
||||
from .barcode import (
|
||||
ProductBarcode as ProductBarcode,
|
||||
ProductBarcodeImage as ProductBarcodeImage,
|
||||
)
|
||||
from .barcode_template import (
|
||||
BarcodeTemplateAttribute as BarcodeTemplateAttribute,
|
||||
BarcodeTemplateSize as BarcodeTemplateSize,
|
||||
BarcodeTemplate as BarcodeTemplate,
|
||||
)
|
||||
from .deal_product import (
|
||||
DealProduct as DealProduct,
|
||||
DealProductService as DealProductService,
|
||||
)
|
||||
from .deal_service import DealService as DealService
|
||||
from .product import Product as Product
|
||||
from .service import Service as Service, ServiceCategory as ServiceCategory
|
||||
|
||||
36
modules/fulfillment_base/models/barcode.py
Normal file
36
modules/fulfillment_base/models/barcode.py
Normal file
@ -0,0 +1,36 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from models.base import BaseModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from modules.fulfillment_base.models import Product
|
||||
|
||||
|
||||
class ProductBarcode(BaseModel):
|
||||
__tablename__ = "fulfillment_base_product_barcodes"
|
||||
|
||||
product_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("fulfillment_base_products.id"),
|
||||
primary_key=True,
|
||||
)
|
||||
product: Mapped["Product"] = relationship(back_populates="barcodes")
|
||||
|
||||
barcode: Mapped[str] = mapped_column(
|
||||
primary_key=True, index=True, comment="ШК товара"
|
||||
)
|
||||
|
||||
|
||||
class ProductBarcodeImage(BaseModel):
|
||||
__tablename__ = "fulfillment_base_product_barcode_images"
|
||||
|
||||
product_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("fulfillment_base_products.id"),
|
||||
primary_key=True,
|
||||
comment="ID товара",
|
||||
)
|
||||
product: Mapped["Product"] = relationship(back_populates="barcode_image")
|
||||
|
||||
filename: Mapped[str] = mapped_column()
|
||||
42
modules/fulfillment_base/models/barcode_template.py
Normal file
42
modules/fulfillment_base/models/barcode_template.py
Normal file
@ -0,0 +1,42 @@
|
||||
from sqlalchemy import ForeignKey, Table, Column
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from models.base import BaseModel
|
||||
from models.mixins import IdMixin, SoftDeleteMixin
|
||||
|
||||
barcode_template_attribute_link = Table(
|
||||
"barcode_template_attribute_links",
|
||||
BaseModel.metadata,
|
||||
Column("barcode_template_id", ForeignKey("barcode_templates.id")),
|
||||
Column("attribute_id", ForeignKey("barcode_template_attributes.id")),
|
||||
)
|
||||
|
||||
|
||||
class BarcodeTemplateAttribute(BaseModel, IdMixin):
|
||||
__tablename__ = "barcode_template_attributes"
|
||||
|
||||
key: Mapped[str] = mapped_column(index=True, comment="Ключ атрибута")
|
||||
name: Mapped[str] = mapped_column(index=True, comment="Метка атрибута")
|
||||
|
||||
|
||||
class BarcodeTemplateSize(BaseModel, IdMixin):
|
||||
__tablename__ = "barcode_template_sizes"
|
||||
|
||||
name: Mapped[str] = mapped_column(index=True, comment="Название размера")
|
||||
width: Mapped[int] = mapped_column(comment="Ширина в мм")
|
||||
height: Mapped[int] = mapped_column(comment="Высота в мм")
|
||||
|
||||
|
||||
class BarcodeTemplate(BaseModel, IdMixin, SoftDeleteMixin):
|
||||
__tablename__ = "barcode_templates"
|
||||
|
||||
name: Mapped[str] = mapped_column(index=True, comment="Название шаблона")
|
||||
attributes: Mapped[list["BarcodeTemplateAttribute"]] = relationship(
|
||||
secondary=barcode_template_attribute_link,
|
||||
lazy="selectin",
|
||||
)
|
||||
|
||||
is_default: Mapped[bool] = mapped_column(default=False, comment="По умолчанию")
|
||||
|
||||
size_id: Mapped[int] = mapped_column(ForeignKey("barcode_template_sizes.id"))
|
||||
size: Mapped["BarcodeTemplateSize"] = relationship(lazy="joined")
|
||||
@ -1,8 +1,15 @@
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from models.base import BaseModel
|
||||
from models.mixins import IdMixin, SoftDeleteMixin
|
||||
from modules.fulfillment_base.models import (
|
||||
ProductBarcode,
|
||||
BarcodeTemplate,
|
||||
ProductBarcodeImage,
|
||||
)
|
||||
|
||||
|
||||
class Product(BaseModel, IdMixin, SoftDeleteMixin):
|
||||
@ -28,6 +35,22 @@ class Product(BaseModel, IdMixin, SoftDeleteMixin):
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
barcodes: Mapped[list["ProductBarcode"]] = relationship(
|
||||
back_populates="product",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
barcode_template_id: Mapped[Optional[int]] = mapped_column(
|
||||
ForeignKey("barcode_templates.id")
|
||||
)
|
||||
barcode_template: Mapped["BarcodeTemplate"] = relationship(lazy="joined")
|
||||
|
||||
barcode_image: Mapped["ProductBarcodeImage"] = relationship(
|
||||
back_populates="product",
|
||||
lazy="joined",
|
||||
uselist=False,
|
||||
)
|
||||
|
||||
|
||||
class ProductImage(BaseModel, IdMixin):
|
||||
__tablename__ = "fulfillment_base_product_images"
|
||||
|
||||
@ -5,3 +5,4 @@ from .product import ProductRepository as ProductRepository
|
||||
from .service import ServiceRepository as ServiceRepository
|
||||
from .services_kit import ServicesKitRepository as ServicesKitRepository
|
||||
from .service_category import ServiceCategoryRepository as ServiceCategoryRepository
|
||||
from .barcode_template import BarcodeTemplateRepository as BarcodeTemplateRepository
|
||||
|
||||
113
modules/fulfillment_base/repositories/barcode_template.py
Normal file
113
modules/fulfillment_base/repositories/barcode_template.py
Normal file
@ -0,0 +1,113 @@
|
||||
from sqlalchemy import update
|
||||
from sqlalchemy.orm import joinedload, selectinload
|
||||
|
||||
from modules.fulfillment_base.models import (
|
||||
BarcodeTemplate,
|
||||
BarcodeTemplateAttribute,
|
||||
BarcodeTemplateSize,
|
||||
)
|
||||
from modules.fulfillment_base.schemas.barcode_template import (
|
||||
CreateBarcodeTemplateSchema,
|
||||
UpdateBarcodeTemplateSchema,
|
||||
)
|
||||
from repositories.mixins import *
|
||||
|
||||
|
||||
class BarcodeTemplateRepository(
|
||||
RepCrudMixin[
|
||||
BarcodeTemplate, CreateBarcodeTemplateSchema, UpdateBarcodeTemplateSchema
|
||||
],
|
||||
):
|
||||
session: AsyncSession
|
||||
entity_class = BarcodeTemplate
|
||||
entity_not_found_msg = "Шаблон штрихкода не найден"
|
||||
|
||||
def _process_get_all_stmt(self, stmt: Select) -> Select:
|
||||
return (
|
||||
stmt.options(
|
||||
selectinload(BarcodeTemplate.attributes),
|
||||
joinedload(BarcodeTemplate.size),
|
||||
)
|
||||
.where(BarcodeTemplate.is_deleted.is_(False))
|
||||
.order_by(BarcodeTemplate.id)
|
||||
)
|
||||
|
||||
async def _get_size_by_id(self, size_id: int) -> Optional[BarcodeTemplateSize]:
|
||||
stmt = select(BarcodeTemplateSize).where(BarcodeTemplateSize.id == size_id)
|
||||
result = await self.session.scalars(stmt)
|
||||
return result.one_or_none()
|
||||
|
||||
async def _get_attrs_by_ids(
|
||||
self, attrs_ids: list[int]
|
||||
) -> list[BarcodeTemplateAttribute]:
|
||||
stmt = select(BarcodeTemplateAttribute).where(
|
||||
BarcodeTemplateAttribute.id.in_(attrs_ids)
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def create(self, data: CreateBarcodeTemplateSchema) -> int:
|
||||
if data.is_default is not None and data.is_default:
|
||||
await self._turn_off_defaults()
|
||||
|
||||
data_dict = data.model_dump()
|
||||
data_dict["size"] = await self._get_size_by_id(data.size.id)
|
||||
data_dict["attributes"] = await self._get_attrs_by_ids(
|
||||
[a.id for a in data.attributes]
|
||||
)
|
||||
|
||||
obj = BarcodeTemplate(**data_dict)
|
||||
self.session.add(obj)
|
||||
await self.session.commit()
|
||||
await self.session.refresh(obj)
|
||||
return obj.id
|
||||
|
||||
async def _turn_off_defaults(self):
|
||||
stmt = (
|
||||
update(BarcodeTemplate)
|
||||
.where(BarcodeTemplate.is_default.is_(True))
|
||||
.values({"is_default": False})
|
||||
)
|
||||
await self.session.execute(stmt)
|
||||
|
||||
async def _set_first_as_default(self, with_commit: bool = False):
|
||||
stmt = select(BarcodeTemplate).limit(1)
|
||||
result = await self.session.execute(stmt)
|
||||
obj = result.scalar()
|
||||
if not obj:
|
||||
return
|
||||
obj.is_default = True
|
||||
self.session.add(obj)
|
||||
if with_commit:
|
||||
await self.session.commit()
|
||||
await self.session.refresh(obj)
|
||||
|
||||
async def update(
|
||||
self, template: BarcodeTemplate, data: UpdateBarcodeTemplateSchema
|
||||
) -> BarcodeTemplate:
|
||||
if data.size is not None:
|
||||
data.size = await self._get_size_by_id(data.size.id)
|
||||
if data.attributes is not None:
|
||||
data.attributes = await self._get_attrs_by_ids(
|
||||
[a.id for a in data.attributes]
|
||||
)
|
||||
if data.is_default is not None:
|
||||
if data.is_default:
|
||||
await self._turn_off_defaults()
|
||||
else:
|
||||
await self._set_first_as_default()
|
||||
return await self._apply_update_data_to_model(template, data, True)
|
||||
|
||||
async def _before_delete(self, template: BarcodeTemplate):
|
||||
if template.is_default:
|
||||
await self._set_first_as_default()
|
||||
|
||||
async def get_attributes(self) -> list[BarcodeTemplateAttribute]:
|
||||
stmt = select(BarcodeTemplateAttribute)
|
||||
result = await self.session.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def get_sizes(self) -> list[BarcodeTemplateSize]:
|
||||
stmt = select(BarcodeTemplateSize)
|
||||
result = await self.session.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
77
modules/fulfillment_base/routers/barcode_template.py
Normal file
77
modules/fulfillment_base/routers/barcode_template.py
Normal file
@ -0,0 +1,77 @@
|
||||
from fastapi import APIRouter, Path
|
||||
|
||||
from backend.dependecies import SessionDependency
|
||||
from modules.fulfillment_base.schemas.barcode_template import *
|
||||
from modules.fulfillment_base.services import BarcodeTemplateService
|
||||
|
||||
router = APIRouter(tags=["barcode_template"])
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=GetBarcodeTemplatesResponse,
|
||||
operation_id="get_barcode_templates",
|
||||
)
|
||||
async def get_barcode_templates(
|
||||
session: SessionDependency,
|
||||
):
|
||||
return await BarcodeTemplateService(session).get_all()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=CreateBarcodeTemplateResponse,
|
||||
operation_id="create_barcode_template",
|
||||
)
|
||||
async def create_barcode_template(
|
||||
session: SessionDependency,
|
||||
request: CreateBarcodeTemplateRequest,
|
||||
):
|
||||
return await BarcodeTemplateService(session).create(request)
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/{pk}",
|
||||
response_model=UpdateBarcodeTemplateResponse,
|
||||
operation_id="update_barcode_template",
|
||||
)
|
||||
async def update_barcode_template(
|
||||
session: SessionDependency,
|
||||
request: UpdateBarcodeTemplateRequest,
|
||||
pk: int = Path(),
|
||||
):
|
||||
return await BarcodeTemplateService(session).update(pk, request)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{pk}",
|
||||
response_model=DeleteBarcodeTemplateResponse,
|
||||
operation_id="delete_barcode_template",
|
||||
)
|
||||
async def delete_barcode_template(
|
||||
session: SessionDependency,
|
||||
pk: int = Path(),
|
||||
):
|
||||
return await BarcodeTemplateService(session).delete(pk)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/attributes",
|
||||
response_model=GetBarcodeAttributesResponse,
|
||||
operation_id="get_barcode_template_attributes",
|
||||
)
|
||||
async def get_barcode_template_attributes(
|
||||
session: SessionDependency,
|
||||
):
|
||||
return await BarcodeTemplateService(session).get_attributes()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/sizes",
|
||||
response_model=GetBarcodeTemplateSizesResponse,
|
||||
operation_id="get_barcode_template_sizes",
|
||||
)
|
||||
async def get_barcode_template_sizes(
|
||||
session: SessionDependency,
|
||||
):
|
||||
return await BarcodeTemplateService(session).get_sizes()
|
||||
82
modules/fulfillment_base/schemas/barcode_template.py
Normal file
82
modules/fulfillment_base/schemas/barcode_template.py
Normal file
@ -0,0 +1,82 @@
|
||||
from typing import Optional
|
||||
|
||||
from schemas.base import BaseSchema, BaseResponse
|
||||
|
||||
|
||||
# region Entity
|
||||
|
||||
|
||||
class BarcodeTemplateAttributeSchema(BaseSchema):
|
||||
id: int
|
||||
key: str
|
||||
name: str
|
||||
|
||||
|
||||
class BarcodeTemplateSizeSchema(BaseSchema):
|
||||
id: int
|
||||
name: str
|
||||
width: int
|
||||
height: int
|
||||
|
||||
|
||||
class CreateBarcodeTemplateSchema(BaseSchema):
|
||||
name: str
|
||||
attributes: list[BarcodeTemplateAttributeSchema]
|
||||
is_default: bool
|
||||
size: BarcodeTemplateSizeSchema
|
||||
|
||||
|
||||
class BarcodeTemplateSchema(CreateBarcodeTemplateSchema):
|
||||
id: int
|
||||
|
||||
|
||||
class UpdateBarcodeTemplateSchema(BaseSchema):
|
||||
name: Optional[str] = None
|
||||
attributes: Optional[list[BarcodeTemplateAttributeSchema]] = None
|
||||
is_default: Optional[bool] = None
|
||||
size: Optional[BarcodeTemplateSizeSchema] = None
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Request
|
||||
|
||||
|
||||
class CreateBarcodeTemplateRequest(BaseSchema):
|
||||
entity: CreateBarcodeTemplateSchema
|
||||
|
||||
|
||||
class UpdateBarcodeTemplateRequest(BaseSchema):
|
||||
entity: UpdateBarcodeTemplateSchema
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Response
|
||||
|
||||
|
||||
class GetBarcodeTemplatesResponse(BaseSchema):
|
||||
items: list[BarcodeTemplateSchema]
|
||||
|
||||
|
||||
class CreateBarcodeTemplateResponse(BaseResponse):
|
||||
entity: BarcodeTemplateSchema
|
||||
|
||||
|
||||
class UpdateBarcodeTemplateResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
class DeleteBarcodeTemplateResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
class GetBarcodeAttributesResponse(BaseSchema):
|
||||
items: list[BarcodeTemplateAttributeSchema]
|
||||
|
||||
|
||||
class GetBarcodeTemplateSizesResponse(BaseSchema):
|
||||
items: list[BarcodeTemplateSizeSchema]
|
||||
|
||||
|
||||
# endregion
|
||||
@ -5,3 +5,4 @@ from .product_service import ProductServiceService as ProductServiceService
|
||||
from .service import ServiceModelService as ServiceModelService
|
||||
from .services_kit import ServicesKitService as ServicesKitService
|
||||
from .service_category import ServiceCategoryService as ServiceCategoryService
|
||||
from .barcode_template import BarcodeTemplateService as BarcodeTemplateService
|
||||
|
||||
39
modules/fulfillment_base/services/barcode_template.py
Normal file
39
modules/fulfillment_base/services/barcode_template.py
Normal file
@ -0,0 +1,39 @@
|
||||
from modules.fulfillment_base.models import BarcodeTemplate
|
||||
from modules.fulfillment_base.repositories import BarcodeTemplateRepository
|
||||
from modules.fulfillment_base.schemas.barcode_template import *
|
||||
from services.mixins import *
|
||||
|
||||
|
||||
class BarcodeTemplateService(
|
||||
ServiceCrudMixin[
|
||||
BarcodeTemplate,
|
||||
BarcodeTemplateSchema,
|
||||
CreateBarcodeTemplateRequest,
|
||||
UpdateBarcodeTemplateRequest,
|
||||
]
|
||||
):
|
||||
schema_class = BarcodeTemplateSchema
|
||||
entity_deleted_msg = "Шаблон штрихкода успешно удален"
|
||||
entity_updated_msg = "Шаблон штрихкода успешно обновлен"
|
||||
entity_created_msg = "Шаблон штрихкода успешно создан"
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.repository = BarcodeTemplateRepository(session)
|
||||
|
||||
async def is_soft_delete(self, template: BarcodeTemplate) -> bool:
|
||||
return True
|
||||
|
||||
async def get_attributes(self) -> GetBarcodeAttributesResponse:
|
||||
attributes = await self.repository.get_attributes()
|
||||
return GetBarcodeAttributesResponse(
|
||||
items=[
|
||||
BarcodeTemplateAttributeSchema.model_validate(attr)
|
||||
for attr in attributes
|
||||
]
|
||||
)
|
||||
|
||||
async def get_sizes(self) -> GetBarcodeTemplateSizesResponse:
|
||||
sizes = await self.repository.get_sizes()
|
||||
return GetBarcodeTemplateSizesResponse(
|
||||
items=[BarcodeTemplateSizeSchema.model_validate(size) for size in sizes]
|
||||
)
|
||||
@ -17,7 +17,12 @@ class RepBaseMixin(Generic[EntityType]):
|
||||
|
||||
|
||||
class RepDeleteMixin(Generic[EntityType], RepBaseMixin[EntityType]):
|
||||
async def _before_delete(self, obj: EntityType) -> None:
|
||||
pass
|
||||
|
||||
async def delete(self, obj: EntityType, is_soft: bool) -> None:
|
||||
await self._before_delete(obj)
|
||||
|
||||
if not is_soft:
|
||||
await self.session.delete(obj)
|
||||
await self.session.commit()
|
||||
|
||||
Reference in New Issue
Block a user