-->

AX 2012 R3 - Customising the Purchasing Policies

maqk® by Maqk | 11:48 PM


This document provides a tutorial to customize the purchase order generation policy. Knowledge shared here can be used to gain a hands on idea to customize the PO generation policy as well as to get a similar idea about other policies coming under the umbrella of purchasing policy like requisition purpose rule and replenishment control rule etc.

ENTRY POINT:

Workflow (PurchReqReview) class,  completion event.

FLOW:

At each status method, namely started, cancelled and completed, helper class static methods are called.

Class: PurchReqWFStatusTransitionHelper

As the name suggests, the class responsibility is to run seperate and specific logic per PurchReq. There is a method foe each status like In review, Draft, Approved etc.
At (C), at method for the status, "completed",, the following line is called
PurchReqWFStatusTransitionHelper::setPurchReqTable2Approved
(_workflowEventArgs.parmWorkflowContext().parmRecId());
This method in its body calls for logic which first update each PurchReqLine flag after considering the configured Purch Req policy. Then it calls code to generate PO for all PurchReqLines of purpose. See code below:
RequisitionReleaseStrategy::updateManaulDocumentGeneration
(_purchReqTableRecId);
purchReqWFStatusTransitionHelper.doVersioning

(_purchReqTableRecId);

if (purchReqTable.RequisitionPurpose == RequisitionPurpose::Consumption)
{
PurchReqPurchaseOrderGenerationInSync::run(purchReqTable);
}

System Querying PurchReq PO generation policy

As per above, (C)ReleaseStrategy >> method  is called. This method updates each line field  based on query policy data read from DB. It is this flag field in each line in the end that ensures if the PO from this line is to be generated manually or automatically

HOW IS POLICY DATA READ FOR EACH PR LINE ?

RequisitionREleaseStrategy calls the boolean returning method in a conditional check to evaluate at runtime if the purchReqLIne field  be set to true or not. Consider the following line of code:
PurchReqPurchaseOrderGenerationRule::
isManualCreatePurchaseOrderStatic(purchReqLine)

if (!purchReqLine.VendAccount || 
    PurchReqPurchaseOrderGenerationRule::
isManualCreatePurchaseOrderStatic(purchReqLine))
{
    purchReqLine.IsPurchaseOrderGenerationManual = true;
    purchReqLine.update(false);
}


PurchReqPurchaseOrderGenerationRule::
isManualCreatePurchaseOrderStatic() 
inits the class object, sets PurchReqLine buffer via parm method, initializes the policy (abstract from us, not in scope of the topic), and calss the instance method <>. See code below
PurchReqPurchaseOrderGenerationRulepurchReqPurchaseOrderGenerationRule;

purchReqPurchaseOrderGenerationRule = PurchReqPurchaseOrderGenerationRule::construct();
purchReqPurchaseOrderGenerationRule.parmPurchReqLine(_purchReqLine);
purchReqPurchaseOrderGenerationRule.initPolicy();

return purchReqPurchaseOrderGenerationRule.
isManualCreatePurchaseOrder();
The method then checks the line against the policy and returns a boolean, true for manual PO creation, false for auto.

Customizing the PO Generation Policy

isManualCreatePurchaseOrder method can be read to get an idea how to customize this policy to add custom fields and conditional checks and extend the logic to evaluate if PO from PR (WorkFlow) should be generated or not.
After reading the simple method, we come to the points as follows;
  • The PO generation policy form is binded with table:
  • The Table has 5 NoYes enum fields which are rendered on the PO generation policy config form as radio hierarchied buttons
  • These fields are evaluated in the (C)PurchReqPurchaseOrderGenerationRule. isManualCreatePurchaseOrder() method.
  • To add custom evaluation logic, you can add a field, a relation, or an instanceRelation extension to this table write valid evaluation code for the new schema in the aforementioned method.
HAPPY PR TO PO GENERATION POLICY EXTENSION

Valid length of a form field label

maqk® by Maqk | 3:34 AM

AX sometimes surprise you. Such surprise often make you chase a wild goes all day to reveal it was just a dead rat :@

