diff --git a/barcodes/generator/default_generator.py b/barcodes/generator/default_generator.py index 12fe7b2..8e3a133 100644 --- a/barcodes/generator/default_generator.py +++ b/barcodes/generator/default_generator.py @@ -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) diff --git a/barcodes/pdf/generator.py b/barcodes/pdf/generator.py index 7aa9f93..b378305 100644 --- a/barcodes/pdf/generator.py +++ b/barcodes/pdf/generator.py @@ -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 - diff --git a/services/barcode.py b/services/barcode.py index c4d641e..ec89bc7 100644 --- a/services/barcode.py +++ b/services/barcode.py @@ -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( diff --git a/services/deal.py b/services/deal.py index 5ddf4b9..cbacbf6 100644 --- a/services/deal.py +++ b/services/deal.py @@ -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: diff --git a/static/css/deal.css b/static/css/deal.css index 9ea93a2..687681b 100644 --- a/static/css/deal.css +++ b/static/css/deal.css @@ -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; diff --git a/templates/documents/deal.html b/templates/documents/deal.html deleted file mode 100644 index 5747fc5..0000000 --- a/templates/documents/deal.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - - - Сделка - - - -
-
-
- -
-
- Название сделки: {{ deal.name }} -
-
-
-
- Дата создания: {{ now().strftime("%d.%m.%Y, %H:%M") }} -
-
- Текущий статус: {{ current_status_str }} -
-
- {% if deal.comment %} - Комментарий к сделке: {{ deal.comment }} - {% endif %} -
-
- - -
- Клиент: {{ deal.client.name }} -
-
- Дата отгрузки: {{ last_status.next_status_deadline.strftime("%d.%m.%Y") }} -
- {% if deal.base_marketplace.name %} -
Маркетплейс: {{ deal.base_marketplace.name }}
- {% endif %} - {% if deal.shipping_warehouse.name %} -
Склад отгрузки: {{ deal.shipping_warehouse.name }}
- {% endif %} - {% if deal.services|length > 0 %} - - - - - - - - - - - - {% for service in deal.services %} - - - - - - {% endfor %} - -
Общие услугиКоличествоСумма
{{ service.service.name }}{{ '{:,}'.format(service.quantity) }} шт.{{ '{:,}'.format(service.price * service.quantity).replace(',', ' ') }} Р
-
- Итого: {{ general_services_total }} Р -
- {% else %} -
-
- {% endif %} - - - {% for product in deal.products %} -
-
-
-
- {% if product.product.images|length > 0 %} - Фото товара - {% else %} - Фото товара - {% endif %} -
-
-
- {{ loop.index }}. {{ product.product.name }} -
-
-
Артикул: {{ product.product.article }}
- {% if product.product.brand %} -
Бренд: {{ product.product.brand }}
- {% endif %} - {% if product.product.color %} -
Цвет: {{ product.product.color }}
- {% endif %} - {% if product.product.size %} -
Размер: {{ product.product.size }}
- {% endif %} - {% if product.quantity %} -
Количество: {{ product.quantity }}
- {% endif %} - {% if product.product.additional_info %} -
- Доп. информация: {{ product.product.additional_info }} -
- {% endif %} -
-
-
-
-
-
- {% if product.product.barcodes|length > 0 %} -
- {{ encode128(product.product.barcodes[0].barcode, "A") }} -
-
- {{ product.product.barcodes[0].barcode }} -
- {% else %} -
- {% endif %} -
-
-
- {% if product.services|length > 0 %} - - - - - - - - - - - - {% for service in product.services %} - - - - - - {% endfor %} - -
Наименование услугиЦенаСумма
{{ service.service.name }}{{ '{:,}'.format(product.quantity) }} Р{{ '{:,}'.format(service.price * product.quantity).replace(',', ' ') }} Р
-
- Итого: {{ product_services_totals[loop.index0].total }} Р, за - единицу: {{ product_services_totals[loop.index0].total_one_product }} Р -
- {% else %} -
- {% endif %} - {% endfor %} - -
-
-
- - \ No newline at end of file diff --git a/templates/documents/deal/deal-product-services.html b/templates/documents/deal/deal-product-services.html new file mode 100644 index 0000000..3b47dd8 --- /dev/null +++ b/templates/documents/deal/deal-product-services.html @@ -0,0 +1,86 @@ +
+
+
+
+
+ {% if product.product.images|length > 0 %} + Фото товара + {% else %} + Фото товара + {% endif %} +
+
+
+ {{ index }}. {{ product.product.name }} +
+
+
Артикул: {{ product.product.article }}
+ {% if product.product.brand %} +
Бренд: {{ product.product.brand }}
+ {% endif %} + {% if product.product.color %} +
Цвет: {{ product.product.color }}
+ {% endif %} + {% if product.product.size %} +
Размер: {{ product.product.size }}
+ {% endif %} + {% if product.quantity %} +
Количество: {{ product.quantity }}
+ {% endif %} + {% if product.product.additional_info %} +
+ Доп. информация: {{ product.product.additional_info }} +
+ {% endif %} +
+
+
+
+
+
+ {% if product.product.barcodes|length > 0 %} +
+ {{ encode128(product.product.barcodes[0].barcode, "A") }} +
+
+ {{ product.product.barcodes[0].barcode }} +
+ {% else %} +
+ {% endif %} +
+
+
+ {% if product.services|length > 0 %} + + + + + + + + + + + + {% for service in product.services %} + + + + + + {% endfor %} + +
Наименование услугиЦенаСумма
{{ service.service.name }}{{ '{:,}'.format(product.quantity) }} Р{{ '{:,}'.format(service.price * product.quantity).replace(',', ' ') }} Р
+
+ Итого: {{ product_services_totals[loop.index0].total }} Р, за + единицу: {{ product_services_totals[loop.index0].total_one_product }} Р +
+ {% else %} +
+ {% endif %} +
diff --git a/templates/documents/deal/deal-services.html b/templates/documents/deal/deal-services.html new file mode 100644 index 0000000..78c3ab8 --- /dev/null +++ b/templates/documents/deal/deal-services.html @@ -0,0 +1,62 @@ + +
+
+ Название сделки: {{ deal.name }} +
+
+
+
+ Дата создания: {{ now().strftime("%d.%m.%Y, %H:%M") }} +
+
+ Текущий статус: {{ current_status_str }} +
+
+ {% if deal.comment %} + Комментарий к сделке: {{ deal.comment }} + {% endif %} +
+
+ + +
+ Клиент: {{ deal.client.name }} +
+
+ Дата отгрузки: {{ last_status.next_status_deadline.strftime("%d.%m.%Y") }} +
+{% if deal.base_marketplace.name %} +
Маркетплейс: {{ deal.base_marketplace.name }}
+{% endif %} +{% if deal.shipping_warehouse.name %} +
Склад отгрузки: {{ deal.shipping_warehouse.name }}
+{% endif %} +{% if deal.services|length > 0 %} + + + + + + + + + + + + {% for service in deal.services %} + + + + + + {% endfor %} + +
Общие услугиКоличествоСумма
{{ service.service.name }}{{ '{:,}'.format(service.quantity) }} шт.{{ '{:,}'.format(service.price * service.quantity).replace(',', ' ') }} Р
+
+ Итого: {{ general_services_total }} Р +
+{% else %} +
+
+{% endif %} + \ No newline at end of file diff --git a/templates/documents/deal/deal.html b/templates/documents/deal/deal.html new file mode 100644 index 0000000..ed57687 --- /dev/null +++ b/templates/documents/deal/deal.html @@ -0,0 +1,57 @@ + + + + + + + + Сделка + + + +
+
+
+ {% include "deal/deal-services.html" %} + {% for product in deal.products %} + {% with index = loop.index %} + {% include "deal/deal-product-services.html" %} + {% endwith %} + {% endfor %} +
+
+
+ + \ No newline at end of file