feat: pallets and boxes for deals
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import os
|
||||
|
||||
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
|
||||
from reportlab.lib.units import mm
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
from reportlab.platypus import SimpleDocTemplate
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from constants import APP_PATH
|
||||
|
||||
|
||||
class BasePdfCardGenerator:
|
||||
def __init__(self, session: AsyncSession):
|
||||
self._session = session
|
||||
assets_folder = os.path.join(APP_PATH, 'assets')
|
||||
fonts_folder = os.path.join(assets_folder, 'fonts')
|
||||
font_file_path = os.path.join(fonts_folder, 'DejaVuSans.ttf')
|
||||
self.page_width = 58 * mm
|
||||
self.page_height = 40 * mm
|
||||
pdfmetrics.registerFont(TTFont('DejaVuSans', font_file_path))
|
||||
|
||||
self.styles = getSampleStyleSheet()
|
||||
self._set_small_paragraph_styles()
|
||||
self._set_medium_paragraph_styles()
|
||||
|
||||
def _set_small_paragraph_styles(self):
|
||||
common_paragraph_style = {
|
||||
"parent": self.styles['Normal'],
|
||||
"fontName": "DejaVuSans",
|
||||
"spaceAfter": 4,
|
||||
"fontSize": 9,
|
||||
}
|
||||
|
||||
self.small_style = ParagraphStyle(
|
||||
'Small',
|
||||
alignment=0,
|
||||
**common_paragraph_style,
|
||||
)
|
||||
|
||||
self.small_centered_style = ParagraphStyle(
|
||||
'SmallCentered',
|
||||
alignment=1,
|
||||
**common_paragraph_style,
|
||||
)
|
||||
|
||||
def _set_medium_paragraph_styles(self):
|
||||
self.medium_style = ParagraphStyle(
|
||||
'Medium',
|
||||
parent=self.styles['Normal'],
|
||||
fontName="DejaVuSans",
|
||||
spaceAfter=6,
|
||||
fontSize=12,
|
||||
alignment=0,
|
||||
)
|
||||
|
||||
def _create_doc(self, buffer):
|
||||
return SimpleDocTemplate(
|
||||
buffer,
|
||||
pagesize=(self.page_width, self.page_height),
|
||||
rightMargin=1,
|
||||
leftMargin=1,
|
||||
topMargin=1,
|
||||
bottomMargin=1
|
||||
)
|
||||
1
generators/shipping_qr_code_generator/__init__.py
Normal file
1
generators/shipping_qr_code_generator/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .generator import ShippingQRCodeGenerator
|
||||
139
generators/shipping_qr_code_generator/generator.py
Normal file
139
generators/shipping_qr_code_generator/generator.py
Normal file
@@ -0,0 +1,139 @@
|
||||
from io import BytesIO
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import HTTPException
|
||||
from reportlab.lib.units import mm
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.platypus import Paragraph, SimpleDocTemplate, Frame, PageBreak
|
||||
from reportlab_qrcode import QRCodeImage
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.orm import joinedload, selectinload
|
||||
|
||||
from constants import DOMAIN_NAME
|
||||
from generators.base_pdf_card_generator.base_pdf_card_generator import BasePdfCardGenerator
|
||||
from models import Deal, ShippingWarehouse, Pallet
|
||||
from models.shipping import Box
|
||||
|
||||
|
||||
class ShippingQRCodeGenerator(BasePdfCardGenerator):
|
||||
async def _get_deal_by_id(self, deal_id: int) -> Optional[Deal]:
|
||||
stmt = (
|
||||
select(Deal)
|
||||
.where(Deal.id == deal_id)
|
||||
.options(
|
||||
joinedload(Deal.shipping_warehouse),
|
||||
selectinload(Deal.pallets),
|
||||
)
|
||||
)
|
||||
deal = (await self._session.execute(stmt)).one_or_none()
|
||||
return deal[0] if deal else None
|
||||
|
||||
async def generate_deal(self, deal_id: int) -> BytesIO:
|
||||
deal = await self._get_deal_by_id(deal_id)
|
||||
if not deal:
|
||||
raise HTTPException(status_code=404, detail=f"Сделка с ID {deal_id}a не найдена")
|
||||
|
||||
buffer = BytesIO()
|
||||
doc: SimpleDocTemplate = self._create_doc(buffer)
|
||||
|
||||
deal_link = f"{DOMAIN_NAME}/deals/{deal_id}"
|
||||
shipping_warehouse = await self._session.get(ShippingWarehouse, deal.shipping_warehouse_id)
|
||||
warehouse_name = shipping_warehouse.name if shipping_warehouse else ""
|
||||
|
||||
def on_first_page(canvas: Canvas, doc):
|
||||
qr = QRCodeImage(deal_link, size=30 * mm)
|
||||
qr.drawOn(canvas, 0, 30)
|
||||
|
||||
deal_id_paragraph = Paragraph(f"ID: {deal_id}", self.small_centered_style)
|
||||
deal_name_paragraph = Paragraph(str(deal.name), self.small_centered_style)
|
||||
|
||||
frame = Frame(x1=28 * mm, y1=5 * mm, width=30 * mm, height=30 * mm)
|
||||
frame.addFromList([deal_id_paragraph, deal_name_paragraph], canvas)
|
||||
|
||||
warehouse_paragraph = Paragraph(warehouse_name, self.small_centered_style)
|
||||
frame = Frame(x1=0 * mm, y1=-7 * mm, width=58 * mm, height=20 * mm)
|
||||
frame.addFromList([warehouse_paragraph], canvas)
|
||||
|
||||
empty_paragraph = Paragraph("", self.small_centered_style)
|
||||
elements = [empty_paragraph]
|
||||
doc.build(elements, on_first_page)
|
||||
|
||||
buffer.seek(0)
|
||||
return buffer
|
||||
|
||||
async def generate_pallets(self, deal_id: int):
|
||||
deal = await self._get_deal_by_id(deal_id)
|
||||
if not deal:
|
||||
raise HTTPException(status_code=404, detail=f"Сделка с ID {deal_id}a не найдена")
|
||||
|
||||
buffer = BytesIO()
|
||||
doc: SimpleDocTemplate = self._create_doc(buffer)
|
||||
|
||||
shipping_warehouse = await self._session.get(ShippingWarehouse, deal.shipping_warehouse_id)
|
||||
warehouse_name = shipping_warehouse.name if shipping_warehouse else ""
|
||||
|
||||
total_pallets = len(deal.pallets)
|
||||
elements = []
|
||||
|
||||
for pallet_counter in range(total_pallets):
|
||||
elements.append(Paragraph(f"ID: {deal_id}", self.medium_style))
|
||||
elements.append(Paragraph(str(deal.name), self.medium_style))
|
||||
elements.append(Paragraph(f"Паллет {pallet_counter + 1}/{total_pallets}", self.medium_style))
|
||||
elements.append(Paragraph(warehouse_name, self.medium_style))
|
||||
elements.append(PageBreak())
|
||||
|
||||
doc.build(elements)
|
||||
|
||||
buffer.seek(0)
|
||||
return buffer
|
||||
|
||||
async def _get_boxes_on_pallets_count(self, deal_id):
|
||||
stmt_boxes_on_pallets = (
|
||||
select(
|
||||
Pallet.id,
|
||||
func.count(Box.id).label("box_count"),
|
||||
)
|
||||
.join(Box, isouter=True)
|
||||
.where(Pallet.deal_id == deal_id)
|
||||
.group_by(Pallet.id)
|
||||
)
|
||||
pallets = (await self._session.execute(stmt_boxes_on_pallets)).all()
|
||||
return pallets
|
||||
|
||||
async def generate_boxes(self, deal_id: int) -> BytesIO:
|
||||
deal = await self._get_deal_by_id(deal_id)
|
||||
if not deal:
|
||||
raise HTTPException(status_code=404, detail=f"Сделка с ID {deal_id}a не найдена")
|
||||
|
||||
shipping_warehouse = await self._session.get(ShippingWarehouse, deal.shipping_warehouse_id)
|
||||
warehouse_name = shipping_warehouse.name if shipping_warehouse else ""
|
||||
|
||||
buffer = BytesIO()
|
||||
doc: SimpleDocTemplate = self._create_doc(buffer)
|
||||
|
||||
elements = []
|
||||
|
||||
total_pallets = len(deal.pallets)
|
||||
boxes_on_pallets = await self._get_boxes_on_pallets_count(deal_id)
|
||||
boxes_without_pallets = len(deal.boxes)
|
||||
|
||||
for box_on_pallet in range(boxes_without_pallets):
|
||||
elements.append(Paragraph(f"ID: {deal_id}", self.medium_style))
|
||||
elements.append(Paragraph(str(deal.name), self.medium_style))
|
||||
elements.append(Paragraph(f"Короб {box_on_pallet + 1}/{boxes_without_pallets}", self.medium_style))
|
||||
elements.append(Paragraph(warehouse_name, self.medium_style))
|
||||
elements.append(PageBreak())
|
||||
|
||||
for pallet_idx, [_, box_count] in enumerate(boxes_on_pallets):
|
||||
for box_on_pallet in range(box_count):
|
||||
elements.append(Paragraph(f"ID: {deal_id}", self.medium_style))
|
||||
elements.append(Paragraph(str(deal.name), self.medium_style))
|
||||
box_label = f"Паллет {pallet_idx + 1}/{total_pallets}, Короб {box_on_pallet + 1}/{box_count}"
|
||||
elements.append(Paragraph(box_label, self.medium_style))
|
||||
elements.append(Paragraph(warehouse_name, self.medium_style))
|
||||
elements.append(PageBreak())
|
||||
|
||||
doc.build(elements)
|
||||
|
||||
buffer.seek(0)
|
||||
return buffer
|
||||
@@ -1,52 +1,16 @@
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab_qrcode import QRCodeImage
|
||||
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
|
||||
from reportlab.lib.units import mm
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.platypus import Paragraph
|
||||
from reportlab_qrcode import QRCodeImage
|
||||
|
||||
from constants import APP_PATH
|
||||
from generators.base_pdf_card_generator.base_pdf_card_generator import BasePdfCardGenerator
|
||||
from models import User
|
||||
from services.user import UserService
|
||||
|
||||
|
||||
class WorkShiftsQRCodeGenerator:
|
||||
def __init__(self, session: AsyncSession):
|
||||
self._session = session
|
||||
assets_folder = os.path.join(APP_PATH, 'assets')
|
||||
fonts_folder = os.path.join(assets_folder, 'fonts')
|
||||
font_file_path = os.path.join(fonts_folder, 'DejaVuSans.ttf')
|
||||
self.page_width = 58 * mm
|
||||
self.page_height = 40 * mm
|
||||
pdfmetrics.registerFont(TTFont('DejaVuSans', font_file_path))
|
||||
|
||||
styles = getSampleStyleSheet()
|
||||
self.small_style = ParagraphStyle(
|
||||
'Small',
|
||||
parent=styles['Normal'],
|
||||
alignment=1,
|
||||
fontName='DejaVuSans', # Specify the new font
|
||||
fontSize=9,
|
||||
leading=12,
|
||||
spaceAfter=2,
|
||||
rightIndent=2,
|
||||
)
|
||||
|
||||
def _create_doc(self, buffer):
|
||||
return SimpleDocTemplate(
|
||||
buffer,
|
||||
pagesize=(self.page_width, self.page_height),
|
||||
rightMargin=1,
|
||||
leftMargin=1,
|
||||
topMargin=1,
|
||||
bottomMargin=1
|
||||
)
|
||||
|
||||
class WorkShiftsQRCodeGenerator(BasePdfCardGenerator):
|
||||
async def generate(self, user_id: int) -> BytesIO:
|
||||
buffer = BytesIO()
|
||||
doc = self._create_doc(buffer)
|
||||
@@ -59,7 +23,7 @@ class WorkShiftsQRCodeGenerator:
|
||||
position = user.position.name if user.position else ""
|
||||
user_info = Paragraph(
|
||||
f"{user.first_name} {user.second_name}\n{position}",
|
||||
self.small_style
|
||||
self.small_centered_style
|
||||
)
|
||||
|
||||
doc.build([user_info], on_first_page)
|
||||
|
||||
Reference in New Issue
Block a user