FrenCo

Barcode initialization

Rather frequently these days (since the coming of WHS) I find myself in a situation where I require all items in a company to have at least some provisional barcode.

Also bear in mind that as long as you do not use your barcodes outside your own warehouse(s), there is no requirement to have an official gtin-range assigned.

To keep things simple (I do like my things on the simple side), here is a little job I wrote to generate a random EAN13-style barcode for each item in the current company.

static void FrenCoInitBarcodes(Args _args)
{
    InventItemBarcode       iibc;
    InventTable             it;
    UnitOfMeasure           uom;
    UnitOfMeasureConversion uomc;
    
    ItemBarCode GenerateEAN13()
    {
        System.Random       random = new System.Random();
        int                 ean[];
        int                 i, a, b, x;
        ItemBarCode         bc;

        InventItemBarcode   inventItemBarcode;

        do
        {
            bc = "";
            
            //generate array with 12 random numbers
            for (i = 1; i < 13; i++)
            {
                ean[i] = random.Next(10);
            }

            //calculate check digit
            for (i = 12; i > 0; i-=2)
            {
                //There is a bug in AX. According to the EAN specs, the digits are numbered from right to left.
                //This means that ean[12, 10, 8, 6, 4, 2] are the sum of the odd numbers (1, 3, 5, 7, 9, 11)
                //Subsequently 11, 9, 7, 5, 3, 1) are the sum of the even numbers (2, 4, 6, 8, 10, 12).
                //However...AX wants them left to right.
                a += ean[i];                //sum of even numbers
                b += ean[i-1];              //sum of odd numbers
                bc += int2str(ean[i]);      //add next odd number to barcode
                bc += int2str(ean[i-1]);    //add next even number to barcode
            }
               //( 10 - [ (3 * Odd + Even) modulo 10 ] ) modulo 10
            bc += int2str((10 - ((3 * b + a) mod 10)) mod 10);  //add checkdigit to barcode

            sleep(5);   //delay to compensate for writing lag
            x++;
        }
        while (InventItemBarcode::findBarcode(bc, false, false) && x < 100);    //check if barcode is available. Give up after 100 attempts

        return bc;
    }

    delete_from iibc;

    while select uomc 
    join it 
    where uomc.Product == it.Product
       && it.ItemType  == ItemType::Item
    {
        uom = UnitOfMeasure::find(uomc.FromUnitOfMeasure);
        select iibc 
        where iibc.itemId == it.ItemId 
           && iibc.UnitID == uom.Symbol;
        if (!iibc)
        {
            iibc.clear();
            iibc.itemId = it.ItemId;
            iibc.barcodeSetupId = 'EAN13';
            iibc.description = it.productName(CompanyInfo::languageId());
            iibc.inventDimId = InventDim::findOrCreateBlank().inventDimId;
            iibc.itemBarCode = GenerateEAN13();
            iibc.useForInput = true;
            iibc.useForPrinting = true;
            iibc.RetailShowForItem = true;
            iibc.UnitID = uom.Symbol;
            iibc.qty = uomc.Factor;
            try
            {
                iibc.insert();
            }
            catch
            {
                exceptionTextFallThrough();
            }
        }
        
        uom = UnitOfMeasure::find(uomc.ToUnitOfMeasure);
        select iibc 
        where iibc.itemId == it.ItemId 
           && iibc.UnitID == uom.Symbol;
        if (!iibc)
        {
            iibc.clear();
            iibc.itemId = it.ItemId;
            iibc.barcodeSetupId = 'EAN13';
            iibc.description = it.productName(CompanyInfo::languageId());
            iibc.inventDimId = InventDim::findOrCreateBlank().inventDimId;
            iibc.itemBarCode = GenerateEAN13();
            iibc.useForInput = true;
            iibc.useForPrinting = true;
            iibc.RetailShowForItem = true;
            iibc.UnitID = uom.Symbol;
            iibc.qty = uomc.Factor;
            try
            {
                iibc.insert();
            }
            catch
            {
                exceptionTextFallThrough();
            }
        }
        
    }
    info('Done!');
}

I ran into some trouble getting the above to work, and I suspect this is due to a bug in AX.
The checkdigit of the barcode is calculated by first adding the (positional) odd numbers and the (positional) even numbers, then multiplying the sum of the odd numbers by 3. According to the EAN13 spec, the digits are numbered from right to left.

Digit836903331153
Pos.121110987654321

So in the above example, the sum of the odd numbers would be aggregating positions 1, 3, 5, 7, 9 and 11: 3 + 1 + 3 + 3 + 9 + 3 =22.
Calculating the check digit this way does however not agree with AX. After some trial and error, I found that AX counts from left to right instead, aggregating positions 12, 10, 8, 6, 4 and 2 to get the sum of the odd positions.

It seems to me that this would cause loads of problems when using an official gtin-range. Personally I have not heard of such problems, so let's not exclude the possibility that I somehow misinterpreted the specs. As always, I welcome any and all feedback on this article.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.