103 lines
3.5 KiB
Python
103 lines
3.5 KiB
Python
"""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()
|