Thursday, September 12, 2013

Microsoft Dynamics AX 2012 Intercompany – Principal Company Model (Part3)

 
Now let's verify that security considerations are taken into account based on the setup we've done
 
Sales Company
 
Sales user in Sales Company should not be able to access information in other companies
 
Intercompany Purchase order in ICS (Sales Company)
 
 
When Sales user will try to navigate to Intercompany order which belongs to another company he/she will see the following message
 
Intercompany Sales order -> Intercompany Sales order in ICP (Principal Company)
 

 
Manufacturing Company
 
Manufacturing user in Manufacturing Company should not be able to access information in other companies
 
Intercompany Sales order in ICM (Manufacturing Company)
 
 
When Manufacturing user will try to navigate to Intercompany order which belongs to another company he/she will see the following message
 
Intercompany Purchase order -> Intercompany Purchase order in ICP (Principal Company)
 

 
Addendum
 
Intercompany Chain function *
 
 
In standard Microsoft Dynamics AX 2012 assuming that appropriate setup is in place Intercompany order/s will be generated when you close Sales orders form
 
Form/SalesTable/Data Sources/SalesTable/Methods/Leave
 
public boolean leave()
{
    boolean ret;
 
    ret = super();
 
    salesTableForm.interCompanyAutoCreateOrders();
 
    if (!salesLine.PBAItemLine::checkMandatory() )
    {
        return false;
    }
 
    return ret;
}
 
Intercompany Chain function is a custom function which will generate full Intercompany chain on demand when needed without a need to close Sales orders form and re-open it
 
As the result full Intercompany chain is generated by one click
 

 
Please note that all 5 Intercompany orders implementing Principal Company Structure model will be created right away: SO (ICS) -> PO (ICS) -> SO (ICP) -> PO (ICP) -> SO (ICM)
 
Development
 
Code defect in Classes/PurchTableType/Methods/interCompanyMirror
 
This code defect prevents default settings ("Automatically create orders" and "Direct delivery" checkboxes) from Customer master to be populated to Intercompany Sales order
 
