Thursday, April 27, 2023

Purchase cycle in Dynamics 365 FO

In this blog of Dynamics Community 101 we will learn about the Purchase cycle in Dynamics 365 FO


Purchase cycle in Dynamics 365 F&O


The purchase cycle in Dynamics 365 Finance and Operations (F&O) outlines the series of steps taken to procure goods or services from vendors. It typically follows this sequence:


Create a Purchase Requisition (optional): Employees can submit a purchase requisition to request specific goods or services. This step is optional, as not all organizations use purchase requisitions.


Create a Request for Quotation (RFQ) (optional): Generate an RFQ to solicit pricing and delivery information from multiple vendors. This step is optional, as not all procurement processes involve requesting quotations.


Evaluate and Select a Vendor: Evaluate the received quotations and select a vendor based on factors such as price, quality, and delivery time.


Create a Purchase Order: Create a purchase order for the selected vendor, detailing the products or services being procured, their quantities, and agreed-upon prices.


Confirm the Purchase Order: Confirm the purchase order, which will finalize the agreement and trigger subsequent processes such as inventory reservation and vendor communication.


Receive the Goods or Services: Upon delivery, receive the goods or services and update the inventory records accordingly.


Create a Vendor Invoice: Receive a vendor invoice, which is a formal request for payment from the vendor. This document includes information about the products or services procured, quantities, prices, taxes, and any applicable discounts.


Post the Vendor Invoice: Post the vendor invoice to update the general ledger and accounts payable. This step finalizes the accounting process and ensures that the financial records are accurate.


Process and Make Payment: Process and make the payment to the vendor, applying it to the appropriate vendor invoice and updating the accounts payable.

Sales cycle in D365 FO

In this blog of Dynamics Community 101 we will learn the Sales cycle in D365 FO


Sales cycle in Dynamics 365 F&O

In Dynamics 365 Finance & Operations (F&O), the sales cycle is similar to the purchase cycle, but it involves different steps and documents as it focuses on the selling side of the business. The typical sales cycle in Dynamics 365 F&O includes the following steps:


Create a Sales Quotation (optional): If a customer requests a quote, you can create a sales quotation to provide the customer with pricing and delivery information. This step is optional, as not all sales processes involve quotations.


Create a Sales Order: Create a sales order for the customer, which is a formal agreement between the seller and the buyer, detailing the products or services being sold, their quantities, and agreed-upon prices.


Confirm the Sales Order: Confirm the sales order, which will finalize the agreement and trigger subsequent processes such as inventory reservation, picking, and packing.


Picking and Packing (Warehouse Management): Based on the confirmed sales order, the system will reserve the inventory, and the warehouse team will pick and pack the items for shipment.


Generate and Send the Packing Slip: Generate a packing slip to be included in the shipment. This document outlines the contents of the package and can also be sent electronically to the customer.


Ship the goods: Ship the products to the customer using the agreed-upon shipping method.


Create a Sales Invoice: Generate a sales invoice, which is a formal request for payment from the customer. This document includes information about the products or services sold, quantities, prices, taxes, and any applicable discounts.


Post the Sales Invoice: Post the sales invoice to update the general ledger and accounts receivable. This step finalizes the accounting process and ensures that the financial records are accurate.


Receive Payment: Receive and process the customer's payment, applying it to the appropriate sales invoice and updating the accounts receivable.

Thursday, April 20, 2023

Delete actions in Dynamics 365 FO

 In Microsoft Dynamics 365 Finance and Operations (F&O), there are several types of delete actions that help maintain data integrity across related tables. These actions define how the system should behave when a record in the primary table is deleted. Here are the main types of delete actions:


1. No action: No action is taken when a record is deleted in the primary table. This means that related records in the secondary table will remain unchanged, which may lead to orphaned records.


2. Cascade: When a record is deleted in the primary table, all related records in the secondary table are also deleted. This ensures that no orphaned records are left behind, maintaining referential integrity.


3. Restricted: If there are related records in the secondary table, the system prevents the deletion of the record in the primary table. This ensures that all related records are addressed before the primary record can be deleted.


