Sumit's profileSumit LoyaPhotosBlogListsMore Tools Help

Sumit Loya

www.axworld.org

Sumit Loya

Occupation
Location
Interests
Links to know about India
June 26

Manage Deployment Issue with 64-Bit Operating Systems

Hi Friends... I was assisting our Infra team in deploying EP on 64-Bit Windows Server 2008 System. While deploying we encountered this error which I thought can be shared with all. Here is the screen shot of this error:

Well I tried searching for it on the web and saw only one solution available and that was deploy the Web parts manually which was obviously a cumbersome task. We contacted Microsoft and they informed us about this tool called AXUpdatePortal.exe which is release as a part of AX 2009 SP1 that can be used to deploy the EP. So here is some information on AXUpdatePortal.exe

 AXUpdatePortal utility

The AxUpdatePortal utility is useful while deploying changes, such as modifications, or additions from AOT to Enterprise Portal Server and to the Sites. This tool is of great help when we have to deploy Web-related changes to all Enterprise Portal sites on an IIS server in just one step. The other options to deploy the EP are to run the AX Setup again on EP Server or Use “Manage Deployment” form (This works only for 32 bit machines).

To deploy selected pages, you need to go to AOT and deploy from the page definition node. This little tool which is a command line tool automates all this in one single step.

AxUpdatePortal is required only if you have made changes in AOT and you want to push these changes to the web server you would use this utility. Other Advantage of AxUpdatePortal utility is that if you have number of page definitions changed in AOT and you want to deploy these changed pages to the existing EP sites, earlier you had to right click each one of the changed page definition node in AOT and deploy.

This tool is located in <System Drive>\Program Files\Microsoft Dynamics AX\50\Setup\ (Basically the setup folder of AX 2009 installation folder).

The AxUpdatePortal.exe is run from a command line. The following examples show the common ways the utility can be run.

AxUpdatePortal

AxUpdatePortal -updateWebSites

AxUpdatePortal -verbose > "C:\EPUpdate.log"

AxUpdatePortal -iisreset

AxUpdatePortal -updateWebSites -verbose > "C:\EPUpdate.log"

For more information on this tool and its usage visit following site: http://msdn.microsoft.com/en-us/library/dd261467.aspx

 

June 25

Selected Elements in a Combo Box

I guess this was one feature that was need of the hour for previous versions. Keeping only a subset of elements from elements in an enum.
 
Many a times we have seen that there is a requirement to remove some of the elements when they are shown in a combo-box on a form. Microsoft has come up with a class just for that purpose. The class is SysFormEnumComboBox.
 
Below is a sample code that depicts how to use this class on a form:
 
Code to write before the super() call in a form's init method
 
//Declaration Section
SysFormEnumComboBox         sysFormEnumComboBox;
Set                                         enumSet = new Set(Types::Enum);
 
//Before super()
enumSet.add(AddressType::Delivery);
enumSet.add(AddressType::Home);
enumSet.add(AddressType::AltDlv);
enumSet.add(AddressType::Invoice);
 
// Initialize the comboBox and fill it with values should be done before super() only
sysFormEnumComboBox = SysFormEnumComboBox::newParameters(element, Control::SamAddressType /*Id of the control*/, enumnum(AddressType), enumSet);
 
Code to be written after super() (Optional)
sysFormEnumComboBox.select(AddressType::Delivery);
 
Now this class is available only in AX 2009. So how do we get this done in AX 4.0?
 
This is also pretty simple after the development of the class in AX 2009. Import the class SysFormEnumComboBox from AX 2009 into AX 4.0 and use it.
 
Other methods have been to use the delete method of Combo Box. Note that this changes the enum value of the elements.
 
Note: I just found out that this class works fine with unbounded controls and doesnt have any effect on Bounded controls. Will investigate more on this.
 
June 24

Developing a new Workflow

The section below describes briefly, the process of developing a new workflow.

 

To develop a new workflow, following artifacts or objects need to be created / modified:

·         Workflow Categories

·         Workflow Templates

·         Workflow Query (Document)

·         Workflow Approvals and Tasks (Tasks are optional)

·         Enabling the workflows on the form

·         Workflow submission classes

Let us go through each of these artifacts one by one:

 

Create Workflow Categories:

A workflow category defines the module in which the workflow will be available. Modules are defined by the SysModule enum. You will be doing following here:

·         Create a new category in workflow categories node (AOT à Workflow à Workflow Categories)

·         Specify the name and module to which it belongs

 

Create Workflow Templates:

A workflow template brings all the different elements of the workflow together. Workflow configurations are created based on a template, and many configurations can be based on the same template. The template defines which actions are allowed and which are required.

You will be doing following here:

·         Create a new template in the workflow templates node (AOT à Workflow à Workflow Templates)

·         Specify a name and category to which it belongs

 

Next we create a work flow document.

 

Create Workflow Document:

A query defines what tables are used to determine that a workflow can be initiated. Here you will do the following

·         Create a new query (AOT à Queries)

·         Specify a name and Add required tables to the data source.

·         Create a new class extending from WorkFlowDocument class (AOT à Classes)

·         Override the method getQueryName and return the name of newly created query

·         Save the class

·         Once query and its supporting class is created, attach this to Workflow template (Specify it under the Document property of template)

 

Next step is to attach Approvals and / or Tasks

 

Create Workflow Approvals (or Tasks): An approval route may contain a number of outcomes. It may be approved, rejected, returned or a change may be requested (For task it will be complete, reject or request change). The Workflow Approval element determines which of these outcomes is allowed and what happens in the event of each outcome. Each outcome can trigger specific code by specifying a menu item for each item. Do the following to create an approval

·         Create a new approval object in the Approvals node. (AOT à Workflow à Approvals)

·         Specify a unique name for the approval

·         Specify the Document (The class created for query) in the Document property

·         Specify ParticipantProvider. Normally you specify WorkflowUserGroupParticipantProvider. But one can create their own participant providers similar to the standard one and use them

·         Specify DueDateProvider. Normally you specify WorkflowWorkCalendarDueDateProvider. But one can create their own participant providers similar to the standard one and use them

·         Specify HierarchyProvider. Normally you specify WorkflowLimitHierarchyProvider. But one can create their own participant providers similar to the standard one and use them

·         Set the DocumentMenuItem to form menu item where you want the workflow to appear (Example sales order etc.)

·         Approval Outcomes:

o   Use a standard class that acts as an engine for all approval outcomes

o   You are not required to do anything but set the workflow to applicable outcomes, therefore call the same class from different menu items. The menu items simply allow you to use two different labels. In more complex workflows it may be necessary to override or copy and modify this class rather than use it directly. Ex: If you have to set an outcome for Approved do the following:

1.       Create a new Action type menu item and specify a name to it

2.       Set ObjectType property to Class and Object property to WorkflowWorkItemActionManager

3.       Now get to Workflow à Approvals à <Approval you created> à Outcomes à Approve node and specify ActionMenuItem as new menuitem created before.

o   Repeat Step 2 for all the outcomes you need

o   If you do not need an outcome you can simply disable it by changing Enabled property to No

·         Once an approval (or task) is created, drag and drop this approval (or task) into the required workflow template

 

Enable workflow on a form:

Now that the workflow template is defined, you can specify which forms will use this template. Do the following:

·         Add a WorkflowState field (More can be added by CreatedBy, Time, Status etc.) to the required table

·         Then traverse to the required form and on the design node properties, Set property WorkFlowEnabled to Yes and Set the WorkFlowDataSource (Will be the table for which you created the above field).

·         If you want, you can override the method canSubmitToWorkflow on the form and specify conditions when user can submit the record to workflow

 

After enabling the workflow on class, create a class to submit the records to workflow

 

Create a Submit to Workflow class:

To submit a document to workflow, call standard code to prompt the user for a comment and to process the submission. (Look at purchReqWorkflow class for an example)

·         After creating the submit to workflow class, create a corresponding action menu item

·         Now traverse to the workflow template and specify this submit to workflow class menu item on the SubmitToWorkFlowMenuItem property

·         Once this is done you can then configure the workflow and test it. (You can refer the Workflows – Installation and Configuration document that I sent earlier for configuring the workflows)

 

This will give you a broad idea as to what needs to be done for creating a workflow.

November 13

Restoring delete sales order or purchase order

Many people know that when a sales order or purchase order is deleted, it is actually not purged from system like lot of other data but sits in the voided tables. These orders can be viewed from AR -> Inquiries -> History -> Voided sales order or AP -> Inquiries -> History -> Voided sales order.
 
Now from this form you can only view the deleted orders but there is not option of restoring them.
 
Below are some sample code that can be used to restore the voided orders.
 
Sales order restoration:
 
