Warehouse Management–Dynamics Ax 2009 Bulk Transfers

18. August 2011


Business Problem
Logistics needs the ability to create individual or bulk transfer journals in order to either move items between bin locations in the same warehouse or to change the inventory dimensions on items in stock.

Solution
Using the current warehouse management application, include a new module that allows logistics to quickly locate inventory, apply new inventory dimensions, and post the transfer journal to Microsoft Dynamics AX 2009.

Technologies:
.Net Framework 4.0, Silverlight 4.0, WCF, MVVM, Microsoft Dynamics 2009

Bulk-Transfers Start Page:

image


Office Style Ribbon Bar And Search Panel:

image

Inventory Search:

image

Search Results:

image


Invalid destination dimensions validation:

image


Inventory with new destination dimensions added to journal:

image


Posting: Transfer Journal Information

image

.Net Framework 4.0, C#, Microsoft Dynamics Ax 2009, Portfolio, Silverlight, WCF, MVVM

Microsoft Dynamics AX WPF Address Import Tool

1. June 2011

Business Problem:
Importing addresses in to Microsoft Dynamics AX 2009 is time consuming and difficult to do and the support staff needed a simpler way to import address across all four Microsoft Dynamics AX 2009 environments (DEV, TEST, UAT, and PROD).

Solution:
The solution was to use WPF, WCF, and the Microsoft Dynamics AX 2009 .Net Business Connector to allow the sales support staff to load up an excel spreadsheet into the application and quickly import the addresses in to separate environments by changing a combo box. This allowed the sales support staff to quickly load, test, and verify addresses.

image

Microsoft Dynamics Ax 2009, Portfolio , , , ,

Microsoft Dynamics Ax 2009 for Android

24. April 2011

Warehouse Management

Initial Home Screen

WCF Authentication

clip_image001

clip_image002

clip_image003

Multi-Threaded

Menu Options

PO Receipt

clip_image004 image image

Microsoft Dynamics Ax 2009, Portfolio, Android SDK, Java

Microsoft Dynamics Ax 2009 Pick List & Packing Slip post through code

22. June 2010

This one is going to be a bit lengthy. We needed a way to Pick/Pack/Ship a sales order line using a single method call in Microsoft Dynamics Ax 2009. We used this code in a WCF service that allowed us to make a single call to pick, pack, and ship a single line from a sales order.

