Saturday, July 17, 2010

Dynamics Axapta: Business Connector & Decimal Places

This is my first post that doesn't involve code!!
Well, I don't know how to write a big article so I'll keep it short. The problem here is an error you might get when using a different quantity of decimal places when inserting transactions through Business Connector to those defined in Axapta. Here's the problem I encountered:
- Dynamics Axapta configured with 2 decimal places
- Entered a Sales Invoice through Business Connector
- When opening up the invoice previously created Axapta would stop responding.

Problem:
- I pushed a 4 decimal places transactions into a 2 decimal places Axapta.

Solution
- Rounded up all amounts to 2 decimal places

Hope it helps. I will try to explain a bit better in the future, who knows, I might even learn how to write posts...

Dynamics Axapta: Sales Orders & Business Connector

Well, again folllowing my same idea of writting close to nothing and pasting code, I'll paste in some code to create a sales order from some basic data and the invoice it. I'll try to explain more in the future.

AxaptaObject axSalesTable = ax.CreateAxaptaObject("AxSalesTable");
AxaptaRecord rcInventDim = ax.CreateAxaptaRecord("InventDim");

AxaptaRecord rcCustTable = ax.CreateAxaptaRecord("CustTable");
rcCustTable.ExecuteStmt("select * from %1 where %1.AccountNum == '" + MySalesOrderObject.CustAccount + "'");
if (MySalesOrderObject.CurrencyCode.Trim().Length == 0)
MySalesOrderObject.CurrencyCode = rcCustTable.get_Field("Currency").ToString().Trim();
string sTaxGroup = rcCustTable.get_Field("taxgroup").ToString().Trim();

//set header level fields
axSalesTable.Call("parmSalesName", MySalesOrderObject.SalesName.Trim());
axSalesTable.Call("parmCustAccount", MySalesOrderObject.CustAccount.Trim());
axSalesTable.Call("parmInvoiceAccount", MySalesOrderObject.InvoiceAccount.Trim());
axSalesTable.Call("parmSalesType", (int)MySalesOrderObject.Type);
axSalesTable.Call("parmSalesStatus", (int)MySalesOrderObject.Status);
axSalesTable.Call("parmCurrencyCode", MySalesOrderObject.CurrencyCode.Trim());
axSalesTable.Call("parmProjId", MySalesOrderObject.ProjId.Trim());
axSalesTable.Call("parmPurchOrderFormNum", MySalesOrderObject.PurchOrderFormNum.Trim());
axSalesTable.Call("parmShippingDateRequested", MySalesOrderObject.InvoiceDate);

if (MySalesOrderObject.Lines.Count > 0)
{
axSalesTable.Call("parmInventLocationId", MySalesOrderObject.Lines[0].IvDim.InventLocationId);
axSalesTable.Call("parmInventSiteId", MySalesOrderObject.Lines[0].IvDim.InventSiteId);
}

//save header
axSalesTable.Call("save");

//get the ID
MySalesOrderObject.SalesId = axSalesTable.Call("parmSalesID").ToString();

//get the RECID for taxes calculations
long iSalesRecId = Convert.ToInt64(rcSalesTable.get_Field("RecId"));
int iSalesTableId = Convert.ToInt32(rcSalesTable.get_Field("TableId"));

//Create the taxes if needed. This could be useful to some of you, let's say you have tax line objects stored against the lines
foreach (SalesTaxLine MySalesLineTaxObject in MySalesOrderObject.TaxLines)
{
AxaptaRecord rcTaxWorkRegulation = ax.CreateAxaptaRecord("TaxWorkRegulation");
rcTaxWorkRegulation.set_Field("HeadingRecId", iSalesRecId);
rcTaxWorkRegulation.set_Field("HeadingTableId", iSalesTableId);
rcTaxWorkRegulation.set_Field("TaxCode", MySalesLineTaxObject.TaxItemCode);
rcTaxWorkRegulation.set_Field("TaxRegulationAmountCur", TaxAmount);
rcTaxWorkRegulation.set_Field("TaxDirection", 0);
rcTaxWorkRegulation.set_Field("ManualInsertedTax", 1);
rcTaxWorkRegulation.Insert();
}

//create lines
foreach (SalesOrderLine MySalesOrderLineObject in MySalesOrderObject.Lines)
{
AxaptaRecord rcInventTableModule = ax.CreateAxaptaRecord("InventTableModule");

//get the unit of measure
rcInventTableModule = ax.CallStaticRecordMethod("InventTableModule", "find", MySalesOrderLineObject.ItemId, "2") as AxaptaRecord;
string sUnitId = rcInventTableModule.get_Field("UnitId").ToString();
if (sUnitId == null) sUnitId = string.Empty;

AxaptaObject axSalesLine = ax.CreateAxaptaObject("AxSalesLine");
axSalesLine.Call("AxSalesTable", axSalesTable);

//set line level fields
axSalesLine.Call("parmSalesId", MySalesOrderObject.SalesId.Trim());
axSalesLine.Call("parmItemId", MySalesOrderLineObject.ItemId.Trim());
axSalesLine.Call("parmSalesUnit", sUnitId.Trim());
axSalesLine.Call("parmSalesQty", MySalesOrderLineObject.SalesQty);
axSalesLine.Call("parmSalesPrice", MySalesOrderLineObject.SalesPrice);
axSalesLine.Call("parmLineDisc", MySalesOrderLineObject.LineDisc);
axSalesLine.Call("parmLinePercent", MySalesOrderLineObject.LinePercent);
axSalesLine.Call("parmLineAmount", MySalesOrderLineObject.LineAmount);
axSalesLine.Call("parmShippingDateRequested", DateTime.Today);

rcInventDim.Clear();
rcInventDim.set_Field("inventDimId", MySalesOrderLineObject.IvDim.InventDimId.Trim());
rcInventDim.set_Field("InventLocationId", MySalesOrderLineObject.IvDim.InventLocationId.Trim());
rcInventDim.set_Field("configId", MySalesOrderLineObject.IvDim.ConfigId.Trim());
rcInventDim.set_Field("InventSizeId", MySalesOrderLineObject.IvDim.InventSizeId.Trim());
rcInventDim.set_Field("InventColorId", MySalesOrderLineObject.IvDim.InventColorId.Trim());
rcInventDim.set_Field("InventSiteId", MySalesOrderLineObject.IvDim.InventSiteId.Trim());
rcInventDim.set_Field("InventBatchId", MySalesOrderLineObject.IvDim.InventBatchId.Trim());
rcInventDim.set_Field("wMSPalletId", MySalesOrderLineObject.IvDim.swMSPalletId);
rcInventDim = ax.CallStaticRecordMethod("inventDim", "findOrCreate", rcInventDim) as AxaptaRecord;

MySalesOrderLineObject.IvDim.InventDimId = rcInventDim.get_Field("inventDimId").ToString().Trim();
axSalesLine.Call("parmInventDimId", MySalesOrderLineObject.IvDim.InventDimId.Trim());

//save the line
axSalesLine.Call("save");

//Update any line items if needed, this can be donde this way:
if (MySalesOrderLineObject.Departamento.Trim() != string.Empty)
{
AxaptaRecord rcSalesLine = ax.CreateAxaptaRecord("SalesLine");
decimal dLineNum = Convert.ToDecimal(axSalesLine.Call("parmLineNum"));
rcSalesLine.ExecuteStmt("select forupdate * from %1 where %1.salesid == '"
+ MySalesOrderObject.SalesId + "' && %1.LineNum == " + dLineNum.ToString());

//By default from the customer
//rcSalesLine.set_Field("Dimension[1]", "DIM1");
//rcSalesLine.set_Field("Dimension[2]", "DIM2");
rcSalesLine.set_Field("Dimension[3]", "DIM3");
rcSalesLine.Call("Update");
}
}


Now let's say we want to invoice our order, we would do the following:
ax.TTSBegin();

AxaptaRecord axSalesTable = ax.CreateAxaptaRecord("SalesTable");
axSalesTable = ax.CallStaticRecordMethod("SalesTable", "find", MySalesOrderObject.SalesId) as AxaptaRecord;
AxaptaObject axSalesFormLetter_Invoice = ax.CreateAxaptaObject("SalesFormLetter_Invoice");
axSalesFormLetter_Invoice.Call("new");
axSalesFormLetter_Invoice.Call("update", axSalesTable);

AxaptaRecord rcCustInvoiceJour = ax.CreateAxaptaRecord("CustInvoiceJour");
rcCustInvoiceJour.ExecuteStmt("select * from %1 where %1.salesid == '" + MySalesOrderObject.SalesId.Trim() + "'");
MySalesOrderObject.InvoiceNumber = Convert.ToString(rcCustInvoiceJour.get_Field("InvoiceId")).Trim(); //get the invoice number if needed

ax.TTSCommit();

Monday, July 12, 2010

Dynamics Axapta: Customer Price Lists & Business Connector

In this post I'll put some code to create price lists through Business Connector. The basic idea is to update the existing price of an existing price list for a specific item (using the combination of the dimension, color and size). It might be a bit specific, but you may find it useful. I don't have enough time now to complete the post, so I will just go ahead and paste the code.

AxaptaRecord rcInventDim = ax.CreateAxaptaRecord("InventDim");
AxaptaRecord rcPriceDiscTable = ax.CreateAxaptaRecord("PriceDiscTable");

// set invent dim, get the correct one using the dimensions you have
rcInventDim.Clear();
rcInventDim.set_Field("InventSizeId", "XXL"); //big guy
rcInventDim.set_Field("InventColorId", "01"); //red
rcInventDim = ax.CallStaticRecordMethod("inventDim", "findOrCreate", rcInventDim) as AxaptaRecord;
string sInventDimId = rcInventDim.get_Field("inventDimId").ToString();

//select the discount group you use, make sure you use the dimension as we don't want to step on the same item all the time
rcPriceDiscTable.ExecuteStmt("select forUpdate * from %1 where %1.ItemRelation == '" + oPriceDisc.Item
+ "' && %1.Currency == '" + oPriceDisc.Currency + "' && %1.InventDimId == '" + sInventDimId .Trim() + "'");

AxaptaObject axPriceDiscTable = ax.CreateAxaptaObject("AxPriceDiscTable");
axPriceDiscTable.Call("PriceDiscTable", rcPriceDiscTable);

//set basic (header) fields. You might not need this, just put it here to show that you can set these fields
axPriceDiscTable.Call("parmAgreement", "001");
axPriceDiscTable.Call("parmAccountCode", 1);
axPriceDiscTable.Call("parmAccountRelation", "002");
axPriceDiscTable.Call("parmModule", 1);

// set all the rest of the line fields
axPriceDiscTable.Call("parmItemCode", "MYWHITETSHIRT");
axPriceDiscTable.Call("parmrelation", 1);
axPriceDiscTable.Call("parmCurrency", "USD");
axPriceDiscTable.Call("parmItemRelation", "MYWHITETSHIRT");

axPriceDiscTable.Call("parmQuantityAmount", 1); //1 is usually what you'll have to put
axPriceDiscTable.Call("parmAmount", 34.5M);
axPriceDiscTable.Call("parmPriceUnit", 34.5M);

//set the DimId we found before
oPriceDisc.IvDim.InventDimId = sInventDimId;
axPriceDiscTable.Call("parmInventDimId", sInventDimId);

//save price disc
axPriceDiscTable.Call("save");

Sunday, July 4, 2010

Dynamics Axapta: Inventory Transfer Orders & Business Connector

I'm going to talk about using the business connector to Ship inventory transfer orders. When doing this, as with many MS Dynamics Axapta development cases, trial and error was my approach, if you are an MS Dynamics Axapta developer you probably already know there's not that much information out there for you to use.
My first requirement was to completely Ship an inventory transfer order. The class I needed to use was: "InventTransferUpdShip". I initialized that class using table "InventTransferParmTable".
Without further ado, let me present to you a snippet of code that does the trick:
try
{
ax.TTSBegin();

AxaptaRecord rcInventTransferParmTable = ax.CreateAxaptaRecord("InventTransferParmTable");
AxaptaObject oInventTransferUpdShip = ax.CreateAxaptaObject("InventTransferUpdShip");

rcInventTransferParmTable.Call("Clear");
rcInventTransferParmTable.Call("initParmDefault");

string sParmId = Convert.ToString(ax.CallStaticClassMethod("RunBaseMultiParm", "getSysParmId"));
rcInventTransferParmTable.set_Field("ParmId", sParmId.Trim());
rcInventTransferParmTable.set_Field("TransferId", "TOID-00001");
rcInventTransferParmTable.set_Field("UpdateType", 0); //InventTransferUpdateType::Shipment
rcInventTransferParmTable.set_Field("PrintTransferShipment", 0); //NO, we don't want to print anything
rcInventTransferParmTable.set_Field("ShipUpdateQty", 1); //InventTransferShipUpdateQty::All
rcInventTransferParmTable.set_Field("EditLines", 1); //YES
rcInventTransferParmTable.set_Field("ExplodeLines", 1); //YES
oInventTransferUpdShip = (AxaptaObject)ax.CallStaticClassMethod("InventTransferUpdShip", "newParmBuffer", rcInventTransferParmTable);

oInventTransferUpdShip.Call("Run");

//no problems, then commit the transaction
ax.TTSCommit();
ax.Logoff();
}
catch(Exception ex)
{
//Do something with this!! At least a rollback!
}

Now that we have our order successfully shipped, we might want to partially ship it, this could be a bit of a problem. I first started trying the same approach, trying to use the same class since it has a very tempting method called updateInventTransferLine. But after trying several things I kept getting an error saying: "Ledger class not initialized when calling InventUpd_Financial.initUpdate". What to do here then? Well, let's initialize the missing object by calling "InventTransferUpdShip" base class method "beginLedger" and initializing the lines. Here's a bit of code to do this:

try
{
ax.TTSBegin();

AxaptaRecord rcInventTransferParmTable = ax.CreateAxaptaRecord("InventTransferParmTable");
AxaptaObject oInventTransferUpdShip = ax.CreateAxaptaObject("InventTransferUpdShip");

rcInventTransferParmTable.Call("Clear");
rcInventTransferParmTable.Call("initParmDefault");

string sParmId = Convert.ToString(ax.CallStaticClassMethod("RunBaseMultiParm", "getSysParmId"));
rcInventTransferParmTable.set_Field("ParmId", sParmId.Trim());
rcInventTransferParmTable.set_Field("TransferId", "TOID-00001"); /*Assign the transfer order id*/
rcInventTransferParmTable.set_Field("UpdateType", 0); //InventTransferUpdateType::Shipment
rcInventTransferParmTable.set_Field("PrintTransferShipment", 0); //NO, we don't want to print anything
rcInventTransferParmTable.set_Field("ShipUpdateQty", 1); //InventTransferShipUpdateQty::All
rcInventTransferParmTable.set_Field("EditLines", 1); //YES
rcInventTransferParmTable.set_Field("ExplodeLines", 1); //YES
oInventTransferUpdShip = (AxaptaObject)ax.CallStaticClassMethod("InventTransferUpdShip", "newParmBuffer", rcInventTransferParmTable);

oInventTransferUpdShip.Call("beginLedger"); //load the ledger to use in the transaction
oInventTransferUpdShip.Call("initJournalLinesList"); //initialize the lines, we will then modify them depending on the quantities we want to ship

//Initialize one line and partialy ship it
//you could do this for lots of lines, just iterate through them and add them to the object
AxaptaRecord rcInventTransferParmLine = ax.CreateAxaptaRecord("InventTransferParmLine");
AxaptaRecord rcInventTransferLine = ax.CreateAxaptaRecord("InventTransferLine");
rcInventTransferLine = ax.CallStaticRecordMethod("InventTransferLine", "find",
"TOID-00001" /*this is my transfer order id*/, 1.00 /*this is the line number, I only want the first one for this code snippet*/, 1) as AxaptaRecord;
if (rcInventTransferLine.Found)
{
rcInventTransferParmLine.Call("initFromInventTransferLine", rcInventTransferLine);
rcInventTransferParmLine.set_Field("QtyShipNow", 12); //only ship the QTY that was selected and close the line
rcInventTransferParmLine.set_Field("QtyReceiveNow", 0M);
rcInventTransferParmLine.set_Field("QtyScrapNow", 0M);
rcInventTransferParmLine.set_Field("InventTransId", rcInventTransferLine.get_Field("InventTransId").ToString().Trim());
rcInventTransferParmLine.set_Field("InventTransIdTransit", rcInventTransferLine.get_Field("InventTransIdTransitTo").ToString().Trim());
rcInventTransferParmLine.set_Field("InventDimId", rcInventTransferLine.get_Field("InventDimId").ToString().Trim());
rcInventTransferParmLine.set_Field("CloseLine", 1); //YES

oInventTransferUpdShip.Call("updateInventTransferLine", rcInventTransferParmLine);
}
else
throw new Exception("Not found! What's that all about, I just checked and it was in the database!");


//no problems, then commit the transaction
ax.TTSCommit();
ax.Logoff();
}
catch(Exception ex)
{
//Do something with this!! At least a rollback!
}

Well, hope somebody finds this post useful.
Cheers.