Tuesday, September 26, 2023

How to add destination Tab in sys operation batch job in D365FO

I have a requirement to generate multiple reports within a batch job. The user would like the option to specify the destination tab in the system operation dialog. Based on their inputs, the system will generate the selected report.

I have followed the standard class "AssetTransferMassController".

In the controller class, I have written the below code. 

class MyController extends SysOperationServiceController implements BatchRetryable
{
    #AssetTransfer

    public static void main(Args args)
    {
        MyController      controller =  new MyController();

        controller.parmExecutionMode(SysOperationExecutionMode::Synchronous);
        controller.startOperation();

        if (!controller.isDialogCancelled())
        {
            MyContract contract = controller.getDataContractObject('_contract') as MyContract;
            
            // This code I have written If user want to open report in sreen or file.
            if (controller.parmExecutionMode() != SysOperationExecutionMode::ScheduledBatch)
            {
                sRSPrintDestinationSettings sRSPrintDestinationSettings = 
                                                controller.getDataContractObject('SRSPrintDestinationSettings') 
                                                                                as SRSPrintDestinationSettings;
            
                MyService::loopRecords(contract, sRSPrintDestinationSettings);
            }
        }
    }  

    public void new()
    {
        super(classStr(MyService), methodStr(MyService, process));

        this.parmDialogCaption("Test multiple reports");
    }

    protected void dialogPostRun()
    {
        super();

        this.updateDesign();
    }

    public boolean isDialogCancelled()
    {
        return dialogCanceled;
    }

    protected boolean canRunInNewSession()
    {
        return true;
    }

    public boolean showPrintSettings()
    {
        return true;
    }
    
    // with this method we are enable the group.
    public void updateDesign()
    {
        FormRun                     formRun;
        FormBuildGroupControl       groupBuildControl;
        FormGroupControl            groupControl;
        SysOperationDialog          sysOperationDialog;

        sysOperationDialog  = dialog as SysOperationDialog;
        formRun             = sysOperationDialog.formRun();

        groupBuildControl   = formRun.form().design().control(#CurrentPrintDestinationControlName);
        groupControl        = formRun.design().control(groupBuildControl.id());
        groupControl.visible(true);
    }

    protected boolean validate()
    {
        boolean                     isValid;

        isValid = super() && this.validateReportPrintSetting();

        return isValid;
    }

    private boolean validateReportPrintSetting()
    {
        boolean                     isValid = true;
        SRSPrintDestinationSettings printSettings;

        if (this.batchInfo() && this.batchInfo().parmBatchExecute())
        {
            printSettings = this.getDataContractObject('SRSPrintDestinationSettings') as SRSPrintDestinationSettings;

            if (printSettings && printSettings.printMediumType() == SRSPrintMediumType::Screen)
            {
                isValid = checkFailed("@SYS329665");
            }
        }

        return isValid;
    }

    [Hookable(false)]
    final boolean isRetryable()
    {
        return true;
    }

}


In the service class's process method, I have added an input parameter SRSPrintDestinationSettings. When generating the report, I am assigning the print destination settings.

class MyService extends SysOperationServiceBase
{    
    public void process(MyContract _contract, SRSPrintDestinationSettings sRSPrintDestinationSettings)
    {
        if (this.isExecutingInBatch())
        {
            MyService::loopRecords(_contract, sRSPrintDestinationSettings);
        }
    }

    public static void loopRecords(MyContract _contract, SRSPrintDestinationSettings sRSPrintDestinationSettings)
    {
        System.Exception        ex;
        QueryRun                queryRun;

        queryRun = new queryRun(_contract.getQuery());

        while(queryRun.next())
        {
            try
            {
                // generate code
                Common  myTable = queryRun.get(tableNum(Common));
                
                MyService::generateReport(myTable, sRSPrintDestinationSettings);
            }
            catch
            {
                ex = CLRInterop::getLastException().GetBaseException();

                continue;
            }
        }
    }