Below is the entire codebase I wrote (with comments) to accomplish this goal. If you have any questions please drop me an email.

   1: // classDeclaration
   2: class VendorIntegration_Shipment
   3: {
   4:     WMSOrderTransType_Output   wmsOrderTransType;
   5:     WMSOrderTrans              wmsOrderTrans;
   6:     DlvMode                    dlvMode;
   7:     InventTable                inventTable;
   8:     WMSRouteId                 pickListId;
   9:     PackingSlipId              packingSlipId;
  10:     CustDlvModeId              custDlvModeId;
  11:     DlvModeId                  dlvModeId;
  12:     str 10                     tradingPartner;
  13: }
  14:  
  15: // new instance method
  16: // Trading Partner: string used to determine calling vendor
  17: // PickListId: Picklist from the sales order
  18: // CustDlvModeId: Vendor's Shipping Method (we will look up ours from an xref table)
  19: void new(str 10 _tradingPartner, WMSRouteId _pickListId, CustDlvModeId _dlvMode)
  20: {
  21:     VendorIntegrationModeOfDeliveryXRef vendDlvModeXRef;
  22:     ;
  23:     // trading partner is a string value we used to determine the vendor so we can look up cross reference information
  24:     // specific to that vendor. Some vendors use different Shipping Method names than we do so we use a cross 
  25:     // reference table to store their Shipping methods to ours.
  26:     if(_tradingPartner == '' || _pickListId == '' || _dlvMode == '')
  27:     {
  28:         throw Global::error("One or more arguments were not supplied. (cstor).");
  29:     }
  30:  
  31:     tradingPartner = _tradingPartner;
  32:     pickListId = _pickListId;
  33:     // Check to see if the supplied shipping method is different
  34:     // from the Picking List.
  35:     if(WMSPickingRoute::find(_pickListId).DlvModeId != _dlvMode)
  36:     {
  37:         // Check to see if we have this shipping method
  38:         dlvMode = DlvMode::find(_dlvMode);
  39:         if(dlvMode == null)
  40:         {
  41:             // Look up the supplied shipping method in the vendorXRef table.
  42:             vendDlvModeXRef = VendorIntegrationModeOfDeliveryXRef::find(_tradingPartner, _dlvMode);
  43:             if(vendDlvModeXRef != null)
  44:             {
  45:                 //Wire it up
  46:                 dlvMode = DlvMode::find(vendDlvModeXRef.DlvModeId);
  47:             }
  48:             else
  49:             {
  50:                 throw Global::error("The supplied shipping method could not be found (VendShippingXRef).");
  51:             }
  52:         }
  53:     }
  54:     else
  55:     {
  56:         dlvMode = DlvMode::find(_dlvMode);
  57:     }
  58: }
  59:  
  60: // Primary method to kick off the PICK/PACK/SHIP
  61: // RecId: Specific record id from the WMSOrderTrans table generated AFTER PickList
  62: // Qty: Quantity to ship
  63: // InventSerialId: The serial # chosen to ship
  64: // TrackingNumbers: x++ Container of tracking numbers supplied for this shipment
  65: // ShipDate: The date of the shipment
  66: public void receiveShipmentLine(int64 _recId, int _quantity, InventSerialId _inventSerialId, container _trackingNumbers, Date _shipDate)
  67: {
  68:     ;
  69:     try
  70:     {
  71:         ttsBegin;
  72:  
  73:         // Look up the order transaction by picklist and recid
  74:         select forUpdate wmsOrderTrans where wmsOrderTrans.RecId == _recId && wmsOrderTrans.routeId == pickListId;
  75:  
  76:         // Construct the Order Trans type object
  77:         wmsOrderTransType = wmsOrderTransType::construct(wmsOrderTrans);
  78:  
  79:         // If the item is serialized, then compare and replace the inventory dimension. Custom method to check if Serialized Part
  80:         if(InventTable::isSerialized(wmsOrderTrans.itemId)) this.updatePickListLineSerial(_inventSerialId);
  81:  
  82:         // Pick the line
  83:         this.registerPickListLine();
  84:  
  85:         // Pack the line
  86:         this.registerPackingSlipLine();
  87:  
  88:         //Save the tracking numbers
  89:         this.saveTrackingNumbers(_trackingNumbers, _shipDate);
  90:  
  91:         ttsCommit;
  92:     }
  93:     catch (Exception::Error)
  94:     {
  95:         error("'Exception::Error' Unable to complete receiveShipmentLine. Transaction aborted.");
  96:     }
  97: }
  98:  
  99: // Used to update the PickList Registration
 100: private void updatePickListLineSerial(InventSerialId _inventSerialId)
 101: {
 102:     InventDim           inventDimOrig;
 103:     InventDim           inventDimNew;
 104:     ;
 105:  
 106:     // If not empty
 107:     if(_inventSerialId != '')
 108:     {
 109:         if(wmsOrderTrans != null)
 110:         {
 111:             // Look up the order transaction dimension
 112:             inventDimOrig       = InventDim::find(wmsOrderTrans.inventDimId);
 113:  
 114:             if(_inventSerialId != inventDimOrig.inventSerialId)
 115:             {
 116:                 // Find dim with the new serial number (our custom method to do the lookups)
 117:                 inventDimNew = InventDim::findOwnCondDispSiteLocSerial(inventDimOrig.configId,
 118:                     inventDimOrig.InventSizeId, inventDimOrig.InventColorId, inventDimOrig.InventSiteId,
 119:                     inventDimOrig.InventLocationId, _inventSerialId);
 120:  
 121:                 // Move the registration to the new part
 122:                 wmsOrderTransType.moveReservation(inventDimNew);
 123:             }
 124:         }
 125:         else
 126:         {
 127:             throw error("'Exception::Error' Unable to updatePickListLineSerial due to null wmsOrderTrans object. Transaction aborted.");
 128:         }
 129:     }
 130: }
 131:  
 132: // PickList Register this line
 133: private void registerPickListLine()
 134: {
 135:     container   pks;
 136:     Map         dict;
 137:     ;
 138:     if(wmsOrderTrans != null)
 139:     {
 140:         // Create a Map (key/value pair)
 141:         dict = new Map(Types::Int64, Types::Container);
 142:         // Create a container
 143:         pks = [wmsOrderTrans.orderId, wmsOrderTrans.recVersion];
 144:         // Load the map with the recid and container
 145:         dict.insert(wmsOrderTrans.RecId, pks);
 146:         // Call finishPickingLines
 147:         wmsOrderTransType.finishPickingLine('', wmsOrderTrans.inventDim(), EmplTable::userId2EmplId(curUserId()), TimeNow());
 148:     }
 149:     else
 150:     {
 151:         throw error("'Exception::Error' Unable to registerPickListLine due to null wmsOrderTrans object. Transaction aborted.");
 152:     }
 153: }
 154:  
 155: // Register the packing slip
 156: private void registerPackingSlipLine()
 157: {
 158:     ;
 159:     if(wmsOrderTrans != null)
 160:     {
 161:         // Register PackingSlip using a custom method we wrote (supplied below)
 162:         packingSlipId = SalesFormLetter::createPackingSlipWithDlvMode(wmsOrderTrans.inventTransRefId, dlvMode.Code);
 163:     }
 164:     else
 165:     {
 166:         throw error("'Exception::Error' Unable to registerPackingSlipLine due to null wmsOrderTrans object. Transaction aborted.");
 167:     }
 168: }
 169:  
 170: // Method to save the tracking numbers for the shipment
 171: public void saveTrackingNumbers(container _trackingNumbers, Date _shipDate)
 172: {
 173:     ShipCarrierPackage      shipPkgCarrier;
 174:     container               trackMetaData;
 175:     int                     pos;
 176:     ;
 177:     if(wmsOrderTrans != null)
 178:     {
 179:         pos = 1;
 180:         while(pos <= conlen(_trackingNumbers))
 181:         {
 182:             trackMetaData = conpeek(_trackingNumbers, pos);
 183:  
 184:             shipPkgCarrier.SalesId                  = wmsOrderTrans.inventTransRefId;
 185:             shipPkgCarrier.Weight                   = conpeek(trackMetaData, 2);
 186:             shipPkgCarrier.ShipDate                 = _shipDate;
 187:             shipPkgCarrier.PackingSlipId            = packingSlipId;
 188:             shipPkgCarrier.ShipCarrierTrackingNum   = conpeek(trackMetaData, 1);
 189:             shipPkgCarrier.CarrierId                = dlvMode.ShipCarrierId;
 190:             shipPkgCarrier.insert();
 191:             pos++;
 192:         }
 193:     }
 194:     else
 195:     {
 196:         throw error("'Exception::Error' Unable to saveTrackingNumbers due to null wmsOrderTrans object. Transaction aborted.");
 197:     }
 198: }
 199:  
 200: // Custom PacklingSlip registration
 201: public static PackingSlipId createPackingSlipWithDlvMode(SalesId _salesId, CustDlvModeId _dlvMode)
 202: {
 203:     // 06/12/2010: CCK
 204:     // Needed to create a new packing slip method that allowed changing of the delivery mode.
 205:     SalesFormLetter_PackingSlip packingSlip;
 206:     SalesTable                  localSalesTable;
 207:     DlvMode                     dlvMode;
 208:     ;
 209:     // Retrieve the dlvmode record
 210:     dlvMode = dlvMode::find(_dlvMode);
 211:     // Retrieve the sales record
 212:     localSalesTable = SalesTable::find(_salesId);
 213:     // Construct the SalesFormLeter using the Document Status Enum
 214:     // The base class knows to construct the SalesFormLetter_PackingSlip from this enum
 215:     packingSlip = SalesFormLetter::construct(DocumentStatus::PackingSlip);
 216:  
 217:     // Set a few override methods (this is only so we can override the Shipping Method set in the Sales Order)
 218:     // There are times when the Shipping Method is changed @ shipment and allows us to override the
 219:     // Shipping method set in the sales order. If you need more information contact me. You can remove
 220:     // this code for your use.
 221:     if(strUpr(localSalesTable.DlvMode) != strUpr(_dlvMode))
 222:     {
 223:         packingSlip.parmOverrideDlvMode(dlvMode.Code);
 224:         packingSlip.parmOverrideShipCarrier(dlvMode.ShipCarrierId);
 225:     }
 226:     // Call update passing the the salesTable
 227:     packingSlip.update(localSalesTable, SystemDateGet(), SalesUpdate::PickingList, AccountOrder::None, NoYes::No, NoYes::No);
 228:     return packingSlip.getPackingListId();
 229: }

