Thursday, December 19, 2013

SP2010 SP2 Failed to create the configuration database - SPUpgradeException was thrown

Today I was setting up a new AX DEV VM with Windows Server 2012 R2. When I install SharePoint 2013 the pre-requisite install failed. A quick google tells me that SP2013 is not supported. (SP1 will fix this).

No problem, I'll go back to SP2010 SP2, which is supported in both Win Server 2012/2012R2. I installed SP2010 without any issues. However, I got a "Failed to create the configuration database" message when configuring the instance. A SPUpgradeException was thrown with a message similar to "Failed to call GetTypes on assembly Microsoft.Office.InfoPath.Server, Version=14.0.0.0, bla bla bla..."

Took me much longer to find an answer this time, turned out the culprit was Microsoft Office InfoPath (In case it has something to do with version, it was Office 2013)! Well, since I was using Office 365 offering, I had no choice but to install everything (including InfoPath). I uninstalled the office and re-install Word/Excel/etc using other means, then this error is gone.

Problem solved! =]

This posting is provided "AS IS" with no warranties, and confers no rights.

Monday, November 04, 2013

Dynamics AX 2012 R2 CU7 Installation error

EDIT: This seems to be an odd issue. I tested again with demo DB (CU6) from partner source and there was no issue at all installing CU7 over it.

I'd also like to point to some very nice posts regarding CU7:
Joris de Gruyter: What you need to know about the 15 minutes CU7 compiler
Tommy Skaue: You got to love AX Build comipler tool
and if you were using DIXF:
André Arnaud de Calavon: How to upgrade Data Import/Export Framework to AX2012 R2 CU7
----------
When I try to install CU7 on a CU6 environment, an error appears when the new models are being imported. Check the log and there is this message:
Error updating model database: Microsoft.Dynamics.Setup.AxSetupException: AxUtil call returned errors:The model contains a customization of a configuration key. The model cannot be imported because you can overlayer a configuration key from the patching layer only.

