Important Notice for Users of Relay and Extensions for eInvoicing

Due to recent updates in Manager (version v25.10.6.2882 or later), a critical issue has been identified affecting custom scripts used in eInvoicing setups.

Please do not edit or save any invoices that have already been reported to your tax authority. Doing so may result in the loss of data stored in custom fields.

The issue is caused by changes to the HTML structure in the Edit Form, which prevent certain scripts from functioning correctly. As a result, custom field contents may be cleared when entering edit mode.

I’m currently working on adjusting the scripts to match the updated form structure. Once I’m confident that everything is functioning properly, users will be asked to update their custom fields to apply the revised script.

This affects all users of:

  • ZatcaEGS
  • ZatcaExtension
  • FBR-Extension
  • MyInvoisExtension
  • EgyptEGS
  • ZimraEGS

Thank you for your patience. A follow-up will be posted once the updated scripts are ready.

3 Likes

Temporary Solution for ZatcaEGS and ZatcaExtension Users

To prevent data loss in custom fields due to recent changes in Manager, please follow these steps to apply a temporary fix:

  1. Go to Settings β†’ Custom Fields β†’ Text Custom Fields
  2. Find and edit the custom field named Approval Status
  3. Replace the current script in the Description field with the following:

for ZatcaEGS

<div id="MainScriptContainer">
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      console.group('[Invoice Script Init]');
      console.log('βœ… DOMContentLoaded triggered');

      // === Config constants ===
      const readonlyLabels = ['Approval Status', 'Zatca UUID', 'Base64 QRCode', 'Date Created'];
      const guidsToClear = [
        'a1b2c3d4-e5f6-4abc-8def-abcdef000016',
        'a1b2c3d4-e5f6-4abc-8def-abcdef000017',
        'a1b2c3d4-e5f6-4abc-8def-abcdef000018'
      ];
      const dateCreatedGuid = 'a1b2c3d4-e5f6-4abc-8def-abcdef000020';
      const allowedButtons = [
        'button.btn.btn-success',
        'button.btn.btn-primary',
        'button.btn.btn-default'
      ];

      // === Check main form ===
      const vModelForm = document.getElementById('v-model-form');
      if (!vModelForm) {
        console.warn('❌ v-model-form not found. Script aborted.');
        return;
      }

      // === Handle readonly fields ===
      const labels = vModelForm.querySelectorAll('label');
      let approvalStatusHasValue = false;

      labels.forEach(label => {
        const labelText = label.textContent.trim();
        if (!readonlyLabels.includes(labelText)) return;

        const formGroup = label.closest('.form-group');
        if (!formGroup) return;

        if (labelText === 'Base64 QRCode') {
          const textarea = formGroup.querySelector('textarea');
          if (textarea) {
            textarea.readOnly = true;
            console.log(`πŸ“Œ Made readonly: ${labelText}`);
          }
        } else {
          const inputs = formGroup.querySelectorAll('input');
          inputs.forEach(input => {
            input.readOnly = true;
            if (labelText === 'Approval Status' && input.value.trim() !== '') {
              approvalStatusHasValue = true;
              console.log('βœ… Approval Status has value:', input.value);
            }
          });
        }
      });

      // === Approval Status Handling ===
      if (approvalStatusHasValue) {
        console.log('⚠️ Approval Status present. Locking form...');
        const updateButton = document.querySelector('button.btn.btn-success');

        if (!updateButton) {
          console.log('❌ Update button not found. Resetting approval-related fields...');
          labels.forEach(label => {
            if (label.textContent.trim() === 'Approval Status') {
              const formGroup = label.closest('.form-group');
              if (formGroup) {
                formGroup.querySelectorAll('input').forEach(input => input.value = null);
                console.log('🧹 Cleared Approval Status input');
              }
            }
          });

          guidsToClear.forEach(guid => {
            if (app?.CustomFields2?.Strings) {
              app.CustomFields2.Strings[guid] = null;
              console.log(`🧹 Cleared GUID field: ${guid}`);
            }
          });

          approvalStatusHasValue = false;
        }

        if (approvalStatusHasValue) {
          console.log('πŸ”’ Locking all inputs and disabling UI...');
          const allowedButtonElements = allowedButtons.flatMap(sel => [...document.querySelectorAll(sel)]);
          vModelForm.querySelectorAll('input, select, textarea, button').forEach(control => {
            const isAllowed =
              (control.tagName === 'INPUT' && control.type === 'checkbox') ||
              allowedButtonElements.includes(control);
            control.disabled = !isAllowed;
          });

          vModelForm.querySelectorAll('.v-select').forEach(vs => {
            vs.querySelector('input.vs__search')?.setAttribute('disabled', 'true');
            vs.querySelector('.vs__clear')?.setAttribute('disabled', 'true');
            vs.style.pointerEvents = 'none';
            vs.style.opacity = '0.6';
          });

          vModelForm.querySelectorAll('.select2-container').forEach(s2 => {
            s2.querySelector('input')?.setAttribute('disabled', 'true');
            s2.style.pointerEvents = 'none';
            s2.style.opacity = '0.6';
          });

          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');
          });
        }
      }

      // === Disable date input ===
      const dateInput = document.querySelector('.mx-input');
      if (dateInput) {
        dateInput.disabled = true;
        document.querySelector('.mx-icon-calendar')?.style.setProperty('pointer-events', 'none');
        console.log('πŸ“… Disabled date input');
      }

      // === Disable automatic reference checkbox ===
      const checkbox = document.querySelector('.input-group-addon input[type="checkbox"]');
      const inputText = document.querySelector('.input-group input[type="text"]');
      if (checkbox && inputText) {
        app.AutomaticReference = false;
        checkbox.checked = false;
        checkbox.disabled = true;
        inputText.disabled = true;
        console.log('πŸ”Œ Disabled automatic reference checkbox and text input');
      }

      // === Handle Create button & date injection ===
      const createButton = document.querySelector('button.btn.btn-primary');
      if (createButton && app?.CustomFields2?.Strings) {
        const now = new Date();
        const localDatetime = new Date(now.getTime() - now.getTimezoneOffset() * 60000);
        const currentDateTime = localDatetime.toISOString().slice(0, 19).replace('T', ' ');

        app.CustomFields2.Strings[dateCreatedGuid] = currentDateTime;
        app.IssueDate = currentDateTime.slice(0, 10);
		app.CustomFields2.Strings[dateCreatedGuid] = currentDateTime;
        console.log('πŸ•’ Set DateCreated & IssueDate:', currentDateTime);

        if (checkbox && inputText) {
          app.AutomaticReference = true;
          checkbox.checked = true;
          checkbox.disabled = true;
          inputText.disabled = true;
          console.log('βœ… Re-enabled automatic reference on create');
        }
      }

      // === Hide "Date Created" field ===
      labels.forEach(label => {
        if (label.textContent.trim() === 'Date Created') {
          label.closest('.form-group').style.display = 'none';
          console.log('πŸ™ˆ Hid "Date Created" field');
        }
      });

      // === Self-clean script container ===
      const selfDeletingContainer = document.getElementById('MainScriptContainer');
      if (selfDeletingContainer) {
        selfDeletingContainer.remove();
        console.log('πŸ—‘οΈ Removed script container from DOM');
      }

      console.groupEnd();
    });
  </script>
