Thursday, April 29, 2010

How to remove leading characters, like zeros, in a string

Ax comes with lots of functions to operate on strings. Like substr, strfind, strscan, strlen, strIns, strLTrim, ...
But it looks like there is always room for more...

This post is about the trimming of strings. Unfortunately, strLTrim (and strRTrim) aren't that flexible. You can only remove leading (or trailing) blanks with these functions, no other characters.

Say you have to remove the leading zeros in a string (an amount you get in string format, a VAT num, bank account number, ...), you are out of luck. You could write your own little function to perform this task, or... use CLRInterop.

You can fall back to the use of TrimStart from System.String. With TrimStart, you can remove all leading occurrences of a set of characters specified in the parameters.

Example

System.String myString2Trim="0000123-456-000-789";
System.String trimChars="0";
;
info(myString2Trim.TrimStart(trimChars.ToCharArray()));


With this, you can remove any leading characters you wish. (Think of spaces, dots, ...)
So if you want your own version of strLTrim from Ax:

System.String myString2Trim=" 123-456-000-789";
System.String trimChars=" ";
;
info(myString2Trim.TrimStart(trimChars.ToCharArray()));


As a complementary function, there is also TrimEnd to remove any trailing occurrences of some specified characters.

(Note: Maybe you use a shortcut like int2str(str2int("001001")) to remove leading zeros from a string. That's OK, as long as you realise this has it's limitations.)

Friday, April 23, 2010

Alternative address - The value 999 is not found in the map

Situation:

When a user tried to set an alternative address on a purchase order, we got following error
The value xyz is not found in the map

After some debugging, the root of the problem was located in the form AddressSelect, in the setFocusRecord method. The value that could not be found was actually a tableid.

What happened: We have some modifications on site, where we setup the delivery address of purchase orders, based on the user who creates the order.
Now this form AddressSelect is used all over Ax, and tries to customize it's behaviour based on the origin of the current address setup. This is where things went wrong.

Let's look at the code:



At first, it's checked if a specific value exists as a key in a map. That's a good thing.
But later on in the same method, a lookup in the map is performed without a check. This will cause an error if the key does not exist. What happened in our situation.
By rearranging the code in this form, we quickly got rid of the error.
Lesson learned: Looking up a non-existent key in a map will cause an error in Ax. So if you are not sure that the value exists, first do a check and act accordingly.

(Version MS Dynamics Ax 2009 SP1 - HFR3)

Monday, April 19, 2010

A different view on ERP software

I've been reading some posts about ERP software in general over at the 360° blog, a blog from Eric Kimberling of the Panorama consulting group.

They turn the spotlight over at MS Dynamics (in general, not Ax specific). With some strange, expected and not so expected conclusions.

Like this one (about MS Dynamics):
  • Leads all ERP vendors in the product’s level of employee satisfaction
  • But a bit further, we read as a tradeoff:

  • Below average executive and management satisfaction.

So if I understand this correctly, the normal users are delighted with their Dynamics solution, but their bosses not so.

There are some more contradictions in this article.
As a pro, we can read following statement for Dynamics ERP:
  • Highest predictability (or least variance) of actual ERP implementation costs of all vendors.

A good thing. The customer gets what was offered to him, moneywise.
But again, further on we read
  • Highest variance and unpredictability of actual implementation duration

As implementation takes longer then expected, costs will rise. Where does that leave us with 'Time is money'?

Microsoft scores well in another post on this blog, in the 2010 ERP Vendor Analysis report.

From this post:
  • Microsoft Dynamics delivers the fastest payback and ROI of all the major ERP vendors, followed by Infor and Epicor.

Nice!
But then again, some statements that make you think. Like this one:
  • Tier I solutions (SAP, Oracle EBS, Microsoft Dynamics) are much more likely to require customization than Tier II and Tier III counterparts.

Strange. You'd expect the bigger systems to be less subject to customizing by code, as more parameterization is possible in general.

Maybe there is no budget with Tier III solutions to customize them, or maybe not even the possibility?



What's your 2p?

How to perform a lookup by code

Custom lookups are a recurring topic in Axapta discussions and blog posts. The lookup functionality in Ax is versatile, flexible and has changed over the different versions.

This post is about programming your lookup without the need of defining a separate lookup form, as this functionality isn't that well documented in Ax. We'll do this by using class SysTableLookup.

Let's look at a full code example first.

