Friday 15 September 2023

Create financial dimension with costcenter(Department, unit…etc):

 Create financial dimension with costcenter(Department, unit…etc): 

Ex: Costcenter financial dimension creation 

public static DimensionDefault createDerivedDimension(String20 _costCenter)

    {

        DimensionAttributeValue dimAttrValue;

        DimensionAttribute      dimAttr;

        str                     dimAttrCCValue;

        DimensionDefault        result;

        boolean                 isDerivedDimension;

                            

        dimAttrCCValue          = _costCenter;

        

        dimAttr                 = DimensionAttribute::findByName("@CCB:Costcenter");

        dimAttrValue            = DimensionAttributeValue::findByDimensionAttributeAndValue(dimAttr, dimAttrCCValue, false, true);

                        

        DimensionAttributeValueDerivedDimensions    derivedDim = DimensionAttributeValueDerivedDimensions::findByDimensionAttributeValue(dimAttrValue.DimensionAttribute, dimAttrValue.RecId);

        DimensionAttributeValueSetStorage           defaultDimStorage = new DimensionAttributeValueSetStorage();

        DimensionHierarchy                          dimHierarchy = DimensionAttributeDerivedDimensions::findDimensionHierarchyForDrivingDimension(DimensionAttribute::find(derivedDim.DimensionAttribute));

        DimensionHierarchyLevel                     dimHierarchyLevel;

        DimensionAttributeDerivedDimensions         derivedDimensions;


        while select RecId, DimensionAttribute from dimHierarchyLevel

                                where dimHierarchyLevel.DimensionHierarchy == dimHierarchy.RecId

                                    join DerivedDimensionFieldNum from derivedDimensions

                                        where derivedDimensions.DimensionHierarchyLevel == dimHierarchyLevel.RecId

        {

            DimensionAttributeValueRecId    davRecId = derivedDim.(derivedDimensions.DerivedDimensionFieldNum);

            DimensionAttributeValue         dav = DimensionAttributeValue::find(davRecId);


            defaultDimStorage.addItemValues(dimHierarchyLevel.DimensionAttribute, dav.RecId, dav.HashKey);

            isDerivedDimension = true;

        }


        // If there is no derived dimension available then at-least create the default dimension with Cost Centre Value only.

        if (!isDerivedDimension)

        {

            defaultDimStorage.addItem(dimAttrValue);

        }


        result = defaultDimStorage.save();

        return result;

    }


Get default ledger dimension from posting profile:

Get default ledger dimension from posting profile: 
 


public static LedgerDimensionAccount getDefaultLedgerDimensionFromPostingProfile(PurchLine _purchLine)

    {

        InventPostingAccountItemLedgerDimensionParameters   inventPostingAccountItemLedgerDimensionParameters;


        inventPostingAccountItemLedgerDimensionParameters =

            InventPostingAccountItemLedgerDimensionParameters::newFromParameters(

                InventAccountType::PurchConsump,

                _purchLine.ItemId,

                _purchLine.inventTable().itemGroupId(),

                _purchLine.ProcurementCategory,

                _purchLine.VendAccount,

                VendTable::find(_purchLine.VendAccount).VendGroup,

                _purchLine.TaxGroup);


        return InventPosting::accountItemLedgerDimensionFromParameters(inventPostingAccountItemLedgerDimensionParameters);

    }


}

Merging ledger dimension and financial dimension with main account id in x++:

 

Merging ledger dimension and financial dimension with main account id  in x++: 

     /// <summary>

    /// To update mainAccounting financial and accounting distribution dimension

    /// </summary>

    public void updateMainAccountinFinancialandAccountingDistributionDimension()

    {

        PurchReqBusinessJustificationCodes reasonCodes;

        LedgerDimensionDefaultAccount      newLedgerDimension;

        LedgerDimensionBase                mergedLedgerDimension;

        DimensionAttributeValueSetStorage  dimStorage;

        DimensionAttribute                 dimAttribute;

        DimensionAttributeValue            dimAttributeValue;

        AccountingDistribution    accDistribution;

        RefRecId                           mainAccountRecId;


        ttsbegin;

        select firstonly reasonCodes

            where reasonCodes.RecId == this.BusinessJustification;


        select firstonly forupdate accDistribution

            where accDistribution.SourceDocumentLine == this.SourceDocumentLine;

        //Get the main accoubt recid

        mainAccountRecId                = MainAccount::findByMainAccountId(reasonCodes.CCBMainAccountId).RecId;

        // Create new ledger dimension with only main account id and other dimensions are empty

        newLedgerDimension              = LedgerDefaultAccountHelper::getDefaultAccountFromMainAccountRecId(mainAccountRecId);

        // Merging new ledger dimension with old ledger dimension 

        mergedLedgerDimension           = LedgerDimensionFacade::serviceMergeLedgerDimensions(newLedgerDimension,accDistribution.LedgerDimension);

        // Update the mrgde ledger dimnesion to table

        accDistribution.LedgerDimension = mergedLedgerDimension;

        accDistribution.update();


        // Merge main account in financial dimension  

        dimStorage                      = DimensionAttributeValueSetStorage::find(this.DefaultDimension);

        dimAttribute                    = DimensionAttribute::findByName('MainAccount');

        dimAttributeValue               = DimensionAttributeValue::findByDimensionAttributeAndValue(dimAttribute, reasonCodes.CCBMainAccountId, true, true);


        dimStorage.addItem(dimAttributeValue);

        if(dimStorage != null)

        {

            this.selectForUpdate(true);

            this.CCBLineToBeExpense = NoYes::Yes;

            this.DefaultDimension   = dimStorage.save();

            this.doUpdate();

        }

        ttscommit;

    }



