ZATCA Phase 1 QR Code Generator

A simple extension that creates QR codes for Saudi Arabian tax invoices.

Installation

  1. Go to Settings → Extensions in Manager.
  2. Create a new extension with:
  • Extension Name: ZATCA Phase 1 QR Code Generator
  • Source: Url
  • Endpoint https:// www.luboshasko.com/extensions/sa/zatca-phase-1-qr-generator/
  • Placement: sales-invoice-view
  1. Click Create button.

Quick Start

  1. Click View button on any invoice

    image

  2. Click ZATCA Phase 1 QR Code Generator button.

    image

  3. Follow the prompts

3 Likes

hI @lubos @Mabaega

Iam trying to set up QR Code for Saudi Entity. the extension is appear in the view screen.

I tried to copy from other company I got below error

will you please guide me how to add or how I could solve below error

below is extension set up

Hello How is Jordan added?

@abdulbari this has been fixed in the latest version now

Thank you @lubos

I just would like to know if you can do something regard the previous invoices before update

QR Code now removed from All invoices except the one has been done with extension

This will put them us in risk if audit from ZATCA is come and find out these invoices without QR code

will you be please able to do something or advise. even if it is agreed to do it again it will capture the date and time of today.

2 Likes

You’re right — we hope for assistance with that, especially given the constant updates that cause unexpected issues.

What is QR extension solving is that QR code is generated once and then stored alongside with the invoice so it won’t change.

Previous approach was to generate QR code on demand which was incorrect because programming code could affect QR code generation in the future. New approach is to store QR code as data which will guarantee it will never change.

I can provide custom theme which will replicate the old behavior for these historical invoices.

3 Likes
<table style="padding: 30px; width: 100%">
    <thead>
        <tr>
            <td colspan="99">
                <table style="margin-bottom: 20px; width: 100%">
                    <tr>
                        <td style="font-weight: bold; font-size: 32px; vertical-align: top">{{ title }}</td>
                        {% if business.logo != null %}
                        <td style="text-align: end">
                            <img src="{{ business.logo }}" style="max-height: 150px; max-width: 300px; display: inline" />
                        </td>
                        {% endif %}
                    </tr>
                </table>

                <table style="margin-bottom: 20px; width: 100%">
                    <tr>
                        <td style="vertical-align: top">
                            <div style="font-weight: bold">
                                <p>{{ recipient.name }}</p>
                            </div>
                            <div>{{ recipient.address | newline_to_br }}</div>
                            <div>{{ recipient.identifier }}</div>
                        </td>
                        <td style="text-align: end; vertical-align: top">
                            {% for field in fields %}
                            <div style="font-weight: bold">{{ field.label }}</div>
                            <div id="{{ field.key }}" style="margin-bottom: 10px">{{ field.text }}</div>
                            {% endfor %}
                        </td>
                        {% if business.address != null %}
                        <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">
                            <div id="BusinessName" style="font-weight: bold">
                                <p>{{ business.name }}</p>
                            </div>
                            <div>{{ business.address | newline_to_br }}</div>
                            {% for field in business.custom_fields %}
                            <div>{{ field.label }} <span id="{{ field.key }}">{{ field.text }}</span></div>
                            {% endfor %}
                        </td>
                        {% endif %}
                    </tr>
                </table>

                <div style="font-size: 14px; font-weight: bold; margin-bottom: 20px">{{ description }}</div>
            </td>
        </tr>
        <tr>
            {% for column in table.columns %}
            <td style="writing-mode: horizontal-tb; border-inline-start-width: 1px; border-inline-start-style: solid; border-inline-start-color: #000; {% if forloop.last == true %} border-inline-end-width: 1px; border-inline-end-style: solid; border-inline-end-color: #000{% endif %}; text-align: {{ column.align }}; font-weight: bold; padding: 5px 10px; border-bottom-width: 1px; border-bottom-color: #000; border-top-width: 1px; border-top-color: #000; border-top-style: solid; border-bottom-style: solid{% if column.nowrap %}; width: 80px{% endif %}">{{ column.label }}</td>
            {% endfor %}
        </tr>
    </thead>
    <tbody>
        {% for row in table.rows %}
        <tr>
            {% for cell in row.cells %}
            <td style="writing-mode: horizontal-tb; border-inline-start-width: 1px; border-inline-start-style: solid; border-inline-start-color: #000; {% if forloop.last == true %} border-inline-end-width: 1px; border-inline-end-style: solid; border-inline-end-color: #000{% endif %}; padding: 5px 10px; text-align: {{ table.columns[forloop.index0].align }}; {% if table.columns[forloop.index0].nowrap %}; white-space: nowrap; width: 80px{% endif %}">{{ cell.text | newline_to_br }}</td>
            {% endfor %}
        </tr>
        {% endfor %}
        <tr>
            {% for column in table.columns %}
            <td style="writing-mode: horizontal-tb; border-inline-start-width: 1px; border-inline-start-style: solid; border-inline-start-color: #000; {% if forloop.last == true %} border-inline-end-width: 1px; border-inline-end-style: solid; border-inline-end-color: #000{% endif %}; border-bottom-width: 1px; border-bottom-color: #000000; border-bottom-style: solid">&nbsp;</td>
            {% endfor %}
        </tr>

        {% for total in table.totals %}
        <tr>
            <td colspan="{{ table.columns | size | minus:1 }}" style="text-align: end; padding: 5px 10px{% if total.emphasis == true %}; font-weight: bold{% endif %}">{{ total.label }}</td>
            <td id="{{ total.key }}" class="{{ total.class }}" data-value="{{ total.number }}" style="border-left-width: 1px; border-left-style: solid; border-left-color: #000; border-right-width: 1px; border-right-style: solid; border-right-color: #000; text-align: right; white-space: nowrap; border-bottom-width: 1px; border-bottom-color: #000000; border-bottom-style: solid; padding: 5px 10px{% if total.emphasis == true %}; font-weight: bold{% endif %}">{{ total.text }}</td>
        </tr>
        {% endfor %}

        {% for field in custom_fields %}
        <tr>
            <td colspan="99">
                <div style="font-weight: bold; padding-top: 20px">{{ field.label }}</div>
                <div>{{ field.text | newline_to_br }}</div>
            </td>
        </tr>
        {% endfor %}

        <tr>
            <td colspan="99">
                <div id="qrcode" style="margin-top: 20px; margin-bottom: 30px"></div>

                {% if emphasis.text != null and emphasis.positive %}
                <div style="text-align: center; margin-top: 40px"><span style="color: #006400; border-width: 5px; border-color: #006400; border-style: solid; padding: 10px; font-size: 20px">{{ emphasis.text | upcase }}</span></div>
                {% endif %}
                {% if emphasis.text != null and emphasis.negative %}
                <div style="text-align: center; margin-top: 40px"><span style="color: #FF0000; border-width: 5px; border-color: #FF0000; border-style: solid; padding: 10px; font-size: 20px">{{ emphasis.text | upcase }}</span></div>
                {% endif %}
            </td>
        </tr>
        <input type="hidden" name="Timestamp" id="TimestampField" value="">
    </tbody>
