Dynamics 365 FO for All
There are lot of blogs and community pages available that shows enthusiasm of ever increasing professionals and reach of MS Dynamics products to Customers. As D365 FnO and its functionalities are expanding so the need for learning materials and forum participation increases to tackle day to day issues we may face while implementing AX/D365. This is one more blog to distribute and spread knowledge I have gained throughout the years in D365 FnO. (load the scripts for better view of the codes)
Wednesday 27 September 2023
Service update failure due to Globalupdate script for service model: AOSService on VM failed due to report deployment
Thursday 19 January 2023
Perform operation at Entity level post all Target data Insert in D365 FnO
There are often requirements where we need to perform some operation(s) Just after target data is inserted/updated fully for all records.
There is a method that can be used at Entity level known as postTargetProcess
Drawback of this method is that It only can be used with Data management framework in non-set based entities.
It can also not be used with Odata operations.
Method can be implemented as below
public class SaddafDemoEntity extends common { /// <summary> /// Post insert into target /// </summary> /// <param name = "_dmfDefinitionGroupExecution">DMFDefinitionGroupExecution</param> public static void postTargetProcess(DMFDefinitionGroupExecution _dmfDefinitionGroupExecution) { TestHeaderTable headerTable; SaddafDemoStaging staging; select firstonly forupdate integrationImportTable where integrationImportTable.DMFExecutionId == _dmfDefinitionGroupExecution.ExecutionId; select count(Recid) from staging where staging.DefinitionGroup == _dmfDefinitionGroupExecution.DefinitionGroup && staging.ExecutionId == _dmfDefinitionGroupExecution.ExecutionId && staging.TransferStatus == DMFTransferStatus::Completed; ttsbegin; headerTable.RecordCount = staging.RecId headerTable.update(); ttscommit; } }For Entities using set based operations, we have a tricky workaround by using an Post event handler for a write method of DmfEntityWriter class.
[PostHandlerFor(classStr(DmfEntityWriter), methodStr(DmfEntityWriter, write))] public static void DmfEntityWriter_Post_write(XppPrePostArgs args) { DMFDefinitionGroupExecution dmfDefinitionGroupExecution = args.getArg('_definitionGroupExecution'); str entityName = dmfDefinitionGroupExecution.EntityXMLName; switch (entityName) { case 'SaddafDemoEntity': TestHeaderTable headerTable; SaddafDemoStaging staging; select firstonly forupdate integrationImportTable where integrationImportTable.DMFExecutionId == dmfDefinitionGroupExecution.ExecutionId; select count(Recid) from staging where staging.DefinitionGroup == dmfDefinitionGroupExecution.DefinitionGroup && staging.ExecutionId == dmfDefinitionGroupExecution.ExecutionId && staging.TransferStatus == DMFTransferStatus::Completed; ttsbegin; headerTable.RecordCount = staging.RecId headerTable.update(); ttscommit; break; } }Unfortunately there is nothing similar for Odata, but Odata has its own set of capabilities to supersede all shortcomings.
Perform operation at Entity level post all Staging data Insert or Before Target insert starts in D365 FnO
As the title suggests, there are sometimes requirements where we need to perform some operation(s) Just before target data insert operation triggers (or in between the whole DMF cycle) but not at the any datasource event or at the Entity insert method.
One of such requirement can where the Any Header data to be populated before line insertion.
Good news is we have a way to achieve it via postGetStagingData method at Entity level, Bad news is it works only after staging is inserted and works even if all target data fails validation.
Below is the implementation way for that
public class SaddafDemoEntity extends common { /// <summary> /// Post staging data /// </summary> /// <param name = "_dmfDefinitionGroupExecution">DMFDefinitionGroupExecution</param> public static void postGetStagingData(DMFDefinitionGroupExecution _dmfDefinitionGroupExecution) { TestHeaderTable headerTable; SaddafDemoStaging staging; select firstonly staging where staging.DefinitionGroup == _dmfDefinitionGroupExecution.DefinitionGroup && staging.ExecutionId == _dmfDefinitionGroupExecution.ExecutionId; headerTable.DMFExecutionId = staging.ExecutionId; //Just the example, you can use any field needed headerTable.insert();
ttsbegin; staging = null; update_recordset staging setting ParentRefRecId = headerTable.RecId
where staging.DefinitionGroup == _dmfDefinitionGroupExecution.DefinitionGroup && staging.ExecutionId == _dmfDefinitionGroupExecution.ExecutionId; ttscommit; } }
Wednesday 16 September 2020
Multiselect lookup on dialog in UI builder class (Sysoperation) in D365 F&O
This way of setting Multiselectlookup(Lookup that allows Multiple value selections) can be used on any module (SSRS, Service, Batch or simple server/client based service class) utilizing SysOperation framework.
Code design would be as follows:-
1. Data contract class: (just showing relevant parameters, there can be other parameters too). Just to note, container type parameter can also be used in place of List.
[DataContract, SysOperationContractProcessing(ClassStr(TestExpImpUIBuilder))] class TESTEXPDC extends SysOperationServiceBaseDataContract { List department; [DataMemberAttribute, SysOperationLabel("@SYS850"), AifCollectionTypeAttribute('return', Types::String)] public List parmDepartment(List _department = department) { department = _department; return department; } }2. UI Builder Class: This is most important to build a multi select lookup.
class TestExpImpUIBuilder extends SysOperationAutomaticUIBuilder { DialogField dialogDepartment; SysLookupMultiSelectCtrl ctrlDepartment; //Override this to add dialog field protected DialogField addDialogField(IdentifierName _methodName, Object _dataContract = this.dataContractObject()) { DialogField dialogField;; switch (_methodName) { case methodStr(TESTEXPDC, parmDepartment): dialogDepartment = this.dialog().addField( extendedTypeStr(Description), "@SYS850"); dialogField = dialogDepartment; break; default: dialogField = super(_methodName, _dataContract); } return dialogField; } //Override this public void postRun() { super(); if (this.dataContractObject() is TESTEXPDC) //if is optional, in my case i was using single UI builder for multiple tasks so required { this.lookupDepartment(); } } //to populate lookup protected void lookupDepartment() { TableId multiSelectTableNum = tableNum(EcoResCategory); Query query = new Query(); QueryBuildDataSource qbds = query.addDataSource(multiSelectTableNum); EcoResCategoryHierarchyRole categoryRole; select firstonly categoryRole where categoryRole.NamedCategoryHierarchyRole == EcoResCategoryNamedHierarchyRole::RetailChannelNavigation; qbds.addRange(fieldNum(EcoResCategory, Level)).value(queryValue(3)); qbds.addRange(fieldNum(EcoResCategory, CategoryHierarchy)).value(queryValue(categoryRole.CategoryHierarchy)); qbds.addSelectionField(fieldNum(EcoResCategory, Code)); //needed for field display in lookup qbds.addSelectionField(fieldNum(EcoResCategory, Name)); container selectedFields = [multiSelectTableNum, fieldNum(EcoResCategory, Code)]; //irrelevant for lookup but value needed for further use in method calling ctrlDepartment = SysLookupMultiSelectCtrl::constructWithQuery(this.dialog().dialogForm().formRun(), dialogDepartment.control(), query, false, selectedFields); } public void getFromDialog() { super(); //keep in mind, multiselect actually brings Recid and works like reference lookup if (this.dataContractObject() is TESTEXPDC) { List listDepartment = new List(Types::String); container conDeptt = ctrlDepartment.get(); Counter conCount = 1; Counter conDepLength = conLen(conDeptt); while (conCount //use sign for less and equal here// conDepLength) { listDepartment.addEnd(EcoResCategory::find(conPeek(conDeptt, conCount)).Code); conCount++; } this.dataContractObject().parmDepartment(listDepartment); //whole list code not needed in case you are using container type parameter //this.dataContractObject().parmDepartment(ctrlDepartment.get()); } } }
3. Service class : Its optional and purely based upon requirement. No lookup code is needed here, just needed for execution type and Contract mapping to further call Task based class.
4. Task class: actual iteration of selected multiselect values would be done here.
class TESTEXPTask { List listDepartment; public static TESTEXPTask construct() { return new TESTEXPTask(); } public void run(TESTEXPDC _contract) { this.processDataforExport(); } protected void processDataforExport() { try { ttsbegin; // any code execution //Line Creation this.createLinesPerDepartment(); ttscommit; } catch (Exception::Error) { exceptionTextFallThrough(); } } protected void createLinesPerDepartment() { if (listDepartment && !listDepartment.empty()) { ListEnumerator listEnumerator; listEnumerator = listDepartment.getEnumerator(); while (listEnumerator.moveNext()) { this.methodName(listEnumerator.current()); } } else { //call execution code this.methodeName(); } }
and you are done. Just squeeze in your other code like additional parameters and business code and voila!!!
Keep in mind, UI builder class in anyways would be used in similar old manner (overridelookup) for conventional field lookups.
See you in next blog. Happy Daxing (Now D365ing)!!! Please drop a feedback in case of any issues or any other concerns.
Tuesday 10 September 2019
Use Coc(Chain of command) for methods on form datasource/Controls/DataSource fields D365 Finance and Operations
Its much easier than you think, only drawback is you don't really have a power of intellisense on this.
But, who cares as long as it works.
Scenario:
Form: SalesTable
Datasource: SalesTableExt (Custom table)
Task: to fillup value in field salestable (recId of salesTable data) on datasource write method.
////// extension class for SalesTableExt on form SalesTable /// [ExtensionOf(formdataSourceStr(SalesTable, SalesTableExt))] final class SalesTablefdsSalesTableExtTest_Extension { ////// write method on datasource /// void write() { this.writePre(); next write(); } ////// prewrite method /// protected void writePre() { this.resolveSalesId(); } ////// salesId(recid) update in SalesTableExt /// protected void resolveSalesId() { FormDataSource ds = this; SalesTable salesTable = ds.formRun().dataSource(formdataSourceStr(SalesTable, SalesTable)).cursor(); SalesTableExt salesTableExt = ds.cursor(); salesTableExt.SalesTable = salesTable.RecId; } }
Attribute
[ExtensionOf(formdataSourceStr(SalesTable, SalesTableExt))]defines the object where COC has to be performed.
In a similar way, we can use Coc on form controls. Only difference would be there on attribute for class, it would be like
[ExtensionOf(formControlStr(SalesTable, SalesTableExt_SalesTable))]
and if needed on form datasource control, then attribute would be like
[ExtensionOf(formDatasourceControlStr(SalesTable, SalesTableExt, SalesTable))]
Mind, this can be done only post Update 22.
Thanks!!! See you on next post......
-
This way of setting Multiselectlookup(Lookup that allows Multiple value selections) can be used on any module (SSRS, Service, Batch or simp...
-
Below method can be used to mark the Invoice amounts for settlement in very easy way. In your code to loop through ledgerjournaltrans(ass...
-
Hi folks, we all might have faced the situation where you needed to use the methods on datasources/controls of d365 standard forms. Its mu...