Microsoft Dynamics Ax 2009 ,

Microsoft Dynamics Ax 2009 Receive Purchase Order Line through code

17. January 2010

We needed to expose a WCF service so that our logistics vendors could execute a purchase order
line receipt. There were a lot of results returned from a google & yahoo search but a good portion
of them weren’t very specific. After hours of trial and error I came up with the x++ code below
that includes a serial number update through the Inventory Dimension.
public static void receivePurchLine(PackingSlipId _packSlipId, PurchId _purchId,
                                  Qty _qty, ItemId _itemId, LineNum _lineNum, str _inventLocation)
{
    PurchFormLetter purchFormLetter;
    ParmId parmId;
    PurchParmTable purchParmTable;
    PurchParmLine purchParmLine;
    PurchTable purchTable;
    PurchLine purchLine;
    InventDim currentInventDim;
    InventDim updatedInventDim;
    ;

    purchTable = PurchTable::find(_purchId);
    purchFormLetter = PurchFormLetter::construct(DocumentStatus::PackingSlip);
    purchFormLetter.createParmUpdate();
    purchFormLetter.createParmTable(purchParmTable, purchTable);
    purchParmTable.Num = _packSlipId;
    purchParmTable.insert();
    while select purchLine
        where purchLine.PurchId == purchTable.PurchId
    {
        purchParmLine.InitFromPurchLine(purchLine);
        purchParmLine.ParmId = purchParmTable.ParmId;
        purchParmLine.TableRefId = purchParmTable.TableRefId;
        if (purchLine.ItemId == _itemId && purchLine.LineNum == _lineNum)
        {
            currentInventDim = InventDim::find(purchLine.InventDimId);
            if (currentInventDim.RecId)
            {
                updatedInventDim = 
                        InventDim::findOrCreateOwnCondDispSiteLocation(currentInventDim.configId,
                                  currentInventDim.InventSizeId,
                                  currentInventDim.InventColorId,
                                  currentInventDim.InventSiteId,
                                  _inventLocation);
                if (!updatedInventDim.RecId)
                {
                    throw error('Unable to find or create destination inventory dimension');
                }
            }
            purchParmLine.ReceiveNow = _qty;
            purchParmLine.RemainAfter = purchLine.QtyOrdered - _qty;
            purchParmLine.InventDimId = updatedInventDim.inventDimId;
        }
        else
        {
            purchParmLine.ReceiveNow = 0;
            purchParmLine.RemainAfter = purchLine.QtyOrdered;
        }
        purchParmLine.setQty(DocumentStatus::PackingSlip, false, true);
        purchParmLine.setLineAmount();
        purchParmLine.insert();
    }

    purchFormLetter.proforma       (false);             // proforma ?
    purchFormLetter.printFormLetter(false);            // print ?
    purchFormLetter.specQty        (PurchUpdate::All);  // what to update?
    purchFormLetter.transDate      (today());           // update date
    purchFormLetter.run();
}

 