/// <summary>
///    Synchronizes the puchase order header with the related intercompany sales order header.
/// </summary>
/// <param name="_interCompanySilent">
///   Indicates whether the creation of the intercompany sales order should be done silently.
/// </param>
public void interCompanyMirror(boolean _interCompanySilent = false)
{
    InterCompanyTradingValueMap         fromInterCompanyTradingValueMap;
    InterCompanyTradingValueMap         toInterCompanyTradingValueMap;
    InterCompanyEndpointActionPolicy    toEndpointActionPolicy;
 
    PurchLine                           purchLine;
    PurchLineType                       purchLineType;
    AxSalesTable                        axSalesTable;
    CustTable                           custTable;
    VendTable                           vendTable;
 
    TradeInterCompanyConv               convDlvTerm, convDlvTermSyncBack;
    TradeInterCompanyConv               convDlvMode, convDlvModeSyncBack;
    TradeInterCompanyConv               convReturnReasonCode;
 
    SalesTable                          salesTableOriginal, salesTableLocal, intercompanySalesTable;
    SalesParameters                     salesParameters;
    boolean                             create;
    SalesCalcAvailableDlvDates          salesCalcAvailableDlvDates;
 
    void clearInterCompanyRef()
    {
        purchTable.InterCompanyOrder        = false;
        purchTable.InterCompanyCompanyId    = '';
        purchTable.InterCompanySalesId      = '';
    }
 
    if (purchTable.SkipUpdate == InterCompanySkipUpdate::InterCompany
    ||  purchTable.SkipUpdate == InterCompanySkipUpdate::Both
    || !purchTable.isInterCompanyOrder()
    || !this.interCompanyCreateAllowed()
    || !purchTable.interCompanyUpdateNow())
    {
        return;
    }
 
    vendTable = purchTable.vendTable_OrderAccount();
 
    if (vendTable.canAutoCreatePurchOrder())
    {
        purchTable.ChangeRequestRequired = NoYes::No;
    }
    else
    {
        // The intercompany sales order cannot be created in company %company ID of the other company%, because change management is enabled and required for vendor %ID of the IC vendor in current company%, %vendor name%, in company %ID of current company%
        warning(strFmt("@SYS344033",
            purchTable.InterCompanyCompanyId,
            vendTable.AccountNum, vendTable.name(),
            curext()));
 
        clearInterCompanyRef();
        return;
    }
 
    if (! TradeInterCompany::checkDataAreaAccess(purchTable.InterCompanyCompanyId))
    {
        clearInterCompanyRef();
        return;
    }
 
 
    fromInterCompanyTradingValueMap = vendTable.interCompanyTradingPartner().interCompanyTradingValueMap();
 
    if (purchTable.DlvTerm)
    {
        convDlvTerm = TradeInterCompanyConv::construct();
        convDlvTerm.axDlvTermId(fromInterCompanyTradingValueMap, purchTable.DlvTerm);
    }
    if (purchTable.DlvMode)
    {
        convDlvMode = TradeInterCompanyConv::construct();
        convDlvMode.axDlvModeId(fromInterCompanyTradingValueMap, purchTable.DlvMode);
    }
    if (purchTable.ReturnReasonCodeId)
    {
        convReturnReasonCode = TradeInterCompanyConv::construct();
        convReturnReasonCode.axReturnReasonCodeId(fromInterCompanyTradingValueMap, purchTable.ReturnReasonCodeId);
    }
 
    salesTableOriginal = purchTable.interCompanySalesTableOriginal();
 
    changecompany(purchTable.InterCompanyCompanyId)
    {
        setPrefix(strFmt("@SYS93821",TradeInterCompany::curCompanyName()));
 
        custTable                       = CustTable::find(vendTable.interCompanyTradingPartnerAccount());
        toInterCompanyTradingValueMap   = custTable.interCompanyTradingPartner().interCompanyTradingValueMap();
        toEndpointActionPolicy          = custTable.interCompanyTradingPartner().interCompanyEndpointActionPolicy();
 
        intercompanySalesTable = SalesTable::find(purchTable.InterCompanySalesId, true);
 
        if (!intercompanySalesTable.RecId)
        {
            create = true;
            axSalesTable = AxSalesTable::construct();
        }
        else
        {
            axSalesTable = AxSalesTable::newSalesTable(intercompanySalesTable);
        }
 
        axSalesTable.parmSkipUpdate(InterCompanySkipUpdate::InterCompany);
 
        if (create)
        {
            switch(toEndpointActionPolicy.SalesIdNumbering)
            {
                case InterCompanySalesIdNumbering::NumberSequence:
                    if (toEndpointActionPolicy.SalesIdNumberSequence)
                    {
                        axSalesTable.parmSalesId(NumberSeq::newGetNumFromId(toEndpointActionPolicy.SalesIdNumberSequence).num());
                    }
                    break;
 
                case InterCompanySalesIdNumbering::Original:
                    if (purchTable.InterCompanyOriginalSalesId)
                    {
                        if (SalesTable::exist      (purchTable.InterCompanyOriginalSalesId)
                        ||  SalesTableDelete::exist(purchTable.InterCompanyOriginalSalesId))
                            warning(strFmt("@SYS94302",purchTable.InterCompanyOriginalSalesId));
                        else
                            axSalesTable.parmSalesId(purchTable.InterCompanyOriginalSalesId);
                    }
                    else
                    {
                        if (SalesTable::exist      (purchTable.PurchId)
                        ||  SalesTableDelete::exist(purchTable.PurchId))
                            warning(strFmt("@SYS94302",purchTable.PurchId));
                        else
                            axSalesTable.parmSalesId(purchTable.PurchId);
                    }
                    break;
 
                case InterCompanySalesIdNumbering::CompanyOriginal:
                    if (purchTable.InterCompanyOriginalSalesId)
                    {
                        if (SalesTable::exist      (purchTable.DataAreaId+purchTable.InterCompanyOriginalSalesId)
                        ||  SalesTableDelete::exist(purchTable.DataAreaId+purchTable.InterCompanyOriginalSalesId))
                            warning(strFmt("@SYS94302",purchTable.DataAreaId+purchTable.InterCompanyOriginalSalesId));
                        else
                            axSalesTable.parmSalesId(purchTable.DataAreaId+purchTable.InterCompanyOriginalSalesId);
                    }
                    else
                    {
                        if (SalesTable::exist      (purchTable.DataAreaId+purchTable.PurchId)
                        ||  SalesTableDelete::exist(purchTable.DataAreaId+purchTable.PurchId))
                            warning(strFmt("@SYS94302",purchTable.DataAreaId+purchTable.PurchId));
                        else
                            axSalesTable.parmSalesId(purchTable.DataAreaId+purchTable.PurchId);
                    }
                    break;
            }
        }
 
        if (create)
        {
            axSalesTable.parmReturnStatus(this.interCompanyReturnStatusCreated());
        }
 
        if (create || purchTable.fieldChanged(fieldNum(PurchTable, OrderAccount)))
        {
            axSalesTable.parmCustAccount(custTable.AccountNum);
            axSalesTable.salesTable().initFromCustTable();
 
            //alex:>>
            axSalesTable.parmInterCompanyAutoCreateOrders(custTable.InterCompanyAutoCreateOrders);
            axSalesTable.parmInterCompanyDirectDelivery(custTable.InterCompanyDirectDelivery);
            //alex:<<
        }
 
        axSalesTable.parmInterCompanyCompanyId               (purchTable.DataAreaId);
        axSalesTable.parmInterCompanyOrder                   (purchTable.InterCompanyOrder);
        axSalesTable.parmInterCompanyPurchId                 (purchTable.PurchId);
        axSalesTable.parmInterCompanyOriginalSalesId         (purchTable.InterCompanyOriginalSalesId);
        axSalesTable.parmInterCompanyOriginalCustAccount     (purchTable.InterCompanyOriginalCustAccount);
        axSalesTable.parmInterCompanyDirectDeliveryOrig      (purchTable.InterCompanyDirectDelivery);
        axSalesTable.parmInterCompanyAllowIndirectCreationOri(purchTable.InterCompanyAllowIndirectCreation);
 
        if (create || purchTable.fieldChanged(fieldNum(PurchTable, PurchaseType)))
        {
            switch(purchTable.PurchaseType)
            {
                case PurchaseType::Purch           :   axSalesTable.parmSalesType(SalesType::Sales);        break;
                case PurchaseType::ReturnItem      :   axSalesTable.parmSalesType(SalesType::ReturnItem);   break;
 
                default : axSalesTable.parmSalesType(SalesType::Journal);
            }
        }
 
 
        if (create || purchTable.fieldChanged(fieldNum(PurchTable, CurrencyCode)))
        {
            axSalesTable.parmCurrencyCode(purchTable.CurrencyCode);
        }
 
        if (create || purchTable.fieldChanged(fieldNum(PurchTable, MatchingAgreement)))
        {
            axSalesTable.parmMatchingAgreement(PurchAgreementHeader::find(purchTable.MatchingAgreement).salesAgreementHeader().RecId);
        }
 
 
        if (create)
        {
            if (purchTable.DlvTerm)
            {
                axSalesTable.parmDlvTerm(convDlvTerm.axDlvTermId(toInterCompanyTradingValueMap));
            }
            else if (axSalesTable.parmDlvTerm())
            {
                convDlvTermSyncBack = TradeInterCompanyConv::construct();
                convDlvTermSyncBack.axDlvTermId(toInterCompanyTradingValueMap, axSalesTable.parmDlvTerm());
 
                changecompany(purchTable.DataAreaId)
                {
                    setPrefix(strFmt("@SYS93821",TradeInterCompany::curCompanyName()));
 
                    purchTable.DlvTerm = convDlvTermSyncBack.axDlvTermId(fromInterCompanyTradingValueMap);
                }
            }
        }
        else if (purchTable.fieldChanged(fieldNum(PurchTable, DlvTerm)))
        {
            axSalesTable.parmDlvTerm(purchTable.DlvTerm ? convDlvTerm.axDlvTermId(toInterCompanyTradingValueMap) : '');
        }
 
        if (create)
        {
            if (purchTable.DlvMode)
            {
                axSalesTable.parmDlvMode(convDlvMode.axDlvModeId(toInterCompanyTradingValueMap));
            }
            else if (axSalesTable.parmDlvMode())
            {
                convDlvModeSyncBack = TradeInterCompanyConv::construct();
                convDlvModeSyncBack.axDlvModeId(toInterCompanyTradingValueMap, axSalesTable.parmDlvMode());
 
                changecompany(purchTable.DataAreaId)
                {
                    setPrefix(strFmt("@SYS93821",TradeInterCompany::curCompanyName()));
 
                    purchTable.DlvMode = convDlvModeSyncBack.axDlvModeId(fromInterCompanyTradingValueMap);
                }
            }
        }
        else if (purchTable.fieldChanged(fieldNum(PurchTable, DlvMode)))
        {
            axSalesTable.parmDlvMode(purchTable.DlvMode ? convDlvMode.axDlvModeId(toInterCompanyTradingValueMap) : '');
        }
 
        if (create || purchTable.fieldChanged(fieldNum(PurchTable, DeliveryDate)))
            axSalesTable.parmDeliveryDate(purchTable.DeliveryDate);
 
        if (create || purchTable.fieldChanged(fieldNum(PurchTable, DeliveryPostalAddress)))
        {
            axSalesTable.parmDeliveryPostalAddress(purchTable.DeliveryPostalAddress);
        }
 
        if (create || purchTable.fieldChanged(fieldNum(PurchTable, DeliveryName)))
            axSalesTable.parmDeliveryName(purchTable.DeliveryName);
 
        if (create || purchTable.fieldChanged(fieldNum(PurchTable, DiscPercent)))
            axSalesTable.parmDiscPercent(purchTable.DiscPercent);
 
        if (purchTable.interCompanyEndpointActionPolicy().getOrCreateInterCompanyPolicyTransfer(InterCompanyFieldTransferType::External).CustomerInfo)
        {
            if (create || purchTable.fieldChanged(fieldNum(PurchTable, InterCompanyCustPurchOrderFormNum)))
                axSalesTable.parmPurchOrderFormNum(purchTable.InterCompanyCustPurchOrderFormNum);
            if (create || purchTable.fieldChanged(fieldNum(PurchTable, VendorRef)))
                axSalesTable.parmCustomerRef(purchTable.VendorRef);
        }
        else
        {
            axSalesTable.parmPurchOrderFormNum(purchTable.PurchId);
        }
        if (axSalesTable.salesTable().SalesType == SalesType::ReturnItem)
        {
            if (purchTable.interCompanyEndpointActionPolicy().getOrCreateInterCompanyPolicyTransfer(InterCompanyFieldTransferType::External).ReturnItemNum)
            {
                if (create || purchTable.fieldChanged(fieldNum(PurchTable, ReturnItemNum)))
                    axSalesTable.parmReturnItemNum(purchTable.ReturnItemNum);
            }
            if (!axSalesTable.salesTable().ReturnItemNum)
            {
                axSalesTable.parmReturnItemNum(NumberSeq::newGetNum(SalesParameters::numRefReturnItemNum()).num());
            }
        }
 
        if (create || purchTable.fieldChanged(fieldNum(PurchTable, ReturnReasonCodeId)))
            axSalesTable.parmReturnReasonCodeId(purchTable.ReturnReasonCodeId ? convReturnReasonCode.axReturnReasonCodeId(toInterCompanyTradingValueMap) : '');
 
        if (create || purchTable.fieldChanged(fieldNum(PurchTable, ReturnReplacementCreated)))
            axSalesTable.parmReturnReplacementCreated(purchTable.ReturnReplacementCreated);
 
        if (create)
        {
            axSalesTable.parmReceiptDateConfirmed(dateNull());
            axSalesTable.parmShippingDateConfirmed(dateNull());
        }
        if (purchTable.InterCompanyDirectDelivery)
        {
            if (new DictConfigurationKey(configurationKeyNum(SalesDeliveryDateControl)).enabled())
            {
                axSalesTable.parmReceiptDateRequested (purchTable.DeliveryDate);
                if (purchTable.InterCompanyOriginalSalesId)
                    axSalesTable.parmShippingDateRequested(purchTable.ShippingDateRequested ? purchTable.ShippingDateRequested : purchTable.DeliveryDate);
                else
                    axSalesTable.parmShippingDateRequested(purchTable.DeliveryDate);
            }
            else
                axSalesTable.parmShippingDateRequested(purchTable.DeliveryDate);
        }
        else
        {
            if (new DictConfigurationKey(configurationKeyNum(SalesDeliveryDateControl)).enabled())
            {
                axSalesTable.parmReceiptDateRequested (purchTable.DeliveryDate);
                if (create
                ||  purchTable.fieldChanged(fieldNum(PurchTable, DeliveryDate))
                ||  purchTable.fieldChanged(fieldNum(PurchTable, DlvMode)))
                {
                    if (SalesParameters::find().DeliveryDateControlType != SalesDeliveryDateControlType::None)
                    {
                        buf2Buf(axSalesTable.salesTable(),salesTableLocal);
                        salesParameters                          = SalesParameters::find();
                        salesTableLocal.DeliveryDateControlType  = salesParameters.DeliveryDateControlType;
                        salesCalcAvailableDlvDates = SalesCalcAvailableDlvDates::newCommonSalesDlvDateType(salesTableLocal);
                        salesCalcAvailableDlvDates.validateWritePrompt(salesTableLocal,false,true,true,false);
                        if (salesTableLocal.ShippingDateRequested)
                        {
                            if (salesTableLocal.ShippingDateRequested < systemDateGet())
                            {
                                if (purchTable.DeliveryDate < systemDateGet())
                                    salesTableLocal.ShippingDateRequested = dateNull();
                                else
                                    salesTableLocal.ShippingDateRequested = systemDateGet();
                            }
                        }
                        axSalesTable.parmShippingDateRequested(salesTableLocal.ShippingDateRequested ? salesTableLocal.ShippingDateRequested : purchTable.DeliveryDate);
                    }
                    else
                        axSalesTable.parmShippingDateRequested(purchTable.DeliveryDate);
                }
                else
                    axSalesTable.parmShippingDateRequested(purchTable.DeliveryDate);
            }
            else
                axSalesTable.parmShippingDateRequested(purchTable.DeliveryDate);
        }
        axSalesTable.parmDeliveryDateControlType(SalesDeliveryDateControlType::None);
 
        if (create)
        {
            axSalesTable.parmInterCompanyOrigin(InterCompanyOrigin::Derived);
        }
 
        axSalesTable.save();
 
        if (create
        && !_interCompanySilent)
        {
            info(strFmt("@SYS94303",axSalesTable.salesTable().SalesId),'', SysInfoAction_TableField::newBuffer(axSalesTable.salesTable()));
        }
 
        purchTable.InterCompanySalesId  = axSalesTable.parmSalesId();
    }
 
    if (create && purchTable.fieldChanged(fieldNum(PurchTable, PurchaseType)))
    {
        if (purchTable.existPurchLine())
        {
            while select forupdate purchLine
                  index hint PurchLineIdx
                  where purchLine.PurchId == purchTable.PurchId
                     && !purchLine.IsDeleted
            {
                purchLineType = PurchLineType::construct(purchLine,purchTable);
                purchLineType.update(false,true);
            }
        }
    }
}
 
