X++ code to get Ledger period start date / End date And calculate main account balance from trail balances

 public AmountMst openingbalance()

    {

        LedgerBalanceMainAccountAmounts ledgerBalance;

        GeneralJournalAccountEntry      generalJournalAccountEntry;

        GeneralJournalEntry              generaljournalentry;

        FiscalCalendarPeriod             fiscalCalendarPeriod;

        AmountMst  opSum;

        LedgerPeriodCode ledgerPeriodCode;

        InventPosting     inventposting;

// To get period start date

       periodstart periodStartDate = FiscalCalendarYear::findYearByCalendarDate(Ledger::fiscalCalendar(), fromDate).StartDate;

       

        select * from inventposting 

            where inventposting.InventAccountType == InventAccountType::PurchPackingSlipPurchaseOffsetAccount;

        /// select statement to get sum of Main account balances

        select sum(AccountingCurrencyAmount) from generalJournalAccountEntry 

            where generalJournalAccountEntry.MainAccount == MainAccount::findByMainAccountId(inventposting.ledgerAccountName()).RecId

                join generaljournalentry where generalJournalAccountEntry.GeneralJournalEntry == generaljournalentry.RecId

                    && generaljournalentry.AccountingDate >= periodStartDate 

                    && generaljournalentry.AccountingDate<= fromDate

                        join fiscalCalendarPeriod where fiscalCalendarPeriod.RecId == generaljournalentry.FiscalCalendarPeriod

                            && (fiscalCalendarPeriod.Type == FiscalPeriodType::Opening

                            || fiscalCalendarPeriod.Type == FiscalPeriodType::Operating);


        return generalJournalAccountEntry.AccountingCurrencyAmount;

Multithreading (Batch bundelling) Using sysoperation in D365 fo X++

 Here is my requirement is I have to create Job card journals that flowing from 3rd part application through a data entity.

For that, I have implemented a Multithreading sysopeartion which is a batch bundling process.

For that, I have created a base class and implementing it through required classes.


Read comments for a better understanding.

1.Create on contract class 

this contract class will hold the parameters for user that he need to specify the maximum number of records and sleep time betweeen each task based on his data.

/// <summary>

/// contract class to get and set the parameters

/// </summary>

[DataContractAttribute]

class WipTransactionsMultithreadContract

{

    TransDate       fromDate,toDate;

    PositiveNumber  taskSleepTimeMs;

    PositiveNumber  maxTaskCount;

    public Range           batchIdentifier;

test

    /// <summary>

    /// Parm method to intilize batch identifier ranges values

    /// </summary>

    /// <param name = "_batchIdentifier">Range</param>

    /// <returns>Range</returns>

    [DataMemberAttribute,SysOperationControlVisibilityAttribute(false)]

    public Range parmBatchIdentifier(Range _batchIdentifier = batchIdentifier)

    {

        batchIdentifier = _batchIdentifier;

        return batchIdentifier;

    }

    /// <summary>

    /// parm method to set the sleep time for batch

    /// </summary>

    /// <param name = "_taskSleepTimeMs">positive integer</param>

    /// <returns>Sleeptime</returns>

    [DataMemberAttribute,SysOperationLabelAttribute("Task Sleep Time"),SysOperationHelpTextAttribute("Task Sleep Time in ms"),SysOperationDisplayOrderAttribute('1')]

    public PositiveNumber parmTaskSleepTimeMs(PositiveNumber _taskSleepTimeMs = taskSleepTimeMs)

    {

        taskSleepTimeMs = _taskSleepTimeMs;

        return taskSleepTimeMs;

    }

    /// <summary>

    ///parm method to specify Max number of task count

    /// </summary>

    /// <param name = "_maxTaskCount">Positive integer</param>

    /// <returns>Maxtaskcount</returns>

    [DataMemberAttribute,SysOperationLabelAttribute("Max task Count"),SysOperationHelpTextAttribute("Enter Max Task count"),SysOperationDisplayOrderAttribute('2')]

    public PositiveNumber parmMaxTaskCount(PositiveNumber _maxTaskCount = maxTaskCount)

    {

        maxTaskCount = _maxTaskCount;

        return maxTaskCount;

    }

}

By this, we are given a user interface for the user

2. Now create one base class which will used to run thread tasks for operation

/// <summary>

/// base class to run opearions in parellel batch process

/// </summary>

class testWiptransactionsMultithreadBase extends SysOperationServiceBase

{

    Range                       batchIdentifier;

    BatchHeader                 batchHeader;

    Batchable                   finalTask;

    /// <summary>

    /// method used to run threads for our batch

    /// </summary>

    /// <param name = "_TestWipTransactionsMultithreadContract">TestWipTransactionsMultithreadContract</param>

    public void runSubTask(TestWipTransactionsMultithreadContract _TestWipTransactionsMultithreadContract)

    {

        container               batchIdentifierCon;

        int                     i;

        batchIdentifier =_TestWipTransactionsMultithreadContract.batchIdentifier;

        if (batchIdentifier) //child task

        {

            if (batchIdentifier == this.finalTaskIdentifier())

            {

                this.runFinalTask();

            }

            else

            {

                this.runThreadTask();

            }

        }

        else

        {

            this.runStartTask();

            batchIdentifier = this.finalTaskIdentifier();

            this.processThreadItem(true); //create the final task, we need a dependency, so create it in the beggining.

            batchIdentifierCon = this.getBatchIdentifiersRangeCon();

            for (i = 1; i <= conLen(batchIdentifierCon); i++)

            {

                batchIdentifier = conPeek(batchIdentifierCon, i);

                this.processThreadItem(false);

            }

            if (finalTask)

            {

                if (this.isExecutingInBatch())

                {

                    batchHeader.save();

                }

                else

                {

                    finalTask.run();

                }

            }

        }

    }

    /// <summary>

    /// Intial method for task run

    /// </summary>

    public void runStartTask()

    {

    }

    /// <summary>

    /// runs thread task for devided batches

    /// </summary>

    public void runThreadTask()

    {

    }

    /// <summary>

    /// runs final task for batch

    /// </summary>

    public void runFinalTask()

    {

    }

    /// <summary>

    /// Identifer method to recognize the end value

    /// </summary>

    /// <returns>Identifier string</returns>

    public Range  finalTaskIdentifier()

    {

        return '--TheLast--';

    }

    /// <summary>

    /// Container for holding the ranges

    /// </summary>

    /// <returns>Range container</returns>

    public container  getBatchIdentifiersRangeCon()

    {

        container  res;

        return res;

    }

    /// <summary>

    /// Process the thread based recrds in service class

    /// </summary>

    /// <param name = "_isLast">boolean</param>

    protected void processThreadItem(boolean _isLast)

    {

        //WiptransactionsMultithreadservice        childThread;

        SysOperationServiceController  controller;

        WipTransactionsMultithreadContract dataContract;

        ;

        if (!batchIdentifier)

        {

            return;

        }

        controller = new SysOperationServiceController(classStr(WiptransactionsMultithreadservice),methodStr(WiptransactionsMultithreadservice,runSubTask));

        dataContract = controller.getDataContractObject('_WipTransactionsMultithreadContract');

        // initialize variables for the data contract

        dataContract.parmBatchIdentifier(batchIdentifier);

        if(this.isExecutingInBatch() && !batchHeader)

        {

            // if no batchheader has been set yet, get it

            batchHeader = this.getCurrentBatchHeader();

        }

        if (_isLast)

        {

            finalTask = controller;

        }

        if (this.isExecutingInBatch())

        {

            batchHeader.addRuntimeTask(controller, this.getCurrentBatchTask().RecId);

            if (! _isLast && finalTask)

            {

                batchHeader.addDependency(finalTask, controller, BatchDependencyStatus::FinishedOrError);

            }

                }

        else

        {

            if (! _isLast)

            {

                controller.run();

                //childThread.run();

            }

        }

    }

}

3. Create one service class which implemnts from Multithreadbase

/// <summary>

/// Service class to create batches for job card operations

/// </summary>

class TestWiptransactionsMultithreadservice extends TestWiptransactionsMultithreadBase

{

    QueryRun       queryRun;

    TransDate       fromDate,toDate;

    PositiveNumber  taskSleepTimeMs;

    PositiveNumber  maxTaskCount;

    /// <summary>

    /// A new method intilzes query

    /// </summary>

    public void new()

    {

        this.initQuery();

    }

    /// <summary>

    /// description for the class

    /// </summary>

    /// <returns>string</returns>

    static ClassDescription description()

    {

        return 'Batch multiple threads';

    }

    /// <summary>

    /// Run method to intilze contract class parameters to base class

    /// </summary>

    /// <param name = "TestWipTransactionsMultithreadContract">contract</param>

    public void runSubTask(TestWipTransactionsMultithreadContract _TestWipTransactionsMultithreadContract)

    {

        super(_TestWipTransactionsMultithreadContract);

    }

    /// <summary>

    /// Start method

    /// </summary>

    public void runStartTask()

    {

        //1. data preparation

        info(strFmt('%1 - Start operation',AifUtil::applyUserPreferredTimeZoneOffset(DateTimeUtil::utcNow())));

    }

    /// <summary>

    /// Used to run the query class

    /// </summary>

    /// <returns>queryrun</returns>

    public QueryRun queryRun()

    {

        return queryRun;

    }

    /// <summary>

    /// Default value for parameter

    /// </summary>

    public void initParmDefault()

    {

        this.initQuery();

        taskSleepTimeMs = 100;

    }

    /// <summary>

    /// Method used for create custom query

    /// </summary>

    public void initQuery()

    {

        Query                   query = new Query();

        query.addDataSource(tablenum(WipTransactions));

        queryRun = new QueryRun(query);

    }

    /// <summary>

    /// Runs Threads and process query for batch job

    /// </summary>

    public void runThreadTask()

    {

        //2. Query Processing

        this.initQuery();

        QueryBuildDataSource  qBDS = queryRun.query().dataSourceTable(tablenum(TesWipTransactions));

        qBDS.addRange(fieldnum(TesWipTransactions, RecId)).value(batchIdentifier);

        qBDS.addRange(Fieldnum(Testwiptransactions ,RecordLaborStatusType)).value('Stop');

        qBDS.addRange(fieldNum(TestWipTransactions, Isprocessed)).value(SysQuery::value(TestIsprocessed::NotProcessed));

        while (queryRun.next())

        {

            TestWipTransactions   wiptrans = queryRun.get(tablenum(TestWipTransactions));

            this.processRecord(wiptrans);

        }

    }

    /// <summary>

    /// end of the runnable task 

    /// </summary>

    public void runFinalTask()

    {

        //3.final task

        info(strfmt('%2 - %1 record(s) processed', SysQuery::countTotal(queryRun),

                AifUtil::applyUserPreferredTimeZoneOffset(DateTimeUtil::utcNow())));

    }

    /// <summary>

    /// sets range values in container based on the given parameter

    /// </summary>

    /// <returns>container</returns>

    public container  getBatchIdentifiersRangeCon()

    {

        container  res;

        QueryRun   queryRunLocal = new QueryRun(queryRun.query());

        QueryBuildDataSource   qBDS = queryRunLocal.query().dataSourceTable(tablenum(TestWipTransactions));

        int                    totalRecords, curRecord, recordsPerBatch;

        RecId                  fromRecId, toRecId;

        qBDS.sortClear();

        qBDS.addSortField(fieldnum(TestWipTransactions, RecId));

        qBDS.addRange(fieldnum(TestWipTransactions, RecId)).value(batchIdentifier);

        qBDS.addRange(Fieldnum(Testwiptransactions ,RecordLaborStatusType)).value('Stop');

        qBDS.addRange(fieldNum(TestWipTransactions, Isprocessed)).value(SysQuery::value(TestIsprocessed::NotProcessed));

        totalRecords = SysQuery::countTotal(queryRunLocal);

        recordsPerBatch = maxTaskCount > 0 ? totalRecords div maxTaskCount : totalRecords;

        if (! recordsPerBatch)

        {

            recordsPerBatch = 1;

        }

        while (queryRunLocal.next())

        {

            TestWipTransactions   wiptransactions = queryRunLocal.get(tablenum(TestWipTransactions));

            if (!fromRecId)

            {

                fromRecId = wiptransactions.RecId;

            }

            curRecord++;

            toRecId = wiptransactions.RecId;

            if ((curRecord mod recordsPerBatch) == 0)

            {

                res += SysQuery::range(fromRecId, toRecId);

                fromRecId = 0;

                curRecord = 0;

            }

        }

        if (curRecord && fromRecId && toRecId)

        {

            res += SysQuery::range(fromRecId, toRecId);

        }

        return res;

    }

    /// <summary>

    /// process records for creating batch

    /// </summary>

    /// <param name = "_wiptransactions">TestWipTransactions</param>

    public void processRecord(TestWipTransactions  _wiptransactions)

    {

        try

        {

            //do some job using WIptransactions   add your service method

            TestWIPTransactionsJobcardsBatch testWIPTransactionsJobcardsBatch = new   TestWIPTransactionsJobcardsBatch();

            TestWIPTransactionsJobcardsBatch.Processoperation(_wiptransactions);

            _wiptransactions.reread();

            sleep(taskSleepTimeMs);

        }

        catch

        {

            error(strfmt('Error occured for RecordId %1 OrderNum %2 ',_wiptransactions.RecId,_wiptransactions.OrderNumber));

        }

    }

    /// <summary>

    /// Method to intiate operation 

    /// </summary>

    /// <param name = "_TestWipTransactionsMultithreadContract">Contract</param>

    public void createJobcards(TestWipTransactionsMultithreadContract _TestWipTransactionsMultithreadContract)

    {

        taskSleepTimeMs=_TestWipTransactionsMultithreadContract.parmTaskSleepTimeMs();

        maxTaskCount=_TestWipTransactionsMultithreadContract.parmMaxTaskCount();

        // this.PopulateGroupTable();

        this.runSubTask(_TestWipTransactionsMultithreadContract);

    }

}

4. Create one process class that is responsible to run our operations

Here I'm creating one class named  "WIPTransactionsJobcardsBatch" which is used to process the records and create job cards, you can create any of the classes that processed your requirement.

After creating this class you must have to call this class in multithread service class so that we can divide the records and process the divided records in this service class.

Let me give you one scenario, this will create and post job cards for incoming records.

TestWIPTransactionsJobcardsBatch:

/// <summary>

/// Clss to run the process for creating job cards

/// </summary>

class  testWIPTransactionsJobcardsBatch

{

    /// <summary>

    /// method used to create the Job cards

    /// </summary>

    /// <param name = "_wiptransactions">TestWipTransactions</param>

    public void Processoperation(TestWipTransactions _wiptransactions)

    {

        ProdJournalTable                        prodJournalTable;

        ProdJournalRoute                        prodJournalRoute;

        ProdRoute                               prodRoute;

        testwiptransactions                      wiptransactions,wiptransactionsUpd;

        TestTransactionBase                      transactionBase;

        try

        {

            ttsbegin;

            wiptransactions = _wiptransactions;

            select firstonly prodRoute where prodRoute.ProdId == wiptransactions.OrderNumber

                        && prodRoute.OprNum == str2Int(wiptransactions.OperationNumber);

            if(!prodRoute)

            {

                throw Global::error(strFmt('Route OprNum -%1 Does not exists for OrderNum - %2',wiptransactions.OperationNumber,wiptransactions.OrderNumber));

            }

            prodJournalTable.clear();

            prodJournalTable.initValue();

            prodJournalTable.JournalType             = prodjournaltype::JobCard;

            prodJournalTable.ProdId                  = prodRoute.ProdId;

            prodJournalTable.JournalNameId           = ProdParametersDim::findDefault().JobCardJournalNameId;

            prodJournalTable.Description             = ProdJournalName::find(prodJournalTable.JournalNameId).Description;

            prodJournalTable.VoucherSeqRecId         = ProdJournalName::find(prodJournalTable.JournalNameId).VoucherSeqRecId;

            prodJournalTable.VoucherDraw             = journalVoucherDraw::Post;

            prodJournalTable.NumOfLines              = 1;

            prodJournalTable.insert();

            //job card entry

            prodJournalRoute.clear();

            prodJournalRoute.JournalId               = prodJournalTable.journalId;

            prodJournalRoute.ProdId                  = prodRoute.ProdId;

            prodJournalRoute.initValue();

            prodJournalRoute.OprNum                  = prodRoute.OprNum;

            prodJournalRoute.JobId                   = ProdRouteJob::findJobOpr(prodRoute.ProdId,prodRoute.OprNum, prodRoute.OprPriority).JobId;

            prodJournalRoute.OprId                   = prodRoute.OprId;

            prodJournalRoute.DefaultDimension        = prodRoute.DefaultDimension;

            prodJournalRoute.JobType                 = RouteJobType::Process;

            // prodJournalRoute.CategoryHoursId         = prodRoute.SetUpCategoryId;

            //prodJournalRoute.CategoryQtyId           = prodRoute.ProcessCategoryId;

            prodJournalRoute.WrkCtrId                = wrkctrtable::findByRecId(str2Int64(wiptransactions.WorkCenterExternalKey)).WrkCtrId;

            //added for wrkctrCategoryhourse id

            WrkCtrTable wrkCtrTable                  = prodJournalRoute.wrkCtrTable();

            ProdJobType prodJobType                  = ProdJobType::construct(prodJournalRoute.JobType);

            prodJournalRoute.setCategoryHours(wrkCtrTable.SetUpCategoryId);

            prodJournalRoute.setCategoryQty(wrkCtrTable.QtyCategoryId);

            // for worker

            transactionBase                          = TestTransactionBase::findbyId(wiptransactions.ID);

            prodJournalRoute.Worker                  = str2Int64(transactionBase.PerformedByErpUserKey) ;

            // for deault dimensions

            //prodJournalRoute.DefaultDimension        = prodJournalRoute.copyDimension(prodJournalRoute.prodTable().DefaultDimension);

            //prodJournalRoute.DefaultDimension        = prodJournalRoute.mergeDimension(wrkCtrTable.DefaultDimension, prodJournalRoute.DefaultDimension);

            prodJournalRoute.QtyGood                 = wiptransactions.CompletedQuantity;

            prodJournalRoute.FromTime                = DateTimeUtil::time(wiptransactions.StartLabourDate);

            prodJournalRoute.ToTime                  = DateTimeUtil::time(wiptransactions.EndLaborDate);

            prodJournalRoute.Hours                   = (wiptransactions.DurationInMinutes)/60;

            if (wiptransactions.CompletedQuantity)

            {

                prodJournalRoute.ProdPickList            = NoYes::Yes;

            }

            prodJournalRoute.insert();

            if(TestParameters::find().PostJobCardJournal == NoYes::Yes)

            {

                ProdJournalCheckPostRoute ProdJournalCheckPostRoute;

                ProdJournalCheckPostRoute = ProdJournalCheckPostRoute::newPostJournal(prodJournalRoute.journalId,true);

                ProdJournalCheckPostRoute.runOperation();

            }

            wiptransactionsUpd                      = Testwiptransactions::findRecId(wiptransactions.RecId,true);

            wiptransactionsUpd.Isprocessed          = TestIsprocessed::Processed;

            wiptransactionsUpd.TestJobCardId         = prodJournalTable.journalId;

            wiptransactionsUpd.ErrorDescription     = ' ';

            wiptransactionsUpd.update();

            ttscommit;

        }

        catch(Exception::Error)

        {

            str errormessage = infolog.text(infologLine());

            ttsbegin;

            wiptransactionsUpd =   Testwiptransactions::findRecId(wiptransactions.RecId,true) ;

            wiptransactionsUpd.Isprocessed = TestIsprocessed::Error;

            wiptransactionsUpd.ErrorDescription = errormessage;

            wiptransactionsUpd.update();

            ttscommit;

        }

    }

}

Now all set to run the operation, one thing is pending that you have to create one controller class to execute this operation and give the scope of both service classes.

5. TestWIPTransactionsJobCardController :

Opeartion will be intiated from Multithread service class itself. Lets Go..!

/// <summary>

/// Controller class to run Batch for job Card creations

/// </summary>

class TestWIPTransactionsJobCardController Extends SysOperationServiceController

{

    /// <summary>

    /// Caption for batch job

    /// </summary>

    /// <returns>string caption</returns>

       public ClassDescription caption()

    {

        return 'Test Job Card Creation';

    }

    /// <summary>

    /// new method used for intilize service class in controller

    /// </summary>

    protected void new()

    {

        super(classStr(TestWiptransactionsMultithreadservice), methodStr(TestWiptransactionsMultithreadservice,createJobcards ), SysOperationExecutionMode::Synchronous);

    }

    /// <summary>

    /// method to create object for controller

    /// </summary>

    /// <param name = "_executionMode">synchronus</param>

    /// <returns>controller</returns>

    public static TestWIPTransactionsJobCardController construct(SysOperationExecutionMode _executionMode = SysOperationExecutionMode::Synchronous)

    {

        TestWIPTransactionsJobCardController controller;

        controller = new  TestWIPTransactionsJobCardController();

        controller.parmExecutionMode(_executionMode);

        return controller;

    }

    /// <summary>

    /// Main method intiate Opeartion for batch

    /// </summary>

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

    public static void main(Args _args)

    {

        TestWIPTransactionsJobCardController controller;

        controller =  TestWIPTransactionsJobCardController::construct();

        controller.parmArgs(_args);

        controller.startOperation();

    }

}

Now create one Action menu item and this controller class and run, Now your batch will run in the multithreading batch bundling process.

Thank you !!

Reference: Click here