    public static void generateReport(Common _table, SRSPrintDestinationSettings _sRSPrintDestinationSettings)
    {
        SRSPrintDestinationSettings settings;
        Filename                    fileName    = strFmt("TestReport");
        SrsReportRunController      controller  = new SrsReportRunController();
        MyReportContract            contract    = new MyReportContract();

        controller.parmArgs(new Args());
        controller.parmReportName('report name');
        controller.parmShowDialog(false);
        controller.parmLoadFromSysLastValue(false);
        controller.parmReportContract().parmRdpContract(contract);

        //controller.parmReportContract().parmPrintSettings(_sRSPrintDestinationSettings);
        // --- or----
        settings = controller.parmReportContract().parmPrintSettings();
        settings.printMediumType(_sRSPrintDestinationSettings.printMediumType());
        settings.fileName(fileName);
        settings.fileFormat(_sRSPrintDestinationSettings.fileFormat());
        settings.overwriteFile(true);

        controller.startOperation();
    }
}


Keep daxing!!





How to loop JSON through an array in a logic app?

 Hi guys, 

I have a requirement to update multiple records in F&O and send a response for each record using a logic app.


Please find the request and response in the below screenshot.



To archive my process I have followed the below steps.


1. I have utilized the 'When a HTTP request is received' trigger.



2. Initialized the 2 variables(Array, string type).


3. I have used a 'for-each' loop to iterate through each record, passing the HTTP response body as input. Inside the loop, I have parsed the current record.

4. After parsing, I updated the record in F&O using the 'update record' trigger.

5. If an update is successful, I am assigning a success status to the 'message' variable.
    If the update fails, I am parsing the error and sending it to the 'message' variable.

