Dynamics AIF : Working Times service

Today I created an AIF web service to expose the Working times functionality in Ax 2009. The service itself is quite simple but there was the issue with the workTimeId. The application on the other side doesn’t care about the Id of the working times and wants Dynamics to use a number sequence for the workTimeId field. This post will not deal with the creation of a number sequence reference but shows how you can fill in the WorkTimeId automatically and especially how you can link the WorkTimeLine to the WorkTimeTable via the WorkTimeId.

First things first, we create a query which contain the two tables.

AxdWorkTime Query Object

Since we are planning on updating / deleting through the service, we must not forget to set the properties on the datasources. Properties Delete and Update must be set to Yes.

Then we create the AIF service by using the Wizard available in AX 2009. Afterwards we have the following objects available :

  • AIF Service object
  • AIF Service class
  • AxWorkTimeTable entity class
  • AxWorkTimeLine entity class
  • AxdWorkTime document classWorkTimeDocument / WorkTimeDocument_WorkTimeTable / WorkTimeDocument_WorkTimeLine Classes
  • Some datacontainertype macros stuff
Created project

Now we are going to make sure the WorkTimeId is fetched from a number sequence and inserted automatically. For this we need to do 2 things :

  • Set the WorkTimeId as not mandatory (Both on the axWorkTimeTable and axWorkTimeLine entities)
  • Modify the setWorkTimeId method on the axWorkTimeTable entity to use the number sequence
protected void initMandatoryFieldsExemptionList()
{
    super();
    this.setParmMethodAsNotMandatory(methodstr(AxWorkTimeTable,parmWorkTimeId));
}
protected void setWorkTimeId()
{
    NumberSequenceReference numberSequenceReference;
    ;
 
    if (this.isMethodExecuted(funcName(), fieldNum(WorkTimeTable, WorkTimeId)))
    {
        return;
    }
 
    if (this.isSetMethodsCalledFromSave())
    {
        if (this.isFieldSetExternally(fieldnum(WorkTimeTable, WorkTimeId)))
        {
            if (!this.workTimeTable())
            {
                numberSequenceReference = NumberSeqReference::findReference(typeId2ExtendedTypeId(typeid(WorkTimeId)));
                this.checkNumber(numberSequenceReference.numberSequenceTable(),fieldNum(WorkTimeTable, WorkTimeId),this.parmWorkTimeId());
                if (numberSequenceReference.NumberSequence && numberSequenceReference.numberSequenceTable().Continuous)
                {
                    NumberSeq::newReserveNum(numberSequenceReference).reserve(this.parmWorkTimeId());
                }
            }
        }
        else
        {
            if (this.isFieldSet(fieldnum(WorkTimeTable, WorkTimeId)))
            {
                return;
            }
 
            if (!this.parmWorkTimeId())
            {
                this.parmWorkTimeId(NumberSeq::newGetNum(NumberSeqReference::findReference(typeId2ExtendedTypeId(typeid(WorkTimeId)))).num());
            }
        }
    }
}

And last but not least we need to link the AxWorkTimeLine to the AxWorkTimeTable via the WorkTimeId. The wizard already did some action to achieve this by putting the prepareForSave method on the AxdWorkTime document class.

public boolean prepareForSave(AxdStack _axdStack,  str _dataSourceName)
{
    AxWorkTimeTable   AxWorkTimeTable;
    AxWorkTimeLine    AxWorkTimeLine;
    ;
    switch (classidget(_axdStack.top()))
    {
        case classnum(AxWorkTimeTable) :
            AxWorkTimeTable = _axdStack.top();
            return true;
 
        case classnum(AxWorkTimeLine) :
            AxWorkTimeLine  = _axdStack.top();
            AxWorkTimeTable = AxWorkTimeLine.parentAxBC();
            AxWorkTimeLine.parmWorkTimeId(AxWorkTimeTable.parmWorkTimeId());
            return true;
 
        default :
            error(strfmt("@SYS88979",classId2Name(classidget(_axdStack.top()))));
            return false;
    }
    return false;
}

Since the parm method will call the setWorkTimeId method once, we still need to place some code there :
The code will call the parent entity’s parmWorkTimeId method.

protected void setWorkTimeId()
{
    if (this.isMethodExecuted(funcName(), fieldNum(WorkTimeLine, WorkTimeId)))
    {
        return;
    }
 
    if (!this.parmWorkTimeId())
    {
        this.parmWorkTimeId(this.axWorkTimeTable().parmWorkTimeId());
    }
}

The only thing still needed now is pointing out who is the parent entity. Therefore I’ve created a method that creates an axWorkTimeTable entity.

