## Documentation Index Access the complete documentation index at: https://www.zoho.com/books/developer/llms.txt Use this file to discover all available documentation pages before proceeding. # Sample Extension Let’s see how to build an extension for Zoho Books from scratch. In this help document, we’ll explain the steps involved in building the Salesperson extension as a sample extension to learn this better. First, let’s understand why Salesperson extension is required. In Zoho Books, it is currently not possible to track salespersons within an organization. Using the Salesperson extension, admins of an organization can keep track of the salespersons and their activities, and assign commissions to them each time a customer associated with them pays for an invoice. Now that you’ve understood the need for the Salesperson extension let’s start building the extension. The steps involved in building this extension are: 1. Creating an extension 2. Custom module for salespersons 3. Connection for extension 4. Custom function to calculate the salesperson’s commission 5. Workflow rule for custom function 6. Salesperson Name custom field in the Expenses module 7. Global field to store salesperson’s data 8. Settings widget for the extension 9. Syncing new salesperson with the Salesperson module 10. Syncing existing salespersons during extension installation ### Creating an Extension The first step is to create an extension. To do this: * Login to [Zoho Sigma](https://sigma.zoho.com/). * If you have a single workspace you’ll be redirected to your workspace. Or, if you have multiple workspaces, select the workspace in which you want to create the extension. The _All Extensions_ tab under _Extensions_ will open by default. * Click **New Extension** in the top right corner. ![Click 'New Extension'](/books/developer/images/extensions/sample-extension/new-extension-button.png) * On the _New Extension_ page: * Enter the **Name** and **Description** of the extension. * Select the **Zoho Books** logo under _Services._ * Agree to the terms and conditions. * Click **Create**. ![Enter the extension details and click 'Create'](/books/developer/images/extensions/sample-extension/new-extension-pop-up.png) With this, you have created the Salesperson extension. The next step is to add the necessary components for the extension. To do this: * Click the **Edit Extension** button in the salesperson extension’s card. You’ll be redirected to **Zoho Books Developer Portal**. ![Click 'Edit Extension'](/books/developer/images/extensions/sample-extension/edit-extension-button.png) ### Custom Module for Salespersons You need a module to keep track of all the Salespersons in your organization. You can use the [Custom Modules](/books/help/custom-modules/) feature to create a module for Salesperson. Here’s how: * Go to the **Build** section at the top. * The **Preferences** tab will open by default. In the _Preferences_ pane of the tab, scroll down and click **View More** in the _Custom Modules_ card. * Click **Create Module**. * A pop-up will appear. In the _Module Details_ section: * Enter the **Module Name**, **Plural Name**, and **Description** for the custom module. * Click **Next**. ![Enter the module details](/books/developer/images/extensions/sample-extension/salesperson-cm-module-details.png) * In the _Primary Field Properties_ section: * Enter a name for the primary data type of the custom module in the **Label Name** field. * Select the data type of the primary field in the **Data Type** field. * Click **Save**. ![Enter the primary field's properties](/books/developer/images/extensions/sample-extension/salesperson-cm-primary-field-properties.png) You’ll have to create the additional fields for the Salesperson module. Here’s how you can do this: * Switch to the **Fields** tab at the top of the _Salesperson_ custom module. * Click **\+ New Field** in the top right corner. * Fill in the required details on the _New Field_ page. Refer to the image below to view the additional fields that you’ll have to create and their details. ![Details of other fields required for the custom module](/books/developer/images/extensions/sample-extension/salesperson-cm-other-fields.png) **Note** * For the _Salesperson Account_ field, select **Accounts** as the _Module_ that has to be looked up. * For the _Salesperson Commission Type_ field, enter **Percentage** and **Amount** as the dropdown options. ### Connection for the Extension You have to create a [connection](/books/help/settings/connections.html) so that the extension has the permissions required to fetch the data necessary from Zoho Books. To create a connection: * Go to the **Build** section at the top. * Click **Connections** in the left sidebar. * Click **New Connection** in the top right corner. * On the following page: * Select **Zoho Books** in the _Default Services_ tab of the _Pick Your Services_ section. * Enter **Zoho Books - Salesperson** as the _Connection Name_ in the _Connection Details_ section. * Enable the following scopes: * ZohoBooks.customerpayments.READ * ZohoBooks.custommodules.All * ZohoBooks.expenses.CREATE * ZohoBooks.expenses.READ * ZohoBooks.expenses.UPDATE * ZohoBooks.invoices.READ * ZohoBooks.settings.CREATE * ZohoBooks.settings.READ * ZohoBooks.settings.UPDATE * Click **Create and Connect**. ### Custom Function to Calculate the Salesperson’s Commission You have to create a [custom function](/books/help/settings/automation.html#custom-functions) that automatically calculates the salesperson’s commission when the salesperson creates an invoice. Also, the custom function will automatically create an expense for the saleseprson’s commission once the invoice’s status is **Paid**. This will help you keep track of the commissions you owe to each salesperson in your organization. To create the custom function: * Click **Build** at the top. * Click _Automation_ in the left sidebar and select **Workflow Actions** from the dropdown. * Select **Custom Functions** on the _Workflow Actions_ pane. * Click **\+ New Custom Function** on the top right corner of the page. * On the _New Custom Function_ page: * Enter the Function Name. * Select the module from which you’re creating the custom function from the dropdown next to the **Module** field. * Check the **Allow use of Global Fields in this custom function** option next to _Global Fields_. * Paste the following code in the **Deluge** screen. ```js organizationID = organization.get("organization_id"); domainUrl = organization.get("api_root_endpoint"); customModuleApiName = "cm__u76nt_salesperson"; expense_api_name = "cf__u76nt_salesperson_name"; cmNameApiName = "cf__u76nt_salesperson_name"; cmEmailApiName = "cf__u76nt_salesperson_e_mail"; cmAccountApiName = "cf__u76nt_salesperson_account"; cmTypeApiName = "cf__u76nt_salesperson_commission_type"; cmRateApiName = "cf__u76nt_salesperson_commission_rate"; cmStatusApiName = "cf__u76nt_salesperson_status"; globalFieldApiName = "vl__u76nt_data_store"; dataStore = global_fields.get(globalFieldApiName).get("value"); user_given_commission = dataStore.get("commission"); user_given_expense_id = dataStore.get("expense_account").get("id"); user_given_commission_type = dataStore.get("type"); user_given_status = dataStore.get("status"); user_given_paid_id = dataStore.get("paid_through_account").get("id"); user_given_specification_type = dataStore.get("specification_type"); customer_id = invoice.get("customer_id"); invoice_status = invoice.get("status"); invoice_number = invoice.get("invoice_number"); invoice_id = invoice.get("invoice_id"); invoice_salesperson_id = invoice.get("salesperson_id"); invoice_salesperson_name = invoice.get("salesperson_name"); invoiceMap = Map(); invoiceMap.put("salesperson_id",invoice_salesperson_id); invoiceMap.put("salesperson_name",invoice_salesperson_name); connection_link_name = "salesperson_commission_books"; total_page = invokeurl [ url :domainUrl + "/" + customModuleApiName + "?page=1&per_page=200&response_option=2&organization_id=" + organizationID type :GET connection:"salesperson_commission_books" ]; total_page = total_page.get("page_context").get("total_pages"); // total page count make as loop loop = rightpad(",",total_page).toList(""); // Get Salesperson Details salesperson_response = invokeurl [ url :domainUrl + "/salespersons?organization_id=" + organizationID type :GET connection:"salesperson_commission_books" ]; if(salesperson_response.get("code") == 0) { data = salesperson_response.get("data"); if(invoice_status == "paid" && user_given_status == 'paid') { // based upon subtotal info "paidStatus"; if(user_given_specification_type == 'SubTotal') { invoice_amount = invoice.get("sub_total"); } // based upon the total else if(user_given_specification_type == 'Total') { invoice_amount = invoice.get("total"); } payment_id_responses = invokeurl [ url :domainUrl + "/invoices/" + invoice_id + "/payments?organization_id=" + organizationID type :GET connection:"salesperson_commission_books" ]; if(payment_id_responses.get("code") == 0) { customerpayments_id = payment_id_responses.get("payments").get(0).get("payment_id"); // get paid_through_account payment_response = invokeurl [ url :domainUrl + "/customerpayments/" + customerpayments_id + "?organization_id=" + organizationID type :GET connection:"salesperson_commission_books" ]; paid_through_id = payment_response.get("payment").get("account_id"); } info payment_id_responses.get("message"); } else if(invoice_status == "sent" && user_given_status == "sent") { // based upon the subtotal info "sentStatus"; if(user_given_specification_type == "SubTotal") { invoice_amount = invoice.get("sub_total"); } //based upon the total else if(user_given_specification_type == 'Total') { invoice_amount = invoice.get("total"); } paid_through_id = user_given_paid_id; } for each count in data { if(count.get("salesperson_id") == invoice_salesperson_id) { salesperson_email = count.get("salesperson_email"); salesperson_status = count.get("is_active"); } } if(salesperson_status == false) { salesperson_status = "Inactive"; } else { salesperson_status = "Active"; } count = 0; page = 0; invoice_amount = invoice_amount.toDecimal(); for each execute in loop { page = page + 1; // Get Custom Module Details customModule_response = invokeurl [ url :(domainUrl + '/') + customModuleApiName + '?page=' + page + '&usestate=false&organization_id=' + organizationID type :GET connection:"salesperson_commission_books" ]; customModule_records = customModule_response.get("module_records"); for each records in customModule_records { if(records.get(cmEmailApiName) == salesperson_email) { count = count + 1; customModule_id = records.get('module_record_id'); salesperson_commission_type = records.get(cmTypeApiName); salesperson_commission_rate = records.get(cmRateApiName); salesperson_expense_id = records.get(cmAccountApiName); body = {"JSONString":{cmNameApiName:invoice_salesperson_name,cmTypeApiName:salesperson_commission_type,cmRateApiName:salesperson_commission_rate,cmEmailApiName:salesperson_email,cmAccountApiName:salesperson_expense_id,cmStatusApiName:salesperson_status}}; if(salesperson_commission_type == 'Percentage') { amount = (invoice_amount * salesperson_commission_rate.toDecimal()) / 100; } else { amount = salesperson_commission_rate; } updatecustomModuleFields = invokeurl [ url :domainUrl + "/" + customModuleApiName + "/" + customModule_id + "?organization_id=" + organizationID type :PUT parameters:body connection:"salesperson_commission_books" ]; if(updatecustomModuleFields.get("code") == 0) { info "updateCustomModule"; } info updatecustomModuleFields.get("message"); } } } if(count == 0) { body = {"JSONString":{cmNameApiName:invoice_salesperson_name,cmTypeApiName:user_given_commission_type,cmRateApiName:user_given_commission,cmEmailApiName:salesperson_email,cmAccountApiName:user_given_expense_id,cmStatusApiName:salesperson_status}}; createcustomModuleFields = invokeurl [ url :domainUrl + "/" + customModuleApiName + "?organization_id=" + organizationID type :POST parameters:body connection:"salesperson_commission_books" ]; if(createcustomModuleFields.get("code") == 0) { info "createCustomModule"; salesperson_commission_type = user_given_commission_type; salesperson_commission_rate = user_given_commission; customModule_id = createcustomModuleFields.get('module_record').get('module_record_id'); salesperson_expense_id = user_given_expense_id; if(salesperson_commission_type == 'Percentage') { amount = (invoice_amount * salesperson_commission_rate.toDecimal()) / 100; } else { amount = salesperson_commission_rate; } } info createcustomModuleFields.get("message"); } if(salesperson_commission_type == 'Percentage') { body = {"JSONString":{"account_id":salesperson_expense_id,"amount":amount,"paid_through_account_id":paid_through_id,"reference_number":invoice_number,"customer_id":customer_id,"description":"SalespersonCommision" + " " + salesperson_commission_rate + "%" + " " + "from invoice to" + " " + invoice_salesperson_name,"custom_fields":{{"value":customModule_id,"api_name":expense_api_name}}}}; } else { body = {"JSONString":{"account_id":salesperson_expense_id,"amount":amount,"paid_through_account_id":paid_through_id,"reference_number":invoice_number,"customer_id":customer_id,"description":"SalespersonCommision" + " " + "₹" + salesperson_commission_rate + " " + "from invoice to" + " " + invoice_salesperson_name,"custom_fields":{{"value":customModule_id,"api_name":expense_api_name}}}}; } // expense total page count expense_count_response = invokeurl [ url :domainUrl + "/expenses?per_page=200&response_option=2&organization_id=" + organizationID type :GET connection:"salesperson_commission_books" ]; expense_records_count = expense_count_response.get("page_context").get("total_pages"); execution_of_loop = rightpad(",",expense_records_count).toList(""); page = 0; for each execute in execution_of_loop { page = page + 1; expense_response = invokeurl [ url :domainUrl + "/expenses?page=" + page + "&per_page=200&filter_by=Status.All&sort_column=created_time&sort_order=D&usestate=true&organization_id=" + organizationID type :GET connection:"salesperson_commission_books" ]; expense_list = expense_response.get("expenses"); // Find the reference number for each count in expense_list { if(count.get("reference_number") == invoice_number) { expense_id = count.get("expense_id"); break; } } if(expense_id != null) { expense_response = invokeurl [ url :domainUrl + "/expenses/" + expense_id + "?organization_id=" + organizationID type :PUT parameters:body connection:"salesperson_commission_books" ]; if(expense_response.get("code") == 0) { info "updateExpense"; } info expense_response.get("message"); break; } } if(expense_id == null) { expense_response = invokeurl [ url :domainUrl + "/expenses?organization_id=" + organizationID type :POST parameters:body connection:"salesperson_commission_books" ]; if(expense_response.get("code") == 0) { info "createExpense"; } info expense_response.get("message"); } } else { info salesperson_response.get("message"); } ``` * Click **Save**. ### Workflow Rule for the Custom Function For the custom function to automatically calculate the salesperson’s commission once they create an invoice, you have to create a [workflow rule](https://www.zoho.com/books/help/settings/automation.html#new-workflow-rule) that gets triggered automatically once an invoice is created or edited. You can associate the custom function with the workflow rule. To create the custom function: * Click **Build** at the top. * Click _Automation_ in the left sidebar and select **Workflow Rules** from the dropdown. * Click **\+ New Workflow Rule** in the top right corner. * In the _Name your workflow_ section: * Enter the **Workflow Rule Name**. * Select the module for which you’re creating the workflow rule from the dropdown next to the **Module** field. * In the _Choose when to trigger_ section: * Select **Created or Edited** for the _When Invoice is_ field. * Select **Any selected field is updated** from the dropdown next to the _Execute the workflow when_ field. * In the dropdown next to it, select **Salesperson**, **Status**, and **Total** as the fields based on which the workflow rule should execute. ![Select when the workflow should be executed](/books/developer/images/extensions/sample-extension/workflow-execute-condition.png) * Select the filters as shown in the image below in the _Filter the triggers_ section. ![Add the required filters](/books/developer/images/extensions/sample-extension/workflow-filters.png) * In the _Actions_ section: * Click the dropdown below the _Type_ column and select **Custom Functions**. * Click the dropdown below the **Name** column and select the custom function you created [earlier](/books/developer/extensions/sample-extension.html#custom-function-to-calculate-the-salesperson-commission). ![Select the custom function as the workflow action](/books/developer/images/extensions/sample-extension/workflow-actions.png) * Click **Save**. ### Salesperson Name Custom Field in the Expenses Module In Zoho Books, the Salesperson field is available in the sales modules such as Quotes, Sales Orders, and Invoices. You have to create the Salesperson field in the Expenses module to keep track of the Salesperson for whom the expense is created. To do this, you can use the [Custom Fields](/books/help/settings/customization/custom-fields.html) feature in Zoho Books. Here’s how: * Go to **Build** at the top. * Click **Preferences** in the left sidebar. * Select **Expenses** in the _Preferences_ pane. * Click **\+ New Custom Field**. * On the _New Custom Field_ page: * Enter the custom field’s name in the **Label Name** field. * Select **Lookup** as the _Data Type_ for the custom field. * Select **Salesperson** as the module from which you want to lookup the data in the _Module_ field. * Enter the **Related List Name**. ![Enter the custom field details](/books/developer/images/extensions/sample-extension/salesperson-name-cf-expenses-module.png) * Click **Save**. ### Global Field to Store the Salesperson’s Data You have to create a global field to store the salesperson’s data. Here’s how you can do this: * Go to the **Configure** section at the top of the **Developer Portal**. * The _Global Fields_ tab will open by default. Click **\+ New Field** in the top right corner. * On the _New Field_ page: * Enter the **Name** and **Description** of the global field. * Select the **Data Type**. * Click **Save**. ![Enter the global field details](/books/developer/images/extensions/sample-extension/global-field.png) ### Settings Widget for the Salesperson Extension You need to create a settings widget for the Salesperson extension. The widget will contain the UI components required for the initial configuration of the Salesperson extension. You can create the settings widget using any framework of your choice. In this extension, we’ve used the [Vue](/books/developer/extensions/salesperson-extension-using-vue.html), [React](/books/developer/extensions/salesperson-extension-using-react.html), and [Vanilla JS](/books/developer/extensions/salesperson-extension-using-vanilla-js.html). After you create the widget, you need to [pack it](/books/developer/widgets/create-widget.html#pack) and [upload it](/books/developer/widgets/create-widget.html#upload) into the global field. ### Sync New Salesperson With the Salesperson Module Whenever you create a new salesperson, you’ll have to add them to the Salespersons module. This can be a time-consuming process. Instead, you can create a [custom button](/books/help/settings/customization/custom-buttons.html) in the Salesperson module. On clicking the custom button, the newly added salesperson will be automatically synced with the Salesperson module. Here’s how you can do this: * Go to **Build** section at the top. * Select the Salesperson custom module in the _Preferences_ pane. * Switch to the **Custom Buttons** tab. * Click **\+ New** in the top right corner. * On the _New Custom Button_ page: * Enter the **Custom Button Name**. For example, Sync New Salesperson. * Choose the custom button’s **Visibility**. * Select the _Location_ of the custom button as **List Page - Action Menu**. * Paste the following code in the **Deluge** screen. ```js organizationID = organization.get("organization_id"); domainUrl = organization.get("api_root_endpoint"); customModuleApiName = "cm__u76nt_salesperson"; cmNameApiName = "cf__u76nt_salesperson_name"; cmEmailApiName = "cf__u76nt_salesperson_e_mail"; cmAccountApiName = "cf__u76nt_salesperson_account"; cmTypeApiName = "cf__u76nt_salesperson_commission_type"; cmRateApiName = "cf__u76nt_salesperson_commission_rate"; cmStatusApiName = "cf__u76nt_salesperson_status"; globalFieldApiName = "vl__u76nt_data_store"; dataStore = global_fields.get(globalFieldApiName).get("value"); user_given_commission = dataStore.get("commission"); user_given_expense_id = dataStore.get("expense_account").get("id"); user_given_commission_type = dataStore.get("type"); connection_link_name = "salesperson_commission_books"; // GET total page count of Custom Module . total_page = invokeurl [ url :domainUrl + "/" + customModuleApiName + "?page=1&per_page=200&response_option=2&organization_id=" + organizationID type :GET connection:"salesperson_commission_books" ]; total_page = total_page.get("page_context").get("total_pages"); // total page count make as loop loop = rightpad(",",total_page).toList(""); // Get Salesperson Details salesperson_response = invokeurl [ url :domainUrl + "/salespersons?organization_id=" + organizationID type :GET connection:"salesperson_commission_books" ]; data = salesperson_response.get("data"); customModule_email_map = Map(); for each record in data { is_present = 0; salesperson_name = record.get("salesperson_name"); salesperson_email = record.get("salesperson_email"); salesperson_status = record.get("is_active"); if(salesperson_status == false) { salesperson_status = "Inactive"; } else { salesperson_status = "Active"; } body = {"JSONString":{cmNameApiName:salesperson_name,cmTypeApiName:user_given_commission_type,cmRateApiName:user_given_commission,cmEmailApiName:salesperson_email,cmAccountApiName:user_given_expense_id,cmStatusApiName:salesperson_status}}; page = 0; for each index in loop { page = page + 1; // Get Custom Module Details customModule_response = invokeurl [ url :(domainUrl + '/') + customModuleApiName + '?page=' + page + '&usestate=false&organization_id=' + organizationID type :GET connection:"salesperson_commission_books" ]; customModule_records = customModule_response.get("module_records"); for each records in customModule_records { email = records.get(cmEmailApiName); customModule_email_map.put(email,email); if(records.get(cmEmailApiName) == salesperson_email) { is_present = is_present + 1; customModule_id = records.get('module_record_id'); salesperson_commission_type = records.get(cmTypeApiName); salesperson_commission_rate = records.get(cmRateApiName); salesperson_expense_id = records.get(cmAccountApiName); body = {"JSONString":{cmNameApiName:salesperson_name,cmTypeApiName:user_given_commission_type,cmRateApiName:user_given_commission,cmEmailApiName:salesperson_email,cmAccountApiName:user_given_expense_id,cmStatusApiName:salesperson_status}}; updatecustomModuleFields = invokeurl [ url :domainUrl + "/" + customModuleApiName + "/" + customModule_id + "?organization_id=" + organizationID type :PUT parameters:body connection:"salesperson_commission_books" ]; } } if(is_present == 0) { createcustomModuleFields = invokeurl [ url :domainUrl + "/" + customModuleApiName + "?organization_id=" + organizationID type :POST parameters:body connection:"salesperson_commission_books" ]; info createcustomModuleFields; } } } for each count in data { salesperson_email = count.get("salesperson_email"); customModule_email_map.remove(salesperson_email); } for each index in customModule_email_map { for each records in customModule_records { if(records.get(cmEmailApiName) == index) { customModule_id = records.get('module_record_id'); salesperson_commission_type = records.get(cmTypeApiName); salesperson_commission_rate = records.get(cmRateApiName); salesperson_expense_id = records.get(cmAccountApiName); salesperson_name = records.get(cmNameApiName); body = {"JSONString":{cmNameApiName:salesperson_name,cmTypeApiName:salesperson_commission_type,cmRateApiName:salesperson_commission_rate,cmEmailApiName:index,cmAccountApiName:salesperson_expense_id,cmStatusApiName:"This Salesperson Name not Exist"}}; updatecustomModuleFields = invokeurl [ url :domainUrl + "/" + customModuleApiName + "/" + customModule_id + "?organization_id=" + organizationID type :PUT parameters:body connection:"salesperson_commission_books" ]; } } } resultMap = Map(); resultMap.put("code",0); return resultMap; ``` * Click **Save** or **Save and Execute**. ### Sync Existing Salespersons During Extension Installation Before an organization installs the Salesperson extension, they might already have created salespersons. Using the [On Installation](/books/developer/extensions/configuration.html#on-installation) component, you can sync these salespersons to the Salesperson module, when the organization installs your extensions. Here’s how: * Go to the **Configure** section at the top. * Select **Install Actions** in the left sidebar. * The **On Installation** tab will open by default. * Paste the following code in the **Deluge** screen. ```js organizationID = organization.get("organization_id"); domainUrl = organization.get("api_root_endpoint"); customModuleApiName = "cm__u76nt_salesperson"; cmNameApiName = "cf__u76nt_salesperson_name"; cmEmailApiName = "cf__u76nt_salesperson_e_mail"; cmAccountApiName = "cf__u76nt_salesperson_account"; cmTypeApiName = "cf__u76nt_salesperson_commission_type"; cmRateApiName = "cf__u76nt_salesperson_commission_rate"; cmStatusApiName = "cf__u76nt_salesperson_status"; globalFieldApiName = "vl__u76nt_data_store"; dataStore = global_fields.get(globalFieldApiName).get("value"); user_given_commission = dataStore.get("commission"); user_given_expense_id = dataStore.get("expense_account").get("id"); user_given_commission_type = dataStore.get("type"); connection_link_name = "salesperson_commission_books"; // GET total page count of Custom Module . total_page = invokeurl [ url :domainUrl + "/" + customModuleApiName + "?page=1&per_page=200&response_option=2&organization_id=" + organizationID type :GET connection:"salesperson_commission_books" ]; total_page = total_page.get("page_context").get("total_pages"); // total page count make as loop loop = rightpad(",",total_page).toList(""); // Get Salesperson Details salesperson_response = invokeurl [ url :domainUrl + "/salespersons?organization_id=" + organizationID type :GET connection:"salesperson_commission_books" ]; data = salesperson_response.get("data"); for each record in data { is_present = 0; salesperson_name = record.get("salesperson_name"); salesperson_email = record.get("salesperson_email"); salesperson_status = record.get("is_active"); if(salesperson_status == false) { salesperson_status = "Inactive"; } else { salesperson_status = "Active"; } body = {"JSONString":{cmNameApiName:salesperson_name,cmTypeApiName:user_given_commission_type,cmRateApiName:user_given_commission,cmEmailApiName:salesperson_email,cmAccountApiName:user_given_expense_id,cmStatusApiName:salesperson_status}}; page = 0; for each index in loop { page = page + 1; // Get Custom Module Details customModule_response = invokeurl [ url :(domainUrl + '/') + customModuleApiName + '?page=' + page + '&usestate=false&organization_id=' + organizationID type :GET connection:"salesperson_commission_books" ]; customModule_records = customModule_response.get("module_records"); for each records in customModule_records { if(records.get(cmEmailApiName) == salesperson_email) { is_present = is_present + 1; customModule_id = records.get('module_record_id'); salesperson_commission_type = records.get(cmTypeApiName); salesperson_commission_rate = records.get(cmRateApiName); salesperson_expense_id = records.get(cmAccountApiName); body = {"JSONString":{cmNameApiName:salesperson_name,cmTypeApiName:user_given_commission_type,cmRateApiName:user_given_commission,cmEmailApiName:salesperson_email,cmAccountApiName:user_given_expense_id,cmStatusApiName:salesperson_status}}; updatecustomModuleFields = invokeurl [ url :domainUrl + "/" + customModuleApiName + "/" + customModule_id + "?organization_id=" + organizationID type :PUT parameters:body connection:"salesperson_commission_books" ]; } } if(is_present == 0) { createcustomModuleFields = invokeurl [ url :domainUrl + "/" + customModuleApiName + "?organization_id=" + organizationID type :POST parameters:body connection:"salesperson_commission_books" ]; info createcustomModuleFields; } } } ``` * Click **Save** or **Save and Execute**. With this, you have build all the components necessary for the Salesperson extension. You can [test your extension](/books/developer/extensions/test-extensions.html) in the Zoho Books Sandbox environment and [publish your extension](/books/developer/extensions/publish-extension.html) in Zoho Marketplace.