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