</div>

For ZatcaExtension

<div id="MainScriptContainer">
  <script>document.addEventListener('DOMContentLoaded', () => {
    const readonlyLabels = ['Approval Status', 'Zatca UUID', 'Base64 QRCode'];
	const hiddenLabels = ['Date Created', 'Zatca UUID', 'Base64 QRCode'];
    const guidsToClear = [
        'a1b2c3d4-e5f6-4abc-8def-abcdef000016',
        'a1b2c3d4-e5f6-4abc-8def-abcdef000017',
        'a1b2c3d4-e5f6-4abc-8def-abcdef000018'
    ];
    const dateCreatedGuid = 'a1b2c3d4-e5f6-4abc-8def-abcdef000020';
    const allowedButtons = [
        'button.btn.btn-success',
        'button.btn.btn-primary',
        'button.btn.btn-default'
    ];
    const vModelForm = document.getElementById('v-model-form');
    if (!vModelForm) return;
    const labels = vModelForm.querySelectorAll('label');
    let approvalStatusHasValue = false;
    const allowedButtonElements = allowedButtons
        .map(selector => [...document.querySelectorAll(selector)])
        .flat();
    labels.forEach(label => {
        const labelText = label.textContent.trim();
        if (!readonlyLabels.includes(labelText)) return;
        const formGroup = label.closest('.form-group');
        if (!formGroup) return;
        if (labelText === 'Base64 QRCode') {
            const textarea = formGroup.querySelector('textarea');
            if (textarea) textarea.readOnly = true;
        } else {
            const inputs = formGroup.querySelectorAll('input');
            inputs.forEach(input => {
                input.readOnly = true;
                if (labelText === 'Approval Status' && input.value && input.value.trim() !== '') {
                    approvalStatusHasValue = true;
                }
            });
        }
    });
	if (approvalStatusHasValue) {
		const updateButton = document.querySelector('button.btn.btn-success');
		if (!updateButton) {
			labels.forEach(label => {
				if (label.textContent.trim() === 'Approval Status') {
					const formGroup = label.closest('.form-group');
					if (formGroup) {
						const inputs = formGroup.querySelectorAll('input');
						inputs.forEach(input => input.value = null);
					}
				}
			});
			guidsToClear.forEach(guid => {
				app.CustomFields2.Strings[guid] = null;
			});
			approvalStatusHasValue = false;
		}
		if (approvalStatusHasValue) {
			const controls = vModelForm.querySelectorAll('input, select, textarea, button');
			controls.forEach(control => {
				const isAllowed =
					(control.tagName === 'INPUT' && control.type === 'checkbox') ||
					allowedButtonElements.includes(control);
				control.disabled = !isAllowed;
			});
			vModelForm.querySelectorAll('.v-select').forEach(vs => {
				const input = vs.querySelector('input.vs__search');
				if (input) input.disabled = true;
				const clearBtn = vs.querySelector('.vs__clear');
				if (clearBtn) clearBtn.disabled = true;
				vs.style.pointerEvents = 'none';
				vs.style.opacity = '0.6';
			});
			vModelForm.querySelectorAll('.select2-container').forEach(s2 => {
				const input = s2.querySelector('input');
				if (input) input.disabled = true;
				s2.style.pointerEvents = 'none';
				s2.style.opacity = '0.6';
			});
			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');
			});
		}
	}
    const dateInput = document.querySelector('.mx-input');
    if (dateInput) {
        dateInput.disabled = true;
        const calendarIcon = document.querySelector('.mx-icon-calendar');
        if (calendarIcon) {
            calendarIcon.style.pointerEvents = 'none';
            calendarIcon.style.opacity = '0.5';
        }
    }
    const checkbox = document.querySelector('.input-group-addon input[type="checkbox"]');
    const inputText = document.querySelector('.input-group input[type="text"]');
    if (checkbox && inputText) {
        app.AutomaticReference = false;
        checkbox.checked = false;
        checkbox.disabled = true;
        inputText.disabled = true;
    }
    const createButton = document.querySelector('button.btn.btn-primary');
    if (createButton && app?.CustomFields2?.Strings) {
        const now = new Date();
        const localDatetime = new Date(now.getTime() - (now.getTimezoneOffset() * 60000));
        const currentDateTime = localDatetime.toISOString().slice(0, 19).replace('T', ' ');
        app.CustomFields2.Strings[dateCreatedGuid] = currentDateTime;
        app.IssueDate = currentDateTime.slice(0, 10);
        if (checkbox && inputText) {
            app.AutomaticReference = true;
            checkbox.checked = true;
            checkbox.disabled = false;
            inputText.disabled = false;
        }
		app.CustomFields2.Strings[dateCreatedGuid] = currentDateTime;
    }
    labels.forEach(label => {
		const labelText = label.textContent.trim();
        if (!hiddenLabels.includes(labelText)) return;
		const formGroup = label.closest('.form-group');
		if (formGroup) {
			formGroup.style.display = 'none';
		}
    });
	const selfDeletingContainer = document.getElementById('MainScriptContainer');
	if (selfDeletingContainer) {
		selfDeletingContainer.remove();
	}
    });
  </script>
