feat: residues accounting
This commit is contained in:
		
							
								
								
									
										1
									
								
								generators/residual_qr_code_generator/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								generators/residual_qr_code_generator/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
from .generator import ResidualQRCodeGenerator
 | 
			
		||||
							
								
								
									
										166
									
								
								generators/residual_qr_code_generator/generator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								generators/residual_qr_code_generator/generator.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,166 @@
 | 
			
		||||
from io import BytesIO
 | 
			
		||||
from typing import Optional
 | 
			
		||||
 | 
			
		||||
from reportlab.lib.units import mm
 | 
			
		||||
from reportlab.pdfgen.canvas import Canvas
 | 
			
		||||
from reportlab.platypus import Paragraph, SimpleDocTemplate, PageBreak, Frame
 | 
			
		||||
from reportlab_qrcode import QRCodeImage
 | 
			
		||||
from sqlalchemy import select
 | 
			
		||||
from sqlalchemy.orm import selectinload, joinedload
 | 
			
		||||
 | 
			
		||||
from barcodes.pdf.pdf_maker import PdfMaker
 | 
			
		||||
from generators.base_pdf_card_generator.base_pdf_card_generator import BasePdfCardGenerator
 | 
			
		||||
from models import Client, ResidualPallet, ResidualBox
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ResidualQRCodeGenerator(BasePdfCardGenerator):
 | 
			
		||||
    async def _get_client_by_id(self, client_id: int) -> Optional[Client]:
 | 
			
		||||
        stmt = (
 | 
			
		||||
            select(Client)
 | 
			
		||||
            .where(Client.id == client_id)
 | 
			
		||||
            .options(
 | 
			
		||||
                selectinload(Client.boxes),
 | 
			
		||||
                selectinload(Client.pallets)
 | 
			
		||||
                .selectinload(ResidualPallet.boxes),
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        client = (await self._session.execute(stmt)).one_or_none()
 | 
			
		||||
        return client[0] if client else None
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _split_string(string: str) -> list[int]:
 | 
			
		||||
        if not string:
 | 
			
		||||
            return []
 | 
			
		||||
        return [int(item) for item in string.split(",")]
 | 
			
		||||
 | 
			
		||||
    async def generate(self, pallet_ids_str: str, box_ids_str: str):
 | 
			
		||||
        pallet_ids = self._split_string(pallet_ids_str)
 | 
			
		||||
        box_ids = self._split_string(box_ids_str)
 | 
			
		||||
 | 
			
		||||
        pallets_buffer = await self.generate_pallets(pallet_ids)
 | 
			
		||||
        boxes_buffer = await self.generate_boxes(box_ids)
 | 
			
		||||
        return self._merge_pdfs([pallets_buffer, boxes_buffer])
 | 
			
		||||
 | 
			
		||||
    async def _get_pallets(self, pallet_ids: list[int]) -> list[ResidualPallet]:
 | 
			
		||||
        stmt = (
 | 
			
		||||
            select(ResidualPallet)
 | 
			
		||||
            .options(
 | 
			
		||||
                joinedload(ResidualPallet.client),
 | 
			
		||||
            )
 | 
			
		||||
            .where(ResidualPallet.id.in_(pallet_ids))
 | 
			
		||||
            .order_by(ResidualPallet.id.asc())
 | 
			
		||||
        )
 | 
			
		||||
        pallets = await self._session.execute(stmt)
 | 
			
		||||
        return list(pallets.unique().scalars().all())
 | 
			
		||||
 | 
			
		||||
    def _generate_empty_doc(self) -> BytesIO:
 | 
			
		||||
        buffer = BytesIO()
 | 
			
		||||
        doc: SimpleDocTemplate = self._create_doc(buffer)
 | 
			
		||||
        doc.build([])
 | 
			
		||||
        buffer.seek(0)
 | 
			
		||||
        return buffer
 | 
			
		||||
 | 
			
		||||
    async def generate_pallets(self, pallet_ids: list[int]) -> BytesIO:
 | 
			
		||||
        if not pallet_ids:
 | 
			
		||||
            return self._generate_empty_doc()
 | 
			
		||||
 | 
			
		||||
        buffer = BytesIO()
 | 
			
		||||
        doc: SimpleDocTemplate = self._create_doc(buffer)
 | 
			
		||||
 | 
			
		||||
        pallet_idx = 0
 | 
			
		||||
        pallets = await self._get_pallets(pallet_ids)
 | 
			
		||||
        client = pallets[0].client
 | 
			
		||||
 | 
			
		||||
        def on_page(canvas: Canvas, _):
 | 
			
		||||
            nonlocal pallet_idx, pallets
 | 
			
		||||
            pallet_id = pallets[pallet_idx].id
 | 
			
		||||
 | 
			
		||||
            qr = QRCodeImage(f"П{pallet_id}", size=30 * mm)
 | 
			
		||||
            qr.drawOn(canvas, 0, 30)
 | 
			
		||||
 | 
			
		||||
            object_name = Paragraph(f"Паллет", self.small_centered_style)
 | 
			
		||||
            pallet_id = Paragraph(f"ID: П{pallet_id}", self.small_centered_style)
 | 
			
		||||
 | 
			
		||||
            frame = Frame(x1=28 * mm, y1=3 * mm, width=30 * mm, height=30 * mm)
 | 
			
		||||
            frame.addFromList([object_name, pallet_id], canvas)
 | 
			
		||||
 | 
			
		||||
            client_name = Paragraph(f"Клиент: {client.name}", self.small_centered_style)
 | 
			
		||||
            frame = Frame(x1=0 * mm, y1=-7 * mm, width=58 * mm, height=20 * mm)
 | 
			
		||||
            frame.addFromList([client_name], canvas)
 | 
			
		||||
 | 
			
		||||
            pallet_idx += 1
 | 
			
		||||
 | 
			
		||||
        elements = []
 | 
			
		||||
        for _ in range(len(pallets)):
 | 
			
		||||
            elements.append(Paragraph("", self.medium_style))
 | 
			
		||||
            elements.append(PageBreak())
 | 
			
		||||
 | 
			
		||||
        doc.build(elements, on_page, on_page)
 | 
			
		||||
 | 
			
		||||
        buffer.seek(0)
 | 
			
		||||
        return buffer
 | 
			
		||||
 | 
			
		||||
    async def _get_boxes(self, box_ids: list[int]) -> list[ResidualBox]:
 | 
			
		||||
        stmt = (
 | 
			
		||||
            select(ResidualBox)
 | 
			
		||||
            .options(
 | 
			
		||||
                joinedload(ResidualBox.client),
 | 
			
		||||
                selectinload(ResidualBox.pallet)
 | 
			
		||||
                .joinedload(ResidualPallet.client),
 | 
			
		||||
            )
 | 
			
		||||
            .where(ResidualBox.id.in_(box_ids))
 | 
			
		||||
            .order_by(ResidualBox.id.asc())
 | 
			
		||||
        )
 | 
			
		||||
        boxes = await self._session.execute(stmt)
 | 
			
		||||
        return list(boxes.unique().scalars().all())
 | 
			
		||||
 | 
			
		||||
    async def generate_boxes(self, box_ids: list[int]) -> BytesIO:
 | 
			
		||||
        if not box_ids:
 | 
			
		||||
            return self._generate_empty_doc()
 | 
			
		||||
 | 
			
		||||
        buffer = BytesIO()
 | 
			
		||||
        doc: SimpleDocTemplate = self._create_doc(buffer)
 | 
			
		||||
 | 
			
		||||
        box_idx = 0
 | 
			
		||||
        boxes = await self._get_boxes(box_ids)
 | 
			
		||||
        client = boxes[0].client or boxes[0].pallet.client
 | 
			
		||||
 | 
			
		||||
        def on_page(canvas: Canvas, _):
 | 
			
		||||
            nonlocal box_idx
 | 
			
		||||
            box_id = boxes[box_idx].id
 | 
			
		||||
 | 
			
		||||
            qr = QRCodeImage(f"П{box_id}", size=30 * mm)
 | 
			
		||||
            qr.drawOn(canvas, 0, 30)
 | 
			
		||||
 | 
			
		||||
            box_info = [
 | 
			
		||||
                Paragraph("Короб", self.small_centered_style),
 | 
			
		||||
                Paragraph(f"ID: К{box_id}", self.small_centered_style),
 | 
			
		||||
            ]
 | 
			
		||||
            if boxes[box_idx].pallet_id:
 | 
			
		||||
                box_info.append(Paragraph("На паллете", self.small_centered_style))
 | 
			
		||||
                box_info.append(Paragraph(f"ID: П{boxes[box_idx].pallet_id}", self.small_centered_style))
 | 
			
		||||
 | 
			
		||||
            frame = Frame(x1=28 * mm, y1=8 * mm, width=30 * mm, height=30 * mm)
 | 
			
		||||
            frame.addFromList(box_info, canvas)
 | 
			
		||||
 | 
			
		||||
            client_name = Paragraph(f"Клиент: {client.name}", self.small_centered_style)
 | 
			
		||||
            frame = Frame(x1=0 * mm, y1=-7 * mm, width=58 * mm, height=20 * mm)
 | 
			
		||||
            frame.addFromList([client_name], canvas)
 | 
			
		||||
 | 
			
		||||
            box_idx += 1
 | 
			
		||||
 | 
			
		||||
        elements = []
 | 
			
		||||
        for _ in range(len(boxes)):
 | 
			
		||||
            elements.append(Paragraph("", self.medium_style))
 | 
			
		||||
            elements.append(PageBreak())
 | 
			
		||||
 | 
			
		||||
        doc.build(elements, on_page, on_page)
 | 
			
		||||
 | 
			
		||||
        buffer.seek(0)
 | 
			
		||||
        return buffer
 | 
			
		||||
 | 
			
		||||
    def _merge_pdfs(self, buffers: list[BytesIO]) -> BytesIO:
 | 
			
		||||
        pdf_maker = PdfMaker((self.page_width, self.page_height))
 | 
			
		||||
        for buffer in buffers:
 | 
			
		||||
            pdf_maker.add_pdfs(buffer)
 | 
			
		||||
        return pdf_maker.get_bytes()
 | 
			
		||||
		Reference in New Issue
	
	Block a user