//! HTML rendering for the printable invoice. //! //! Renders a self-contained, print-friendly HTML document. The //! frontend uses this for the in-browser invoice view, and the same //! HTML is what would be saved as a PDF. use crate::{Invoice, InvoiceTotals, LineItem}; use chrono::{DateTime, Utc}; pub fn render_html( invoice: &Invoice, lines: &[LineItem], totals: &InvoiceTotals, ) -> String { let mut out = String::with_capacity(4096); out.push_str(r#"
"#); out.push_str(&escape_html(&invoice.invoice_number)); out.push_str("
\nIssued "#); out.push_str(&invoice.issued_at.format("%d %b %Y").to_string()); out.push_str("
\nFrom
\n"); out.push_str(&escape_html(&invoice.seller_name)); out.push_str("
\n"); out.push_str(&escape_html(&invoice.seller_address)); out.push_str("
\n");
if let Some(gstin) = &invoice.seller_gstin {
out.push_str(&format!("GSTIN: {}
", escape_html(gstin)));
}
if let Some(pan) = &invoice.seller_pan {
out.push_str(&format!("PAN: {}", escape_html(pan)));
}
out.push_str("
Bill To
\n"); out.push_str(&escape_html(invoice.customer_name.as_deref().unwrap_or("N/A"))); out.push_str("
\n"); out.push_str(&escape_html( invoice.customer_billing_address.as_deref().unwrap_or("N/A"), )); out.push_str("
\n");
if let Some(gstin) = &invoice.customer_gstin {
out.push_str(&format!("GSTIN: {}
", escape_html(gstin)));
}
if let Some(state) = &invoice.place_of_supply_state {
out.push_str(&format!("Place of Supply: {}", escape_html(state)));
}
out.push_str("
| # | \nDescription | \nHSN/SAC | \nQty | \nRate | \nAmount | \n
|---|---|---|---|---|---|
| {} | \n{} | \n{} | \n{} | \n{} | \n{} | \n
\n This is a computer-generated invoice. No signature is required.\n
\n