</div>

:warning: This script should be adjusted to match the current HTML structure of the Edit Form. If you’re unsure about the correct version v25.10.6.2882 or later, please wait for the finalized update before applying changes.


This workaround is specifically for users of ZatcaEGS and ZatcaExtension. It helps preserve functionality while the full fix is being finalized.

3 Likes

@Mabaega appreciated your effort :+1:

Temporary Solution for FBR-Extension Users

To prevent data loss in custom fields due to recent changes in Manager, please follow these steps to apply a temporary fix:

  1. Go to Settings β†’ Custom Fields β†’ Text Custom Fields
  2. Find and edit the custom field named Invoice Number
  3. Replace the current script in the Description field with the following:
<div id="MainScriptContainer">
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const readonlyLabels = ['Invoice Number', 'QR Code'];
      const guidsToClear = [
        '24b11f97-46d7-472e-ad4b-e99fbed9197e',
        'd2e9265a-460e-4a06-83f9-29a523a4d516',
        '94b9bf0a-1c4e-488e-a50f-87c04e1ef390'
      ];
      const allowedButtons = [
        'button.btn.btn-success',
        'button.btn.btn-primary',
        'button.btn.btn-default'
      ];

      const vModelForm = document.getElementById('v-model-form');
      if (!vModelForm) return;

      const labels = vModelForm.querySelectorAll('label');
      let invoiceNumberHasValue = false;
      const allowedButtonElements = allowedButtons
        .map(selector => [...document.querySelectorAll(selector)])
        .flat();

      // Set readonly / disabled untuk field tertentu
      labels.forEach(label => {
        const labelText = label.textContent.trim();
        if (!readonlyLabels.includes(labelText)) return;

        const formGroup = label.closest('.form-group');
        if (!formGroup) return;

        const inputs = formGroup.querySelectorAll('input');
        inputs.forEach(input => {
          const type = input.type;

          if (type === 'file') {
            input.disabled = true;
            const wrapper = formGroup.closest('.flex') || formGroup.parentElement;
            if (wrapper) {
              const button = wrapper.querySelector('button');
              if (button) {
                button.disabled = true;
                button.style.pointerEvents = 'none';
                button.style.opacity = '0.5';
              }
            }
          } else {
            input.readOnly = true;
          }

          if (labelText === 'Invoice Number' && input.value?.trim() !== '') {
            invoiceNumberHasValue = true;
          }
        });
      });

      // Jika Invoice Number punya nilai
      if (invoiceNumberHasValue) {
        const updateButton = document.querySelector('button.btn.btn-success');

        // Jika tidak ada tombol update, reset nilai dan GUID
        if (!updateButton) {
          labels.forEach(label => {
            const labelText = label.textContent.trim();
            if (!readonlyLabels.includes(labelText)) return;

            const formGroup = label.closest('.form-group');
            if (!formGroup) return;

            const inputs = formGroup.querySelectorAll('input');
            inputs.forEach(input => input.value = null);
          });

          guidsToClear.forEach(guid => {
            app.CustomFields2.Strings[guid] = null;
            app.CustomFields2.Images[guid] = null;
          });

          invoiceNumberHasValue = false;
        }

        // Jika tetap ada nilai invoice, nonaktifkan seluruh form
        if (invoiceNumberHasValue) {
          const controls = vModelForm.querySelectorAll('input, select, textarea, button');
          controls.forEach(control => {
            const isCheckbox = control.tagName === 'INPUT' && control.type === 'checkbox';
            const isAllowed = isCheckbox || allowedButtonElements.includes(control);
            control.disabled = !isAllowed;
          });

          // Nonaktifkan v-select
          vModelForm.querySelectorAll('.v-select').forEach(vs => {
            const input = vs.querySelector('input.vs__search');
            if (input) input.disabled = true;

            const clearBtn = vs.querySelector('.vs__clear');
            if (clearBtn) clearBtn.disabled = true;

            vs.style.pointerEvents = 'none';
            vs.style.opacity = '0.6';
          });

          // Nonaktifkan select2
          vModelForm.querySelectorAll('.select2-container').forEach(s2 => {
            const input = s2.querySelector('input');
            if (input) input.disabled = true;

            s2.style.pointerEvents = 'none';
            s2.style.opacity = '0.6';
          });

          // Nonaktifkan drag handle
          vModelForm.querySelectorAll('td.handle').forEach(td => {
            td.style.pointerEvents = 'none';
            td.style.opacity = '0.5';
          });

          // Nonaktifkan tombol delete
          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');
          });

          // Nonaktifkan date picker
          const dateInput = document.querySelector('.mx-input');
          if (dateInput) {
            dateInput.disabled = true;
            const calendarIcon = document.querySelector('.mx-icon-calendar');
            if (calendarIcon) {
              calendarIcon.style.pointerEvents = 'none';
              calendarIcon.style.opacity = '0.5';
            }
          }

          // Nonaktifkan checkbox dan input referensi
          const checkbox = document.querySelector('.input-group-addon input[type="checkbox"]');
          const inputText = document.querySelector('.input-group input[type="text"]');
          if (checkbox && inputText) {
            app.AutomaticReference = false;
            checkbox.checked = false;
            checkbox.disabled = true;
            inputText.disabled = true;
          }
        }
      }

      // Hapus container script setelah eksekusi
      const selfDeletingContainer = document.getElementById('MainScriptContainer');
      if (selfDeletingContainer) selfDeletingContainer.remove();
    });
  </script>
