Cross-company data sharing vs. Duplication in Dynamics

Cross-company data sharing vs. Duplication in Dynamics

Cross-company data sharing

 
Cross-company data sharing in Dynamics 365 for Finance and SCM (see System administration > Setup > Configure cross-company data sharing) supposed to be a valid replacement of the virtual companies in Dynamics AX2012 and earlier. I don’t know how about you, but I get the error “Table … is a Main table as specified by the table group and may not be shared unless its Data Sharing Type is Duplicate” every time I wish to share something useful.

It looks for the TableGroup property in the D365 table metadata and practically rejects any table which is not a Group or does not have a simple primary key. What is left for sharing? Some little configuration tables such as the Delivery terms (INCOTERMS), Payment terms ans so on: only these have the TableGroup = Group.  This rules out the most interesting tables in Dynamics such as the InventTable (Product list), CustTable (Customers) or VendTable (Suppliers) (well, this is not entirely true: for the latter there is a dedicated Feature Customer and Vendor master data sharing available for activation). Namely, these have TableGroup = Main.

The TableGroup property of the standard tables is not extendable. The developers left a backdoor: a Main table with the property DataSharingType explicitly set to Single or Duplicate may be eligible too. CustTable is an example. Needless to say, the DataSharingType is not extendable either. There seems to be a yet another backdoor in the form of the SysDataSharingTypeTableConfiguration table where this DataSharingType is supposed to be configured. However, this feature is protected by the flight EnableSysDataSharingTypeTableConfiguration. As with every flight it is very promising and tempting but it remains hard-locked in any production environment.

Dead end? Not quite.

Recurring ‘Copy into legal entity’ data project 

 
In Data management, create a new data project of the Copy into legal entity kind, select the destination company(ies) and the appropriate entity or entities:

It should not grab unfinished work, though. In this example it is obvious to only consider an approved bill of materials. The BOM lines do not have an approved  flag, but if there is no BOMId, no line can be copied either, i.e. it is sufficient to only filter the BOM headers. In general, you should take care of the data consistency. In the particular case of the bills of materials, they should not contain any unknown items or refer to any local inventory dimension such as the site, warehouse or the warehouse location: ensure the default sites in all companies in question have the same code “STD” or “DEF”, and set the Resource consumption checkbox in all lines to make them warehouse-agnostic. The Approver is not a problem, since the employee list is technically shared across all legal entities.

Click Copy into legal entity in batch and set a Recurrence e.g. every weekday. The data project may be run over and over again in a cycle copying the same data, but it is not a problem as it works in the UPSERT mode in Dynamics. However, as the data grows the data project runs slower. We should restrict the set and take only the changes of the day, for example. If you are lucky, there will be a ModifiedDateTime column you can use in the Filter:

(here with a Join and an advanced moving date query).

Find and merge a DimensionAttributeValue

Find and merge a DimensionAttributeValue

Recently I was given an opportunity by an American partner to develop something around dimensions in Dynamics 365 for Finance. A few years ago, my blog Get a cost center in D365FO resonated quite a bit, this time it goes deeper.
The task is generally to enrich a source document with a dimension [segment] according to the custom business logic, and it is one of the most common customizations in D365FO.

In this particular case, I had to put the Intercompany dimension into the GL transaction every time the respective company was involved in an IC exchange. Naturally, the IC dimension should be an entity-backed one, it should be! A few times I had a debate why didn’t we make a custom [free text] dimension for the intercompany: some legal entities were not in the D365 implementation scope [yet]. “At the headquarters D365 will only be rolled out in 2028!” The argument is flawed, since it only takes 3-5 minutes to create a new legal entity in D365 with the given ID and name.

The task can be decomposed into the following steps:

  • Identify the dimension attribute in question.
  • For the given legal entity ID (DataAreaID), find the corresponding attribute value.
  • Find a DimensionAttributeValueSet record for the given value, where the IC is the only segment populated.
  • Merge it with the existing dimensions of a transaction or a source document.