This just happened with me but all to reveal something I don;t think would be available on internet

Try these:

  1. Find or create a label with 250+ length. 
  2. Assign this label to a form field (not static field or any such control)
  3. Open that form
  4. Your client just crashed
Why am I telling this to you at all is the fact that my client wanted me to put huge texts against form fields. Weird, but sometimes we face requirements about which client donot accept any consultancy or education, and we just HAVE to implement it.

Now can you imagine how wild those 400+ length labels made me work and search and find the clue why my client is crashing. I went through everything including full compile, sync, cache cleanup, .auc files, label file regeneration. I had to take every step due to the reason that evenvwr log was not much of a help in my scenario. I am attaching the log for your reference here;


Application: Ax32.exe

Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.AccessViolationException
Stack:
   at Microsoft.Dynamics.Kernel.Client.ActionPaneInterop.ClickEventHelper.Clicked(System.Object, System.EventArgs)
   at Microsoft.Dynamics.Framework.UI.WinForms.Controls.ActionItem.OnButtonClick()
   at Microsoft.Dynamics.Framework.UI.WinForms.Controls.ActionButton.OnMouseUp(System.Windows.Forms.MouseEventArgs)
   at System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, Int32)
   at System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
   at System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)
   at System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
   at ._wWinMainCRTStartup()


Conclusion
Length for a field label (non static text control) is 250
Just resolved a basic error with my functional team mate Qamar Zaman Khan. We both were online, Qamar for reproducing the error. Basically our cube processing was failing with the error, 

OLE DB or ODBC error: Login failed for user 'Domain\UserName$'.; 28000

I checked the cube and it had the wrong impersonation settings. I googled and found the issue hence echoing the same to all :D

http://stackoverflow.com/questions/18702987/olede-error-login-failed-for-user-domain-computername-28000  

In my case, the sql server analysis service login account was set local account which I think is not a good practice. And as default, the impersonation account is set to service account. Due to this, the impersonation was not working since local account (rendered as Domain\) did not had the required accounts on sql as a login. I provided a sql domain account and the error dissappeared :D. Snapshots for your reference





Enterprise Portal Development Videos

maqk® by Maqk | 3:05 PM

I have just started EP development - and I never knew it was all the same - just that proxies adds minor of complexity

And guess what - all my web dev xp is gonna get polished back

EP comes with some very powerful and ready to use web parts. This post is however a 'LINKS' and not 'TUTORIAL' post, the latter will come by next year since every1 is going for holidays. 

So without any delay, here they are, all videos, enjoy:

  • How do I videos - Enterprise Portal Development Series: 
    http://msdn.microsoft.com/en-us/dynamics/ax/cc507280.aspx
  • Videos at CustomerSource
    • Creating simple Enterprise Portal Lookups using EDT, DataSetField and custom lookups using DataSet or User Controls in Microsoft Dynamics AX 2012: http://mediadl.microsoft.com/mediadl/www/d/dynamics/customersource/CCAX2012HT0030_Enterprise%20Portal%20Lookup.wmv
    • Understanding record context when interacting with data in the Enterprise Portal for Microsoft Dynamics AX 2012: http://mediadl.microsoft.com/mediadl/www/d/dynamics/customersource/CCAX2012HT0029_Enterprise%20Portal%20Record%20Context.wmv
    • List page development in the Microsoft Dynamics AX 2012 Enterprise Portal: http://mediadl.microsoft.com/mediadl/www/d/dynamics/customersource/CCAX2012HT0031_Enterprise%20Portal%20List%20Page%20Development.wmv
    • Detail page development in the Microsoft Dynamics AX 2012 Enterprise Portal: http://mediadl.microsoft.com/mediadl/www/d/dynamics/customersource/AX2012CoreConceptsVideo_CCAX2012HT0032_EPDetailsPagesDev.wmv
    • Developing detail page interactions with Microsoft Dynamics AX 2012 Enterprise Portal, Part1: http://mediadl.microsoft.com/mediadl/www/d/dynamics/customersource/nCCAX2012DI0015_1_Enterprise%20Portal%20Details%20Page%20Interaction%20Patterns%20-%20Part%201.wmv
    • Developing detail page interactions with Microsoft Dynamics AX 2012 Enterprise Portal, Part2: http://mediadl.microsoft.com/mediadl/www/d/dynamics/customersource/CCAX2012DI0015_2_Enterprise%20Portal%20Details%20Page%20Interaction%20Patterns%20-%20Part%202.wmv
    • Debugging customizations and new development with the Enterprise Portal for Microsoft Dynamics AX 2012: http://mediadl.microsoft.com/mediadl/www/d/dynamics/customersource/CCAX2012HT0028_Enterprise%20Portal%20Debugging.wmv



