Tuesday, August 26, 2025

Strategy and Factory Pattern Combo

 Strategy and Factory Patterns 

These two design patterns go quite hand in hand. While strategy is a behavioral pattern, the factory is a creational pattern. 

Strategy Patterns: Allows us to create interchangeable code that can be changed at runtime. Basically we create an interface and multiple concrete classes implementing that interface each concrete class would have a algorithm to be applied . Any of these concrete classes can be used as long as they implement the interface.  

Factory Pattern: Allows us to create patterns without specifying the exact type. In this pattern we return an interface and the actual object. The core behavior of the factory pattern is that it allows creation of an object based on an interface and return the same. This works hand in hand with Strategy as factory can take care of creating the object based on the strategy (logic) that has to be applied.     

Thursday, August 21, 2025

Runtime Controls

 Runtime controls can provide a lot of flexibility and sometimes this is required. I spent a lot of time to understand the details of how this works. Below are the basics 

  • Runtime controls should be created 
In the below code snipped, a group control is being used to add all the controls to. As the same group is being used multiple times the code clears off all the controls at the start and then adds new controls. 

 
	    str                 controlName;
            FormStringControl   formBuildLookupControl, formBuildStringControl;
            FormIntControl      formBuildIntControl;
            FormDateControl     formBuildDateControl;
            FormRealControl     formBuildRealControl;
            FormGroupControl    FormBuildGroupControl;

            //unload all the existing controls within the given group
            while ( GroupVariables.controlCount() )
            {
                element.design().removeControl( GroupVariables.controlNum(1).id() );
            }

            _controlsMap = QtnPackageLineControls::loadControls( _pkgCode, _NewItem.data() );
            _controlsEnumerator = _controlsMap.getEnumerator();

            //add all the controls to the group
            while ( _controlsEnumerator.moveNext() )
            {
                QtnPackageLineParams    _controlParams = _controlsEnumerator.currentValue();
                controlName = _controlsEnumerator.currentKey();
            
                FormBuildGroupControl = GroupVariables.addControl( FormControlType::Group, strFmt("%1Group", controlName));
                FormBuildGroupControl.caption( _controlParams.parmControlLabel() );
                FormBuildGroupControl.visible(true);

                switch (_controlParams.parmControlType() )
                {
                    case QtnPackageConfigTypesEnum::Date:
                        FormBuildDateControl = FormBuildGroupControl.addControl( FormControlType::Date, controlName);
                        FormBuildDateControl.registerOverrideMethod( methodStr( FormDateControl, modified ), methodStr(QtnPackageConfigLookupValue, "DynamicsDateControl_modified"), runTimeMethods);
                        FormBuildDateControl.visible(true);

                        break;

                    case QtnPackageConfigTypesEnum::Integer:
                        formBuildIntControl = FormBuildGroupControl.addControl( FormControlType::Integer, controlName);
                        formBuildIntControl.registerOverrideMethod( methodStr( FormDateControl, modified ), methodStr(QtnPackageConfigLookupValue,"DynamicsIntControl_modified"), runTimeMethods);
                        formBuildIntControl.visible(true);
                        break;

                    case QtnPackageConfigTypesEnum::Lookup:
                        FormBuildStringControl = FormBuildGroupControl.addControl( FormControlType::String, controlName);
                        FormBuildStringControl.registerOverrideMethod(methodStr( FormStringControl, modified ),  methodStr(QtnPackageConfigLookupValue,"DynamicsStringControl_modified"), runTimeMethods);
                        FormBuildStringControl.registerOverrideMethod(methodStr( FormStringControl, lookup ),  methodStr(QtnPackageConfigLookupValue,"DynamicsStringControl_lookup"), runTimeMethods);
                        FormBuildStringControl.lookupOnly(true);
                        FormBuildStringControl.visible(true);
                        break;

                    case QtnPackageConfigTypesEnum::Real:
                        FormBuildRealControl = FormBuildGroupControl.addControl( FormControlType::Real, controlName);
                        FormBuildRealControl.registerOverrideMethod( methodStr( FormDateControl, modified ),methodStr(QtnPackageConfigLookupValue,"DynamicsRealControl_modified"), runTimeMethods);
                        FormBuildRealControl.visible(true);
                        break;

                    case QtnPackageConfigTypesEnum::String:
                        FormBuildStringControl = FormBuildGroupControl.addControl( FormControlType::String, controlName);
                        FormBuildStringControl.registerOverrideMethod(methodStr( FormStringControl, modified ),  methodStr(QtnPackageConfigLookupValue,"DynamicsStringControl_modified"), runTimeMethods);
                        FormBuildStringControl.visible(true);
                        break;
                }
            }

 

  • The registerOverrideMethod should be used to provide a callback method 
When the controls are created the override method is also provided. There are multiple parts to the override methods as shown below: 
   formButtonControl.registerOverrideMethod(
    methodStr(FormButtonControl,clicked), //method to override
    methodStr(testClass,testMethod),      //method to invoke
    new testClass());                     //object of class containing method

 The registerOverideMethod is called using an instance of a control, so it already knows which source object is being overloaded. The rest of the parameters are used as follows: 

  1. The first parameter identifies the method of this object to overload. The name of the method can be identified using the methodStr function; make sure to use the right control type class.  
  2. The second parameter the target method to be called instead 
  3. The third parameters identified the runtime object to find the target methods in

  • The next step is to create the callback method that would be called at runtime on the control being created and loaded 
    public boolean DynamicsIntControl_modified(FormControl _control)
    {
        boolean ret = true;
        
        info("control is modified");
        
        return ret;
    }

Below are some of the points to note about the runtime callback function:

  1.  This method should always have a parameter of type FormControl type.
  2.  The name of the method can be anything. The signature of the method can be determined by checking an actual method of a similar contol on the form.

