"""Server-side GST invoice PDF (reportlab). INR, 18% GST.""" import io from datetime import datetime from reportlab.lib import colors from reportlab.lib.pagesizes import A4 from reportlab.lib.units import mm from reportlab.pdfgen import canvas def _inr(paise_or_rupees: int) -> str: return f"Rs. {paise_or_rupees:,.2f}" def build_invoice_pdf(invoice: dict, org: dict) -> bytes: buf = io.BytesIO() c = canvas.Canvas(buf, pagesize=A4) w, h = A4 y = h - 25 * mm # Header c.setFillColor(colors.HexColor("#0b1f4d")) c.setFont("Helvetica-Bold", 20) c.drawString(20 * mm, y, "VerifyPack") c.setFont("Helvetica", 9) c.setFillColor(colors.grey) c.drawString(20 * mm, y - 6 * mm, "Tax Invoice") c.setFont("Helvetica-Bold", 12) c.setFillColor(colors.black) c.drawRightString(w - 20 * mm, y, f"Invoice {invoice.get('invoice_number', '')}") c.setFont("Helvetica", 9) c.setFillColor(colors.grey) date = invoice.get("created_at") date_str = date.strftime("%d %b %Y") if isinstance(date, datetime) else str(date or "") c.drawRightString(w - 20 * mm, y - 6 * mm, f"Date: {date_str}") y -= 22 * mm c.setStrokeColor(colors.HexColor("#e2e8f0")) c.line(20 * mm, y, w - 20 * mm, y) # Billed to y -= 10 * mm c.setFillColor(colors.black) c.setFont("Helvetica-Bold", 10) c.drawString(20 * mm, y, "Billed To") c.setFont("Helvetica", 9) c.drawString(20 * mm, y - 5 * mm, org.get("name", "")) if org.get("gstin"): c.drawString(20 * mm, y - 10 * mm, f"GSTIN: {org['gstin']}") if org.get("address"): c.drawString(20 * mm, y - 15 * mm, org["address"][:60]) c.setFont("Helvetica-Bold", 10) c.drawRightString(w - 20 * mm, y, "From") c.setFont("Helvetica", 9) c.drawRightString(w - 20 * mm, y - 5 * mm, "VerifyPack Technologies") c.drawRightString(w - 20 * mm, y - 10 * mm, "Bengaluru, Karnataka, India") # Line items table y -= 30 * mm c.setFillColor(colors.HexColor("#f1f5f9")) c.rect(20 * mm, y, w - 40 * mm, 8 * mm, fill=1, stroke=0) c.setFillColor(colors.HexColor("#334155")) c.setFont("Helvetica-Bold", 9) c.drawString(23 * mm, y + 2.5 * mm, "Description") c.drawRightString(w - 23 * mm, y + 2.5 * mm, "Amount") subtotal = invoice.get("subtotal", 0) gst = invoice.get("gst_amount", 0) total = invoice.get("total", 0) y -= 10 * mm c.setFillColor(colors.black) c.setFont("Helvetica", 9) period = invoice.get("period", "") c.drawString(23 * mm, y, f"{invoice.get('plan_name', 'Plan')} subscription {period}".strip()) c.drawRightString(w - 23 * mm, y, _inr(subtotal)) # Totals y -= 14 * mm c.line(w - 80 * mm, y + 6 * mm, w - 20 * mm, y + 6 * mm) c.setFont("Helvetica", 9) c.drawRightString(w - 55 * mm, y, "Subtotal") c.drawRightString(w - 23 * mm, y, _inr(subtotal)) y -= 6 * mm c.drawRightString(w - 55 * mm, y, "GST (18%)") c.drawRightString(w - 23 * mm, y, _inr(gst)) y -= 8 * mm c.setFont("Helvetica-Bold", 11) c.drawRightString(w - 55 * mm, y, "Total") c.drawRightString(w - 23 * mm, y, _inr(total)) # Footer c.setFont("Helvetica", 8) c.setFillColor(colors.grey) c.drawString(20 * mm, 20 * mm, f"Place of supply: {invoice.get('place_of_supply', 'Karnataka')}") c.drawString(20 * mm, 15 * mm, "This is a computer-generated invoice and does not require a signature.") c.drawRightString(w - 20 * mm, 15 * mm, f"Status: {str(invoice.get('status', '')).upper()}") c.showPage() c.save() return buf.getvalue()