This modification will include "Automatically create orders" and "Direct delivery" settings into a list of touched fields, so they will not be skipped upon processing
 
Implementation of Intercompany Chain function
 
server static boolean mainOnServer(SalesTable _salesTable)
{
    SalesTable                      salesTable;
    PurchTable                      purchTable;
 
    SalesLine                       salesLine;
 
    InventTable                     inventTable;
    VendTable                       vendTable;
 
    InterCompanyPurchSalesReference interCompanyPurchSalesReference;
 
    boolean                         interCompanyVendorFound = false;
    boolean                         interCompanyOrderSearch = true;
 
    SalesId                         salesId    = _salesTable.SalesId;
    DataAreaId                      dataAreaId = _salesTable.dataAreaId;
 
    while (interCompanyOrderSearch)
    {
        changeCompany (dataAreaId)
        {
            interCompanyVendorFound = false;
            interCompanyOrderSearch = false;
 
            salesTable = SalesTable::find(salesId);
 
            if (salesTable.InterCompanyAutoCreateOrders
            &&  salesTable.ReturnStatus != ReturnStatusHeader::Canceled
            &&  salesTable.ReturnStatus != ReturnStatusHeader::Closed
            && (salesTable.CreatedBy  == curUserId() ||
                salesTable.ModifiedBy == curUserId() ||
               !SalesTable::find(salesTable.SalesId).InterCompanyAutoCreateOrders)
            &&  SalesTableType::construct(salesTable).canCreatePurchOrder()
            && !salesTable.existInterCompanySales())
            {
                while select forceplaceholders crossCompany SalesId, InventDimId from salesLine
                      where salesLine.SalesId     == salesTable.SalesId
                      &&    salesLine.InventRefId == ''
                      &&    salesLine.DataAreaId  == dataAreaId
                join  inventTable
                      where inventTable.ItemId     == salesLine.ItemId
                      &&    inventTable.DataAreaId == dataAreaId
                {
                    vendTable = VendTable::find(inventTable.primaryVendorId(salesLine.InventDimId));
 
                    if (vendTable.interCompanyTradingRelationActive())
                    {
                        interCompanyVendorFound = true;
                        break;
                    }
                }
 
                if (interCompanyVendorFound)
                {
                    TradeInterCompany::autoCreateOrder(salesTable);
                }
            }
 
            select firstonly interCompanyPurchSalesReference
                where interCompanyPurchSalesReference.SalesId == salesId;
 
            if (interCompanyPurchSalesReference)
            {
                purchTable = PurchTable::find(interCompanyPurchSalesReference.PurchId);
 
                if (purchTable)
                {
                    salesTable = purchTable.interCompanySalesTable();
 
                    if (salesTable)
                    {
                        salesId    = salesTable.SalesId;
                        dataAreaId = salesTable.dataAreaId;
 
                        interCompanyOrderSearch = true;
                    }
                }
            }
        }
    }
 
    return true;
}
 