Both CU6 and CU7 use the same model name "Update for Foundation" with a different display name.("Foundation ([KB#])" with KB# equals to their respective KB number.) And as suggested in the error message configuration key changes in CU6 blocked the installation.

Uninstall the existing CU6 model first will allow the installation to proceed. However, please do note that this work around does NOT guarantee element ID consistency and is not suitable for production system.

This posting is provided "AS IS" with no warranties, and confers no rights.

Tuesday, October 15, 2013

A Power shell script for updating admin user SID and other details

Often I will load up the AX demo database under a different domain. I have write the following PowerShell script to help modify the admin user SID, login info, etc in AX. It uses Invoke-Sqlcmd SQL Server cmdlet to update the USERINFO table.
                
$networkDomain = "[domain]"     # e.g. yourDomain.com
$networkAlias = "[user alias]"  
$name = "[user name]"                
$dbname = "[your AX DB Name]"             
$sqlServer = "localhost"             

$targetUserId = "admin"         # Change this to update other existing AX users

$objUser = New-Object System.Security.Principal.NTAccount("$networkDomain","$networkAlias")
$sid = $objUser.Translate([System.Security.Principal.SecurityIdentifier])

$sqlstmt = "UPDATE {5}.dbo.USERINFO SET SID = '{0}', NETWORKDOMAIN = '{1}', NETWORKALIAS = '{2}', Name = '{3}' " +
        "WHERE ID = '{4}'"
$sqlstmt = [string]::Format($sqlstmt,$sid, $networkDomain, $networkAlias, $name, $targetUserId, $dbname)

Invoke-Sqlcmd -Query $sqlstmt -ServerInstance $sqlServer
Since I am using this on an all in one DEV box, the Invoke-Sqlcmd command-let is readily available. If Invoke-SqlCmd is not recognized in the machine you want to run the script, try the tips from this link.

This posting is provided "AS IS" with no warranties, and confers no rights.

Thursday, October 03, 2013

Database Entity Relationship Diagrams for AX2012 R2 is out.

You can find it here
This posting is provided "AS IS" with no warranties, and confers no rights.

Thursday, September 26, 2013

Recycling number sequence

So far I've seen a few instances where standard AX2012 can't handle continuous number sequence on certain EDT. Purchase order number (PurchId) is one of those affected.
- If you create a new order and then delete it, no numbers added to the status list and the purchId is not recycled.
- If you create a new order using the "Create purchase order" form, and click "cancel" in the form. The number is now added to the status list. Then the number can be freed by the clean-up action.

In these cases, one option is to add code to free the number ourselves. Using PurchId as an example, add the code below at purchTableType.delete() method will add the purchId to the status list of the number sequence.
   
VersioningPurchaseOrder::newPurchaseOrder(purchTable).delete();
purchTable.doDelete();

// Add the line below to free up the purchId
NumberSeq::releaseNumber(PurchParameters::numRefPurchId().NumberSequenceId, purchTable.PurchId); 

sourceDocumentHeader.delete();
            
One thing to consider, though, is that we have to be sure the Id we are freeing are no longer used/referenced in other tables in AX. Other than that, it should be pretty straight forward.

Please leave a comment if you think of other things to consider when making this change. =D

Cheers.

This posting is provided "AS IS" with no warranties, and confers no rights.

Thursday, September 05, 2013

Lookup dimension attributes used in a company.

This job look up the financial dimension attributes which are being used in the current company/entity. I used it while writing certain import customization. You might find other use of it.
  
static void Job_GetDefaultDimensions(Args _args)
{
    DimensionEnumeration            dimEnum;
    DimensionAttributeSetItem       dimAttrSetItem;
    DimensionAttribute              dimAttr;
    
    container   cDimAttr, cDimAttrName;

    // Get the financial dimensions
    dimEnum = DimensionCache::getDimensionAttributeSetForLedger();

    while select * from dimAttr
        order by dimAttr.Name asc
        join dimAttrSetItem
        where dimAttrSetItem.DimensionAttribute == dimAttr.RecId
            && dimAttrSetItem.DimensionAttributeSet == dimEnum
            && dimAttr.Name != "MainAccount"
    {
        cDimAttr = conIns(cDimAttr, conLen(cDimAttr)+1, dimAttr.recId);
        cDimAttrName = conIns(cDimAttrName, conLen(cDimAttrName)+1, dimAttr.Name);
    }
}              
This posting is provided "AS IS" with no warranties, and confers no rights.

Friday, August 30, 2013

Impression on using Team Foundation Service Online with AX2012

I am a slow adapter to version control. In my current position, I need to work with teammates who are in a different region and we had to come up with a setup of version control that works for everyone. Then I came across Team Foundation Service Online (http://tfs.visualstudio.com/).

TFS Online is free for up to 5 users. And it is accessible from anywhere, which is what I needed. Also, there is no hardware/network investment involved so the managers are happy!

It was quite easy to setup and get started. Just register an account and create a project online. Connect AX to it, add a model to the project and start adding objects to the repository. During our use, there are minor issues here and there (e.g. Synchronizing labels sometime crashes the client; managing workspaces) but in general it is very responsive. Check-out, check-in, labels handling, code merge, etc all works as expected.

It is possible to look at the online repository from the web UI. It's easy to view change set, check object history and compare different versions of an object. It is also recommended to have visual studio 2012 (Express version is Ok.) install on the computer. Some action (e.g. Delete a project, applying labels) cannot be done through the web interface and needed a command issue from remote computer.

So if you are in similar situation (In a small team where members resides indifferent geographic regions) and don't have any existing version control setup, I'd recommend give TFS Online a try.

Here's some pros/cons of using TFS Online...
Pros: 
- Free, for up to 5 users.
- No hardware/network investment needed.
- Easy to setup and use.
- All data is stored in triplicate on three physically-distinct servers. Full backups are taken every day with incremental backups every hour.

Cons: 
- Not officially supported, have to work around minor issues.
- Your source code is on the cloud.
 - No Active Directory support.
- Features are limited at the moment. (If you looking for more than simple code versioning)

This posting is provided "AS IS" with no warranties, and confers no rights.

Saturday, July 20, 2013

Top 10 issues discovered from Dynamics AX Health Check

Very good post, check it out

This posting is provided "AS IS" with no warranties, and confers no rights.

Friday, June 28, 2013

Data Import Export Framework - Generate Auto Numbers

Recently I needed to setup ledger journal import using Data Import Export framework (DIEF). Even though there exists the Opening Balance entity, getting it done wasn't as straight forward as I thought. I've found some problem using the "Auto generated" flag in Source to Target mapping. The focus is on the DMFGenerateSSISPackage class.

The GetSchemaName method

First of all, there is the getSchemaName method. When dealing with auto number generation from number sequence, DIEF creates a SQL function (FN_FMT_NUMBERSEQUENCE) on the fly to do the number assignment and drops it afterwards. DIEF uses the getSchemaName method to get the schema name when creating this function. The original code uses "SELECT CURRENT_USER" but it can returns the AOS service account instead of "dbo", which is what is expected. Changed it to "SELECT SCHEMA_NAME()" will solve this.

Before moving on, let's define the import data. Let say we want to import 3 lines into the same journal. So I created a "Opening Balance" entity in a processing group. I modify the source-to-staging format, marking JournalNumber auto generated, and I also added the JournalName field in Query Criteria such that all 3 lines (with the same JournalName value) will be assigned the same JournalNumber.

The "Next" value in number sequence

Ok, so the import was fine and I see all 3 lines assigned the same journalNumber in staging. However, when looking at the Number Sequence, the "Next" number has increased by 3 instead of 1. Checking the code in the generateAutoNumbers method shows that this part is not handled properly. Luckily I can count the number of distinct journalNumber used in staging table and fix it.

"Free" numbers consideration for continuous number sequence

Finally, there's the "Free" number consideration for continuous number sequence. The original code simply assign one free number to one staging record until all free numbers are used up. That's not good enough. (e.g. say there is 1 free number in the journalBatchNumber number sequence, then the first line will be assigned the free number and the other two lines a new journal number.) Further editing needs to be done in generateAutoNumbers method to set this part straight as well.

The fixes required are quite straight forward so I won't bother to post the code here. Hopefully some of you will find this information helpful.
                 
This posting is provided "AS IS" with no warranties, and confers no rights.


Thursday, June 13, 2013

Data Import Export Framework - Financial Dimension 0 does not exists

EDIT: This bug is fixed in DIXF that comes with CU7
There *seems* to be a bug in Data Import Export Framework when importing financial dimensions. When importing ledger journal lines I get a "Financial Dimension 0 does not exists" error.
I have made a guess on how it was suppose to work and made some code changes below:

In the DMFDimensionHelper::generateDynamicDimension method, add a new variable for LedgerStructure Table. Then comment out the exists join to ledgerChartOfAccountsStructure and join it with ledgerStructure instead.

                

/*
  exists join ledgerChartOfAccountsStructure where
    ledgerChartOfAccountsStructure.DimensionHierarchy == dimHierarchy.RecId &&
    ledgerChartOfAccountsStructure.ChartOfAccounts == LedgerChartOfAccounts::current()
*/
  exists join ledgerStructure where
    ledgerStructure.DimensionHierarchy == dimHierarchy.RecId &&
    ledgerStructure.Ledger == Ledger::current()
{
    i ++;
    dimAttributeList = conIns(dimAttributeList,i,dimAttribute.Name);
}
Please feel free to comments on the error or this fix.

This posting is provided "AS IS" with no warranties, and confers no rights.

Friday, May 24, 2013

AX2012 SSRS error when trying to add a data method

Normally, adding a new data method to a SSRS report in a ReportModel project will automatically build the xxx.budinessLogic project.
I don't know how to reproduce this, but today I ran into a problem that when I try to add a data method to a SSRS report (which had no data methods previously) I get an error message saying:

"could not resolve xxx.BusinessLogic from the AOT. Verify that the Buiness logic library property is set correctly, the referenced project is added to the solution and loaded"

The work around is actually fairly straight forward:
1) Add a new report in the same solution.
2) Add a new data method in the new report. By doing so the businessLogic project is created like it should.
3) Delete the report created in step 1.
Well, took me awhile to think of trying this. But at least I can move on now. =]

