diff --git a/crm/api/demo.py b/crm/api/demo.py index 1ac27b7f..a5960729 100644 --- a/crm/api/demo.py +++ b/crm/api/demo.py @@ -15,12 +15,10 @@ def login(): frappe.local.response["location"] = frappe.local.response["redirect_to"] -def validate_reset_password(user): +def validate_reset_password(doc, event): if frappe.conf.demo_username and frappe.session.user == frappe.conf.demo_username: frappe.throw( - _("Password cannot be reset by Demo User {}").format( - frappe.bold(frappe.conf.demo_username) - ), + _("Password cannot be reset by Demo User {}").format(frappe.bold(frappe.conf.demo_username)), frappe.PermissionError, ) @@ -28,9 +26,6 @@ def validate_reset_password(user): def validate_user(doc, event): if frappe.conf.demo_username and frappe.session.user == frappe.conf.demo_username and doc.new_password: frappe.throw( - _("Password cannot be reset by Demo User {}").format( - frappe.bold(frappe.conf.demo_username) - ), + _("Password cannot be reset by Demo User {}").format(frappe.bold(frappe.conf.demo_username)), frappe.PermissionError, ) - diff --git a/crm/api/doc.py b/crm/api/doc.py index 175bccb9..9cc5c653 100644 --- a/crm/api/doc.py +++ b/crm/api/doc.py @@ -2,6 +2,7 @@ import json import frappe from frappe import _ +from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.model import no_value_fields from frappe.model.document import get_controller from frappe.utils import make_filter_tuple @@ -178,23 +179,39 @@ def get_doctype_fields_meta(DocField, doctype, allowed_fieldtypes, restricted_fi @frappe.whitelist() -def get_quick_filters(doctype: str): - meta = frappe.get_meta(doctype) - fields = [field for field in meta.fields if field.in_standard_filter] +def get_quick_filters(doctype: str, cached: bool = True): + meta = frappe.get_meta(doctype, cached) quick_filters = [] + if global_settings := frappe.db.exists("CRM Global Settings", {"dt": doctype, "type": "Quick Filters"}): + _quick_filters = frappe.db.get_value("CRM Global Settings", global_settings, "json") + _quick_filters = json.loads(_quick_filters) or [] + + fields = [] + + for filter in _quick_filters: + if filter == "name": + fields.append({"label": "Name", "fieldname": "name", "fieldtype": "Data"}) + else: + field = next((f for f in meta.fields if f.fieldname == filter), None) + if field: + fields.append(field) + + else: + fields = [field for field in meta.fields if field.in_standard_filter] + for field in fields: - options = field.options - if field.fieldtype == "Select" and options and isinstance(options, str): + options = field.get("options") + if field.get("fieldtype") == "Select" and options and isinstance(options, str): options = options.split("\n") options = [{"label": option, "value": option} for option in options] if not any([not option.get("value") for option in options]): options.insert(0, {"label": "", "value": ""}) quick_filters.append( { - "label": _(field.label), - "fieldname": field.fieldname, - "fieldtype": field.fieldtype, + "label": _(field.get("label")), + "fieldname": field.get("fieldname"), + "fieldtype": field.get("fieldtype"), "options": options, } ) @@ -205,6 +222,55 @@ def get_quick_filters(doctype: str): return quick_filters +@frappe.whitelist() +def update_quick_filters(quick_filters: str, old_filters: str, doctype: str): + quick_filters = json.loads(quick_filters) + old_filters = json.loads(old_filters) + + new_filters = [filter for filter in quick_filters if filter not in old_filters] + removed_filters = [filter for filter in old_filters if filter not in quick_filters] + + # update or create global quick filter settings + create_update_global_settings(doctype, quick_filters) + + # remove old filters + for filter in removed_filters: + update_in_standard_filter(filter, doctype, 0) + + # add new filters + for filter in new_filters: + update_in_standard_filter(filter, doctype, 1) + + +def create_update_global_settings(doctype, quick_filters): + if global_settings := frappe.db.exists("CRM Global Settings", {"dt": doctype, "type": "Quick Filters"}): + frappe.db.set_value("CRM Global Settings", global_settings, "json", json.dumps(quick_filters)) + else: + # create CRM Global Settings doc + doc = frappe.new_doc("CRM Global Settings") + doc.dt = doctype + doc.type = "Quick Filters" + doc.json = json.dumps(quick_filters) + doc.insert() + + +def update_in_standard_filter(fieldname, doctype, value): + if property_name := frappe.db.exists( + "Property Setter", + {"doc_type": doctype, "field_name": fieldname, "property": "in_standard_filter"}, + ): + frappe.db.set_value("Property Setter", property_name, "value", value) + else: + make_property_setter( + doctype, + fieldname, + "in_standard_filter", + value, + "Check", + validate_fields_for_doctype=False, + ) + + @frappe.whitelist() def get_data( doctype: str, @@ -382,7 +448,7 @@ def get_data( all_count = frappe.get_list( doctype, filters=convert_filter_to_tuple(doctype, new_filters), - fields="count(*) as total_count" + fields="count(*) as total_count", )[0].total_count kc["all_count"] = all_count @@ -485,9 +551,9 @@ def get_data( "page_length_count": page_length_count, "is_default": is_default, "views": get_views(doctype), - "total_count": frappe.get_list( - doctype, filters=filters, fields="count(*) as total_count" - )[0].total_count, + "total_count": frappe.get_list(doctype, filters=filters, fields="count(*) as total_count")[ + 0 + ].total_count, "row_count": len(data), "form_script": get_form_script(doctype), "list_script": get_form_script(doctype, "List"), diff --git a/crm/fcrm/doctype/crm_global_settings/__init__.py b/crm/fcrm/doctype/crm_global_settings/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/crm/fcrm/doctype/crm_global_settings/crm_global_settings.js b/crm/fcrm/doctype/crm_global_settings/crm_global_settings.js new file mode 100644 index 00000000..1a5a5af6 --- /dev/null +++ b/crm/fcrm/doctype/crm_global_settings/crm_global_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("CRM Global Settings", { +// refresh(frm) { + +// }, +// }); diff --git a/crm/fcrm/doctype/crm_global_settings/crm_global_settings.json b/crm/fcrm/doctype/crm_global_settings/crm_global_settings.json new file mode 100644 index 00000000..9214f829 --- /dev/null +++ b/crm/fcrm/doctype/crm_global_settings/crm_global_settings.json @@ -0,0 +1,74 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "format:{type}-{dt}", + "creation": "2025-02-28 14:37:10.002433", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "dt", + "column_break_kipp", + "type", + "section_break_vass", + "json" + ], + "fields": [ + { + "default": "DocType", + "fieldname": "dt", + "fieldtype": "Link", + "in_list_view": 1, + "label": "DocType", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "column_break_kipp", + "fieldtype": "Column Break" + }, + { + "fieldname": "type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "options": "Quick Filters\nSidebar Items", + "reqd": 1 + }, + { + "fieldname": "section_break_vass", + "fieldtype": "Section Break" + }, + { + "fieldname": "json", + "fieldtype": "JSON", + "label": "JSON" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-02-28 14:55:33.801215", + "modified_by": "Administrator", + "module": "FCRM", + "name": "CRM Global Settings", + "naming_rule": "Expression", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/crm/fcrm/doctype/crm_global_settings/crm_global_settings.py b/crm/fcrm/doctype/crm_global_settings/crm_global_settings.py new file mode 100644 index 00000000..a66508c1 --- /dev/null +++ b/crm/fcrm/doctype/crm_global_settings/crm_global_settings.py @@ -0,0 +1,9 @@ +# 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 CRMGlobalSettings(Document): + pass diff --git a/crm/fcrm/doctype/crm_global_settings/test_crm_global_settings.py b/crm/fcrm/doctype/crm_global_settings/test_crm_global_settings.py new file mode 100644 index 00000000..e0e1fb20 --- /dev/null +++ b/crm/fcrm/doctype/crm_global_settings/test_crm_global_settings.py @@ -0,0 +1,30 @@ +# 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 UnitTestCRMGlobalSettings(UnitTestCase): + """ + Unit tests for CRMGlobalSettings. + Use this class for testing individual functions and methods. + """ + + pass + + +class IntegrationTestCRMGlobalSettings(IntegrationTestCase): + """ + Integration tests for CRMGlobalSettings. + Use this class for testing interactions between multiple components. + """ + + pass diff --git a/crm/hooks.py b/crm/hooks.py index e652e3e1..1f7d2ad0 100644 --- a/crm/hooks.py +++ b/crm/hooks.py @@ -132,7 +132,6 @@ before_uninstall = "crm.uninstall.before_uninstall" override_doctype_class = { "Contact": "crm.overrides.contact.CustomContact", "Email Template": "crm.overrides.email_template.CustomEmailTemplate", - "User": "crm.overrides.user.CustomUser", } # Document Events @@ -161,6 +160,7 @@ doc_events = { }, "User": { "before_validate": ["crm.api.demo.validate_user"], + "validate_reset_password": ["crm.api.demo.validate_reset_password"], }, } diff --git a/crm/overrides/user.py b/crm/overrides/user.py deleted file mode 100644 index d938825c..00000000 --- a/crm/overrides/user.py +++ /dev/null @@ -1,10 +0,0 @@ -# import frappe -from frappe import _ -from frappe.core.doctype.user.user import User -from crm.api.demo import validate_reset_password - - -class CustomUser(User): - def validate_reset_password(self): - # restrict demo user to reset password - validate_reset_password(self) diff --git a/frontend/src/components/Apps.vue b/frontend/src/components/Apps.vue index 6f9e3a0f..92686937 100644 --- a/frontend/src/components/Apps.vue +++ b/frontend/src/components/Apps.vue @@ -1,5 +1,10 @@