feat: spacers between barcodes of diff products, avoid product cut in deal document, deal document refactoring

This commit is contained in:
2024-09-30 01:36:31 +04:00
parent 413d8755cc
commit 61d379e7dc
9 changed files with 271 additions and 240 deletions

View File

@@ -1,3 +1,4 @@
from io import BytesIO
from typing import List, Dict from typing import List, Dict
from barcodes.attributes import AttributeWriterFactory from barcodes.attributes import AttributeWriterFactory
@@ -8,7 +9,7 @@ from schemas.barcode import PdfBarcodeGenData
class DefaultBarcodeGenerator(BaseBarcodeGenerator): class DefaultBarcodeGenerator(BaseBarcodeGenerator):
def generate(self, barcodes_data: List[Dict[str, str | Product | BarcodeTemplate | int]]): def generate(self, barcodes_data: List[Dict[str, str | Product | BarcodeTemplate | int]]) -> BytesIO:
pdf_generator = PDFGenerator() pdf_generator = PDFGenerator()
pdf_barcodes_gen_data: List[PdfBarcodeGenData] = [] pdf_barcodes_gen_data: List[PdfBarcodeGenData] = []
@@ -35,6 +36,4 @@ class DefaultBarcodeGenerator(BaseBarcodeGenerator):
) )
) )
print(f"value = {barcode_data['barcode']}, text = {barcode_text}, num = {barcode_data['quantity']}")
return pdf_generator.generate(pdf_barcodes_gen_data) return pdf_generator.generate(pdf_barcodes_gen_data)

View File