4. Cascade + Restricted: This is a combination of the Cascade and Restricted actions. If there are no related records in the secondary table, the primary record can be deleted. If there are related records, the system will first attempt to cascade delete the related records. If any of the related records cannot be deleted due to other restrictions, the system will prevent the deletion of the primary record. Example : Imagine you have two tables: a "Customers" table and an "Orders" table. The Customers table contains information about your customers, and the Orders table stores their orders.

Now, let's say you want to delete a customer record from the Customers table. With the Cascade + Restricted delete action, the system will check if there are any orders associated with that customer in the Orders table.

a. If there are no related orders, the customer record will be deleted without any issues.

b. If there are related orders, the system will attempt to delete all those orders first (Cascade).

c. If any of the related orders cannot be deleted due to restrictions (like dependencies on other tables), the system will prevent you from deleting the customer record (Restricted).

In this way, the Cascade + Restricted delete action ensures that all related records are either deleted or addressed before deleting the primary record, maintaining data integrity and avoiding potential issues.

Tuesday, April 18, 2023

Dynamics 365 Finance and Operations (F&O) supports several types of synchronous and asynchronous integrations

Dynamics 365 Finance and Operations (F&O) supports several types of synchronous and asynchronous integrations


Synchronous Integrations:

OData Services: OData is an open standard protocol for creating and consuming RESTful APIs. Dynamics 365 F&O exposes OData services that can be used to read and write data in real-time.

Custom Services: Dynamics 365 F&O allows developers to create custom services using .NET or X++ code. These services can be invoked synchronously and can provide real-time integration with other systems.

Logic Apps: Logic Apps is a cloud-based service from Microsoft that allows users to create workflows to automate business processes. Dynamics 365 F&O provides connectors for Logic Apps, allowing users to trigger workflows synchronously when specific events occur in the system.

Service Bus: Dynamics 365 F&O supports integration with Azure Service Bus, which provides a reliable messaging platform for exchanging data between systems. Service Bus can be used to implement real-time integration patterns such as publish/subscribe and request/reply.


Asynchronous Integrations:
Data Management: Dynamics 365 F&O provides a data management framework that allows users to import and export data to and from the system asynchronously. Users can schedule data jobs to run at a specific time or on a recurring basis.

Power Automate: Power Automate (formerly known as Microsoft Flow) is a cloud-based service that allows users to create workflows to automate business processes. Dynamics 365 F&O provides connectors for Power Automate, allowing users to trigger workflows asynchronously when specific events occur in the system.

Data Entities: Dynamics 365 F&O provides data entities that can be used to extract data from the system asynchronously. Users can schedule data entity exports to run at a specific time or on a recurring basis.

Batch Framework: Dynamics 365 F&O provides a batch framework that allows users to run long-running processes asynchronously. Users can schedule batch jobs to run at a specific time or on a recurring basis.

File-based Integrations: Dynamics 365 F&O provides support for file-based integrations, where data is exchanged between systems using flat files such as CSV, XML, or JSON. Users can schedule file-based integrations to run asynchronously at a specific time or on a recurring basis.

Tuesday, April 4, 2023

Legal entity in Dynamics 365 FO | Functional 1

A legal entity is a group or organization that has been officially recognized by the government. It can sign contracts and has to make reports about how well it's doing.


How to create a Legal entity in Dynamics 365 F&O:

Go to organization administration module and open Legal entities as shown in the picture

a new form (Legal entity) will open like below


now click on New button as shown in the picture


A new dialog form will open like below


Fill in the information like below


now click on "ok"

Once the legal entity is created, it will be opened automatically as shown in the below screenshot


Put in the Memo/Description of the organization, Language, Time Zone, etc as shown in the picture below



Now go to address tab and update accordingly

I have added an address for Delivery as shown in the picture below (in the advanced option you can see some hidden settings like tax information, registration id and some more settings)


now coming down to contact information for the entity, click on add and add the details as shown below and click on save, in my case I am saving Telephone and email id (you can make it as primary if required, also in the advanced option you can see some hidden settings)


Also, if we go below and check Dashboard image/ banner, we can set as per our requirement, just click on banner in the 'Dashboard company image type' and then click on change as shown below



Reference: TechTalk365

Get email,phone number,fax,URL,Linkedin etc from InventLocation or warehouse