Identify the dimension attribute

The below dimension is backed by the Legal entity table CompanyInfo, in the demo company USMF it bears the name LegalEntity:

We won’t look it up by the name though, but the constant BackingEntityType, which is the number of the table. The developers did not use the TableID of the CompanyInfo directly, they abstracted the backend table with a view DimAttribute* as they did for every other ‘back-able’ entity in D365FO (Customers, Suppliers, Assets etc.) to only return the Key=RecId and Value=the symbolic 4 letters code of every legal entity, here in pseudocode *):

CREATE VIEW DimAttributeCompanyInfo
AS SELECT RecId AS KEY, DataArea AS VALUE, …
FROM CompanyInfo

*) I call it pseudocode, since a CompanyInfo does not physically exist in the database. It’s rather a DirPartyTable record. This is probably the reason why the views were put on top: the DirPartyTable does not only hold legal entities but also cost centres, departments, other organisations.

The below snippet will return the RecId of the dimension LegalEntity in D365 demo database, its performance is reinforced with the global cache:

public class DimensionICHelper
{
private const GlobalObjectCacheScope sgocScope = classStr(DimensionICHelper);
private const int sgocKey = 1;
public static DimensionAttributeRecId getICAttributeRecId()
{
SysGlobalObjectCache sgoc = classFactory.globalObjectCache();
DimensionAttributeRecId ret;
[ret] = sgoc.find(sgocScope, [sgocKey]);
if (! ret)
{
ret = (select firstonly RecId from DimensionAttribute
where DimensionAttribute.BackingEntityType == tableNum(DimAttributeCompanyInfo)
&& DimensionAttribute.DimensionKeyColumnName /*active*/).RecId
;
sgoc.insert(sgocScope, [sgocKey], [ret]);
}
return ret;
}
}

See also Get a cost center in D365FO.

Find the corresponding attribute value

The financial dimensions do not relate to the original records in the backing table (aka EntityInstance; here: CompanyInfo) but the global DimensionAttributeValue records. These only store references to the “Entity Instances”. The below code returns a DimensionAttributeValue for the given DataAreaID legal entity symbol:


public static DimensionAttributeValue getICAttributeValueByDataAreaId(DataAreaId _value)
{
DimAttributeCompanyInfo dimAttributeCompanyInfo;
DimensionAttributeValue icAttributeValue;
select firstonly icAttributeValue
where icAttributeValue.DimensionAttribute == DimensionICHelper::getICAttributeRecId()

exists join dimAttributeCompanyInfo
where dimAttributeCompanyInfo.Key == icAttributeValue.EntityInstance
&& dimAttributeCompanyInfo.Value == _value;

if (! icAttributeValue)
{
icAttributeValue = DimensionAttributeValue::findByDimensionAttributeAndValue(
DimensionAttribute::find(DimensionICHelper::getICAttributeRecId()),
_value,
false,
true); // Create if needed

}
return icAttributeValue;
}

The DimensionAttributeValue::findByDimensionAttributeAndValue() call is for an improbable situation where the backing record exists but its DimensionAttributeValue replica has not been created yet. They only do it as the legal entity (in general, an Entity Instance) is used as a dimension for the first time somewhere.

Try it for yourselves: in the demo AxDB, only USMF, USRT, USSI and DEMF are present in the DimensionAttributeValue table, while the General ledger > Chart of accounts > Dimensions > Financial dimensions, Dimension values form shows all 25+ potential ‘candidates’. Weird.

Find or create a DimensionAttributeValueSet

Next, let’s convert the dimension value found into a dimension “set” of only one entry, e.g. {USMF}. This corresponds to a DefaultDimension (the one without the MainAccount segment inside) of this kind:

Again, this Set may not exist. For example, imagine a holding with 100 companies, but only 4 are in intercompany relations with each other. Then the number of the DefaultDimensions will be in the range 0..4. What, zero?! It depends if any historical business case between the companies X and Y has ever been recorded in this D365 instance.

This is why the code is what it is:

public static DimensionDefault getDefaultDimensionForCompany(DataAreaId _value)
{
DimensionAttributeValueSetStorage valueSetStorage = new DimensionAttributeValueSetStorage();
valueSetStorage.addItem(DimensionICHelper::getICAttributeValueByDataAreaId(_value));
return valueSetStorage.save();
}

Merge the value with the existing dimensions

Finally, at some extension point we will be using the above DimensionAttributeValueSet according to some custom business logic. There may be 3 options:

  • We use the ‘naked’ default dimension as is (very seldom)
  • We merge it with another Default dimension in a master data record or in a source document (example: a new customer representing the IC company immediately becomes this segment populated)
  • We merge it with a LedgerDimension i.e. with a dimension set where the Main Account segment is populated (example: a GL transaction or a GL journal line of the Ledger AC type)

The 3rd case is the hardest since we have to convert the DefaultDimension into a LedgerDimension first, then merge:

DimensionDefault icDimensionDefault = DimensionICHelper::getDefaultDimensionForCompany(curExt());
LedgerDimensionBase icLedgerDimension = LedgerDimensionFacade::serviceCreateLedgerDimForDefaultDim(icDimensionDefault, ledgerJournalTrans.LedgerDimension);
ledgerJournalTrans.LedgerDimension = LedgerDimensionFacade::serviceMergeLedgerDimensions(icLedgerDimension, ledgerJournalTrans.LedgerDimension);

Revenue recognition with project budget shadow forecasts

Revenue recognition with project budget shadow forecasts

Recently I came across this: Idea: Automaticaly update the total forecast when budget is revised and I thought it is time to share with everybody my sacred knowledge in Dynamics 365 for Finance: the use of project budgets in the fixed fee project planning and revenue recognition. I encourage all my customers to consider the project budget as the only UI to maintain and keep the history of forecasts.

Introduction

It seems that the regular project review is the best practice in the construction industry and in the complex engineering. Once a month the project manager assesses the project progress, extracts the current actual cost, evaluates the cost “burn rate” and produces an Estimate at Completion (EAC), which is the forecasted cost of the project at its end. The new EAC is compared with the original (baseline) project forecast. The baseline (or the last adjusted) forecast is copied, updated and becomes the most recent forecast of the project:

Current Project Forecast := EAC

The ratio of the actual costs (Inception to Date, ITD) to the Project forecast is the new estimated Percentage of Completion (POC) of the project:

POC = Actual_cost_ITD / EAC = Actual_cost_ITD / Current_Forecast

The Estimated to Complete (ETC) is the forecasted cost still to occur:

ETC = Current_Forecast – Actual_cost_ITD

For a “Fixed fee” project, the contract amount is fixed and the current estimated revenue in line with IFRS 15 can be calculated as

Revenue to Date = Contract amount * POC%

In Dynamics 365 for Finance, the process of the PoC estimation and revenue recognition is called an “Estimation” (About estimates | Microsoft Docs), the forecast is called … well, a Forecast, and the actual cost is recorded by the Project posted transactions. The percentage of completion revenue recognition method is called Completed percentage in the Project group:

The problem with the D365 forecasts is that

  • There are three of them: Hour forecasts, Expense forecasts, Item forecasts. There is a view All forecasts, but it is not editable.
  • There is no facility to aggregate forecasts, e.g. Item IDs → Item Project categories
  • There is no adequate tracking of forecast revisions other than with the static forecast models (‘Jan’, ‘Feb’ and so on).

Basically, the project forecast UI in Dynamics 365 for Finance is unusable, but there is a trick:

Project budgets