@@ -20,6 +20,7 @@ class PDFGenerator:
FONT_FILE_PATH = os.path.join(FONTS_FOLDER, 'DejaVuSans.ttf') FONT_FILE_PATH = os.path.join(FONTS_FOLDER, 'DejaVuSans.ttf')
self.page_width = 58 * mm self.page_width = 58 * mm
self.page_height = 40 * mm self.page_height = 40 * mm
self.number_of_spacing_pages = 2
pdfmetrics.registerFont(TTFont('DejaVuSans', FONT_FILE_PATH)) pdfmetrics.registerFont(TTFont('DejaVuSans', FONT_FILE_PATH))
@@ -40,12 +41,14 @@ class PDFGenerator:
buffer = BytesIO() buffer = BytesIO()
# Create document with specified page size # Create document with specified page size
doc = SimpleDocTemplate(buffer, doc = SimpleDocTemplate(
pagesize=(self.page_width, self.page_height), buffer,
rightMargin=1 * mm, pagesize=(self.page_width, self.page_height),
leftMargin=1 * mm, rightMargin=1 * mm,
topMargin=1 * mm, leftMargin=1 * mm,
bottomMargin=1 * mm) topMargin=1 * mm,
bottomMargin=1 * mm
)
# Create paragraph with new style # Create paragraph with new style
paragraph = Paragraph(text, self.small_style) paragraph = Paragraph(text, self.small_style)
@@ -61,10 +64,12 @@ class PDFGenerator:
# Calculate barWidth # Calculate barWidth
bar_width = available_width / num_elements bar_width = available_width / num_elements
barcode = code128.Code128(barcode_value, barcode = code128.Code128(
barWidth=bar_width, barcode_value,
barHeight=barcode_height, barWidth=bar_width,
humanReadable=True) barHeight=barcode_height,
humanReadable=True
)
# Function to draw barcode on canvas # Function to draw barcode on canvas
def add_barcode(canvas, doc): def add_barcode(canvas, doc):
@@ -85,22 +90,26 @@ class PDFGenerator:
buffer.seek(0) buffer.seek(0)
return buffer return buffer
def generate(self, barcodes_data: List[PdfBarcodeGenData]): # Создание документа с указанным размером страницы
def _create_doc(self, buffer):
return SimpleDocTemplate(
buffer,
pagesize=(self.page_width, self.page_height),
rightMargin=1 * mm,
leftMargin=1 * mm,
topMargin=1 * mm,
bottomMargin=1 * mm
)
def generate(self, barcodes_data: List[PdfBarcodeGenData]) -> BytesIO:
buffer = BytesIO() buffer = BytesIO()
# Создаем документ с указанным размером страницы # Создаем документ с указанным размером страницы
doc = SimpleDocTemplate(buffer, doc = self._create_doc(buffer)
pagesize=(self.page_width, self.page_height),
rightMargin=1 * mm,
leftMargin=1 * mm,
topMargin=1 * mm,
bottomMargin=1 * mm)
# Список элементов для добавления в документ # Список элементов для добавления в документ
elements = [] elements = []
product_barcode_canvases = [] product_barcode_canvases = []
product_counter = 0
product_barcodes_counter = 0
for barcode_data in barcodes_data: for barcode_data in barcodes_data:
# Создаем абзац с новым стилем # Создаем абзац с новым стилем
@@ -122,10 +131,12 @@ class PDFGenerator:
# Рассчитываем ширину штриха # Рассчитываем ширину штриха
bar_width = available_width / num_elements bar_width = available_width / num_elements
barcode = code128.Code128(barcode_data.barcode_value, barcode = code128.Code128(
barWidth=bar_width, barcode_data.barcode_value,
barHeight=barcode_height, barWidth=bar_width,
humanReadable=True) barHeight=barcode_height,
humanReadable=True
)
product_barcode_canvases.append(barcode) product_barcode_canvases.append(barcode)
# Добавление штрихкодов в список элементов документа # Добавление штрихкодов в список элементов документа
@@ -134,24 +145,39 @@ class PDFGenerator:
elements.append(Spacer(1, space_between_text_and_barcode)) # Отступ между текстом и штрихкодом elements.append(Spacer(1, space_between_text_and_barcode)) # Отступ между текстом и штрихкодом
elements.append(PageBreak()) elements.append(PageBreak())
# Добавление спейсеров
for _ in range(self.number_of_spacing_pages):
elements.append(PageBreak())
# Удалить последние спейсеры
elements = elements[:-2]
product_counter, product_barcodes_counter = 0, 0
# Функция для отрисовки штрихкода на canvas # Функция для отрисовки штрихкода на canvas
def add_barcode(canvas, doc): def add_barcode(canvas, doc):
nonlocal product_barcodes_counter, product_counter nonlocal product_barcodes_counter, product_counter
barcode_canvas = product_barcode_canvases[product_counter] barcode_canvas = product_barcode_canvases[product_counter]
product_barcodes_counter += 1
# Если данная страница это спейсер, то оставить пустой
num_duplicates = barcodes_data[product_counter].num_duplicates
if product_barcodes_counter > num_duplicates:
if product_barcodes_counter >= num_duplicates + self.number_of_spacing_pages:
product_barcodes_counter = 0
product_counter += 1
return
# Отрисовка штрихкода
barcode_width = barcode_canvas.width barcode_width = barcode_canvas.width
barcode_x = (self.page_width - barcode_width) / 2 # Центрируем штрихкод barcode_x = (self.page_width - barcode_width) / 2 # Центрируем штрихкод
barcode_y = human_readable_height + 2 * mm # Размещаем штрихкод снизу с учетом отступа barcode_y = human_readable_height + 2 * mm # Размещаем штрихкод снизу с учетом отступа
barcode_canvas.drawOn(canvas, barcode_x, barcode_y) barcode_canvas.drawOn(canvas, barcode_x, barcode_y)
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 doc.build(elements[:-1], onFirstPage=add_barcode, onLaterPages=add_barcode) # Убираем последний PageBreak
buffer.seek(0) buffer.seek(0)
return buffer return buffer

View File