X++ - Cancelling the event / method in its Pre event handler

maqk® by Maqk | 4:44 PM

Eventing enables to extend applications as well to decouple the components and underlying implementations. It is a great way to write new logic without worrying a change in the underlying components.

Automatic Event Handlers

X++ provides custom delegates as well as Automatic event handlers
  • Pre - Occurs before
  • Post - Occurs after

Pre Event Handler

One of the responsibilities / usage of the Pre event handler is to validate if all conditions are correct for the commencement of the main method. Or to populate / fill any other component(s) which is required in the main method.

Cancel Event

This became a pain for me last night. I was trying to do something that we does in DotNet windows application :D 

e.Cancel = true;

Where is that ? Answer is, perhaps no where :P (educate me if I am wrong please)

But there are alternates to it. Thanks to my ex colleague and friend Asim Saeed's blog post: Yes it is quite a natural alternate, simply throw an exception.

Problem

But that will end the whole program execution, unless caught in any catch and taken care there. What I mean is, consider a complex logic that spans multiple classes or methods. What if one of the optional method's Pre event handler simply throw's exception whereas the process should have continued. In such case, that particular event should be cancelled rather than the whole program flow. I want to have granular control here, only cancel the specfic event producer method, not the whole process 

One way to do this is to have a flag parameter in the event producer method's parameters, set it to false in the Pre event handler on particular condition (i.e. if things are not found good to continue this event). Then at the start of event producer method, simply return out if the status flag is found to be false.

To demonstrate the same, we have 2 classes,

  • CMaqkProducer_Event
    • With a method and its event handler subscription
  • CMaqkConsumer_Event
    • With event handlers registered to handle event subscription defined on the producer

The code for the producer class is provided for our reference;

Producer class: Maq_EventProducer




class Maq_EventProducer
{
    #PC
    //Macros for Product Configuration, carries #true and #false for sting to bool conversions
}

/// 
/// This method is producing a Pre event handler
/// 
/// 
/// A sample parameter passed to the method for
/// 
/// 
/// Status parameter passed from XppPrePostArgs class containing the result from Pre event Handler
/// 
/// 
/// We will return in the begining of this method if the results form Pre event Handler are not ok
/// 