public AxWorkTimeTable axWorkTimeTable(AxWorkTimeTable _axWorkTimeTable = null)
{
    AxWorkTimeTable axWorkTimeTable;
 
    if (!prmisdefault(_axWorkTimeTable))
    {
        axWorkTimeTable = _axWorkTimeTable;
    }
    else
    {
        this.setWorkTimeId();
        axWorkTimeTable = WorkTimeTable::find(this.parmWorkTimeId()).axWorkTimeTable();
    }
 
    return axWorkTimeTable;
}
public AxWorkTimeTable axWorkTimeTable()
{
    AxWorkTimeTable axWorkTimeTable = AxWorkTimeTable::newWorkTimeTable(this);
    return AxWorkTimeTable;
}

If someone wants to add something, be my guest… comments are welcome !

19 thoughts on “Dynamics AIF : Working Times service

  1. Hi, Its great work you have done here. It really helped me a lot, as I’m in the same situation implementing Ids using AIF.
    I have a question about the code you have written above. In the method “setWorkTimeId” for table “AxWorkTimeLine” can we use this.parentAxBC() instead of getting it from “this.axWorkTimeTable()” as the relation is already set in prepareforsave(). If not where should the last two methods should be created?

  2. The second last one is made on the entity class itself. And the last one is on the WorkTimeTable table.

    But you may have a point about the necessity of these methods. I have followed an example of another service to build this one.

    The reason why I used it like this is that I am always certain that even in case that the parentAxBC() should be empty, the methods will perform a find operation on the table and create an entity to be used. So then it is always available.

    You can see similar behavior on the AxSalesLine entity class, method axSalesTable. (omit the code for caching and you have similar code)

  3. Hi Saelen and Navid,

    I too am facing a ‘number sequence’ problem while using AIF document services. I created a ProductionOrder service(through AIF document service wizard) based on a query on the ProdTable and InventDim tables. Now, PordTable already has a number sequence(Inve_96) associated to InventTransId(Lot ID) field of the ProdTable. Yet, when i call the service, the following error is showing up in ‘Basic->Application Integration Framework->Exceptions’:
    “Number Sequence for parameter Lot ID in Inventory management module is not set up.”

    From what I know, the above error comes up when a number sequence has not been set up for Lot ID in ‘Inventory Management->Parameters->Number Sequences’. But, I made sure that it is set up, and moreover, when manually creating a record in ProdTable, the InventtransId(Lot ID) is automatically being generated through the Inve_96 number sequence. Which means the number sequence is there at the table level but not being called when trying to insert thruogh the AIF service.

    So, on the lines of the code that you provided above, I wrote code for generating number sequence in the ‘setInventTransId’ method of the AIF service’s Ax class. But, that did not help. The same error is occuring. I even tried writing similar number sequence code in the ‘parmInventTransId’ method of the Ax classes. I also tried putting the code in the ‘insert’ and ‘validatewrite’ methods of the ProdTable. None of these were of any use.

    the code chunk for the number sequence is as follows:
    “this.parmInventTransId(NumberSeq::newGetNum(NumberSeqReference::findReference(typeId2ExtendedTypeId(typeid(InventTransId)))).num());” – when written in the service classes.

    “this.InventTransId = NumberSeq::newGetNum(NumberSeqReference::findReference(typeId2ExtendedTypeId(typeid(InventTransId)))).num();” – when written in the table methods.

    Finally, to be able to debug the issue from within AX, I created a Job to call the ProductionOrder service and its ‘create’ method. Now, the following error comes up:

    “Line=1, Pos=140, Xpath=/ProdcutionOrder2/ProdTable[1]

    The value ‘LOTID082013’ in field ‘Lot ID’ is not found in relating table ‘Inventory transactions’.
    Error found when validating record.
    The number sequence Inve_96 does not allow the Lot ID to be defined.
    Creation has been canceled.
    Document could not be created”

    So, I thought maybe the Lot ID(InventTransId) being generated by the number sequence is creating a conflict with existing data, So, this time, i gave the InventTransId(Lot ID) manually through code in the Job (because at table level, the AllowEdit property for InventTransId is true) . Even Now, the following error comes up:

    “Line=1, Pos=140, Xpath=/ProdcutionOrder2/ProdTable[1]
    The number sequence Inve_96 does not allow the Lot ID to be defined.
    Creation has been canceled.
    Document could not be created”

    Please help me out with this issue friends, coz I’ve been stuck with this issue for weeks together now, trying all possibilities, but of no use. Maybe, there is something w.r.t AIF that I am failing to configure/program.

    Thank You,
    Ray.

  4. Hi Ray,

    The error concercing the presence of an ID in the document is somthing that I also encountered. In my case there was a difference in the files between the value ’empty’ and ‘null’ and there was my problem. I doubt this will be the issue here.

    Two things:

    Did you try to debug the service and step through the AIF code to see if the methods are actually called the right way? Maybe you can see something there. (I have a post on this blog also dealing with the various things needed to do before the AIF can be debugged.
    http://www.ksaelen.be/wordpresses/dynamicsaxblog/2010/07/debugging-aif-on-windows-2008-2/

    If you could make an export of the service you created (all objects : Entity classes, service class, document classes, query, macro, …) I would try and import it here and try to have a look at what goes wrong for you if you want.

    Kind regards,
    Kenny

  5. Thank you very much for the swift reply Kenny.

    I already followed the steps you provided for debugging AIF service. It wasnt working. When I run the code in C#, it should automatically debug the service classes and methods in AX, if the Debugger is open and all the necessary options are selected in the config(as you showed). but, that wasn’t happening. That is the reason why I chose to create a Job within AX to debug the service code. But even then, I am unable to see clearly the flow of control of the program.

    Actually, I do not see two seperate config utilities as the ‘AX server configuration util’ and ‘client config util’. I am accessing the server that has AOS, and I only see one single ‘Dynamics AX Configuration Utility’ here.

    Also, Can you please provide me your e-mail address so that I can send you the “.xpo” file of the Project containing all the Service objects?

    And I did not understand what you said about ’empty’ and ‘null’. Can you please be more specific?

    Thank you,
    Ray.

  6. Regarding the debugging. Make sure you place the debugger on the correct tier.
    I placed it on the AOS and wasn’t working, but eventually I needed to place the debugger on the IIS server since the BC was executing there..
    You also need the fix from MS to work (kernel version *.3076)

    Either way, if you want to mail me, kennysaelen@gmail.com

    kind regards,
    kenny

  7. Hi Kenny,

    I sent you the mail with following things attached:

    (1). the “.xpo” of the project containing the service objects for the AIF document service named ‘ProdcutionOrder2’ service.
    (Note: This might not be very important but if you are modifying the code it might cause problems. I misspelt ‘production’ as ‘prodcution’ while creating the service through the wizard. So, all the class names are also on similar lines.)

    (2). the job i created in AX to call the service objects. After a few modifications to the service methods, I am now able to successfully create production order in ProdTable through this job(which is inturn calling the service).

    (3). But, through visual studio’s C# code, I am unable to do the same. Infact, the C# code to call the objects of the service, is a little different from what is written in the AX job. I am also attaching a word file that has the C# code.

    (4). Also, I am attaching the screenshot of the Exceptions form in AX (Basic->Periodic->Application Integration Framework->Exceptions) which is showing the errors.

    I know that I am bombarding you with too many things, but please dont mind. You are my only source for AIF knowledge. No one else(even throughout the internet) even came close to solving my issue.

    Thank You,
    Ray.

  8. Hey Kenny,

    The issue is solved. The service was sending data to the DAT company, while my data was there in another one. Changing the default company for user account ‘msaxadmin’ and ‘bcproxy’ also did not work out.
    Finally just changed the start-up company in the settings and reopened that AX instance. and the AIF service then sent the data to the correct company.

    But, I’m wondering if there is a way to configure AIF so that any inbound data is destined to more than one company accounts. Do you know if that is possible?

  9. @Ray
    Hi raghu,

    Nice to hear the issue is solved. That explains indeed why the number sequence wasn’t set-up.

    As for the company redirection (found on the net)
    The messageheader of each aif message has two elements called SourceEndpoint and DestinationEndpoint, which are used to set the AX company and also the AX AIF Endpoint which should be used for message processing.
    If you are working with a webservice (not fileadapter or so) you must also keep in mind, that it is only possible to override the headers of the message when the webservice has ws*-binding configured.
    If you are working with basic-binding you cann’t override the message headers (this is the default).
    If you are calling the webservice you can specify the company account with the DestinationEndpoint Element of the header. With the SourceEndpoint Element in the message header you can specify the AIF Endpoint which should be used. If not set, the default endpoint will be used.
    Please take a look at this example, describing how to specify the company and AIF Endpoint when calling an AIF webservice:
    http://msdn.microsoft.com/en-US/library/cc652581.aspx

    Basically it al comes down to modifying the message header and setting the sourceendpoint and destinationendpoint.

    greets,
    Kenny

  10. Hi,

    I put my code in the initMandatoryFieldsExemptionList() but the service is still making it a required field in the schema (I can’t uncheck it as “required” from the endpoint action policies). I refreshed the service too. Any suggestions?

    Thanks,
    Amber

  11. @Amber
    Nevermind, the problem was that the Axd Service class had that field in the initMandatoryFieldsMap() method. So it was making it mandatory after I told it to not be mandatory! I had to comment that out. :) Thanks anyway!

  12. Hi Amber,

    Glad it worked out for you!

    It also happens a lot that the AIF service needs to be refreshed on the .NET side befor the change is visible but that people forget to do that.

    Kind regards,
    Kenny

  13. Hi amber,

    Funny though, I just see the last line in your first comment where you already mention that the service was refreshed ! :)

    If you ever have other questions, please fire away !

    greets,
    Kenny

  14. Hi Kenny,

    I am Ray, who earlier posted about the Production Order AIF service. Now regarding the same, I am able to create Prodcution Orders in AX from external C# code. But only certan fields like ProdID, ItemID, etc that are coming into the “Production Order Details” form from the ProdTable table are being transferred successfully through the web service.

    Data pertainnig to Inventory dimensions, like InventLocationId (Warehouse field at the form level) are not appearing in the form. The query beneath the AIF service is made on both the ProdTable and InventDim. Also, the C# code i wrote is meant to send data to the child tables as well(InventDim) apart from ProdTable.

    The msdn example of “How to call an AIF web service from C# to create a sales order” has similar code. But it is not working in my case.

    The following is the C# code snippet:

    ProdcutionOrder2ServiceClient service1 = new ProdcutionOrder2ServiceClient();

    // Instance of document class.
    AxdProdcutionOrder2 prodOrder = new AxdProdcutionOrder2();
    prodOrder.ProdTable = new AxdEntity_ProdTable[1];
    prodOrder.ProdTable[0] = new AxdEntity_ProdTable();

    // Instances of the entities that are used in the service and setting the needed fields on those entities.
    AxdEntity_ProdTable prodTable = new AxdEntity_ProdTable();
    //prodOrder.ProdTable[0].ProdId = “WO014163”; The production id could be given here as well, but if not given, it will be generated through number sequence in AX.
    prodOrder.ProdTable[0].ItemId = “23000011”;

    AxdEntity_InventDim inventDim = new AxdEntity_InventDim();
    // Adding the sub-entity instances to their parent entities as an array of the sub-entity type.
    prodTable.InventDim = new AxdEntity_InventDim[1] { inventDim } ;
    prodTable.InventDim[0] = new AxdEntity_InventDim();
    prodTable.InventDim[0].InventLocationId = “PFP-W”;

    //Create method on the service passing in the document.
    finalProdOrder.AifReference.EntityKey[] prodOrderEntityKey = service1.create(prodOrder);

    // The create method returns an EntityKey which contains the ID of the production order.
    finalProdOrder.AifReference.EntityKey returnedProdOrder = (EntityKey)prodOrderEntityKey.GetValue(0);

  15. I have already mailed you all my code(service classes in AX, as well as C# code), while asking a solution for my previous issue

    Nevertheless, I am mailing you this issue(along with above code snippet) once again.

    Please reply to me if possible. It will be very very helpful.

    Thank You very much,
    Ray.

  16. I have a requirement in which I have allow creating of record in the child table conditionally it depends on some value in the haeader table.

    How can i full fill this requriement in AIF

  17. @Huzaifa

    I believe this can be done by using the PrepareForSave method. There you can place validations on the document. The method is called for every record in the document.

    Check out the AxdBaseCreate class. Method DeserializeDocument : There you can see the following code :

    if (this.hasTableAccess(dataSourceName,AccessType::Add))
    {
    // Always try to save the element, even though it can’t be popped of yet. I.e. needs to have header saved before lines.
    if (axdBase.prepareForSave(axBcStack,dataSourceName))
    {
    axBcStack.top().save();
    axdBase.processingRecord(axBcStack.top().currentRecord() ) ;
    }
    }

    So my guess would be to use the prepareForSave method to do your validation and return false if you do not want the line to be created.

    I do want to point out that I did not test this myself, but do feel free to let me know if this works or not.

    Kind Regards,
    Kenny

  18. Hello,

    nice Post. Thanks a lot.
    But you describe to make a change in the method setWorkTimeId() of the ax-class.
    I don´t have this method on my ax-Class. Not for any of the fields from the table. Do you know what I have to do to get the set-methods?

Leave a Comment Yourself

Your email address will not be published. Required fields are marked *