Wednesday, February 16, 2011

Fixing addresses that are not formatted correctly

An issue that comes up often at various clients is addresses being formatted incorrectly.  Functional people have been able to fix this by "touching" the zip code on the address with an issue...basically they click on it and re-choose the zip code and save to fix it.

What is going on behind the scenes, is the AddressMap is being used to correctly format the address.

The Address.address, CustTable.address, VendTable.address, etc. fields should not be manually filled in.  They should not have data dumped into them either.  It is a derived/built field that depends on the country code and each individual format.

This is important when you deal with international customers and customs.  Customs for European countries, for example, can be very picky with documents.  If the address is even slightly malformed, I've seen product get held for up to a month, and sometimes it's seized...all for a silly address error.

Here is a job I wrote that can be adapted to automate the process of building addresses using AddressMap.  Right now, it lets you pick a customer that you want to correct the addresses for, then displays a before/after of the addresses.

Enjoy!  As always, comments are welcome!

static void updateCustAddress(Args _args)
    Dialog                              dialog;
    DialogField                         field;
    CustTable                           custTable;
    Address                             address;
    DirPartyAddressRelationship         dpar;
    DirPartyAddressRelationshipMapping  dparm;
    AccountNum                          accountNum;

    dialog  = new Dialog("Build customer address");
    dialog.addText("Select your customer to update:");
    field   = dialog.addField(typeid(CustAccount));;

        accountNum = field.value();

        if (CustTable::exist(accountNum))
            while select forupdate address
                join dpar
                join dparm
                join custTable
                where custTable.AccountNum      == accountNum          &&
                      custTable.PartyId         == dpar.PartyId         &&
                      dpar.RecId                == dparm.PartyAddressRelationshipRecId  &&
                      dparm.RefCompanyId        == curExt()               &&
                      dparm.AddressRecId        == address.RecId
                info(strfmt("Type %1 - before:", address.type));
                info(strFmt(">%1<", address.Address));
                info(strfmt("Type %1 - after:", address.type));
                info(strFmt("<%1>", address.Address));
            info("Invalid Account");



Tuesday, February 8, 2011

Change form's background color for Dev/Test/Prod

This is a simple mod that saves TONS of headaches.  There are really three main ways to use this:

  1. Change color to signify dev, test, or prod environments
  2. Change color to signify which company is active
  3. Change color to signify which layer you're logged in to
Just put this code in Classes\SysSetupFormRun\Run to change the form's background color.  Surround it with and if ( ) block to make it change by layer or company.

Monday, February 7, 2011

How to use TreeNode to navigate objects in the AOT

During a code merge into an environment managed by another company, I came across some tables with "UNKNOWN" delete actions in their production environment.  This worried me a little bit, so I whipped up a quick job to search every table for unknown delete actions to see what the real damage was.  This demonstrates how to use TreeNode to traverse the AOT.  The reason I used "subStr" for a portion of the code was I also searched for "DEL_" delete actions.

