Wednesday, November 27, 2013

Changing remaining quantity on sales line (intercompany)

While investigating intercompany processes in AX2012 I've found an interesting thing.

Let's assume the scenario from my previous post: an intercompany direct delivery.
Out supply chain looks like this:
SO 101 -> PO 2012 -> SO 102, where 101 and 102 are company names, SO stands for "Sales order" and PO for "Purchase order".

If you're trying to manipulate delivery remainder from the code (not changing the ordered quantity), you would do the following:

    SalesLine    salesLine102;
    SalesQty     qtyDifference;
    InvetnQty    qtyDifferenceInvent;
    //select sales line
    //...
    qtyDifferenceInvent               =
       salesLine102.unitConvertSales2Invent(qtyDifference);
    salesLine102.RemainSalesPhysical -= qtyDifference;
    salesLine102.RemainInventPhysical-= qtyDifferenceInvent;                
      
    salesLine102.update();



The logic behind an update method should update the whole chain of order lines (PO102 and than SO101). However it won't happen!

The right code is placed into the form "Update delivery remainder", which can be called from the order line:


\Forms\SalesUpdateRemain\Methods\closeOk

The right code should be placed before calling update on sales order line buffer:

    

    InterCompanyUpdateRemPhys::synchronize(saleLine102,
                                           qtyDifferenceInvent,
                                           qtyDifference);



Hope it helps you in customizing AX!

Thursday, October 17, 2013

Summary update "Invoice account" doesn't work for intercompany sales orders


Hello altogether

I have discovered a strange behavior of AX (AX2012) with regards to intercompany orders and summary update.

Our customer has two subsidiaries and ships packages to the end customers (persons, further customers) in name of its clients (companies, further accounts). Sometimes one subsidiary (I will mention the company name – 101) ships packages to the customers of second subsidiary’s (102) accounts. We create an intercompany direct delivery order for that. Therefore, the whole supply chain looks like this:

SO 101 - PO 102 - SO 102

Intercompany is configured to post packing slip (product receipt) through the whole chain.
Picking is performed once a day and sometimes it comes out, that there are several orders for one customer, which are shipped together in one package. We summarize these orders into one packing slip and consider, that both PO 102 and SO 102 will also have a single delivery document posted (product receipt and packing slip).

Issue 1:

At first we used automatic summary update in both modules (Sales and Purchase):
Doing that we faced some severe performance problems, related to the query, choosing lines for update. There are more than a million customers in each company. Automatic summary update uses a sub table SalesJournalAutoSummary, containing one row per document type, storing update parameters in the customer master and on the sales order, to enable flexible summary of different document types:


The query, created inside the class SalesFormLetterParmData then looks like this:

SELECT FORUPDATE * 
 FROM SalesParmSubTable(SalesParmSubTable) 
 ORDER BY SalesTable.InvoiceAccount ASC, 
                 SalesTable.CurrencyCode ASC, 
                 SalesParmSubTable.TableRefId ASC 
 WHERE ((ParmId = N'101-005992')) 
EXISTS JOIN * 
 FROM SalesParmLine(SalesParmLine) 
 WHERE SalesParmSubTable.ParmId = SalesParmLine.ParmId AND 
              SalesParmSubTable.TableRefId = SalesParmLine.TableRefId AND 
              SalesParmSubTable.OrigSalesId = SalesParmLine.OrigSalesId 
JOIN * 
 FROM SalesTable(SalesTable) ON SalesParmSubTable.OrigSalesId = SalesTable.SalesId 
JOIN * 
 FROM SalesJournalAutoSummary(SalesJournalAutoSummary) 
 ON SalesTable.AutoSummaryModuleType = SalesJournalAutoSummary.ModuleType AND 
 (((
    (
     (SalesTable.AutoSummaryModuleType == 0) && 
     (SalesJournalAutoSummary.CustAccount == SalesTable.CustAccount)
            ) || 
           (
            (SalesTable.AutoSummaryModuleType == 1) && 
            (SalesJournalAutoSummary.SalesId == SalesTable.SalesId)
         )))
        ) AND ((DocumentStatus = 5))

If you select summary update = “Invoice account”, then this query will look like this:

SELECT FORUPDATE * 
 FROM SalesParmSubTable(SalesParmSubTable) 
 ORDER BY SalesTable.InvoiceAccount ASC, 
                 SalesTable.CurrencyCode ASC, 
                 SalesParmSubTable.TableRefId ASC 
 WHERE ((ParmId = N'101-005992')) 
