@Ealfardan Please can you help with your css code
Hey everyone,
Ever since the latest update, weâve been facing a few major issues with the Sales Invoice feature:
-Weâre no longer able to save or print Sales Invoices.
-When we try to print, the invoice shows up with a scroll bar and each single invoice is showing up as 40â80 pages long, with the footer pushed all the way to the last page.
-Also, the new Theme Enhancer feature looks broken on our side. We canât scroll down to reach it properly â the page keeps extending endlessly and the scroll bar keeps moving up on its own, even after 10+ minutes of being on the invoice tab.
-For context, weâre using a custom theme for our invoices.
Another thing we noticed â in Sales Quotes, the custom fields that used to appear at the top are now showing up at the bottom of the document.
Is anyone else facing the same issues? Has anyone figured out a fix or workaround yet?
Appreciate any help!
I have merged multiple topics into this one and changed the topic accordingly.
I will also pin this topic to the top in order to help new reports find their way.
The idea of ââusing AI in theme design is brilliant, but:
- Itâs necessary to preserve the old themes and the default theme.
- When generating a theme using AI, there must be a way to modify or improve the same theme, as modifications and improvements are limited to the default theme only.
- How can it be applied to other modules?
same with me
do you use a theme?
you have to edit the display css code for it
To remove the scrollbar, temporarily try setting the margin to none, this worked in some of my experiments.
where i can do this?
In your browserâs print dialogue menu. It might be folded under More settings
Or try use my costum theme (modified from default custom theme), this work with Margin Default
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<!-- Styling for consistent look and feel -->
<style>
/* Print-specific fixes only */
@media print {
@page {
margin: 0.5in;
}
body {
margin: 0 !important;
}
}
body {
margin: 30px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
color: #171717;
font-size: 12px;
line-height: 1.428571429;
}
table {
font-size: 12px;
}
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
}
tbody#table-rows tr:last-child td {
padding-bottom: 30px;
border-bottom: 1px solid #000;
}
</style>
</head>
<body>
<!-- Top section: document title and optional business logo -->
<table style='margin-bottom: 20px; width: 100%'>
<tbody>
<tr>
<td style='vertical-align: top'>
<div style='font-size: 32px; line-height: 32px; font-weight: bold' id='title'>Invoice</div>
</td>
<td style='text-align: end' id='business-logo'>
</td>
</tr>
</tbody>
</table>
<!-- Second section: recipient info, document fields (like date, invoice #), and business info -->
<table style='margin-bottom: 20px; width: 100%'>
<tbody>
<tr>
<td style='vertical-align: top' id='recipient-info'></td>
<td style='text-align: end; vertical-align: top' id='fields'></td>
<td style='width: 20px'></td>
<td style='width: 1px; border-left-width: 1px; border-left-color: #000; border-left-style: solid'></td>
<td style='width: 20px'></td>
<td style='width: 1px; white-space: nowrap; vertical-align: top' id='business-info'></td>
</tr>
</tbody>
</table>
<!-- Description block -->
<table style='border-collapse: collapse; width: 100%'>
<tr><td><div style='font-weight: bold; font-size: 14px; margin-bottom: 20px' id='description'></div></td></tr>
</table>
<!-- Main table containing column headers, line items, and totals -->
<table style='border-collapse: collapse; width: 100%'>
<thead>
<tr id='table-headers'></tr>
</thead>
<tbody id='table-rows'>
</tbody>
<tfoot id='totals'>
</tfoot>
</table>
<script src='resources/qrcode/qrcode.js'></script>
<table style='border-collapse: collapse; width: 100%'>
<tr><td><div id='qrcode' style='margin-bottom: 20px'></div></td></tr>
<!-- Section for any additional custom fields -->
<tr><td><div id='custom-fields' style=' word-break: break-all; overflow-wrap: break-word; white-space: normal;'></div></td></tr>
<!-- Section for footers -->
<tr><td><div id='footers' style='word-break: break-all; overflow-wrap: break-word; white-space: normal;'></div></td></tr>
</table>
<!-- Status div -->
<div id='status'></div>
<script>
// Listen for messages sent from the parent frame (via postMessage API)
window.addEventListener('message', (event) => {
if (event.source !== window.parent) return; // Only accept messages from parent
if (event.data.type !== 'context-response') return; // Ignore irrelevant messages
// Extract the main data object sent from parent
const data = event.data.body;
// Set text direction (LTR or RTL) for the whole document
document.documentElement.dir = data.direction;
// Populate title and description if provided
document.getElementById('title').innerHTML = data.title || 'No title';
document.getElementById('description').innerHTML = data.description || '';
// Inject business logo image if available
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);
}
// Populate business info section with name and address (convert line breaks)
const business = data.business || {};
document.getElementById('business-info').innerHTML = `<strong id="BusinessName">${business.name || ''}</strong><br>${business.address ? business.address.replace(/\n/g, '<br>') : ''}`;
// Populate recipient info section with name and address (convert line breaks)
const recipient = data.recipient || {};
document.getElementById('recipient-info').innerHTML = `<strong>${recipient.name || ''}</strong><br>${recipient.address ? recipient.address.replace(/\n/g, '<br>') : ''}`;
// Insert fields (e.g. issue date, due date) into right-hand side
const fieldsDiv = document.getElementById('fields');
fieldsDiv.innerHTML = '';
(data.fields || []).forEach(f => {
const div = document.createElement('div');
div.innerHTML = `<strong>${f.label}</strong><br />${f.text || ''}<br /><br />`;
fieldsDiv.appendChild(div);
});
// Build table headers dynamically based on `columns` definition
const headersRow = document.getElementById('table-headers');
headersRow.innerHTML = '';
(data.table.columns || []).forEach(col => {
const th = document.createElement('th');
th.innerHTML = col.label;
th.style.textAlign = col.align;
if (col.minWidth) {
th.style.whiteSpace = 'nowrap';
th.style.width = '1px';
}
else if (col.nowrap) {
th.style.whiteSpace = 'nowrap';
th.style.width = '80px';
}
headersRow.appendChild(th);
});
// Populate main table body with rows and alignments based on column definitions
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 = '80px';
}
tr.appendChild(td);
});
rowsBody.appendChild(tr);
});
// Populate totals section with subtotals, taxes, grand total, etc.
const totalsBody = document.getElementById('totals');
totalsBody.innerHTML = '';
(data.table.totals || []).forEach(total => {
const tr = document.createElement('tr');
const tdLabel = document.createElement('td');
const tdValue = document.createElement('td');
tdLabel.innerHTML = total.label;
tdLabel.colSpan = data.table.columns.length - 1;
tdLabel.style = 'padding: 5px 10px; text-align: end; vertical-align: top';
tdValue.innerHTML = total.text;
tdValue.id = total.key;
tdValue.dataset.value = total.number;
tdValue.style = 'padding: 5px 10px; border: 1px solid #000; text-align: right; white-space: nowrap; vertical-align: top';
// Bold totals if marked as 'emphasis'
if (total.emphasis) {
tdLabel.style.fontWeight = 'bold';
tdValue.style.fontWeight = 'bold';
}
tr.appendChild(tdLabel);
tr.appendChild(tdValue);
totalsBody.appendChild(tr);
});
// Render custom fields section below the table (e.g. notes, terms)
const customFieldsDiv = document.getElementById('custom-fields');
customFieldsDiv.innerHTML = '';
let qrcodetext = '';
(data.custom_fields || []).forEach(f => {
if (f.label === 'Base64 QRCode') {
console.log('Found Base64 QRCode:', f.text);
qrcodetext = f.text;
} else {
console.log(f.label + ':', f.text);
const div = document.createElement('div');
div.innerHTML = `<strong>${f.label}</strong><br />${(f.text || '').split('\n').join('<br />')}<br /><br />`;
customFieldsDiv.appendChild(div);
}
});
// Render footers section
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);
// Find and execute any script tags
const scripts = div.querySelectorAll('script');
scripts.forEach(script => {
const newScript = document.createElement('script');
// Copy script attributes if needed
for (const attr of script.attributes) {
newScript.setAttribute(attr.name, attr.value);
}
// Inline script handling
if (script.textContent) {
newScript.textContent = script.textContent;
}
// Replace the old <script> with the new one so it executes
script.parentNode.replaceChild(newScript, script);
});
});
// Display status label (e.g. PAID, CANCELLED) with colored border based on status type
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);
}
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);
// Request context data from parent frame when page loads
window.addEventListener('load', () =>
window.parent.postMessage({ type: 'context-request' }, '*')
);
</script>
</body>
</html>
@AYUB the issue is that your document has a lot of columns and it doesnât fit horizontally. Please check the latest version (25.6.24.2437) where this should be fixed.
Same problem here. Only way I could temporarily override this is by going to âmore settingsâ in the print menu and selecting the scale as custom and then putting the scale at more or less 87. @lubos fix for this in near future?
I have the same issueâŚ
I can see the issue here. The View screen now assumes A4 portrait orientation by default and whatever doesnât fit that orientation is overflowed.
@lubos, I wonder if the new View screens using iframe could be made print page agnostic like before?