// Get the main account from ledger dimension:

MainAccount                     mainAccount = LedgerDimensionFacade::getMainAccountFromLedgerDimension(projPosting.LedgerDimension);


Get the default dimension(financial dimension) from ledger dimension: 

DimensionDefault defaultDimension = DimensionAttributeValueSetStorage::getDefaultDimensionFromDimensionCombination(_accDistribution.LedgerDimension);


Get the dimension value from default dimension recid :

(Ex: get Cost center value from default dimension)

public static str getEquipmentCostCenter(DimensionDefault _defaultDimension)

    {

        DimensionAttributeValueSetStorage   dimensionAttributeValueSetStorage ;

        DimensionAttribute                  dimensionAttribute;

        DimensionValue                      dimensionValue;

        #define.DimensionName("@CCB:Costcenter")


        dimensionAttributeValueSetStorage   = dimensionAttributeValueSetStorage::find(_defaultDimension);

        dimensionAttribute                  = dimensionAttribute::findbyname(#DimensionName);

        dimensionValue                      = dimensionAttributeValueSetStorage.getDisplayValueByDimensionAttribute(dimensionAttribute.recId);


        return dimensionValue;

    }



Thursday 13 July 2023

Get total count of records from static query in x++

 Get total count of records from static query in x++:


Query        query          = new Query();

//query = contract.parmOurCustomQuery();

QueryRun  queryRun   = new QueryRun(q);

int               totals          = 0;

/// get total record count with single datasource

totals           = SysQuery::countTotal(queryRun);

/// get total records count with multiple datasources

totals           = SysQuery::countLoops();

// get total records count with multiple datasources and adding group by or order by in query with multiple data sources

totals           = QueryRun::getQueryRowCount(query,maxInt());

Wednesday 14 June 2023

In multi threading batch last task is dependent on all the tasks execution . Need to create dependency for execute last task on other tasks statuses in x++ D365 (F&O)

 1. In multi threading batch last task is dependent on all the tasks execution . Need to create dependency for execute last task on other tasks statuses in x++ D365 (F&O)

2. Dependency task is created in createCleanupTask method.

Contract class:

/// <summary>

/// A data contract class for CE sync batch <c>CCBOrderSyncCEService</c>.

/// </summary>

/// <remarks>

/// Developed to sync F&O orders with CE, dated 09 Dec, 22 by Himanshu.

/// </remarks>

[DataContractAttribute]

class CCBOrderSyncCEContract

{

    int     batchThread;

    str     soList;


    /// <summary>

    /// Parameter method for <c>ThreadCount</c>.

    /// </summary>

    /// <param name = "_threadCount">Thread count for batch</param>

    /// <returns>NoYesId</returns>

    [DataMemberAttribute('Thread count'), SysOperationLabelAttribute("Thread count"), SysOperationHelpText('Max thread count in the current batch.')]

    public int parmBatchThread(int _threadCount = batchThread)

    {

        batchThread = _threadCount;

        return batchThread;

    }


    /// <summary>

    /// Parameter method for <c>SOList</c>.

    /// </summary>

    /// <param name = "_solist">List of SO to be processed</param>

    /// <returns>SO list</returns>

    [DataMemberAttribute, SysOperationControlVisibilityAttribute(false)]

    public str parmSOList(str _solist = soList)

    {

        soList = _solist;

        return soList;

    }


    /// <summary>

    /// This method sets the sales order in a list.

    /// </summary>

    /// <param name = "_solist">List of SO to be processed</param>

    public void setSOList(List _solist)

    {

        soList = SysOperationHelper::base64Encode(_solist.pack());

    }


    /// <summary>

    /// This method gets the list of SO to be processed.

    /// </summary>

    public List getSOList()

    {

        return List::create(SysOperationHelper::base64Decode(soList));

    }


}

Controller class:

/// <summary>

/// Controller class for F&O orders to sync with CE.

/// </summary>

/// <remarks>

/// N Developed to sync F&O orders with CE, dated 04 Oct, 22 by Himanshu.

/// </remarks>

class CCBOrderSyncCEController extends SysOperationServiceController

{

    /// <summary>

    /// Method for batch job description.

    /// </summary>

    /// <returns>Caption</returns>

    public ClassDescription caption()

    {

        ClassDescription    ret;


        ret = "@CCB:CCBSyncOrderWithCE";


        return ret;

    }


    /// <summary>

    /// Method for dialog caption.

    /// N Developed to sync F&O orders with CE, dated 04 Oct, 22 by Himanshu.

    /// </summary>

    /// <param name = "_dialogCaption">_dialogCaption</param>

    /// <returns>returns caption for dialog</returns>

    public LabelType parmDialogCaption(LabelType _dialogCaption = '')

    {

        LabelType   caption;


        caption = "@CCB:CCBSyncOrderWithCE";


        return caption;

    }


    /// <summary>

    /// Construct Method for controller class.

    /// N Developed to sync F&O orders with CE, dated 04 Oct, 22 by Himanshu.

    /// </summary>

    /// <returns>returns CCBOrderSyncCEController object</returns>

    public static CCBOrderSyncCEController construct()

    {

        return new CCBOrderSyncCEController();

    }


    /// <summary>

    /// Main Method for controller class.

    /// N Developed to sync F&O orders with CE, dated 04 Oct, 22 by Himanshu.

    /// </summary>

    /// <param name = "args">args</param>

    public static void main(Args args)

    {

        CCBOrderSyncCEController        controller;

        IdentifierName                  className;

        IdentifierName                  methodName;

        SysOperationExecutionMode       executionMode;


        [className, methodName, executionMode] = SysOperationServiceController::parseServiceInfo(args);


        controller = new CCBOrderSyncCEController(className, methodName, executionMode);

        

        if (controller.prompt())

        {

            controller.run();

        }

    }


}

Service class:

/// <summary>

/// Class to sync F&O orders with CE.

/// </summary>

/// <remarks>

/// N Developed to sync F&O orders with CE, dated 04 Oct, 22 by Himanshu.

/// </remarks>

class CCBOrderSyncCEService extends SysOperationServiceBase

{

    Set     setClass = new Set(Types::Class);


    /// <summary>

    /// This method gets the class name to attach a method for multi thread.

    /// </summary>

    /// <returns>Returns <c>CCBOrderSyncCEService</c> class object.</returns>

    ClassName getClassName()

    {

        return classStr(CCBOrderSyncCEService);

    }


    /// <summary>

    /// This method performs CE sync of pending orders from F&O.

    /// </summary>

    /// <param name = "_contract"> Contract for CE sync batch job.</param>

    public void startProcess(CCBOrderSyncCEContract _contract)

    {

        #OCCRetryCount

        SysOperationServiceController   controller;

        CCBOrderSyncCEContract          dataContract;

        SalesTable                      salesTable, salesTableExists;

        CCBOrderSyncCEErrorLogTable     syncErrorLog;

        BatchHeader                     batchHeader;

        List                            soList;

        int                             batchThread, soCount, soErrRectCnt, totRecord, recordCount, recordPerThread;


        //Method to create separate threads for the available orders to sync with CE

        void createTask()

        {

            try

            {

                controller = new SysOperationServiceController(this.getClassName(), methodStr(CCBOrderSyncCEService,syncOrdersWithCE), SysOperationExecutionMode::Asynchronous);

                dataContract = controller.getDataContractObject('_contract');

                dataContract.setSOList(soList);

                setClass.add(controller);

                batchHeader.addRuntimeTask(controller, batchHeader.parmBatchHeaderId());

                //batchHeader.save();

            }

            catch(Exception::UpdateConflict)

            {

                if (appl.ttsLevel() == 0)

                {

                    if (xSession::currentRetryCount() >= #RetryNum)

                    {

                        throw Exception::UpdateConflictNotRecovered;

                    }

                    else

                    {

                        retry;

                    }

                }

            }

            catch

            {

                info(strFmt(this.getErrorStr()));

            }

        }

        //Check if dual wite map for sales table/sales line is running or not

        if (!this.checkDualWriteMap())

        {

            throw error("@CCB:CCBCESyncDWMapStatus");

            return;

        }


        //Get batch thread count from batch parameter

        batchThread = _contract.parmBatchThread();


        //Get total record count to sync with CE

        select count(RecId) from salesTable

            where salesTable.CCBToSync       == NoYes::Yes

               && (salesTable.SalesStatus    == SalesStatus::Invoiced || salesTable.SalesStatus == SalesStatus::Canceled)

            notexists join syncErrorLog

                where syncErrorLog.OrderNumber  == salesTable.SalesId;


        soCount = salesTable.RecId;


        //Get total record count to sync with CE from exixting error log, where error is rectified

        select count(RecId) from salesTableExists

            join syncErrorLog

                where syncErrorLog.OrderNumber          == salesTableExists.SalesId

                   && syncErrorLog.ErrorRectified       == NoYes::Yes

                   && salesTableExists.CCBToSync        == NoYes::Yes

                   && (salesTableExists.SalesStatus     == SalesStatus::Invoiced || salesTableExists.SalesStatus == SalesStatus::Canceled);


        soErrRectCnt = salesTableExists.RecId;


        //Get total records to be processed

        totRecord = soCount + soErrRectCnt;


        if (totRecord)

        {

            batchHeader = BatchHeader::getCurrentBatchHeader();


            //Calculate no of records that needs to be bundled in a single thread.

            recordPerThread = totRecord != 0 ? real2int(roundUp((totRecord / batchThread), 1)) : 0;


            //Fetch all the orders which do not exists in the CE sync error log and sync is pending.

            soList = new List(Types::String);


            while select SalesId from salesTable

                where salesTable.CCBToSync       == NoYes::Yes

                   && (salesTable.SalesStatus    == SalesStatus::Invoiced || salesTable.SalesStatus == SalesStatus::Canceled)

                notexists join syncErrorLog

                    where syncErrorLog.OrderNumber  == salesTable.SalesId

            {

                recordCount++ ;

                soList.addEnd(salesTable.SalesId);


                if (recordCount == recordPerThread)

                {

                    createTask();

                    recordCount = 0;

                    soList = new List(Types::String);

                }

            }


            //Fetch all the orders which exists in the CE sync error log where error is rectified.

            while select SalesId from salesTableExists

                join syncErrorLog

                    where syncErrorLog.OrderNumber          == salesTableExists.SalesId

                       && syncErrorLog.ErrorRectified       == NoYes::Yes

                       && salesTableExists.CCBToSync        == NoYes::Yes

                       && (salesTableExists.SalesStatus     == SalesStatus::Invoiced || salesTableExists.SalesStatus == SalesStatus::Canceled)

            {

                recordCount++ ;

                soList.addEnd(salesTableExists.SalesId);


                if (recordCount == recordPerThread)

                {

                    createTask();

                    recordCount = 0;

                    soList = new List(Types::String);

                }

            }


            //Create last thread with all remaining orders

            if (recordCount > 0)

            {

                createTask();

                recordCount = 0;

            }


            // for clean up data we added new task

            this.createCleanupTask(setClass,batchHeader);

        }

        else

        {

            Info("@CCB:CCBCESyncOrderStatus");

        }

    }


    /// <summary>

    /// Reset dual write check for all orders in F&O to let them sync with CE.

    /// </summary>

    /// <param name = "_contract">Contract for CE sync batch job.</param>

    public void syncOrdersWithCE(CCBOrderSyncCEContract _contract)

    {

        #OCCRetryCount


        SalesTable                      salesTableUpd;

        SalesLine                       salesLineUpd;

        SalesId                         salesId;

        List                            salesOrderList;

        ListEnumerator                  soEnumList;

        //int                             cnt;

        

        salesOrderList  = new List(Types::String);

        salesOrderList  = _contract.getSOList();

        soEnumList      = salesOrderList.getEnumerator();

        

        while (soEnumList.moveNext())

        {

            salesId = soEnumList.current();


            if (salesId)

            {

                //cnt++;

                //Fetch sales table to update CE sync flag

                select firstonly forupdate salesTableUpd

                    where salesTableUpd.SalesId == salesId;


                try

                {

                    while select forupdate salesLineUpd

                        where salesLineUpd.SalesId      == salesTableUpd.SalesId

                           && salesLineUpd.CCBToSync    == NoYes::Yes

                    {

                        salesLineUpd.CCBToSync  = NoYes::No;

                        ttsbegin;

                        salesLineUpd.update();

                        ttscommit;

                    }


                    ttsbegin;

                    salesTableUpd.CCBToSync = NoYes::No;

                    salesTableUpd.update();

                    ttscommit;

                }

                catch(Exception::Deadlock)

                {

                    if (xSession::currentRetryCount() >= #RetryNum)

                    {

                        this.addErrorLog(salesTableUpd.SalesId, salesTableUpd.InventLocationId);

                    }

                    else

                    {

                        salesTableUpd.reread();

                        retry;

                    }

                }

                catch(Exception::UpdateConflict)

                {

                    if (xSession::currentRetryCount() >= #RetryNum)

                    {

                        salesTableUpd.reread();

                        this.addErrorLog(salesTableUpd.SalesId, salesTableUpd.InventLocationId);

                    }

                    else

                    {

                        salesTableUpd.reread();

                        retry;

                    }

                }

                catch

                {

                    this.addErrorLog(salesTableUpd.SalesId, salesTableUpd.InventLocationId);

                }

            }

        }

        //Info(strFmt("%1", cnt));

    }


    /// <summary>

    /// Add error log for CE sync.

    /// </summary>

    /// <param name = "_salesId">Sales order</param>

    /// <param name = "_warehouse">Warehouse</param>

    public void addErrorLog(SalesId _salesId, SalesInventLocationId _warehouse)

    {

        CCBOrderSyncCEErrorLogTable     ceSyncErrorLog;

        SalesLine                       salesLine;

        WHSLoadLine                     loadLine;


        //Fetch load id

        select firstonly LoadId from loadLine

            exists join salesLine

            where salesLine.InventTransId   == loadLine.InventTransId

               && salesLine.SalesId         == _salesId

               && loadLine.InventTransType  == InventTransType::Sales;


        ceSyncErrorLog.LoadId           = loadLine.LoadId;

        ceSyncErrorLog.InventLocationId = _warehouse;

        ceSyncErrorLog.OrderNumber      = _salesId;

        ceSyncErrorLog.ErrorInfo        = this.getErrorStr();

        

        ceSyncErrorLog.insert();

    }


    /// <summary>

    /// Method to capture the standard error caught during the current process.

    /// </summary>

    /// <returns>error</returns>

    public str getErrorStr()

    {

        SysInfologEnumerator        enumerator;

        SysInfologMessageStruct     msgStruct;

        Exception                   exception;

        str                         error;


        enumerator = SysInfologEnumerator::newData(infolog.cut());


        while (enumerator.moveNext())

        {

            msgStruct = new SysInfologMessageStruct(enumerator.currentMessage());

            exception = enumerator.currentException();

            error = strfmt("@SYS82282"+ ' '+"@MCR26302", error, msgStruct.message());

        }

        return error;

    }


    /// <summary>

    /// This method is used to check if Dual write map is running or not.

    /// </summary>

    /// <returns>Boolean true/false, if the process can move further.</returns>

    public boolean checkDualWriteMap()

    {

        DualWriteProjectConfiguration   dualWriteProjConfig;

        DMFEntity                       dmfEntity;

        BusinessEventsDefinition        eventDef;

        boolean                         ret = false;


        Select firstonly RecId from dualWriteProjConfig

            exists join dmfEntity

            exists join eventDef

            where eventDef.RefEntityName        == dmfEntity.TargetEntity

               && dmfEntity.EntityName          == dualWriteProjConfig.InternalEntityName

               && dualWriteProjConfig.Status    == DualWriteProjectStatus::Queueing

               && (eventDef.RefTableName == tableStr(SalesTable) || eventDef.RefTableName == tableStr(SalesLine));


        ret = (dualWriteProjConfig.RecId) ? false : true;


        return ret;

    }


    /// <summary>

    /// Create task for clean up data

    /// </summary>

    /// <param name = "_set"> set</param>

    /// <param name = "_batchHeader">BatchHeader</param>

    public void createCleanupTask(Set _set, BatchHeader _batchHeader)

    {

        #OCCRetryCount

        SysOperationServiceController   controller,batchTask;

        SetEnumerator                   se;


        try

        {

            controller = new SysOperationServiceController(this.getClassName(), methodStr(CCBOrderSyncCEService,cleanUpData), SysOperationExecutionMode::Asynchronous);

            

            _batchHeader.addRuntimeTask(controller, _batchHeader.parmBatchHeaderId());

            

            // Create dependencies

            se = _set.getEnumerator();

            while (se.moveNext())

            {

                batchTask = se.current(); // Task to make dependent on

                _batchHeader.addDependency(controller,batchTask,BatchDependencyStatus::Finished);

            }

            _batchHeader.save();

        }

        catch(Exception::UpdateConflict)

        {

            if (appl.ttsLevel() == 0)

            {

                if (xSession::currentRetryCount() >= #RetryNum)

                {

                    throw Exception::UpdateConflictNotRecovered;

                }

                else

                {

                    retry;

                }

            }

        }

        catch

        {

            info(strFmt(this.getErrorStr()));

        }

    }


    /// <summary>

    ///  Clean up error log data

    /// </summary>

    public void cleanUpData()

    {

        CCBOrderSyncCEErrorLogTable errorLog,errorLogLast,errorLogDelete;

        SalesTable                  salesTable;


        //Delete logs where order is synced.

        delete_from errorLog

            exists join salesTable

            where salesTable.SalesId == errorLog.OrderNumber

            && salesTable.CCBToSync  == NoYes::No;


        errorLog.clear();

        //delete rectified errors

        //delete_from errorLog where errorLog.ErrorRectified == NoYes::Yes;

        delete_from errorLog

            where errorLog.ErrorRectified == NoYes::Yes;

        

        errorLog.clear();

        //keep only the last error of each order:

        while select OrderNumber from errorLog

            group by OrderNumber

        {

            errorLogLast.clear();

            errorLogDelete.clear();


            select firstonly RecId from errorLogLast

                order by CreatedDateTime desc

                where errorLogLast.OrderNumber == errorLog.OrderNumber;


            ttsbegin;

            //Delete records

            delete_from errorLogDelete

                where errorLogDelete.OrderNumber == errorLog.OrderNumber

                &&    errorLogDelete.RecId != errorLogLast.RecId;

            ttscommit;

        }

        Info("@CCB:LogCleanUp");

    }


}





Multi threading batch in x++ (D365 F&O)

 1. User has option to select number of threads from contract class parameters.

2.  We have calculation to select number of records per thread

3. startProcess() method is the main method for deviding records per thread and adding tasks

4. defaultly batch processing check box enabled when we open menuitem.


Contract class:

[DataContractAttribute]

class CCBSOPostalAddressUpdateBatchContract implements SysOperationValidatable

{

    int     batchThread;

    str     soList,inventTransIdList;


    

    /// <summary>

    /// Parameter method for <c>ThreadCount</c>.

    /// </summary>

    /// <param name = "_threadCount">Thread count for batch</param>

    /// <returns>NoYesId</returns>

    [DataMemberAttribute('Thread count'), SysOperationLabelAttribute("Thread count"), SysOperationHelpText('Max thread count in the current batch.')]

    public int parmBatchThread(int _threadCount = batchThread)

    {

        batchThread = _threadCount;

        return batchThread;

    }


    /// <summary>

    /// Parameter method for <c>SOList</c>.

    /// </summary>

    /// <param name = "_solist">List of SO to be processed</param>

    /// <returns>SO list</returns>

    [DataMemberAttribute, SysOperationControlVisibilityAttribute(false)]

    public str parmSOList(str _solist = soList)

    {

        soList = _solist;

        return soList;

    }


    /// <summary>

    /// This method sets the sales order in a list.

    /// </summary>

    /// <param name = "_solist">List of SO to be processed</param>

    public void setSOList(List _solist)

    {

        soList = SysOperationHelper::base64Encode(_solist.pack());

    }


    /// <summary>

    /// This method gets the list of SO to be processed.

    /// </summary>

    /// <returns>decode so list </returns>

    public List getSOList()

    {

        return List::create(SysOperationHelper::base64Decode(soList));

    }


    /// <summary>

    /// Parameter method for <c>inventTransIdList</c>.

    /// </summary>

    /// <param name = "_inventTransIdList">List of location to be processed</param>

    /// <returns>location list</returns>

    [DataMemberAttribute, SysOperationControlVisibilityAttribute(false)]

    public str parmInventTransIdList(str _inventTransIdList = inventTransIdList)

    {

        inventTransIdList = _inventTransIdList;

        return inventTransIdList;

    }


    /// <summary>

    /// This method sets the invent transactions in a list.

    /// </summary>

    /// <param name = "_inventTransIdList">List of SO to be processed</param>

    public void setInventTransIdList(List _inventTransIdList)

    {

        inventTransIdList = SysOperationHelper::base64Encode(_inventTransIdList.pack());

    }


    /// <summary>

    /// This method gets the list of invent transactions to be processed.

    /// </summary>

    /// <returns>decode invent transaction ids</returns>

    public List getInventTransIdList()

    {

        return List::create(SysOperationHelper::base64Decode(inventTransIdList));

    }


    /// <summary>

    /// validation for parameters

    /// </summary>

    /// <returns>true/false</returns>

    public boolean validate()

    {

        boolean ret = true;


        if (batchThread < 2 )

        {

            ret = checkFailed("@CCB:SOThreadCount");

        }

        return ret;

    }


}


Controller class:

/// <summary>

/// to update SO postal addressess

/// </summary>

class CCBSOPostalAddressUpdateBatchController extends SysOperationServiceController

{


    /// <summary>

    /// Retrieves the caption of the dialog box.

    /// </summary>

    /// <returns>The caption of the dialog box.</returns>

    public ClassDescription caption()

    {

        return "@CCB:SODeliveryAddressUpdate";

    }


    /// <summary>

    /// Construct Method for controller class.

    /// N Developed to sync update SO postal addresses, dated 27 May, 23 by Irfan.

    /// </summary>

    /// <returns>returns CCBSOPostalAddressUpdateController object</returns>

    public static CCBSOPostalAddressUpdateBatchController construct()

    {

        return new CCBSOPostalAddressUpdateBatchController();

    }


    /// <summary>

    /// Initializes the update update so postal addressessr controller class instance.

    /// </summary>

    /// <param name = "_args">args</param>

    public static void main(Args _args)

    {

        CCBSOPostalAddressUpdateBatchController     controller;

        IdentifierName                              className;

        IdentifierName                              methodName;

        SysOperationExecutionMode                   executionMode;


        [className, methodName, executionMode]      = SysOperationServiceController::parseServiceInfo(_args);

        

        controller = new CCBSOPostalAddressUpdateBatchController(className, methodName, executionMode);

        controller.isInBatchFlagSet = true;   /// for default batch processing as enabled

        //controller.isInBatch();

        controller.parmInBatch(true);

        controller.batchInfo().parmBatchExecute(true);

        //controller.batchInfo().fieldBatchExecuteValue(true);

        if (controller.prompt())

        {

            controller.run();

        }

    }


}

Service Class:

/// <summary>

/// for update cost estimate process number for blank process number records in cost estimate header and lines tables

/// </summary>

class CCBSOPostalAddressUpdateBatchService extends SysOperationServiceBase

{

    #OCCRetryCount

    utcdatetime                      validDateTime       = DateTimeUtil::utcNow();

    utcdatetime                      validFrom           = DateTimeUtil::minValue();

    utcdatetime                      validTo             = DateTimeUtil::maxValue();



    /// <summary>

    /// This method gets the class name to attach a method for multi thread.

    /// </summary>

    /// <returns>Returns <c>CCBSOPostalAddressUpdateBatchService</c> class object.</returns>

    ClassName getClassName()

    {

        return classStr(CCBSOPostalAddressUpdateBatchService);

    }


    /// <summary>

    /// This method performs update SO postal addresses.

    /// </summary>

    /// <param name = "_contract"> Contract for update SO postal addresses.</param>

    public void startProcess(CCBSOPostalAddressUpdateBatchContract _contract)

    {

        

        SysOperationServiceController           controller;

        CCBSOPostalAddressUpdateBatchContract   dataContract;

        BatchHeader                             batchHeader;

        List                                    soList,inventTransIdList;

        int                                     batchThread,batchThreadHeader,batchThreadLines;

        int                                     totRecord,headerCount,LinesCount,recordCount, recordPerThread;

        SalesTable                              salesTable;

        SalesLine                               salesLine;

        LogisticsPostalAddress                  logisticsPostalAddress,logisticsPostalAddressNewAddress;

        CCBDeliveryHeaderImport                 deliveryHeaderImport;

        boolean                                 btachProcessFlag = false;

        

        //Method to create separate threads for the available orders to sync with CE

        void createTask(boolean lastTask = false)

        {

            try

            {

                if (lastTask)

                {

                    controller = new SysOperationServiceController(this.getClassName(), methodStr(CCBSOPostalAddressUpdateBatchService,processOrderLinesUpdate), SysOperationExecutionMode::Asynchronous);

                }

                else

                {

                    controller = new SysOperationServiceController(this.getClassName(), methodStr(CCBSOPostalAddressUpdateBatchService,processOrderHeaderUpdate), SysOperationExecutionMode::Asynchronous);

                }

                dataContract = controller.getDataContractObject('_contract');


                if (lastTask)

                {

                    dataContract.setInventTransIdList(inventTransIdList);

                }

                else

                {

                    dataContract.setSOList(soList);

                }

                batchHeader.addRuntimeTask(controller, batchHeader.parmBatchHeaderId());

                batchHeader.save();

            }

            catch(Exception::UpdateConflict)

            {

                if (appl.ttsLevel() == 0)

                {

                    if (xSession::currentRetryCount() >= #RetryNum)

                    {

                        throw Exception::UpdateConflictNotRecovered;

                    }

                    else

                    {

                        retry;

                    }

                }

            }

        }

        try

        {

            if (!this.isExecutingInBatch())

            {

                btachProcessFlag = true;

                throw error("@CCB:SOMultiThreadBatchError");

            }


            //Get batch thread count from batch parameter

            batchThread     = _contract.parmBatchThread(); 

            if (batchThread < 2)

            {

                batchThread = 2;

            }

        

            batchThreadHeader = roundDown(batchThread/2,1);

            batchThreadLines  = roundUp(batchThread/2,1);


            select forupdate validtimestate(validFrom, validTo) count(RecId) from salesTable

                where salesTable.SalesStatus          != SalesStatus::Delivered

                    && salesTable.SalesStatus         != SalesStatus::Invoiced

                    && salesTable.SalesStatus         != SalesStatus::Canceled

            join  logisticsPostalAddress

                where logisticsPostalAddress.RecId    == salesTable.DeliveryPostalAddress

                    && logisticsPostalAddress.ValidTo  < validDateTime

            exists join logisticsPostalAddressNewAddress

                where logisticsPostalAddressNewAddress.Location == logisticsPostalAddress.Location

                && logisticsPostalAddressNewAddress.ValidTo     >= validDateTime

            notexists join deliveryHeaderImport

                where deliveryHeaderImport.OrderNum   == salesTable.SalesId;


            headerCount = salesTable.RecId;


            logisticsPostalAddress.clear();

            deliveryHeaderImport.clear();

            logisticsPostalAddressNewAddress.clear();

            select validtimestate(validFrom, validTo) count(RecId) from salesLine

                where salesLine.SalesStatus         != SalesStatus::Delivered

                && salesLine.SalesStatus            != SalesStatus::Invoiced

                && salesLine.SalesStatus            != SalesStatus::Canceled

            join  logisticsPostalAddress

                where logisticsPostalAddress.RecId  == salesLine.DeliveryPostalAddress

                && logisticsPostalAddress.ValidTo    < validDateTime

            exists join logisticsPostalAddressNewAddress

                where logisticsPostalAddressNewAddress.Location == logisticsPostalAddress.Location

                && logisticsPostalAddressNewAddress.ValidTo     >= validDateTime

            notexists join deliveryHeaderImport

                where deliveryHeaderImport.OrderNum == salesLine.SalesId;


            LinesCount = salesLine.RecId;


            totRecord  = headerCount+LinesCount;


            if (totRecord)

            {

                batchHeader = BatchHeader::getCurrentBatchHeader();


                // for header orders loop

                if (headerCount)

                {

                    recordPerThread   = headerCount != 0 ? real2int(roundUp((headerCount / batchThreadHeader), 1)) : 0;

                    soList            = new List(Types::String);


                    salesTable.clear();

                    logisticsPostalAddress.clear();

                    deliveryHeaderImport.clear();

                    logisticsPostalAddressNewAddress.clear();


                    while select validtimestate(validFrom, validTo) DeliveryPostalAddress,SalesId,SalesStatus from  salesTable

                        order by salesTable.DeliveryPostalAddress asc

                        where salesTable.SalesStatus          != SalesStatus::Delivered

                        && salesTable.SalesStatus             != SalesStatus::Invoiced

                        && salesTable.SalesStatus             != SalesStatus::Canceled

                    join  RecId,ValidFrom,ValidTo,Location from logisticsPostalAddress

                        where logisticsPostalAddress.RecId    == salesTable.DeliveryPostalAddress

                        && logisticsPostalAddress.ValidTo      < validDateTime

                    exists join logisticsPostalAddressNewAddress

                        where logisticsPostalAddressNewAddress.Location == logisticsPostalAddress.Location

                        && logisticsPostalAddressNewAddress.ValidTo     >= validDateTime

                    notexists join deliveryHeaderImport

                        where deliveryHeaderImport.OrderNum   == salesTable.SalesId

                    {

                        recordCount++ ;

                        soList.addEnd(salesTable.SalesId);


                        if (recordCount == recordPerThread)

                        {

                            createTask();

                            recordCount         = 0;

                            soList              = new List(Types::String);

                        }

                    }


                    //Create last thread with all remaining orders

                    if (recordCount > 0)

                    {

                        createTask();

                        recordCount         = 0;

                    }

            

                }


                // for order lines loop

                recordPerThread = 0;

                recordCount     = 0;

                if (LinesCount)

                {

                    //Calculate no of records that needs to be bundled in a single thread.

                    recordPerThread   = LinesCount != 0 ? real2int(roundUp((LinesCount / batchThreadLines), 1)) : 0;

                    inventTransIdList = new List(Types::String);


                    salesTable.clear();

                    salesLine.clear();

                    logisticsPostalAddress.clear();

                    deliveryHeaderImport.clear();

                    logisticsPostalAddressNewAddress.clear();


                    // fetch line loop records

                    while select validtimestate(validFrom, validTo) InventTransId,DeliveryPostalAddress,SalesStatus,SalesId,LineNum from salesLine

                        order by salesLine.DeliveryPostalAddress asc

                        where salesLine.SalesStatus           != SalesStatus::Delivered

                        && salesLine.SalesStatus              != SalesStatus::Invoiced

                        && salesLine.SalesStatus              != SalesStatus::Canceled

                    join RecId,ValidFrom,ValidTo,Location from logisticsPostalAddress

                        where logisticsPostalAddress.RecId    == salesLine.DeliveryPostalAddress

                        && logisticsPostalAddress.ValidTo      < validDateTime

                    exists join logisticsPostalAddressNewAddress

                        where logisticsPostalAddressNewAddress.Location == logisticsPostalAddress.Location

                        && logisticsPostalAddressNewAddress.ValidTo     >= validDateTime

                    notexists join deliveryHeaderImport

                        where deliveryHeaderImport.OrderNum   == salesLine.SalesId

                    {

                        recordCount++ ;

                        inventTransIdList.addEnd(salesLine.InventTransId);


                        if (recordCount == recordPerThread)

                        {

                            createTask(true);

                            recordCount         = 0;

                            inventTransIdList   = new List(Types::String);

                        }

                    }


                    //Create last thread with all remaining orders

                    if (recordCount > 0 )

                    {

                        createTask(true);

                        recordCount         = 0;

                    }

                }

            

                Info("@CCB:SODeliveryAddressUpdateCompleted");

            }

            else

            {

                Info("@CCB:SOExpiredAddress");

            }

        }

        catch(Exception::Error)

        {

            if (btachProcessFlag)

            {

                Info("@CCB:SOMultiThreadBatchError");

            }

        }

    }


    /// <summary>

    /// update all sales order headers

    /// </summary>

    /// <param name = "_contract">Contract for CE sync batch job.</param>

    public void processOrderHeaderUpdate(CCBSOPostalAddressUpdateBatchContract _contract)

    {

        SalesTable                      salesTable;

        WHSLoadLine                     whsLoadLine;

        SalesId                         salesId;

        LogisticsPostalAddress          logisticsPostalAddress,logisticsPostalAddressNew;

        List                            salesOrderList = new List(Types::String);

        ListEnumerator                  soEnumList;

        LogisticsPostalAddressRecId     oldDeliveryAddress;

        

        salesOrderList                  = _contract.getSOList();

        soEnumList                      = salesOrderList.getEnumerator();

        

        while (soEnumList.moveNext())

        {

            salesId = soEnumList.current();


            if (salesId)

            {

                try

                {

                    select firstonly forupdate validtimestate(validFrom, validTo)  salesTable

                        join  logisticsPostalAddress

                        where logisticsPostalAddress.RecId    == salesTable.DeliveryPostalAddress

                            && logisticsPostalAddress.ValidTo < validDateTime

                            && salesTable.SalesId             == salesId;

                    if (salesTable.RecId)

                    {

                        if (oldDeliveryAddress != salesTable.DeliveryPostalAddress)

                        {

                            logisticsPostalAddressNew             = LogisticsPostalAddress::findByLocation(logisticsPostalAddress.Location);

                        }

                        oldDeliveryAddress      = salesTable.DeliveryPostalAddress;


                        if ( logisticsPostalAddressNew.RecId != logisticsPostalAddress.RecId)

                        {

                            select firstonly whsLoadLine

                                    where whsLoadLine.OrderNum == salesTable.SalesId;


                            if ( !whsLoadLine

                                || (whsLoadLine && whsLoadTable::find(whsLoadLine.LoadId).LoadStatus != WHSLoadStatus::Shipped)

                               )

                            {

                                ttsbegin;

                                salesTable.DeliveryPostalAddress = logisticsPostalAddressNew.RecId;

                                salesTable.doUpdate();

                                ttscommit;

                            }

                        }

                    }

                }

                catch(Exception::UpdateConflict)

                {

                    if (appl.ttsLevel() == 0)

                    {

                        if (xSession::currentRetryCount() >= #RetryNum)

                        {

                            salesTable.reread();

                            throw Exception::UpdateConflictNotRecovered;

                        }

                        else

                        {

                            salesTable.reread();

                            retry;

                        }

                    }

                }

                catch

                {

                    info(strFmt("@CCB:SOHeaderAddressIssue", salesTable.SalesId));

                }

            }

        }

    }


    /// <summary>

    /// Update sales order lines address

    /// </summary>

    /// <param name = "_contract">CCBSOPostalAddressUpdateBatchContract</param>

    public void processOrderLinesUpdate(CCBSOPostalAddressUpdateBatchContract _contract)

    {

        SalesLine                       salesLine;

        WHSLoadLine                     whsLoadLine;

        TradeInventTransId              inventTransId;

        LogisticsPostalAddress          logisticsPostalAddress,logisticsPostalAddressNew;

        List                            inventTransIdList = new List(Types::String);

        ListEnumerator                  inventEnumTransIdList;

        LogisticsPostalAddressRecId     oldDeliveryAddress;

        

        inventTransIdList               = _contracT.getInventTransIdList();

        inventEnumTransIdList           = inventTransIdList.getEnumerator();

        

        while (inventEnumTransIdList.moveNext())

        {

            inventTransId = inventEnumTransIdList.current();

            if (inventTransId)

            {

                try

                {

                    select firstonly forupdate validtimestate(validFrom, validTo)  salesLine

                        join  logisticsPostalAddress

                        where logisticsPostalAddress.RecId    == salesLine.DeliveryPostalAddress

                            && logisticsPostalAddress.ValidTo < validDateTime

                            && salesLine.InventTransId        == inventTransId;

                    {

                        if (oldDeliveryAddress != salesLine.DeliveryPostalAddress)

                        {

                            logisticsPostalAddressNew             = LogisticsPostalAddress::findByLocation(logisticsPostalAddress.Location);

                        }

                        oldDeliveryAddress      = salesLine.DeliveryPostalAddress;


                        if ( logisticsPostalAddressNew.RecId != logisticsPostalAddress.RecId)

                        {

                            select firstonly whsLoadLine

                                    where whsLoadLine.OrderNum == salesLine.SalesId;

                    

                            if ( !whsLoadLine

                                 || (whsLoadLine && whsLoadTable::find(whsLoadLine.LoadId).LoadStatus != WHSLoadStatus::Shipped)

                               )

                            {

                                salesLine.DeliveryPostalAddress = logisticsPostalAddressNew.RecId;

                                ttsbegin;

                                salesLine.doUpdate();

                                ttscommit;

                            }

                        }

                    }

                }

                catch(Exception::Deadlock)

                {

                    if (xSession::currentRetryCount() >= #RetryNum)

                    {

                        //this.addErrorLog(salesTableUpd.SalesId, salesTableUpd.InventLocationId);

                    }

                    else

                    {

                        //salesTableUpd.reread();

                        retry;

                    }

                }

                catch(Exception::UpdateConflict)

                {

                    if (appl.ttsLevel() == 0)

                    {

                        if (xSession::currentRetryCount() >= #RetryNum)

                        {

                            salesLine.reread();

                            throw Exception::UpdateConflictNotRecovered;

                        }

                        else

                        {

                            salesLine.reread();

                            retry;

                        }

                    }

                }

                catch

                {

                    info(strFmt("@CCB:SOLineAddressIssue", salesLine.SalesId,salesLine.LineNum));

                }

            }

        }

    }


}



Dynamics extension is not visible after update visual studio 2019 in D365 F&O

 1.    Navigate to K:\DeployablePackages\<Guid>(latest date time)\DevToolsService\Scripts.

2.    Locate a file "Microsoft.Dynamics.Framework.Tools.Installer.vsix" and double click it . (It will be an      application )

3.    Make sure Visual Studio 2017/2019 checkbox is selected, complete the process.

4.    Restart the dev environment.