Tuesday, November 28, 2023

How to add custom Execute action in logic app


 How to add custom Execute action in the logic app.



  • In the drop-down, the system will display standard OData execute actions. To enable this, we need to create an OData execute action. 
  • This involves creating a method in the data entity and adding the following attribute at the top of the method.

        [SysODataActionAttribute("Name", false)]

    •  For a static method, we will pass 'false.' For an instance method, we need to pass 'true'.

Below code I have written in my custom entity.
[SysODataActionAttribute("GetSalesStatus", false)]
public static str getStatus(str    salesId, DataAreaId company)
{
    str  status;

    changecompany (company)
    {
	status = enum2Str(SalesTable::find(salesId).SalesStatus);
    }

    return  status;
} 


After rebuilding the project, it appears in the drop-down.




Sales ID and company are the input parameters. After selecting the execute action, click on 'Add parameter,' then select these two parameters and assign their respective values





In the blog below, he has explained OData actions with different scenarios. Please check for further clarification.   
OData actions


Keep Daxing!!

Wednesday, November 22, 2023

Convert CSV file to JSON in Logic app

 Convert CSV file to JSON in Logic app.

This is the full step-by-step process, and I have provided its output below.


I used the below CSV file, separated by pipes ('|'), to test this.

Please follow the below steps.


        Step 1: I fetched the file from Blob by searching for the blob and selecting the 'Get blob content' action.


       Step 2: For this action, I provided the storage account name and file path.

 

        Step 3: To process the output content from the blob, I used the 'Compose' action and applied the 'Split' function.

         Expression:

        split(body('Get_blob_content_'),decodeUriComponent('%0D%0A'))


        Step 4: Additionally, I used another 'Compose' action to remove the last line, as it appeared empty. You can choose to include or omit this step based on your file structure.

            Expression:

        take(outputs('Split_by_line'),add(length(outputs('Split_by_line')),-1))


    Step 5: Another 'Compose' action was selected to split the text data based on the separator '|'.

        Expression:(separator '|')

        split(first(outputs('Split_by_line')), '|')

                                        

    Step 6:  Under data operations, I chose 'Select.' 

 

    • For the 'From' node, I used the specified expression

        skip(outputs('Remove_last_line'), 1)

    •  For the 'Map' node, I used the provided expression. (separator '|') 

            Left                                Right
        outputs('Fields')[0]	---	split(item(), '|')?[0]	
        outputs('Fields')[1]	---	split(item(), '|')?[1]
        outputs('Fields')[2]	---	split(item(), '|')?[2]
        outputs('Fields')[3]	---	split(item(), '|')?[3]

                               

Step 7: Finally, I selected the 'Parse JSON' action and provided the content from the outputs of the 'Select' step.      




  Outputs :

    Step 2 output:

    

Step 3 Output :

Step 4 Output :

  

Step 5 Output :

Step 6  Output :

 

                   Step 7 Output :

    

    

Reference: Link 1, Link 2


Keep Daxing!!

Tuesday, November 21, 2023

Create financial Dimension using x++ in D365FO