  •     While parsing the error I have used the below JSON.
    {
	"status": 400,
	"message": "An error has occurred.  Write failed for table row of type 'CustCustomerV3Entity'. Infolog: Warning: ",
	"error": {
		"message": "An error has occurred.  Write failed for table row of type 'CustCustomerV3Entity'. Infolog: Warning:"
	"source": "URL"
    }
    To fetch the above error use the below expression or parse JSON.
    outputs('Update_record')?['error']?['code'] 
    Status code:
    outputs('Update_record')?['statusCode']
  •  Afterward, I use 'Append to array variable' to create the response JSON and store it in the array.

6. Configure run after options for Append to the array variable has set up in the below.

7. After the for loop, I used the 'HTTP Response' action. In the response body, I converted the array into a JSON string.
json(string(variables('OutputJson')))

8. Open the 'For each' setting and setup like the below screenshot.



The output you will find in 1st screenshot.


Keep Daxing!!

Tuesday, September 12, 2023

Multi select workflow using x++

 I got a requirement to submit multiple records to workflow.

For this, I have created multiple buttons like below.

Submit Code:

#define.WorkFlowTemplateName("Template Name")  
// or -- workFlowTypeStr(Template name)

if (common.WorkflowApprovalStatus == WFApprovalStatus::Draft)
{
    Workflow::activateFromWorkflowType(#WorkFlowTemplateName, common.RecId, 
	                                'Workflow submitted by button', false, Curuserid());
}

Approval Code:
WorkflowWorkItemTable   WorkflowWorkItemTable;

// main code
select firstonly WorkflowWorkItemTable
    where workflowWorkItemTable.Type == WorkflowWorkItemType::WorkItem
	&& workflowWorkItemTable.Status == WorkflowWorkItemStatus::Pending // this should be Pending
	&& WorkflowWorkItemTable.RefTableId == tableNum(common)
	&& WorkflowWorkItemTable.RefRecId  == common.RecId;

if (WorkflowWorkItemTable)
{
    WorkflowWorkItemActionManager::dispatchWorkItemAction(WorkflowWorkItemTable,
							"Mulitple Approve by button", 
							curuserId(), 
                                                        WorkflowWorkItemActionType::Complete,
							menuitemActionStr("Approve menu item name"));
}


Reject Code:
WorkflowWorkItemTable   WorkflowWorkItemTable;

// main code
select WorkflowWorkItemTable
    where workflowWorkItemTable.Type == WorkflowWorkItemType::WorkItem
	&& workflowWorkItemTable.Status == WorkflowWorkItemStatus::Pending
	&& WorkflowWorkItemTable.RefTableId == tableNum(Common)
	&& WorkflowWorkItemTable.RefRecId  == common.RecId;

if (WorkflowWorkItemTable)
{
    ttsbegin;
    WorkflowWorkItemActionManager::dispatchWorkItemAction(WorkflowWorkItemTable,
							    "Mulitple Reject by button", 
							    curuserId(), 
							    WorkflowWorkItemActionType::Return,
							    menuitemActionStr('Reject menu item Name'));
    ttscommit;
}


Recall Code:
#define.WorkFlowTemplateName("Template Name")  
// or -- workFlowTypeStr(Template name)

if (common.WorkflowApprovalStatus == WFApprovalStatus::Submitted 
    || common.WorkflowApprovalStatus == WFApprovalStatus::PendingApproval)
{
    WorkflowCorrelationId	workflowCorrelationId = Workflow::activateFromWorkflowType(#WorkFlowTemplateName, 
											    common.RecId, 
                                                                                            'By button RecallAllToWorkflow', 
											    true, Curuserid());
   
    Workflow::cancelWorkflow(workflowCorrelationId,"Mulitple Recalled by user");
}


Button Clicked method Code:
    For multi-select, we need to loop the record by record so for that I have used the button clicked. Instead of this, we can create a new class and write in the main method.

void clicked()
{   
    FormDataSource      Common_ds = element.Common_ds;
    Common    	       common    = Common_ds.cursor(); 
    System.Exception    ex;

    next clicked();      

    if (Common_ds.Anymarked())
    {
        for (common = getFirstSelection(Common_ds); common; common = Common_ds.getNext())
	{
	    try
	    {
	        // call the Approve or submit or Reject code 
	    }    
	    catch
	    {
		ex = CLRInterop::getLastException().GetBaseException();
		error(ex.get_Message());
	    }
	}
    }
	
    Common_ds.research(true);
}

I have set the image for the button group using the below properties.




Keep daxing!!











Monday, September 11, 2023

Fetch the Format of financial dimension formats for data entities using X++

 Fetch the Format of financial dimension formats for data entities using X++.



I have a requirement to fetch the value mentioned in the above SC and the below one.

BusinessUnit-CostCenter-Department-Campaign-Center-Customer-Project-Store


To get this I have used the Dimension format type  and Active for type


Way 1:

    private container getFDFromParameters()
    {
	DimensionHierarchy              dimensionHierarchy;
	DimensionHierarchyIntegration   dimensionHierarchyIntegration;
	container                       con;
	str				delimiter = DimensionParameters::getDimensionSegmentDelimiter();
	
	select firstonly DisplayString from dimensionHierarchyIntegration
	    exists join dimensionHierarchy
		where dimensionHierarchy.RecId == dimensionHierarchyIntegration.DimensionHierarchy
		    && dimensionHierarchy.StructureType == DimensionHierarchyType::DataEntityDefaultDimensionFormat
		    && dimensionHierarchyIntegration.IsDefault == true;

	return str2con(dimensionHierarchyIntegration.DisplayString, delimiter);
    }


Way 2:

    private container getFDFromParameters()
    {
	DimensionHierarchyLevel         dimensionHierarchyLevel;
	DimensionHierarchy              dimensionHierarchy;
	DimensionHierarchyIntegration   dimensionHierarchyIntegration;
	container                       con;

	while select dimensionHierarchyLevel order by dimensionHierarchyLevel.Level
		exists join dimensionHierarchy
		    where dimensionHierarchy.RecId == dimensionHierarchyLevel.DimensionHierarchy
		exists join dimensionHierarchyIntegration
		    where dimensionHierarchyIntegration.DimensionHierarchy == dimensionHierarchy.RecId
	                && dimensionHierarchyIntegration.IsDefault == true
			&& dimensionHierarchy.StructureType == DimensionHierarchyType::DataEntityDefaultDimensionFormat
	{
	    con += DimensionAttribute::find(dimensionHierarchyLevel.DimensionAttribute).Name;
	}

	return con;
    }


Output:




Keep Daxing!!