feat: barcode printing

This commit is contained in:
2025-10-10 23:00:39 +04:00
parent 6b0f8a1aa5
commit 4c871e1e1b
16 changed files with 413 additions and 5 deletions

View File

@ -0,0 +1,2 @@
from .barcode_pdf_generator import BarcodePdfGenerator as BarcodePdfGenerator
from .types import BarcodeData as BarcodeData

View File

@ -0,0 +1,161 @@
from io import BytesIO
from typing import Any, Optional
from reportlab.graphics.barcode import code128
from reportlab.lib.units import mm
from reportlab.platypus import Spacer, PageBreak, Paragraph
from modules.fulfillment_base.barcodes_pdf_gen.types import *
from utils.pdf import PdfMaker, PDFGenerator
class BarcodePdfGenerator(PDFGenerator):
def _get_attr_by_path(
self, value: Any, path: str
) -> Optional[str | int | float | bool]:
keys = path.split(".")
for key in keys:
try:
if isinstance(value, dict):
value = value[key]
else:
value = getattr(value, key)
except (KeyError, AttributeError, TypeError):
return None
return value
def generate(
self, barcodes_data: list[BarcodeData | PdfBarcodeImageGenData]
) -> BytesIO:
pdf_barcodes_gen_data: list[PdfBarcodeGenData | PdfBarcodeImageGenData] = []
for barcode_data in barcodes_data:
if "barcode" not in barcode_data:
pdf_barcodes_gen_data.append(barcode_data)
continue
attributes = {}
for attribute in barcode_data["template"].attributes:
value = self._get_attr_by_path(barcode_data["product"], attribute.key)
if not value or not value.strip():
continue
attributes[attribute.name] = value
barcode_text = "<br/>".join(
[f"{key}: {value}" for key, value in attributes.items()]
)
pdf_barcodes_gen_data.append(
{
"barcode_value": barcode_data["barcode"],
"text": barcode_text,
"num_duplicates": barcode_data["num_duplicates"],
}
)
return self._generate(pdf_barcodes_gen_data)
def _generate(
self, barcodes_data: list[PdfBarcodeGenData | PdfBarcodeImageGenData]
) -> BytesIO:
pdf_maker = PdfMaker((self.page_width, self.page_height))
pdf_files: list[BytesIO] = []
for barcode_data in barcodes_data:
if "barcode_value" in barcode_data:
pdf_files.append(self._generate_for_one_product(barcode_data))
else:
pdf_files.append(self._generate_for_one_product_using_img(barcode_data))
pdf_files.append(self._generate_spacers())
for file in pdf_files[:-1]:
pdf_maker.add_pdfs(file)
return pdf_maker.get_bytes()
def _generate_for_one_product(self, barcode_data: PdfBarcodeGenData) -> BytesIO:
buffer = BytesIO()
doc = self._create_doc(buffer)
# Создаем абзац с новым стилем
paragraph = Paragraph(barcode_data["text"], self.small_style)
# Получаем ширину и высоту абзаца
paragraph_width, paragraph_height = paragraph.wrap(
self.page_width - 2 * mm, self.page_height
)
# Рассчитываем доступное пространство для штрихкода
human_readable_height = 6 * mm # Высота human-readable текста
space_between_text_and_barcode = 4 * mm # Отступ между текстом и штрихкодом
barcode_height = (
self.page_height
- paragraph_height
- human_readable_height
- space_between_text_and_barcode
- 4 * mm
) # Учитываем поля и отступы
# Создаем штрихкод
available_width = self.page_width - 4 * mm # Учитываем поля
# Приблизительное количество элементов в штрихкоде Code 128 для средней длины
num_elements = 11 * len(
barcode_data["barcode_value"]
) # Примерная оценка: 11 элементов на символ
# Рассчитываем ширину штриха
bar_width = available_width / num_elements
barcode = code128.Code128(
barcode_data["barcode_value"],
barWidth=bar_width,
barHeight=barcode_height,
humanReadable=True,
)
# Добавление штрихкодов в список элементов документа
elements = []
for _ in range(barcode_data["num_duplicates"]):
elements.append(paragraph)
elements.append(
Spacer(1, space_between_text_and_barcode)
) # Отступ между текстом и штрихкодом
elements.append(PageBreak())
# Функция для отрисовки штрихкода на canvas
def add_barcode(canvas, doc):
barcode_width = barcode.width
barcode_x = (self.page_width - barcode_width) / 2 # Центрируем штрихкод
# Размещаем штрихкод снизу с учетом отступа
barcode_y = human_readable_height + 2 * mm
barcode.drawOn(canvas, barcode_x, barcode_y)
# Создаем документ
doc.build(
elements, onFirstPage=add_barcode, onLaterPages=add_barcode
) # Убираем последний PageBreak
buffer.seek(0)
return buffer
def _generate_for_one_product_using_img(
self, barcode_data: PdfBarcodeImageGenData
) -> BytesIO:
with open(barcode_data["barcode_image_url"], "rb") as pdf_file:
pdf_bytes = pdf_file.read()
pdf_maker = PdfMaker((self.page_width, self.page_height))
for _ in range(barcode_data["num_duplicates"]):
pdf_maker.add_pdfs(BytesIO(pdf_bytes))
return pdf_maker.get_bytes()
def _generate_spacers(self) -> BytesIO:
buffer = BytesIO()
doc = self._create_doc(buffer)
elements = []
for _ in range(self.number_of_spacing_pages):
elements.append(PageBreak())
doc.build(elements)
buffer.seek(0)
return buffer

View File

@ -0,0 +1,21 @@
from typing import TypedDict
from modules.fulfillment_base.models import BarcodeTemplate, Product
class BarcodeData(TypedDict):
barcode: str
template: BarcodeTemplate
product: Product
num_duplicates: int
class PdfBarcodeGenData(TypedDict):
barcode_value: str
text: str
num_duplicates: int
class PdfBarcodeImageGenData(TypedDict):
barcode_image_url: str
num_duplicates: int