For creating Financial Dimensions we have 2 ways.


 container offsetConLoc  =   [_mainaccount, #empty, department, costcenter, #empty, project];
 container offsetConLoc  =   [_mainaccount,_businessunit,_site, region, department,_Vendor, #empty, #empty, _fixedasset];

Way 1:

    public DimensionDynamicAccount   generateLedgerDimension(container    _conData,   MainAccountNum  mainAccountNum)
    {
        int                                  hierarchyCount;
        int                                  hierarchyIdx;
        RecId                                dimAttId_MainAccount;
        LedgerRecId                          ledgerRecId;
        MainAccount                          mainAccount;
        RefRecId                             recordvalue;
        DimensionAttribute                   dimensionAttribute;
        DimensionAttributeValue              dimensionAttributeValue;
        DimensionSetSegmentName              DimensionSet;
        DimensionStorage                     dimStorage;
        LedgerAccountContract                LedgerAccountContract = new LedgerAccountContract();
        DimensionAttributeValueContract      ValueContract;
        List                                 valueContracts = new List(Types::Class);
        dimensionAttributeValueCombination   dimensionAttributeValueCombination;
 

        mainAccount     =  MainAccount::findByMainAccountId(_mainAccountNum);
        recordvalue     =  DimensionHierarchy::getAccountStructure(mainAccount.RecId,Ledger::current());
        hierarchyCount  =  DimensionHierarchy::getLevelCount(recordvalue);
        DimensionSet    =  DimensionHierarchyLevel::getDimensionHierarchyLevelNames(recordvalue);

 

        for(hierarchyIdx = 1;hierarchyIdx<=hierarchyCount;hierarchyIdx++)
        {
            if(hierarchyIdx == 1)
            {
                continue;
            }
            dimensionAttribute = DimensionAttribute::findByLocalizedName(DimensionSet[hierarchyIdx],false, #enus);

 

            if(dimensionAttribute)
            {
                dimensionAttributeValue =DimensionAttributeValue::findByDimensionAttributeAndValue(
                                                    dimensionAttribute,conPeek(_conData,hierarchyIdx));

                if(dimensionAttributeValue)
                {
                    ValueContract = new DimensionAttributeValueContract();
                    ValueContract.parmName(dimensionAttribute.Name) ;
                    ValueContract.parmValue(dimensionAttributeValue.CachedDisplayValue);
                    valueContracts.addEnd(ValueContract);
                }
            }
        }
        LedgerAccountContract.parmMainAccount(_mainAccountNum);
        LedgerAccountContract.parmValues(valueContracts);
        dimStorage                          =    DimensionServiceProvider::buildDimensionStorageForLedgerAccount(LedgerAccountContract);
        dimensionAttributeValueCombination  =    DimensionAttributeValueCombination::find(dimStorage.save());
        ledgerRecId                         =    dimensionAttributeValueCombination.RecId;

 

        return ledgerRecId;
    }


Way 2:

    public static void main(Args _args)
    {
	Class::generateLedgerDimension('110110', 
					'011', 
					'001', 
					'C-000002');
    }
	
    public static DimensionDynamicAccount generateLedgerDimension(
		MainAccountNum _mainAccount,
        str _department,
        str _businessUnit,
        str _customer)
    {
        DimensionAttributeValueSetStorage dimensionAttributeValueSetStorage 
            = new DimensionAttributeValueSetStorage();

        void addDimensionAttributeValue(
            DimensionAttribute _dimensionAttribute, 
            str _dimValueStr)
        {
            DimensionAttributeValue dimensionAttributeValue;

            if (_dimValueStr)
            {
                dimensionAttributeValue = 
                    DimensionAttributeValue::findByDimensionAttributeAndValueNoError(
                        _dimensionAttribute,
                        _dimValueStr);
            }

            if (dimensionAttributeValue.RecId != 0)
            {
                dimensionAttributeValueSetStorage.addItem(dimensionAttributeValue);
            }
        }

        DimensionAttribute dimensionAttribute;

        while select dimensionAttribute
            where dimensionAttribute.ViewName == tableStr(DimAttributeOMDepartment)
               || dimensionAttribute.ViewName == tableStr(DimAttributeOMBusinessUnit)
               || dimensionAttribute.ViewName == tableStr(DimAttributeCustTable)
        {
            switch (dimensionAttribute.ViewName)
            {
                case tableStr(DimAttributeOMDepartment):
                    addDimensionAttributeValue(dimensionAttribute, _departmentId);
                    break;

                case tableStr(DimAttributeOMBusinessUnit):
                    addDimensionAttributeValue(dimensionAttribute, _businessUnit);
                    break;

                case tableStr(DimAttributeCustTable):
                    addDimensionAttributeValue(dimensionAttribute, _customer);
                    break;
            }            
        }

        RecId defaultDimensionRecId = dimensionAttributeValueSetStorage.save();

        return LedgerDimensionFacade::serviceCreateLedgerDimension(
            LedgerDefaultAccountHelper::getDefaultAccountFromMainAccountRecId(
                MainAccount::findByMainAccountId(_mainAccount).RecId),
            defaultDimensionRecId);
    }


Merge Main account and ledger dimensions:

LedgerDimensionFacade::serviceMergeLedgerDimensions(_mainAccountLedgerDimensionRecid, _ledgerDimension)

Keep Daxing!!






Monday, November 20, 2023

The operation is not allowed by RBAC. If role assignments were recently changed, please wait several minutes for role assignments to become effective. Error in Azure

 The operation is not allowed by RBAC. If role assignments were recently changed, please wait several minutes for role assignments to become effective. Error in Azure.



            ·       To address the above error, please follow the process outlined below. 

          ·       Navigate to "Access Control (IAM)" and click on "Add."

        ·       Choose "Add Role Assignment."

        ·       Select "Key Vault Administrator" and click "Next."

        ·       Click on "Select members" and choose the user.

        ·       Click the "Select" button.



        ·       The selection will be displayed in the highlighted grid below. Click on "Review + assign."

        ·       Again, click on "Review + assign."

         ·        This action will resolve the error.


Keep Daxing!!

Encryption And Decryption Using A Symmetric Key(AES) using x++

 Encryption And Decryption Using A Symmetric Key(AES) using x++.


I received a requirement to generate an XML file with encrypted data using a symmetric key. The recipients on the other side will decrypt the text using the same symmetric key. To test this, I used a dummy value such as 'RRR'.

To achieve this, I wrote the code in C# and added the resulting DLL to my project references.


C# Code:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace EncryptionDecryptionUsingSymmetricKey
{
    public class AesOperation
    {
        public static string EncryptString(string key, string plainText)
        {
            byte[] iv = new byte[16];
            byte[] array;

            using (Aes aes = Aes.Create())
            {
                aes.Key = Encoding.UTF8.GetBytes(key);
                aes.IV = iv;

                ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream))
                        {
                            streamWriter.Write(plainText);
                        }

                        array = memoryStream.ToArray();
                    }
                }
            }

            return Convert.ToBase64String(array);// It will convert bytes to string
        }

        public static string DecryptString(string key, string cipherText)
        {
            byte[] iv = new byte[16];
            byte[] buffer = Convert.FromBase64String(cipherText); // It will convert string to bytes
using (Aes aes = Aes.Create()) { aes.Key = Encoding.UTF8.GetBytes(key); aes.IV = iv; ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); using (MemoryStream memoryStream = new MemoryStream(buffer)) { using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read)) { using (StreamReader streamReader = new StreamReader((Stream)cryptoStream)) { return streamReader.ReadToEnd(); } } } } } } }



