diff --git a/barcodes/generator/base.py b/barcodes/generator/base.py
index 3636c73..8062463 100644
--- a/barcodes/generator/base.py
+++ b/barcodes/generator/base.py
@@ -1,13 +1,11 @@
from abc import abstractmethod
+from typing import List, Dict
-from models import ProductBarcode, Product, BarcodeTemplate
+from models import BarcodeTemplate, Product
class BaseBarcodeGenerator:
@abstractmethod
- def generate(self,
- barcode: ProductBarcode,
- product: Product,
- template: BarcodeTemplate):
+ def generate(self, data: List[Dict[str, str | Product | BarcodeTemplate | int]]):
pass
diff --git a/barcodes/generator/default_generator.py b/barcodes/generator/default_generator.py
index d07b8d2..12fe7b2 100644
--- a/barcodes/generator/default_generator.py
+++ b/barcodes/generator/default_generator.py
@@ -1,26 +1,40 @@
+from typing import List, Dict
+
from barcodes.attributes import AttributeWriterFactory
from barcodes.generator.base import BaseBarcodeGenerator
from barcodes.pdf import PDFGenerator
-from models import ProductBarcode, Product, BarcodeTemplate
+from models import Product, BarcodeTemplate
+from schemas.barcode import PdfBarcodeGenData
class DefaultBarcodeGenerator(BaseBarcodeGenerator):
- def generate(self,
- barcode: str,
- product: Product,
- template: BarcodeTemplate,
- quantity: int = 1):
+ def generate(self, barcodes_data: List[Dict[str, str | Product | BarcodeTemplate | int]]):
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 or not value.strip():
- continue
- attributes[attribute.name] = value
- for additional_attribute in template.additional_attributes:
- attributes[additional_attribute.name] = additional_attribute.value
- barcode_text = '
'.join([f'{key}: {value}' for key, value in attributes.items()])
- return pdf_generator.generate(barcode, barcode_text, num_duplicates=quantity)
+
+ pdf_barcodes_gen_data: List[PdfBarcodeGenData] = []
+
+ for barcode_data in barcodes_data:
+ attributes = {}
+ for attribute in barcode_data["template"].attributes:
+ attribute_getter = AttributeWriterFactory.get_writer(attribute.key)
+ if not attribute_getter:
+ continue
+ value = attribute_getter.get_value(barcode_data["product"])
+ if not value or not value.strip():
+ continue
+ attributes[attribute.name] = value
+ for additional_attribute in barcode_data["template"].additional_attributes:
+ attributes[additional_attribute.name] = additional_attribute.value
+ barcode_text = '
'.join([f'{key}: {value}' for key, value in attributes.items()])
+
+ pdf_barcodes_gen_data.append(
+ PdfBarcodeGenData(
+ barcode_value=barcode_data["barcode"],
+ text=barcode_text,
+ num_duplicates=barcode_data["quantity"]
+ )
+ )
+
+ print(f"value = {barcode_data['barcode']}, text = {barcode_text}, num = {barcode_data['quantity']}")
+
+ return pdf_generator.generate(pdf_barcodes_gen_data)
diff --git a/barcodes/pdf/generator.py b/barcodes/pdf/generator.py
index e031120..7aa9f93 100644
--- a/barcodes/pdf/generator.py
+++ b/barcodes/pdf/generator.py
@@ -1,5 +1,7 @@
import os
from io import BytesIO
+from typing import List
+
from reportlab.graphics.barcode import code128
from reportlab.lib.pagesizes import mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
@@ -8,6 +10,7 @@ from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
from constants import APP_PATH
+from schemas.barcode import PdfBarcodeGenData
class PDFGenerator:
@@ -82,7 +85,7 @@ class PDFGenerator:
buffer.seek(0)
return buffer
- def generate(self, barcode_value, text, num_duplicates=1):
+ def generate(self, barcodes_data: List[PdfBarcodeGenData]):
buffer = BytesIO()
# Создаем документ с указанным размером страницы
@@ -93,43 +96,58 @@ class PDFGenerator:
topMargin=1 * mm,
bottomMargin=1 * mm)
- # Создаем абзац с новым стилем
- paragraph = Paragraph(text, self.small_style)
+ # Список элементов для добавления в документ
+ elements = []
+ product_barcode_canvases = []
+ product_counter = 0
+ product_barcodes_counter = 0
- # Получаем ширину и высоту абзаца
- paragraph_width, paragraph_height = paragraph.wrap(self.page_width - 2 * mm, self.page_height)
+ for barcode_data in barcodes_data:
+ # Создаем абзац с новым стилем
+ paragraph = Paragraph(barcode_data.text, self.small_style)
- # Рассчитываем доступное пространство для штрихкода
- 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 # Учитываем поля и отступы
+ # Получаем ширину и высоту абзаца
+ paragraph_width, paragraph_height = paragraph.wrap(self.page_width - 2 * mm, self.page_height)
- # Создаем штрихкод
- available_width = self.page_width - 2 * mm # Учитываем поля
+ # Рассчитываем доступное пространство для штрихкода
+ 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 # Учитываем поля и отступы
- # Приблизительное количество элементов в штрихкоде Code 128 для средней длины
- num_elements = 11 * len(barcode_value) # Примерная оценка: 11 элементов на символ
+ # Создаем штрихкод
+ available_width = self.page_width - 2 * mm # Учитываем поля
- # Рассчитываем ширину штриха
- bar_width = available_width / num_elements
- barcode = code128.Code128(barcode_value,
- barWidth=bar_width,
- barHeight=barcode_height,
- humanReadable=True)
+ # Приблизительное количество элементов в штрихкоде 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)
+ product_barcode_canvases.append(barcode)
+
+ # Добавление штрихкодов в список элементов документа
+ 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
+ nonlocal product_barcodes_counter, product_counter
+
+ barcode_canvas = product_barcode_canvases[product_counter]
+ barcode_width = barcode_canvas.width
barcode_x = (self.page_width - barcode_width) / 2 # Центрируем штрихкод
barcode_y = human_readable_height + 2 * mm # Размещаем штрихкод снизу с учетом отступа
- barcode.drawOn(canvas, barcode_x, barcode_y)
+ barcode_canvas.drawOn(canvas, barcode_x, barcode_y)
- # Создаем список элементов для добавления в документ
- elements = []
- for _ in range(num_duplicates):
- elements.append(paragraph)
- elements.append(Spacer(1, space_between_text_and_barcode)) # Отступ между текстом и штрихкодом
- elements.append(PageBreak())
+ product_barcodes_counter += 1
+ if product_barcodes_counter >= barcodes_data[product_counter].num_duplicates:
+ product_barcodes_counter = 0
+ product_counter += 1
# Создаем документ
doc.build(elements[:-1], onFirstPage=add_barcode, onLaterPages=add_barcode) # Убираем последний PageBreak
diff --git a/routers/deal.py b/routers/deal.py
index 04d1d57..5c80633 100644
--- a/routers/deal.py
+++ b/routers/deal.py
@@ -1,3 +1,4 @@
+import base64
from io import BytesIO
from typing import Annotated
@@ -7,8 +8,10 @@ from sqlalchemy.ext.asyncio import AsyncSession
from backend.dependecies import SessionDependency, CurrentUserDependency
from backend.session import get_session
from models import User
+from schemas.barcode import GetProductBarcodePdfResponse, GetDealProductsBarcodesPdfRequest
from schemas.deal import *
from services.auth import get_current_user, authorized_user, guest_user
+from services.barcode import BarcodeService
from services.billing import BillingService
from services.deal import DealService
@@ -391,4 +394,24 @@ async def add_kit_to_deal_product(
user: CurrentUserDependency
):
return await DealService(session).add_kit_to_deal_product(user, request)
+
+
+@deal_router.post(
+ '/barcodes/get-pdf',
+ operation_id='get_deal_barcodes_pdf',
+ response_model=GetProductBarcodePdfResponse
+)
+async def get_deal_products_barcodes_pdf(
+ request: GetDealProductsBarcodesPdfRequest,
+ session: Annotated[AsyncSession, Depends(get_session)]
+):
+ filename, pdf_buffer = await BarcodeService(session).get_deal_barcodes_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'
+ )
+
# endregion
diff --git a/schemas/barcode.py b/schemas/barcode.py
index 859e10b..4f904ae 100644
--- a/schemas/barcode.py
+++ b/schemas/barcode.py
@@ -47,6 +47,12 @@ class BarcodeSchema(BaseSchema):
additional_field: str | None = None
+class PdfBarcodeGenData(BaseSchema):
+ barcode_value: str
+ text: str
+ num_duplicates: int = 1
+
+
# endregion
# region Requests
@@ -81,6 +87,10 @@ class GetProductBarcodePdfRequest(GetProductBarcodeRequest):
quantity: int
+class GetDealProductsBarcodesPdfRequest(BaseSchema):
+ deal_id: int
+
+
# endregion
# region Responses
diff --git a/services/barcode.py b/services/barcode.py
index b6d7450..c4d641e 100644
--- a/services/barcode.py
+++ b/services/barcode.py
@@ -1,10 +1,12 @@
+from typing import Dict
+
from sqlalchemy import select, update, insert
from sqlalchemy.orm import selectinload, joinedload
from barcodes.attributes import AttributeWriterFactory
from barcodes.generator.default_generator import DefaultBarcodeGenerator
from models import BarcodeTemplate, BarcodeTemplateAttribute, barcode_template_attribute_link, Product, \
- BarcodeTemplateAdditionalField, BarcodeTemplateSize
+ BarcodeTemplateAdditionalField, BarcodeTemplateSize, Deal, DealProduct
from schemas.barcode import *
from services.base import BaseService
@@ -91,13 +93,50 @@ class BarcodeService(BaseService):
default_generator = DefaultBarcodeGenerator()
filename = f'{product.id}_barcode.pdf'
pdf_buffer = default_generator.generate(
- request.barcode,
- product,
- barcode_template,
- request.quantity
+ [{
+ "barcode": request.barcode,
+ "product": product,
+ "template": barcode_template,
+ "quantity": request.quantity
+ }]
)
return filename, pdf_buffer
+ async def get_deal_barcodes_pdf(self, request: GetDealProductsBarcodesPdfRequest) -> GetProductBarcodeResponse:
+ stmt = (
+ select(Deal)
+ .options(
+ selectinload(Deal.products).joinedload(DealProduct.product).selectinload(Product.client),
+ selectinload(Deal.products).joinedload(DealProduct.product).joinedload(Product.barcodes)
+ )
+ .filter(Deal.id == request.deal_id)
+ )
+ query = await self.session.execute(stmt)
+ deal: Deal = query.scalar()
+ if not deal:
+ raise ValueError('Сделка не найдена')
+
+ barcodes_data: List[Dict[str, str | Product | BarcodeTemplate | int]] = []
+ for deal_product in deal.products:
+ product_request = GetProductBarcodeRequest(
+ product_id=deal_product.product_id,
+ barcode="",
+ barcode_template_id=deal_product.product.barcode_template_id,
+ )
+ barcode_template = await self._get_barcode_template(product_request, deal_product.product)
+ barcodes_data.append({
+ "barcode": deal_product.product.barcodes[0].barcode,
+ "product": deal_product.product,
+ "template": barcode_template,
+ "quantity": deal_product.quantity
+ })
+
+ default_generator = DefaultBarcodeGenerator()
+ filename = f'{deal.id}_deal_barcodes.pdf'
+ pdf_buffer = default_generator.generate(barcodes_data)
+
+ return filename, pdf_buffer
+
# endregion
# region Template
diff --git a/services/billing.py b/services/billing.py
index 4b87c2d..d3e29de 100644
--- a/services/billing.py
+++ b/services/billing.py
@@ -4,7 +4,7 @@ from typing import List
from fastapi import HTTPException
from number_to_string import get_string_by_number
from sqlalchemy import select
-from sqlalchemy.orm import selectinload
+from sqlalchemy.orm import selectinload, joinedload
from starlette import status
from weasyprint import HTML, CSS
@@ -168,6 +168,8 @@ class BillingService(BaseService):
.options(
selectinload(Deal.products).selectinload(DealProduct.services),
selectinload(Deal.services).selectinload(DealServiceModel.service),
+ joinedload(Deal.shipping_warehouse),
+ joinedload(Deal.client),
)
)
diff --git a/templates/documents/bill-of-payment.html b/templates/documents/bill-of-payment.html
index 9fd999d..5c55890 100644
--- a/templates/documents/bill-of-payment.html
+++ b/templates/documents/bill-of-payment.html
@@ -24,6 +24,14 @@