Convert private Workspace to public in Azure DevOps

Sometime we do not have permission to access private workspace in Azure DevOps to make it accessible follow below steps :

  1. Global Administrator permissions –Member of the “Project Collection Administrators” permissions group in Azure DevOps Project which is linked with workspace.
  2. Open up Developer Command Prompt for VS 2019.
  3.  Identify the workspace name and owner:
    tf workspaces /owner:* /computer:* /collection:https://<site.url>/defaultcollection
  4.  Change Permission to Public:
    tf workspace /collection:https://<site.url>.visualstudio.com/defaultcollection        AzureBIDEV01_01;https://<site.url>.visualstudio.com\<username> /permission:Public  

Running custom X++ scripts in Production

Running custom X++ scripts without any downtime?

In 10.0.25 you can run simple X++ scripts on a production environment without any downtime.

This feature lets you run custom X++ scripts without having to go through Dynamics LCS or suspend your system. Therefore, you can correct minor data inconsistencies without causing any disruptive downtime.

Script details page.

Read more :

https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/deployment/run-custom-scripts

Settle payment journal against sales invoice in D365 FinOpsApps

Code used to settle posted payment journal transaction against the sales invoice

// <summary>
    /// Settle payment journal (which still has balance) against sales invoice. Each payment journal is make for one sales order only.
    /// No payment is made for multiple order. If balance is available after settle, leave the balance as there might be another invoice
    /// not comes in yet for the same order. This periodic job will settle against them when the invoice is in.
    /// </summary>
    /// <param name = "_contract"></param>
    public void processSettlement(AG_CustSalesPaymSettlementDataContract _contract)
    {
        RefRecId            paymLedgerJournalTransRefRecId = _contract.parmPaymLedgerJournalTransRecId();
        boolean             showInfoLog                    = _contract.parmShowInfoLog();
        LedgerJournalTable  paymLedgerJournalTable;
        LedgerJournalTrans  paymLedgerJournalTrans;
        CustTransOpen       paymCustTransOpen, custTransOpen;
        CustTrans           paymCustTrans;

        //Loop through all payment journal which still has open balance
        while select paymLedgerJournalTable
            where   paymLedgerJournalTable.Posted       == NoYes::Yes
            join    paymLedgerJournalTrans
            where   paymLedgerJournalTrans.JournalNum   == paymLedgerJournalTable.JournalNum
                &&  ((!paymLedgerJournalTransRefRecId)                                      //If specific payment journal line is provided, get that only,
                ||   (paymLedgerJournalTrans.RecId      == paymLedgerJournalTransRefRecId)) //else, get all lines (which satisfy other query criteria)
            join    paymCustTrans
            where   paymCustTrans.Voucher               == paymLedgerJournalTrans.Voucher
                &&  paymCustTrans.RecId                 == paymLedgerJournalTrans.CustTransId
                &&  paymCustTrans.TransType             == LedgerTransType::Payment
            join    paymCustTransOpen
            where   paymCustTransOpen.RefRecId          == paymCustTrans.RecId
        {
           AG_CustSalesPaymSettlementService::settleByPaymJourTrans(paymLedgerJournalTrans, showInfoLog);
        }

    }

    public static void settleByPaymJourTrans(LedgerJournalTrans _paymLedgerJournalTrans, boolean _showInfoLog)
    {
        boolean             useLegacyMethod = false;
        CustTable           custTable;
        LedgerJournalTrans  paymLedgerJournalTrans;
        CustTransOpen       invCustTransOpen, paymCustTransOpen, custTransOpen;
        CustTrans           invCustTrans, paymCustTrans;
        CustInvoiceJour     custInvoiceJour;
        SpecTransManager    manager;
        CustVendTransData   custVendTransData;

        //Given the payment LedgerJournalTrans, find the related CustTrans which has CustTransOpen
        select firstonly paymLedgerJournalTrans
            where   paymLedgerJournalTrans.RecId  == _paymLedgerJournalTrans.RecId
            join    paymCustTrans
            where   paymCustTrans.Voucher         == paymLedgerJournalTrans.Voucher
                &&  paymCustTrans.RecId           == paymLedgerJournalTrans.CustTransId
                &&  paymCustTrans.TransType       == LedgerTransType::Payment
            join    paymCustTransOpen
            where   paymCustTransOpen.RefRecId    == paymCustTrans.RecId;

        //Find the related invoice to be settled against the payment journal in the query above
        select firstonly invCustTrans
            where   invCustTrans.AccountNum      == paymCustTrans.AccountNum
                &&  invCustTrans.MCRPaymOrderID  == paymCustTrans.MCRPaymOrderID
                &&  invCustTrans.TransType       == LedgerTransType::Sales
            join    invCustTransOpen
            where   invCustTransOpen.RefRecId    == invCustTrans.RecId;

        custTable = CustTable::find(paymCustTrans.AccountNum);

        try
        {
            ttsbegin;

            if(paymCustTransOpen && invCustTransOpen)
            {
                //Legacy code for settlement --------------------------------------------------------------------------------------------------------
                if(useLegacyMethod)
                {
                    //Create an object of the CustVendTransData class with the invoice transaction as parameter and mark it for settlement
                    custVendTransData = CustVendTransData::construct(invCustTrans);
                    custVendTransData.markForSettlement(custTable);

                    //Create an object of the CustVendTransData class with the payment transaction as parameter and mark it for settlement
                    custVendTransData = CustVendTransData::construct(paymCustTrans);
                    custVendTransData.markForSettlement(custTable);

                    // Settle all transactions marked for settlement for this customer
                    CustTrans::settleTransact(custTable, null, true, SettleDatePrinc::DaysDate, systemdateget());
                }
                //New code for settlement -----------------------------------------------------------------------------------------------------------
                else
                {
                    //Mark for settlement
                    SpecTransExecutionContext specTransExecutionContext = SpecTransExecutionContext::newFromSource(custTable);
                    SpecTransManager          specTransManager          = SpecTransManager::construct(specTransExecutionContext.parmSpecContext());
                    Amount                    settleAmount              = invCustTransOpen.AmountCur;

                    //Prevent over settle
                    if(settleAmount > (-paymCustTransOpen.AmountCur))
                        settleAmount = (-paymCustTransOpen.AmountCur);

                    //Payment
                    specTransManager.insert(paymCustTransOpen.DataAreaId,
                                            paymCustTransOpen.TableId,
                                            paymCustTransOpen.RecId,
                                            -settleAmount,
                                            paymCustTrans.CurrencyCode);
            
                    //Invoice
                    specTransManager.insert(invCustTransOpen.DataAreaId,
                                            invCustTransOpen.TableId,
                                            invCustTransOpen.RecId,
                                            settleAmount,
                                            invCustTrans.CurrencyCode);
                
                    //Settle
                    if(CustTrans::settleTransaction(specTransExecutionContext, CustTransSettleTransactionParameters::construct()))
                    {
                        if(_showInfoLog)
                            info(strFmt("@AG:AG_SettlementCompletedFor", invCustTrans.MCRPaymOrderID, invCustTrans.Invoice, settleAmount)); //Label: Settlement completed for Sales order %1, Invoice %2, Amount %3
                    }
                }
            }

            ttscommit;
        }
        catch
        {
            error(strFmt("@AG:AG_SettlementForPaymentJournalLineFailed", _paymLedgerJournalTrans.RecId, _paymLedgerJournalTrans.accountDisplay())); //label: Settlement for payment journal line is unsuccessful (RecId: %1, Account: %2)
        }
    }