X++ Code:

b14ca5898a4e4133bbce2ea2315a1916

Using EncryptionDecryptionUsingSymmetricKey;

internal final class TestRunnableClass1
{
    public static void main(Args _args)
    {
        str key = "b65ff7654brt8799fghj4ed7892b6798";
 
        str	text = 'RRR';
 
        str encryptedString = AesOperation::EncryptString(key, text);
 
        info(encryptedString);
 
        str decryptedString = AesOperation::DecryptString(key, encryptedString);
 
        info(decryptedString);
    }
}

Reference: click here

Keep daxing!!










Wednesday, November 15, 2023

Add File upload option in sys operation dialog in D365FO

         Add File upload option in sys operation dialog in D365FO. By using this we can upload CSV files and Excel files. This upload option I have added this in sys operation and batch jobs using the below code.

Dialog:



Code :

Using sys operation:

Controller:

class MyController extends SysOperationServiceController
{
    public void new()
    {
        super();
     
        this.parmClassName(classStr(MyService));
        this.parmMethodName(methodStr(MyService, processOperation));
     
        this.parmDialogCaption("Caption");
    }
	
    public ClassDescription caption()
    {
        return "Caption";
    }
	
    public static void main(Args args)
    {
	new MyController().startOperation();
    }
}

Contract:

[   DataContract,
    SysOperationContractProcessing(classStr(MyUIBuilder))
]
class MyContract
{
    container       storageResult;

    [DataMemberAttribute('StorageResult')]
    public container parmStorageResult(container _storageResult =  storageResult)
    {
        storageResult = _storageResult;
        return storageResult;
    }
}

UI Builder:

class MyUIBuilder extends SysOperationUIBuilder
{
    const str           OkButtonName = 'CommandButton';
    const str           FileUploadName = 'FileUpload';
	
    MyContract   contract;

    public void postBuild()
    {
        DialogGroup      dialogGroup;
        FormBuildControl formBuildControl;
        FileUploadBuild  dialogFileUpload;

        super();

        contract = this.dataContractObject();
        
        dialogGroup = dialog.addGroup("File path");
        formBuildControl = dialog.formBuildDesign().control(dialogGroup.name());
       
        dialogFileUpload = formBuildControl.addControlEx(classstr(FileUpload), FileUploadName);
        dialogFileUpload.style(FileUploadStyle::MinimalWithFilename);
        dialogFileUpload.baseFileUploadStrategyClassName(classstr(FileUploadTemporaryStorageStrategy));
        dialogFileUpload.fileTypesAccepted(".csv");
	//dialogFileUpload.fileTypesAccepted(".xlsx");
        dialogFileUpload.fileNameLabel("@SYS308842");
    }


    private void dialogEventsSubscribe(FormRun _formRun)
    {
        FileUpload fileUpload = _formRun.control(_formRun.controlId(FileUploadName));
		
        fileUpload.notifyUploadCompleted += eventhandler(this.uploadCompleted);
        fileUpload.notifyUploadAttemptStarted += eventhandler(this.uploadStarted);
		
        _formRun.onClosing += eventhandler(this.dialogClosing);
    }
	
    private void dialogClosing(xFormRun sender, FormEventArgs e)
    {
        this.dialogEventsUnsubscribe(sender as FormRun);
    }
	

    private void dialogEventsUnsubscribe(FormRun _formRun)
    {
        FileUpload fileUpload = _formRun.control(_formRun.controlId(FileUploadName));
        fileUpload.notifyUploadCompleted -= eventhandler(this.uploadCompleted);
        fileUpload.notifyUploadAttemptStarted -= eventhandler(this.uploadStarted);
		
        _formRun.onClosing -= eventhandler(this.dialogClosing);
    }
	
    protected void uploadCompleted()
    {
        var 					formRun 	 = this.dialog().dialogForm().formRun();
        FileUpload 				fileUpload 	 = formRun.control(formRun.controlId(FileUploadName));
        FileUploadTemporaryStorageResult 	uploadResult     = fileUpload.getFileUploadResult();

        if (uploadResult != null && uploadResult.getUploadStatus())
        {
            contract.parmStorageResult(uploadResult.pack());
        }

        this.setDialogOkButtonEnabled(formRun, true);
    }
	
    private void uploadStarted()
    {
        var formRun = this.dialog().dialogForm().formRun();
		
        this.setDialogOkButtonEnabled(formRun, false);
    }

    protected void setDialogOkButtonEnabled(FormRun _formRun, boolean _isEnabled)
    {
        FormControl okButtonControl = _formRun.control(_formRun.controlId(OkButtonName));
        if (okButtonControl)
        {
            okButtonControl.enabled(_isEnabled);
        }
    }

    public void postRun()
    {
        super();

        FormRun formRun = this.dialog().dialogForm().formRun();
        this.dialogEventsSubscribe(formRun);

        this.setDialogOkButtonEnabled(formRun, false);
    }
}

Service:

