Wednesday, October 5, 2011

Dynamics Ax 4: Using Outlook object to resolve email address from Exchange User

Today I was using the outlook object model to read emails from outlook.  I was using

   1: fromEmail = mailItemClass.get_SenderEmailAddress();

I thought this would return something like john.smith@perfect-10.tv.  Instead it returned something like this
"/O=PERFECT10/OU=FIRST GROUP/CN=RECIPIENTS/CN=JOHNSM"
I needed to get the smtp email address, after a lot of searching I did not find anything that really worked within Ax.  So I wrote a function to do this in Ax..


   1: str ResolveEmail(str _emailAddress)
   2: {
   3:     Microsoft.Office.Interop.Outlook.Recipients recipients;
   4:     Microsoft.Office.Interop.Outlook.Recipient recipient, newRecipient;
   5:     Microsoft.Office.Interop.Outlook.AddressEntry addressEntry;
   6:     Microsoft.Office.Interop.Outlook.ExchangeUserClass exchangeUser;
   7:  
   8:     Microsoft.Office.Interop.Outlook._Application outAppl;
   9:     Microsoft.Office.Interop.Outlook.MAPIFolder mapiFolder;
  10:     Microsoft.Office.Interop.Outlook.NameSpaceClass Nspace;
  11:     Microsoft.Office.Interop.Outlook.ItemsClass itemClass;
  12:     Microsoft.Office.Interop.Outlook.OlDefaultFolders olFolderInbox
  13:         = CLRInterop::parseClrEnum('Microsoft.Office.Interop.Outlook.OlDefaultFolders','olFolderInbox');
  14:  
  15:  
  16:     int numOfEmails;
  17:     int numOfFolders;
  18:     str value;
  19:     int cnt;
  20:  
  21:     ;
  22:         try
  23:         {
  24:  
  25:             recipients = mailItemClass.get_Recipients();
  26:             cnt = recipients.get_Count();
  27:             recipients.Add(_emailAddress);
  28:             cnt = recipients.get_Count();
  29:             recipients.ResolveAll();
  30:             recipient = recipients.get_Item(cnt);
  31:             addressEntry = recipient.get_AddressEntry();
  32:             exchangeUser = addressEntry.GetExchangeUser();
  33:             if(exchangeUser)
  34:             {
  35:                 value = exchangeUser.get_PrimarySmtpAddress();
  36:             }
  37:             else
  38:             {
  39:                 value = _emailAddress;
  40:             }
  41:  
  42:         }
  43:         catch(Exception::CLRError)
  44:         {
  45:             error('cannot read email');
  46:             continue;
  47:         }
  48: //    }
  49:         return value;
  50: }

 


NOTE #1: The Dynamics Ax code to get the mailItemClass this uses is a global variable on the class and so is the FolderClass.  See this article to get the first mailItem..  http://dc313.4shared.com/doc/J3LRQLgs/preview.html


NOTE #2:  I found a article http://anoriginalidea.wordpress.com/2008/01/11/getting-the-smtp-email-address-of-an-exchange-sender-of-a-mailitem-from-outlook-in-vbnet-vsto/

which probably works but seemed like overkill to me, but luckily there was a comment in the article that had a simple .Net solution, which is what I used to translate into my Dynamics Ax method.  Here is the .Net solution I used based off the comment..Just create a standard VB App for windows forms and copy paste this code, add a reference to outlook dll.


   1:  
   2: Imports Microsoft.Office.Interop
   3:  
   4:  
   5:  
   6:  
   7: Public Class Form1
   8:  
   9:  
  10:  
  11:     Private Sub btnButton1_Click(sender As System.Object, e As System.EventArgs) Handles btnButton1.Click
  12:         MsgBox(fnGetSMTPAddress("/O=PERFECT10/OU=FIRST ADMINISTRATIVE GROUP/CN=RECIPIENTS/CN=LARRYB"))
  13:     End Sub
  14:  
  15:     Public Function fnGetSMTPAddress(ExchangeMailAddress As String) As String
  16:         Dim objOutlook As Outlook.Application
  17:         Dim objMailItem As Outlook.MailItem
  18:  
  19:         objOutlook = New Outlook.Application
  20:         objMailItem = objOutlook.CreateItem(0)
  21:         objMailItem.To = ExchangeMailAddress
  22:         objMailItem.Recipients.ResolveAll()
  23:         fnGetSMTPAddress = objMailItem.Recipients.Item(1).AddressEntry.GetExchangeUser.PrimarySmtpAddress
  24:         objMailItem = Nothing
  25:         objOutlook = Nothing
  26:  
  27:     End Function
  28:  
  29:  
  30: End Class

