Thursday, November 28, 2019

D365FO VM trail license expired

Microsoft releasing the D365FO VM,s with trail license, it automatically expire after 180 days.

Some cases we need to extend the trail license for some more days. Below comment will help you to extend the D365FO VM trail license.

  1.  Open the command prompt in Administrator mode (Command prompt à Run as administrator)
  2. Execute the below comment to know current license details
    1. slmgr.vbs /dlv
  3. In order to extend the trail period, execute below command. 
    1. slmgr.vbs /rearm
  4. Restart the computer.
  5. The trail period is extended now.
  6. In order to check the trail period run the below command
    1. slmgr.vbs /dlv

Enable navigation bar in D365FO new version

Microsoft was removed the navigation bar in newer version of D365FO.

But many customer requested the navigation bar in D365FO. So microsoft brings the navigation bar as parameter setup in Client performance options forms.

If you required just enable the option in Client performance options

1.      Go to system administration > setup > Client performance options



 And Enable legacy navigation bar toggle.


Click Ok in the bottom of the page.

Refresh the browser. 

The legacy navigation bar will be available now.

Wednesday, November 27, 2019

SQL "IN" Operator

Microsoft Introduce IN Operator in X++ Syntax, but it will work with Enums  only.

Following are the example how you can use this

SalesTable  salesTable;
container   con = [SalesType::Sales,
SalesType::ReturnItem,
SalesType::Subscription];

while select SalesId from salesTable
where salesTable.SalesType in con
{
Info(salesTable.SalesId);
}

Update AX system fields either CreatedDateTime or ModifiedDateTime

Sometimes while creating a record if we need to update the created date time or modified date time system field in D365FO. Please find the below code to update the createddatetime field during the record creation.

This method will help you to update createDateTime field while inserting the record.

static void updateCreatedDateTime(Args _args)
{
CustGroup custGroup;

ttsBegin;
new OverwriteSystemfieldsPermission().assert();
custGroup.overwriteSystemfields(true);
custGroup.custGroupId= “Abcd”;
custGroup.Name= “Abcd”;
custGroup.(fieldnum(custGroup,CreatedDateTime))= str2datetime( “2019/11/28 05:30:00” ,321 );
custGroup.doInsert();
custGroup.overwriteSystemfields(false);
CodeAccessPermission::revertAssert();
ttsCommit;
}


static void updateCreatedDateTime(Args _args)
{
        CustGroup custGroup;

ttsBegin;
new OverwriteSystemfieldsPermission().assert();
custGroup.overwriteSystemfields(true);
custGroup = custGroup::find("Abcd");
custGroup.Name= “Abcd”;
custGroup.(fieldnum(custGroup,ModifiedDateTime))= str2datetime( “2019/11/28 06:30:00” ,321 );
custGroup.doUpdate();
custGroup.overwriteSystemfields(false);
CodeAccessPermission::revertAssert();
ttsCommit;
}

Tuesday, November 26, 2019

UnitOfMeasureConverter::convert() method is depreciated

Hi All,

In new D365FO the UnitOfMeasureConverter::convert() method is depreciated so we need to use EcoResProductUnitConverter method for unit conversion.

Please refer below code, the EcoResProductUnitConverter::convertGivenUnitSymbolsForReleasedProduct method will convert the quantity from invent quantity to sales quantity

public PurchQty convertSalesQty(ItemId _itemId,InventDimId _inventDimId, Qty _qty, SalesUnit _salesUnit)
    {
        SalesQty        salesQty;
        InventUnitId    inventUnit;

        inventUnit  = InventTableModule::find(_itemId,ModuleInventPurchSales::Invent).UnitId;
       

         //salesQty    = UnitOfMeasureConverter::convert(_qty
        //                                            , UnitOfMeasure::unitOfMeasureIdBySymbol(inventUnit)
        //                                            , UnitOfMeasure::unitOfMeasureIdBySymbol(_salesUnit)
        //                                            , NoYes::Yes
        //                                            , InventTable::itemProduct(_itemId));

In AX2012 we used UnitOfMeasureConverter::convert() method to convert the unit from invent to sales. But if we use same method in D365Fo we will receive the best practices error like the method UnitOfMeasureConverter::convert() is depreciated use EcoResProductUnitConverter class.

Please find below how to use EcoResProductUnitConverter class in D365FO to convert unit.

        salesQty = EcoResProductUnitConverter::convertGivenUnitSymbolsForReleasedProduct(_itemId
                                                                                        , _inventDimId
                                                                                        , _qty
                                                                                        , inventUnit
                                                                                        , _salesUnit
                                                                                        , NoYes::Yes
                                                                                        , NoYes::No);

        return salesQty;
    }