Saturday, May 18, 2013

AX2012 R2 Data Import Export Framework:"Treenode object not initialized"

There is a bug in Data Import Export Framework (AX2012 R2) custom object generation wizard in the part where it is cleaning up table relations of the entity table. One example of the problem is when creating a new entity for CustGroup, it will generate an error.  (Details can be found here:)

In the CustGroup example, DefaultDimension is imported as String (e.g. MainAccount-Department-CostCenter) and not an Int64 value. Hence the relation between the Dimension field (a RefRecId) and the reference table (DimensionAttributeValueSet) no longer applies and should be deleted. That is where the bug hits.

In the DMFGenerateEntityTable.modifyRelations method, the cursor which is suppose to traverse through all relations is accidentally discarded when deleting the table relation. Make the following changes to fix it:







This posting is provided "AS IS" with no warranties, and confers no rights.












Tuesday, April 30, 2013

AX2012 TFS online: "The path X is already mapped in workspace Y"

Trying to take advantage of Team Foundation Service online using AX2012 is often not straight forward. 
One problem pops up today related to "The path X already mapped in workspace Y" when trying to activate version control. The problem is related to cached workspace info.

Removing workspaces in VS2012 does not do the trick as the caching info that creates the problem resides somewhere else. Go to  C:\Users\<username>\AppData\Local\Microsoft\Team Foundation\3.0\Cache and look for the VersionControl.config. Clear the workspaces info there to solve the issue.