EXISTS JOIN * 
 FROM SalesParmLine(SalesParmLine) 
 WHERE SalesParmSubTable.ParmId = SalesParmLine.ParmId AND 
              SalesParmSubTable.TableRefId = SalesParmLine.TableRefId AND 
              SalesParmSubTable.OrigSalesId = SalesParmLine.OrigSalesId 
JOIN * 
 FROM SalesTable(SalesTable) ON SalesParmSubTable.OrigSalesId = SalesTable.SalesId 


So it’s the same, but without SalesJournalAutoSummary and its complex range.
Due to the fact, that it is an intercompany direct delivery, we have a double slow down: the query is executed for both companies.

So, if you have a really huge customer master (usual for retail companies), please rethink using automatic summary update


So we gave up an idea to use the automatic summary and switched to a summary by invoice account. In fact, invoice account and currency were the group fields for summary.

Issue 2:

It comes out, that the summary does not work for intercompany orders in case of summary update option “Invoice account”. Funny, but if you choose the option “Automatic” and select only two fields “Invoice account” and “Currency” to summarize by, it works fine! The error is in this code (\Classes\SalesFormletterParmData\reArrangeLines, line 5, follow my comments):
    
    //-------------------------------------------------
    // it’s executed only for non-intercompany orders or
    // if summary update is “Automatic” or “Order”. 
    //---------------------------------------------------
    if (!salesTable.isInterCompanyOrder()
    ||   salesParmUpdate.SumBy == AccountOrder::Auto
    ||   salesParmUpdate.SumBy == AccountOrder::Journal)
    {
        if (this.createNewJournal(localSalesParmSubTable))
        {
            //-------------------------------------------------
            // responsible for storing summary information (is done only once for the first order)
            //---------------------------------------------------
            if (!salesParmUpdate.SumSalesId)
            {
                salesSummary.summarySalesId (salesTable.SalesId);
                salesSummary.summaryTableRef(formletterParmData::getNewTableRefId());
            }
            oldSalesParmTable = localSalesParmSubTable.salesParmTable();
            if (oldSalesParmTable.RecId && _queryRun.changed(tableNum(SalesParmSubTable)))
            {
                salesParmTableReArrange.data(oldSalesParmTable);
                if (salesParmTableReArrange.SalesId             != salesTable.SalesId)
                {
                    this.updateParmTable(salesParmTableReArrange, salesTable);
                }
                salesParmTableReArrange.TableRefId = salesSummary.summaryTableRef();
                this.insertParmTable(salesParmTableReArrange);
            }
            else
            {
                salesParmTableReArrange.clear();
                this.createParmTable(salesTable, null, salesSummary.summaryTableRef(), true);
            }
        }

        moveParmLine(localSalesParmSubTable.OrigSalesId, localSalesParmSubTable.TableRefId,
                     salesSummary.summarySalesId(), salesSummary.summaryTableRef());

        localSalesParmSubTable.TableRefId   = salesSummary.summaryTableRef();
    }
    //-------------------------------------------------
    // this code will be executed in case of intercompany and summary update =
    // "Invoice account. There will be no summary
    //---------------------------------------------------
    else
    {
        ...

This is also true for AX2009 (\Classes\SalesFormLetter\reArrange, line 242)

We haven’t posted any request to Microsoft so far, but we have corrected the “if” statement so that the orders, posted from our customization are also summarized.

I consider the issue to be in place also for normal intercompany purchase (SO 101 - PO 102). It has nothing to do with the direct delivery.

I haven’t found any documentation, stating, that intercompany packing slips cannot be summarized by account, so I suppose it's a bug.


Saturday, March 7, 2009

LINQ in X++

LINQ (Language INtegrated Query) was introduced in C# 3.0 and was created for unified SQL-like inquiry interface to any data source: database, XML files, or object collections.

In C# it can be performed in SQL-like format or through extention methods:
collection.Where(....), collection.Select(....)

In X++ SQL for DB access is already implemented, but sometimes developers need to fetch objects from collections (Lists, Maps, Sets...) by some specific criteria. This is usualy performed iterating objects and comparing their properties to specific value.

To simplify this approach I wrote small framework which allows to select objects from collections (List in my case) using predecative conditions.

Lets imageine we have simple class QueryListTEST:

class QueryListTEST
{
boolean flag;
int value;
str nam;
}

public Boolean parmFlag(Boolean _flag = flag)
{
;
flag = _flag;
return flag;
}

public Str parmName(Str _name = nam)
{
;
if (!prmISDefault(_name))
{
nam = _name;
}
return nam;
}

public Int parmValue(Int _value = value)
{
;
if (!prmISDefault(_value))
{
value = _value;
}
return value;
}

public static QueryListTEST construct(str _name, boolean _flag, int _value)
{
QueryListTEST test = new QueryListTEST();
;
test.parmName(_name);
test.parmFlag(_flag);
test.parmValue(_value);
return test;
}


It contains 3 properties (parmMethods with 1 parameter with default value).
Lets initialize list of objects of type QueryListTEST:

QueryList list = new QueryList();
;
list.addEnd(QueryListTEST::construct("", true, 0));
list.addEnd(QueryListTEST::construct("2", false, -1));
list.addEnd(QueryListTEST::construct("3", true, 2));


QueryList class is basically what I'm going to implement in this example. It extends List but has one extra method select

found = list.select(new QueryListCondition_LessOrEq('parmFlag', false).and(new QueryListCondition_More('parmName', "2"));

found is also list of objects of the same type.
As you can see, list calls select method, passing condition to it.
Condition is represented by instance of class QueryListCondition.
It's an abstract class, which compares value of class instance property (first parameter in constructor) to some value (second parameter).
Its child classes implement all possible comparison boolean operators:
==, !=, >, >=, <, <= As you also can see, you can add several conditions, just calling methods of QueryListCondition.and(...) or QueryListCondition.or(...)

What happens inside is QueryListCondition adds condition, passed in methods and and or to its internal clhildConditon list and returns itself after method call.

Now let's see what happens inside QueryList.select:

public QueryList select(QueryListCondition _condition , boolean _firstonly = false)
{
ListEnumerator elements = this.getEnumerator();
QueryList foundList = new QueryList();
Object current;
;
if (_condition)
{
while (elements.moveNext())
{
current = elements.current();
if (_condition.result(current))
{
foundList.addEnd(current);
if (_firstonly)
{
break;
}
}
}
}
return foundList;
}


QueryList iterates through its elements and passes them to QueryListCondition for check of result

QueryListCondition itself checks its own result at first for particular object and results of all its inner conditions with regard to boolean operation and, or:

public boolean result(Object _object = null)
{
boolean res;
ListEnumerator conditionsEnumerator;
ListEnumerator operations;
QueryListCondition childCondition;
boolean andOr;
;
if (_object)
{
this.parmObject(_object);
}
res = this.ownResult();
if (childConditions)
{
conditionsEnumerator = childConditions.getEnumerator();
operations = childConditionsOp.getEnumerator();
while (conditionsEnumerator.moveNext() && operations.moveNext())
{
childCondition = conditionsEnumerator.current();
andOr = operations.current();
res = andOr ? res && childCondition.result(_object) : res childCondition.result(_object);
}
}
return res;
}


In QueryListCondition.ownResult condition fetches value from object property, validates it and returs proper result, according to condition type.

QueryListObject class represents object abstration for conditions. It takes Object as parameter in its construction and creates SysDictClass object according to Object's class.

public Types parmPropertyType(str _parmMethodName)
{
SysDictMethod method = new SysDictMethod(UtilElementType::Class, thisClass.id(), _parmMethodName);
;
return method.returnType();
}

public class QueryListObject
{
Object object;
SysDictClass thisClass;
}

public ClassId classId()
{
return thisClass.id();
}

protected void new()
{
}

public Object parmObject(Object _object = object)
{
if (!object && _object)
{
object = _object;
thisClass = new SysDictClass(classIdGet(object));
}
return object;
}

public anytype parmValue(str _parmMethodName)
{
anyType value;
;
if (thisClass.hasObjectMethod(_parmMethodName))
{
new ExecutePermission().assert();
//BP Deviation documented
value = thisClass.callObject(_parmMethodName, object);
}
return value;
}

public static QueryListObject newObject(object _object)
{
QueryListObject queryListObjet;
;
if (_object != null)
{
queryListObjet = new QueryListObject();
queryListObjet.parmObject(_object);
}
return queryListObjet;
}


Then when QueryListCondition fetches value from object using property name, it refers to QueryListObject to invoke property method:

public Anytype parmValue(str _propName = property)
{
;

if (_propName && queryListOBject)
{
value = queryListOBject.parmValue(_propName);
}
return value;
}


It's also possible to pass another property value as value to compare:

QueryListCondition prop2prop;
;
prop2prop = new QueryListCondition_Equal('parmName');
prop2prop.parmProp2Compare('parmFlag');

found = list.select(prop2prop);


However I faced some X++ reflection problems in this framework. When either property or value to compare is null (0, '', dateNull, conNull), there is comparison problem (stack trace), since anytype is used as return type in QueryListObject.value and system doesn't know how to cast type.

At first I relied on X++ reflection and wrote the next code:
QueryListCondition.ownResult looks like this:

protected boolean ownResult()
{
boolean ret;
anyType val = this.parmValue();
anyType val2Compare = this.parmValue2Compare();
;
if (typeOf(val) == typeOf(val2Compare) !val !val2Compare )
{
try
{
ret = this.thisResult(val,val2Compare);
}
catch
{
ret = false;
}
}

return ret;
}


Where method thisResult is abstrat. Let's see its implementation for >= condition:
QueryListCondition_MoreOrEq.thisResult:

protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret;
;
if (_val && !_val2Compare)
{
ret = this.numericProperty() ? this.compareNumVal(_val) : true;
}
else if (!_val && _val2Compare)
{
ret = this.numericProperty() ? !this.compareNumVal(_val2Compare) : false;
}
else if (!_val && !_val2Compare)
{
ret = true;
}
else
{
ret = _val >= _val2Compare;
}
return ret;
}


It checks, whether both of value and value to compare are set or not set. Depending on that it returns proper value.
It's easy to do with almost each type, but numeric types, such as Integer or Real because they can have negative values and that's why they not obviously compared to 0.
To solve thi problem class QueryListObject has method propertyType which is supposed to return property type using SysDictMethod.returnType(...) . Then, in QueryListCondition.thisResult method next code isexecuted:

ret = this.numericProperty() ? this.compareNumVal(_val) : true;

QueryListCondition.compareNumVal compares Real value to 0. So it also applies for Integer values.
The problem in this case is that SysDictMEthod.returnType always returns void.

But this can be solved by adding one extra parameter to QueryListCondition: propertyType.

This small framework provides developers with simple access to objects in collections without serious performance degradation. Now you don't have to program collection objects itrerative comparison with several if else statements and vast of status saving variables.

Enjoy :)