In one form Project budget one can manage all 3 types of costs: Hours, Expenses (procurement category-based costs) and Items (costs for stocked products). To start using the project budgets, activate Use budget control in the project master (and project management parameters) and make sure that the Project budget number sequence is set. Independent budgeting… is advised. There must be an [automatic] budget approval and budget revision workflow configured, too.

The planned hours may be quickly Imported into the project budget from the Work breakdown structure with an aggregation by the project category (i.e. reduced by the WBS Task IDs).
One useful feature is missing: an ability to import Item requirements and compress them by the Category ID. Anyway, a project budget by category may be entered in 2 minutes, while maintaining the project forecasts for the same in 3 different forms may take 20 minutes.

There is an instant totals calculation. There is a (mandatory) approval workflow, which is a common process in large engineering companies. There is a strong history of Revisions and a workflow for their approvals.

Here comes the surprise: on an approval in the Workflow, the project budget [revision] creates 2 shadow forecasts (“original” and “remaining”, here: PRJ-O, PRJ-R) you can use in the PoC calculation or elsewhere.

The “original” forecast is what was called Current forecast or EAC above: on any project budget [revision] the Original forecast is updated with the budget values.

The “remaining” forecast is what was called ETC. I do not consider it useful, because the actual costs ITD to be subtracted from the “Original forecast” are taken at the moment of the budget revision approval. However, if the burning rate lays within the predicted limits, there is no practical need to make or submit a new project budget revision, and the Remaining forecast becomes outdated. There is an ability to update the Remaining forecast on-line by choosing the forecast model (here: PRJ-R) in the Project management and accounting parameters, but I do not recommend that: this online update on every project transaction seems to be buggy and brings errors and warnings across the whole system.

To create the PRJ-O and PRJ-R models, use the Project management and accounting > Setup > Forecasts > Forecast models. Choose the Budget type = Original budget or Remaining budget, respectively, otherwise you won’t be able to pick the models in the Project budget form.

However, if you do, so, the model becomes automatically Stopped. This will prevent it from being mis-used in the revenue recognition later. You should use the trick of exporting and updating them in Excel: Overwrite a read-only configuration in D365FO.

To display the shadow forecasts once the budget [revision] is approved in the workflow, remove the default filter Budget type = None in the forecast form(s):

The Cost price = Total budget = Original budget + Approved revisions. The shadow forecast models only work properly with amounts but not quantities: the quantity in the forecast is always 1. One can enter a budget line, and click Details then break the hours into multiple lines for different resources, but they are rolled up and applied as 1 hour for the total Budget line amount. There are some bugs with regards to hour indirect costs too.

Revenue recognition (estimation)

To use the ‘shadow forecasts’ in the Project Estimation, configure an appropriate Project management and accounting > Setup > Estimates > Cost template and Cost lines:

The Cost to complete method must be Total forecast – actual, because we will be hijacking the “Original forecast” PRJ-O. Tick Percentage of completion where appropriate. In the construction industry the Item line may be de-selected. As the great Rav Lal once explained to me, “Having all the materials delivered to the construction site doesn’t mean the house has been erected”.

If you don’t want your users asking for the right forecast model every time, pre-populate the revenue recognition periods (Project management and accounting > Setup > Timesheets > Timesheet period types, button Periods) with the Model = PRJ-O.

The proper estimation mode will be then From cost template – From cost template – PRJ-O, unless for the last estimation at the end of the project where Cost to complete method = Set cost to complete to zero.

Data migration

The only area where the project budget falls short is the data migration. As of today, there are no entities for it. For the total project contract, you can use my voodoo practice Electronic reporting for data migration
For the forecasts, there are 3 entities:

  • Project hour forecasts
  • Project expense forecasts
  • Project item forecasts

If the number of projects is low, import the 3 parts of the forecast for each project, scroll the projects one by one, click Project budget→ Import from Forecast → OK → Workflow → Submit. An experienced PC gamer with nimble fingers or a trained Tinder millennial may process up to 200 project budgets per hour. A larger volume may require a X++ job.