</div>

Please note that this script update is still not a perfect solution. Users will unable to update invoices that have already been reported, which may be necessary in certain edge cases (e.g., adjusting print settings or correcting non-critical fields).

However, for now, this workaround is preferable to losing critical dataβ€”especially invoice number, qrcode and other reference that are required for compliance. Preserving these fields is essential, and this script helps prevent accidental data loss until a more robust solution is available.

1 Like

For ZatcaEGS, ZatcaExtension, MyInvoisExtension and FBR-Extension

Please update your Manager application to the latest version. This update will force a refresh of Custom Field data when we send Invoice to Invoicing Portal.

Once the update is complete and Custom Fields have been adjusted accordingly, the eInvoicing feature should resume functioning as expected.

:bell: Message for Relay users:

We strongly recommend that you begin transitioning to the Manager Extension feature. Relay will soon be removed from Manager and fully replaced by Extensions. This transition is essential to ensure your eInvoicing integration continues to function properly and remains compliant with the latest standards, both from the Tax Server side and within Manager itself.

1 Like

I’m currently using the relay button for development purposes, mainly to send data to a server file that restructures it for e-Invoice certification.
If the relay is going to be removed, will its features also be discontinued?
It would be helpful to know if the relay functionality will be preserved in the extension section to ensure a smooth transition.
Please share more details so I can prepare accordingly.

When trying to transfer using the new tool, the amount and tax appear as the same value. This does not happen in the old method.

1 Like