Excel integration (filter records with form value)

Scenario: We want to filter records in excel adding based on form value.

Standard D365 example is the General journal form.

Solution:

  1. Create a data entity for the data source
  2. Create an excel file and add it as a resource in Dynamics365
  3. Creating a class that will extend DocuTemplateRegistrationBaseBelow is an example of that
    class AG_IARRequestProcessTemplate extends DocuTemplateRegistrationBase
    {
        private const DocuTemplateName ExcelTemplateName    = resourceStr(AG_IARExcelTemplate);
    
        public void registerTemplates()
        {
            this.addTemplate(
                    OfficeAppApplicationType::Excel,
                    ExcelTemplateName,
                    ExcelTemplateName,
                    literalStr("Excel template for IAR  Process"),
                    literalStr("IAR process - Excel template"),
                    NoYes::No,
                    NoYes::No,
                    NoYes::Yes);
       		}
    
    }
  4. On your form where you want to add this excel and filter you can implement below interfaces
    (officeIMenuCustomizer,OfficeITemplateCustomExporter) and implement customize menu options method on AG_IARProcess

Public class AG_IARProcess extends FormRun implements officeIMenuCustomizer,OfficeITemplateCustomExporter

public void customizeMenuOptions(OfficeMenuOptions _menuOptions)
{
ListIterator dataEntityIterator = new       ListIterator(_menuOptions.dataEntityOptions());
        while (dataEntityIterator.more())
        {
            dataEntityIterator.delete();
        }
        	
DocuTemplate docuTemplate = DocuTemplate::findTemplate(OfficeAppApplicationType::Excel, resourceStr(AG_IARExcelTemplate));
        if (docuTemplate)
        {
OfficeTemplateExportMenuItem menuItem = OfficeTemplateExportMenuItem:constructWithDocuTemplate(docuTemplate, docuTemplate.TemplateID);

            _menuOptions.customMenuItems().addEnd(menuItem);
        }
    }
}

