Please find references to Part1 and Part2 below
Part1 - Setup and Configuration: http://ax2012manufacturing.blogspot.com/2013/09/microsoft-dynamics-ax-2012-intercompany.html
Part2 - Creation and Execution: http://ax2012manufacturing.blogspot.com/2013/09/microsoft-dynamics-ax-2012-intercompany_2087.html
Security
Part1 - Setup and Configuration: http://ax2012manufacturing.blogspot.com/2013/09/microsoft-dynamics-ax-2012-intercompany.html
Part2 - Creation and Execution: http://ax2012manufacturing.blogspot.com/2013/09/microsoft-dynamics-ax-2012-intercompany_2087.html
Security
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