This is an article to apply an automation that when creating an invoice in CRM from a related quote, it maps in the fields.
Why?
The process should be that you go to the CRM quote record and click on "Convert" > then select "Invoice", and it should map the fields over. Great! But what if someone skips the process, in other words, goes to the quote record, and clicks on the plus icon next to "Invoices" in the left sidebar?
This script will map over the core fields, address, and line item. This took me a while to get the right syntax for writing to the invoice line items as well as setting the value of lookup fields so I thought it was worth an article and may help someone else (or even myself) in the future.
At time of print (9th October 2025), for some reason, if you click on the plus icon next to Sales Order (creates a new sales order) on the quote record, the fields auto-map. The client script below does the same for invoice as the quote lookup is a custom field on the invoice.
How?
First, we'll setup a client script specifying what triggers it (on load of the page when creating an invoice) then I'll just put the client script I used to map over all the applicable field values from the quote to the invoice within ZohoCRM.
Set up the Client Script
- Login to ZohoCRM > Setup (cog icon next to profile photo) > Developer Hub > Client Script > New Script
- Give it a name, I'm calling mine Invoice TBC
- Give it a description. I said "Sets the subject/name of the invoice to TBC when first created. Maps values from the related quote if loaded from somewhere other than converting quote to invoice.". Note that I have a workflow that when saved, uses an auto-number field, pads it with zeros, and prefixes it with "INV-" overwriting the invoice subject.
- Category is Module, Page is Create Page (Standard) (change this depending on if you're using a canvas or not)
- Module is Invoices
- Type is Page Event
- Event is onLoad
- Click on the button Next
the Script
copyraw
/* ******************************************************************************* Function: Invoice TBC Label: Invoice TBC Page Details Category Module Page Create Page (Standard) Module Invoices Layout Standard Event Details Event Type Page Event Event onLoad Purpose: Loads in a "TBC" so that the the naming of the invoice can happen on save Maps other fields to fill in the blanks Inputs: - Outputs: - Date Created: 2025-10-07 (Joel Lipman) - Initial release Date Modified: ??? - ??? More Information: https://www.zohocrm.dev/explore/client-script/webapi/Modules#fetchById ******************************************************************************* */ // for debugging console.clear(); // set the value of the subject ZDK.Page.getField('Subject').setValue('TBC'); // get the quote values (if linked - note this is a custom lookup field on the invoice) var o_InvoicedQuote = ZDK.Page.getField('Quote').getValue(); // go down this route if a quote is specified (they were on the quote and clicked on the plus next to 'invoice') if (o_InvoicedQuote != null) { // check on the quote associated to this invoice var o_QuoteDetails = ZDK.Apps.CRM.Quotes.fetchById(o_InvoicedQuote.id); console.log(o_QuoteDetails); // consider map over deal from quote to invoice lookup field (if quote deal is not blank) if (o_QuoteDetails.Deal_Name_Lookup_Id != null) { // check on the deal record on this invoice var o_LinkedDeal = ZDK.Page.getField('Deal_Name__s'); // if linked deal is blank, then see if we can find a relevant deal if (o_LinkedDeal.getValue() == null) { // if deal on the quote is not blank, then let's associate that one to this invoice if (o_QuoteDetails.Deal_Name_Lookup_Id != null) { // retrieve the deal record var o_OppDetails = ZDK.Apps.CRM.Deals.fetchById(o_QuoteDetails.Deal_Name_Lookup_Id); console.log(o_OppDetails); // set the deal lookup value on this page ZDK.Page.getField('Deal_Name__s').setValue({ id: o_QuoteDetails.Deal_Name_Lookup_Id, name: o_OppDetails._Deal_Name }); } } } // check on the sales order record on this invoice var o_LinkedSO = ZDK.Page.getField('Sales_Order'); // if linked SO is blank, then see if we can find a relevant SO if (o_LinkedSO.getValue() == null) { // find sales orders related to this quote (fetchRelatedRecords doesn't seem to work for me) var a_QuotedSalesOrders = ZDK.Apps.CRM.Sales_Orders.searchByCriteria("(Quote_Name:equals:" + o_QuoteDetails.id + ")"); // convert to a usable array var a_QuotedSalesOrders = typeof a_QuotedSalesOrders === "string" ? JSON.parse(a_QuotedSalesOrders) : a_QuotedSalesOrders || []; // set first valid sales order ID var v_FirstSalesOrderID = null; // loop through related sales orders for (var i = 0; i < a_QuotedSalesOrders.length; i++) { // check status is neither cancelled nor expired if (a_QuotedSalesOrders[i].Status !== "Cancelled" && a_QuotedSalesOrders[i].Status !== "Expired") { // found one so let's bail v_FirstSalesOrderID = a_QuotedSalesOrders[i].id; break; } } // if not null (was found) then let's assign it to this invoice if (v_FirstSalesOrderID != null) { // retrieve the deal record var o_SalesOrderDetails = ZDK.Apps.CRM.Sales_Orders.fetchById(v_FirstSalesOrderID); console.log(o_SalesOrderDetails); // set the deal lookup value on this page ZDK.Page.getField('Sales_Order').setValue({ id: o_SalesOrderDetails.id, name: o_SalesOrderDetails.Subject }); } } // this is on create of the invoice, so let's map over the quote fields irrespectively ZDK.Page.getField('Billing_City').setValue(o_QuoteDetails._Billing_City); ZDK.Page.getField('Billing_Country').setValue(o_QuoteDetails._Billing_Country); ZDK.Page.getField('Billing_State').setValue(o_QuoteDetails._Billing_State); ZDK.Page.getField('Billing_Code').setValue(o_QuoteDetails._Billing_Code); ZDK.Page.getField('Billing_Street').setValue(o_QuoteDetails._Billing_Street); ZDK.Page.getField('Shipping_City').setValue(o_QuoteDetails._Shipping_City); ZDK.Page.getField('Shipping_Country').setValue(o_QuoteDetails._Shipping_Country); ZDK.Page.getField('Shipping_State').setValue(o_QuoteDetails._Shipping_State); ZDK.Page.getField('Shipping_Code').setValue(o_QuoteDetails._Shipping_Code); ZDK.Page.getField('Shipping_Street').setValue(o_QuoteDetails._Shipping_Street); ZDK.Page.getField('Currency').setValue(o_QuoteDetails._Currency); ZDK.Page.getField('Terms_and_Conditions').setValue(o_QuoteDetails._Terms_and_Conditions); ZDK.Page.getField('Adjustment').setValue(o_QuoteDetails._Adjustment); // field mapping of quoted items to invoiced items (i console logged out the quote record and used the API names of the invoiced items) // declare new array to store line items for invoice var a_InvoiceLineItems = []; // loop through quoted line items for (i = 0; i < o_QuoteDetails._Product_Details.length; i++) { // initialize new line item var o_NewLineItem = {}; // store this line item as an object o_ThisLineItem = o_QuoteDetails._Product_Details[i]; console.log(o_ThisLineItem); // assigning to a lookup field (Product) var o_ProductDetails = ZDK.Apps.CRM.Products.fetchById(o_ThisLineItem._product_Lookup_Id); o_NewLineItem.Product_Name = { id: o_ProductDetails.id, name: o_ProductDetails.Product_Name }; // other fields for this line item o_NewLineItem.Description = o_ThisLineItem._product_description; o_NewLineItem.Discount = o_ThisLineItem._Discount; o_NewLineItem.List_Price = o_ThisLineItem._list_price; o_NewLineItem.Quantity = o_ThisLineItem._quantity; o_NewLineItem.Tax = o_ThisLineItem._Tax; // going to create a new set of line items a_InvoiceLineItems.push(o_NewLineItem); } // write back the new line items to the invoice ZDK.Page.getField('Invoiced_Items').setValue(a_InvoiceLineItems); }
- /* *******************************************************************************
- Function: Invoice TBC
- Label: Invoice TBC
- Page Details
- Category Module
- Page Create Page (Standard)
- Module Invoices
- Layout Standard
- Event Details
- Event Type Page Event
- Event onLoad
- Purpose: Loads in a "TBC" so that the the naming of the invoice can happen on save
- Maps other fields to fill in the blanks
- Inputs: -
- Outputs: -
- Date Created: 2025-10-07 (Joel Lipman)
- - Initial release
- Date Modified: ???
- - ???
- More Information:
- https://www.zohocrm.dev/explore/client-script/webapi/Modules#fetchById
- ******************************************************************************* */
- // for debugging
- console.clear();
- // set the value of the subject
- ZDK.Page.getField('Subject').setValue('TBC');
- // get the quote values (if linked - note this is a custom lookup field on the invoice)
- var o_InvoicedQuote = ZDK.Page.getField('Quote').getValue();
- // go down this route if a quote is specified (they were on the quote and clicked on the plus next to 'invoice')
- if (o_InvoicedQuote != null) {
- // check on the quote associated to this invoice
- var o_QuoteDetails = ZDK.Apps.crm.Quotes.fetchById(o_InvoicedQuote.id);
- console.log(o_QuoteDetails);
- // consider map over deal from quote to invoice lookup field (if quote deal is not blank)
- if (o_QuoteDetails.Deal_Name_Lookup_Id != null) {
- // check on the deal record on this invoice
- var o_LinkedDeal = ZDK.Page.getField('Deal_Name__s');
- // if linked deal is blank, then see if we can find a relevant deal
- if (o_LinkedDeal.getValue() == null) {
- // if deal on the quote is not blank, then let's associate that one to this invoice
- if (o_QuoteDetails.Deal_Name_Lookup_Id != null) {
- // retrieve the deal record
- var o_OppDetails = ZDK.Apps.crm.Deals.fetchById(o_QuoteDetails.Deal_Name_Lookup_Id);
- console.log(o_OppDetails);
- // set the deal lookup value on this page
- ZDK.Page.getField('Deal_Name__s').setValue({
- id: o_QuoteDetails.Deal_Name_Lookup_Id,
- name: o_OppDetails._Deal_Name
- });
- }
- }
- }
- // check on the sales order record on this invoice
- var o_LinkedSO = ZDK.Page.getField('Sales_Order');
- // if linked SO is blank, then see if we can find a relevant SO
- if (o_LinkedSO.getValue() == null) {
- // find sales orders related to this quote (fetchRelatedRecords doesn't seem to work for me)
- var a_QuotedSalesOrders = ZDK.Apps.crm.Sales_Orders.searchByCriteria("(Quote_Name:equals:" + o_QuoteDetails.id + ")");
- // convert to a usable array
- var a_QuotedSalesOrders = typeof a_QuotedSalesOrders === "string" ? JSON.parse(a_QuotedSalesOrders) : a_QuotedSalesOrders || [];
- // set first valid sales order ID
- var v_FirstSalesOrderID = null;
- // loop through related sales orders
- for (var i = 0; i < a_QuotedSalesOrders.length; i++) {
- // check status is neither cancelled nor expired
- if (a_QuotedSalesOrders[i].Status !== "Cancelled" && a_QuotedSalesOrders[i].Status !== "Expired") {
- // found one so let's bail
- v_FirstSalesOrderID = a_QuotedSalesOrders[i].id;
- break;
- }
- }
- // if not null (was found) then let's assign it to this invoice
- if (v_FirstSalesOrderID != null) {
- // retrieve the deal record
- var o_SalesOrderDetails = ZDK.Apps.crm.Sales_Orders.fetchById(v_FirstSalesOrderID);
- console.log(o_SalesOrderDetails);
- // set the deal lookup value on this page
- ZDK.Page.getField('Sales_Order').setValue({
- id: o_SalesOrderDetails.id,
- name: o_SalesOrderDetails.Subject
- });
- }
- }
- // this is on create of the invoice, so let's map over the quote fields irrespectively
- ZDK.Page.getField('Billing_City').setValue(o_QuoteDetails._Billing_City);
- ZDK.Page.getField('Billing_Country').setValue(o_QuoteDetails._Billing_Country);
- ZDK.Page.getField('Billing_State').setValue(o_QuoteDetails._Billing_State);
- ZDK.Page.getField('Billing_Code').setValue(o_QuoteDetails._Billing_Code);
- ZDK.Page.getField('Billing_Street').setValue(o_QuoteDetails._Billing_Street);
- ZDK.Page.getField('Shipping_City').setValue(o_QuoteDetails._Shipping_City);
- ZDK.Page.getField('Shipping_Country').setValue(o_QuoteDetails._Shipping_Country);
- ZDK.Page.getField('Shipping_State').setValue(o_QuoteDetails._Shipping_State);
- ZDK.Page.getField('Shipping_Code').setValue(o_QuoteDetails._Shipping_Code);
- ZDK.Page.getField('Shipping_Street').setValue(o_QuoteDetails._Shipping_Street);
- ZDK.Page.getField('Currency').setValue(o_QuoteDetails._Currency);
- ZDK.Page.getField('Terms_and_Conditions').setValue(o_QuoteDetails._Terms_and_Conditions);
- ZDK.Page.getField('Adjustment').setValue(o_QuoteDetails._Adjustment);
- // field mapping of quoted items to invoiced items (i console logged out the quote record and used the API names of the invoiced items)
- // declare new array to store line items for invoice
- var a_InvoiceLineItems = [];
- // loop through quoted line items
- for (i = 0; i < o_QuoteDetails._Product_Details.length; i++) {
- // initialize new line item
- var o_NewLineItem = {};
- // store this line item as an object
- o_ThisLineItem = o_QuoteDetails._Product_Details[i];
- console.log(o_ThisLineItem);
- // assigning to a lookup field (Product)
- var o_ProductDetails = ZDK.Apps.crm.Products.fetchById(o_ThisLineItem._product_Lookup_Id);
- o_NewLineItem.Product_Name = { id: o_ProductDetails.id, name: o_ProductDetails.Product_Name };
- // other fields for this line item
- o_NewLineItem.Description = o_ThisLineItem._product_description;
- o_NewLineItem.Discount = o_ThisLineItem._Discount;
- o_NewLineItem.List_Price = o_ThisLineItem._list_price;
- o_NewLineItem.Quantity = o_ThisLineItem._quantity;
- o_NewLineItem.Tax = o_ThisLineItem._Tax;
- // going to create a new set of line items
- a_InvoiceLineItems.push(o_NewLineItem);
- }
- // write back the new line items to the invoice
- ZDK.Page.getField('Invoiced_Items').setValue(a_InvoiceLineItems);
- }
Category: Zoho :: Article: 912
Add comment