from io import BytesIO from typing import List, Dict, Optional from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload, joinedload from weasyprint import HTML, CSS from constants import DEAL_STATUS_STR, ENV, APP_PATH from generators.deal_pdf_generator.deal_data import DealTechSpecProductData, DealTechSpecData from models import Deal, DealProduct, DealService as DealServiceModel, Product from utils.images_fetcher import fetch_images class DealTechSpecPdfGenerator: def __init__(self, session: AsyncSession): self._session = session @staticmethod async def _group_deal_products_by_products(deal_products: List[DealProduct]) -> Dict[str, DealTechSpecProductData]: products: Dict[str, DealTechSpecProductData] = {} additional_info: Optional[str] for deal_product in deal_products: # Для группировки по артикулу и услугам key = f"{deal_product.product.article} - " + ",".join( str(service.service_id) for service in deal_product.services ) if key not in products: products[key] = { "deal_products": [deal_product], "quantity": deal_product.quantity, "additional_info": deal_product.product.additional_info, } else: products[key]["deal_products"].append(deal_product) products[key]["quantity"] += deal_product.quantity if not products[key]["additional_info"]: products[key]["additional_info"] = deal_product.product.additional_info return products async def _get_deal_by_id(self, deal_id: int) -> Optional[Deal]: deal: Deal | None = await self._session.scalar( select(Deal) .where(Deal.id == deal_id) .options( selectinload(Deal.products).selectinload(DealProduct.services), selectinload(Deal.products).selectinload(DealProduct.product).selectinload(Product.barcodes), selectinload(Deal.services).selectinload(DealServiceModel.service), selectinload(Deal.status_history), joinedload(Deal.client), joinedload(Deal.shipping_warehouse), ) ) return deal async def _create_deal_tech_spec_document_html(self, deal_id: int): deal = await self._get_deal_by_id(deal_id) if not deal: return "" products = await self._group_deal_products_by_products(deal.products) product_urls: List[Optional[str]] = [] for product in products.values(): if len(product["deal_products"][0].product.images) > 0: product_urls.append(product["deal_products"][0].product.images[0].image_url) else: product_urls.append(None) product_images = await fetch_images(product_urls) document_deal_data: DealTechSpecData = { "deal": deal, "products": products, "current_status_str": DEAL_STATUS_STR[deal.current_status], "last_status": max(deal.status_history, key=lambda status: status.changed_at), "product_images": product_images, } template = ENV.get_template("deal/deal-tech-spec.html") result = template.render({"data": document_deal_data, "sign_place_text": "_" * 22}) return result async def create_deal_tech_spec_pdf(self, deal_id) -> BytesIO: doc = await self._create_deal_tech_spec_document_html(deal_id) pdf_file = BytesIO() HTML(string=doc).write_pdf(pdf_file, stylesheets=[CSS(APP_PATH + '/static/css/deal-tech-spec.css')]) return pdf_file