public LogisticsElectronicAddress getElectronicAddressByPostalAddress(InventLocation _inventLocation, LogisticsElectronicAddressMethodType _type)

{

LogisticsLocation logisticsLocation;

InventLocationLogisticsLocation inventLocationLogisticsLocation;

LogisticsElectronicAddress LogisticsElectronicAddress;


 select firstOnly RecId from inventLocationLogisticsLocation

        where inventLocationLogisticsLocation.InventLocation == _inventLocation.RecId

    join RecId from logisticsLocation

        where logisticsLocation.ParentLocation == inventLocationLogisticsLocation.Location;


     LogisticsElectronicAddress = LogisticsElectronicAddress::findByLocationAndType(logisticsLocation.RecId, _type);


    return LogisticsElectronicAddress;

}

Sunday, April 2, 2023

Ui Builder example in Dynamics 365 FO

Contract class:

[DataContractAttribute,

SysOperationContractProcessingAttribute(classstr(GITS_UIBuilderFixedAsset))]


class GITS_FixedAssetContract

{

AssetRoomNumber roomNumber;

[

DataMemberAttribute('roomNumber'),

SysOperationLabelAttribute('Room Number'),

SysOperationDisplayOrderAttribute('1')]


public AssetRoomNumber parmAssetRoomNumber(AssetId _roomNumber= roomNumber)

{

roomNumber = _roomNumber;

return roomNumber;

}

}






DP class:

[

    SRSReportQueryAttribute(querystr(GITS_FixedAssetQuery)),

    SRSReportParameterAttribute(classStr(GITS_FixedAssetContract))

]

class GITS_FixedAssetDP extends SRSReportDataProviderBase

{  

    GITS_FixedAssetContract          contract;

    GITS_FixedAssetDetailsTmp        fixedAssetTemp;

    AssetTable                       assetTable;

    AssetBook                        assetBook;

    CompanyInfo                      companyinfo;

    AssetRoomNumber                  roomNumber;


    [SRSReportDataSetAttribute('GITS_FixedAssetDetailsTmp')]

    public GITS_FixedAssetDetailsTmp getLineFixedAssetDetails()

    {

        select * from fixedAssetTemp;

        return fixedAssetTemp;

    }


    [SysEntryPointAttribute(False)]

    public void processReport()

    {

        contract                                    = this.parmDataContract() as GITS_FixedAssetContract;

        roomNumber                                  = contract.parmAssetRoomNumber();

        Query                                 query =       this.parmQuery();

        QueryRun    qr;

        QueryBuildRange  qbr;

        query.dataSourceTable(tableNum(AssetTable)).addRange(fieldNum(AssetTable,Roomnumber)).value(roomNumber);

        companyinfo = CompanyInfo::find();

        qr          = new QueryRun(Query);

        while(qr.next())

        {

            assetTable=qr.get(tableNum(AssetTable));

            assetBook=qr.get(tableNum(AssetBook));

            fixedAssetTemp.clear();

            fixedAssetTemp.CompanyLogo   = FormLetter::companyLogo();

            fixedAssetTemp.CompanyName   = companyinfo.name();               

            fixedAssetTemp.RoomNumber            = assetTable.RoomNumber;

            fixedAssetTemp.AssetcCode            = assetTable.AssetId;

            fixedAssetTemp.AssetDescription      = assetTable.Name;

            fixedAssetTemp.LocationDirectorate   = assetTable.Location;

            fixedAssetTemp.AssetValue            = assetBook.AcquisitionPrice;

            fixedAssetTemp.AssetAcqDate          = assetBook.AcquisitionDate;

            fixedAssetTemp.insert();

        }

    }

}






UI builder class:

class GITS_UIBuilderFixedAsset extends SrsReportDataContractUIBuilder

{   

    DialogField   dialogAssetRoomNumber;

    DialogGroup   dialogGroup;

    boolean       enable;  

    private void roomNumberLookup(FormStringControl roomNumberLookup)

