Thursday, April 28, 2022

D365 F&O Showing posts with label COC and Method Wrapping in D365

 

Class extension via method wrapping and Chain of Command (CoC)

The functionality for class extension, or class augmentation, has been improved in Microsoft Dynamics 365 for Finance and Operations. You can now wrap logic around methods that are defined in the base class that you're augmenting. 
  • You can extend the logic of public and protected methods without having to use event handlers. 
  • When you wrap a method, you can also access public and protected methods, and variables of the base class. 
In this way, you can start transactions and easily manage state variables that are associated with your class.

For example, a model contains the following code.

class BusinessLogic1 { str DoSomething(int arg) { … } }

You can now augment the functionality of the DoSomething method inside an extension class by reusing the same method name. An extension class must belong to a package that references the model where the augmented class is defined.

[ExtensionOf(ClassStr(BusinessLogic1))] final class BusinessLogic1_Extension { str DoSomething(int arg) { // Part 1 var s = next DoSomething(arg + 4); // Part 2 return s; } }

In this example, the wrapper around DoSomething and the required use of the next keyword create a Chain of Command (CoC) for the method. CoC is a design pattern where a request is handled by a series of receivers. The pattern supports loose coupling of the sender and the receivers.

We now run the following code.

BusinessLogic1 c = new BusinessLogic1(); info(c.DoSomething(33));

When this code is run, the system finds any method that wraps the DoSomething method. The system randomly runs one of these methods, such as the DoSomething method of the BusinessLogic1_Extension class. When the call to the next DoSomething method occurs, the system randomly picks another method in the CoC. If no more wrapped methods exist, the system calls the original implementation.

Capabilities

The following sections give more details about the capabilities of method wrapping and CoC.

Wrapping public and protected methods

Protected or public methods of classes, tables, or forms can be wrapped by using an extension class that augments that class, table, or form. The wrapper method must have the same signature as the base method.
  • When you augment form classes, only root-level methods can be wrapped. You can't wrap methods that are defined in nested classes.
  • Only methods that are defined in regular classes can be wrapped. Methods that are defined in extension classes can't be wrapped by augmenting the extension classes.

What about default parameters?

Methods that have default parameters can be wrapped by extension classes. However, the method signature in the wrapper method must not include the default value of the parameter.
For example, the following simple class has a method that has a default parameter.
class Person
{ Public void salute( str message = "Hi"){ } }

In this case, the wrapper method must resemble the following example.
[ExtensionOf(classtr(Person))]
final class aPerson_Extension { Public void salute( str message ){ } }
In the aPerson_Extension extension class, notice that the salute method doesn't include the default value of the message parameter.

Wrapping instance and static methods

Instance and static methods can be wrapped by extension classes. If a static method is the target that will be wrapped, the method in the extension must be qualified by using the static keyword.
For example, we have the following A class.
class A { public static void aStaticMethod( int parameter1) { // … } }
In this case, the wrapper method must resemble the following example.
[ExtensionOf(classstr(A)] final class An_Extension { public static void aStaticMethod( int parameter1) { Next aStaticMethod( 10 ); } }

Wrapper methods must always call next

Wrapper methods in an extension class must always call next, so that the next method in the chain and, finally, the original implementation are always called. This restriction helps guarantee that every method in the chain contributes to the result.
In the current implementation of this restriction, the call to next must be in the first-level statements in the method body.
Here are some important rules:
  • Calls to next can't be done conditionally inside an if statement.
  • Calls to next can't be done in whiledo-while, or for loop statements.
  • next statement can't be preceded by a return statement.
  • Because logical expressions are optimized, calls to next can't occur in logical expressions. At runtime, the execution of the complete expression isn't guaranteed.

Wrapping a base method in an extension of a derived class

The following example shows how to wrap a base method in an extension of a derived class. For this example, the following class hierarchy is used.
class A
{ public void salute(str message) { Info(message); } } class B extends A { } class C extends A { }

Therefore, there is one base class, A. Two classes, B and C, are derived from A. We will augment or create an extension class of one of the derived classes (in this case, B), as shown here.
[Extensionof(classstr(B))]
final class aB_Extension { public void salute(str message) { next salute( message ); Info("B extension"); } }

Although the aB_Extension class is an extension of B, and B doesn't have a method definition for the salute method, you can wrap the salute method that is defined in the base class, A. Therefore, only instances of the B class will include the wrapping of the salute method. Instances of the A and Cclasses will never call the wrapper method that is defined in the extension of the B class.
This behavior becomes clearer if we implement a method that uses these three classes.
class ProgramTest
{ Public static void Main( Args _args) { var a = new A( ); var b = new B( ); var c = new C( ); a.salute("Hi"); b.salute("Hi"); c.salute("Hi"); } }
For calls to a.salute(“Hi”) and c.salute(“Hi”), the Infolog shows only the message “Hi.” However, when b.salute(“Hi”) is called, the Infolog shows “Hi” followed by “B extension.”
By using this mechanism, you can wrap the original method only for specific derived classes.

Accessing protected members from extension classes

As of Platform update 9, you can access protected members from extension classes. These protected members include fields and methods. Note that this support isn't specific to wrapping methods but applies all the methods in the class extension. Therefore, class extensions are more powerful than they were before.

The Hookable attribute

If a method is explicitly marked as [Hookable(false)], the method can't be wrapped in an extension class. In the following example, anyMethod can't be wrapped in a class that augments anyClass1.
class anyClass1 { [HookableAttribute(false)] public void anyMethod() {…} }

Final methods and the Wrappable attribute

Public and protected methods that are marked as final can't be wrapped in extension classes. You can override this restriction by using the Wrappable attribute and setting the attribute parameter to true ([Wrappable(true)]). Similarly, to override the default capability for (non-final) public or protected methods, you can mark those methods as non-wrappable ([Wrappable(false)]).
In the following example, the doSomething method is explicitly marked as non-wrappable, even though it's a public method. The doSomethingElse method is explicitly marked as wrappable, even though it's a final method.
class anyClass2 { [Wrappable(false)] public void doSomething(str message) { …} [Wrappable(true)] final public void doSomethingElse(str message){ …} }

Restrictions on wrapper methods

The following sections describe restrictions on the use of CoC and method wrapping.

Kernel methods can't be wrapped

Kernel classes aren't X++ classes. Instead, they are classes that are defined in the kernel of the Microsoft Dynamics 365 Unified Operations platform. Even though extension classes are supported for kernel classes, method wrapping isn't supported for methods of kernel classes. In other words, if you want to wrap a method, the base method must be an X++ method.

X++ classes that are compiled by using Platform update 8 or earlier

The method wrapping feature requires specific functionality that is emitted by an X++ compiler that is part of Platform update 9 or later. Methods that are compiled by using earlier versions don't have the infrastructure to support this feature.

Nested class methods (forms) can't be wrapped

The concept of nested classes in X++ applies to forms for overriding data source methods and form control methods. Methods in nested classes can't be wrapped in class extensions.

D365 F&O Get formRun, Form control, datasource and selected record from form datasource using Eventhandlers on Form in D365

 Get formRun, Form control, datasource and selected record from form datasource :


[FormDataSourceEventHandler(formDataSourceStr(MyForm, MyRandomTableDS), FormDataSourceEventType::Written)] public static void MyRandomTableDS_OnWritten(FormDataSource sender, FormDataSourceEventArgs e)
{

FormRun formRun = sender.formRun() as FormRun;

// you can even call custom methods
formRun.myCustomMethod();

// Get the selected datasource record
TableName tableBuffer = sender.cursor();

// Get datasource variable
FormDataSource DSVariable = sender.formRun().dataSource(“TableName”);
}

Get form datasource from xFormRun
[FormEventHandler(formStr(SomeForm), FormEventType::Initialized)] public static void SomeForm_OnInitialized(xFormRun sender, FormEventArgs e)
{
FormDataSource MyRandomTable_ds = sender.dataSource(formDataSourceStr(SomeForm, MyRandomTableDS));

}

Access form control from xFormRun
[FormEventHandler(formStr(SomeForm), FormEventType::Initialized)] public static void SomeForm_OnInitialized(xFormRun sender, FormEventArgs e)
{
// set the control to invisible as an example
sender.design().controlName(formControlStr(SomeForm, MyControl)).visible(false);
}

Get FormRun from form control
[FormControlEventHandler(formControlStr(MyForm, MyButton), FormControlEventType::Clicked)] public static void MyButton_OnClicked(FormControl sender, FormControlEventArgs e)
{
FormRun formRun = sender.formRun() as FormRun;
formRun.myCustomMethod();
}

Get current record in form control event
[FormControlEventHandler(formControlStr(SomeForm, SomeButton), FormControlEventType::Clicked)] public static void SomeButton_OnClicked(FormControl sender, FormControlEventArgs e)
{
// as an example the datasource number is used for access; I perceive the formDataSourceStr as more robust
SomeTable callerRec = sender.formRun().dataSource(1).cursor();
}

D365 F&O / AX 2012 SysQuery functions/ query functions


Instead of direct assignment of the values to the query build range value, we can use SysQuery class methods to avoid the errors of datatype conversion. So here in this post I gave some useful methods.

QueryBuildRange qbr;

---To set the value we usually follow as below mentioned code

qbr.value(“Our Value”);

qbr.value(queryValue(“Our Value”));


We can set the value using SysQuery methods like mentioned below

qbr.value(SysQuery::value(“Our Value”));

qbr.value(SysQuery::valueLike(“Our Value”));


For logical NOT

qbr.value(SysQuery::valueNot(“Our Value”));


To retrieve all records

in usual way we can give like empty (” “) but using sysquery class

qbr.value(SysQuery::valueUnlimited());


*  To retrieve Null records

qbr.value(SysQuery::valueEmptyString());


To retrieve Not Null records

qbr.value(SysQuery::valueNotEmptyString());


To give range

usually we add qbr.value(fromDate,toDate)  but using sysQuery

qbr.value(SysQuery::range(fromDate,toDate);

The method will add the dots (‘..’)  like this


To find total number of records available in the resulting query

info(strFmt(“%1”,SysQuery::countTotal(queryRun)));


To find total number of Datasource available in the resulting query

info(strFmt(“%1”,SysQuery::countLoops(queryRun)));


For Datasource and Range creation

SysQuery::findOrCreateRange(parameter);

SysQuery::findOrCreateDataSource(parameter);

SysQuery::findOrCreateDataSourceByName(parameter);


Tuesday, April 26, 2022

D365 F&O Filter records in a form by code using extensions in d365 F&O x++

  In this case filtering vendor invoice journals by creating an extension class in the form datasource -> init() method


trying to filter vendor invoices with SAR currency code


[ExtensionOf(formDataSourceStr(vendinvoicejournal, VendInvoiceJour))]

final class RKFormDS_VendInvoiceJournal_Extension

{

    public void init()

    {

        next init();

        

        str formName =  this.formRun().name();

        str callerName =  this.formRun().args().callerName();


        if(formName == 'VendInvoiceJournal' && callerName == 'RKVendInvoiceJournal')

        {

                          this.query().dataSourceName(this.name()).addRange(fieldnum(VendInvoiceJour,              Currency)).value(SysQuery::valueLike("SAR "));

            }

          

        }

    }

D365 F&O / AX 2012 - Account Payable settle payment with invoice x++

 Hello Guys..!


Here I'm sharing code for Settle Vendor Invoice/Payment through X++  

Code:
-----------
static void VendorSettlement(Args _args)
{
VendTable vendTable;
VendTrans invVendTrans, payVendTrans;
SpecTransManager manager;
CustVendTransData custVendTransData;
;
vendTable = VendTable::find("12345");
// Find the oldest unsettled invoice
select firstonly invVendTrans  order by TransDate asc
                            where invVendTrans.AccountNum == vendTable.AccountNum &&
                            invVendTrans.TransType == LedgerTransType::Purch &&
                            !invVendTrans.closed;
// Find the oldest unsettled payment
select firstonly payVendTrans order by TransDate asc
                    where payVendTrans.AccountNum == vendTable.AccountNum &&
                    payVendTrans.TransType == LedgerTransType::Payment &&
                    !payVendTrans.closed;
ttsbegin;
// Create an object of the CustVendTransData class with the invoice transaction as parameter
custVendTransData = CustVendTransData::construct(invVendTrans);
// Mark it for settlement
custVendTransData.markForSettlement(vendTable);
// Create an object of the CustVendTransData class with the payment transaction as parameter
custVendTransData = CustVendTransData::construct(payVendTrans);
//mark it for settlement
custVendTransData.markForSettlement(vendTable);
ttscommit;
// Settle all marked transactions
if(VendTrans::settleTransact(vendTable, null, true,SettleDatePrinc::DaysDate, systemdateget()))
info("Transactions settled");
}

reference Click Here

D365 F&O / AX 2012 Create a payment journal x++ - Account Payable



LedgerJournalTable jourTable;

    LedgerJournalTrans jourTrans;

    LedgerJournalTableData jourTableData;

    JournalTransData jourTransData;

    LedgerJournalStatic jourStatic;

    DimensionDynamicAccount ledgerDim;

    DimensionDynamicAccount offsetLedgerDim;

    NumberSeq numberseq;


ttsBegin;

    ledgerDim = DimensionStorage::getDynamicAccount('29901-00162',LedgerJournalACType::Vend);

    offsetLedgerDim = AxdDimensionUtil::getLedgerAccountId(['1213011','1213011',1,'Vendor','29901-00162']); //DimensionStorage::getDynamicAccount('NBE.SAR',LedgerJournalACType::Bank);


    jourTableData = JournalTableData::newTable(jourTable);

    jourTable.JournalNum = jourTableData.nextJournalId();

    jourTable.JournalType = LedgerJournalType::Payment;

    jourTable.JournalName = '207';

    jourTableData.initFromJournalName(

    LedgerJournalName::find(jourTable.JournalName));

    jourStatic = jourTableData.journalStatic();

    jourTransData = jourStatic.newJournalTransData(

    jourTrans,

    jourTableData);

    jourTransData.initFromJournalTable();


    jourTrans.CurrencyCode ='SAR';

    jourTrans.initValue();

   numberseq = NumberSeq::NewGetVoucherFromCode('207');

    jourTrans.TransDate = systemDateGet();

    jourTrans.AccountType = LedgerJournalACType::Vend;

    jourTrans.LedgerDimension = ledgerDim;

    jourTrans.Txt = 'Vendor payment journal demo';

    jourTrans.OffsetAccountType = LedgerJournalACType::Ledger;

    jourTrans.OffsetLedgerDimension = offsetLedgerDim;

    jourTrans.AmountCurDebit = 1000;

    //jourTransData.create();

    jourTable.insert();

    jourTransData.insert();

    ttsCommit;

    info(strFmt(

    "Journal '%1' has been created", jourTable.JournalNum));

    ttsCommit; 

D365 F&O / AX2012 Filter the financial dimension for a security role

 

D365 F&O / AX2012 Filter the financial dimension for a security role

 One of the most common needs to restrict the user for specific financial dimension values

create a new query and add a table DimensionFinancialTag as the image below


browse the table to get the category 


after we get the category id to add range into the query as the first step like this



You should set the value as the expression below

((FinancialTagCategory==5637144577)&&(Value>="10000"))&&(Value<="19999"))


then create a new policy and assign the query to the policy and assign also the Security role as below


and make sure that to set the property of the contained table to yes 


D365 F&O X++ add a new dimension value into financial dimensions

DimensionValueContract _contract = new DimensionValueContract();

        _contract.parmDimensionAttribute('Dimension name');

        _contract.parmValue('Dimension value');

        _contract.parmDescription('Dimension Description');

        DimensionValueService _service = new DimensionValueService();

        _service.createDimensionValue(_contract);