public void method1(str _str, str _status = "")
{

    if (_status == #false)
        return;
    // continue; ;)
    info("Maq_EventProducer.method1() called");

}

/// 
/// Another method besides method1() 
/// 
/// 
/// This method exists and is called to demonstrate continuation of program flow besides event cancellation
/// 

public void method2()
{
    info("second method, Maq_EventProducer.method2() called");
}


Notice the line 'return;' at the begining of method1() after evaluating the _status defaulted value (string) value with #true. Using this theme, we cancel the whole event from progress.

Consumer Class: Maq_EventConsumer


class Maq_EventConsumer
{
    #PC
    //Macros for Product Configuration
    
    Maq_EventProducer   objProducer; //Reference to producer
}


/// 
/// parm method to access producer
/// 
/// 
/// the reference variable to the event producer class object
/// 
/// 
/// 
/// 
/// 
/// 
/// 

public Maq_EventProducer parmObjProducer(Maq_EventProducer _objProducer = objProducer)
{
    objProducer = _objProducer;

    return objProducer;
}

/// 
/// The static main method
/// 
/// 
/// arguments passed
/// 
/// 
/// 
/// 

public static void main(Args args)
{
    Maq_EventConsumer objConsumer = new Maq_EventConsumer();
    Maq_EventProducer objProducer = new Maq_EventProducer();
    objConsumer.parmObjProducer(objProducer);
    objConsumer.run();
}



/// 
/// the default logic processing method of consumer class
/// 
/// 
/// 
/// 

public void run()
{
    info("Consumer class run() method called");
    this.parmObjProducer().method1("Maqk");
    this.parmObjProducer().method2();

}


/// 
/// The pre Event Handler
/// 
/// 
/// payload
/// 
/// 
/// 
/// 

public static void preClass1_Method1(XppPrePostArgs _args)
{
    info(strFmt("pre event handler called"));

    // Cancelling the event by setting the function's parameter, "_status" = 0
    _args.setArg("_status",#false);

    // other code logic body may go here
    // other code logic body may go here

    // Checking if some logic has asked to cancel the event
    if (_args.getArg("_status") == #false)
        warning("Class1 method1() event cancelled due to condition");

    // Later in the event producer method body, we will check the value and would work accordingly if found 0

}

Result


Calling the consumer class's run() method calls both the methods of producer class. The snapshot below shows the program flow result.

Results - Program flow continues only canceling the event

The Pre Event Handler cancels the main event, but the program flow continues. Had we thrown error / warning in the Pre event handler, producer's method2() would not have been called. But now using the event payload (XppPrePostArgs) and having a default value parameter theme, we can only cancel the event we want to and not the whole program flow.
I recently started to study SysOperation framework. It is the new way to perform the interactive batchable code execution. Compared to RunBaseBatch which had 12 overloads, it has quite few, mainly

  • new (where we specify which class's method to execute actually)
  • main (where we call the base class's startOperation() method)
  • \
Apart from these two, another method is a key method, the method mapped in the new() body which is called by the framework on the startOperation() framework method
  • customMethod (the method we map in the new() body, this method do not need to reside here, it can be any class;s method, sometimes its the controller method, better practice is to create a service class and place this method there)
The microsoft provided guide might be good for robots and androids, but I am a human and I raise question. There was no where mentioned how a controller class comes to know which contract class is ITS CONTRACT CLASS ?

There was no clue. Only wild or intellegent guesses. One guess was, does it go by the name ? Not possible, simply not a practice at all to follow. Then What ? I read the code hard ? How many places have we provided the reference of a data contract ??? 

Yes I got it, instantly I realized this cab only be the contract class parameter passed as argument in the method which was mapped to be called by the framework method, 'startOperation()'. And this is all done in the controller class's new() method body. For proof of concept, I performed a rawwxample at my local machine.

I created a simple controller and 2 ccontract classes one having 1 more argument than the other. The code for all the three classes are provided for our reference;


Controller Class
class MaqkControllerClass extends SysOperationServiceController
{
}


void new()
{
    super();

    this.parmClassName(classStr(MaqkControllerClass));
    this.parmMethodName(methodStr(MaqkControllerClass, maqkMethod1));
    this.parmDialogCaption('Basic SysOperation Sample');
}

public static void main(Args args)
{
    MaqkControllerClass operation;

    operation = new MaqkControllerClass();
    operation.startOperation();
}

// The Data Contract class passed as Parameter - Whatever class is passed here becomes the Data Contract class for this controller
public void maqkMethod1(MaqkContractClass2 _maqkContract)
{ 

    info(strFmt('SysOpSampleBasicController: %1, %2', _maqkContract.parmNumber(), _maqkContract.parmText()));
}
Contract class 1 - just 2 members, 1 string, 1 integer
[DataContractAttribute]
class MaqkContractClass
{
    str text;
    int number;
}

[DataMemberAttribute,
SysOperationLabelAttribute('Number Property'),
SysOperationHelpTextAttribute('Type some number >= 0'),
SysOperationDisplayOrderAttribute('2')]
public int parmNumber(int _number = number)
{
    number = _number;

    return number;
}

[DataMemberAttribute,
SysOperationLabelAttribute('Text Property'),
SysOperationHelpTextAttribute('Type some text'),
SysOperationDisplayOrderAttribute('1')]
public Description255 parmText(str _text = text)
{
    text = _text;

    return text;
}
2nd Contract class with 1 more text variable
[DataContractAttribute]
class MaqkCOntractClass2
{
    str text;
    int number;
    str text2;
}

[DataMemberAttribute,
SysOperationLabelAttribute('Number Property'),
SysOperationHelpTextAttribute('Type some number >= 0'),
SysOperationDisplayOrderAttribute('2')]
public int parmNumber(int _number = number)
{
    number = _number;

    return number;
}

[DataMemberAttribute,
SysOperationLabelAttribute('Text Property'),
SysOperationHelpTextAttribute('Type some text'),
SysOperationDisplayOrderAttribute('1')]
public Description255 parmText(str _text = text)
{
    text = _text;

    return text;
}

[DataMemberAttribute,
SysOperationLabelAttribute('Text Property 2'),
SysOperationHelpTextAttribute('Type some other text2'),
SysOperationDisplayOrderAttribute('1')]
public Description255 parmText2(str _text2 = text2)
{
    text2 = _text2;

    return text2;
}

Note the signature of the custom method (mapped to be called on startOperation() framework method). It is of the type of our data contract class, MaqkContractClass2. In my example, I sent the MaqkControllerClass2 class parameter. What rendered as dialog? See the following



Changing this parameter to MaqkContractClass loads different dialog, notice the difference of one dialo field less below;



Hence prooved :) Question arises, what if we give a class with no [DataContractAttribute] or [DataMember] parameter ? Answer is given below :)



and you can press the edit method to see the internal code of how the contract class is exactly associated with the controller class ;)



Recently I had to change, increase rather the length of a string variable in a service constituting Table. Simple thing huh. I created a new string EDT with more length and attached to the string field. My Import Utility (AIF) for Items (Products) failed with the data length error.

Invalid document schema. The following error was returned:  The 'http://schemas.microsoft.com/dynamics/2008/01/documents/TableName:FieldName' element is invalid - The value 'sample value provided by API consumer' is invalid according to its datatype 'http://schemas.microsoft.com/dynamics/2008/01/sharedtypes:AxdExtType_SomeDefaultEDTName' - The actual length is greater than the MaxLength value. The actual length is greater than the MaxLength value.

Actually the document schema was mentioning the same old data type. I was quite sure there must be an automated way to refresh the xml schema of the related document.There is one, under Tools > AIF > Update Document Service (from IDE). This opens the 'Update document service' form



In my case however, it created some new classes, new Ax<Table> classes. Then I refreshed AIF Services from AOT > Forms > AifService (form).


One more thing to mention is that the new classes had bugs in them, specifically in the methods that were dealing with the cacheObject classes. Their bodies were commented for developer to write desired implementation. But this was totally out of scope for me, I removed those methods. All fine, incremental CIL generated. AIF refreshed, hit my utility > Import, same old jeopardizing error :(

The schema was same old, same old shared type referenced :S. I realized that Ax<Table> class is wrapping the table, lets see there :) Yes there it is, the parm method referring to the field i changed.


public fieldEDT parmField(fieldEDT _fieldEDT = '')
{
    if (!prmisDefault(_searchName))
    {
        this.setField(fieldNum(TableName, FieldName), _fieldEDT);
    }

    return tableBuffer.FieldName;
}

Solution

Actually this class wraps the table, and this class and its methods and meta data are used to create and refresh Document Schema. If you have to add a new field in your web service, you may add / modify the parm methods in the particular Ax<Table> class.

The BUG

Refreshing through Update Document Schema added new Ax<Table> classes. However, in the respective Axd<Table> class class method (), they were not mentioned in any case. This resulted in following error;
This document does not support the AxEcoResProductIdentifier class.
I am quite sure it was the update document schema > 'Regenerate data object classes' process bug, it should have made respective case scenarios and initialized relevant Ax<Table> classes. See below illustration

top