Microsoft Dynamics Ax 2009

Using Microsoft Dynamics Ax 2009 Business Connector To Import Address and Performance

3. August 2009

So, importing addresses the correct way, according to professionals we have talked to, is to import by using the DirParty and PartyId values to do the inserts. Let me tell you this is DOG slow and performance is horrible. If you bypass and just use the AddrTableId from say the CustTable performance is a huge improvement.

I don’t know what Microsoft (or it’s previous developers were thinking) but the DirParty and DirPartyRelationships need some SERIOUS improvement in performance or just get rid of them all together.

Imports using the Business Connector for any other data other than Addresses FLY!

Microsoft Dynamics Ax 2009 ,

Disabling a row in a Grid based off of a value in the source

14. July 2009

So I needed to disable a row in a Grid based on a value in one of the columns. I found an example on Microsoft’s website and wanted to document it here:

Overwriting the ‘active’ method on the datasource:

public int active()
{
    int ret;

    ret = super();
    if (MDSI_ConfigurationLine.MDSI_ConfigurationLineStatusId == 'CLOSED')
    {
        MDSI_ConfigurationLine_ds.allowEdit(false);
    }
    else
    {
        MDSI_ConfigurationLine_ds.allowEdit(true);
    }

    return ret;
}

You need to make sure you have the else to allowEdit otherwise all of rows will be disabled.