The idea behind this modification is not to change the core Intercompany business logic, but stay outside of that logic and trigger automatic generation of Intercompany chain iteratively taking into account current Intercompany orders in place on each iteration
 
Summary: This document describes how implement Principal Company Model in Microsoft Dynamics AX 2012 using Intercompany feature. This implementation involves a configuration as well as relatively simple development effort to automate Intercompany chain creation. In this document I primarily focused on Supply Chain visibility and transparency and didn't touch enough on the actual tax implementation. In fact tax scenario can be implemented in Microsoft Dynamics AX 2012 using standard Tax framework or tax may also be handled outside of Microsoft Dynamics AX 2012 when Microsoft Dynamics AX 2012 is primarily responsible for Supply Chain operations (and not Financials). Please note that Intercompany feature also allows to do Intercompany MRP for decentralized material supply scenarios. Intercompany feature in Microsoft Dynamics AX 2012 provides a solid foundation for implementing complex, real-world Supply Chain scenarios and allows you to run your business globally, effectively and efficiently.  
 
Tags: Microsoft Dynamics AX 2012, Supply Chain Management, Principal Company Model, Intercompany.
 
Note: This document is intended for information purposes only, presented as it is with no warranties from the author. This document may be updated with more content to better outline the issues and describe the solutions.
 
Author: Alex Anikiev, PhD, MCP