Copy Invoice functionality: Example of ribbon modification + XrmSvcToolkit advanced Javascripting
For our internal CRM we needed a functionality that enables you to copy an invoice with all of the related invoice products. This is a very useful functionality for monthly maintenance invoices. So I decided to create a ribbon button and use XrmSvcToolkit to manipulate with the data.
This will be a more technical blog post and I’m assuming that you already know the basics of ribbon customization and JavaScript. If you’re not familiar with ribbon customization there is a lot of available material on the web, and if you want to learn about XrmSvcToolkit you can read my previous blog post.
We’ll divide this post in two parts:
- Creating the ribbon button
- Implementing the business logic
1. Creating the ribbon button
So first off we need an icon for the ribbon button. Find a suitable 32×32 png transparent icon and upload it to CRM. I gave it a schema name new_copyinvoice.png.
Next we need a JavaScript library that will hold our function that will do the copying. So create a new one and give it a schema name new_copyinvoice. Create and empty function that accepts argument invoiceid.
function copyInvoice(invoiceid) { }
Now we’re ready to create our ribbon button. Since manual ribbon customization is quite hard and error prone I’m using the Visual Ribbon Editor. So fire it up, connect to your CRM and open the Invoice ribbon. Choose the ribbon type Homepage and create a new button after the Edit button. You can see the settings for the button in the following pictures. In the enable rules we configure that the button is only enabled if a single invoice is selected.
Now we need to specify what happens when you click the button. So in the action tab we add a new JavaScript function action and call our function from the library. We then need to pass a Crm parameter to the function so add a new CrmParameter of type FirstSelectedId. This will pass the guid of the selected invoice to the function.
Click save the ribbon and now you should see your button displayed.
Now here comes the tricky part. Since we will be using the XrmSvcToolkit to read and create data we need to load it up when the ribbon loads. How do you achieve that? Well a small trick is to export the RibbonEditor solution, unzip it and edit the customization.xmlfile. Add two Javascript actions that call isNan function (is not a number function). We call this function just to get the library loaded. Zip the solution and load it up in CRM.
Now we’re ready for the second part.
2. Implementing the business logic
Since we receive in our function only the invoice guid, first we need to fetch the invoice entity. We also need to get the server url since we’’ll need it to open the newly created invoice.
var invoice; var serverUrl = Xrm.Page.context.getServerUrl(); XrmSvcToolkit.retrieve({ entityName: "Invoice", id: invoiceid, async: false, successCallback: function (result) { invoice = result; }, errorCallback: function (error) { for(var propertyName in error) { alert(error[propertyName]);} } });
Next we need to get all the related invoice products. Here we’re using the OData filtering methods. What we get back is an array of invoice products.
var invoiceGuid = invoiceid.replace("{","'").replace("}","'"); var filterQuery = "?$filter=InvoiceId/Id eq guid" + invoiceGuid; rmSvcToolkit.retrieveMultiple({ entityName: "InvoiceDetail", async: false, odataQuery: filterQuery, successCallback: function (result) { products = result; }, errorCallback: function (error) { for(var propertyName in error) { alert(error[propertyName]);} } );
Now we have invoice and related products and we can start copying them. In this portion of the code we are mapping the attributes of the invoice we retrived to the attributes of new invoice entity. Important notice: We’re using the attribute SchemaName so be careful regarding the casing. So choose the attributes you want to copy and map them. If the copying of the invoice is successful we need to copy all of the related products. I created a function that copies the product called copyInvoiceProduct(). When all of the invoice products are copied we open a new window that opens up this new invoice.
var today = new Date(); XrmSvcToolkit.createRecord({ entityName: "Invoice", entity: {Name: invoice.Name, CustomerId: invoice.CustomerId, PriceLevelId: invoice.PriceLevelId, TransactionCurrencyId: invoice.TransactionCurrencyId, BillTo_City: invoice.BillTo_City, BillTo_Country: invoice.BillTo_Country, BillTo_PostalCode: invoice.BillTo_PostalCode, BillTo_Line1: invoice.BillTo_Line1, ShipTo_Country: invoice.ShipTo_Country, ShipTo_Line1: invoice.ShipTo_Line1, ShipTo_PostalCode: invoice.ShipTo_PostalCode, ShipTo_City: invoice.ShipTo_City, DateDelivered: today }, async: false, successCallback: function (result) { for (i=0;i<products.length;i++) { copyInvoiceProduct(products[i],result); } window.open(serverUrl + "/main.aspx?etn=invoice&pagetype=entityrecord&id=%7b" + result.InvoiceId + "%7d",'_blank'); }, errorCallback: function (error) { for(var propertyName in error) { alert(error[propertyName]);} } });
function copyInvoiceProduct(product, invoice) { XrmSvcToolkit.createRecord({ entityName: "InvoiceDetail", entity: { InvoiceId: {__metadata: { type: "Microsoft.Crm.Sdk.Data.Services.EntityReference" }, Id: invoice.InvoiceId,LogicalName: "invoice" }, ProductDescription: product.ProductDescription, Quantity: product.Quantity, PricePerUnit: product.PricePerUnit, Tax: product.Tax, IsProductOveridden: product.IsProductOveridden, ManualDiscountAmount: product.ManualDiscountAmount }, async: false, successCallback: function (result) { }, errorCallback: function (error) { for(var propertyName in error) { alert(error[propertyName]);} } }); }
And that’s it. Simple isn’t it? :) Here is the complete function: copyInvoice.js
I know it looks complicated but once you get a hang of it you’ll be faster and more creative.
I didn’t go into all of the details since it would take me another page or two to have everything covered. So if you have any questions feel free to ask.
Below is the URL -
No comments:
Post a Comment