//Refer smmSalesCustItemStatisticsDP class

EcoResProductUnitConverter productUnitConverter = EcoResProductUnitConverter::newGivenUnitSymbolsForReleasedProduct(
                                                                                                _itemId, 
                                                                                                _inventDimId, 
                                                                                                _unitFrom,
                                                                                                unitInvent, 
                                                                                                NoYes::Yes);

Tuesday, November 19, 2019

Transfer order picking list registration

Below code will help you to register picking list for transfer order

public void registerPickingRoute(PickingRouteId _pickingRouteId)
{
            pickingRoute = WMSPickingRoute::find(_pickingRouteId,true);
            if(pickingRoute)
            {
                pickingRoute.finish();
            }
}

D365FO Transfer order picking list registration with split quantity

During picking list registration in Transfer order we may need to split the lines for batch number or serial number registration.

below code will be used for split the transaction. (Select line -> Function -> Spilt)

public void split transactions(RefRecId _recId, Qty _qty)
{
            WMSOrderTrans     orderTrans;
             WMSOrderTrans    splitTrans;
             InventDim               inventDim;

          orderTrans                            = WMSOrderTrans::findOrderTrans(_recId,true);
          splitedTrans                          = orderTrans.split(_qty);
          inventDim                             =  orderTrans.inventDim();
          inventDim.inventBatchId     = shipmentDataTmp.InventBatchId;
          inventDim                             = InventDim::findOrCreate(inventDim);

           splitedTrans.selectForUpdate(true);
          splitedTrans.inventDimId    = inventDim.inventDimId;
           if(splitedTrans.validateWrite())
           {
                  splitedTrans.update();
           }
}

Note: findOrderTrans method will not be available in standard table. i have written extension class for WMSOrderTrans table.

[ExtensionOf(tableStr(WMSOrderTrans))]
final class WMSOrderTransExample_Extension
{

 static WMSOrderTrans findOrderTrans(RefRecId _recId, boolean _forupdate = false)
    {
        WMSOrderTrans   orderTrans;

        orderTrans.selectForUpdate(_forupdate);

        select firstonly orderTrans
            where orderTrans.RecId == _recId;

        return orderTrans;
    }
}

Monday, November 18, 2019

D365FO Receive transfer order with batch number registration using x++

The below code will used to Receive the transfer order with batch number registration through code

 public void processTransferOrder()
{
this.registerInventory("100001");
this.postTransferOrderReceive("10001",systemdateget());
}
public void postTransferOrderReceive(InventTransferOrderId _transferId, TransDate _postingDate)
{
        InventTransferTable         inventTransferTable;
        InventTransferParmTable     inventTransferParmTable;
        InventTransferUpdReceive    inventTransferUpdReceive;

        inventTransferTable = InventTransferTable::find(_transferId,true);
        if(inventTransferTable.TransferId)
        {
            inventTransferParmTable.initParmDefault();
            inventTransferParmTable.ParmId                  = RunBaseMultiParm::getSysParmId();
            inventTransferParmTable.TransferId              = inventTransferTable.TransferId;
            inventTransferParmTable.UpdateType              = InventTransferUpdateType::Receive;
            inventTransferParmTable.PrintTransferReceipt    = NoYes::No;
            inventTransferParmTable.ReceiveUpdateQty        = InventTransferReceiveUpdateQty::Registered;
            inventTransferParmTable.EditLines               = NoYes::Yes;
            inventTransferParmTable.ExplodeLines            = NoYes::Yes;
            inventTransferParmTable.TransDate               = _postingDate;

            inventTransferUpdReceive = InventTransferUpdReceive::newParmBuffer(inventTransferParmTable);
            inventTransferUpdReceive.run();
        }
}

