diff --git a/modules/fulfillment_base/models/barcode.py b/modules/fulfillment_base/models/barcode.py index ba1a8c1..c74f5f3 100644 --- a/modules/fulfillment_base/models/barcode.py +++ b/modules/fulfillment_base/models/barcode.py @@ -33,4 +33,4 @@ class ProductBarcodeImage(BaseModel): ) product: Mapped["Product"] = relationship(back_populates="barcode_image") - filename: Mapped[str] = mapped_column() + image_url: Mapped[str] = mapped_column() diff --git a/modules/fulfillment_base/models/product.py b/modules/fulfillment_base/models/product.py index 9d37c9b..bc76631 100644 --- a/modules/fulfillment_base/models/product.py +++ b/modules/fulfillment_base/models/product.py @@ -52,7 +52,6 @@ class Product(BaseModel, IdMixin, SoftDeleteMixin): barcode_image: Mapped["ProductBarcodeImage"] = relationship( back_populates="product", lazy="joined", - uselist=False, ) diff --git a/modules/fulfillment_base/repositories/product.py b/modules/fulfillment_base/repositories/product.py index cd0ecce..31898d8 100644 --- a/modules/fulfillment_base/repositories/product.py +++ b/modules/fulfillment_base/repositories/product.py @@ -1,7 +1,7 @@ from sqlalchemy import or_, delete from sqlalchemy.orm import selectinload, joinedload -from modules.fulfillment_base.models import Product, ProductBarcode, BarcodeTemplate +from modules.fulfillment_base.models import Product, ProductBarcode, BarcodeTemplate, ProductBarcodeImage from modules.fulfillment_base.models.product import ProductImage from modules.fulfillment_base.schemas.product import ( CreateProductSchema, @@ -106,6 +106,13 @@ class ProductRepository( else: await self.session.flush() + async def delete_barcode_image(self, barcode_image: ProductBarcodeImage, with_commit: bool = False): + await self.session.delete(barcode_image) + if with_commit: + await self.session.commit() + else: + await self.session.flush() + async def create_image(self, product_id: int, image_url: str) -> ProductImage: product_image = ProductImage( product_id=product_id, @@ -114,3 +121,12 @@ class ProductRepository( self.session.add(product_image) await self.session.commit() return product_image + + async def create_barcode_image(self, product_id: int, image_url: str) -> ProductBarcodeImage: + product_barcode_image = ProductBarcodeImage( + product_id=product_id, + image_url=image_url, + ) + self.session.add(product_barcode_image) + await self.session.commit() + return product_barcode_image diff --git a/modules/fulfillment_base/schemas/product.py b/modules/fulfillment_base/schemas/product.py index 4b589bd..7493794 100644 --- a/modules/fulfillment_base/schemas/product.py +++ b/modules/fulfillment_base/schemas/product.py @@ -16,6 +16,11 @@ class ProductImageSchema(BaseSchema): image_url: str +class ProductBarcodeImageSchema(BaseSchema): + product_id: int + image_url: str + + class CreateProductSchema(BaseSchema): name: str article: str @@ -27,21 +32,27 @@ class CreateProductSchema(BaseSchema): composition: Optional[str] size: Optional[str] additional_info: Optional[str] - barcodes: list[str] - image_url: str | None = None - images: list[ProductImageSchema] | None = [] + barcodes: list[str] = [] + + +class BaseProductSchema(CreateProductSchema): + image_url: Optional[str] = None + images: Optional[list[ProductImageSchema]] = [] + barcode_image_url: Optional[str] = None + barcode_image: Optional[ProductBarcodeImageSchema] = None @model_validator(mode="after") def images_list_to_image_url(cls, values): - images = values.images - if not images: - return values - latest_image = images[-1] - values.image_url = latest_image.image_url + if values.images: + latest_image = values.images[-1] + values.image_url = latest_image.image_url + + if values.barcode_image: + values.barcode_image_url = values.barcode_image.image_url return values -class ProductSchema(CreateProductSchema): +class ProductSchema(BaseProductSchema): id: int barcode_template: BarcodeTemplateSchema @@ -116,4 +127,12 @@ class GetProductBarcodePdfResponse(BasePdfResponse): pass +class BarcodeUploadImageResponse(BaseResponse): + image_url: Optional[str] = None + + +class DeleteBarcodeImageResponse(BaseResponse): + pass + + # endregion diff --git a/modules/fulfillment_base/services/product.py b/modules/fulfillment_base/services/product.py index c329873..ff21cfc 100644 --- a/modules/fulfillment_base/services/product.py +++ b/modules/fulfillment_base/services/product.py @@ -48,19 +48,51 @@ class ProductService( async def upload_image( self, product_id: int, upload_file: UploadFile ) -> ProductUploadImageResponse: - try: - product: Product = await self.repository.get_by_id(product_id) - s3_uploader = S3Uploader() + product: Product = await self.repository.get_by_id(product_id) + s3_uploader = S3Uploader() + if len(product.images) > 0: for image in product.images: s3_key = image.image_url.split("/")[-1] await s3_uploader.delete_image(s3_key) await self.repository.delete_images(product.images) - image_url = await s3_uploader.upload_from_upload_file_obj(upload_file) - await self.repository.create_image(product_id, image_url) - return ProductUploadImageResponse( - ok=True, message="Изображение успешно загружено", image_url=image_url + image_url = await s3_uploader.upload_from_upload_file_obj(upload_file) + await self.repository.create_image(product_id, image_url) + return ProductUploadImageResponse( + message="Изображение успешно загружено", image_url=image_url + ) + + async def upload_barcode_image( + self, product_id: int, upload_file: UploadFile + ) -> BarcodeUploadImageResponse: + product: Product = await self.repository.get_by_id(product_id) + s3_uploader = S3Uploader() + + if product.barcode_image: + s3_key = product.barcode_image.image_url.split("/")[-1] + await s3_uploader.delete_image(s3_key) + await self.repository.delete_barcode_image(product.barcode_image) + + image_url = await s3_uploader.upload_from_upload_file_obj(upload_file) + await self.repository.create_barcode_image(product_id, image_url) + return BarcodeUploadImageResponse( + message="Изображение штрихкода успешно загружено", image_url=image_url + ) + + async def delete_barcode_image(self, product_id: int) -> DeleteBarcodeImageResponse: + product: Product = await self.repository.get_by_id(product_id) + + if not product.barcode_image: + return DeleteBarcodeImageResponse( + message="У товара нет изображения штрихкода" ) - except Exception as e: - return ProductUploadImageResponse(ok=False, message=str(e)) + + s3_uploader = S3Uploader() + s3_key = product.barcode_image.image_url.split("/")[-1] + await s3_uploader.delete_image(s3_key) + await self.repository.delete_barcode_image(product.barcode_image, True) + + return DeleteBarcodeImageResponse( + message="Изображение штрихкода успешно удалено" + ) diff --git a/routers/crm/v1/modules/fulfillment_base/product.py b/routers/crm/v1/modules/fulfillment_base/product.py index 9a6721d..7821a68 100644 --- a/routers/crm/v1/modules/fulfillment_base/product.py +++ b/routers/crm/v1/modules/fulfillment_base/product.py @@ -49,16 +49,16 @@ async def update_product( @router.post( - "/images/upload/{productId}", + "{pk}/images/upload", response_model=ProductUploadImageResponse, operation_id="upload_product_image", ) async def upload_product_image( session: SessionDependency, upload_file: Annotated[UploadFile, File()], - product_id: int = Path(alias="productId"), + pk: int = Path(), ): - return await ProductService(session).upload_image(product_id, upload_file) + return await ProductService(session).upload_image(pk, upload_file) @router.delete( @@ -86,3 +86,28 @@ async def get_product_barcode_pdf( return GetProductBarcodePdfResponse( base64_string=base64_string, filename=filename, mime_type="application/pdf" ) + + +@router.post( + "{pk}/barcode/image/upload", + response_model=BarcodeUploadImageResponse, + operation_id="upload_product_barcode_image", +) +async def upload_product_barcode_image( + upload_file: UploadFile, + session: SessionDependency, + pk: int, +): + return await ProductService(session).upload_barcode_image(pk, upload_file) + + +@router.delete( + "{pk}/barcode/image", + response_model=DeleteBarcodeImageResponse, + operation_id="delete_product_barcode_image", +) +async def delete_product_barcode_image( + session: SessionDependency, + pk: int, +): + return await ProductService(session).delete_barcode_image(pk)