web
You’re offline. This is a read only version of the page.
close
Skip to main content

Announcements

News and Announcements icon
Community site session details

Community site session details

Session Id :
Customer experience | Sales, Customer Insights,...
Suggested Answer

open record after its line items have been created via web api

(0) ShareShare
ReportReport
Posted on by 75

hi I have some javascript code which is used to take contract pieces of information and items and create an invoice with those very items.

Though I am not quite sure whether this approach is the best for creating a new record from one entity into another.

My code:

    var currentYear = new Date().getFullYear();
    var currentStart = new Date(currentYear, 0, 1);
    //currentStart.setHours(0,0,0,0);
    var currentEnd = new Date(currentYear, 11, 31);
    //currentEnd.setHours(0,0,0,0);
function start() {
  var ID = parent.Xrm.Page.data.entity.getId().substring(1, 37); 
    var fetchxml = `














   










`;

    var encodedFetchXML = encodeURIComponent(fetchxml);
    var queryPath = "/api/data/v8.2/contractdetails?fetchXml="   encodedFetchXML;
    var requestPath = parent.Xrm.Page.context.getClientUrl()   queryPath;

    var req = new XMLHttpRequest();
    req.open("GET", requestPath, true);
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.setRequestHeader("Prefer", "odata.include-annotations=\"*\"");
    req.onreadystatechange = function ()

    {

        if (this.readyState === 4)

        {

            this.onreadystatechange = null;
            if (this.status === 200)

            {
                var returned = JSON.parse(this.responseText);
                var results = returned.value;
                
                getContractInfo(results);
            } else

            {

                alert(this.statusText);

            }

        }

    };

    req.send();
}

function test(data, newInvoiceID) {
    var data = data;
    var ID = newInvoiceID;

    for (var i = 0; i < data.length; i  ) {
        var existingProduct = data[i];
        
       if(Date.parse(existingProduct.activeon) < Date.parse(currentStart)) {
           
           if(Date.parse(existingProduct.expireson) > Date.parse(currentEnd)) {
               var lineitem = {};
        lineitem["productid@odata.bind"] = "/products("   existingProduct._productid_value   ")";
        lineitem["uomid@odata.bind"] = "/uoms("   existingProduct._uomid_value   ")";
        lineitem["new_contractactiveon"] = currentStart;
        lineitem["new_contractexpireson"] = currentEnd;
        lineitem["new_ortfirma@odata.bind"] = "/accounts("   existingProduct._new_ortfirma_value   ")";
        lineitem.quantity = existingProduct.initialquantity;
        lineitem["invoiceid@odata.bind"] = "/invoices("  ID  ")";
        lineitem["manualdiscountamount"] = existingProduct.discount;
        test3(lineitem, ID);
           }
           
           if(Date.parse(existingProduct.expireson) < Date.parse(currentEnd)) {
               var lineitem = {};
        lineitem["productid@odata.bind"] = "/products("   existingProduct._productid_value   ")";
        lineitem["uomid@odata.bind"] = "/uoms("   existingProduct._uomid_value   ")";
        lineitem["new_contractactiveon"] = currentStart;
        lineitem["new_contractexpireson"] = existingProduct.expireson;
        lineitem["new_ortfirma@odata.bind"] = "/accounts("   existingProduct._new_ortfirma_value   ")";
        lineitem.quantity = existingProduct.initialquantity;
        lineitem["invoiceid@odata.bind"] = "/invoices("  ID  ")";
        test3(lineitem, ID);
           }
           
       } else if(Date.parse(existingProduct.activeon) > Date.parse(currentStart)) {
           
           if(Date.parse(existingProduct.expireson) > Date.parse(currentEnd)) {
                var lineitem = {};
        lineitem["productid@odata.bind"] = "/products("   existingProduct._productid_value   ")";
        lineitem["uomid@odata.bind"] = "/uoms("   existingProduct._uomid_value   ")";
        lineitem["new_contractactiveon"] = existingProduct.activeon;
        lineitem["new_contractexpireson"] = currentEnd;
        lineitem["new_ortfirma@odata.bind"] = "/accounts("   existingProduct._new_ortfirma_value   ")";
        lineitem.quantity = existingProduct.initialquantity;
        lineitem["invoiceid@odata.bind"] = "/invoices("  ID  ")";
        lineitem["manualdiscountamount"] = existingProduct.discount;
        test3(lineitem, ID);
           }
           
           if(Date.parse(existingProduct.expireson) < Date.parse(currentEnd)) {
                var lineitem = {};
        lineitem["productid@odata.bind"] = "/products("   existingProduct._productid_value   ")";
        lineitem["uomid@odata.bind"] = "/uoms("   existingProduct._uomid_value   ")";
        lineitem["new_contractactiveon"] = existingProduct.activeon;
        lineitem["new_contractexpireson"] = existingProduct.expireson;
        lineitem["new_ortfirma@odata.bind"] = "/accounts("   existingProduct._new_ortfirma_value   ")";
        lineitem.quantity = existingProduct.initialquantity;
        lineitem["invoiceid@odata.bind"] = "/invoices("  ID  ")";
        test3(lineitem, ID);
           }
           
       }
            
            
            
        
        }
    }         