Implement the below method as well getInitialTemplateFilters
In this method, you can pass your filters

public Map getInitialTemplateFilters(OfficeTemplateExportMenuItem _menuItem)
{
     AG_IARequestDetailsTable   iarTable = AG_IARequestDetailsTable_ds.cursor();
     const str templateName = resourceStr(QTQ_IARExcelTemplate);
     DocuTemplate template = DocuTemplate::findTemplate(OfficeAppApplicationType::Excel, templateName);
     str iARequestDetailsTableEntity = tableStr(AG_IARequestDetailsTableEntity);  
      if (template && template.TemplateID == templateName)
       {
          ExportToExcelFilterTreeBuilder filterBuilder = new ExportToExcelFilterTreeBuilder(iARequestDetailsTableEntity);
          var filter = filterBuilder.areEqual(fieldStr(AG_IARequestDetailsTableEntity, CaseId), detailBase.CaseId);
          filtersToApply.insert(iARequestDetailsTableEntity, filter);
       }
}

Grid Capabilities in D365FO

Microsoft recently announced in new feature of Grid Capabilities in Platform updates for version 10.0.16 of Finance and Operations apps (February 2021)

This feature will help users will be able to expand or collapse groups as desired, which can help create a summarized view of data. Subtotals will also be shown at the group header level.

The new grid control provides several useful and powerful capabilities that you can use to enhance user productivity, construct more interesting views of your data, and get meaningful insights into your data. This article will cover the following capabilities:

Calculating totals
Typing ahead of the system
Evaluating math expressions
Grouping tabular data (enabled separately using the (Preview) Grouping in grids feature)
Pinned system columns.

1. Enable Feature
New Grid control
Go to System Admin > Feature Management > Search for New Grid control.

Note : If new feature is not available then click on Check for updates on top right corner.

Enable the feature and reload the form.

2. Grouping in grids

Enable the feature and reload the form.

Calculating totals
Users have the ability to see totals at the bottom of numeric columns in grids. A footer section at the bottom of the grid shows these totals.

First select a column where you need total

Depending on data, it will take some time to calculate totals

Once this is done, the total will be visible at the bottom.

Grouping tabular data
This is one of the good features for users to group the data based on certain columns and to get count.

Select the column where you need grouping. I have selected the grouping based on Department

Now it is showing count of positions under department and We can easily expands positions

Remove the grouping
If you want to remove the grouping then go to last column and click on three dots and select Ungroup all.

Hide/Show footer
Go to last column and select Hide/Show footer option for total.

 

Truncate AxDB database log

Recently on one of the environments with the below error

After investigation, found an issue with the MSSQL_Logs drive got full

So I figure the issue by truncating or shrinking of database log by following the below steps.

