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