New themes issues - Megathread

Thank you and appreciate @Mabaega for your immediate help, it works.

1 Like

Batch view problems

I understand what you mean.

Update to ZatcaEGS v25.06.27.0003.
Let’s give it a try…

1 Like

@AYUB please check the latest version (25.6.28.2459) to see if your issue with scrollbars has been resolved.

how do I know if it is updated in manager or how can I update manager to get ZatcaEGS v25.06.27.0003

Create new business data, and integrate it to Non Production environment. I want to make sure this really works. I am preparing a way to auto update all customfields for business data that is already integrated with previous version of zatcaegs.

1 Like

Why the issue with printing (in new update) is not being solved so far. It has been many days. Previously i used to select only the printer (80 mm or A5) while printing the receipt or invoice and it used to print perfectly. But now this is not working. Even the invoices are not being printed on A5 properly.

@lubos @Mabaega

No QR code appears in credit notes for Zatca Phase 1 and Phase 2.

@lubos Thanks, its ok now no scroll bar is showing on view screen

1 Like

As you can see, the footer is getting split into 2 pages. No matter what i do -scale down, reduce margins, etc. i end up with the same result. How can I get all this into a single page.

P.S.: Its a custom theme

There will be soon new version of theme enhancer which will be able to design templates with proper headers and footer.

4 Likes

Please keep in mind the Solution for Top Header (Logo, Business Details, All Field, Client Details, Description) Repeat on all pages.

@AlFalahAccounting

If you want to use a custom theme, you might get some ideas from this example.
Not so good, but it seems to fit what you want

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <style>
    *, ::after, ::before, ::backdrop, ::file-selector-button {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      border: 0 solid;
    }

    body {
      margin: 0;
      padding: 0;
      font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
      color: #171717;
      font-size: 12px;
      line-height: 1.428571429;
    }

    table {
      font-size: 12px;
      width: 100%;
      border-collapse: collapse;
    }

    .table-default {
      margin-bottom: 10px;
    }

    #main-items-table {
      margin-top: 10px;
      margin-bottom: 0px;
    }

    tr#table-headers th {
      font-weight: bold;
      padding: 5px 10px;
      border: 1px solid #000;
      text-align: start;
    }

    tbody#table-rows td {
      padding: 5px 10px;
      border-left: 1px solid #000;
      border-right: 1px solid #000;
      text-align: start;
      vertical-align: top;
    }

    tfoot#dummy-footer-row td {
      padding-bottom: 0;
      border-left: 1px solid #000;
      border-right: 1px solid #000;
      border-bottom: 1px solid #000;
      height: 0.5em;
      background: #fff;
    }

    .totals-table {
      border-collapse: collapse;
      width: 100%;
      margin-top: -1px;
      border: none;
    }

    .totals-table tr td,
    .totals-table tr th {
      border: none !important;
    }

    .totals-table td,
    .totals-table th {
      padding: 5px 10px;
      font-size: 12px;
      text-align: end;
    }

    .totals-table .label-cell {
      text-align: end;
      border: none !important;
    }

    .totals-table .col-total {
      width: 100px;
      white-space: nowrap;
      border: 1px solid #000 !important;
      background: #fff;
      text-align: end;
    }

    .totals-table tr:first-child td,
    .totals-table tr:first-child th {
      border-top: 1px solid #000 !important;
    }

    #title {
      font-size: 32px;
      line-height: 32px;
      font-weight: bold;
    }

    .text-end {
      text-align: end;
    }

    .text-start {
      text-align: start;
    }

    .text-center {
      text-align: center;
    }

    .vertical-align-top {
      vertical-align: top;
    }

    .spacer-20 {
      width: 20px;
    }

    .divider {
      width: 1px;
      border-left: 1px solid #000;
    }

    @media screen {
      .screen-padding {
        padding: 30px;
      }
    }
    
    @media print {
      @page {
        size: A4;
        margin: 30px;
      }
      .screen-padding {
        padding: 0px;
      }
    }

  </style>