Like in the above example we can create a modified method on a Integer type of control. Because the modified method of an integer control has a return value of boolean; we use the same return types in the callback method.

Tuesday, June 24, 2025

The number of defined parameters is not equal to the number of cell definitions in the parameter panel.

Every SSRS report has a parameters layout. The parameter layouts is where the parameters are presented to the user at the run time to drive the report behavior. The parameter layout has a layout with no of rows and columns that is used to present the parameters. If the no of parameters are more than the no of cells that the cross section of Rows and Columns can accommodate then we will have the following error: 

"The number of defined parameters is not equal to the number of cell definitions in the parameter panel."

Solution: 

In Dynamics we don't have a visual layout to control the parameters, hence we have to open the SSRS Report in XML format in order to edit the parameter layout. To open a report in XML format we need to right click the same and choose open with.. 

We should find the following section in the xml format and delete the same, so that the no of rows and columns are not defined and restricted. 

<ReportParametersLayout>
  <GridLayoutDefinition>
    <NumberOfColumns>3</NumberOfColumns> 
    <NumberOfRows>...</NumberOfRows>
  </GridLayoutDefinition>
</ReportParametersLayout> 

 

Wednesday, June 18, 2025

Add new design to Print Management

 Some of the document prints in the Dynamics Finance and Operations application have been configured as a part of the Print management framework. 

The print management framework makes it possible that the design of a document print is configurable. However, this also restricts the configuration to be picked from the set of reports that are configured to be a part of the framework. 

Hence, if a new design is being prepared for a document that needs to be configured in the document print management framework, then the following steps should be followed to register the new layout in the system. 

Find the PrintMgmtDocType class and create a subscriber to the getDefaultReportFormatDelegate method. 


       

class AFZPrintMgmtDocType_EventHandler
{
    [SubscribesTo(classstr(PrintMgmtDocType), delegatestr(PrintMgmtDocType, getDefaultReportFormatDelegate))]
    public static void getDefaultReportFormatDelegate(PrintMgmtDocumentType _docType, EventHandlerResult _result)
    {
        switch (_docType)
        {
            case PrintMgmtDocumentType::SalesFreeTextInvoice:
                _result.result(ssrsReportStr(AFZFreeTextInvoice, Report));
                break;

            case PrintMgmtDocumentType::SalesOrderInvoice:
                _result.result(ssrsReportStr(AFZSalesInvoice, Report));
                break;
        }
    }

}

There is a bug in the base system when a new DocType is registered in PrintManagement, the latest report registered becomes the default report. 

The default report is printed when we use the Original Preview option in the journal prints. 



If we wish to have a different report as the default report we can change the same from a backend table PrintMgmtReportFormat. 

This table has a row for each DocumentType that has been registered in the system and for each documentType there would be only one row that marked as System. If we want to change the default report then we have to switch on the system column for that report layout. 




Tuesday, November 26, 2024

Get FieldID TableID in SQL

 There are two important views that can be used in SQL scripts to refer to X++ TableID and FieldID values. 

SysTableIDView: In this table the ID column refers to TableID which can be fetched using the name of the table in the Name column.

SysTableFieldIDView: In this table the ID columns refers to the TableID for which the fields are being fetched and the FieldID column refers to the ID of the field in the given table. 

To fetch all the fields in a give table we can use the following SQL 


       
select TID.ID TableID, TID.Name TableName, FID.Name FieldName , FID.FIELDID
from SYSTABLEIDVIEW TID
inner join SYSTABLEFIELDIDVIEW FID
	on FID.Id = TID.Id
where TID.Name = 'InventDim'
 

Tuesday, September 17, 2024

SQL View for Dimension

 The following SQL view can be used to extract dimension information using SQL 


1. DefaultDimensionView: for default dimension each dimension value is listed as a separate row. Hence, we  might need multiple joins for one each dimension to get values in a columnar format. 

2. DimensionAttributeValueCombination: For Ledger dimension. Each dimension would exist as a column in this view. 

Thursday, September 12, 2024

Choose design patterns

 As you learn more and more design patterns it becomes increasingly difficult to understand which one to use in a given scenario. 

Below are some of the observations:

CoR Vs Decorator:

CoR (Chain of Responsibility): To be used when there is a sequence of activities to be performed in a given sequence. These activities and their sequence can change based on a given business scenario. Each activity is a done by a separate class in the chain and we can add new classes as and when they are discovered. 

Decorator: Initially I was quite confused between CoR and Decorator as both of them provided classes that can be added to a base object and these can be changed at runtime. I later realized that the difference between the two is that while Decorator is designed to change the behavior of the base object, CoR is designed to perform different activities with each class. Which means that if the given functionality has to be enhanced based on conditions then decorators should be used, and if different functionalities have to be triggered in a given sequence CoR should be used. 

Summary: Decorators are additive in nature and every decorator handles every message. This is the basic behavior of the pattern. Hence when a client wishes to enhance one or more additional behaviors decorator is used. Decorator would exhibit at least a minimum of two behaviors.

Chain of Responsibility will exhibit one behavior or less. The potential for multiple links to handle one message is possible but not the intention. Hence ideally the cardinality is zero to one so when a CoR pattern is being used the message should be processed by one chain.


Strategy Vs Visitor

Strategy: Involves a class objects being induced into a method as a parameter and this contains an algorithm to do a certain process in a certain way. By parameterizing the algorithm the functionality can be enhanced later to accommodate new algorithms. 

Visitor: Visitor involves the current object being passed as an argument to the visitor class method and later invoking a specific method in the visitor class which can then access the current objects and perform any desired operation. 

So while strategy is used to change the behaviour, visitor is used to add new behavior