Microsoft Dynamics Ax 2009

Microsoft Dynamics AX 2009 AOS Server Service Fault

11. July 2009

Discovered a problem with the Axapta AOS Server service stopping and faulting:

Faulting application Ax32Serv.exe, version 5.0.1000.52, time stamp 0x490c68d8, faulting module Ax32Serv.exe, version 5.0.1000.52, time stamp 0x490c68d8, exception code 0xc0000005, fault offset 0x00000000004be844, process id 0x3a0, application start time 0x01c992a34fef6bbc.

The error was discovered when attempting to run the Label Editor. We deleted all the .ali .alc .ald files and restarted the service.

Microsoft Dynamics Ax 2009 ,

Getting values from another table to wire up into a form’s fields

11. July 2009

I had to develop a unique situation where a table in axapta needed to be populated using some fields from a newly created table. The problem was that the table ‘ConfigTable’ and it’s associated form allowed the user to enter in information via a textbox/string and we wanted the form to populate this data from another table. We were worried that people won’t do the due diligence to check and see if the ‘ConfigId’ already existed nor would the follow formatting rules. So what we did is was created a new “OwnerMasterTable” table that contained all the unique “ConfigId” and “Name” fields we then changed the form to do a lookup.

We hid the first two fields in the ConfigTable form (ConfigId and Name) and added a new field to the form: OwnerId. Using this field, we were able to lookup the values from the “OwnerMasterTable” and populate the “hidden” values in the form that would then be used to populate the "ConfigTable”. We did it by overwriting the textChange event on the OwnerId so that we could wireup the hidden fields and not break the existing “ConfigTable” schema.

public void textChange()
{
    super();
    // We know what the text is that was selected so lets wire up the configTable's ConfigId value
    configtable.ConfigId = this.text();
    // We need to do a quick lookup of the ownerMasterTable so we can
    // can get the correct Name to wireup to the ConfigTable Name property using the text from above.
    select ownerMasterTable where ownerMasterTable.OwnerId == this.text();
    configtable.Name = ownerMasterTable.Name;
}

Microsoft Dynamics Ax 2009

Dealing with lists in .Net Business Connector and how to get around it

11. July 2009

So I had the pleasure of having to deal with Lists and Containers in the .Net Business Connector and it was NOT a pleasurable experience. Axapta uses lists and containers and trying to use them in C# was a complete nightmare because using either type always generated a ‘Bad Container’ error.

The Axapta SDK states that a List is equivalent  to a generic list in C#….this is incorrect Axapta doesn’t support generics. The real c# array to use is the ArrayList because it is not of generic type. When you go to pass the ArrayList into the method you must use the following code to do so: list.ToArray(typeof(string)). Otherwise you will get an invalid parameter exception but this still generated a ‘Bad Container’ exception.

The Axapta SDK states that you can use a AxaptaContainer to pass into a method where the container object is being used but unfortunately COM interop doesn’t translate it correctly and once again throws a ‘Bad Container’ exception.

The solution? I ended up using a comma separated  string to pass into a new method and used the Axapta ‘strSplit’ Global method to split the string into an Axapta List.

Below is the x++ method that I created

server static void finishMultiMDSi(str _pickListIds)
{
    List list = new List(Types::String);
    ;
    if (_pickListIds)
    {
        // We pass in a comma sep string, we split the string into a list
        list = strSplit(_pickListIds, ',');
        // Pass the list into the finishMulti method to post the pick list
        WMSPickingRoute::finishMulti(list.pack());
    }
}

Microsoft Dynamics Ax 2009 ,