feat: spacers between barcodes of diff products, avoid product cut in deal document, deal document refactoring
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
from io import BytesIO
|
||||
from typing import List, Dict
|
||||
|
||||
from barcodes.attributes import AttributeWriterFactory
|
||||
@@ -8,7 +9,7 @@ from schemas.barcode import PdfBarcodeGenData
|
||||
|
||||
|
||||
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_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)
|
||||
|
||||
@@ -20,6 +20,7 @@ class PDFGenerator:
|
||||
FONT_FILE_PATH = os.path.join(FONTS_FOLDER, 'DejaVuSans.ttf')
|
||||
self.page_width = 58 * mm
|
||||
self.page_height = 40 * mm
|
||||
self.number_of_spacing_pages = 2
|
||||
|
||||
pdfmetrics.registerFont(TTFont('DejaVuSans', FONT_FILE_PATH))
|
||||
|
||||
@@ -40,12 +41,14 @@ class PDFGenerator:
|
||||
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)
|
||||
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)
|
||||
@@ -61,10 +64,12 @@ class PDFGenerator:
|
||||
|
||||
# Calculate barWidth
|
||||
bar_width = available_width / num_elements
|
||||
barcode = code128.Code128(barcode_value,
|
||||
barWidth=bar_width,
|
||||
barHeight=barcode_height,
|
||||
humanReadable=True)
|
||||
barcode = code128.Code128(
|
||||
barcode_value,
|
||||
barWidth=bar_width,
|
||||
barHeight=barcode_height,
|
||||
humanReadable=True
|
||||
)
|
||||
|
||||
# Function to draw barcode on canvas
|
||||
def add_barcode(canvas, doc):
|
||||
@@ -85,22 +90,26 @@ class PDFGenerator:
|
||||
buffer.seek(0)
|
||||
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()
|
||||
|
||||
# Создаем документ с указанным размером страницы
|
||||
doc = SimpleDocTemplate(buffer,
|
||||
pagesize=(self.page_width, self.page_height),
|
||||
rightMargin=1 * mm,
|
||||
leftMargin=1 * mm,
|
||||
topMargin=1 * mm,
|
||||
bottomMargin=1 * mm)
|
||||
doc = self._create_doc(buffer)
|
||||
|
||||
# Список элементов для добавления в документ
|
||||
elements = []
|
||||
product_barcode_canvases = []
|
||||
product_counter = 0
|
||||
product_barcodes_counter = 0
|
||||
|
||||
for barcode_data in barcodes_data:
|
||||
# Создаем абзац с новым стилем
|
||||
@@ -122,10 +131,12 @@ class PDFGenerator:
|
||||
|
||||
# Рассчитываем ширину штриха
|
||||
bar_width = available_width / num_elements
|
||||
barcode = code128.Code128(barcode_data.barcode_value,
|
||||
barWidth=bar_width,
|
||||
barHeight=barcode_height,
|
||||
humanReadable=True)
|
||||
barcode = code128.Code128(
|
||||
barcode_data.barcode_value,
|
||||
barWidth=bar_width,
|
||||
barHeight=barcode_height,
|
||||
humanReadable=True
|
||||
)
|
||||
product_barcode_canvases.append(barcode)
|
||||
|
||||
# Добавление штрихкодов в список элементов документа
|
||||
@@ -134,24 +145,39 @@ class PDFGenerator:
|
||||
elements.append(Spacer(1, space_between_text_and_barcode)) # Отступ между текстом и штрихкодом
|
||||
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
|
||||
def add_barcode(canvas, doc):
|
||||
nonlocal product_barcodes_counter, 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_x = (self.page_width - barcode_width) / 2 # Центрируем штрихкод
|
||||
barcode_y = human_readable_height + 2 * mm # Размещаем штрихкод снизу с учетом отступа
|
||||
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
|
||||
|
||||
buffer.seek(0)
|
||||
return buffer
|
||||
|
||||
|
||||
@@ -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.orm import selectinload, joinedload
|
||||
@@ -76,8 +77,7 @@ class BarcodeService(BaseService):
|
||||
)
|
||||
return GetProductBarcodeResponse(barcode=barcode)
|
||||
|
||||
async def get_barcode_pdf(self, request: GetProductBarcodePdfRequest) -> GetProductBarcodeResponse:
|
||||
# get product by id
|
||||
async def get_barcode_pdf(self, request: GetProductBarcodePdfRequest) -> Tuple[str, BytesIO]:
|
||||
stmt = (
|
||||
select(Product)
|
||||
.options(
|
||||
@@ -102,7 +102,7 @@ class BarcodeService(BaseService):
|
||||
)
|
||||
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 = (
|
||||
select(Deal)
|
||||
.options(
|
||||
|
||||
@@ -1036,7 +1036,7 @@ class DealService(BaseService):
|
||||
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))
|
||||
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]] = []
|
||||
for product in deal.products:
|
||||
|
||||
@@ -60,14 +60,14 @@ hr {
|
||||
}
|
||||
|
||||
.product-data {
|
||||
height: 160px;
|
||||
height: 130px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.product-data > * {
|
||||
width: 170px;
|
||||
width: 200px;
|
||||
padding-right: 170px;
|
||||
}
|
||||
|
||||
@@ -143,8 +143,8 @@ table tfoot {
|
||||
}
|
||||
|
||||
.product-img {
|
||||
height: 300px;
|
||||
width: 200px;
|
||||
height: 240px;
|
||||
width: 160px;
|
||||
object-fit: cover;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
File diff suppressed because one or more lines are too long
86
templates/documents/deal/deal-product-services.html
Normal file
86
templates/documents/deal/deal-product-services.html
Normal file
File diff suppressed because one or more lines are too long
62
templates/documents/deal/deal-services.html
Normal file
62
templates/documents/deal/deal-services.html
Normal 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 -->
|
||||
57
templates/documents/deal/deal.html
Normal file
57
templates/documents/deal/deal.html
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user