static void restoreDeletedSO(Args _args)
{
    SalesTableDelete    salesTableDelete;
    SalesLineDelete     salesLineDelete;
    SalesTable          salesTable;
    SalesLine           salesLine;
    ;
    SalesTableDelete = SalesTableDelete::find('00450_036', true);
    ttsbegin;
    switch (salesTableDelete.Cancelled)
    {
        case Voided::Voided :
            salesTable  = conpeek(salesTableDelete.SalesTable, 1);
            salesTable.insert();
            while select forupdate salesLineDelete where salesLineDelete.SalesId == salesTableDelete.SalesId
            {
                salesLine = conpeek(salesLineDelete.SalesLine, 1);
                salesLine.insert();
            }
            salesTableDelete.delete();
            break;
        case Voided::linesVoided :
            while select forupdate salesLineDelete where salesLineDelete.SalesId == salesTableDelete.SalesId
            {
                salesLine = conpeek(salesLineDelete.SalesLine, 1);
                salesLine.insert();
                salesLineDelete.delete();
            }
            salesTableDelete.delete();
            break;
   }
   ttscommit;
}
Purchase order restoration:
static void restoreDeletedPO(Args _args)
{
    PurchTableDelete    purchTableDelete;
    PurchLineDelete     purchLineDelete;
    PurchTable          purchTable;
    PurchLine           purchLine;
    ;
    purchTableDelete = PurchTableDelete::find('00242_049', true);
    ttsbegin;
    switch (purchTableDelete.Cancelled)
    {
        case Voided::Voided :
            purchTable  = conpeek(purchTableDelete.PurchTable, 1);
            purchTable.insert();
            while select forupdate purchLineDelete where purchLineDelete.PurchId == purchTableDelete.PurchId
            {
                purchLine = conpeek(purchLineDelete.PurchLine, 1);
                purchLine.insert();
            }
            purchTableDelete.delete();
            break;
        case Voided::linesVoided :
            while select forupdate purchLineDelete where purchLineDelete.PurchId == purchTableDelete.PurchId
            {
                purchLine = conpeek(purchLineDelete.PurchLine, 1);
                purchLine.insert();
                purchLineDelete.delete();
            }
            purchTableDelete.delete();
            break;
   }
   ttscommit;
}
One can easily create a class using this code to provide an option of restoring the orders. Here is the link to one such project
 
 
September 17

Insight Into Record IDs

Record IDs are unique IDs. In AX 4.0 Record id is unique for a table, a significant shift in 4.0 when compared to 3.0 where record ids were unique across the application. This allows AX to store more data and support enormous number of records.
 
In AX 4.0 SystemSequences table stores record ids details for all the tables in AX. The generation of Record ids is handled by class SystemSequence.
 
Record ids are generated at the time of saving the record. This is what system does. based on the table id, ID = -1 and name ='SEQNO' system gets a block of record ids and caches them and stores in the client. The block is of size 250 (in 3.0 we could change the block size, but in 4.0 MS doesnt allow anybody to change the block size).
 
Here is a sample code that shows how we can get next record id in AX 4.0
 
static void getNextRecIdAX40(Args _args)
{
    //Table that stores record ids details for tables
    SystemSequences systemSequences;
 
    //Class that handles Record id generation
    SystemSequence  systemSequence = new SystemSequence();
    ;
 
    select firstonly systemSequences where systemSequences.tabId == tableNum(CustTable);
 
    systemSequence.suspendRecIds(systemSequences.tabId);
    info(strFmt('Next record id: %1', systemSequence.reserveValues(systemSequences.minVal, systemSequences.tabId)));
    systemSequence.removeRecIdSuspension(systemSequences.tabId);
}
 
Here is a sample code that shows how we can get next record id in AX 3.0
 
static void getNaxtRecIdAX30(Args _args)
{
    SystemSequence  systemSequence;
    ;
 
    systemSequence = new SystemSequence();

    systemSequence.flushCache();
    systemSequence.setCacheSize(30)
 
    info(strFmt('Buffer size: %1', systemSequence.getCacheSize()));
    info(strFmt('Next record id: %1', systemSequence.nextVal();));
}

Below is a sample script to generate record ids while you are inserting data from SQL server scripts.
 
[Source for the code: How to Write Data Upgrade Scripts for Microsoft Dynamics AX 4.0 white paper from MS]

CREATE PROCEDURE initFromSMMQuotationTable @DATAAREAID NVARCHAR(3

AS DECLARE @NEXTVAL BIGINT, @ROWCOUNT BIGINT

 

SELECT ......,

RECID = IDENTITY(BIGINT,0,1) AS QUOTATIONID --Assign an IDENTITY column with a starting value of 0 incremented by 1

INTO #TEMP

FROM DEL_SMMQUOTATIONTABLE WHERE QUOTATIONSTATUS = 0 --SMMQuotationStatus::InProcess

 

SELECT @NEXTVAL=NEXTVAL --Retrieve the next value for RECID for this table (by TABID)

FROM SYSTEMSEQUENCES

WITH(UPDLOCK, HOLDLOCK) WHERE ID = -1 AND TABID = 1967

 

INSERT INTO SALESQUOTATIONTABLE

(column-list)

SELECT ......,

RECID = QUOTATIONID+@NEXTVAL --When we insert into the permanent table, we add the temporary tables IDENTITY column to the next value retrieved from SYSTEMSEQUENCES

FROM #TEMP

 

SELECT @ROWCOUNT = COUNT(*) FROM #TEMP

 

UPDATE SYSTEMSEQUENCES             --We update SYSTEMSEQUENCES to reflect the number of rows that we have added to this table

SET NEXTVAL=NEXTVAL + @ROWCOUNT

WHERE ID = -1 AND TABID = 1967

GO

 
Loading...
Quick Links to DAX related sites