    {

        Query                   query = new Query();

        QueryBuildDataSource    qbds_AssetTable;

        SysTableLookup          sysTableLookup;

        QueryBuildRange         qbr;

        if (roomNumberLookup != null)

        {

            sysTableLookup = SysTableLookup::newParameters(tablenum(AssetTable), roomNumberLookup);

            qbds_AssetTable = query.addDataSource(tableNum(AssetTable));

            sysTableLookup.addLookupfield(fieldnum(AssetTable, RoomNumber), true);

            qbr = qbds_AssetTable.addRange(fieldNum(AssetTable,RoomNumber));

            qbds_AssetTable.addGroupByField(fieldNum(AssetTable,RoomNumber));

            qbr.value('');

            sysTableLookup.parmUseLookupValue(false);

            sysTableLookup.parmQuery(query);

            sysTableLookup.performFormLookup();

        }

    }

    public void build()

    {

        GITS_FixedAssetContract rdpContract =  this.dataContractObject();

        dialogAssetRoomNumber = this.addDialogField(methodstr(GITS_FixedAssetContract,parmAssetRoomNumber),rdpContract);

        dialogAssetRoomNumber.lookupButton(2);

    }

    public void postRun()

    {

        Dialog dialogLocal = this.dialog();

        DialogField dialogField;

        super();

        dialogLocal.dialogForm().formRun().controlMethodOverload(false);

        dialogField = this.bindInfo().getDialogField(this.dataContractObject(), methodstr (GITS_FixedAssetContract, parmAssetRoomNumber));

        dialogField.registerOverrideMethod(methodstr(FormStringControl, lookup), methodstr(GITS_UIBuilderFixedAsset, roomNumberLookup), this);

    }

}

Records to include SSRS report example in Dynamics 365 FO

Contract class:

class GITS_FixedAssetCardCodeContract

{

        AssetId                    assetId;

        AssetAcquisitionDate       fromdate;

        AssetAcquisitionDate       todate;

        [

        DataMemberAttribute(identifierStr(fromDate)),

        SysOperationLabelAttribute('From Date'),

        SysOperationDisplayOrderAttribute('1')

        ]

        public AssetAcquisitionDate  parmFromDate(AssetAcquisitionDate  _FromDate = FromDate)

        {

            FromDate = _FromDate;

            return FromDate;

        }


        [

        DataMemberAttribute(identifierStr(toDate)),

        SysOperationLabelAttribute ('To Date'),

        SysOperationDisplayOrderAttribute('2')

        ]

        public AssetAcquisitionDate  parmToDate(AssetAcquisitionDate  _ToDate = ToDate)

        {

            ToDate = _ToDate;

            return ToDate;

        }


        [

        DataMemberAttribute('assetId'),

        SysOperationLabelAttribute('Asset Code'),

        SysOperationDisplayOrderAttribute('3')

        ]

    

        public AssetId parmAssetId(AssetId _assetId= assetId)

        {

        assetId = _assetId;

        return  assetId;

        }


        public boolean validate()

        {

            boolean result = true;


            if (!fromDate || !toDate || toDate < fromDate)

            {

                result = checkFailed("Invalid Date");

            }

            return result;

        }

}




DP class:

[

    SRSReportQueryAttribute(querystr(GITS_FixedAssetQuery)),

    SRSReportParameterAttribute(classStr(GITS_FixedAssetContract))

]

class GITS_FixedAssetDP extends SRSReportDataProviderBase

{  

    GITS_FixedAssetContract          contract;

    GITS_FixedAssetDetailsTmp        fixedAssetTemp;

    AssetTable                       assetTable;

    AssetBook                        assetBook;

    CompanyInfo                      companyinfo;

    //  AssetId                          assetId;

    AssetRoomNumber                  roomNumber;

    [SRSReportDataSetAttribute('GITS_FixedAssetDetailsTmp')]

    public GITS_FixedAssetDetailsTmp getLineFixedAssetDetails()

    {

        select * from fixedAssetTemp;

        return fixedAssetTemp;

    }

    [SysEntryPointAttribute(False)]

    public void processReport()