Be welcome to find code of QueryListCondition and all it ancestors below

QueryListCondition

protected boolean numericProperty()
{
return propertyType == Types::Integer propertyType == Types::Real;
}

protected abstract boolean compareNumVal(real _numVal)
{
}

protected abstract boolean thisResult(anytype _val, anytype _val2Compare)
{
}

public final QueryListCondition and(QueryListCondition _condition)
{
;
if (_condition)
{
childConditions.addEnd(_condition);
childConditionsOp.addEnd(NoYes::Yes);
}
return this;
}

abstract public class QueryListCondition
{
List childConditions;
List childConditionsOp;
anytype value;
anytype value2Compare;
str property, property2Compare;
Object object;
QueryListObject queryListObject;
Types propertyType;
}

void new(str _property = '', anytype _value2Compare = null, Types _propertyType = Types::AnyType)
{
if (_property)
{
this.parmProperty(_property);
}
if (_value2Compare)
{
this.parmValue2Compare(_value2Compare);
}
if (_propertyType != Types::AnyType)
{
propertyType = _propertyType;
}
childConditions = new List(Types::Class);
childConditionsOp = new List(Types::String);
}

public final QueryListCondition or(QueryListCondition _condition)
{
;
if (_condition)
{
childConditions.addEnd(_condition);
childConditionsOp.addEnd(NoYes::No);
}
return this;
}

