I am pleased to announce the release of the MyInvois Extension for Manager.io.
This extension is now available for testing and feedback.
What is MyInvois Extension?
The MyInvois Extension is an add-on for Manager.io that enables businesses in Malaysia to comply with LHDNM e-Invoicing requirements.
With this extension, you can:
Verify taxpayer details using QR codes. Supported methods:
Scan with camera
Drag & drop a QR image
Upload a QR file from device
Manually type QR content
Retrieved taxpayer details can be saved as Customer Data in Manager, ensuring consistency with the official LHDNM database.
Feedback
I kindly invite everyone to try out the MyInvois Extension in PreProduction and share your feedback. Please report any issues or bugs you encounter and feel free to suggest ideas for improvements. Your input will help shape the future development of this extension and ensure it becomes more useful for the community.
Hi, thank you for your effort in MyInvois integration with Manager.
For the business details that filled in at Integration Wizard, itβs only 1 time update, then next time will skip this stage to submit the MyInvois direct or we have to fill in own business details every time we do submission of an invoice ?
For the invoice type you mentioned, could you please share an example of how you normally create it in Manager? This will help me better understand the differences between that invoice type and a standard invoice or credit note.
I have not yet touched the Recurring Sales Invoice section.
Could you try updating the script in the Document Status custom field description?
<div id='MainScriptContainer'>
<script>
document.addEventListener('DOMContentLoaded', () => {
const vModelForm = document.getElementById('v-model-form');
if (!vModelForm) return;
const documentIssueDateGuid = '65bf01ec-9d33-45c5-b441-4b29b1bfdfba';
// ------------------------------------------------------------
// Find Document Status input
// ------------------------------------------------------------
function findDocumentStatusInput() {
const labels = vModelForm.querySelectorAll('label');
for (const label of labels) {
if (label.textContent.trim().toLowerCase() === 'document status') {
const grp = label.closest('.form-group') || label.parentElement;
if (!grp) continue;
const inp = grp.querySelector('input, textarea, select, input[type=hidden]');
if (inp && inp.value && inp.value.toString().trim() !== '') {
return inp;
}
}
}
return null;
}
const documentStatusInput = findDocumentStatusInput();
let documentStatusHasValue = !!documentStatusInput;
// ------------------------------------------------------------
// Detect Update Button
// ------------------------------------------------------------
const updateButton = document.querySelector('button.btn.btn-success');
// Jika status ada tapi tidak ada update button β reset
if (documentStatusHasValue && !updateButton && documentStatusInput) {
documentStatusInput.value = '';
documentStatusHasValue = false;
}
// ------------------------------------------------------------
// Lock Date Picker
// ------------------------------------------------------------
const dateInput = document.querySelector('.mx-input');
if (dateInput) {
dateInput.readOnly = true;
dateInput.setAttribute('readonly', 'readonly');
dateInput.style.opacity = '0.8';
const cal = document.querySelector('.mx-icon-calendar');
if (cal) {
cal.style.pointerEvents = 'none';
cal.style.opacity = '0.5';
}
}
// ------------------------------------------------------------
// Lock form if DocumentStatus has value
// ------------------------------------------------------------
if (documentStatusHasValue) {
const allowedButtons = [
...document.querySelectorAll('button.btn.btn-success, button.btn.btn-primary, button.btn.btn-default')
];
vModelForm.querySelectorAll('input, textarea, select, button').forEach(control => {
const isCheckbox = control.tagName === 'INPUT' && control.type === 'checkbox';
const isAllowedButton = allowedButtons.includes(control);
if (!isCheckbox && !isAllowedButton) {
if (control.tagName === 'INPUT' || control.tagName === 'TEXTAREA') {
control.readOnly = true;
control.setAttribute('readonly', 'readonly');
} else if (control.tagName === 'SELECT') {
control.style.pointerEvents = 'none';
control.style.opacity = '0.6';
} else if (control.tagName === 'BUTTON') {
control.style.pointerEvents = 'none';
control.style.opacity = '0.6';
}
} else if (isCheckbox) {
control.style.pointerEvents = 'none';
control.style.opacity = '0.8';
}
});
// v-select
vModelForm.querySelectorAll('.v-select').forEach(vs => {
vs.style.pointerEvents = 'none';
vs.style.opacity = '0.6';
vs.querySelectorAll('input.vs__search, .vs__clear').forEach(el => {
try {
el.readOnly = true;
el.setAttribute('readonly', 'readonly');
el.style.pointerEvents = 'none';
} catch (e) {}
});
});
// select2
vModelForm.querySelectorAll('.select2-container').forEach(s2 => {
s2.style.pointerEvents = 'none';
s2.style.opacity = '0.6';
s2.querySelectorAll('input').forEach(el => {
try {
el.readOnly = true;
el.setAttribute('readonly','readonly');
} catch(e) {}
});
});
// Remove handle delete
vModelForm.querySelectorAll('td.handle').forEach(td => {
td.style.pointerEvents = 'none';
td.style.opacity = '0.5';
});
vModelForm.querySelectorAll('a.btn.btn-danger').forEach(link => {
link.style.pointerEvents = 'none';
link.style.opacity = '0.6';
link.setAttribute('aria-disabled', 'true');
link.removeAttribute('href');
});
}
// ------------------------------------------------------------
// Reference checkbox & text
// ------------------------------------------------------------
const refGroup = Array.from(vModelForm.querySelectorAll('.input-group'))
.find(g => g.querySelector('input[type=checkbox]') && g.querySelector('input[type=text]'));
if (refGroup) {
const refCheckbox = refGroup.querySelector('input[type=checkbox]');
const refText = refGroup.querySelector('input[type=text]');
if (refCheckbox && refText) {
if (documentStatusHasValue) {
app.AutomaticReference = false;
refCheckbox.checked = false;
refCheckbox.style.pointerEvents = 'none';
refCheckbox.style.opacity = '0.8';
refText.readOnly = true;
refText.setAttribute('readonly', 'readonly');
} else {
app.AutomaticReference = true;
refCheckbox.checked = true;
refCheckbox.style.pointerEvents = 'auto';
refCheckbox.style.opacity = '1';
refText.readOnly = false;
refText.removeAttribute('readonly');
}
}
}
// ------------------------------------------------------------
// CREATE BUTTON β Set Document Issue Date
// (Mode Create)
// ------------------------------------------------------------
const createButton = document.querySelector('button.btn.btn-primary');
if (createButton && typeof app !== 'undefined' && app?.CustomFields2?.Strings) {
const now = new Date();
const currentDate = now.toISOString().slice(0,19).replace('T',' ');
const currentDateOnly = now.toISOString().slice(0,10);
app.CustomFields2.Strings[documentIssueDateGuid] = currentDate;
app.IssueDate = currentDateOnly;
}
// ------------------------------------------------------------
// NEW FEATURE REQUESTED BY USER
// If UpdateButton exists AND documentIssueDate empty β create new timestamp
// ------------------------------------------------------------
if (updateButton &&
typeof app !== 'undefined' &&
app?.CustomFields2?.Strings &&
!app.CustomFields2.Strings[documentIssueDateGuid]) {
// Use existing app.IssueDate for the date
const issueDate = app.IssueDate; // expected format: yyyy-MM-dd
// Current time
const now = new Date();
const hh = String(now.getHours()).padStart(2, '0');
const mm = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
// Combine: existing date + current time
const finalDateTime = `${issueDate} ${hh}:${mm}:${ss}`;
// Apply
app.CustomFields2.Strings[documentIssueDateGuid] = finalDateTime;
// Do NOT change app.IssueDate
}
// ------------------------------------------------------------
// Remove script block
// ------------------------------------------------------------
document.getElementById('MainScriptContainer')?.remove();
});
</script>
</div>
The new script should add the Document Issue Date in Edit mode after batch creation from a recurring sales invoice. Document Issue Date should be generated based on the invoice date and the time when you perform the first update.
Please make sure to back up your data before trying this and let me know if this approach works as expected.