Skip to main content

Notifications

Announcements

No record found.

Dynamics 365 Community / Blogs / Learn with Subs / Your one stop solution for ...

Your one stop solution for Default dimension creatiion through X++

Subhad365 Profile Picture Subhad365 7 User Group Leader
 

Ever come across a situation, where you needed to update the default financial dimensions, runtime? Suppose you have sales lines, and they dimensions like [Costcenter-purpose-department-Employee]. There is a requirement, that asks you to update: depending on certain situations, the dimension code on 'Purpose' needs to be changed. This sorta requirement is quite common, and I bet you have confronted several situations like this, before.
Even a few versions of D365FO this, was damn easy. There was this function, which can let you change twitch between any Fin. dimension values:
 ledgerDimensionDefaultFacade::serviceReplaceAttributeValue(SalesLine.DefaultDimension, InventTable.DefaultDimension, DimensionAttribute::findByName("ProdLine").RecId);
But this method has changed and it looks like the following:
dimension = LedgerDimensionDefaultFacade::serviceReplaceAttributeValue(
                        _defaultDimension,
                        _dimensionDefaultMap.DefaultDimension,
                        dimensionAttributeRecId);
Whereby you have to generate a new dimension and then assign it to the original dimension.
Oh Rama....there could be sheer chances of again the structure of this method getting changed and we have to refurbish our code again.
Here is a pretty easy-to-use code, that can help you anytime to replace any dimension value with a new one -- regardless of whatever changes happen to any of these underlying Dimension classes.
private DimensionDefault updateDefaultDimension(SalesLine   _salesLine, InventLocationId  _newLocationVal)
    {
        DimensionDefault        fromDimensionDefault = _salesLine.DefaultDimension, 
                                toDimensionDefault;
        DefaultDimensionView    defaultDimensionView;
        Map                     mapDimvalues = new Map(Types::String, Types::String);
        Counter                 repeats = 1;
        
        while select DisplayValue, Name from defaultDimensionView
            order by RecId asc
                where defaultDimensionView.DefaultDimension == fromDimensionDefault
        {
            if (repeats == 1)
            {
                mapDimvalues.insert(defaultDimensionView.Name, _newLocationVal);
            }
            else
            {
                mapDimvalues.insert(defaultDimensionView.Name, defaultDimensionView.DisplayValue);
            }   
            repeats ++;
        }


        DimensionAttributeValueSetStorage   valueSetStorage = new DimensionAttributeValueSetStorage();        
        MapEnumerator                       dimValEnum = mapDimvalues.getEnumerator();
       
        DimensionAttributeValue             dimensionAttributeValue;


        while (dimValEnum.moveNext())
        {
            DimensionAttribute  dimensionAttribute = dimensionAttribute::findByName(dimValEnum.currentKey());
            if (dimensionAttribute.RecId == 0)
            {
                continue;
            }
            DimensionValue dimValue = dimValEnum.currentValue();
            if (dimValue != "")
            {
                dimensionAttributeValue = dimensionAttributeValue::findByDimensionAttributeAndValue(dimensionAttribute, dimValue, false, true);
                valueSetStorage.addItem(dimensionAttributeValue);
            }
        }
    
        toDimensionDefault = valueSetStorage.save();


        return toDimensionDefault;
    }
Here, as per our business need, I needed to change the first dimension (i.e.: cost-center from CostCenter-Purpose-Department-Employee-Region combination) with a new code. That's what the 'if (repeats == 1)' check is doing. You can change it to any value you want, depending on the which dimension value you want to update (3rd/4th/5th and so on). The all-time favorite 'DefaultDimensionView ' holds all the records of any given dimension combination (field DefaultDimension) which we can loop through as per our requirement. 


Creating LedgerDimension from DefaultDimension in D365F&O

Its a very common requirement to create ledgerDimension, based on DefaultDimension. Generally, we have a habit of hardcoding the dimension names, to get the resulting ledgerDimension value: ex --

 dimensionMap.insert('CostCenter', '0033501_036');
        
 dimensionMap.insert('Location', '2023GBG');