public void lookup()
{
SysTableLookup sysTableLookup = SysTableLookup::newParameters(tablenum(CustTable),this,true);

Query query;
QueryBuildDataSource queryBuildDataSource;
;

sysTableLookup.addLookupfield(fieldnum(CustTable,AccountNum),true);
sysTableLookup.addLookupfield(fieldnum(CustTable,Name));

query = new Query();

queryBuildDataSource = query.addDataSource(tablenum(CustTable));
queryBuildDataSource.addSortField(fieldnum(CustTable,AccountNum));

queryBuildDataSource.addRange(fieldnum(CustTable,Currency)).value(queryvalue('EUR'));

sysTableLookup.parmQuery(query);
sysTableLookup.parmUseLookupValue(true);

sysTableLookup.performFormLookup();
}

You can use the code above to override the lookup method of a control in a form.

Now let's have a look at the different parts in the code.

First, we create the table lookup by instantiating the sysTableLookup class.

SysTableLookup sysTableLookup = SysTableLookup::newParameters(tablenum(CustTable),this,true);

The form control from which the code is called is included (this).

We define which fields to include in our lookup form.
The second parameter is set to true, if that's the field you wish to return from your lookup.
If you accidentally set it multiple times to true, the field where it's last set will be the lookup field, so your lookup may not work correctly.

sysTableLookup.addLookupfield(fieldnum(CustTable,AccountNum),true);
sysTableLookup.addLookupfield(fieldnum(CustTable,Name));

Now we create the actual query, setting sorting fields and ranges as desired.

query = new Query();

queryBuildDataSource = query.addDataSource(tablenum(CustTable));
queryBuildDataSource.addSortField(fieldnum(CustTable,AccountNum));

queryBuildDataSource.addRange(fieldnum(CustTable,Currency)).value(queryvalue('EUR'));


We pass along the parameters to our sysTableLookup...

sysTableLookup.parmQuery(query);
sysTableLookup.parmUseLookupValue(true);

And we're ready to go

sysTableLookup.performFormLookup();

I tend to use this functionality, when using edit methods instead of display methods in a form (where the return EDT is for example Name, and not a specific extended data type).

How to run code in the security context of another user

If you are reading this, you're probably an Ax administrator in your company. Or you have full control over your Ax system.
But that's not the case with all the users in your Ax environment (thank God for that).
Security limits normal users in their actions, sometimes so much they cannot get the job done. Sometimes exceptions are needed, in order to let a "normal" user perform some actions. Or vice versa, as an administrator you want to run code with "lower" security rights for testing. Or you need different security rights when running batch operations.

Ax is equiped with a function to allow code to run as if it's run by another user, it's called RunAs. (We're not talking about the RunAs from Windows, which allows you to run complete programs with different security rights.)

We are running Ax with our normal user account, only temporary impersonating another user's security.

Primary condition: The code is started on the server.
Seconday condition: The called function is a static one.

Take following method of a class we have created as example.

server static void MyMethod(UserId _UserId)
{
   RunAsPermission perm;
   ;
   perm = new RunAsPermission(_UserId);
   perm.assert();

   RunAs(_UserId, classnum(YourClassName), "YourMethodName");

   CodeAccessPermission::revertAssert();
}

Now we can call that piece of code from for example a job, like this:

MyClas::MyMethod(desiredUserId);

That's all there is to it. By calling MyMethod, the runAs is activated and the defined class and method are activated with different rights.

If necessary, you can pass on additional parameters with the call to RunAs. You can include a container in your arguments. Like this:

RunAs(_UserId, classnum(YourClassName), "YourMethodName", [param1,param2]);
Be careful with what you program, as you can give any normal user administrator rights like this. Sometimes convenient if a user doesn't have specific table access, but sometimes simply dangerous or unwanted.

Wednesday, April 14, 2010

How to use the like operator with string comparisons

If you programmed SQL statements in Ax, you've probably used the like operator in your statement.
Like (!) this

select * from CustTable where CustTable.Name like 'A*'

Something less known about the like operator, is that you can also use it in string comparisons.
Example:

static void LikeOperator(Args _args)
{ str test;
;

test="Dynamics Ax";

if(test like "Dynamics*")
info('OK!');
}




You can use the like operator with your well known wildcards as * and ?
So for example

test like "D?namics*"


As such, it's use is complimentary to the likes of functions as strscan and strfind.