@@ -1,4 +1,5 @@
from typing import Dict from io import BytesIO
from typing import Dict, Tuple
from sqlalchemy import select, update, insert from sqlalchemy import select, update, insert
from sqlalchemy.orm import selectinload, joinedload from sqlalchemy.orm import selectinload, joinedload
@@ -76,8 +77,7 @@ class BarcodeService(BaseService):
) )
return GetProductBarcodeResponse(barcode=barcode) return GetProductBarcodeResponse(barcode=barcode)
async def get_barcode_pdf(self, request: GetProductBarcodePdfRequest) -> GetProductBarcodeResponse: async def get_barcode_pdf(self, request: GetProductBarcodePdfRequest) -> Tuple[str, BytesIO]:
# get product by id
stmt = ( stmt = (
select(Product) select(Product)
.options( .options(
@@ -102,7 +102,7 @@ class BarcodeService(BaseService):
) )
return filename, pdf_buffer return filename, pdf_buffer
async def get_deal_barcodes_pdf(self, request: GetDealProductsBarcodesPdfRequest) -> GetProductBarcodeResponse: async def get_deal_barcodes_pdf(self, request: GetDealProductsBarcodesPdfRequest) -> Tuple[str, BytesIO]:
stmt = ( stmt = (
select(Deal) select(Deal)
.options( .options(

View File

@@ -1036,7 +1036,7 @@ class DealService(BaseService):
last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at) last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at)
general_services_total = sum((service.price * service.quantity for service in deal.services)) general_services_total = sum((service.price * service.quantity for service in deal.services))
product_services_totals: List[Dict[str, int]] = await self._get_product_services_totals(deal) product_services_totals: List[Dict[str, int]] = await self._get_product_services_totals(deal)
template = ENV.get_template("deal.html") template = ENV.get_template("deal/deal.html")
product_urls: List[Optional[str]] = [] product_urls: List[Optional[str]] = []
for product in deal.products: for product in deal.products:

View File

@@ -60,14 +60,14 @@ hr {
} }
.product-data { .product-data {
height: 160px; height: 130px;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
flex-direction: column; flex-direction: column;
} }
.product-data > * { .product-data > * {
width: 170px; width: 200px;
padding-right: 170px; padding-right: 170px;
} }
@@ -143,8 +143,8 @@ table tfoot {
} }
.product-img { .product-img {
height: 300px; height: 240px;
width: 200px; width: 160px;
object-fit: cover; object-fit: cover;
padding: 0; padding: 0;
margin: 0; margin: 0;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,62 @@
<!--#region Header -->
<div class="deal-name-id-container">
<div class="medium-text bold">
Название сделки: {{ deal.name }}
</div>
</div>
<hr/>
<div class="medium-text">
Дата создания: {{ now().strftime("%d.%m.%Y, %H:%M") }}
</div>
<div class="medium-text">
Текущий статус: {{ current_status_str }}
</div>
<div class="medium-text">
{% if deal.comment %}
Комментарий к сделке: {{ deal.comment }}
{% endif %}
</div>
<hr/>
<!--#endregion -->
<!--#region General services -->
<div class="large-text bold">
Клиент: {{ deal.client.name }}
</div>
<div class="medium-text bold">
Дата отгрузки: {{ last_status.next_status_deadline.strftime("%d.%m.%Y") }}
</div>
{% if deal.base_marketplace.name %}
<div class="medium-text bold">Маркетплейс: {{ deal.base_marketplace.name }}</div>
{% endif %}
{% if deal.shipping_warehouse.name %}
<div class="medium-text bold">Склад отгрузки: {{ deal.shipping_warehouse.name }}</div>
{% endif %}
{% if deal.services|length > 0 %}
<table>
<thead>
<tr>
<th>Общие услуги</th>
<th>Количество</th>
<th>Сумма</th>
</tr>
<tfoot>
</tfoot>
</thead>
<tbody>
{% for service in deal.services %}
<tr>
<td>{{ service.service.name }}</td>
<td>{{ '{:,}'.format(service.quantity) }} шт.</td>
<td>{{ '{:,}'.format(service.price * service.quantity).replace(',', ' ') }} Р</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="medium-text total align-right bold">
Итого: {{ general_services_total }} Р
</div>
{% else %}
<div class="medium-text total align-right bold">
</div>
{% endif %}
<!--#endregion -->

File diff suppressed because one or more lines are too long