function test3(data, invoiceID) {
    var req = new XMLHttpRequest();
req.open("POST", parent.Xrm.Page.context.getClientUrl()   "/api/data/v8.2/invoicedetails", false);
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.onreadystatechange = function() {
    if (this.readyState === 4) {
        req.onreadystatechange = null;
        if (this.status === 204) {
            var uri = this.getResponseHeader("OData-EntityId");
            var regExp = /\(([^)] )\)/;
            var matches = regExp.exec(uri);
            var newEntityId = matches[1];
        } else {
            parent.Xrm.Utility.alertDialog(this.statusText);
        }
    }
};
req.send(JSON.stringify(data));
setTimeout(50000, Xrm.Utility.openEntityForm("invoice", invoiceID).then(
    function (success) {
        console.log(success);
    },
    function (error) {
        console.log(error);
    }));
}



//info from contract that is going to be used in the invoice
function getContractInfo(data) {
    var ID = parent.Xrm.Page.data.entity.getId().substring(1, 37);

    var req = new XMLHttpRequest();
    req.open("GET", parent.Xrm.Page.context.getClientUrl()   "/api/data/v8.2/contracts("   ID   ")?$select=activeon,_customerid_value,expireson,_serviceaddress_value,title&$expand=contract_line_items($select=activeon,expireson,initialquantity,price,title,new_thisstart, new_thisend)", true);
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.setRequestHeader("Prefer", "odata.include-annotations=\"*\"");
    req.onreadystatechange = function () {
        if (this.readyState === 4) {
            req.onreadystatechange = null;
            if (this.status === 200) {
                var result = JSON.parse(this.response);
                var activeon = result["new_thisstart"];
                var _customerid_value = result["_customerid_value"];
                var expireson = result["new_thisend"];
                var title = result["title"];

                createInvoice(_customerid_value, title, data, activeon, expireson, ID);
            } else {
                parent.Xrm.Utility.alertDialog(this.statusText);
            }
        }
    };
    req.send();
}
//create the invoice
function createInvoice(customer, title, data, activeon, expireson, contractID) {

    var entity = {};
    entity["customerid_account@odata.bind"] = "/accounts("   customer   ")";
    entity["pricelevelid@odata.bind"] = "/pricelevels(be2084c3-1e50-eb11-bc99-00155db20709)";
    entity.name = title;
    entity["transactioncurrencyid@odata.bind"] = "/transactioncurrencies(59f3fa86-385a-ea11-bc6a-00155db20709)";
    entity["new_VertragId@odata.bind"] = "/contracts(" contractID ")";
    entity.new_contractactiveon = activeon;
    entity.new_contractexpireson = expireson;

    var req = new XMLHttpRequest();
    req.open("POST", parent.Xrm.Page.context.getClientUrl()   "/api/data/v8.2/invoices", true);
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.onreadystatechange = function () {
        if (this.readyState === 4) {
            req.onreadystatechange = null;
            if (this.status === 204) {
                var uri = this.getResponseHeader("OData-EntityId");
                var regExp = /\(([^)] )\)/;
                var matches = regExp.exec(uri);
                var newEntityId = matches[1];
                var invoiceID = this.getResponseHeader("OData-EntityId").substring(67,103); 
                test(data, invoiceID);

                
                
            } else {
                parent.Xrm.Utility.alertDialog(this.statusText);
            }
        }
    };
    req.send(JSON.stringify(entity));

}





