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!!