This post demonstrates how to create batch classes using Business Operation Framework (or sysOperation). We used to use RunBaseBatch framework to implement batch functionality before this new concept of AX services was taken in place.
Compensations of BOF (or sysOperation) framework over RunBaseBatch are very well explained here http://daxmusings.codecrib.com/2011/08/from-runbase-to-sysoperation-business.html.
Key points RunBase/RunBaseBatch Vs sysOperation Frameworks
- The sysOperation framework is an implementation of the MVC pattern (Model-View-Controller)
- Model - parameters [contract class]
- View - Dialog [contract class or an optional UI builder class]
- Controller - Process (the code that runs in the essence of pattern)
- The framework influence the Services framework, whereas a service in AX has a data contract with some special attributes
- DataMember attributes in contract class take the entered values in parameters (dialog fields) to the process (aka "operations")
- sysOperationFramework simplifies the pack/unpack of variables taking advantage of attributes, this was pretty nasty in RunBaseBatch framework
Let’s
jump into example now, we are going to create a batch looks like this;
Create a new Contract Class
General concept of contract class: Contract class in basically an AX class with special attributes as you can see mentioned at the top of this class declaration. This class acts as a model of MVC (model-view-controller) pattern.
In runbase/runbasebatch framework this model has member variables and pack/unpack (aka serialization).
These member variables are defined in runbase/runbasebatch class declaration with pack/unpack variables handle under currentVersion and localVersion macros.
Specific to this this example: This class has two attributes; DataContractAttribute which is making it special class to leverage with AX services and the other attribute is referring to UIBuilder class. More on UIBuilder class explains well in this post follows.
[
DataContractAttribute,
SysOperationContractProcessingAttribute(classstr(FF_ExampleUIBuilder))
]
class FF_ExampleContract implements SysOperationValidatable
{
FromDate fromDate;
ToDate toDate;
NoYesId todaysDate;
MainAccountNum mainAccount;
}
[DataMemberAttribute]
public MainAccountNum parmMainAccount(MainAccountNum _mainAccount = mainAccount)
{
mainAccount = _mainAccount;
return mainAccount;
}
[DataMemberAttribute]
public FromDate parmFromDate(ExchangeRateImportFromDate _fromDate = fromDate)
{
fromDate = _fromDate;
return fromDate;
}
[DataMemberAttribute]
public ToDate parmToDate(ExchangeRateImportToDate _toDate = toDate)
{
toDate = _toDate;
return toDate;
}
[DataMemberAttribute]
public NoYesId parmTodaysDate(NoYesId _todaysDate = todaysDate)
{
todaysDate = _todaysDate;
return todaysDate;
}
public boolean validate()
{
boolean ok = true;
if (mainAccount == "")
{
ok = checkFailed("Main account cannot be blank");
}
if (todaysDate == NoYes::No)
{
if (fromDate == dateNull())
{
ok = checkFailed("From date cannot be blank");
}
if (toDate == dateNull())
{
ok = checkFailed("To date cannot be blank");
}
}
return ok;
}
Create a new UIBuider class;
This class is only required when you want to play with added parameter (data member attributes) in contract class. Like in this example I will be enabling/disabling parameters based on other parameter values, this class can also be used to build custom lookups and perform some custom logic only to play with parameters and their values.
class FF_ExampleUIBuilder extends
SysOperationAutomaticUIBuilder
{
#define.MainAccount('mainAccount')
#define.TodaysDate('todaysDate')
#define.FromDate('fromDate')
#define.ToDate('toDate')
FormComboBoxControl
mainAccountControl;
FormCheckBoxControl
todaysDateControl;
FormDateControl
fromDateControl;
FormDateControl
toDateControl;
}
public void
build()
{
DialogGroup
fromToDateRangeGroup;
DialogField
dialogField;
SAB_RetailConsolidateContract
dataContract;
dialog
= this.dialog();
dataContract = this.dataContractObject();
dialogField = dialog.addField(extendedtypestr(MainAccountNum));
mainAccountControl = dialogField.control();
mainAccountControl.text(dataContract.parmMainAccount());
mainAccountControl.name(#MainAccount);
dialogField = dialog.addField(extendedtypestr(NoYesId));
todaysDateControl = dialogField.fieldControl();
todaysDateControl.value(NoYes::No);
todaysDateControl.name(#TodaysDate);
todaysDateControl.label("Today");
fromToDateRangeGroup = dialog.addGroup("Date
range");
fromToDateRangeGroup.columns(2);
fromToDateRangeGroup.widthMode(FormWidth::ColumnWidth);
fromToDateRangeGroup.caption('');
dialogField = dialog.addField(extendedtypestr(FromDate));
fromDateControl = dialogField.control();
fromDateControl.name(#FromDate);
fromDateControl.allowEdit(true);
fromDateControl.dateValue(systemDateGet());
fromToDateRangeGroup.addFieldname(#ToDate);
dialogField = dialog.addField(extendedtypestr(ToDate));
toDateControl = dialogField.control();
toDateControl.name(#ToDate);
toDateControl.allowEdit(true);
toDateControl.dateValue(systemDateGet());
fromToDateRangeGroup.addFieldname(#ToDate);
}
/// <summary>
/// Provides form logic related to
the date type selected.
/// </summary>
/// <param
name="_importDateTypeControl">
/// The <c>FormComboBoxControl</c>
control for the date type.
/// </param>
public boolean
dateControlSelectionChg(FormCheckBoxControl _importDateTypeControl)
{
FormDateControl fromDateControlLocal;
FormDateControl toDateControlLocal;
boolean ret;
fromDateControlLocal = this.dialog().dialogForm().runControl(#FromDate);
toDateControlLocal =
this.dialog().dialogForm().runControl(#ToDate);
ret =
_importDateTypeControl.modified();
if (_importDateTypeControl.checked() ==
NoYes::Yes)
{
fromDateControlLocal.dateValue(dateNull());
toDateControlLocal.dateValue(dateNull());
fromDateControlLocal.allowEdit(false);
toDateControlLocal.allowEdit(false);
}
else
{
fromDateControlLocal.dateValue(systemDateGet());
toDateControlLocal.dateValue(systemDateGet());
fromDateControlLocal.allowEdit(true);
toDateControlLocal.allowEdit(true);
}
return
ret;
}
/// <summary>
/// Called after the user interface
has been built to allow for method overrides.
/// </summary>
public void
postRun()
{
super();
dialog.dialogForm().formRun().controlMethodOverload(false);
todaysDateControl.registerOverrideMethod(methodstr(FormCheckBoxControl,
modified), methodstr(FF_ExampleUIBuilder,
dateControlSelectionChg), this);
}
Create a new Controller class;
This class as a controller class of the MVC pattern and runs the business logic. In this example it is setting dialog title, retrieving dialog values from contract class and from Main method it is calling service class with provided method name and execution mode.
class FF_ExampleController extends SysOperationServiceController
{
#define.MainAccount('mainAccount')
#define.TodaysDate('todaysDate')
#define.ToDate('toDate')
#define.FromDate('fromDate')
}
public ClassDescription caption()
{
ClassDescription ret;
// This is used to identify the batch job within batch
processing
ret = "Example sysOperation Framework";
return ret;
}
public void
getFromDialog()
{
FormComboBoxControl
mainAccountControl;
FormCheckBoxControl
todaysDateControl;
FormDateControl
fromDateControl;
FormDateControl
toDateControl;
DialogForm theDialogForm;
FF_ExampleContract
FF_ExampleContract;
theDialogForm = this.dialog().dialogForm();
super();
mainAccountControl = theDialogForm.runControl(#MainAccount);
todaysDateControl = theDialogForm.runControl(#TodaysDate);
fromDateControl = theDialogForm.runControl(#toDate);
toDateControl = theDialogForm.runControl(#fromDate);
FF_ExampleContract = this.getDataContractObject(classStr(FF_ExampleContract));
if (FF_ExampleContract)
{
// Set the values in data contract
FF_ExampleContract.parmMainAccount(mainAccountControl.text());
FF_ExampleContract.parmTodaysDate(todaysDateControl.value());
FF_ExampleContract.parmToDate(fromDateControl.dateValue());
FF_ExampleContract.parmFromDate(toDateControl.dateValue());
}
}
public LabelType parmDialogCaption(LabelType
_dialogCaption = "")
{
LabelType caption;
// This appears as the window name
caption = "Example for SysOperation
Framework";
return caption;
}
public static
FF_ExampleController construct()
{
return new
FF_ExampleController();
}
public static
void main(Args args)
{
FF_ExampleController controller;
identifierName
className;
identifierName methodName;
SysOperationExecutionMode executionMode;
[className, methodName, executionMode] =
SysOperationServiceController::parseServiceInfo(args);
controller = new
FF_ExampleController(className, methodName, executionMode);
if (controller.prompt())
{
controller.run();
}
}
Create a new Service class;
This class is an AX service class with [SysEntryPointAttribute] attribute at the top of the method which will be called from action menu item.
[SysEntryPointAttribute]
public void transactionConsolidation(FF_ExampleContract FF_ExampleContract)
{
FromDate fromDate = FF_ExampleContract.parmFromDate();
ToDate toDate = FF_ExampleContract.parmToDate();
NoYesId todaysDate = FF_ExampleContract.parmTodaysDate();
MainAccountNum mainAccountNum = FF_ExampleContract.parmMainAccount();
MainAccount mainAccount = mainAccount::findByMainAccountId(mainAccountNum);
if (todaysDate == NoYes::Yes)
{
fromDate = systemDateGet();
toDate = systemDateGet();
}
/*
ttsBegin;
... call any logic here
ttsCommit;
*/
}
Create a new action menu item;
Create new action menu item with following properties set as shown;
This menu item is calling controller class with parameters ServiceClass.MethodName with execution mode. If you see the Main method of controller class it is getting these passed parameters in this piece of code and eventually calling transactionConsolidation method of Service class.
[className, methodName, executionMode] = SysOperationServiceController::parseServiceInfo(args);
controller = new FF_ExampleController(className, methodName, executionMode);
Since BOF services run X++ in the CLR, it is very important to generate incremental CIL after implementing new service in AX.
Another important point; the parameter name in FF_ExampleService.transactionConsolidation method MUST BE same as it is in getFromDialog() method of controller class. Otherwise you will get error object not initialised in getFromDialog method.
FF_ExampleService.transactionConsolidation
[SysEntryPointAttribute]
public void transactionConsolidation(FF_ExampleContract FF_ExampleContract)
{
FF_ExampleControler
public void getFromDialog()
{
FormComboBoxControl mainAccountControl;
FormCheckBoxControl todaysDateControl;
FormDateControl fromDateControl;
FormDateControl toDateControl;
DialogForm theDialogForm;
FF_ExampleContract FF_ExampleContract;
theDialogForm = this.dialog().dialogForm();
super();
mainAccountControl = theDialogForm.runControl(#MainAccount);
todaysDateControl = theDialogForm.runControl(#TodaysDate);
fromDateControl = theDialogForm.runControl(#toDate);
toDateControl = theDialogForm.runControl(#fromDate);
FF_ExampleContract = this.getDataContractObject(classStr(FF_ExampleContract));
if (FF_ExampleContract)
{
// Set the values in data contract
FF_ExampleContract.parmMainAccount(mainAccountControl.text());
FF_ExampleContract.parmTodaysDate(todaysDateControl.value());
FF_ExampleContract.parmToDate(fromDateControl.dateValue());
FF_ExampleContract.parmFromDate(toDateControl.dateValue());
}
}
Mr Benjamin went above and beyond their requirements to assist me with my loan which i used expand my pharmacy business,They were friendly, professional, and absolute gems to work with.I will recommend anyone looking for loan to contact. 247officedept@gmail.com.WhatsApp ... + 19893943740.
ReplyDeleteMy name is Jane Wembli Josh and i live in the USA California and life is worth living right now for me and my family and all this is because of one man sent by GOD to help me and my family, i once had a life filled with sorrow because my first SON needed a kidney transplant and all our savings were going towards his medications and this normally leaves us with no money to pay our bills or even put enough food on our table and our rent was due and no funds to pay these bills and life felt so unfair to me and every night my wife will cry herself to sleep every night until one day, i was browsing through yahoo answers and i saw a striking advert of a man that gives out personal loans and that the offer is opened to all and i have heard so many things about internet scams but at this point of my life, i was very desperate and ready to take any risk and at the end of the day, i applied for this loan and from one step to another, i got my loan within 12 hours through bank transfer and you know, it was all like a dream and i called Rev. Fr. Kevin Doran A Man who is the GOD sent lender i found and said, i have received my loan and he smiled stating that to GOD be the glory and i was so shocked as i have never ever seen anyone with such a GOD fearing and kind heart and today, i am the happiest man on earth because by GOD’S grace, my SON kidney transplant was successful and today he is healthy, i and my family are living very comfortable and happy and if you are in my former situation or in serious and legitimate need of a loan, you can reach this GOD sent lender via consumerloanfirm@gmail.com
ReplyDelete