In Dynamics 365 environments where project resources are pivotal to billing processes, automating the generation of invoice lines can enhance efficiency and accuracy. Today, we’ll explore how to implement custom JavaScript automation to create invoice lines based on project resources with the click of a button.
Understanding the Scenario
Imagine you are working within a Dynamics 365 environment where project resources are assigned to specific projects, each resource having its own agreed-upon rate and role. To automate the creation of invoice lines based on these project resources, we will implement a custom JavaScript function triggered by clicking a button added to the invoice form. This automation will streamline invoicing workflows and ensure accuracy in billing processes within Dynamics 365.
Prerequisites
Before diving into implementation, ensure the following:
- Access to a Dynamics 365 environment with necessary privileges.
- Basic understanding of JavaScript and Dynamics 365 development.
Implementation: Creating the SyncResources Function
We’ll start by defining the SyncResources function, which orchestrates the automation of invoice line creation based on project resources.
async function SyncResources(invoiceid) {
var formContext = Xrm.Page;
if (invoiceid != null) {
invoiceid = invoiceid.replace("{", "").replace("}", "");
}
console.log(`InvoiceId is ${invoiceid}`);
var projectlookup = formContext.getAttribute("dyn_projectid").getValue();
var projectId = projectlookup[0].id.replace("{", "").replace("}", "");
console.log(`ProjectId is ${projectId}`);
// Retrieve invoice line records
var InvoiceLineRecords = await Xrm.WebApi.retrieveMultipleRecords("dyn_invoiceline", `?$filter=_dyn_invoiceid_value eq '${invoiceid}' and dyn_billingtype eq 1`);
var InvoiceLineArray = InvoiceLineRecords.entities;
// console.log("InvoiceLines Array are:");
// console.log(JSON.stringify(InvoiceLineArray, null, 2));
// Retrieve project resources
var filter = `?$filter=_dyn_projectid_value eq ${projectId}`;
var results = await Xrm.WebApi.retrieveMultipleRecords("dyn_projectresource", filter);
var ResourceArray = results.entities;
// Remove duplicates based on _dyn_resourceid_value--- Code Starts
// Remove duplicates based on _dyn_resourceid_value
var uniqueResourceArray = ResourceArray.filter((resource, index, self) =>
index === self.findIndex((r) => r._dyn_resourceid_value === resource._dyn_resourceid_value)
);
// Remove duplicates based on _dyn_resourceid_value--- Code Ends
if (InvoiceLineArray.length == 0 && uniqueResourceArray.length > 0) {
// No existing invoice lines, but there are project resources
for (var i = 0; i < uniqueResourceArray.length; i++) {
var data = {};
data["dyn_invoiceid@odata.bind"] = `/dyn_invoices(${invoiceid})`;
data["dyn_priceperunit"] = parseFloat(uniqueResourceArray[i].dyn_agreedrate);
data["dyn_resourceid@odata.bind"] = `/dyn_resources(${uniqueResourceArray[i]._dyn_resourceid_value})`;
data["dyn_unitid@odata.bind"] = `/dyn_units(${uniqueResourceArray[i]._dyn_unitid_value})`;
data["dyn_resourceroleid@odata.bind"] = `/dyn_resourceroles(${uniqueResourceArray[i]._dyn_resourceroleid_value})`;
try {
await Xrm.WebApi.createRecord("dyn_invoiceline", data);
// Run the code after a successful record creation
setTimeout(function() {
// Get the subgrid control
var subgridControl = formContext.getControl("Subgrid_new_1");
// Check if the subgrid control exists
if (subgridControl != null) {
// Refresh the subgrid
subgridControl.refresh();
} else {
console.error("Subgrid control not found.");
}
}, 5000); // 5000 milliseconds = 5 seconds
} catch (error) {
console.error(`Error creating records: ${error.message}`);
}
}
} else if (InvoiceLineArray.length !== uniqueResourceArray.length) {
// Existing invoice lines count doesn't match project resources count
// Add new invoice lines based on newly added project resources
for (var i = 0; i < uniqueResourceArray.length; i++) {
var resourceExists = InvoiceLineArray.some(function(invoiceLine) {
// Check if the resource already exists in the invoice lines
return invoiceLine._dyn_resourceid_value === uniqueResourceArray[i]._dyn_resourceid_value;
});
if (!resourceExists) {
// Create a new invoice line for the resource
var data = {};
data["dyn_invoiceid@odata.bind"] = `/dyn_invoices(${invoiceid})`;
data["dyn_priceperunit"] = parseFloat(uniqueResourceArray[i].dyn_agreedrate);
data["dyn_resourceid@odata.bind"] = `/dyn_resources(${uniqueResourceArray[i]._dyn_resourceid_value})`;
data["dyn_unitid@odata.bind"] = `/dyn_units(${uniqueResourceArray[i]._dyn_unitid_value})`;
data["dyn_resourceroleid@odata.bind"] = `/dyn_resourceroles(${uniqueResourceArray[i]._dyn_resourceroleid_value})`;
try {
await Xrm.WebApi.createRecord("dyn_invoiceline", data);
// Run the code after a successful record creation
setTimeout(function() {
// Get the subgrid control
var subgridControl = formContext.getControl("Subgrid_new_1");
// Check if the subgrid control exists
if (subgridControl != null) {
// Refresh the subgrid
subgridControl.refresh();
} else {
console.error("Subgrid control not found.");
}
}, 5000); // 5000 milliseconds = 5 seconds
} catch (error) {
console.error(`Error creating record: ${error.message}`);
}
}
}
} else {
// No need to add new invoice lines
var alertStrings = {
confirmButtonLabel: "OK",
text: "Invoice Lines already added based on Resources."
};
var alertOptions = {
height: 120,
width: 260
};
try {
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions);
} catch (error) {
console.error(`Error displaying alert: ${error.message}`);
}
}
}
Triggering the Automation
To invoke the SyncResources function, add a button to the invoice form in Dynamics 365 and associate the button’s click event with the function. Here’s a high-level overview of how this can be achieved:
- Add a Button: Edit the invoice form in Dynamics 365 and insert a new button element.
- Configure Button Click Event: Specify the
SyncResourcesfunction as the handler for the button’s click event using form customization options. - Test the Automation: Click the button on the invoice form to trigger the automation and observe the creation of invoice lines based on project resources.
Conclusion
By leveraging custom JavaScript automation within Dynamics 365, organizations can streamline invoicing workflows and ensure accuracy in billing processes. The SyncResources function showcased in this blog post exemplifies how Dynamics 365’s extensibility can be harnessed to meet specific business needs effectively.
Implementing similar automation not only saves time but also enhances data integrity and operational efficiency within Dynamics 365 project management environments.