protected boolean ownResult()
{
boolean ret;
anyType val = this.parmValue();
anyType val2Compare = this.parmValue2Compare();
;
if (typeOf(val) == typeOf(val2Compare) !val !val2Compare )
{
try
{
ret = this.thisResult(val,val2Compare);
}
catch
{
ret = false;
}
}
return ret;
}

public Object parmObject(Object _object = object)
{
;
if (!prmIsDefault(_object))
{
object = _object;
queryListObject = QueryListObject::newObject(object);
propertyType = propertyType == Types::AnyType propertyType == Types::void
? queryListObject.parmPropertyType(this.parmProperty())
: propertyType;
}
return object;
}

public str parmProp2Compare(str _prop2Compare = property2Compare)
{
;
property2Compare = _prop2Compare;
return property2Compare;
}

public str parmProperty(str _property = property)
{
;
property = _property;
return property;
}

public Anytype parmValue(str _propName = property)
{
;
if (_propName && queryListOBject)
{
value = queryListOBject.parmValue(_propName);
}
return value;
}

public Anytype parmValue2Compare(Anytype _value2Compare = value2Compare)
{
;
value2Compare = _value2Compare;
if (property2Compare && queryListOBject)
{
value2Compare = queryListOBject.parmValue(property2Compare);
}
return value2Compare;
}