Thursday, April 18, 2013

Friday, April 12, 2013

How to get LedgerDimensionAccount value for a vendor account.

Use the getDynamicAccount method in DimensionStorage class!

DimensionStorage::getDynamicAccount(<vendorAccount>,enum2int(LedgerJournalACType::Vend));

Cheers~

Wednesday, April 03, 2013

AX2012 – Create default dimension with a set of dimension values

The following job will get a DimensionAttributeValueSet record ID base on the a set of dimension values.


static void DEV_CreateDefaultDimension(Args _args)
{
    DimensionAttributeValueSetStorage   valueSetStorage = new DimensionAttributeValueSetStorage();
    DimensionDefault                    result;  
   
    int                     i;
    DimensionAttribute      dimensionAttribute;
    DimensionAttributeValue dimensionAttributeValue;
   
    // Note that "Item" is not one of the default dimension,
    // but DimensionAttributeValueSetStorage will handle it gracefully
    container               conAttr = ["Department", "ExpensePurpose", "Item"];
    container               conValue = ["00000028", "Training", "1000"];  
    str                     dimValue;            
   
    for (i = 1; i <= conLen(conAttr); i++)
    {              
        dimensionAttribute = dimensionAttribute::findByName(conPeek(conAttr,i));
       
        if (dimensionAttribute.RecId == 0)
        {
            continue;
        }
       
        dimValue = conPeek(conValue,i);
       
        if (dimValue != "")
        {
            // The last parameter is "true". A dimensionAttributeValue record will be created if not found.
            dimensionAttributeValue =
                    dimensionAttributeValue::findByDimensionAttributeAndValue(dimensionAttribute,dimValue,false,true);
           
            // Add the dimensionAttibuteValue to the default dimension
            valueSetStorage.addItem(dimensionAttributeValue);
        }              
    }      
   
    result = valueSetStorage.save();  
}

** The above is for interest only and comes with no warranties **

Monday, January 21, 2013

AX2012: Update Invent Registration from code


*** Update: 2013-2-4 ***
Credits go to Andy Adamak for spotting a problem with the code below.
This bit of code simply grabs the first InventTransOrigin record where the InventTransID's match, then grabs the FIRST record of the InventTrans table where it matches up with the origin... regardless of status. The issue I was running into was even if it was received/registered, whatever status, it would grab the first InventTrans and override the record and it's InventDim record as well.
So... It works, but not really because it totally messes up all your transactions if you receive more than once against the same PO line
------------------------------------------------------------------------------------------------------------------

Here's a job to update Invent Registration in AX2012. Feel free to try the following job in your testing/development environment.
static void DEV_InventTransRegistrationFromCode(Args _args)
{
    InventTransWMS_Register inventTransWMS_register;
    InventTrans             inventTrans = InventTrans::findTransId( "<inventTransId>");
    InventSerialId          id1 = "<serialId1>", id2 = "<serialId2>" ;
    TmpInventTransWMS       tmpInventTransWMS;
    InventDim               inventDim = inventTrans.inventDim();
   
    inventTransWMS_register = inventTransWMS_register::newStandard(tmpInventTransWMS);
   
    tmpInventTransWMS.clear();
    tmpInventTransWMS.initFromInventTrans(inventTrans);
    tmpInventTransWMS.InventQty = 1;
    inventDim.inventSerialId = id1;
    tmpInventTransWMS.InventDimId = inventDim::findOrCreate(inventDim).inventDimId;
    tmpInventTransWMS.insert();
   
    inventTransWMS_register.writeTmpInventTransWMS(tmpInventTransWMS,
                                                   inventTrans,
                                                   inventTrans.inventDim());
   
    tmpInventTransWMS.clear();
    tmpInventTransWMS.initFromInventTrans(inventTrans);
    tmpInventTransWMS.InventQty = 1;
    inventDim.inventSerialId = id2;
    tmpInventTransWMS.InventDimId = inventDim::findOrCreate(inventDim).inventDimId;
    tmpInventTransWMS.insert();
   
    inventTransWMS_register.writeTmpInventTransWMS(tmpInventTransWMS,
                                                   inventTrans,
                                                   inventTrans.inventDim());
   
    inventTransWMS_register.updateInvent(inventTrans);   
}