static void findTablesWithSpecificDeleteAction(Args _args)
    TreeNode    rootNode;
    TreeNode    childNode;
    TreeNode    tempNode;
    rootNode = infolog.findNode(#TablesPath).AOTfirstChild();
    while (rootNode)
        childNode = rootNode.AOTfindChild('DeleteActions');
        if (childNode)
            tempNode = childNode.AOTfirstChild();
            while (tempNode)
                if (subStr(tempNode.AOTname(), 1, 3) == "UNK")
                tempNode = tempNode.AOTnextSibling();
        rootNode = rootNode.AOTnextSibling();

Sunday, February 6, 2011

How to use a tree form control, recursion, and containers to view nested containers

Everybody likes something tangible (well...digitally tangible) that you can download, import, and learn from.  This is a single form I developed that demonstrates the use of tree form control, recursion, and nested containers.

I expanded on a previous post about using recursion with containers specifically for the purpose of looking closely at usage data.  Look closely at the comments, as there is some good info there.  You can pass any container you want in this form.

Usage data explained

Usage data is incredibly important to the end user, and very delicate as far as data goes in my opinion.  I'll expand on why I think it's delicate later on in this post.  This post will detail what I've discovered about it, but is limited due to the fact that usage data is system level and mostly hidden.

As a developer, I am clearing my usage data regularly without giving it a second thought.  I once made the mistake of clearing all of a user's usage data when they were experiencing a strange issue with a form.  They lost all of their stored queries, user-specific form customizations, ad-hoc reports, pre-filled in data, etc.  Let's just say I haven't made that same mistake twice.

Why I researched usage data:
We have some BUS-layer IP (intellectual property) that we have sold at a client.  It was sold with the understanding of both parties that it is in development, and a very living/breathing product.  They received a big price break for being the guinea pigs on the software.  Anyway, some objects were heavily customized, such as the SalesTable form.

We started phase 1 on a version IP 1.0, and we were live and operating while phase 2 was developed.  We deployed phase 2 successfully, from a technical standpoint.  The system looks exactly as I, the developer, would expect, however many users were complaining that all of their Intellimorph customizations to certain objects (SalesTable) were gone.

How usage data is stored:
Usage data is stored in a single hidden system table called SysLastValue.  It has the following main fields
  • userId
  • recordType - UtilElementsType::*
  • elementName
  • designName
  • isKernel
  • company
  • value - Container, this is where the important stuff is
Where usage data gets difficult:
Usage data, according to MS, is different for pretty much every object.  I like to think of it as a custom, system level pack/unpack.  So unfortunately, this means you really can't just modify usage data without knowing the underlying code structure.  I personally think that there is some sort of framework for these objects once they're past the sys layer.  Like a custom pack method for SalesTable with a generic pack-other method for any custom modifications.

What you can do with this info:
You can backup usage data and copy it to other users.  If you have some custom Intellimorph modifications to a form, for example, you can run a quick job to copy your saved settings to every user in the system.  You can make a "golden user" of sorts where you make modifications to forms, then copy to other users.  I'll be posting a neat code example shortly on how to use recursion, containers, and tree nodes to view usage data shortly.

This is what I've come to understand of usage data from my experiences only.  My viewpoint is limited because the code is unavailable.

Formatting C#/X++ code to be viewable in blogger

This is a very useful website for other bloggers to convert their code.  I'm honestly posting this as a plug for them, and because I keep losing the link.

A second one I like is below:

Recursion with containers

Here is a little job that demonstrates recursion with containers.  I used a heavily modified version of this to analyize the way usage data worked.

static void containerRecursion(Args _args)
container look = ['ABC', ['1', '2', ['99', '88']], 'ZXY'];
container recurse (container _c)
int         i;
int         len = conLen(_c);
container   retVal;
while (i < len)
if (typeof(conPeek(_c, i)) != Types::Container)
retVal += conPeek(_c, i);
retVal += recurse(conPeek(_c, i));
return retVal;
info("Missing values: " + con2str(look));
info("Not missing values: " + con2str(recurse(look)));

Thursday, February 3, 2011

Zip codes, postal codes, states, counties, countries of the world!

Surprisingly, it is very hard to find a good download of zip/postal codes, states, countries, etc.  Many places charge for this up to date information.  Where I work, there is often an excel file being emailed around the office with zip codes that were harvested from some other system that we dump into clients' systems.  It's sort of a copy-paste mash of different countries that we've slowly built and it's mostly there.

Well a somewhat unknown site that is available under a creative commons attribute license that is incredibly comprehensive is

What I did is go to:

Grab the latest, import into excel (tab delimited), then throw some filters on there and use it as your master country/zip/state/county/etc list.

The site is a little difficult to navigate and they have many giant data dumps...for example, there are two "" files I've come across.  One has the good stuff (ISO country, zip, state, county, etc), and the other has stuff that I can't really find a use for (population, elevation, etc).

Hope this saves somebody else a headache when they get a client who does business globally!