</head>
<body>
  <div class="screen-padding">
    <table id="main-items-table">
      <thead>
        <tr><td colspan="100%">
            <table class="table-default">
              <tbody>
                <tr>
                  <td class="vertical-align-top">
                    <div id="title">Invoice</div>
                  </td>
                  <td class="text-end" id="business-logo"></td>
                </tr>
              </tbody>
            </table>
        </td></tr>
        <tr><td colspan="100%">
            <table class="table-default">
              <tbody>
                <tr>
                  <td class="vertical-align-top" id="recipient-info"></td>
                  <td class="text-end vertical-align-top" id="fields"></td>
                  <td class="spacer-20"></td>
                  <td class="divider"></td>
                  <td class="spacer-20"></td>
                  <td class="vertical-align-top" id="business-info"></td>
                </tr>
              </tbody>
            </table>
        </td></tr>
        <tr><td colspan="100%">
            <table class="table-default">
              <tr>
                <td id="description" style="font-weight: bold; font-size: 14px;"></td>
              </tr>
            </table>
        </td></tr>
        <tr id="table-headers"></tr>
      </thead>
      <tbody id="table-rows"></tbody>
      <tfoot id="dummy-footer-row"></tfoot>
    </table>

    <table class="totals-table" id="totals-table"></table>

    <table class="table-default">
      <tr>
        <td id="qrcode" class="text-start" style="width: 30%;"></td>
        <td id="status" class="text-center" style="width: 40%;"></td>
        <td id="tdnone" class="text-end" style="width: 30%;"></td>
      </tr>
    </table>

    <table class="table-default"><tr><td><div id="custom-fields"></div></td></tr></table>
    <table class="table-default"><tr><td><div id="footers"></div></td></tr></table>
    
    <script src="resources/qrcode/qrcode.js"></script>
    
	<script>
	window.addEventListener("message", (event) => {
		if (event.source !== window.parent) return;
		if (event.data.type !== 'context-response') return;
		const data = event.data.body;
		
		// Log seluruh data JSON ke console
		console.log("Received JSON data:", data);
		
		document.documentElement.dir = data.direction;
		document.getElementById("title").innerHTML = data.title || "No title";
		document.getElementById("description").innerHTML = data.description || "";
		// Business logo
		var businessLogoTd = document.getElementById("business-logo");
		if (data.business.logo) {
			const img = document.createElement("img");
			img.src = data.business.logo;
			img.style = "max-height: 150px; max-width: 300px; display: inline";
			businessLogoTd.appendChild(img);
		}
		// Business info
		const business = data.business || {};
		document.getElementById("business-info").innerHTML = `<strong>${business.name || ""}</strong><br>${business.address ? business.address.replace(/\n/g, "<br>") : ""}`;
		const recipient = data.recipient || {};
		document.getElementById("recipient-info").innerHTML = `<strong>${recipient.name || ""}</strong><br>${recipient.address ? recipient.address.replace(/\n/g, "<br>") : ""}`;
		
		const fieldsDiv = document.getElementById("fields");
        fieldsDiv.innerHTML = "";
        (data.fields || []).forEach(f => {
        	const div = document.createElement("div");
        	div.style.marginBottom = "0.3rem";
        	div.innerHTML = `
        		<div><strong>${f.label}</strong></div>
        		<div>${f.text || ""}</div>
        	`;
        	fieldsDiv.appendChild(div);
        });
        
        
        // Cari text terpanjang dari total.text
        function getTextWidth(text, font = "12px Helvetica, Arial, sans-serif") {
          const canvas = document.createElement("canvas");
          const ctx = canvas.getContext("2d");
          ctx.font = font;
          return ctx.measureText(text).width;
        }

        const totalTexts = (data.table && data.table.totals || []).map(t => t.text || "");
        const longestText = totalTexts.reduce((a, b) => (a.length > b.length ? a : b), "");
        
        const estimatedWidth = getTextWidth(longestText) + 20 + 4;
        //console.log(longestText);

        const widthPx = estimatedWidth + "px";
        
        //console.log(widthPx);

		const headersRow = document.getElementById("table-headers");
        headersRow.innerHTML = "";
        
        const columns = data.table.columns || [];
        
        columns.forEach((col, idx) => {
            const th = document.createElement("th");
            th.innerHTML = col.label;
            th.style.textAlign = col.align;
        
            const isLast = idx === columns.length - 1;
        
            if (isLast) {
                // Set lebar tetap untuk kolom terakhir
                th.style.width = widthPx;
                th.style.minWidth = widthPx;
                th.style.maxWidth = widthPx;
                th.style.whiteSpace = "nowrap";
            } else if (col.minWidth) {
                th.style.whiteSpace = 'nowrap';
                th.style.width = '1px';
            } else if (col.nowrap) {
                th.style.whiteSpace = 'nowrap';
                th.style.width = '60px';
            }
        
            headersRow.appendChild(th);
        });

		
		const rowsBody = document.getElementById("table-rows");
		rowsBody.innerHTML = "";
		(data.table.rows || []).forEach(row => {
			const tr = document.createElement("tr");
			row.cells.forEach((cell, i) => {
				var col = data.table.columns[i];
				const td = document.createElement("td");
				td.innerHTML = (cell.text || "").split("\n").join("<br />");
				td.style.textAlign = col.align;
				if (col.minWidth) {
					td.style.whiteSpace = 'nowrap';
					//td.style.width = '1px';
				}
				else if (col.nowrap) {
					td.style.whiteSpace = 'nowrap';
					//td.style.width = '60px';
				}
				tr.appendChild(td);
			});
			rowsBody.appendChild(tr);
		});
		
		// --- DUMMY FOOTER ROW FOR REPEATED TABLE FOOTER ---
		const tfoot = document.getElementById("dummy-footer-row");
		tfoot.innerHTML = "";
		const dummyTr = document.createElement("tr");
		for (let i = 0; i < data.table.columns.length; i++) {
			const td = document.createElement("td"); // dummy, blank
			dummyTr.appendChild(td);
		}
		tfoot.appendChild(dummyTr);
		// ---
		
		const totalsTable = document.getElementById("totals-table");
        totalsTable.innerHTML = '';
        const mainCols = data.table.columns;
        const colCount = mainCols.length;
        const totalRows = data.table.totals || [];
        
       // Cari custom field 'Total amount in words' (bahasa Inggris atau Arab)
        const amountInWordsField = (data.custom_fields || []).find(f =>
            f.label === "Total amount in words" || f.label === "إجمالي القيمة بالأحرف"
        );


        let tdblank;
        
        totalRows.forEach((total, idx) => {
            const tr = document.createElement("tr");
        
            if (idx === 0) {
                tdblank = document.createElement("td");
                tdblank.className = 'label-cell';
                tdblank.rowSpan = totalRows.length + 1;
                tdblank.colSpan = colCount - 3;
                tdblank.style.textAlign = 'start';
                tdblank.style.verticalAlign = 'top';
                tdblank.style.borderTop = '1px solid #000';
                tdblank.style.padding = '10px';
                tdblank.style.paddingInlineStart = '0px';
        
                if (amountInWordsField) {
                    tdblank.innerHTML = `<strong>${amountInWordsField.label}</strong><br />${(amountInWordsField.text || "").split("\n").join("<br />")}`;
                } else {
                    tdblank.innerHTML = '&nbsp;';
                }
        
                tr.appendChild(tdblank);
            }
        
            // Label cell
            const tdLabel = document.createElement("td");
            tdLabel.className = 'label-cell';
            tdLabel.colSpan = 2;
            tdLabel.style.textAlign = 'end';
            tdLabel.innerHTML = total.label;
            if (idx === 0) tdLabel.style.borderTop = '1px solid #000';
            tr.appendChild(tdLabel);
        
            // Amount cell
            const totalTd = document.createElement("td");
            totalTd.className = 'col-total';
            totalTd.style.width = widthPx;
            totalTd.style.minWidth = widthPx;
            totalTd.style.maxWidth = widthPx;
            totalTd.id = total.key;
            totalTd.style.textAlign = 'right';
            totalTd.innerHTML = total.text;
            if (total.class) totalTd.classList.add(total.class);
            totalTd.dataset.value = total.number;
            if (total.emphasis) totalTd.style.fontWeight = 'bold';
            totalTd.style.border = '1px solid #000';
            if (idx === 0) totalTd.style.borderTop = '1px solid #000';
            tr.appendChild(totalTd);
        
            totalsTable.appendChild(tr);
        });
        
        // Tambahkan baris kosong tambahan (tanpa border) setelah total rows
        const emptyRow = document.createElement("tr");
        
        const emptyLabel = document.createElement("td");
        emptyLabel.colSpan = 2;
        emptyLabel.innerHTML = "&nbsp;";
        emptyLabel.style.border = "none";
        emptyRow.appendChild(emptyLabel);
        
        const emptyValue = document.createElement("td");
        emptyValue.innerHTML = "&nbsp;";
        emptyValue.style.border = "none";
        emptyRow.appendChild(emptyValue);
        
        totalsTable.appendChild(emptyRow);
        
        
        // --- Tampilkan custom field lain selain 'Total amount in words'
        const customFieldsDiv = document.getElementById("custom-fields");
        customFieldsDiv.innerHTML = "";
        
        (data.custom_fields || []).forEach(f => {
            
            //for zatca qrcode phase II
            if (f.key === "a1b2c3d4-e5f6-4abc-8def-abcdef000018") return;
            
            if (f.label === "Total amount in words" || f.label === "إجمالي القيمة بالأحرف") return;
        
            const text = (f.text || "").trim();
            if (text === "") return; // skip jika kosong atau hanya spasi
        
            const div = document.createElement("div");
            div.style.wordWrap = "break-word";       // Allow break inside long words
            div.style.whiteSpace = "normal";         // Allow wrapping
            div.style.overflowWrap = "break-word";   // Modern browsers
            div.style.maxWidth = "100%";             // Optional: limit width to container
            
            if (f.displayAtTheTop === true) {
                div.style.marginBottom = "0.3rem";
                div.innerHTML = `
                    <div><strong>${f.label}</strong></div>
                    <div>${(f.text || "").split("\n").join("<br />")}</div>
                `;
                fieldsDiv.appendChild(div);
            } else {
                div.innerHTML = `<strong>${f.label}</strong><br />${(f.text || "").split("\n").join("<br />")}<br /><br />`;
                customFieldsDiv.appendChild(div);
            }
        });
        
        //remove last div margin from fields cells
        if (fieldsDiv.lastElementChild) {
        	fieldsDiv.lastElementChild.style.marginBottom = "0rem";
        }


		const footersDiv = document.getElementById("footers");
		footersDiv.innerHTML = "";
		(data.footers || []).forEach(f => {
			const div = document.createElement("div");
			div.style = 'margin-top: 20px';
			div.innerHTML = f;
			footersDiv.appendChild(div);
			const scripts = div.querySelectorAll("script");
			scripts.forEach(script => {
				const newScript = document.createElement("script");
				for (const attr of script.attributes) {
					newScript.setAttribute(attr.name, attr.value);
				}
				if (script.textContent) {
					newScript.textContent = script.textContent;
				}
				script.parentNode.replaceChild(newScript, script);
			});
		});
		const statusDiv = document.getElementById("status");
		if (data.emphasis?.text != null) {
			statusDiv.style.marginTop = '40px';
			const span = document.createElement("span");
			span.style = 'border-width: 5px; border-color: #FF0000; border-style: solid; padding: 10px; font-size: 20px; text-transform: uppercase';
			if (data.emphasis.positive) {
				span.style.color = 'green';
				span.style.borderColor = 'green';
			}
			if (data.emphasis.negative) {
				span.style.color = 'red';
				span.style.borderColor = 'red';
			}
			span.innerHTML = data.emphasis.text;
			statusDiv.appendChild(span);
		}
		
		//for zatca qrcode phase II
        const qrcodetext = (data.custom_fields || [])
        .find(f => f.key === "a1b2c3d4-e5f6-4abc-8def-abcdef000018")?.text || "";
  
		const qrcodeDiv = document.getElementById('qrcode');
            // This handles QR code Phase II on invoices for Saudi Arabia
            if (qrcodetext && qrcodetext.length > 10 && qrcodeDiv) {
            	qrcodeDiv.innerHTML = '';
            	renderQRCode(qrcodetext, 'qrcode'); 
			} else {
				// This handles QR code on invoices for Saudi Arabia - this is here just temporarily. Better way will be implemented.
    			if (data.type === 'salesinvoice') {
    				if (data.business.country === 'ar-SA' || data.business.country === 'en-SA') {
    
    					function appendTLV(tag, text, byteList) {
    						const encoded = new TextEncoder().encode(text);
    						byteList.push(tag);
    						byteList.push(encoded.length);
    						for (let b of encoded) byteList.push(b);
    					}
    
    					const byteList = [];
    
    					let businessName = 'No name';
    					if (data.business) businessName = data.business.name;
    
    					let vatNumber = '0000000000000';
    					let vatField = data.business.custom_fields.find(item => item.key === 'd96d97e8-c857-42c6-8360-443c06a13de9');
    					if (vatField) vatNumber = vatField.text;
    
    					let timestamp = new Date((data.timestamp - 621355968000000000) / 10000).toISOString();
    
    					let total = 0;
    					let totalElement = document.getElementById('Total');
    					if (totalElement != null) total = parseFloat(totalElement.getAttribute('data-value'));
    
    					let vat = 0;
    					let taxAmounts = document.getElementsByClassName('taxAmount');
    					for (let i = 0; i < taxAmounts.length; i++) {
    						vat += parseFloat(taxAmounts[i].getAttribute('data-value'));
    					}
    
    					appendTLV(1, businessName, byteList);
    					appendTLV(2, vatNumber, byteList);
    					appendTLV(3, timestamp, byteList);
    					appendTLV(4, total.toFixed(2), byteList);
    					appendTLV(5, vat.toFixed(2), byteList);
    
    					// Convert to Uint8Array
    					const tlvBytes = Uint8Array.from(byteList);
    
    					// Convert to Base64
    					const qrData = btoa(String.fromCharCode(...tlvBytes));
    
    					console.log(qrData);
    
    					new QRCode(document.getElementById('qrcode'), {
    						text: qrData,
    						width: 128,
    						height: 128,
    						colorDark: '#000000',
    						colorLight: '#ffffff',
    						correctLevel: QRCode.CorrectLevel.L
    					});
    				}
    			}
			}
        
            function renderQRCode(content, elementId) {
                new QRCode(document.getElementById(elementId), {
                    text: content,
                    width: 160,
                    height: 160,
                    colorDark: '#000000',
                    colorLight: '#fafafa',
                    correctLevel: QRCode.CorrectLevel.L
                });
        
                // Tambahan opsional: parsing isi QR untuk tooltip
                let details = parseQRCodeContent(content);
                if (details.size > 0) {
                    let title = Array.from(details)
                        .map(([tag, value]) => `Tag ${tag} : ${value.join(', ')}`)
                        .join('\n');
                    const qrCodeDiv = document.getElementById(elementId);
                    if (qrCodeDiv) qrCodeDiv.title = title.trim();
                }
            }
        
            function parseQRCodeContent(qrCodeBase64) {
                let details = new Map();
                try {
                    let data = atob(qrCodeBase64.replace(/\+/g, '+'));
                    let index = 0;
                    while (index < data.length) {
                        let tag = data.charCodeAt(index++);
                        let length = data.charCodeAt(index++);
                        let value = data.substr(index, length);
                        index += length;
                        let decodedValue = (tag === 8 || tag === 9)
                            ? [...value].map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' ').toUpperCase()
                            : new TextDecoder('utf-8').decode(new Uint8Array([...value].map(c => c.charCodeAt(0))));
                        details.set(tag, [decodedValue]);
                    }
                } catch (ex) {
                    console.error('Error decoding QR code: ' + ex.message);
                }
                return details;
            }

	}, false);

    window.addEventListener("load", () =>
		window.parent.postMessage({ type: "context-request" }, "*")
	);
		</script>