** The above is for interest only and comes with no warranties **

Tuesday, January 15, 2013

AX2012 Service: Customize dialog layout

When using AX services with data contract, all parameters are placed one by one and there is always a “Parameters” caption on top.
20130115_000523
This would look much nicer if I can place each check box to the right of their respective string input.

The way to customize the layout is not very obvious and I’ll show one way of doing it here.

1) First of all, create a new class which extends SysOperationAutomaticUIBuilder.
20130115_000525 

2) Override the postBuild() method and get a reference for each dialog field.
20130115_000526

3) After that, add code to modify the layout. (I put them in a separate method in this example.)
20130115_000527
Things to note:
- Create a new FormBuildGroupControl (subGroup) and set it’s hierarchyParent to “General”
- After that, build the rest of the form within “subGroup”
- Place the parameters fields to your desired place by modifying their hierarchyParent property.
- The order of assigning the controls affect their order of display, play with it to figure out how it works.

End result:
20130115_000528

** The above is for interest only and comes with no warranties **

Wednesday, January 09, 2013

Create financial dimension combination in AX2012 from code

I found this excellent post from Becky Newell about creating financial dimension combinations under X++.
===================
The first and best way to create dimension combinations is to use the class and method DimensionServiceProvider\buildDimensionStorageForLedgerAccount.  The buildDimensionStorageForLedgerAccount method takes a
LedgerAccountContract class.  In the LedgerAccountContract class there are two parm methods, parmMainAccount and parmValues.  The parmMainAccount method takes the MainAccount like ‘10060’ and the parmValues method takes a list of the other dimension values you want to set.  More specifically the parmValues method takes a list of classes.  The classes are of type DimensionAttributeValueContract.  On the DimensionAttributeValueContract class you set two
methods, parmName and parmValue.  For example, ‘Department’ for the name and ‘10’ for the value. Build up a list of DimensionAttributeValueContract classes for each dimension you want to specify and then pass the list to the parmValues method on the LedgerAccountContract class.  Once you have the LedgerAccountContract built up send it to the DimensionServiceProvider\buildDimensionStorageForLedgerAccount.

Optionally you can use a utility method in AX in AxdDimensionUtil\getLedgerAccountId.  This one is not as nice as buildDimensionStorageForLedgerAccount because it takes a container instead of a class (with known types).  Here’s an example of the method in AX:
container myContainer = ["110180-USAA", “110180”, 1, "Country", "USAA"];  //The third argument, 1, indicates the number of dimensions you are going to include, this does not include the main account.
AxdDimensionUtil::getLedgerAccountId(myContainer);
Here is another example constructing the contents of the
container with the dimensions included in the demo data

    // Construct input
    dimensionValueIn =
    [
        // Display value      
        strFmt('%1-%2-%3-%4', ‘10060’, '10’, ‘2020’, ‘M010’),
         // Main account
        ‘10060’,
         // Segment count
        3,
         // 2nd segment of 1st hierarchy
        ‘Department’, ‘10’,
        // 3rd segment of 1st hierarchy
        ‘Center’, ‘2020’,
         // 4th segment of 1st hierarchy
        ‘Purpose’, ‘M010’
    ];

Tuesday, January 08, 2013

Validate dialog input in Business Operation Framework

In AX2012, SysOperation framework replaces the old Runbase Batch framework. While working with it, I wasn’t able to found out how to add validation before closing the dialog. But Palle saves the day. Check out his full posting here.

=====================================
However the documentation from Microsoft in this area currently is not yet comprehensive, so here is my first article on a small area of the framework.
If you need to validate the input of the dialog for a data contract, you need to add code to the data contract. Your data contract must implement the SysOperationValidatable interface:

[DataContractAttribute]
public class BOFDataContractA implements SysOperationValidatable
{
int parmIntProperty;
str parmStrProperty;
}

And you need to add the validate method of the interface:

public boolean validate()
{
if (this.parmIntProperty() <= 25)
{
return checkFailed("The number must be higher than 25");
}
return true;
}