    {

        contract                                    = this.parmDataContract() as GITS_FixedAssetContract;

        roomNumber                                  = contract.parmAssetRoomNumber();

        Query                                 query =       this.parmQuery();

        QueryRun    qr;

        QueryBuildRange  qbr;

        query.dataSourceTable(tableNum(AssetTable)).addRange(fieldNum(AssetTable,Roomnumber)).value(roomNumber);

        companyinfo = CompanyInfo::find();

        qr          = new QueryRun(Query);

        while(qr.next())

        {

            assetTable=qr.get(tableNum(AssetTable));

            assetBook=qr.get(tableNum(AssetBook));

            fixedAssetTemp.clear();

            fixedAssetTemp.CompanyLogo   = FormLetter::companyLogo();

            fixedAssetTemp.CompanyName   = companyinfo.name();

            fixedAssetTemp.RoomNumber            = assetTable.RoomNumber;

            fixedAssetTemp.AssetcCode            = assetTable.AssetId;

            fixedAssetTemp.AssetDescription      = assetTable.Name;

            fixedAssetTemp.LocationDirectorate   = assetTable.Location;

            fixedAssetTemp.AssetValue            = assetBook.AcquisitionPrice;

            fixedAssetTemp.AssetAcqDate          = assetBook.AcquisitionDate;

            fixedAssetTemp.insert();

        }

    }

}

List page Interaction class code to enable disable control button in Dynamics 365 FO

[ExtensionOf(classStr(SalesAgreementListPageInteraction))]

final class SalesAgreementListPageInteraction_Extension

{

    public void selectionChanged()

    {

        next selectionChanged();

        if(AgreementHeader.AgreementState == AgreementState::Effective)

        {

            this.listPage().actionPaneControlEnabled(formcontrolstr(SalesAgreementListPage,MCOPurchaseOrder),true);

        }

        else

        {

            this.listPage().actionPaneControlEnabled(formcontrolstr(SalesAgreementListPage,MCOPurchaseOrder),false);

        }

    }


}

Lookup on form control Dynamics 365 FO using form control event handler on standard form

class MCOSAtoPOFormEventHandler

{

    [FormControlEventHandler(formControlStr(MCOSAtoPO, MCOSAtoPO_MCOSeasonCode), FormControlEventType::Lookup)]

    public static void MCOSAtoPO_MCOSeasonCode_OnLookup(FormControl sender, FormControlEventArgs e)

    {

        FormDataSource fds;

        MCOSAtoPO rTable;

        FormStringControl strControl = sender as FormStringControl ;

        fds = sender.formRun().dataSource("MCOSAtoPO");

        rTable = fds.cursor();

        Query lookupQuery = new Query();

        var selectField = [tableNum(RetailSeasonTable), fieldNum(RetailSeasonTable, seasonCode)];

        QueryBuildDataSource qbds = lookupQuery.addDataSource(tableNum(RetailSeasonTable));

        qbds.addSelectionField(fieldNum(RetailSeasonTable, seasonCode));

        qbds.addSelectionField(fieldNum(RetailSeasonTable, Description));

        qbds.addSelectionField(fieldNum(RetailSeasonTable, StartDate));

        qbds.addSelectionField(fieldNum(RetailSeasonTable, EndDate));

        var lookup =  MCOSysMultiselectionLookup::lookup(lookupQuery, selectField, strControl,strControl,strControl,strControl.valueStr());

        rTable.MCOSeasonCode = lookup.getSelectedStringsJoined();

    }

}

Lookup on form control Dynamics 365 FO on customized form

[Control("String")]
class MCORevisionReasonCodeId
{
    public void lookup()
    {
        Query                   query = new Query();
        QueryBuildDataSource queryBuildDataSource;
        SysTableLookup  sysTableLookup = sysTableLookup::newParameters(tableNum(MCOPORevReasonCodeTable), this);
        queryBuildDataSource = query.addDataSource(tableNum(MCOPORevReasonCodeTable));
        sysTableLookup.addLookupfield(fieldNum(MCOPORevReasonCodeTable, ReasonCodeId),true);
        sysTableLookup.addLookupfield(fieldNum(MCOPORevReasonCodeTable, Description));
        sysTableLookup.parmQuery(query);
        sysTableLookup.performFormLookup();
    }

    public boolean validate()
    {
        boolean ret;
        ret = super();
        if(ret && !MCOPORevReasonCodeTable::find(this.valueStr()))
        {
            ret = checkFailed(strFmt("@SCM:TableFieldValidation", this.valueStr(), "@MCO:RevReasonCode", "@MCO:PORevReasonCode"));
        }
        return ret;
    }
}