</div>
</body>
</html>
2 Likes

Thank you so much, it will help a lot @Mabaega

All Good, a small issue in the theme, please, if you can help

  1. While clicking on Print Numbers are not coming into the box properly.
  2. While in Print option, A4 size top header is not repeating, If Legal, then Top header is repeating.
  3. When generating a direct PDF, there is no margin or space around the Table, Title, and Logo.

Yes, printing web pages can be quite tricky. I’ve made some adjustments to the code—hopefully it works better now. The output for both Print and PDF should now be more consistent.

1 Like

Thank you @Mabaega
The top header is not repeating now.

The template code works well with my business data.
Try creating a new theme and replace all the code with the code above.
Link it to your invoice — it should work properly.

It’s highly recommended that you do not modify the theme above unless you fully understand what you’re doing.

1 Like

Manager version 25.7.2.2469 has some issues with print settings. The previous version before this version used to print the invoices, etc. at the correct scale.

Now, version 25.7.2.2469 cuts off margins when printing invoices, etc.

Please check to verify.

Hello @frank2cook,

Welcome to the forum.

I have moved your post to this megathread which is meant to address all teething problems for the new and improved Themes.

Please share some screenshots of your problem so we can see exactly what you see.

Also, please feel free to go through this topic to see if any previous post is helpful in your case, you can also use AI to summarize this long thread, the button should have this icon:

This is a screenshot for default settings that do not need to be changed. Still the margin is cut.