Christopher Kane

Ramblings of a Software Architect

Recent posts

Tags

Categories

Navigation

Pages

Archive

Blogroll

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

Axapta x++ Pick List Registration & packing slip post through code

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 Axapta. 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: }
Posted: Jun 22 2010, 08:35 by ckane | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

(SOLVED) Dynamics AX 2009 – Receive Purchase Order Line using x++ & .Net Business Connector

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(); }

 

Posted: Jan 17 2010, 08:14 by ckane | Comments (1) RSS comment feed |
  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Axapta Business Connector To Import Address and Performance

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!

Posted: Aug 03 2009, 13:14 by ckane | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

AOS Server Service Fault

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.

Posted: Jul 11 2009, 02:14 by ckane | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

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

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;
}

Posted: Jul 11 2009, 02:10 by ckane | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

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

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());
    }
}

Posted: Jul 11 2009, 02:10 by ckane | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Executing Pick List Registration in code

So I needed to post the pick list registration outside of Axapta. After quite a bit of time I found out exactly where it is done and it’s very simple.

WMSPickingRoute::finishMulti() is the method that is used to post the pick list registration and all the method takes is a container of PickingRouteId’s. You simply pass in all the PickingRouteId’s that you need to be Registered and the logic in this method takes care of it.

Posted: Jul 11 2009, 02:06 by ckane | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5