feat: barcode printing
This commit is contained in:
2
modules/fulfillment_base/barcodes_pdf_gen/__init__.py
Normal file
2
modules/fulfillment_base/barcodes_pdf_gen/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .barcode_pdf_generator import BarcodePdfGenerator as BarcodePdfGenerator
|
||||
from .types import BarcodeData as BarcodeData
|
||||
@ -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
|
||||
21
modules/fulfillment_base/barcodes_pdf_gen/types.py
Normal file
21
modules/fulfillment_base/barcodes_pdf_gen/types.py
Normal 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
|
||||
Reference in New Issue
Block a user