using System.IO;
using OfficeOpenXml;
using OfficeOpenXml.ExcelPackage;
using OfficeOpenXml.ExcelRange;
class MyService extends SysOperationServiceBase
{
    #File
    container               currentLine;
    CommaTextStreamIo       cSVStream;
    System.IO.Stream        stream;
    ExcelSpreadsheetName    sheeet;
    System.Exception        ex;
public void processOperation(MyContract _contract) { if (_contract.parmStorageResult() != conNull()) { FileUploadTemporaryStorageResult fileUploadResult = new FileUploadTemporaryStorageResult(); fileUploadResult.unpack(_contract.parmStorageResult()); if (fileUploadResult != null && fileUploadResult.getUploadStatus()) { try {     // CSV file code cSVStream = CommaTextStreamIo::constructForRead(File::UseFileFromURL(fileUploadResult.getDownloadUrl())); if (cSVStream.status() != IO_Status::Ok) { throw error(strfmt('Is not possible to open the file. Error %1',enum2str(cSVStream.status()))); } cSVStream.inFieldDelimiter("\,"); cSVStream.inRecordDelimiter("\n"); currentLine = cSVStream.read(); while(currentLine) { str id = conPeek(currentLine, 1); info(id); currentLine = cSVStream.read(); }     // Excel code     stream = fileUploadResult.openResult(); using (ExcelPackage Package = new ExcelPackage(stream)) { int rowCount, i; Package.Load(stream); ExcelWorksheet     worksheet = package.get_Workbook().get_Worksheets().get_Item(1); OfficeOpenXml.ExcelRange range = worksheet.Cells; rowCount = worksheet.Dimension.End.Row - worksheet.Dimension.Start.Row + 1; for (i = 2; i<= rowCount; i++) { str custAccount     = range.get_Item(i, 1).value; str id = range.get_Item(i, 2).value; int number = range.get_Item(i, 3).value; TranDate         date = range.get_Item(i, 4).value; }     } } catch { ex = CLRInterop::getLastException().GetBaseException();                     error(ex.get_Message()); } } } } }


Using Run base batch:

class MyBatch extends RunBaseBatch
{
    Filename        filename;
    dialog          dialog;

    #define.CurrentVersion(1)
    #define.Version1(1)
    #localmacro.CurrentList
        fileName
    #endmacro

    client server static ClassDescription description()
    {
        return 'Upload CSV file'; // or Excel file
    }

    protected boolean canRunInNewSession()
    {
        return false;
    }


    public Object dialog()
    {
        DialogGroup         dialogGroup;
        FormBuildControl    formBuildControl;
        FileUploadBuild     dialogFileUpload;
       // Set              enumSet = new Set(Types::Enum);
                
        dialog              = super();
        dialogGroup         = dialog.addGroup('File picker');
        formBuildControl    = dialog.formBuildDesign().control(dialogGroup.name());
        
        dialogFileUpload = formBuildControl.addControlEx(classstr(FileUpload), filename);
        dialogFileUpload.style(FileUploadStyle::MinimalWithFilename);
        dialogFileUpload.baseFileUploadStrategyClassName(classstr(FileUploadTemporaryStorageStrategy));
        dialogFileUpload.fileTypesAccepted('.csv');
        //dialogFileUpload.fileTypesAccepted('.xlsx');
dialogFileUpload.fileNameLabel('Select worker data file'); return dialog; } static void main(Args _args) { MyBatch objClass = new MyBatch();
if (objClass.prompt()) { objClass.runOperation(); } } public void run() { #File container currentLine; CommaTextStreamIo localStream; str textFile; FileUpload fileUploadControl = this.getFormControl(dialog, filename); FileUploadTemporaryStorageResult fileUploadResult = fileUploadControl.getFileUploadResult();         // CSV file if (fileUploadResult != null && fileUploadResult.getUploadStatus()) { textFile = fileUploadResult.getDownloadUrl(); } localStream = CommaTextStreamIo::constructForRead(File::UseFileFromURL(textFile)); if (localStream.status() != IO_Status::Ok) { throw error(strfmt('Is not possible to open the file. Error %1',enum2str(localStream.status()))); } localStream.inFieldDelimiter(','); while (localStream.status() == IO_Status::Ok) { currentLine = localStream.read(); if (!currentLine) { break; } try { Id = conPeek(currentLine, 1); Date = conPeek(currentLine, 2); // Remaining fields } catch (Exception::Error) { Throw (Exception::Error); } }         // Excel file                  stream = fileUploadResult.openResult();
       using (ExcelPackage Package = new ExcelPackage(stream))
       {
           int                         rowCount, i;
           Package.Load(stream);
						
           ExcelWorksheet  	       worksheet   = package.get_Workbook().get_Worksheets().get_Item(1);
           OfficeOpenXml.ExcelRange    range 	   = worksheet.Cells;
           rowCount                    = worksheet.Dimension.End.Row - worksheet.Dimension.Start.Row + 1;

           for (i = 2; i<= rowCount; i++)
           {
               str			custAccount     = range.get_Item(i, 1).value;
               str			id     		= range.get_Item(i, 2).value;
               int			number    	= range.get_Item(i, 3).value;
               TranDate   	        date      	= range.get_Item(i, 4).value;
	    }
    }
info('Success'); } protected FormControl getFormControl(DialogRunbase dialog, str controlName) { return dialog.formRun().control(_dialog.formRun().controlId( controlName)); } }


Using Job:

        AsciiStreamIo                                   file;
        Array                                           fileLines;
        FileUploadTemporaryStorageResult                fileUpload;
        fileUpload = File::GetFileFromUser() as FileUploadTemporaryStorageResult;
        file = AsciiStreamIo::constructForRead(fileUpload.openResult());

        if (file)
        {
            if (file.status())
            {
                throw error("@SYS52680");
            }

            file.inFieldDelimiter(',');
            file.inRecordDelimiter('\r\n');
        }

        container record;
        while (!file.status())
        {
            record = file.read();
            if (conLen(record))
            {
                info(strFmt("%1 - %2",conPeek(record,1),conPeek(record,2)));
            }
        }



Keep Daxing!!





Monday, October 30, 2023

How to fetch values from records to Include filter using x++ in D365FO

  Hi guys, I have a requirement to fetch the record to include filtered values from the report dialog.



I need to show those values in the report footer.

 Code :

Users can apply filters from the filter screen, and if any new tables are joined or new fields are added. To get those values I have used a query filter.



        str                         value;
	Query                       query;
	QueryFilter                 qf;

	query = qr.query();// getting query from query run.

	int  cnt = query.queryFilterCount();

	// Fetching data from query if any new tables are joined or new Fileds are added
	for (int ds =1; ds<= cnt; ds++)
	{
		qf = query.queryFilter(ds);

		if(qf.value())
		{
			value += strFmt('%1 : %2',
				            qf.field(),
					    qf.value());
		}
	}


The below code is to fetch data from the standard query.

        str                         value;
	Query                       query;
	QueryBuildDataSource        qbds;
	QueryBuildRange             qbr;
	int                         totalDS, totalRanges;

	query             = qr.query();// getting query from query run.
	totalDS           = query.dataSourceCount();

	// Used to fectch data from the standard view query 
	for (int ds =1; ds<= totalDS; ds++)
	{
	    qbds            = query.dataSourceNo(ds);
	    totalRanges     = qbds.rangeCount();

	    for (int r =1; r<= totalRanges; r++)
	    {
		qbr =  qbds.range(r);
			
		if (qbr.value())
		{
                    // Way 1 : 
		    value += strFmt('%1 : %2',
			                        qbr.prompt(),
						qbr.value());
									

		    // Way2 :
		    TableId     tableId 	=  qbds.table();//tableName2Id(qbds.AOTname());
		    DictTable   dictTable   = new SysDictTable(tableId);

		    str fieldname = qbr.AOTname();

		    if(qbr.value())
		    {
			MiscParameters += strFmt('%1 : %2  ',
					dictTable.fieldObject(fieldName2Id(tableId, qbr.fieldName())).label(),
					qbr.value());				   
		    }


		    // Way3 :
		    // Direct Field
		    FieldId  fieldId = qbr.field();
		    if(qbr.value())
		    {
			MiscParameters += strFmt('%1 : %2  ',
						        dictTable.fieldObject(fieldId).label(),
							qbr.value());						  
		    }


		    // Way4 :
		    str     value 	      = strFmt('%1', qbr.value());
		    Map     miscParametersMap = new Map(Types::String, Types::String);// Write this line at out side of loop

		    if (value)
		    {
			if (miscParametersMap.exists(qbr.prompt()))
			{
			    container   con = str2con(miscParametersMap.lookup(qbr.prompt()), ',' , false);

			    if (!conFind(con, value))
			    {
				con += value;
			    }

			    miscParametersMap.insert(qbr.prompt(), con2Str(con));
			}
			else
			{
			    miscParametersMap.insert(qbr.prompt(), value);
			}
		    }

		    if (miscParametersMap.elements())
		    {
			MapEnumerator   mapEnumerator = miscParametersMap.getEnumerator();

			while (mapEnumerator.moveNext())
			{
			    value += (strfmt("%1 : %2  ",
						mapEnumerator.currentKey(),
						mapEnumerator.currentValue()));
			}
		    }
		}
	    }
	}




Keep Daxing!!



Thursday, October 26, 2023

Merge multiple pdf’s into a single pdf using x++ in D365FO

 Got the requirement to Merge multiple pdf’s into a single pdf in D365FO.



To achieve this need to download the (pdfsharp)3rd party DLL from the below link 

https://www.dllme.com/dll/files/pdfsharp#dl.

once DLL is downloaded you have to add at the project reference node.


Write the below code.

using PdfSharp;
internal final class PFDRun
{
    public static void main(Args _args)
    { 
	PdfSharp.Pdf.PdfDocument 	outPutPDFDocument = new PdfSharp.Pdf.PdfDocument();
	PdfSharp.Pdf.PdfDocument 	inputPDFDocument  = new PdfSharp.Pdf.PdfDocument(); 
        PdfSharp.Pdf.PdfPages 		pdfPages;

        container con = ['D:\\Test1.pdf','D:\\Test2.pdf', 'D:\\Test3.pdf'];

        int i, j, pageCount;

	FileName 		pdfFile;
        InteropPermission 	permission;
        str 			errorMessage;

        try
        {
            permission = new InteropPermission(InteropKind::ClrInterop);

            permission.assert();

            for (i = 1; i <= 2; i++)
            {
		// I have written custom class to get the file stream from blob.
                System.IO.MemoryStream pdfStream = MyClass::GetStream(i);

                // pdfStream.Seek(0); // Ensure the stream is at the beginning

		// intialize With stream
                inputPDFDocument = PdfSharp.Pdf.IO.PdfReader::Open(pdfStream, PdfSharp.Pdf.IO.PdfDocumentOpenMode::Import);
				
		// intialize With file name(File to be stored in local folder)
		// pdfFile = conpeek(con, i);
                //inputPDFDocument = PdfSharp.Pdf.IO.PdfReader::Open(pdfFile, PdfSharp.Pdf.IO.PdfDocumentOpenMode::Import);

                outputPDFDocument.set_Version(inputPDFDocument.get_Version());

                pageCount = inputPDFDocument.get_PageCount();
                pdfPages  = inputPDFDocument.get_Pages();

                if (pageCount >0)
                {
                    if (pageCount == 1)
                    {
                        outputPDFDocument.AddPage(pdfPages.get_Item(0));
                    }
                    else
                    {
                        for (j = 1 ; j <= pageCount; j++)
                        {
                            outputPDFDocument.AddPage(pdfPages.get_Item(j-1));
                        }
                    }
                }
            }

            System.IO.MemoryStream outPutStream= new System.IO.MemoryStream();

            outputPDFDocument.Close();
            outputPDFDocument.Save('D:\\Output\\mergedFile.pdf'); // To save in local folder.
            outputPDFDocument.Save(outPutStream, false);

            file::SendFileToUser(outPutStream, 'mergedFile.pdf'); // To download

            CodeAccessPermission::revertAssert();
        }
        catch(Exception::CLRError)
        {
            errorMessage = AifUtil::getClrErrorMessage();

            CodeAccessPermission::revertAssert();

            throw error(errorMessage);
        }
    
    }

}

Sunday, October 22, 2023

Generate Payment Advice report using x++

  Generate Payment Advice report using x++.




Way 1: 
            The system will download the PDF using the Print setting.

Way 2 :

        Get the stream to send the mail or store it in the server. For this, we have to add the parm method in the contract class and assign the value in savePrintArchiveDetails method.


Code:

    public void generatePaymAdvice(LedgerJournalTrans _ledgerJournalTrans)
    {
        Filename                        fileName;
        BankPaymAdviceVendControllerV2  controller  = new BankPaymAdviceVendControllerV2();
        BankPaymAdviceContract          contract;
        SRSPrintDestinationSettings     settings;
        
        fileName = strFmt("%1_%2_PaymemtAdvice.pdf", _ledgerjournalTrans.JournalNum, _ledgerjournalTrans.Voucher);

        contract = BankPaymAdviceContract::newFromPaymentLine(_ledgerjournalTrans, false);
        contract.parmLedgerJournalTransRecId(_ledgerJournalTrans.RecId);

        controller.parmArgs(new Args());
        controller.parmReportName(ssrsReportStr(BankPaymAdviceVendV2, Report));
        controller.parmShowDialog(false);
        controller.parmLoadFromSysLastValue(false);
        controller.parmReportContract().parmRdpContract(contract);
        
        // Way 1
        settings = controller.parmReportContract().parmPrintSettings();
        settings.printMediumType(SRSPrintMediumType::Archive);
        settings.fileName(fileName);
        settings.fileFormat(SRSReportFileFormat::PDF);
        settings.overwriteFile(true);

        controller.startOperation();
    }

Way 2:
Contract class code:

    [ExtensionOf(classStr(SRSPrintArchiveContract))]
    public final class SRSPrintArchiveContract_Extension
    {
	public RefRecId printJobHeaderRecId;

	public RefRecId parmPrintJobHeaderRecId(RefRecId _printJobHeaderRecId = printJobHeaderRecId)
	{
	    printJobHeaderRecId = _printJobHeaderRecId;

    	    return printJobHeaderRecId;
	}

	public RecId savePrintArchiveDetails(container binData)
	{
	    RecId recId = next savePrintArchiveDetails(binData);

	    this.parmPrintJobHeaderRecId(recId);

	    return recId;
	}
    }


Call the below code after controller.Startoperation();

    

    DocuRef  		docuref;
    PrintJobHeader 	printJobHeader;
    select forupdate printJobHeader
        where printJobHeader.RecId == settings.parmSRSPrintArchiveContract().parmPrintJobHeaderRecId();
        
   select forupdate docuref
        where docuref.RefRecId == printJobHeader.RecId
           && docuRef.ActualCompanyId == curExt(); 	



			
   BinData 	        binData 	= new BinData();
   System.IO.Stream 	fileStream 	= DocumentManagement::getAttachmentStream(docuRef);

   File::SendFileToUser(fileStream, fileName);
		
   ttsbegin;
   printJobHeader.delete();
   docuref.delete();
   ttscommit;
    


Get the 'Stream' from docuref table:

 	DocuRef                         docuRef;
	DocuType                        DocuType;
	DocuValue                       docuValue;

	select * from docuRef
	    order by docuRef.createddatetime desc
	    exists join docuValue
		where docuValue.RecId == docuRef.ValueRecId
		    && docuValue.FileType =="PDF"
		    && docuRef.RefTableId == PurchTable.TableId
		    && docuRef.RefRecId      == PurchTable.RecId
		    && docuRef.RefCompanyId  == PurchTable.DataAreaId;
     System.IO.Stream 	fileStream  = DocumentManagement::getAttachmentStream(docuRef);

     File::SendFileToUser(fileStream, fileName);
    // Convert Stream into memory stream

     var memoryStream = new System.IO.MemoryStream();
     fileStream.CopyTo(memoryStream);

        


Keep Daxing!!