Sunday, December 19, 2021

How to change data provider class and sales invoice report in AX 2012 to print QR Code for KSA

In order to meet the KSA ZATCA regulations, it is mandatory to print a QR code containing 5 fields including Company name, VAT registration number, Invoice date/time, VAT Amount, and Invoice Amount including VAT on any customer invoice. 

In order to meet the above requirements, you need to first create an Extended Data Type (EDT) of container type to hold the QR code. 

Link to youtube video: https://youtu.be/iaKCD202jt0

Link to XPO on Github: https://github.com/pkrashdi/ax2012public/blob/main/SharedProject_PKR_SalesInvoiceQRCodeChanges.zip

Second, the EDT need to be dragged and dropped on SalesInvoiceHeaderFooterTmp temporary table holding header and footer information for sales invoice report.

Thirdly, in the class declaration of SalesInvoiceDP class, declare the following variables or arrays at the end:

.....

    //global variable

    //PKR qr code

    int                             qrCodeByteArrIndex;

    System.Byte[]                   qrCodeByteArray;

......

Fourth, add following method in SalesInvoiceDP class:


/// <summary>

/// The method takes TLV structure for KSA QR code and sets into byte array containing data for QR code.

/// </summary>

/// <param name="_tag">

/// The serial number of structure.

/// </param>

/// <param name="_length">

/// The length of value of field.

/// </param>

/// <param name="[System.Byte[]]_byteArrayStr">

/// The value of a field.

/// </param>

/// <remarks>

/// Various field values such as VAT number, company name, VAT Amount, Invoice Amount including VAT, and Transaction date

/// and time need to be printed as QR code. The method serves as setting each TLV structure into a byte array.

/// </remarks>

void PKR_QRCodeFillByteArray(int _tag, int _length, System.Byte[] _byteArrayStr)

{

    int counter;


    qrCodeByteArray.SetValue( System.Convert::ToByte(_tag), qrCodeByteArrIndex);

    qrCodeByteArrIndex++;

    qrCodeByteArray.SetValue(System.Convert::ToByte(_length), qrCodeByteArrIndex);

    qrCodeByteArrIndex++;


    for (counter=0; counter < CLRInterOp::getAnyTypeForObject(_byteArrayStr.get_Length()); counter++)

    {

        qrCodeByteArray.SetValue(_byteArrayStr.GetValue(counter), qrCodeByteArrIndex);

        qrCodeByteArrIndex++;

    }

}


Fifth, add another method as follows in SalesInvoiceDP class:

public display container  PKR_QRCode(CustInvoiceJour _custInvoiceJour)

{

    str                                 strCompanyName, strVATNum, strDateTime, strTotalVATAmount, strTotalAmountIncVAT, strQrCodeBase64;

    int                                 qrByteArrayLength;

    Bindata                             bindata = new Bindata();

    System.Drawing.Bitmap               obj;

    Filepath                            filePath;

    container                           con;

    Microsoft.Dynamics.QRCode.Encoder   encoder;

    Set                                 permissionSet;

    System.Text.Encoding                encodingUTF8;


    filePath = @"C:\temp\newQRCode.bmp";

    permissionSet = new Set(Types::Class);

    permissionSet.add(new FileIOPermission(filePath,'rw'));

    permissionSet.add(new InteropPermission(InteropKind::ClrInterop));

    CodeAccessPermission::assertMultiple(permissionSet);


    encoder   = new Microsoft.Dynamics.QRCode.Encoder();

    encodingUTF8 = System.Text.Encoding::get_UTF8();

    qrCodeByteArrIndex = 0;


    strCompanyName = companyInfo.Name;

    strVATNum = companyInfo.CoRegNum;

    strDateTime =  date2Str(_custInvoiceJour.InvoiceDate,321, DateDay::Digits2, DateSeparator::Hyphen, DateMonth::Digits2, DateSeparator::Hyphen, DateYear::Digits4)+ "T12:00:00Z";

    strTotalVATAmount = strfmt("%1", _custInvoiceJour.SumTax);

    strTotalAmountIncVAT = strfmt("%1", _custInvoiceJour.InvoiceAmount);


    qrByteArrayLength = strlen(strfmt("%1%2%3%4%5", strCompanyName, strVATNum, strDateTime, strTotalVATAmount, strTotalAmountIncVAT)) + 10;


    qrCodeByteArray = new System.Byte[qrByteArrayLength](); //initialize global byte array as per length of all TLV structures plus 2 chars per each struct for TL.


    this.pkr_QRCodeFillByteArray(1, strlen(strCompanyName), encodingUTF8.GetBytes(strCompanyName));

    this.pkr_QRCodeFillByteArray(2, strlen(strVATNum), encodingUTF8.GetBytes(strVATNum));

    this.pkr_QRCodeFillByteArray(3, strlen(strDateTime), encodingUTF8.GetBytes(strDateTime));

    this.pkr_QRCodeFillByteArray(4, strlen(strTotalAmountIncVAT), encodingUTF8.GetBytes(strTotalAmountIncVAT));

    this.pkr_QRCodeFillByteArray(5, strlen(strTotalVATAmount), encodingUTF8.GetBytes(strTotalVATAmount));


    strQrCodeBase64 = System.Convert::ToBase64String(qrCodeByteArray);

    //info(strfmt("%1", strQrCodeBase64));

    obj = new System.Drawing.Bitmap(encoder.Encode(strQrCodeBase64));

    obj.Save(filePath, System.Drawing.Imaging.ImageFormat::get_Bmp());

    bindata.loadFile(filePath);


    con = bindata.getData();

    CodeAccessPermission::revertAssert();


    return con;

}

Sixth, add following line to the method insertIntoSalesInvoiceHeaderFooterTmp() before insert() but before localized block of code:

    salesInvoiceHeaderFooterTmp.PKR_QRCode = this.PKR_QRCode(_custInvoiceJour); //sets qr code to the field value.


Lastly, open the SalesInvoice or any custom sales report which you might be using, and refresh the dataset for the report so that a new field is appearing. Then add an image control on the report design on a suitable place and use the newly added field and select MIME type as BMP, and deploy the report.

You can also follow the video as a guideline to this this post to complete the task.

Link to youtube video: https://youtu.be/iaKCD202jt0