</table>

<!-- QR CODE SCRIPT UPDATED -->
<script src="resources/qrcode/qrcode.js"></script>
<script type="text/javascript">
    // قراءة تاريخ الفاتورة
    var invoiceDateElement = document.getElementById('InvoiceDate');
    var realDate = null;
    var timestampValue = NaN;

    if (invoiceDateElement) {
        var invoiceDateString = invoiceDateElement.innerText.trim();
        var parts = invoiceDateString.split(/[- /.]/);
        if (parts.length === 3) {
            var day = parseInt(parts[0], 10);
            var month = parseInt(parts[1], 10);
            var year = parseInt(parts[2], 10);
            realDate = new Date(year, month - 1, day);
            if (isNaN(realDate.getTime())) realDate = new Date(year, day - 1, month);
        }
    }

    if (!isNaN(realDate?.getTime())) {
        var now = new Date();
        realDate.setHours(now.getHours(), now.getMinutes(), now.getSeconds());
        timestampValue = realDate.getTime();
    }
    document.getElementById("TimestampField").value = timestampValue;

    // توليد كود QR بنظام TLV
    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);
    }

    try {
        var businessName = document.getElementById("BusinessName")?.innerText?.trim() || "No name";
        var vatNumber = "0000000000000";
        var vatField = document.querySelector('[id^="d96d97e8-c857-42c6-8360-443c06a13de9"]');
        if (vatField && vatField.innerText.trim() !== "") vatNumber = vatField.innerText.trim();

        var isoDate = new Date(timestampValue).toISOString();

        var totalElement = document.getElementById('Total');
        var total = 0;
        if (totalElement) total = parseFloat(totalElement.getAttribute('data-value')) || 0;

        var vat = 0;
        var taxElements = document.getElementsByClassName('taxAmount');
        for (let i = 0; i < taxElements.length; i++) {
            vat += parseFloat(taxElements[i].getAttribute('data-value')) || 0;
        }

        const byteList = [];
        appendTLV(1, businessName, byteList);
        appendTLV(2, vatNumber, byteList);
        appendTLV(3, isoDate, byteList);
        appendTLV(4, total.toFixed(2), byteList);
        appendTLV(5, vat.toFixed(2), byteList);

        const tlvBytes = Uint8Array.from(byteList);
        const qrData = btoa(String.fromCharCode(...tlvBytes));

        new QRCode(document.getElementById("qrcode"), {
            text: qrData,
            width: 128,
            height: 128,
            colorDark: "#000000",
            colorLight: "#ffffff",
            correctLevel: QRCode.CorrectLevel.L
        });
    } catch (e) {
        console.error("QR Code generation error:", e);
    }
</script>

I did that and I think it works.
@lubos

1 Like

Hi @lubos

  • First of all, why does every time the updates damage all our work, and we need to re-work again to fix

  • All old invoices are now without a QR code, and this is very bad if someone from ZATCA comes and asks to check will give us a penalty

  • The extension that you talk about for QR is not visible after the step (view), and no QR code with any new invoice (we haven’t printed any invoice for clients since yesterday)

  • All the colours in the system disappear, and from last night it is black and white (our logo and items photo)

Please, I need a solution

use this subject to add ZATCA e-Invoice Phase II Extension for Manager.io - #39 by Mabaega

1 Like

Thank you, brother, it has worked

Still the issue with colours, everything is black and white

I think if you deactivate the night theme, the colours will return