I believe this part is at fault here:

setTimeout(50000, Xrm.Utility.openEntityForm("invoice", invoiceID).then(
function (success) {
console.log(success);
},
function (error) {
console.log(error);
}));

I'd like to open my freshly created invoice record after all of its line items are created. As of now line item creation is incomplete and yet it jumps to the record

I would appreciate your feedback and solutions as I am not too well-versed in coding. Overall improvements to my code would be very welcome as well :)

I have the same question (0)
  • Suggested answer
    Pradeep Rai Profile Picture
    5,489 Moderator on at

    Hi,

    Please try below code:

    Code:

    var currentYear = new Date().getFullYear();
    var currentStart = new Date(currentYear, 0, 1);
    //currentStart.setHours(0,0,0,0);
    var currentEnd = new Date(currentYear, 11, 31);
    //currentEnd.setHours(0,0,0,0);
    function start() {
        var ID = parent.Xrm.Page.data.entity.getId().substring(1, 37);
        var fetchxml = `
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                           
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        `;
    
        var updatedfetchXml = "?fetchXml="   fetchxml;
    
        Xrm.WebApi.retrieveMultipleRecords("contractdetail", updatedfetchXml).then(
            function success(result) {
                if (result != undefined &&
                    result.entities != undefined &&
                    result.entities.length > 0) {
                    getContractInfo(result.entities);
                }
            },
            function (error) {
                console.log(error.message);
                // handle error conditions
            }
        );
    }
    
    function test(data, newInvoiceID) {
        var data = data;
        var ID = newInvoiceID;
    
        for (var i = 0; i < data.length; i  ) {
            var existingProduct = data[i];
            var lineitem = {};
            lineitem["productid@odata.bind"] = "/products("   existingProduct._productid_value   ")";
            lineitem["uomid@odata.bind"] = "/uoms("   existingProduct._uomid_value   ")";
            lineitem["new_contractactiveon"] = currentStart;
            lineitem["new_ortfirma@odata.bind"] = "/accounts("   existingProduct._new_ortfirma_value   ")";
            lineitem["invoiceid@odata.bind"] = "/invoices("   ID   ")";
            lineitem.quantity = existingProduct.initialquantity;
            if (Date.parse(existingProduct.activeon) < Date.parse(currentStart)) {
    
                if (Date.parse(existingProduct.expireson) > Date.parse(currentEnd)) {
                    lineitem["new_contractexpireson"] = currentEnd;
                    lineitem["manualdiscountamount"] = existingProduct.discount;
                }
    
                if (Date.parse(existingProduct.expireson) < Date.parse(currentEnd)) {
                    var lineitem = {};
    
                    lineitem["new_contractexpireson"] = existingProduct.expireson;
    
                }
    
            } else if (Date.parse(existingProduct.activeon) > Date.parse(currentStart)) {
    
                if (Date.parse(existingProduct.expireson) > Date.parse(currentEnd)) {
                    var lineitem = {};
                    lineitem["new_contractactiveon"] = existingProduct.activeon;
                    lineitem["new_contractexpireson"] = currentEnd;
                    lineitem["manualdiscountamount"] = existingProduct.discount;
                }
    
                if (Date.parse(existingProduct.expireson) < Date.parse(currentEnd)) {
                    var lineitem = {};
                    lineitem["new_contractactiveon"] = existingProduct.activeon;
                    lineitem["new_contractexpireson"] = existingProduct.expireson;
                }
    
            }
    
            // create account record
            Xrm.WebApi.createRecord("invoicedetail", lineitem).then(
                function success(result) {
                    if (data.length == i  ) {
                        Xrm.Utility.openEntityForm("invoice", newInvoiceID).then(
                            function (success) {
                                console.log(success);
                            },
                            function (error) {
                                console.log(error);
                            })
                    }
                },
                function (error) {
                    console.log(error.message);
                    // handle error conditions
                }
            );
    
    
    
        }
    }
    
    //info from contract that is going to be used in the invoice
    function getContractInfo(data) {
        var ID = parent.Xrm.Page.data.entity.getId().substring(1, 37);
    
        Xrm.WebApi.retrieveRecord("contract", ID, "?$select=activeon,_customerid_value,expireson,_serviceaddress_value,title&$expand=contract_line_items($select=activeon,expireson,initialquantity,price,title,new_thisstart, new_thisend)").then(
            function success(result) {
                var activeon = result["new_thisstart"];
                var _customerid_value = result["_customerid_value"];
                var expireson = result["new_thisend"];
                var title = result["title"];
    
                createInvoice(_customerid_value, title, data, activeon, expireson, ID);
    
            },
            function (error) {
                console.log(error.message);
                // handle error conditions
            }
        );
    }
    //create the invoice
    function createInvoice(customer, title, data, activeon, expireson, contractID) {
    
        var entity = {};
        entity["customerid_account@odata.bind"] = "/accounts("   customer   ")";
        entity["pricelevelid@odata.bind"] = "/pricelevels(be2084c3-1e50-eb11-bc99-00155db20709)";
        entity.name = title;
        entity["transactioncurrencyid@odata.bind"] = "/transactioncurrencies(59f3fa86-385a-ea11-bc6a-00155db20709)";
        entity["new_VertragId@odata.bind"] = "/contracts("   contractID   ")";
        entity.new_contractactiveon = activeon;
        entity.new_contractexpireson = expireson;
    
    
    
        // create account record
        Xrm.WebApi.createRecord("invoice", entity).then(
            function success(result) {
                test(data, result.id);
            },
            function (error) {
                console.log(error.message);
                // handle error conditions
            }
        );
    }


    In above code i have removed the XmlHttpRequest with Xrm.webapi.

    In dynamics, whenever we want to perform CRUD operation then we can use the XRM.WEBAPI.

    Reference link:
    https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/xrm-webapi/createrecord

    Thanks,
    Pradeep.
    Please mark this as VERIFIED if it helps.

  • MusterMax Profile Picture
    75 on at

    Hi, thank you for your reply! I forgot to mention we are on CRM 2016 (8.2) which does not have access to xrm.webapi unfortunately.

    Is there perhaps another way?

  • Suggested answer
    Pradeep Rai Profile Picture
    5,489 Moderator on at

    Hi,

    Yes, we can use the David Yack library.

    But i have update your code please check below code:

    var currentYear = new Date().getFullYear();
    var currentStart = new Date(currentYear, 0, 1);
    //currentStart.setHours(0,0,0,0);
    var currentEnd = new Date(currentYear, 11, 31);
    //currentEnd.setHours(0,0,0,0);
    function start() {
        var ID = parent.Xrm.Page.data.entity.getId().substring(1, 37);
        var fetchxml = `
    
    
    
    
    
    
    
    
    
    
    
    
    
    
       
    
    
    
    
    
    
    
    
    
    
    `;
    
        var encodedFetchXML = encodeURIComponent(fetchxml);
        var queryPath = "/api/data/v8.2/contractdetails?fetchXml="   encodedFetchXML;
        var requestPath = parent.Xrm.Page.context.getClientUrl()   queryPath;
    
        var req = new XMLHttpRequest();
        req.open("GET", requestPath, true);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("Prefer", "odata.include-annotations=\"*\"");
        req.onreadystatechange = function () {
    
            if (this.readyState === 4) {
    
                this.onreadystatechange = null;
                if (this.status === 200) {
                    var returned = JSON.parse(this.responseText);
                    var results = returned.value;
    
                    getContractInfo(results);
                } else {
    
                    alert(this.statusText);
    
                }
    
            }
    
        };
    
        req.send();
    }
    
    function test(data, newInvoiceID) {
        var data = data;
        var ID = newInvoiceID;
    
        for (var i = 0; i < data.length; i  ) {
            var existingProduct = data[i];
            var lineitem = {};
            lineitem["productid@odata.bind"] = "/products("   existingProduct._productid_value   ")";
            lineitem["uomid@odata.bind"] = "/uoms("   existingProduct._uomid_value   ")";
            lineitem["new_contractactiveon"] = currentStart;
            lineitem["new_ortfirma@odata.bind"] = "/accounts("   existingProduct._new_ortfirma_value   ")";
            lineitem["invoiceid@odata.bind"] = "/invoices("   ID   ")";
            lineitem.quantity = existingProduct.initialquantity;
            if (Date.parse(existingProduct.activeon) < Date.parse(currentStart)) {
    
                if (Date.parse(existingProduct.expireson) > Date.parse(currentEnd)) {
                    lineitem["new_contractexpireson"] = currentEnd;
                    lineitem["manualdiscountamount"] = existingProduct.discount;
                }
    
                if (Date.parse(existingProduct.expireson) < Date.parse(currentEnd)) {
                    var lineitem = {};
    
                    lineitem["new_contractexpireson"] = existingProduct.expireson;
    
                }
    
            } else if (Date.parse(existingProduct.activeon) > Date.parse(currentStart)) {
    
                if (Date.parse(existingProduct.expireson) > Date.parse(currentEnd)) {
                    var lineitem = {};
                    lineitem["new_contractactiveon"] = existingProduct.activeon;
                    lineitem["new_contractexpireson"] = currentEnd;
                    lineitem["manualdiscountamount"] = existingProduct.discount;
                }
    
                if (Date.parse(existingProduct.expireson) < Date.parse(currentEnd)) {
                    var lineitem = {};
                    lineitem["new_contractactiveon"] = existingProduct.activeon;
                    lineitem["new_contractexpireson"] = existingProduct.expireson;
                }
    
            }
            //API CALL
            var req = new XMLHttpRequest();
            req.open("POST", parent.Xrm.Page.context.getClientUrl()   "/api/data/v8.2/invoicedetails", false);
            req.setRequestHeader("OData-MaxVersion", "4.0");
            req.setRequestHeader("OData-Version", "4.0");
            req.setRequestHeader("Accept", "application/json");
            req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
            req.onreadystatechange = function () {
                if (this.readyState === 4) {
                    req.onreadystatechange = null;
                    if (this.status === 204) {
                        var uri = this.getResponseHeader("OData-EntityId");
                        var regExp = /\(([^)] )\)/;
                        var matches = regExp.exec(uri);
                        var newEntityId = matches[1];
                        if (data.length == i) {
                            Xrm.Utility.openEntityForm("invoice", newInvoiceID).then(
                                function (success) {
                                    console.log(success);
                                },
                                function (error) {
                                    console.log(error);
                                })
                        }
                    } else {
                        parent.Xrm.Utility.alertDialog(this.statusText);
                    }
                }
            };
            req.send(JSON.stringify(lineitem));
          
        }
    }
    
    //info from contract that is going to be used in the invoice
    function getContractInfo(data) {
        var ID = parent.Xrm.Page.data.entity.getId().substring(1, 37);
    
        var req = new XMLHttpRequest();
        req.open("GET", parent.Xrm.Page.context.getClientUrl()   "/api/data/v8.2/contracts("   ID   ")?$select=activeon,_customerid_value,expireson,_serviceaddress_value,title&$expand=contract_line_items($select=activeon,expireson,initialquantity,price,title,new_thisstart, new_thisend)", true);
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("Prefer", "odata.include-annotations=\"*\"");
        req.onreadystatechange = function () {
            if (this.readyState === 4) {
                req.onreadystatechange = null;
                if (this.status === 200) {
                    var result = JSON.parse(this.response);
                    var activeon = result["new_thisstart"];
                    var _customerid_value = result["_customerid_value"];
                    var expireson = result["new_thisend"];
                    var title = result["title"];
    
                    createInvoice(_customerid_value, title, data, activeon, expireson, ID);
                } else {
                    parent.Xrm.Utility.alertDialog(this.statusText);
                }
            }
        };
        req.send();
    }
    //create the invoice
    function createInvoice(customer, title, data, activeon, expireson, contractID) {
    
        var entity = {};
        entity["customerid_account@odata.bind"] = "/accounts("   customer   ")";
        entity["pricelevelid@odata.bind"] = "/pricelevels(be2084c3-1e50-eb11-bc99-00155db20709)";
        entity.name = title;
        entity["transactioncurrencyid@odata.bind"] = "/transactioncurrencies(59f3fa86-385a-ea11-bc6a-00155db20709)";
        entity["new_VertragId@odata.bind"] = "/contracts("   contractID   ")";
        entity.new_contractactiveon = activeon;
        entity.new_contractexpireson = expireson;
    
        var req = new XMLHttpRequest();
        req.open("POST", parent.Xrm.Page.context.getClientUrl()   "/api/data/v8.2/invoices", true);
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.onreadystatechange = function () {
            if (this.readyState === 4) {
                req.onreadystatechange = null;
                if (this.status === 204) {
                    var uri = this.getResponseHeader("OData-EntityId");
                    var regExp = /\(([^)] )\)/;
                    var matches = regExp.exec(uri);
                    var newEntityId = matches[1];
                    var invoiceID = this.getResponseHeader("OData-EntityId").substring(67, 103);
                    test(data, invoiceID);
    
    
    
                } else {
                    parent.Xrm.Utility.alertDialog(this.statusText);
                }
            }
        };
        req.send(JSON.stringify(entity));
    
    }

    Thanks,
    Pradeep.
    Please mark this as VERIFIED if it helps.

  • MusterMax Profile Picture
    75 on at

    Hello Pradeep Rai,

    i tried your solution but unfortuantely it did not work for me

    Do you have any other ideas perhaps?

    Kindly

    MusterMax

  • Suggested answer
    Pradeep Rai Profile Picture
    5,489 Moderator on at

    Hi MusterMax,

    Yes, I have one suggestion instead of managing everything on client side.

    We can move some logic in Plugin side as below:

    1. We will create one custom action

    2. On custom action we will register one plugin

    3. The plugin is responsible for performing all business logic which you have done in script.

    4. Now, in plugin we will set output parameter. In this output parameter we set record Id which we need to open.

    5. Now in our script we will call the custom. This point will trigger the plugin.

    Overview:

    Script-> Custom Script code to call custom action-> upon action call, plugin will be triggered->

    Plugin  logic executed -> recordId will be found in success call in script code.

    Now. we can open the newly created record using script method.

    Thanks,

    Pradeep.

    Please mark this as VERIFIED if it helps.

Under review

Thank you for your reply! To ensure a great experience for everyone, your content is awaiting approval by our Community Managers. Please check back later.

Helpful resources

Quick Links

Season of Sharing Community Challenge Launch!

Jump in, show your community spirit, and win prizes!

Women in Power Builds Momentum

Expanding mentorship, skilling, and AI innovation

Congratulations to the May Top 10 Community Leaders

These are the community rock stars!

Leaderboard > Customer experience | Sales, Customer Insights, CRM

#1
Hamza H Profile Picture

Hamza H 140 Super User 2026 Season 1

#2
Nagaraju_Matta Profile Picture

Nagaraju_Matta 128

#3
Abhilash Warrier Profile Picture

Abhilash Warrier 70 Super User 2026 Season 1

Last 30 days Overall leaderboard

Product updates

Dynamics 365 release plans