You can start from this AI generated Codes
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
@page { size: 58mm auto; margin: 0; }
*, ::after, ::before { margin: 0; padding: 0; box-sizing: border-box; border: 0 solid; }
body {
font-family: Consolas, 'Courier New', monospace;
font-size: 11px; line-height: 1.4; color: #111;
width: 58mm; padding: 4mm 3mm;
}
@media print { body { padding: 2mm 3mm; } }
/* Header */
#header { text-align: center; margin-bottom: 3px; }
#business-logo img { max-width: 46mm; max-height: 15mm; display: block; margin: 0 auto 3px; }
#business-name { font-size: 14px; font-weight: bold; text-transform: uppercase; letter-spacing: .5px; }
#business-address { font-size: 10px; color: #555; margin-top: 1px; }
#title { font-size: 11px; font-weight: bold; text-transform: uppercase; letter-spacing: 1px; margin-top: 4px; }
#description { font-size: 10px; color: #444; margin-top: 2px; }
/* Dividers */
.div { border: none; border-top: 1px dashed #aaa; margin: 4px 0; }
.div-solid { border: none; border-top: 1px solid #111; margin: 4px 0; }
/* Fields */
#fields { font-size: 11px; margin-bottom: 2px; }
#fields .row { display: flex; justify-content: space-between; gap: 4px; padding: 1px 0; }
#fields .lbl { color: #555; flex: 1; }
#fields .val { font-weight: bold; text-align: right; }
/* Recipient */
#recipient-section { margin-bottom: 3px; text-align: center; }
#recipient-section > small { font-size: 9px; text-transform: uppercase; color: #777; letter-spacing: .4px; }
#recipient-name { font-size: 11px; font-weight: bold; }
#recipient-detail { font-size: 10px; color: #555; }
/* Items table — 3 col: Item | Qty | Total */
#items-table { width: 100%; border-collapse: collapse; font-size: 9.5px; }
#items-table th {
font-size: 10px; font-weight: bold; text-transform: uppercase; color: #444;
padding: 2px 1px; white-space: nowrap;
border-top: 1px solid #111; border-bottom: 1px solid #111;
}
#items-table td { padding: 2px 1px; vertical-align: top; text-align: right; }
#items-table tr.item-row td { border-bottom: 1px dotted #ddd; }
.col-item { text-align: left !important; word-break: break-word; line-height: 1.3; }
.col-qty { text-align: center !important; width: 18px; }
.col-total { font-weight: bold; width: 30px; }
/* Column totals */
#items-table tr.col-total td {
font-size: 10px; font-weight: bold; text-align: right;
white-space: nowrap; border-top: 1px solid #111; padding: 2px 1px;
}
/* Invoice totals */
#items-table tr.total td { font-size: 10px; white-space: nowrap; text-align: right; padding: 1px; }
#items-table tr.total td:first-child { color: #555; }
#items-table tr.total.emphasis td {
font-size: 11px; font-weight: bold;
border-top: 1px solid #111; padding-top: 3px;
}
/* Tax breakdown table */
#tax-breakdown { width: 100%; border-collapse: collapse; font-size: 9px; margin-top: 3px; }
#tax-breakdown thead th {
font-size: 9px; font-weight: bold; text-transform: uppercase; color: #777;
padding: 1px; border-bottom: 1px dashed #aaa; text-align: right;
}
#tax-breakdown thead th:first-child { text-align: left; }
#tax-breakdown td { padding: 1px; text-align: right; color: #444; }
#tax-breakdown td:first-child { text-align: left; font-weight: bold; }
/* Custom fields */
#custom-fields { margin-top: 4px; font-size: 10px; text-align: center; }
#custom-fields .cf-lbl { font-size: 9px; font-weight: bold; text-transform: uppercase; color: #777; letter-spacing: .3px; }
#custom-fields .cf-val { font-size: 10px; word-break: break-all; margin-bottom: 4px; }
#custom-fields .cf-qr { text-align: center; margin: 3px 0 5px; }
#custom-fields .cf-qr img { max-width: 35mm; width: 35mm !important; height: auto !important; image-rendering: pixelated; display: block; margin: 0 auto; }
/* Amount in words */
#amount-in-words { display: none; font-size: 10px; margin-top: 4px; border-top: 1px dashed #aaa; padding-top: 3px; text-align: center; }
#amount-in-words strong { font-size: 9px; text-transform: uppercase; color: #666; display: block; margin-bottom: 1px; }
/* Footers & bottom */
#footers { font-size: 11px; margin-top: 4px; }
#receipt-footer { text-align: center; margin-top: 6px; padding-top: 4px; border-top: 1px dashed #aaa; font-size: 9px; color: #888; }
</style>
</head>
<body>
<div id="header">
<div id="business-logo"></div>
<div id="business-name"></div>
<div id="business-address"></div>
<div id="title"></div>
<div id="description"></div>
</div>
<hr class="div-solid" />
<div id="fields"></div>
<hr class="div" />
<div id="recipient-section">
<small>Bill To</small>
<div id="recipient-name"></div>
<div id="recipient-detail"></div>
</div>
<hr class="div" />
<!-- 3-col items table -->
<table id="items-table">
<thead><tr id="table-headers"></tr></thead>
<tbody id="table-rows"></tbody>
</table>
<!-- Tax breakdown per tax code -->
<table id="tax-breakdown" style="display:none">
<thead>
<tr>
<th style="text-align:left">Tax</th>
<th>Taxable</th>
<th>Tax Amt</th>
</tr>
</thead>
<tbody id="tax-rows"></tbody>
</table>
<div id="custom-fields"></div>
<div id="amount-in-words"></div>
<div id="footers"></div>
<div id="receipt-footer">*** Thank You ***</div>
<script src="resources/writtennumber/writtennumber.js"></script>
<script>
const $ = id => document.getElementById(id);
function sendResize() {
window.parent.postMessage({
type: 'resize',
width: document.documentElement.scrollWidth + 1,
height: document.documentElement.scrollHeight + 1
}, '*');
}
function el(tag, { cls, html, text, colSpan, id, dataset } = {}) {
const e = document.createElement(tag);
if (cls) e.className = cls;
if (html) e.innerHTML = html;
if (text) e.textContent = text;
if (colSpan) e.colSpan = colSpan;
if (id) e.id = id;
if (dataset) Object.assign(e.dataset, dataset);
return e;
}
function addField(parent, label, value) {
parent.insertAdjacentHTML('beforeend',
`<div class="row"><span class="lbl">${label}</span><span class="val">${value}</span></div>`);
}
function colIdx(cols, ...labels) {
return cols.findIndex(c => labels.includes((c.label || '').toLowerCase()));
}
// ── Build tax breakdown from rows ──────────────────────────────────────
// Groups by tax code, sums taxable amount and tax amount per code
function buildTaxBreakdown(rows, fullCols) {
const taxI = colIdx(fullCols, 'tax', 'vat', 'gst');
const amtI = colIdx(fullCols, 'amount');
const taxAmtI = colIdx(fullCols, 'tax amount');
if (taxI < 0 || amtI < 0 || taxAmtI < 0) return [];
const map = new Map();
rows.forEach(row => {
const code = row.cells[taxI]?.text || '';
const amt = row.cells[amtI]?.value || 0;
const taxAmt = row.cells[taxAmtI]?.value || 0;
if (!code) return;
const entry = map.get(code) || { taxable: 0, taxAmt: 0 };
entry.taxable += amt;
entry.taxAmt += taxAmt;
map.set(code, entry);
});
return [...map.entries()].map(([code, v]) => ({
code,
taxable: v.taxable.toFixed(2),
taxAmt: v.taxAmt.toFixed(2),
}));
}
// ── Amount-in-words ────────────────────────────────────────────────────
function spellRupeeLike(n, unit, subunit, single) {
const fr = Math.round(n * 100) % 100;
return spellOutRupees(n) + ' ' + unit +
(fr === 0 ? ' Only' : fr === 1 ? ` and One ${single}` : ` and ${spellOutRupees(fr)} ${subunit}`);
}
function renderAmountInWords(aiw, totals) {
const entry = [...(totals || [])].reverse().find(t => t.key === 'Total');
if (!entry) return;
const n = entry.number;
let text;
if (aiw.currencyPrefix === '₹' || aiw.currencyCode === 'INR')
text = spellRupeeLike(n, 'Rupees', 'Paise', 'Paisa');
else if (aiw.currencyPrefix === '৳' || aiw.currencyCode === 'BDT')
text = spellRupeeLike(n, 'Taka', 'Paise', 'Paisa');
else {
const mult = 10 ** aiw.decimalPlaces;
let wn = writtenNumber(Math.floor(n), { language: aiw.language, currency: aiw.currencyCode });
if (wn) wn = wn[0].toUpperCase() + wn.slice(1);
const fr = Math.round(n * mult) % mult;
text = wn + (fr > 0 ? ` ${aiw.andText} ${fr}/${'1' + '0'.repeat(aiw.decimalPlaces)}` : '');
}
if (text) {
const div = $('amount-in-words');
div.style.display = 'block';
div.innerHTML = `<strong>${aiw.label}</strong>${text}`;
}
sendResize();
}
// ── Main ───────────────────────────────────────────────────────────────
window.addEventListener('message', ({ source, data: msg }) => {
if (source !== window.parent || msg.type !== 'context-response') return;
const d = msg.body;
document.documentElement.dir = d.direction || 'ltr';
document.title = [d.business?.name, d.title, d.reference].filter(Boolean).join(' - ');
// Logo
if (d.business?.logo) {
const img = Object.assign(document.createElement('img'), { src: d.business.logo });
img.addEventListener('load', sendResize);
$('business-logo').appendChild(img);
}
// Header
$('business-name').textContent = d.business?.name || '';
$('business-address').innerHTML = (d.business?.address || '').replace(/\n/g, '<br>');
$('title').textContent = (d.title || '').toUpperCase();
$('description').textContent = d.description || '';
// Fields
const fieldsDiv = $('fields');
fieldsDiv.innerHTML = '';
(d.fields || []).forEach(f => addField(fieldsDiv, f.label, f.text));
// Recipient
$('recipient-name').textContent = d.recipient?.name || '';
$('recipient-detail').innerHTML = [d.recipient?.code, d.recipient?.address]
.filter(Boolean).join('<br>');
// ── 3-col table: Item | Qty | Total ───────────────────────────────────
const fullCols = d.table.columns || [];
const qtyI = colIdx(fullCols, 'pce', 'qty', 'quantity');
const itemI = colIdx(fullCols, 'item', 'description', 'product');
const totalI = fullCols.findIndex(c => c.alwaysShow) !== -1
? fullCols.findIndex(c => c.alwaysShow)
: colIdx(fullCols, 'total');
const condensed = [
{ label: 'Item', cls: 'col-item', srcI: itemI !== -1 ? itemI : 1, sumText: null },
{ label: fullCols[qtyI]?.label || 'Qty', cls: 'col-qty', srcI: qtyI, sumText: fullCols[qtyI]?.sumText || null },
{ label: fullCols[totalI]?.label || 'Total', cls: 'col-total', srcI: totalI, sumText: fullCols[totalI]?.sumText || null },
];
// Headers
const thead = $('table-headers');
thead.innerHTML = '';
condensed.forEach(c => thead.appendChild(el('th', { cls: c.cls, text: c.label })));
// Rows
const tbody = $('table-rows');
tbody.innerHTML = '';
(d.table.rows || []).forEach(row => {
const tr = el('tr', { cls: 'item-row' });
condensed.forEach(col => {
tr.appendChild(el('td', {
cls: col.cls,
html: row.cells[col.srcI]?.text || ''
}));
});
tbody.appendChild(tr);
});
// Column totals
const ctTr = el('tr', { cls: 'col-total' });
let hasCT = false;
condensed.forEach(c => {
const td = el('td', { cls: c.cls });
if (c.sumText) { td.textContent = c.sumText; hasCT = true; }
ctTr.appendChild(td);
});
if (hasCT) tbody.appendChild(ctTr);
// Invoice totals — only show emphasis row (grand total), skip sub-total & tax rows
(d.table.totals || []).filter(t => t.emphasis).forEach(t => {
const tr = el('tr', { cls: 'total' + (t.emphasis ? ' emphasis' : '') });
tr.appendChild(el('td', { text: t.label, colSpan: condensed.length - 1 }));
const tdVal = el('td', { html: t.text, id: t.key || '', dataset: { value: t.number } });
if (t.class) tdVal.classList.add(t.class);
tr.appendChild(tdVal);
tbody.appendChild(tr);
});
// ── Tax breakdown ──────────────────────────────────────────────────────
const taxData = buildTaxBreakdown(d.table.rows || [], fullCols);
if (taxData.length) {
const taxTbody = $('tax-rows');
taxTbody.innerHTML = '';
taxData.forEach(({ code, taxable, taxAmt }) => {
taxTbody.insertAdjacentHTML('beforeend',
`<tr>
<td>${code}</td>
<td>${taxable}</td>
<td>${taxAmt}</td>
</tr>`);
});
$('tax-breakdown').style.display = 'table';
}
// Custom fields
const cfDiv = $('custom-fields');
cfDiv.innerHTML = '';
(d.custom_fields || []).forEach(f => {
if (f.displayAtTheTop) return addField(fieldsDiv, f.label, f.text);
const isImg = (f.text || '').includes('<img');
cfDiv.insertAdjacentHTML('beforeend',
`<div class="cf-lbl">${f.label}</div>
<div class="${isImg ? 'cf-qr' : 'cf-val'}">${
isImg ? f.text : (f.text || '').replace(/\n/g, '<br>')
}</div>`);
// Strip inline width/height from QR images so CSS can scale them properly
if (isImg) {
cfDiv.querySelectorAll('.cf-qr img').forEach(img => {
img.style.removeProperty('width');
img.style.removeProperty('height');
});
}
});
// Amount in words
if (d.amountInWords) {
const s = Object.assign(document.createElement('script'),
{ src: `resources/writtennumber/lang-${d.amountInWords.language}.js` });
s.onload = () => renderAmountInWords(d.amountInWords, d.table.totals);
document.head.appendChild(s);
}
// Footers
const footersDiv = $('footers');
footersDiv.innerHTML = '';
(d.footers || []).forEach(f => {
const div = Object.assign(document.createElement('div'), { innerHTML: f });
div.style.marginTop = '8px';
div.querySelectorAll('script').forEach(s => {
const ns = document.createElement('script');
[...s.attributes].forEach(a => ns.setAttribute(a.name, a.value));
ns.textContent = s.textContent;
s.replaceWith(ns);
});
footersDiv.appendChild(div);
});
sendResize();
});
window.addEventListener('load', () =>
window.parent.postMessage({ type: 'context-request' }, '*')
);
</script>
</body>
</html>
