"""Order confirmation PDF generation (reportlab).""" from __future__ import annotations from datetime import datetime from io import BytesIO from app.models.client_access import ClientAccount from app.models.ordering import Order class OrderPdfUnavailableError(RuntimeError): pass def _fmt_money(value: float | None) -> str: return "Quote" if value is None else f"${value:,.2f}" def build_order_confirmation_pdf(order: Order, customer: ClientAccount | None) -> bytes: try: from reportlab.lib import colors from reportlab.lib.pagesizes import A4 from reportlab.pdfgen import canvas except ModuleNotFoundError as exc: # pragma: no cover raise OrderPdfUnavailableError( "PDF generation is unavailable because 'reportlab' is not installed." ) from exc buffer = BytesIO() page_width, page_height = A4 pdf = canvas.Canvas(buffer, pagesize=A4) margin = 40 y = page_height - margin pdf.setFont("Helvetica-Bold", 18) pdf.drawString(margin, y, "Order Confirmation") y -= 24 pdf.setFont("Helvetica", 10) order_ref = order.order_number or f"#{order.id}" pdf.drawString(margin, y, f"Order: {order_ref}") pdf.drawRightString(page_width - margin, y, datetime.utcnow().strftime("%d %b %Y %H:%M UTC")) y -= 14 pdf.drawString(margin, y, f"Customer: {customer.name if customer else order.client_account_id}") y -= 14 pdf.drawString(margin, y, f"Status: {order.status.replace('_', ' ').title()}") y -= 14 if order.purchase_order_number: pdf.drawString(margin, y, f"PO Number: {order.purchase_order_number}") y -= 14 pdf.drawString(margin, y, f"Fulfilment: {order.fulfilment_method.title()}") y -= 14 if order.requested_delivery_date: pdf.drawString(margin, y, f"Requested date: {order.requested_delivery_date.strftime('%d %b %Y')}") y -= 14 y -= 6 # Table header pdf.setFont("Helvetica-Bold", 9) pdf.setFillColor(colors.HexColor("#22362d")) pdf.drawString(margin, y, "Product") pdf.drawString(margin + 220, y, "SKU") pdf.drawRightString(margin + 330, y, "Qty") pdf.drawRightString(margin + 420, y, "Unit (ex GST)") pdf.drawRightString(page_width - margin, y, "Line total") y -= 6 pdf.setStrokeColor(colors.HexColor("#cccccc")) pdf.line(margin, y, page_width - margin, y) y -= 14 pdf.setFont("Helvetica", 9) pdf.setFillColor(colors.black) for line in order.lines: if y < margin + 60: pdf.showPage() y = page_height - margin pdf.setFont("Helvetica", 9) unit = line.admin_override_price if line.admin_override_price is not None else line.unit_price line_total = None if unit is None else round(unit * line.quantity, 2) pdf.drawString(margin, y, (line.product_name or "")[:38]) pdf.drawString(margin + 220, y, (line.product_sku or "")[:16]) pdf.drawRightString(margin + 330, y, f"{line.quantity:g}") pdf.drawRightString(margin + 420, y, _fmt_money(unit)) pdf.drawRightString(page_width - margin, y, _fmt_money(line_total)) y -= 14 y -= 4 pdf.line(margin, y, page_width - margin, y) y -= 16 pdf.setFont("Helvetica-Bold", 10) pdf.drawRightString(margin + 420, y, "Subtotal (ex GST)") pdf.drawRightString(page_width - margin, y, _fmt_money(order.subtotal_ex_gst)) y -= 20 if order.requires_quote: pdf.setFont("Helvetica-Oblique", 9) pdf.setFillColor(colors.HexColor("#8a5a00")) pdf.drawString(margin, y, "This order contains quote-only items. Final pricing will be confirmed by our team.") y -= 14 if order.delivery_notes: pdf.setFont("Helvetica", 9) pdf.setFillColor(colors.black) pdf.drawString(margin, y, f"Notes: {order.delivery_notes[:90]}") pdf.setFont("Helvetica-Oblique", 8) pdf.setFillColor(colors.HexColor("#888888")) pdf.drawString(margin, margin - 12, "Prices exclude GST. This is an order confirmation, not a tax invoice.") pdf.showPage() pdf.save() return buffer.getvalue()