Where CostCenter, Location, Purpose, etc. are various financial dimensions. 
But wouldn't it have been great, if we could generalize the code, so as to eliminate dimension names hardcodes, so that if tomorrow the client decides to include more dimensions, edit or remove some of them, we don't have to change our code?
This article can give you a very handy code to do the same. All you need to do is to call it correctly 😊

I have created a class separately: DimDimensionHelper.
The following method has been added:

public static Map getDimensionWiseDimensionValues_DefaultDimension(DimensionDefault _dimensionDefault)
    {
        Map dimMap = new Map(Types::String, Types::String);
        
        DEFAULTDIMENSIONVIEW defaultDimensionView;

        while select Name, DisplayValue from defaultDimensionView
            where defaultDimensionView.DefaultDimension == _dimensionDefault
        {
            dimMap.insert(defaultDimensionView.Name, defaultDimensionView.DisplayValue);
        }

        return dimMap;
    }

Here you are  passing the DefaultDimension as a parameter, and then looping through the DefaultDimensionView view, and loading up the dimension name and displayValue as key-value pairs in a map.

Next we will pass on this Map along with the main account to create the segmented entry structure: 


public static DimensionDynamicAccount getLedgerDimension(MainAccount    _mainAccount, Map _conData)
    {
        
        DimensionDynamicAccount ledgerRecId;
        DimensionAttribute      dimensionAttribute;
        DimensionAttributeValue dimensionAttributeValue;
        DimensionSetSegmentName DimensionSet;
        DimensionStorage        dimStorage;
        LedgerAccountContract   ledgerAccountContract = new LedgerAccountContract();
        DimensionAttributeValueContract ValueContract;
        List valueContracts = new List(Types::Class);
        dimensionAttributeValueCombination dimensionAttributeValueCombination;
        MapEnumerator           mapEnum = _conData.getEnumerator();
 
        
        while (mapEnum.moveNext())
        {
            
            dimensionAttribute = DimensionAttribute::findByLocalizedName(mapEnum.currentKey(),false, CompanyInfo::find().LanguageId);
 
            if(dimensionAttribute)
            {
                dimensionAttributeValue = DimensionAttributeValue::findByDimensionAttributeAndValue(dimensionAttribute, mapEnum.currentValue());
                if(dimensionAttributeValue)
                {
                    ValueContract = new DimensionAttributeValueContract();
                    ValueContract.parmName(dimensionAttribute.Name) ;
                    ValueContract.parmValue(dimensionAttributeValue.CachedDisplayValue);
                    valueContracts.addEnd(ValueContract);
                }
            }
        }
        ledgerAccountContract.parmMainAccount(_mainAccount.MainAccountId);
        ledgerAccountContract.parmValues(valueContracts);
        dimStorage = DimensionServiceProvider::buildDimensionStorageForLedgerAccount(LedgerAccountContract);
        dimensionAttributeValueCombination = DimensionAttributeValueCombination::find(dimStorage.save());
        ledgerRecId = dimensionAttributeValueCombination.RecId;
        return ledgerRecId;
    }

Here its essentially looping through the map's key-value pair, and getting each and every dimension name, searching in the hierarchy. If a match is found, its trying to search the associated value pair and then loading in a list called 'ValueContracts'. Rest is very simple -- as we all know -- using DimensionStorageProvider to create a the dimensionValueCombination recId, and then sending it back to calling code.
Summing it up, here is how, you can call the above methods:

Map dimension = DimDimensionHelper::getDimensionWiseDimensionValues_DefaultDimension(_dimensionDefault);
ledgerDimaccount = DimDimensionHelper::getLedgerDimension(mainAccount, dimension);
transJournal.LedgerDimension          = ledgerDimaccount;


Hope it helps 😊 

Comments

*This post is locked for comments