diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.js b/crm/fcrm/doctype/crm_deal/crm_deal.js
index 944ac6d4..3526767a 100644
--- a/crm/fcrm/doctype/crm_deal/crm_deal.js
+++ b/crm/fcrm/doctype/crm_deal/crm_deal.js
@@ -5,4 +5,68 @@ frappe.ui.form.on("CRM Deal", {
refresh(frm) {
frm.add_web_link(`/crm/deals/${frm.doc.name}`, __("Open in Portal"));
},
+ update_total: function (frm) {
+ let total = 0;
+ let total_qty = 0;
+ let net_total = 0;
+ frm.doc.products.forEach((d) => {
+ total += d.amount;
+ total_qty += d.qty;
+ net_total += d.net_amount;
+ });
+
+ frappe.model.set_value(frm.doctype, frm.docname, "total", total);
+ frappe.model.set_value(
+ frm.doctype,
+ frm.docname,
+ "net_total",
+ net_total || total
+ );
+ }
+});
+
+frappe.ui.form.on("CRM Products", {
+ products_add: function (frm, cdt, cdn) {
+ frm.trigger("update_total");
+ },
+ products_remove: function (frm, cdt, cdn) {
+ frm.trigger("update_total");
+ },
+ product_code: function (frm, cdt, cdn) {
+ let d = frappe.get_doc(cdt, cdn);
+ frappe.model.set_value(cdt, cdn, "product_name", d.product_code);
+ },
+ rate: function (frm, cdt, cdn) {
+ let d = frappe.get_doc(cdt, cdn);
+ if (d.rate && d.qty) {
+ frappe.model.set_value(cdt, cdn, "amount", d.rate * d.qty);
+ }
+ frm.trigger("update_total");
+ },
+ qty: function (frm, cdt, cdn) {
+ let d = frappe.get_doc(cdt, cdn);
+ if (d.rate && d.qty) {
+ frappe.model.set_value(cdt, cdn, "amount", d.rate * d.qty);
+ }
+ frm.trigger("update_total");
+ },
+ discount_percentage: function (frm, cdt, cdn) {
+ let d = frappe.get_doc(cdt, cdn);
+ if (d.discount_percentage && d.amount) {
+ discount_amount = (d.discount_percentage / 100) * d.amount;
+ frappe.model.set_value(
+ cdt,
+ cdn,
+ "discount_amount",
+ discount_amount
+ );
+ frappe.model.set_value(
+ cdt,
+ cdn,
+ "net_amount",
+ d.amount - discount_amount
+ );
+ }
+ frm.trigger("update_total");
+ }
});
diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.json b/crm/fcrm/doctype/crm_deal/crm_deal.json
index 133630ce..67f441c3 100644
--- a/crm/fcrm/doctype/crm_deal/crm_deal.json
+++ b/crm/fcrm/doctype/crm_deal/crm_deal.json
@@ -43,6 +43,12 @@
"mobile_no",
"phone",
"gender",
+ "products_tab",
+ "products",
+ "section_break_ccbj",
+ "total",
+ "column_break_udbq",
+ "net_total",
"sla_tab",
"sla",
"sla_creation",
@@ -334,11 +340,46 @@
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
+ },
+ {
+ "fieldname": "products_tab",
+ "fieldtype": "Tab Break",
+ "label": "Products"
+ },
+ {
+ "fieldname": "products",
+ "fieldtype": "Table",
+ "label": "Products",
+ "options": "CRM Products"
+ },
+ {
+ "fieldname": "section_break_ccbj",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_udbq",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "total",
+ "fieldtype": "Currency",
+ "label": "Total",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "description": "Total after discount",
+ "fieldname": "net_total",
+ "fieldtype": "Currency",
+ "label": "Net Total",
+ "options": "currency",
+ "read_only": 1
}
],
+ "grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-12-11 14:31:41.058895",
+ "modified": "2025-05-12 12:30:55.415282",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Deal",
@@ -370,10 +411,11 @@
"write": 1
}
],
+ "row_format": "Dynamic",
"show_title_field_in_link": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "organization",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.js b/crm/fcrm/doctype/crm_lead/crm_lead.js
index 0a9d57e1..af944f40 100644
--- a/crm/fcrm/doctype/crm_lead/crm_lead.js
+++ b/crm/fcrm/doctype/crm_lead/crm_lead.js
@@ -5,4 +5,68 @@ frappe.ui.form.on("CRM Lead", {
refresh(frm) {
frm.add_web_link(`/crm/leads/${frm.doc.name}`, __("Open in Portal"));
},
+ update_total: function (frm) {
+ let total = 0;
+ let total_qty = 0;
+ let net_total = 0;
+ frm.doc.products.forEach((d) => {
+ total += d.amount;
+ total_qty += d.qty;
+ net_total += d.net_amount;
+ });
+
+ frappe.model.set_value(frm.doctype, frm.docname, "total", total);
+ frappe.model.set_value(
+ frm.doctype,
+ frm.docname,
+ "net_total",
+ net_total || total
+ );
+ }
});
+
+frappe.ui.form.on("CRM Products", {
+ products_add: function (frm, cdt, cdn) {
+ frm.trigger("update_total");
+ },
+ products_remove: function (frm, cdt, cdn) {
+ frm.trigger("update_total");
+ },
+ product_code: function (frm, cdt, cdn) {
+ let d = frappe.get_doc(cdt, cdn);
+ frappe.model.set_value(cdt, cdn, "product_name", d.product_code);
+ },
+ rate: function (frm, cdt, cdn) {
+ let d = frappe.get_doc(cdt, cdn);
+ if (d.rate && d.qty) {
+ frappe.model.set_value(cdt, cdn, "amount", d.rate * d.qty);
+ }
+ frm.trigger("update_total");
+ },
+ qty: function (frm, cdt, cdn) {
+ let d = frappe.get_doc(cdt, cdn);
+ if (d.rate && d.qty) {
+ frappe.model.set_value(cdt, cdn, "amount", d.rate * d.qty);
+ }
+ frm.trigger("update_total");
+ },
+ discount_percentage: function (frm, cdt, cdn) {
+ let d = frappe.get_doc(cdt, cdn);
+ if (d.discount_percentage && d.amount) {
+ discount_amount = (d.discount_percentage / 100) * d.amount;
+ frappe.model.set_value(
+ cdt,
+ cdn,
+ "discount_amount",
+ discount_amount
+ );
+ frappe.model.set_value(
+ cdt,
+ cdn,
+ "net_amount",
+ d.amount - discount_amount
+ );
+ }
+ frm.trigger("update_total");
+ }
+});
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.json b/crm/fcrm/doctype/crm_lead/crm_lead.json
index e39af407..a9c25416 100644
--- a/crm/fcrm/doctype/crm_lead/crm_lead.json
+++ b/crm/fcrm/doctype/crm_lead/crm_lead.json
@@ -37,6 +37,12 @@
"annual_revenue",
"image",
"converted",
+ "products_tab",
+ "products",
+ "section_break_ggwh",
+ "total",
+ "column_break_uisv",
+ "net_total",
"sla_tab",
"sla",
"sla_creation",
@@ -285,12 +291,47 @@
"fieldtype": "Table",
"label": "Status Change Log",
"options": "CRM Status Change Log"
+ },
+ {
+ "fieldname": "products_tab",
+ "fieldtype": "Tab Break",
+ "label": "Products"
+ },
+ {
+ "fieldname": "products",
+ "fieldtype": "Table",
+ "label": "Products",
+ "options": "CRM Products"
+ },
+ {
+ "fieldname": "section_break_ggwh",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "total",
+ "fieldtype": "Currency",
+ "label": "Total",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_uisv",
+ "fieldtype": "Column Break"
+ },
+ {
+ "description": "Total after discount",
+ "fieldname": "net_total",
+ "fieldtype": "Currency",
+ "label": "Net Total",
+ "options": "currency",
+ "read_only": 1
}
],
+ "grid_page_length": 50,
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2025-01-02 22:14:01.991054",
+ "modified": "2025-05-14 19:51:06.184569",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Lead",
@@ -331,6 +372,7 @@
"share": 1
}
],
+ "row_format": "Dynamic",
"sender_field": "email",
"sender_name_field": "first_name",
"show_title_field_in_link": 1,
@@ -339,4 +381,4 @@
"states": [],
"title_field": "lead_name",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/crm/fcrm/doctype/crm_product/__init__.py b/crm/fcrm/doctype/crm_product/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/crm/fcrm/doctype/crm_product/crm_product.js b/crm/fcrm/doctype/crm_product/crm_product.js
new file mode 100644
index 00000000..66925f81
--- /dev/null
+++ b/crm/fcrm/doctype/crm_product/crm_product.js
@@ -0,0 +1,9 @@
+// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on("CRM Product", {
+ product_code: function (frm) {
+ if (!frm.doc.product_name)
+ frm.set_value("product_name", frm.doc.product_code);
+ }
+});
diff --git a/crm/fcrm/doctype/crm_product/crm_product.json b/crm/fcrm/doctype/crm_product/crm_product.json
new file mode 100644
index 00000000..18ed3466
--- /dev/null
+++ b/crm/fcrm/doctype/crm_product/crm_product.json
@@ -0,0 +1,105 @@
+{
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:product_code",
+ "creation": "2025-04-28 11:45:09.309636",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "naming_series",
+ "product_code",
+ "product_name",
+ "column_break_bpdj",
+ "disabled",
+ "standard_rate",
+ "image",
+ "section_break_rtwm",
+ "description"
+ ],
+ "fields": [
+ {
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Naming Series",
+ "options": "CRM-PROD-.YYYY.-"
+ },
+ {
+ "fieldname": "product_code",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Product Code",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "product_name",
+ "fieldtype": "Data",
+ "label": "Product Name"
+ },
+ {
+ "fieldname": "column_break_bpdj",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "label": "Disabled"
+ },
+ {
+ "fieldname": "image",
+ "fieldtype": "Attach Image",
+ "label": "Image"
+ },
+ {
+ "fieldname": "section_break_rtwm",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Text Editor",
+ "label": "Description"
+ },
+ {
+ "fieldname": "standard_rate",
+ "fieldtype": "Currency",
+ "label": "Standard Selling Rate"
+ }
+ ],
+ "grid_page_length": 50,
+ "image_field": "image",
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "make_attachments_public": 1,
+ "modified": "2025-04-28 12:47:25.087957",
+ "modified_by": "Administrator",
+ "module": "FCRM",
+ "name": "CRM Product",
+ "naming_rule": "By fieldname",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "row_format": "Dynamic",
+ "search_fields": "product_name,description",
+ "show_name_in_global_search": 1,
+ "show_preview_popup": 1,
+ "sort_field": "creation",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "product_name",
+ "track_changes": 1
+}
diff --git a/crm/fcrm/doctype/crm_product/crm_product.py b/crm/fcrm/doctype/crm_product/crm_product.py
new file mode 100644
index 00000000..a04845e1
--- /dev/null
+++ b/crm/fcrm/doctype/crm_product/crm_product.py
@@ -0,0 +1,16 @@
+# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe.model.document import Document
+
+
+class CRMProduct(Document):
+ def validate(self):
+ self.set_product_name()
+
+ def set_product_name(self):
+ if not self.product_name:
+ self.product_name = self.product_code
+ else:
+ self.product_name = self.product_name.strip()
diff --git a/crm/fcrm/doctype/crm_product/test_crm_product.py b/crm/fcrm/doctype/crm_product/test_crm_product.py
new file mode 100644
index 00000000..c6c8b1fb
--- /dev/null
+++ b/crm/fcrm/doctype/crm_product/test_crm_product.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests import IntegrationTestCase, UnitTestCase
+
+# On IntegrationTestCase, the doctype test records and all
+# link-field test record dependencies are recursively loaded
+# Use these module variables to add/remove to/from that list
+EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
+IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
+
+
+class UnitTestCRMProduct(UnitTestCase):
+ """
+ Unit tests for CRMProduct.
+ Use this class for testing individual functions and methods.
+ """
+
+ pass
+
+
+class IntegrationTestCRMProduct(IntegrationTestCase):
+ """
+ Integration tests for CRMProduct.
+ Use this class for testing interactions between multiple components.
+ """
+
+ pass
diff --git a/crm/fcrm/doctype/crm_products/__init__.py b/crm/fcrm/doctype/crm_products/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/crm/fcrm/doctype/crm_products/crm_products.json b/crm/fcrm/doctype/crm_products/crm_products.json
new file mode 100644
index 00000000..2d96656b
--- /dev/null
+++ b/crm/fcrm/doctype/crm_products/crm_products.json
@@ -0,0 +1,136 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2025-04-28 12:50:49.812915",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "product_code",
+ "column_break_gvbc",
+ "product_name",
+ "section_break_fnvf",
+ "qty",
+ "column_break_ajac",
+ "rate",
+ "section_break_olqb",
+ "discount_percentage",
+ "column_break_uvra",
+ "discount_amount",
+ "section_break_cnpb",
+ "column_break_pozr",
+ "amount",
+ "column_break_ejqw",
+ "net_amount"
+ ],
+ "fields": [
+ {
+ "fieldname": "column_break_gvbc",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "product_name",
+ "fieldtype": "Data",
+ "label": "Product Name",
+ "reqd": 1
+ },
+ {
+ "fieldname": "section_break_fnvf",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "section_break_olqb",
+ "fieldtype": "Section Break"
+ },
+ {
+ "bold": 1,
+ "fieldname": "discount_percentage",
+ "fieldtype": "Percent",
+ "label": "Discount %"
+ },
+ {
+ "fieldname": "discount_amount",
+ "fieldtype": "Currency",
+ "label": "Discount Amount",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_cnpb",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_pozr",
+ "fieldtype": "Column Break"
+ },
+ {
+ "bold": 1,
+ "fieldname": "rate",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Rate",
+ "options": "currency",
+ "reqd": 1
+ },
+ {
+ "bold": 1,
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "label": "Amount",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "bold": 1,
+ "depends_on": "discount_percentage",
+ "description": "Amount after discount",
+ "fieldname": "net_amount",
+ "fieldtype": "Currency",
+ "label": "Net Amount",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "bold": 1,
+ "columns": 5,
+ "fieldname": "product_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Product",
+ "options": "CRM Product"
+ },
+ {
+ "bold": 1,
+ "default": "1",
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "label": "Quantity"
+ },
+ {
+ "fieldname": "column_break_ajac",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_uvra",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_ejqw",
+ "fieldtype": "Column Break"
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2025-05-14 18:52:26.183306",
+ "modified_by": "Administrator",
+ "module": "FCRM",
+ "name": "CRM Products",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "sort_field": "creation",
+ "sort_order": "DESC",
+ "states": []
+}
diff --git a/crm/fcrm/doctype/crm_products/crm_products.py b/crm/fcrm/doctype/crm_products/crm_products.py
new file mode 100644
index 00000000..59f28e5c
--- /dev/null
+++ b/crm/fcrm/doctype/crm_products/crm_products.py
@@ -0,0 +1,110 @@
+# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe.model.document import Document
+
+
+class CRMProducts(Document):
+ pass
+
+
+def create_product_details_script(doctype):
+ if not frappe.db.exists("CRM Form Script", "Product Details Script for " + doctype):
+ script = get_product_details_script(doctype)
+ frappe.get_doc(
+ {
+ "doctype": "CRM Form Script",
+ "name": "Product Details Script for " + doctype,
+ "dt": doctype,
+ "view": "Form",
+ "script": script,
+ "enabled": 1,
+ "is_standard": 1,
+ }
+ ).insert()
+
+
+def get_product_details_script(doctype):
+ doctype_class = "class " + doctype.replace(" ", "")
+
+ return (
+ doctype_class
+ + " {"
+ + """
+ update_total() {
+ let total = 0
+ let total_qty = 0
+ let net_total = 0
+ let discount_applied = false
+
+ this.doc.products.forEach((d) => {
+ total += d.amount
+ net_total += d.net_amount
+ if (d.discount_percentage > 0) {
+ discount_applied = true
+ }
+ })
+
+ this.doc.total = total
+ this.doc.net_total = net_total || total
+
+ if (!net_total && discount_applied) {
+ this.doc.net_total = net_total
+ }
+ }
+}
+
+class CRMProducts {
+ products_add() {
+ let row = this.doc.getRow('products')
+ row.trigger('qty')
+ this.doc.trigger('update_total')
+ }
+
+ products_remove() {
+ this.doc.trigger('update_total')
+ }
+
+ async product_code(idx) {
+ let row = this.doc.getRow('products', idx)
+
+ let a = await call("frappe.client.get_value", {
+ doctype: "CRM Product",
+ filters: { name: row.product_code },
+ fieldname: ["product_name", "standard_rate"],
+ })
+
+ row.product_name = a.product_name
+ if (a.standard_rate && !row.rate) {
+ row.rate = a.standard_rate
+ row.trigger("rate")
+ }
+ }
+
+ qty(idx) {
+ let row = this.doc.getRow('products', idx)
+ row.amount = row.qty * row.rate
+ row.trigger('discount_percentage', idx)
+ }
+
+ rate() {
+ let row = this.doc.getRow('products')
+ row.amount = row.qty * row.rate
+ row.trigger('discount_percentage')
+ }
+
+ discount_percentage(idx) {
+ let row = this.doc.getRow('products', idx)
+ if (!row.discount_percentage) {
+ row.net_amount = row.amount
+ row.discount_amount = 0
+ }
+ if (row.discount_percentage && row.amount) {
+ row.discount_amount = (row.discount_percentage / 100) * row.amount
+ row.net_amount = row.amount - row.discount_amount
+ }
+ this.doc.trigger('update_total')
+ }
+}"""
+ )
diff --git a/crm/install.py b/crm/install.py
index 1e0d816f..5f25e7d0 100644
--- a/crm/install.py
+++ b/crm/install.py
@@ -4,6 +4,8 @@ import click
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+from crm.fcrm.doctype.crm_products.crm_products import create_product_details_script
+
def before_install():
pass
@@ -19,6 +21,7 @@ def after_install(force=False):
add_default_industries()
add_default_lead_sources()
add_standard_dropdown_items()
+ add_default_scripts()
frappe.db.commit()
@@ -353,3 +356,8 @@ def add_standard_dropdown_items():
crm_settings.append("dropdown_items", item)
crm_settings.save()
+
+
+def add_default_scripts():
+ for doctype in ["CRM Lead", "CRM Deal"]:
+ create_product_details_script(doctype)
diff --git a/crm/patches.txt b/crm/patches.txt
index f602b2bc..59f37bfb 100644
--- a/crm/patches.txt
+++ b/crm/patches.txt
@@ -11,4 +11,5 @@ crm.patches.v1_0.create_default_fields_layout #22/01/2025
crm.patches.v1_0.create_default_sidebar_fields_layout
crm.patches.v1_0.update_deal_quick_entry_layout
crm.patches.v1_0.update_layouts_to_new_format
-crm.patches.v1_0.move_twilio_agent_to_telephony_agent
\ No newline at end of file
+crm.patches.v1_0.move_twilio_agent_to_telephony_agent
+crm.patches.v1_0.create_default_scripts
\ No newline at end of file
diff --git a/crm/patches/v1_0/create_default_scripts.py b/crm/patches/v1_0/create_default_scripts.py
new file mode 100644
index 00000000..37e3b0d1
--- /dev/null
+++ b/crm/patches/v1_0/create_default_scripts.py
@@ -0,0 +1,5 @@
+from crm.install import add_default_scripts
+
+
+def execute():
+ add_default_scripts()
diff --git a/crm/uninstall.py b/crm/uninstall.py
index 34622d0b..a186c79f 100644
--- a/crm/uninstall.py
+++ b/crm/uninstall.py
@@ -1,12 +1,13 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-from __future__ import unicode_literals
import click
import frappe
+
def before_uninstall():
delete_email_template_custom_fields()
+
def delete_email_template_custom_fields():
if frappe.get_meta("Email Template").has_field("enabled"):
click.secho("* Uninstalling Custom Fields from Email Template")
@@ -19,4 +20,4 @@ def delete_email_template_custom_fields():
for fieldname in fieldnames:
frappe.db.delete("Custom Field", {"name": "Email Template-" + fieldname})
- frappe.clear_cache(doctype="Email Template")
\ No newline at end of file
+ frappe.clear_cache(doctype="Email Template")
diff --git a/frontend/components.d.ts b/frontend/components.d.ts
index 82afdb19..99bd28e3 100644
--- a/frontend/components.d.ts
+++ b/frontend/components.d.ts
@@ -53,6 +53,7 @@ declare module 'vue' {
ContactsListView: typeof import('./src/components/ListViews/ContactsListView.vue')['default']
ConvertIcon: typeof import('./src/components/Icons/ConvertIcon.vue')['default']
CountUpTimer: typeof import('./src/components/CountUpTimer.vue')['default']
+ CreateDocumentModal: typeof import('./src/components/Modals/CreateDocumentModal.vue')['default']
CRMLogo: typeof import('./src/components/Icons/CRMLogo.vue')['default']
CustomActions: typeof import('./src/components/CustomActions.vue')['default']
DashboardIcon: typeof import('./src/components/Icons/DashboardIcon.vue')['default']
@@ -113,9 +114,11 @@ declare module 'vue' {
FileVideoIcon: typeof import('./src/components/Icons/FileVideoIcon.vue')['default']
Filter: typeof import('./src/components/Filter.vue')['default']
FilterIcon: typeof import('./src/components/Icons/FilterIcon.vue')['default']
+ FormattedInput: typeof import('./src/components/Controls/FormattedInput.vue')['default']
FrappeCloudIcon: typeof import('./src/components/Icons/FrappeCloudIcon.vue')['default']
GenderIcon: typeof import('./src/components/Icons/GenderIcon.vue')['default']
GeneralSettings: typeof import('./src/components/Settings/GeneralSettings.vue')['default']
+ GlobalModals: typeof import('./src/components/Modals/GlobalModals.vue')['default']
GoogleIcon: typeof import('./src/components/Icons/GoogleIcon.vue')['default']
Grid: typeof import('./src/components/Controls/Grid.vue')['default']
GridFieldsEditorModal: typeof import('./src/components/Controls/GridFieldsEditorModal.vue')['default']
diff --git a/frontend/src/components/Controls/FormattedInput.vue b/frontend/src/components/Controls/FormattedInput.vue
new file mode 100644
index 00000000..6414af92
--- /dev/null
+++ b/frontend/src/components/Controls/FormattedInput.vue
@@ -0,0 +1,58 @@
+
+
+ {{ attrs.description }}
+