feat: pallets and boxes for deals

This commit is contained in:
2024-12-09 16:45:10 +04:00
parent d56e292276
commit 863dd226c3
14 changed files with 631 additions and 44 deletions

View File

@@ -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
)

View File

@@ -0,0 +1 @@
from .generator import ShippingQRCodeGenerator

View 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

View File

@@ -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)