public boolean result(Object _object = null)
{
boolean res;
ListEnumerator conditionsEnumerator;
ListEnumerator operations;
QueryListCondition childCondition;
boolean andOr;
;
if (_object)
{
this.parmObject(_object);
}
res = this.ownResult();
if (childConditions)
{
conditionsEnumerator = childConditions.getEnumerator();
operations = childConditionsOp.getEnumerator();
while (conditionsEnumerator.moveNext() && operations.moveNext())
{
childCondition = conditionsEnumerator.current();
andOr = operations.current();
res = andOr ? res && childCondition.result(_object) : res childCondition.result(_object);
}
}
return res;
}


QueryListCondition_Equal

public class QueryListCondition_Equal extends QueryListCondition
{
}


protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret;
;
if (_val && !_val2Compare)
{
ret = false;
}
else if (!_val && _val2Compare)
{
ret = false;
}
else if (!_val && !_val2Compare)
{
ret = true;
}
else
{
ret = _val == _val2Compare;
}
return ret;
}


protected boolean compareNumVal(real _numVal)
{
return _numVal == 0;
}


QueryListCondition_NotEqual

public class QueryListCondition_NotEqual extends QueryListCondition
{
}

protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret
;
if ((_val && !_val2Compare) (!_val && _val2Compare))
{
ret = true;
}
else if (!_val && !_val2Compare)
{
ret = false;
}
else
{
ret = _val == _val2Compare;
}
return ret;
}


protected boolean compareNumVal(real _numVal)
{
return _numVal != 0;
}

QueryListCondition_More

public class QueryListCondition_More extends QueryListCondition
{
}

protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret;
;
if (_val && !_val2Compare)
{
ret = this.numericProperty() ? this.compareNumVal(_val) : true;
}
else if (!_val && _val2Compare)
{
ret = this.numericProperty() ? !this.compareNumVal(_val2Compare) : false;
}
else if (!_val && !_val2Compare)
{
ret = false;
}
else
{
ret = _val > _val2Compare;
}
return ret;
}


protected boolean compareNumVal(real _numVal)
{
return _numVal > 0;
}


QueryListCondition_MoreOrEq

public class QueryListCondition_MoreOrEq extends QueryListCondition
{
}

protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret;
;
if (_val && !_val2Compare)
{
ret = this.numericProperty() ? this.compareNumVal(_val) : true;
}
else if (!_val && _val2Compare)
{
ret = this.numericProperty() ? !this.compareNumVal(_val2Compare) : false;
}
else if (!_val && !_val2Compare)
{
ret = true;
}
else
{
ret = _val >= _val2Compare;
}
return ret;
}


protected boolean compareNumVal(real _numVal)
{
return _numVal >= 0;
}

QueryListCondition_Less

public class QueryListCondition_Less extends QueryListCondition
{
}

protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret;
;
if (_val && !_val2Compare)
{
ret = this.numericProperty() ? this.compareNumVal(_val) : false;
}
else if (!_val && _val2Compare)
{
ret = this.numericProperty() ? !this.compareNumVal(_val2Compare) : true;
}
else if (!_val && !_val2Compare)
{
ret = false;
}
else
{
ret = _val <>

protected boolean compareNumVal(real _numVal)
{
return _numVal <>

QueryListCondition_LessOrEq

public class QueryListCondition_LessOrEq extends QueryListCondition
{
}

protected boolean thisResult(anytype _val, anytype _val2Compare)
{
boolean ret;
;
if (_val && !_val2Compare)
{
ret = this.numericProperty() ? this.compareNumVal(_val) : false;
}
else if (!_val && _val2Compare)
{
ret = this.numericProperty() ? !this.compareNumVal(_val2Compare) : true;
}
else if (!_val && !_val2Compare)
{
ret = true;
}
else
{
ret = _val > _val2Compare;
}
return ret;
}


protected boolean compareNumVal(real _numVal)
{
return _numVal <= 0; }


QueryList

public class QueryList extends List
{
}

public void new(Types _type = Types::Class)
{
;
super(_type);
}

public QueryList select(QueryListCondition _condition , boolean _firstonly = false)
{
ListEnumerator elements = this.getEnumerator();
QueryList foundList = new QueryList();
Object current;
;
if (_condition)
{
while (elements.moveNext())
{
current = elements.current();
if (_condition.result(current))
{
foundList.addEnd(current);
if (_firstonly)
{
break;
}
}
}
}
return foundList;
}



Sunday, July 13, 2008

Dynamics AX Performance issue. Cross object references drawbacks.

Recently I'm working on framework for Dynamics AX 4.0. Project includes form tree control data viewing.

Basically tree framework includes class representing the whole tree and each tree node.
At first each tree node class instance (let's call it TreeItemClass) is associated with particular FormTreeItem storing reference to it (parmFormTreeItem()). Then reference to this instance is being stored in FormTreeItem.data() property.




The issue appeared when tree was built and I was trying to expand it's node containing a lot of sub nodes. Each time I expanded a new node, expanding time increased.
After throughly debugging I discovered that there were huge delays in object assignment.
Probably the problem was in garbage collector, which tried to calculate references.

Anyway, problem was solved by removing FormTreeItem reference from TreeItemClass and changing it to TreeItemIdx.

So be careful with cross object refernces in your code and try to avoid it.

Wednesday, July 2, 2008

CLR Interoperbility. Problem with Assembly Versions

If you're using CLR interoperability to access external logic, you can perform construct objectes two possible ways:

1. Add Reference and then construct needed object
Microsoft.Office.Interop.Outlook._Apllication appl = new Microsoft.Office.Interop.Outlook.ApllicationClass();

2. Also you can use
CLRObject appl = new CLRObject("Microsoft.Office.Interop.Outlook.ApllicationClass") ;

Using th first approach you're strictly tied to certain version of assembly. So, for instance, if user doesn't have Office 2007, assembly reference in DAX 4.0 will not be compiled, since certain 12th version is specified on it. So to be sure user will not receive this problem it's preferable to use second approach. How ever then you don't have an option to use .NET intelisence.

I'm looking forward fixing this issue in DAX 2009

Monday, June 2, 2008

Updating records without FORUDATE statement

I had an experience on previous project when I couldn't have inserted record in database because it was selected from another scope on server with forupdate parameter and then passed to another class to either insert or update it. Such kind of transfer was made due to abstraction of record initializer and to have the only place where record is written to DB.

The error saying that record was not selected for update was thrown.

But solution can be found in "Inside Dynamics AX" book.

The skipTTSCheck method can be called with parameter true on record in place where the record is written.
So by forcing skip of TTS check even record which was not selected for upadate can be written.

Though "last writer" rule applies here...
Anyway this record processing was expected to take place only from single machine at one concrete moment of time using batch processing.

.NET enumerations in Dynamics AX

As you know starting from DAX 4.0 .NET interoperability was introduced.
You can easily instantiate .NET classes either likewise COM or by directly calling constructor method:

Microsoft.Office.Interop.Outlook._Application outlookApplication =
new Microsoft.Office.Interop.Outlook.ApplicationClass();

it is the same with

Microsoft.Office.Interop.Outlook._Application outlookApplication =
new CLRObject("Microsoft.Office.Interop.Outlook.ApplicationClass");

Although usage of .NET objects is quiet common to COM, you cannot pass enum parameter to .NET object as int or str

Instead of doing it you have to initialize it this way:

Microsoft.Office.Interop.Outlook.OlItemType olAppointmentItem = CLRInterop::parseClrEnum('Microsoft.Office.Interop.Outlook.OlItemType',
'olAppointmentItem'
);

Here you're initializing type of new Outlook item with type Appointment.

This inconvenient trick should be held every time you want to initialize enum parameter. Anyway it is more convenient than defining macro values like is done for COM enumerations.