Monday, October 3, 2011

Display fields not showing up, but went ahead and tried it and it worked.

 

I am using Dynamics Ax 4.  I added the code below to a table

   1: display name ParentWebCategoryName()
   2: {
   3:     ptnWebCategories    ptnWebCategories;
   4:     name                name;
   5:     ;
   6:     ptnWebCategories = ptnWebCategories::find(this.webCategoryID);
   7:     name= ptnWebCategories.ParentCategoryName;
   8:     return name;
   9: }



In the past I thought that this would show up as a field when I added the table as a data source to a form.  I could not get it to show up for some reason but it did work, even in a grid..  Go figure..  I added a new field to a grid, changed the datasource to the table, changed the datamethod to the above method name, and it worked.

Friday, September 30, 2011

Ax DocuRef not showing changes

In Dynamics Ax document handling, if you make changes to the DocuRef table they are not immediately available.  You have to do a AOS restart for them to show up.

 

From the screen below..

image

Click on “Type” and go to the Main table.

It takes you to the DocuType form, I made changes to the ArchivePath, but they did not show up until I did a AOS refresh.  Caused me a couple of hours of grief..

image

Thursday, September 29, 2011

How to Deploy .NET DLL to Dynamics Ax 4

First create the .net dll then strong name it.  See .Net strong name to see how to do this.


On each AOS I have installed the gacutil from 2005, if you are using 2008 you may have to install the gacutil from that version.

It is located on your box under "C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin" for 2005, not sure if there is a 2008 version

Once you have a strong named dll, and the gacutil on the server then the following under cmd prompt should install the dll on the server

cd "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727"

gacutil /i "C:\Program Files\Microsoft Dynamics AX\40\Server\PTN_AXDB_Live\Bin\Paypal.dll"

Once installed add reference in AOT
May need to restart the AOS
Code to it..
in code need to set class to runon server (in its own class) so each client does not need to install it.

Packing Slip post of only service items on Sales Order in x++ code for Dynamics Ax 4

We are currently integrated with Manhatten SCALE warehouse management solution and we needed a way to post the service items only to packing slip status so our normal invoice batch job could pick them up and post them normally. 

We could have created a packing slip batch job that only post service items, but the business decided they wanted this done when the interface from SCALE sends the order back to us (so we know the warehouse is finished with the order),  The following is a job that updates a sales orders service items to packing slip posted..  You could easily imagine this being used on the interface back up from SCALE.

Some things that are interesting about this is it uses a query to limit what will get posted instead of using just the parameters available.  Also, interesting is that it adds a datasource that is not included in the default query and uses relationship to limit the query.

static void ptn2478_RunTestPackslipPost(Args _args)
{
SalesFormLetter salesFormLetter;
QueryRun queryRun;
Query query;
str strSalesTable = '0009430007';
QueryBuildDataSource qbdsSalesTable, qbdsSalesLine, qbdsInventTable;
;
salesFormLetter = SalesFormLetter::construct(DocumentStatus::PackingSlip);

query = new Query(QueryStr(SalesUpdate));
//Add Tables to the datasource that are not already there
qbdsSalesTable = query.dataSourceTable(tablenum(SalesTable));
qbdsSalesLine = query.dataSourceTable(tablenum(SalesLine));
qbdsInventTable = qbdsSalesLine.addDataSource(tableNum(InventTable));
//add relations
qbdsSalesLine.relations(true);
qbdsInventTable.relations(true);
//add ranges (where clause)
qbdsSalesTable.addRange(fieldnum(SalesTable, SalesId)).value(strSalesTable);
qbdsInventTable.addRange(fieldnum(InventTable, ItemType)).value(strFmt('%1',itemType::Service));
queryRun = new QueryRun(query);

salesFormLetter.chooseLinesQuery(queryRun);
salesFormLetter.transDate(systemdateget());
salesFormLetter.specQty(SalesUpdate::All);
salesFormLetter.printFormLetter(false);
salesFormLetter.createParmUpdate();

salesFormLetter.chooseLines(null,true);

salesFormLetter.reArrangeNow(true);

salesFormLetter.run();
info('complete');

}