Truncate the transaction log

  1. Right-click the AxDB database and select Properties -> Options.
  2. Set the recovery model to Simple and exit the menu.
  3. Right-click the database again and select Tasks -> Shrink -> Files.
  4. Change the type to Log .
  5. Under Shrink action, select Reorganize pages before releasing unused space and click OK.

Dimension Controls in a Dialog for Dynamics 365 for Operations

 

To put it simply, we just need to create our own custom control type, let’s call it “Dimension Entry No Datasource” control.

Firstly, create an extension package so that you are not tempted to overlayer code. Be sure to include the Dimensions model in your package.  Visit the Dynamics ‘AX7’ menu and create a new model:

Name it however you wish:

And select extension package:

Include any other packages you want to reference.  Think of this as an assembly (DLL) referencing other assemblies.

Extensions is the new bag, baby!

Create a new class in your model, and extend the DimensionEntryControl class:

  1. [FormControlAttribute(‘Group’, ”, classStr(DimensionEntryControlBuildNoDS))]
  2. class DimensionEntryControlNoDS extends DimensionEntryControl
  3. {
  4. public DimensionDefault getDimensionRefRecId()
  5. {
  6. return this.parmDimensionValueSetId();
  7. }
  8. }

You’ll notice in the metadata I’ve also drafted a separate extension class for DimensionEntryControlBuildNoDS as well.

In the class example above, you’ll see we have overridden the getDimensionRefRecId() method which is declared as Protected in the parent class.  By setting it to public, we can use it as required in our dialog form.

Build your package from the AX7 menu.

Now it’s time to make use of your training in SysOperation Framework from AX 2012! Create a new controller class which extends SysOperationServiceController, in this example I’ve called it MyController.  Override the templateForm() method and specify a new form:

  1. class MyController extends SysOperationServiceController
  2. {
  3. /// <summary>
  4. /// Swenka internal use only.
  5. /// </summary>
  6. /// <returns>
  7. /// A <c>formName</c> value.
  8. /// </returns>
  9. protected FormName templateForm()
  10. {
  11. return formStr(MyTemplateForm);
  12. }
  13. public static void main(Args _args)
  14. {
  15. MyController c = new MyController();
  16. c.initializeFromArgs(_args);
  17. c.startOperation();
  18. }
  19. }

The new form I’m using above called MyTemplateForm is just a duplicate of the SysOperationTemplateForm.  Expand the design to the DialogStartGrp group which is the main group for any SysOperation dialog.

Add your new custom dimension control here by right-click and new control:

Typically a dimension entry control can infer the type of dimension entry we are requiring based on the Extended Data Type (EDT) of the table field that is associated to the control.  This will either be a Default Dimension such as on master data, or a Ledger Dimension such as on General Journal lines.

In our case however, we have no datasource on the form. So we must specify explicitly the default dimension controller class in the control properties.  Also set the auto declare to True.

From here it is a quick couple lines of code in the init() of the form to tell the control to display values upon loading:

  1. public void init()
  2. {
  3. super();
  4. DimensionEntryControlNoDS.parmDisplayValues(true);
  5. }

And upon closing the dialog we want to capture that default dimension recId and save it to our data contract for use in our service:

  1. // <summary>
  2. /// Command control message called when the form’s OK button is clicked.
  3. /// </summary>
  4. public void closeOk()
  5. {
  6. if (this.controller().checkCloseDialog())
  7. {
  8. super();
  9. var someRecId = DimensionEntryControlNoDS.getDimensionRefRecId();
  10. //now we need to save this in the data contract
  11. //via calling…
  12. DataContract c = this.controller().getDataContractObject(‘_myContractParameterName’);
  13. c.parmDim(someRecId);
  14. //now we can close the form, and execute the job
  15. if(this.controller().skipRunOperation())
  16. {
  17. this.controller().skipRunOperation(false);
  18. }
  19. else
  20. {
  21. this.controller().dialogClosedWithOk(this.dialog());
  22. }
  23. }
  24. }

Using a simple action menu item to invoke the service, we can see the dialog displays our default dimensions for the current legal entity: