feat: new barcodes

This commit is contained in:
2024-06-01 04:27:31 +03:00
parent 70178d94e9
commit 795e28fe41
10 changed files with 167 additions and 7 deletions

4
.gitignore vendored
View File

@@ -3,4 +3,6 @@
.venv .venv
.idea .idea
__pycache__ __pycache__
/venv /venv
/test
/test/*

BIN
assets/fonts/DejaVuSans.ttf Normal file

Binary file not shown.

View File

@@ -1,10 +1,26 @@
from barcodes.attributes import AttributeWriterFactory
from barcodes.generator.base import BaseBarcodeGenerator from barcodes.generator.base import BaseBarcodeGenerator
from barcodes.pdf import PDFGenerator
from models import ProductBarcode, Product, BarcodeTemplate from models import ProductBarcode, Product, BarcodeTemplate
class DefaultBarcodeGenerator(BaseBarcodeGenerator): class DefaultBarcodeGenerator(BaseBarcodeGenerator):
def generate(self, def generate(self,
barcode: ProductBarcode, barcode: str,
product: Product, product: Product,
template: BarcodeTemplate): template: BarcodeTemplate,
pass quantity: int = 1):
pdf_generator = PDFGenerator()
attributes = {}
for attribute in template.attributes:
attribute_getter = AttributeWriterFactory.get_writer(attribute.key)
if not attribute_getter:
continue
value = attribute_getter.get_value(product)
if not value:
continue
attributes[attribute.name] = value
for additional_attribute in template.additional_attributes:
attributes[additional_attribute.name] = additional_attribute.value
barcode_text = '<br/>'.join([f'{key}: {value}' for key, value in attributes.items()])
return pdf_generator.generate(barcode, barcode_text, num_duplicates=quantity)

1
barcodes/pdf/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .generator import PDFGenerator

71
barcodes/pdf/generator.py Normal file
View File

@@ -0,0 +1,71 @@
import os
from io import BytesIO
from reportlab.graphics.barcode import code128
from reportlab.lib.pagesizes import mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
from constants import APP_PATH
class PDFGenerator:
def __init__(self):
ASSETS_FOLDER = os.path.join(APP_PATH, 'assets')
FONTS_FOLDER = os.path.join(ASSETS_FOLDER, 'fonts')
FONT_FILE_PATH = os.path.join(FONTS_FOLDER, 'DejaVuSans.ttf')
self.page_width = 58 * mm
self.page_height = 40 * mm
pdfmetrics.registerFont(TTFont('DejaVuSans', FONT_FILE_PATH))
# Get standard styles and create a new style with a smaller font size
styles = getSampleStyleSheet()
self.small_style = ParagraphStyle(
'Small',
parent=styles['Normal'],
fontName='DejaVuSans', # Specify the new font
fontSize=6,
leading=7,
spaceAfter=2,
leftIndent=2,
rightIndent=2
)
def generate(self, barcode_value, text, num_duplicates=1):
buffer = BytesIO()
# Create document with specified page size
doc = SimpleDocTemplate(buffer,
pagesize=(self.page_width, self.page_height),
rightMargin=1 * mm,
leftMargin=1 * mm,
topMargin=1 * mm,
bottomMargin=1 * mm)
# Create paragraph with new style
paragraph = Paragraph(text, self.small_style)
# Create barcode
barcode = code128.Code128(barcode_value, humanReadable=True)
# Function to draw barcode on canvas
def add_barcode(canvas, doc):
barcode_width = barcode.width
barcode_x = (self.page_width - barcode_width) / 2 # Center the barcode
barcode.drawOn(canvas, barcode_x, self.page_height - barcode.height - 5)
# Create list of elements to add to the document
elements = []
for _ in range(num_duplicates):
elements.append(Spacer(1, 8 * mm))
elements.append(paragraph)
elements.append(PageBreak())
# Build document
doc.build(elements[:-1], onFirstPage=add_barcode, onLaterPages=add_barcode) # Remove the last PageBreak
buffer.seek(0)
return buffer

View File

@@ -1,3 +1,9 @@
import os
import sys
APP_PATH = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(__file__)
allowed_telegram_ids = [ allowed_telegram_ids = [
454777987, # Me 454777987, # Me
816217667, # Igor 816217667, # Igor

View File

@@ -21,4 +21,7 @@ python-dotenv
aiohttp aiohttp
aiohttp[speedups] aiohttp[speedups]
openpyxl openpyxl
lexorank-py lexorank-py
# PDF
reportlab

View File

@@ -1,11 +1,15 @@
import base64
from io import BytesIO
from typing import Annotated, Union from typing import Annotated, Union
from fastapi import APIRouter, Depends, UploadFile from fastapi import APIRouter, Depends, UploadFile
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from starlette.responses import StreamingResponse
from fastapi.responses import FileResponse
import utils.dependecies import utils.dependecies
from backend.session import get_session from backend.session import get_session
from schemas.barcode import GetProductBarcodeResponse, GetProductBarcodeRequest from schemas.barcode import GetProductBarcodeResponse, GetProductBarcodeRequest, GetProductBarcodePdfResponse, \
GetProductBarcodePdfRequest
from schemas.base import PaginationSchema from schemas.base import PaginationSchema
from schemas.product import * from schemas.product import *
from services.auth import get_current_user from services.auth import get_current_user
@@ -127,6 +131,28 @@ async def get_product_barcode(
return await BarcodeService(session).get_barcode(request) return await BarcodeService(session).get_barcode(request)
@product_router.post(
'/barcode/get-pdf',
operation_id='get_product_barcode_pdf',
response_model=GetProductBarcodePdfResponse
)
async def get_product_barcode_pdf(
request: GetProductBarcodePdfRequest,
session: Annotated[AsyncSession, Depends(get_session)]
):
filename, pdf_buffer = await BarcodeService(session).get_barcode_pdf(request)
pdf_buffer: BytesIO
base64_string = base64.b64encode(pdf_buffer.read()).decode('utf-8')
return GetProductBarcodePdfResponse(
base64_string=base64_string,
filename=filename,
mime_type='application/pdf'
)
# return StreamingResponse(content=pdf_buffer,
# media_type='application/pdf',
# headers={"Content-Disposition": f"inline; filename={filename}"})
@product_router.post( @product_router.post(
'/images/upload/{product_id}', '/images/upload/{product_id}',
response_model=ProductUploadImageResponse, response_model=ProductUploadImageResponse,

View File

@@ -77,6 +77,10 @@ class GetProductBarcodeRequest(CustomModelCamel):
barcode_template_id: int | None = None barcode_template_id: int | None = None
class GetProductBarcodePdfRequest(GetProductBarcodeRequest):
quantity: int
# endregion # endregion
# region Responses # region Responses
@@ -115,4 +119,10 @@ class GetProductBarcodeResponse(CustomModelCamel):
class GetAllBarcodeTemplateSizesResponse(CustomModelCamel): class GetAllBarcodeTemplateSizesResponse(CustomModelCamel):
sizes: list[BarcodeTemplateSizeSchema] sizes: list[BarcodeTemplateSizeSchema]
class GetProductBarcodePdfResponse(CustomModelCamel):
base64_string: str
filename: str
mime_type: str
# endregion # endregion

View File

@@ -2,6 +2,7 @@ from sqlalchemy import select, update, insert
from sqlalchemy.orm import selectinload, joinedload from sqlalchemy.orm import selectinload, joinedload
from barcodes.attributes import AttributeWriterFactory from barcodes.attributes import AttributeWriterFactory
from barcodes.generator.default_generator import DefaultBarcodeGenerator
from models import BarcodeTemplate, BarcodeTemplateAttribute, barcode_template_attribute_link, Product, \ from models import BarcodeTemplate, BarcodeTemplateAttribute, barcode_template_attribute_link, Product, \
BarcodeTemplateAdditionalField, BarcodeTemplateSize BarcodeTemplateAdditionalField, BarcodeTemplateSize
from schemas.barcode import * from schemas.barcode import *
@@ -73,6 +74,30 @@ class BarcodeService(BaseService):
) )
return GetProductBarcodeResponse(barcode=barcode) return GetProductBarcodeResponse(barcode=barcode)
async def get_barcode_pdf(self, request: GetProductBarcodePdfRequest) -> 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)
default_generator = DefaultBarcodeGenerator()
filename = f'{product.id}_barcode.pdf'
pdf_buffer = default_generator.generate(
request.barcode,
product,
barcode_template,
request.quantity
)
return filename, pdf_buffer
# endregion # endregion
# region Template # region Template