AL, Business Central, Codeunit, Dataverse, Events, Extension, Integration, Procedures, Subscription, VS Code

Create Integration Codeunit for Business Central & Dataverse Integration

This is the Seventh post in the series. If you want to go to previous post click here.

From the series of steps this post is dedicated to Step-6:

As a Sixth Step we will Create Integration Codeunit in Business Central

codeunit 50120 CDSDataverseEvent
{
}

Add these Procedures

local procedure LookupCDSProspect(SavedCRMId: Guid; var CRMId: Guid; IntTableFilter: Text): Boolean
    var
        CDSProspect: Record "CDS cr95d_Prospects";
        OriginalCDSProspect: Record "CDS cr95d_Prospects";
        OriginalCDSProspectList: Page "CDS Prospect List";
    begin
        if not IsNullGuid(CRMId) then begin
            if CDSProspect.Get(CRMId) then
                OriginalCDSProspectList.SetRecord(CDSProspect);
            if not IsNullGuid(SavedCRMId) then
                if OriginalCDSProspect.Get(SavedCRMId) then
                    OriginalCDSProspectList.SetCurrentlyCoupledCDSProspect(OriginalCDSProspect);
        end;

        CDSProspect.SetView(IntTableFilter);
        OriginalCDSProspectList.SetTableView(CDSProspect);
        OriginalCDSProspectList.LookupMode(true);
        if OriginalCDSProspectList.RunModal = ACTION::LookupOK then begin
            OriginalCDSProspectList.GetRecord(CDSProspect);
            CRMId := CDSProspect.cr95d_ProspectsId;
            exit(true);
        end;
        exit(false);
    end;

local procedure AddEntityTableMapping(CRMEntityTypeName: Text; TableID: Integer; var TempNameValueBuffer: Record "Name/Value Buffer" temporary)
    begin
        TempNameValueBuffer.Init();
        TempNameValueBuffer.ID := TempNameValueBuffer.Count + 1;
        TempNameValueBuffer.Name := CopyStr(CRMEntityTypeName, 1, MaxStrLen(TempNameValueBuffer.Name));
        TempNameValueBuffer.Value := Format(TableID);
        TempNameValueBuffer.Insert();
    end;

local procedure InsertIntegrationTableMapping(var IntegrationTableMapping: Record "Integration Table Mapping"; MappingName: Code[20]; TableNo: Integer; IntegrationTableNo: Integer; IntegrationTableUIDFieldNo: Integer; IntegrationTableModifiedFieldNo: Integer; TableConfigTemplateCode: Code[10]; IntegrationTableConfigTemplateCode: Code[10]; SynchOnlyCoupledRecords: Boolean)
    begin
        IntegrationTableMapping.CreateRecord(MappingName, TableNo, IntegrationTableNo, IntegrationTableUIDFieldNo, IntegrationTableModifiedFieldNo, TableConfigTemplateCode, IntegrationTableConfigTemplateCode, SynchOnlyCoupledRecords, IntegrationTableMapping.Direction::Bidirectional, 'CDS');
    end;

procedure InsertIntegrationFieldMapping(IntegrationTableMappingName: Code[20]; TableFieldNo: Integer; IntegrationTableFieldNo: Integer; SynchDirection: Option; ConstValue: Text; ValidateField: Boolean; ValidateIntegrationTableField: Boolean)
    var
        IntegrationFieldMapping: Record "Integration Field Mapping";
    begin
        IntegrationFieldMapping.CreateRecord(IntegrationTableMappingName, TableFieldNo, IntegrationTableFieldNo, SynchDirection,
            ConstValue, ValidateField, ValidateIntegrationTableField);
    end;

To Add Event Subscriptions, Use the new Shift+Alt+E shortcut in the AL code editor to invoke a list of all events.

Search for Event OnGetCDSTableNo“CRM Setup Defaults”

[EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Setup Defaults", 'OnGetCDSTableNo', '', false, false)]

local procedure OnGetCDSTableNo(BCTableNo: Integer; var CDSTableNo: Integer; var handled: Boolean);
    begin
        if BCTableNo = DATABASE::"Prospect" then begin
            CDSTableNo := DATABASE::"CDS cr95d_Prospects";
            handled := true;
        end;
    end;

Search for Event OnLookupCRMTables“Lookup CRM Tables”

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Lookup CRM Tables", 'OnLookupCRMTables', '', false, false)]

local procedure OnLookupCRMTables(CRMTableID: Integer; NAVTableId: Integer; SavedCRMId: Guid; var CRMId: Guid; IntTableFilter: Text; var Handled: Boolean);
    begin
        if CRMTableID = Database::"CDS cr95d_Prospects" then
            Handled := LookupCDSProspect(SavedCRMId, CRMId, IntTableFilter);
    end;

Search for Event OnAddEntityTableMapping“CRM Setup Defaults”

[EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Setup Defaults", 'OnAddEntityTableMapping', '', false, false)]

local procedure OnAddEntityTableMapping(var TempNameValueBuffer: Record "Name/Value Buffer");
    begin
        AddEntityTableMapping('Prospect', DATABASE::"CDS cr95d_Prospects", TempNameValueBuffer);
    end;

Search for Event OnAfterResetConfiguration “CDS Setup Defaults”

[EventSubscriber(ObjectType::Codeunit, Codeunit::"CDS Setup Defaults", 'OnAfterResetConfiguration', '', false, false)]

local procedure OnAfterResetConfiguration(CDSConnectionSetup: Record "CDS Connection Setup");
    var
        IntegrationTableMapping: Record "Integration Table Mapping";
        IntegrationFieldMapping: Record "Integration Field Mapping";
        CDSProspect: Record "CDS cr95d_Prospects";
        Prospect: Record "Prospect";
    begin
        InsertIntegrationTableMapping(
            IntegrationTableMapping, 'Prospect',
            DATABASE::"Prospect", DATABASE::"CDS cr95d_Prospects", CDSProspect.FieldNo(cr95d_ProspectsId), CDSProspect.FieldNo(ModifiedOn), '', '', true);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo("No."), CDSProspect.FieldNo(cr95d_ProspectsId), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo(Name), CDSProspect.FieldNo(cr95d_ProspectName), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo(Probability), CDSProspect.FieldNo(cr95d_Probability), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo("Contract Amount"), CDSProspect.FieldNo(cr95d_ContractAmount), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo("Contract Amount (Base)"), CDSProspect.FieldNo(cr95d_contractamount_Base), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo(Stage), CDSProspect.FieldNo(cr95d_Stage), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo("Forecast Revenue"), CDSProspect.FieldNo(cr95d_ForcastedRevenue), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo("Forecast Revenue (Base)"), CDSProspect.FieldNo(cr95d_forcastedrevenue_Base), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);
    end;

Now you are good to proceed with Next Step.

You can jump to Next Step from here.

Advertisement
AL, BC18, Business Central, Code, Codeunit, Combinations, Development Tips, Dimension, Dynamics 365, Environment, Events, Extension, Extension Package, How To, Online, Page, Sandbox, Subscription, Table, Tip & Tricks, VS Code

Walkthrough Extension Development in Online Sandbox – Business Central

I have my Sandbox environment as below:

Details of the Sandbox as below:

Connecting VS code to above environment.

When you try to Publish the extension, it will ask you to authenticate.

Copy the Link and open in the browser and paste the code in the box as shown below.

Next it will ask for your Online Instance User Id & Password provide it and on confirmation close the page. It will start deploying the extension.

In this walkthrough I am using below scenario:

Requirement is we need to be able to define some code for dimension combinations. Let me say it will be Sales Code, you can choose name of your choice, this is not Salesperson code.

I am assuming these dimensions will follow the sequence as defined on my General Ledger Setup as below:

On Sales Order & Invoice user should be able to select this Sales Code and dimensions should be populated on order accordingly.

For tracking purpose this Sales Code should flow to Posted Sales Invoice and Customer Ledger.

So, Let’s Start with the development process:

Step-1 We will Create the Table

Here is the code for LookupDimValue Function, it will set filter for Dimension Code on Dimension Value table, as per the Dimension No passed. (1 is for Shortcut Dimension defined on General Ledger Setup, similarly for other 8 dimensions)

Step-2 Next, we will create the Page for this Setup

Step-3 Next, we will add the Sales Code field to all required Tables & Pages

Here is the code for AddDim Function. It is assumed that only combination provided in Sales Code Setup will be used. If you have defined Default dimensions or Combinations, those need to be preserved else this code will overwrite them. You will have to find the Data Set Entry, store them in temporary table used in below code and then add all the dimensions from the setup.

When you Select Sales Code on the Order or Invoice it will populate all the dimensions defined in the Setup.

Code for other Tables & Pages

Step-4 Next, we need to take care to flow the Sales Code to the Ledger and Posted documents.

For posted documents we need not to worry it will flow automatically provided we have defined the fields on same Id.

However, for ledger we will require to use Events to pass the data to their destinations. In this case we are only passing to Customer Ledger Entry. For this we will create a Codeunit.

To Add Event Subscriptions, Use the new Shift+Alt+E shortcut in the AL code editor to invoke a list of all events.

Search for the even you are looking for.

When pressing Enter to select an event entry, an event subscriber for the event will be inserted at the cursor position in the active AL code editor window.

Here is the Codeunit Code:

This is not the final code; much more can be done or need to be done before it can be delivered to customer for their use. Purpose of this walkthrough was to demo the way we can customize the solution using extensions and publishing to Online tenant in Sandbox.

Hope you enjoyed the information. Will come with more similar information in my next posts. Till then keep exploring, learning and sharing your knowledge with others.

Remain safe, take care of your loved ones, put your mask, maintain safe distance and don’t forget to get vaccinated.

AL, API, Application, AppVersion, BC14, BC17, Business Central, Code, Codeunit, DataVersion, Dependencies, Development Tips, Dynamics 365, Extension, GetCurrentModuleInfo, GetModuleInfo, How To, Information, Instalation & Configuration, Install, Name, on-premises, OnInstallAppPerCompany, Publisher, Tip & Tricks, Version.Create, Visual Studio Code, Wave 1, Wave 2

Extension Install Code

Extension install code is executed when:

  • An extension is installed for the first time.
  • An uninstalled version is installed again.

This gives you control to write different logics for first time installation of extension and reinstallations of uninstalled extensions.

This is achieved by defining Install Codeunit in your Extension.

First thing first:

  1. Subtype property of codeunit need to be set to Install
  2. OnInstallAppPerCompany trigger is triggered when the Extension is Installed first time or subsequent install of same version on Extension.
  3. DataVersion property one of the important properties which tells you what version of data that you’re dealing with.
  4. AppVersionDataVersionDependenciesIDName, and Publisher. These properties are encapsulated in a ModuleInfo data type. You can access these properties by using the NavApp.GetCurrentModuleInfo and NavApp.GetModuleInfo methods.
  5. If the DataVersion property equals Version.Create(0,0,0,0), then it’s the first time that the extension is installed because no data exists in the archive.

Sample codeunit can be similar to below:

codeunit <ID> “Name of Codeunit

{

    // Install Logic

    Subtype = Install;

    trigger OnInstallAppPerCompany();

    var

        myAppInfo: ModuleInfo;

    begin

        NavApp.GetCurrentModuleInfo(myAppInfo);

// Get info about the currently executing module

        if myAppInfo.DataVersion = Version.Create(0, 0, 0, 0) then

// A ‘DataVersion’ of 0.0.0.0 indicates a ‘fresh/new’ install

            HandleFreshInstall

        else

            HandleReinstall;

// If not a fresh install, then we are Reinstalling the same version of the extension

    end;

    local procedure HandleFreshInstall();

    begin

        // Logic to execute on first time this extension is ever installed for this tenant.

        // Some possible usages: – Initial data setup for use

    end;

    local procedure HandleReinstall();

    begin

        // Logic to execute on reinstalling the same version of this extension back on this tenant.

        // Some possible usages: – Data ‘patchup’ work, for example, detecting if new ‘base’

// records have been changed while you have been working ‘offline’.

    end;

}

Happy Learning.

AL, Assisted Setup, Business Central, Development Tips, Extension Package, How To, Information, Tip & Tricks, V2, Visual Studio Code

Creating Assisted Setup in AL for Business Central

Today we will see how we can add our Wizard Page to Assisted Setup.

Continuing from previous post this is the second part as both are inter related.

If you have not gone through earlier post see here how to create Wizard Page for Business Central. Creating a Wizard Page in AL for Business Central

Let’s have some overview why we need assisted setup:

What is Assisted Setup?

  • List of setup scenarios, presented in list form.
  • Uses Wizard Page with relevant options.
  • Display Status of activity, completed or incomplete.
  • Predefined set of setup scenarios. Like E-Mail Setup, Cash Flow Forecast, Approval Workflows, Connection to other entities like CRM connection etc.
  • Possible to add new custom setup scenarios.

AS-1

Today we will see how we can add our Wizard Page for Company Information Setup into this List, and how the Status is updated in this list.

So further not going into theory let’s jump to practical Approach.

To add a New Assisted Setup:

  • 2 tables are involved Assisted Setup & Aggregated Assisted Setup.
  • Need to Subscribe to Event OnRegisteredAssistedSetup and call AddExtensionAssistedSetup.
  • Wizard should update the status in the Assisted Setup table.
  • Subscribe to Event OnUpdateAssistedSetupStatus to store the updated status.
  • Alternatively can determine actual status based on data.

Steps:

Create New Codeunit on available ID and unique name.

Code will be similar to below:

AS-2

Save the file and publish your Extension.

Now you should be able to locate your Wizard as entry ‘Setup Company Information’

AS-3

Click to Run your Company Information Setup Wizard.

AS-4

Complete your Setup by entering information and Click Finish.

AS-5

If you have completed all the step and provided information, your Status should show Completed.

You may need to re-open the Assisted Setup page.

It was just to give you an idea how we can get this done.

You will require to perform other some more steps to get it functioning smooth.

Sometime later will come up with more details.

Explore the existing Setups and can get more insight on the same.

I have given you the start explore learn and reach to conclusion.

See you again with some other topic in my upcoming post. Till then keep exploring and keep learning.

License, NAV 2018, Tip & Tricks

Microsoft Dynamics NAV 2018 Licenses

You have the choice of purchasing Microsoft Dynamics NAV licenses up front, or paying a monthly fee to a service provider.

Perpetual Licensing:

  • You pay affordable upfront starting price, rapid start tools and built in functionality.
  • You license the ERP Solution functionality, and access to that functionality is secured by licensing users.

 

Perpetual License Overview

Service Provider’s Subscription Licensing:

  • You have choice to keep the upfront cost to a minimum through a “per user per month” licensing fee.
  • This helps get started with a low initial cost while leveraging the built-in functionality and rapid start tools.

 

In both licensing models

You have the choice of two Concurrent-user types –

  1. Limited User (Full Read & Limited Write)
  2. Full User (Full Read & Full Write)

Option to give those users access to advanced functionality through the “Extended Pack.”

Starter and Extended Pack License Overview

Starter Pack (Base Pack – Required)

The Starter Pack offers for one price.

  • Core Financials
  • Distributions
  • Professional Services functionality
  • 3 Full User licenses/ Concurrent CALs included
  • Additional Software licenses may be required, like Server, SQL, Office 365, SharePoint. Additional Software must be licensed according to the applicable license terms.

For many businesses, this is the only license component required.

 

Extended Pack (Optional)

The Extended Pack enables customers to integrate core financials and distribution management with broader functionality extensions such as:

  • Manufacturing to support and control the manufacturing environment
  • Warehousing to manage the warehouse to support operations
  • The first three Full Users included in the Start Pack and coming users get to access to all of the incremental functionality.
  • Starter Pack is the prerequisite to the Extended Pack

 

Packaging of Functionality

A Microsoft Dynamics NAV customer can choose whether to deploy the Starter and Extended Pack through the

  • Microsoft Windows client for Microsoft Dynamics NAV
  • Web client for Microsoft Dynamics NAV
  • Microsoft Dynamics NAV Portal framework for Microsoft SharePoint (also known as the Microsoft Dynamics NAV SharePoint client)

All through the same User types.

2018 Functionality -1

 

2018 Functionality -2

Starter Pack is for companies who need

  • Core Financials
  • Trade functionality
  • Basic Financials Management (General Ledger and Fixed Assets)
  • Basic Supply Chain Management
  • Basic Sales Management (Sales, Purchasing and Inventory)
  • Professional Services (Project management)
  • A broad set of Business Insight and reporting functionality as an integral part of the product.
  • A wide range of tools to customize the solution, to meet the needs of every customer together with deep integration opportunities to be made through web services.

 

Extended Pack is for growing, midmarket, or high-functional-needs companies who are looking for an adaptive solution with a broad set of functionality:

  • Warehousing
  • Manufacturing
  • It comes with additional customization objects for doing more extensive customizations.

 

CONFIGURATION AND DEVELOPMENT

Page Designer

  • Change existing pages and enables you to create 100 page objects (included and numbered from 50,000 to 50,099).
  • The Page Designer also enables you to use the Navigation Pane Designer. This means, for example, that you can create new menu items.
  • This module does not include access to C/AL from pages.

Report Designer

  • Change existing reports and create 100 new report objects (numbered from 50,000 to 50,099).
  • This module provides access to C/AL (the C/SIDE application language) from reports used for defining special calculations and business rules.
  • Create new reports from scratch or copy an existing report to use as a starting point.
  • This module enables you to take advantage of the functionality included for developers in the Navigation Pane Designer (for example, creating new menu items).

Table Designer

  • Change existing table definitions and create ten new tables (numbered from 50,000 to 50,009).
  • You can change properties on fields, such as the field name, decimal places, and maximum value, add new fields to existing tables, and create new tables to store data specific to your business.
  • Create keys for sorting information and change or create new FlowFields and FlowFilters for “slicing and dicing” information in new ways.
  • This module does not include access to C/AL from tables.

XMLports (100)

  • Create new or change existing XMLport objects.
  • XMLport Designer provides access to C/AL (the C/SIDE application language) from XMLports used for defining special calculations and business rules.
  • Every XMLport object in Microsoft Dynamics NAV is created using this tool and can therefore be customized easily.
  • This module enables you to create 100 new XMLport objects (numbered from 50,000 to 50,099) and to take advantage of the functionality included for developers in the
  • Navigation Pane Designer (for example, creating new menu items).

Query Designer

  • The Query Designer provides the ability to modify existing queries in the application, as well as create up to 100 new queries.
  • The Query Designer is the main tool for creating objects of the type Query.
  • Query objects retrieve subsets of data spread across the database and are data pumps for various places within the application such as charts and business logic.

 

Application Builder (A la carte)

  • Change the business rules and special calculations that work behind the scenes. Business rules and special calculations are defined in C/AL (the C/SIDE application language).
  • Although this granule includes access to C/AL, it does not permit access to existing C/AL code that updates write-protected tables (for example, postings).
  • With Application Builder, you can create entirely new areas of functionality for your application, enabling you to tailor Microsoft Dynamics NAV to fit your entire organization.
  • It also enables you to create 100 codeunit objects (numbered from 50,000 to 50,099).
  • You can also take advantage of the functionality included for developers in the Navigation Pane Designer (for example, creating new menu items).

 

Solution Developer (A la carte)

  • Change the business rules and special calculations that work behind the scenes. Business rules and special calculations are defined in C/AL (the C/SIDE application language).
  • This module provides access to code that updates write-protected tables to the Merge and Upgrade Tools.
  • You can also Change or create any object type, Use the menu options Translate/Export and Translate/ Import in the Object Designer.

(These options are not available with the Application Builder module).

 

Application Objects

Codeunits (100)

  • 10 Codeunits are included in the Starter Pack
  • 10 Codeunits are included in the Extended Pack.
  • Additional groups of 100 are available in the Customization suite.

Reports (100)

  • Additional groups of 100 are available in the Customization suite.

Tables (10)

  • 10 tables are included with the Extended Pack.
  • Additional groups of 10 are available in the Customization suite.

XMLports (100)

  • 100 XMLports are included with the Extended Pack.
  • Additional groups of 100 are available in the Customization suite.

Queries (100)

  • Additional groups of 100 are available in the Customization suite.

Pages (100)

  • 100 Pages are included with the Extended Pack.
  • Additional groups of 100 are available in the Customization suite.

 

For more details you can check here.

 

 

 

 

Development Tips, Excel, Instalation & Configuration, Jet Reports, Office Integration, Report

Publishing the Jet Data Source Codeunit to the Web Service

Microsoft Dynamics NAV 2015 includes a “Jet Data Source” codeunit which must be published to enable Jet Essentials or Jet Express to operate. This can be published using the Microsoft Dynamics NAV Role Tailored Client (RTC).

To publish this code unit, inside the RTC go to Departments > Administration > IT Administration > General and select Web Services.

JetExcel-4
Publish the Jet Data Source code unit by selecting New from the Web services ribbon

  • Object Type: Codeunit
  • Object ID: 14125500
  • Service Name: Jet Data Source
  • And check the box to publish.
  • Click OK

Checkout Upcoming posts for more information.

Development Tips

Adding a Test to a Test Runner Codeunit

How to add a line to test runner codeunit that runs the new TestVendorDiscount codeunit.

The test runner codeunit runs all the test codeunits that you may have created to test the other customized functionality.

To add a test to a test runner codeunit

  • In the development environment, on the Tools menu, choose Object Designer.
  • In Object Designer, choose Codeunit, select the existing test runner codeunit, and then choose the Design button.
  • In the C/AL Editor, in the OnRun function, add the following code.

CODEUNIT.RUN(CODEUNIT::TestVendorDiscount);

  • On the File menu, choose Save.

For more details see below posts:

Creating a Test Codeunit and Test Function
Creating a Test Runner Codeunit

Development Tips

Creating a Test Runner Codeunit

Follow below Steps to Create Test Runner

  • In the development environment, on the Tools menu, choose Object Designer.
  • In Object Designer, choose Codeunit, and then choose New.
  • On the View menu, choose Properties.
  • In the Properties window, in the Subtype field, select TestRunner to specify that this is a test runner codeunit.
  • In the TestIsolation field, specify which changes to that you want to roll back. You can choose from the following values:
    • Disabled
    • Codeunit
    • Function

TestCu-5

Value Description
Disabled Do not roll back any changes to the database. Tests are not isolated from each other.This is the default value.
Codeunit Roll back all changes to the database after each test codeunit executes.
Function Roll back all changes to the database after each test function executes.

It is recommend that you design tests to be independent of each other. Tests might read from and write to the same database, which means that tests can interact with each other.

If tests interact, then you may experience incorrect test results.

To eliminate test interactions, use the TestIsolation property to roll back changes to the database after each test function or after each test codeunit.

If you specify that you want to roll back database changes, then all database changes are rolled back, including changes that were explicitly committed to the database during the test by using the COMMIT function.

  • In the C/AL Editor, in the OnRun function, enter code to run the test codeunits. For example, the following code in the OnRun function of a test runner codeunit runs test codeunits.

CODEUNIT.RUN(CODEUNIT::TestVendorDiscount);

Note
You may want to define your test suite in a table and then write code in the OnRun function of the test runner codeunit to iterate through items in the table and run each test codeunit.
  • (optional) To create an OnBeforeTestRun trigger, do the following steps:
    • On the View menu, choose C/AL Globals.
    • In the C/AL Globals window, on the Functions tab, on a new line in the Name field, enter OnBeforeTestRun, and then choose Locals.
    • In the C/AL Locals window, on the Parameters tab, enter the following.

TestCu-6

    • In the C/AL Locals window, on the Return Value tab, enter the following

TestCu-7

    • Close the C/AL Locals window.
    • In the C/AL Editor, in the OnBeforeTestRun trigger, enter code that executes before each test function. Typically, the code in the OnBeforeTestRun function determines if the test function should execute and returns true if it should. Otherwise, the trigger returns false. The OnBeforeTestRun trigger may also initialize logging variables. For example, the following code initializes a logging variable and returns true to indicate that the test function should execute. This example requires that you create the following global variable.

TestCu-8

  • (optional) Do the following steps to create an OnAfterTestRun trigger:
    • On the View menu, choose C/AL Globals.
    • In the C/AL Globals window, on the Functions tab, on a new line in the Name field, enter OnAfterTestRun, and then choose Locals.
    • In the C/AL Locals window, on the Parameters tab, enter the following.

TestCu-9

    • Close the C/AL Locals window.
    • In the C/AL Editor, in the OnAfterTestRun trigger, enter code that executes after each test function. For example, the following code logs the results of the tests to the test reporting system. This example requires that you create a record variable named log.

TestCu-10

TestCu-11

log.INIT;

log.UnitId := CodeunitId;

log.Unit := CodeunitName;

log.Func := FunctionName;

log.Before := Before;

log.After := CURRENTDATETIME;

If Success THEN

log.Status := log.Status::Success

ELSE BEGIN

log.Status := log.Status::Failure;

IF FunctionName <> ” THEN

log.Message := GETLASTERRORTEXT;

END

log.INSERT(true);

Note

If you implement the OnAfterTestRun trigger, then it suppresses the automatic display of the results message after the test codeunit runs.

  • On the File menu, choose Save.
  • In the Save As window, in the ID field, enter an ID and in the Name field, enter a name for the codeunit. Verify that the Compiled check box is selected, and then choose OK.

For more details see also below posts:

Creating a Test Codeunit and Test Function
Adding a Test to a Test Runner Codeunit

Development Tips

Creating a Test Codeunit and Test Function

Let’s creates the test function, which tests the Purch-Calc.Discount functionality, to this test codeunit.

To create the test codeunit and test function

  • In the development environment, on the Tools menu, choose Object Designer.
  • In Object Designer, choose Codeunit, and then choose New.
  • On the View menu, choose Properties.
  • In the Properties window, in the Subtype field, select Test to specify that this is a test codeunit.
  • On the View menu, choose C/AL Globals.
  • In the C/AL Globals window, on the Functions tab, enter CalculateVendorDiscount. This is the name of the test function.

TestCU-1

Note
By default, functions that you add to test codeunits have the FunctionType property set to Test.
  • On the Functions tab, choose Locals.
  • In the C/AL Locals window, on the Variables tab, enter the following variables, which you will use in the CalculateVendorDiscount test function.

TestCU-2

Important

Be sure to create these variables on the Variables tab, not on the Parameters tab. If you create them on the Parameters tab, then you get an error when you compile that says the test method signature is invalid

  • In the C/AL Locals window, on the Text Constants tab, in the Name field, enter VendorDiscountError. In the ConstValue field, enter Vendor Discount Error – Line Amount: %1, Discount: %2, Discount Amount: %3.

TestCU-3

  • Close the C/AL Locals window and the C/AL Globals window.
  • In the C/AL Editor, in the CalculateVendorDiscount function, enter the following code.

TestCU-4

You can copy above code from below:

Discount := RANDOM(99) + 1; // Set Discount > 0, <= 100

// Find purchase line.

PurchaseLine.SETFILTER(“Line Amount”, ‘>0’);

PurchaseLine.SETFILTER(“Allow Invoice Disc.”, ‘=%1’, TRUE);

IF NOT (PurchaseLine.FINDFIRST) THEN

ERROR(‘No Purchase Line found for the Calculate Vendor Discount test’);

// Create vendor discount.

WITH PurchaseLine DO BEGIN

IF NOT (VendorDiscount.GET(“Buy-from Vendor No.”, “Currency Code”, “Line Amount”)) THEN BEGIN

VendorDiscount.INIT;

VendorDiscount.Code := “Buy-from Vendor No.”;

VendorDiscount.VALIDATE(“Currency Code”,”Currency Code”);

VendorDiscount.VALIDATE(“Minimum Amount”,”Line Amount”);

VendorDiscount.INSERT(TRUE);

END;

END;

VendorDiscount.VALIDATE(“Discount %”, Discount);

VendorDiscount.MODIFY(TRUE);

// Run codeunit “Purch.-Calc.Discount” for calculating discount.

PurchCalcDisc.RUN(PurchaseLine);

PurchaseLine.GET(PurchaseLine.”Document Type”,PurchaseLine.”Document No.”,PurchaseLine.”Line No.”);

// Validate purchase discount amount

WITH PurchaseLine DO BEGIN

IF NOT (ROUND(“Line Amount” * Discount / 100) = “Inv. Discount Amount”) THEN

ERROR(VendorDiscountError, “Line Amount”, Discount, “Inv. Discount Amount” );

END;

The code in this test function prepares the test data by setting a random discount amount, getting a record from the Purchase Line table that satisfies two filters, and creating a record in the Vendor Invoice Disc. table with the random discount amount.

Next, it runs the Purch-Calc.Discount codeunit, which contains the code that is being tested. Finally, it validates the results of running the Purch-Calc.Discount codeunit and raises an error if the results are not as expected.

Note

This test code does not guarantee that the state of the database after you run the test is the same as the state of the database before you run the test.

  • On the File menu, choose Save.
  • In the Save As window, in the ID field, enter ID. In the Name field, enter TestVendorDiscount. Verify that the Compiled check box is selected, and then choose the OK button.

Now create additional test functions in the TestVendorDiscount test codeunit to test other aspects of vendor discounts.

These test functions should include negative tests, which validate that the code being tested works as intended under failing conditions.

For more details See below posts:

Creating a Test Runner Codeunit
Adding a Test to a Test Runner Codeunit

Development Tips

Data Upgrade Codeunit in Navision 2015 – Part -2

We have seen how to create Upgrade code unit in our previous post.

Please go through it, if not seen earlier the Data Upgrade Codeunit in Navision 2015 – Part -1 here 

Let us continue from where we left our previous post.

We have created the Upgrade code unit to copy the data to upgrade table while performing destructive changes to My Sales Customer table.

We removed certain fields from the My Sales Customer table and the related information was moved to UPG My Sales Customer table.

Now it’s time to move these information to actual table we create for this purpose My Detailed Customer Entry table.

Although in this example this table will be exact copy of UPG My Sales Customer table, so simply we need to copy records to My Detailed Customer Entry table.

But some time we may require to add certain more field’s value while moving the data Like Entry No, Posting date to maintain the data integrity. Like think of some Service type of information you are maintaining then the same set of record may repeat number of times on different dates, then in this case we will require to add certain fields like Entry No. and Posting Dates.

Let us assume we will be maintain data of My Detailed Customer Entry in incremental way as of situation on every transaction. Balances will be maintained as of when transaction was performed.

Although this example is not feasible or practice but still we are continuing to just see how to write Upgrade Function for such purpose.

Our today’s task is to design and run data upgrade function to move data from the upgrade table to its new location.

Now the fields are deleted from the My Sales Customer table and the data is saved in the UPG My Sales table. The next step is to create the new table where the data will reside (My Detailed Customer Entry) and move the data into that table.

Let’s create the table to be used for recording of all future Customer Balances and to accommodate the data we already had in the My Sales Customer table and which we moved into the UPG My Customer table.

  • In the Object Designer select the Tables button and then select New button to create a new table
  • Define the table layout based on the layout shown in the Screenshot.

Upgrade Codeunit 2 - 1

  • In the Dev. Environment menu, select File-Save action to save the table.
  • On the Save dialogue, define ID, Name = My Detailed Customer Entry, and select OK to save the table.
  • Close the table editor.

We will now create a new function which will contain the code to migrate the data from the upgrade table to the new table.

Note that the FunctionType property of the newly created function is set to “Upgrade”. This is how the NAV server can later figure out which function from the upgrade codeunit it must execute.

The function marked as “Upgrade” function can call functions marked as “Normal” if necessary.

  • In the Object Designer select the Codeunits button, select codeunit 50000 UPGTK Codeunit and then select the Design button.
  • In the Dev. Environment menu, select View-C/AL Globals action and in the form which is opened select the Functions tab.
  • Enter the name of the function, e.g. UpgradeCustomerDetails

Upgrade Codeunit 2 - 2

  • Select the UpgradeCustomerDetails function on the Functions tab and then select the Locals button to define function parameters.
  • On the C/AL Locals form, select Variables tab and add 3 local variables as shown in the Screenshot section.
  • Close the C/AL Locals and C/AL Globals forms to return to the C/AL Editor.

Upgrade Codeunit 2 - 3

The code we add to the UpgradeCustomerDetails function will copy the data from the UPG My Sales Customer table to the My Detailed Customer Entry table and then clean up the UPG Vehicle table.

  • Code like the Screenshot into the UpgradeCustomerDetails function.

//>> Upgrade Code

EntryNo := 1;

WITH UpgCustomer DO BEGIN

IF FINDSET THEN

REPEAT

DetailedCustomerEntry.INIT;

DetailedCustomerEntry.”Entry No.” := EntryNo;

DetailedCustomerEntry.”Customer No.” := “Customer No.”;

DetailedCustomerEntry.”As of Date” := today;

DetailedCustomerEntry.”Last SO Value” := “Last SO Value”;

DetailedCustomerEntry.”Last Invoiced Value” := “Last Invoiced Value”;

DetailedCustomerEntry.”Total Outstanding Value” := “Total Outstanding Value”;

DetailedCustomerEntry.INSERT;

EntryNo := EntryNo + 1;

UNTIL NEXT = 0;

DELETEALL;

END;

//<<Upgrade Code

  • In the Dev. Environment menu, select File-Save action to save the codeunit.
  • On the Save dialogue select OK button.
  • Close the codeunit editor.

Upgrade Codeunit 2 - 4

  • In the Dev. Environment menu select Tools – Data Upgrade – Start… action.
  • Agree to the confirmation dialogue.
  • In the Start Data Upgrade window select the OK button.

You can start the data upgrade process from the Dev. Environment, or from Microsoft Dynamics NAV 2015 Administration Shell, using Start-NAVDataUpgrade cmdlet.

The NAV server will locate all upgrade codeunits and will invoke all upgrade functions within these codeunits for all companies in the database.

If you have multiple upgrade functions in your upgrade codeunit, you can choose to execute them in parallel or in sequence. You can also choose to stop at first error, or continue execution of other functions, see the list of all failed ones, fix them and start them again using the Data Upgrade – Resume… action.

  • In the Dev. Environment menu select Tools – Data Upgrade – Start… action.
  • Agree to the confirmation dialogue.
  • In the Start Data Upgrade window select the OK button.

Upgrade Codeunit 2 - 5

Upgrade Codeunit 2 - 6

  • In the Object Designer select the Tables button, then select table My Detailed Customer Entry and select Run button

Upgrade Codeunit 2 - 7

You can see that the data from the upgrade table was successfully transferred into the new table.

Now since our data is sitting in its right place, now we can remove objects we are using for Upgrade purpose.

Now the data upgrade is completed and we need to remove the objects used within that process.

Force-delete the upgrade table, since the data was already transferred into the new table.

Delete the upgrade codeunit to make sure the old synchronization instructions defined in it are not used by the NAV server when you decide to change the My Sales Customer table again. Alternatively (if you want to reuse the upgrade codeunit later) you can choose to change the type of the GetTableSynchSetup function to “Normal”.

Upgrade Codeunit 2 - 8

Now we are done with our Upgrade project.

Conclusion

This set of demonstrations have provide you the insight, new way of running the data upgrade in Microsoft Dynamics NAV 2015 by involving the schema synchronization and upgrade codeunits.

The new approach:

  • Simplifies the upgrade environment, since all steps are performed by the latest version of the product.
  • Increases the data upgrade performance, since it is possible to parallelize execution of the data upgrade functions within the upgrade codeunit and across companies.
  • Reduces the amount of code to be written to handle the data upgrade, since some actions are now executed automatically by the NAV Server.
  • Allows data upgrade to be invoked by the NAV server, minimizing the number of manual actions which should be performed during the data upgrade.
  • Uses familiar upgrade toolkit design concepts (e.g. upgrade tables, upgrade functions).