public void postTransferOrderReceive(InventTransferOrderId _transferId)
{
        InventTransWMS_Register    inventTransWMS_register;
        TmpInventTransWMS           tmpInventTransWMS;
        InventTrans                            inventTranslocal;
        InventDim                              inventDimlocal;
        InventTransIdReceive            inventTransIdReceive;

 while select inventTransferLine
            where inventTransferLine.TransferId == _transferId
        {

            inventTranslocal        = InventTrans::findTransId(InventTransferLine.InventTransIdReceive, true);
            inventDimlocal          = inventTranslocal.inventDim();

            if(arrivalDataTmp.InventBatchId)
            {
                inventDimlocal.inventBatchId    = arrivalDataTmp.inventBatchId;
                inventDimlocal                  = InventDim::findOrCreate(inventDimlocal);
                inventTranslocal.inventDimId    = inventDimlocal.inventDimId;
            }

            inventTransWMS_register             = inventTransWMS_register::newStandard(tmpInventTransWMS);
            tmpInventTransWMS.clear();
            tmpInventTransWMS.initFromInventTrans(inventTranslocal);
            tmpInventTransWMS.ItemId            = inventTranslocal.ItemId;
            tmpInventTransWMS.InventQty         = arrivalDataTmp.qty;
            if(tmpInventTransWMS.validateWrite())
            {
                tmpInventTransWMS.insert();
                inventTransWMS_register.writeTmpInventTransWMS(tmpInventTransWMS
                                                           , inventTranslocal
                                                           , inventDimlocal);
     
                inventTransWMS_register.updateInvent(inventTranslocal);
            }
         
        }

Certificate issue in D365FO

We use virtual machines to perform development tasks which are provided as images by Microsoft run via Hyper-V on a local server. After the trail period expires the certificates will be expired. Here i have explained how to re activate the certificates

Error details

On your development machine you cannot open the application in the browser anymore and face an error message like
There is a problem with the server
Sorry, the server has encountered an error. It is either not available or it can't respond at this time. Please contact your system administrator.
If you check the event log using the Event Viewer you’ll find a warning message pointing to an ExpiredCertificateException there:
Process information: 
    Process ID: 14516 
    Process name: w3wp.exe 
    Account name: NT AUTHORITY\NETWORK SERVICE 
 
Exception information: 
    Exception type: ExpiredCertificateException 
    Exception message: Expired certificate for id 'C0E503DC8987D25B63897A7BE0B3E34BDCC89F41'.
   at Microsoft.Dynamics.AX.Configuration.CertificateHandler.LocalStoreCertificateHandler.GetCertificatesForId(String id)
etc.
Solution
Find Certificates
You can see the certificates that are relevant here using Manage computer certificates from Windows Start menu. Navigate to Certificates – Local Computer > Personal > Certificates.

In the column Expiration Date you can easily identify the ones that recently expired, in this case
  • DeploymentsOnebox.DaxRunnerTokenUserCertificate.pfx
  • DeploymentsOnebox.LcsClientCertificate.pfx
  • DeploymentsOnebox.MRClientCertificate.pfx
  • DeploymentsOnebox.SessionAuthenticationCertificate.pfx
Identify Thumbprint of Expired Certificate
Certificates get accessed by their thumbprint which is a 40-digit hexadecimal value. You can see it by double-clicking the certificate in the certificates viewer and open the Details tab.

Copy the thumprint values and make sure all letters are capital and remove all spaces.
example:43082FE50B4D02562C89EA728B2359C598E84886 
You can use any text editor or event VS, my preferred one for such operations is Notepad++. Make sure to run it as Administrator so you can save the files later without any issues. All three files we need are located in 

Clone the Certificate

Use PowerShell (and Run as Administrator, of course) to execute the following command (and make sure to replace the thumbprint with the one you just identified):
Set-Location -Path "cert:\LocalMachine\My"
$OldCert = (Get-ChildItem -Path 43082FE50B4D02562C89EA728B2363C598E84886)
New-SelfSignedCertificate -CloneCert $OldCert -NotAfter (Get-Date).AddMonths(999)
999 is the number of months the certificate will be valid until. Should be fine for quite some time.
The execution of this creates some output – copy and note the thumbprint of the newly created certificate. In the certificate manager you can see the clone (you might have to Refresh after a right click on the folder on the left).

Update References

The new thumprint values we need to update in web config, wif config and wif services config (take backup the files before modification). The files are available in the below path 

C:\AOSService\webroot:
  • web.config
  • wif.config
  • wif.services.config
Find the old thumprint 4(3082FE50B4D02562C89EA728B2359C598E84886) and replace new thumprint value which is generated.

Repeat this for all expired certificates. 

Reboot

Restart Batch, Management reporter, DMF, SQL and IIS services.
Restart the server 


Form event handlers in D365FO

Form datasource from xFormRun
[FormEventHandler(formStr(SomeForm), FormEventType::Initialized)]
public static void SomeForm_OnInitialized(xFormRun sender, FormEventArgs e)
{
    FormDataSource MyRandomTable_ds = sender.dataSource(formDataSourceStr(SomeForm, MyRandomTableDS));
    ...
}

Get FormRun from form datasource

[FormDataSourceEventHandler(formDataSourceStr(MyForm, MyRandomTableDS), FormDataSourceEventType::Written)]
public static void MyRandomTableDS_OnWritten(FormDataSource sender, FormDataSourceEventArgs e)
{
    FormRun formRun = sender.formRun() as FormRun;
    // you can even call custom methods (I think IntelliSense won't work though)
    formRun.myCustomMethod();
}

Get FormRun from form control

[FormControlEventHandler(formControlStr(MyForm, MyButton), FormControlEventType::Clicked)]
public static void MyButton_OnClicked(FormControl sender, FormControlEventArgs e)
{
   FormRun formRun = sender.formRun() as FormRun;
   formRun.myCustomMethod();
}

Access form control from xFormRun

[FormEventHandler(formStr(SomeForm), FormEventType::Initialized)]
public static void SomeForm_OnInitialized(xFormRun sender, FormEventArgs e)
{
    // set the control to invisible as an example
    sender.design().controlName(formControlStr(SomeForm, MyControl)).visible(false);
}

Get current record in form control event

[FormControlEventHandler(formControlStr(SomeForm, SomeButton), FormControlEventType::Clicked)]
public static void SomeButton_OnClicked(FormControl sender, FormControlEventArgs e)
{
    // as an example the datasource number is used for access; I perceive the formDataSourceStr as more robust
    SomeTable callerRec = sender.formRun().dataSource(1).cursor();
}

Convert Common and use DataEventArgs

[DataEventHandler(tableStr(AnyTable), DataEventType::ValidatedWrite)]
public static void InventLocation_onValidatedWrite(Common sender, DataEventArgs e)
{
    // convert Common to AnyTable
    AnyTable anyTable = sender;
    // the DataEventArgs actually are ValidateEventArgs and can be converted
    ValidateEventArgs validateEventArgs = e;
    // the ValidateEventArgs carry the validation result (so far)
    boolean ret = validateEventArgs.parmValidateResult();
    // the table has some additional validation logic and gives back the result
    ret = anyTable.doSomeAdditionalCustomValidation(ret);
    // provide the args with the validation result
    validateEventArgs.parmValidateResult(ret);
}

Use the onValidatedFieldValue event properly

[DataEventHandler(tableStr(SomeTable), DataEventType::ValidatedFieldValue)]
public static void SomeTable_onValidatedFieldValue(Common sender, DataEventArgs e)
{
    SomeTable someTable = sender;
    // the clue is to know that the DataEventArgs actually are ValidateFieldValueEventArgs and that you can get the field name from them
    ValidateFieldValueEventArgs validateEventArgs = e;
    boolean ret = validateEventArgs.parmValidateResult();
    FieldName fieldName = validateEventArgs.parmFieldName();
    switch (fieldName)
    {
        case fieldStr(SomeTable, SomeCustomField):
            ... do some magic
            break;
    }
    validateEventArgs.parmValidateResult(ret);
}

Use the MappedEntityToDataSource event

[DataEventHandler(tableStr(MyTableEntity), DataEventType::MappedEntityToDataSource)]
public static void MyTableEntity_onMappedEntityToDataSource(Common _sender, DataEventArgs _eventArgs)
{
    DataEntityContextEventArgs eventArgs = _eventArgs;
    MyTableEntity entity = _sender;
    if (eventArgs.parmEntityDataSourceContext().name() == dataEntityDataSourceStr(MyTableEntity, MyTable))
    {
        MyTable myTable = eventArgs.parmEntityDataSourceContext().getBuffer();
        ... do some magic with it
    }
}