Friday, September 23, 2011

Migrating from SourceGear Vault to Microsoft TFS

Yesterday and today we have started migrating from SourceGear Vault to Microsoft TFS. 

We are not importing in any of the history.  Our approach has been the following:
1) Open project under Vault source control
2) Unbind from Vault
3) Open Project (after we connect to TFS and set it as our source control provider in VS 2010.
4) Copy unbound project to folder we want it in TFS
5) Click on project and check references, adding the files needed to the common libraries folder we created in a separate folder
6) Go to file--> Source Control and choose option to add project to source control

This has worked fairly well, a good test is to delete the file, get latest and see if it will still build..

We have also created a build server so we can try out a build before we check it in.

I think we are going to be very happy with the additional functionality that TFS has over Vault, but it definitely is going to take some getting use to..

The main thing I miss right now is the Search tab on Vault client, and choosing "Any Status"..  This quickly shows all files that are different and quickly why they are different (Edited, Renegade, Old, etc).

The only thing I have found similiar in TFS is doing a "Compare" but it is ackward to say the least..

We are on Dynamics Ax 4 so integrating TFS to it is not an option from my understanding..

Friday, September 9, 2011

Dynamics Ax: Change default drop down for a field (lookup)

 

Recently, in my Credit/Debit memo project, I needed to create a process that would show a list of available sales orders for a invoice account that could be “Credited or Debited”, then a list of vouchers for that sales order, then a list of items from that voucher, etc..  See the form below, and in the grid is where the filtered drop down boxes would be..

image

The sales order drop down was filtered by the invoice account and looked like the following..

image

This is not an out of the box lookup box, so the rest of this blog is going to explain how to create your own custom lookup query to display.

Since this was a custom process, it has custom tables, so on the ptnCreditDebitMemoLine table I created a static method that would perform the Dynamics Ax query.  The method is below..

   1: public client static void lookupSalesId(FormStringControl _ctrl,
   2: ptnInvoiceAccount _InvoiceAccount)
   3: {
   4:  
   5:     SysTableLookup          sysTableLookup = SysTableLookup::newParameters(tablenum(SalesTable), _ctrl);
   6:     Query                   query = new Query();
   7:     QueryBuildDataSource    qbds;
   8:     QueryBuildRange         SalesTableFilter;
   9:     ;
  10:     sysTableLookup.addLookupfield(fieldnum(SalesTable, SalesId),true);
  11:     sysTableLookup.addLookupfield(fieldnum(SalesTable, CreatedDate));
  12:     qbds = query.addDataSource(tablenum(SalesTable));
  13:     qbds.addRange(fieldnum(SalesTable, InvoiceAccount)).value(queryValue(_InvoiceAccount));
  14:     qbds.addRange(fieldnum(SalesTable, SalesStatus)).value(queryValue(SalesStatus::Invoiced));
  15:     qbds.addRange(fieldnum(SalesTable, SalesType)).value(queryValue(SalesType::Sales));
  16:     qbds.addSortField(fieldNum(SalesTable,SalesId), SortOrder::Descending);
  17:     sysTableLookup.parmQuery(query);
  18:     sysTableLookup.performFormLookup();
  19: }
  20:  



In line 5 I set up the sysTableLookup to use the SalesTable.


In line 10 & 11  I add the SalesID and CreatedDate to the visible fields when you click the drop down box in Dynamics Ax.  Line 10 also sets the SalesID as the return item.


Line 12 adds the sales table to the datasource and lines 13 thru 15 add the where clause for the query.


Line 16 sorts the drop down by descending order of sales id.


Just add a static method to the table where you want a special lookup. 


Then go to the form where you want the special lookup, find the datasource


image


Expand this to get to the field you want to perform the custom lookup


image


Right click on “Methods” and choose override method, “lookup”


comment out the super call and new method should look like this..


   1: public void lookup(FormControl _formControl, str _filterStr)
   2: {
   3:     ;
   4:     //super(_formControl, _filterStr);
   5:     ptnCreditDebitMemoLine::lookupSalesId(_formControl, ptnCreditDebitMemoLine.InvoiceAccount);
   6: }





Line 5 is the call to the static method we created earlier.


That is it, now wherever you have that field on your form, either in a grid or on a tab, it will display your custom drop down.