Book Image

Extending Microsoft Dynamics 365 Finance and Supply Chain Management Cookbook - Second Edition

By : Simon Buxton
Book Image

Extending Microsoft Dynamics 365 Finance and Supply Chain Management Cookbook - Second Edition

By: Simon Buxton

Overview of this book

Dynamics 365 Finance and Supply Chain Management is Microsoft’s ERP solution, which can be implemented as a cloud or on-premise solution to facilitate better decision-making with the help of contemporary, scalable ERP system tools. This book is updated with the latest features of Dynamics 365 Finance and Supply Chain Management including Chain of Command (CoC), Acceptance Test Libraries (ATL), and Business Events. The book not only features more than 100 tutorials that allow you to create and extend business solutions, but also addresses specific problems and offers solutions with insights into how they work. This cookbook starts by helping you set up a Azure DevOps project and taking you through the different data types and structures used to create tables. You will then gain an understanding of user interfaces, write extensible code, manage data entities, and even model Dynamics 365 ERP for security. As you advance, you’ll learn how to work with various in-built Dynamics frameworks such as SysOperation, SysTest, and Business Events. Finally, you’ll get to grips with automated build management and workflows for better application state management. By the end of this book, you’ll have become proficient in packaging and deploying end-to-end scalable solutions with Microsoft Dynamics 365 Finance and Supply Chain Management.
Table of Contents (17 chapters)

Creating a parameter table

A parameter table only contains one record per company. The table contains a list of fields, which can be defaults or company-specific options used in code to determine what should happen. The parameter table is usually created first, and the various fields that act as parameters are added as we create the solution.

This follows on directly from the Creating setup tables recipe.

How to do it...

To create the parameter table, follow these steps:

  1. Create a new table called ConVMSPararameters; again, the prefix and suffix are based on best practice. Usually, the name will only be <Prefix>+<Area - if required to scope table>+Parameters.
  2. Set the table parameters as follows:
Property Value
Label Vehicle management parameters
Title Field 1
Title Field 2
Cache Lookup EntireTable
Table Group Parameter

Created By, Created Date Time

Modified By, Modified Date Time

Yes

Developer Documentation

The ConVMSParameters table contains settings used within the vehicle management solution.
  1. Drag the ConVMSVehicleGroupId EDT onto the Fields node and rename it to DefaultVehicleGroupId.
  2. Drag the ParametersKey EDT from the Application Explorer to our Fields node.
  3. Rename it to Key and change the Visible property to No.
This is only used as a constraint to limit the table to only having one record. All visible fields need to be in a field group.
  1. Create a field group named Defaults and set the Label property. Use the label lookup (the ellipsis button) to locate a suitable label. Note that @SYS334126 is suitable in this case. As always, check the description of the label to understand its usage.
  2. Drag the DefaultVehicleGroupId field to the new Defaults field group.

We will use this on the parameter form so that it has the heading as Defaults. This is why we don't need to change the field's label to specify the context.
  1. Right-click on the Relations node, and select New | Foreign Key Relation. Rename the relation to ConVMSVehicleGroup.
  2. Complete the parameters as follows; if not specified, leave them as the default:

Property Value Description

Related Table

ConVMSVehicleGroup The table to which our foreign key relates.
Cardinality ZereOne There will be either one or no parameter record relating to the vehicle group record. A one-to-many relationship would use ZeroMore or OneMore.
Related Table Cardinality ZeroOne The value is not mandatory, so we can therefore relate to zero vehicle group records, or one.
Relationship Type Association The parameter record is associated with a vehicle record. Composition would be used in header/lines datasets, where deleting the header should delete the lines records.
On Delete Restricted This will prevent a vehicle group record from being deleted, if it is specified on this table. See the There's more section for more information on delete actions.
Role This is the role of the relation, and it must be unique within this table. We will need to specify this if we have two foreign key relations to the same table. For example, on the SalesTable table, this has two relations to CustTable as there are two fields that relate to this table. In that case, the Role will need to be populated in order to differentiate them.
  1. Right-click on the ConVMSVehicleGroup relation and choose New | Normal.
  2. In the Field property, specify the foreign key (the field in this table): DefaultVehicleGroupId.
  3. In the Related Field property, specify the key in the parent table: VehicleGroupId.
  4. Create a new index called KeyIdx and add the Key field to it. It is unique by default, so it acts as a constraint index.
  1. We can now create the Find and Exist methods. There is a difference for parameter tables, in that the Find method creates a record in a particular way. Create the Find method as shown in the following piece of code:
public static ConVMSParameters Find(boolean _forUpdate = false)
{
ConVMSParameters parm;
parm.selectForUpdate(_forUpdate);
select firstonly parm where parm.Key == 0;
if (!parm && !parm.istmp())
{
Company::createParameter(parm);
}
return parm;
}
  1. We will use a slightly different select statement where we can write the select statement inline, which means that we don't have to declare the type as a variable; write the Exist method as follows:
public static boolean Exist()
{
return (select firstonly RecId from ConVMSParameters).RecId != 0;
}
  1. We want to ensure that the record cannot be deleted. So, we will override the Delete method. Press Return at the start of the Find method to create a blank line at the top. Right-click on this blank line and choose Insert Override Method | validateDelete. Change the method so that it reads as follows:
public boolean validateDelete()
{
return checkFailed("@SYS23721"); //Cannot delete transaction
}
This is called to check whether the record can be deleted when the user tries to delete the record in the UI. We could also override the delete method should we wish to prevent the record from being deleted in the code. This is done by either commenting out the super() call or replacing it with throw error("@SYS23721").
  1. We set the Table Cache property to EntireTable. Whenever this table is updated, we will need to flush the cache so that the system uses the updated values. Override the update method as follows:

public void update()
{
super();
flush ConVMSParameters;
}

This tells SCM to write the record buffer with the super() call and then flush the cache in order to force the system to read it from the database when it is next read.

There's more...

The build operation will validate and compile the package into a Dynamic Link Library (DLL). This must be done before we synchronize the database. This can fail, and at this stage, it is normally due to missing references. Within the Application Explorer, each element shows the package to which it belongs. We must ensure that our model references all types that we use within our project. If we don't, we will get build errors like this:

To add the required references, we can follow these steps:

  1. Locate the type with the error in Application Explorer.
  2. Note the package it is in, which is in square brackets.
  3. Navigate to Dynamics 365 | Model Management | Update model parameters....
  4. Select the ConVehicleManagement model.
  5. Click on Next.
  6. Check if the required package is checked, and then press Next.
We normally reference the ApplicationPlatform, ApplicationFoundation, and ApplicationSuite packages, as we often use elements from these packages.
  1. Press Finish.
  2. Navigate to Dynamics 365 | Model Management and select Refresh models.
  3. Try the build operation again; you may need to repeat this as one error can mask another.

Capitalization of method names

You may notice that the Find method started with a capital letter, yet the overridden methods did not. When SCM was first released as AX 7, new methods were created in the same way as with C# and started with a capital letter. Existing methods were then refactored. There is no direct advice as yet as to whether to capitalize the first letter of a method, although it is my preference as it helps to more easily differentiate between methods and public variables when using IntelliSense. So the reason I choose to do this is for readability and because new methods in SCM were created with the first letter capitalized.

Whichever route you take, it is important to be consistent. When overriding methods or implementing a method from an interface, it is critical. In this case, if you implement a method from an interface and change the casing, this will build without error, but will fail at runtime. You may find strange behavior even when overriding methods if the case is different. This is not just the first letter, of course, so using IntelliSense is a much safer way to override methods.

Copying and pasting methods to save time

Be careful when copying the Find and Exist methods to other tables as a template. As they are static, the methods can technically be on any class or table—that is, check the return type. This can cause some confusion when they behave strangely. As EDTs can be used interchangeably, we won't get a type error unless the base type of the EDT is different. This means that you could pass a variable of the ConVMSVehicleGroupId type to InventItemGroup::Find() and it would simply return a record (or empty buffer) of the InventItemGroup type. So, if we copied the Find method from InventItemGroup to our table, the following scenarios would be possible:

Code Result
ConVMSVehicleGroup group = CustGroup::find(_VehGroupId); This would cause a compilation error, as you can't assign an object of the CustGroup type to the ConVMSVehicleGroup type.
return CustGroup::find(_vehGroupId).Name; This would compile without error as the compiler only checks that the base type is correct. ConVMSVehicleGroupId and CustGroupId are both strings. It will just not behave as expected and will return an empty string as the customer group record will not be found: records are never null.

Optimistic concurrency and selectForUpdate

There are several system fields that are always created on all tables. One of which is RecVersion, which is used by Optimistic Concurrency (OCC). Optimistic concurrency is enabled by default on new tables. We select a record "for update" by adding a forUpdate clause to a select or while select statement, or by using the selectForUpdate(true) method that exists on all tables.

When we select a record for update, a physical lock is not placed on the record, and it is therefore possible for two different processes to select the same record for update.

As the record or records are read, they are read from the database into an internal record buffer. When the record is written back, it will check that the value of the RecVersion field in the physical table is the same as when the record was fetched into the internal buffer.

If RecVersion is different, an exception will be thrown. If this is thrown whilst editing data, the user is given a message that the record has changed and is asked to refresh the data. If the error is thrown within code, we will get an update conflict exception that can be caught. Should the update succeed, the RecVersion field will be changed to a different number.

If we are using OCC, we can make the call to selectForUpdate() even after the record has been fetched from the database. This is because it does not lock the selected records but states intent that we wish to do so.

See also