diff --git a/crm/api/doc.py b/crm/api/doc.py index d109f409..eb963a63 100644 --- a/crm/api/doc.py +++ b/crm/api/doc.py @@ -30,6 +30,11 @@ def get_filterable_fields(doctype: str): "Text", ] + c = get_controller(doctype) + restricted_fields = [] + if hasattr(c, "get_non_filterable_fields"): + restricted_fields = c.get_non_filterable_fields() + from_doc_fields = ( frappe.qb.from_(DocField) .select( @@ -42,6 +47,7 @@ def get_filterable_fields(doctype: str): .where(DocField.parent == doctype) .where(DocField.hidden == False) .where(Criterion.any([DocField.fieldtype == i for i in allowed_fieldtypes])) + .where(Criterion.all([DocField.fieldname != i for i in restricted_fields])) .run(as_dict=True) ) res = [] @@ -50,21 +56,31 @@ def get_filterable_fields(doctype: str): @frappe.whitelist() -def get_list_data(doctype: str, filters: dict, order_by: str): - columns = [ - {"label": "Name", "type": "Data", "key": "name", "width": "16rem"}, - {"label": "Last Modified", "type": "Datetime", "key": "modified", "width": "8rem"}, - ] - rows = ["name"] - +def get_list_data(doctype: str, filters: dict, order_by: str, columns=None , rows=None, custom_view_name=None): + custom_view = False + filters = frappe._dict(filters) is_default = True + if columns or rows: + custom_view = True + is_default = False + columns = frappe.parse_json(columns) + rows = frappe.parse_json(rows) - if frappe.db.exists("CRM List View Settings", doctype): - list_view_settings = frappe.get_doc("CRM List View Settings", doctype) + if not columns: + columns = [ + {"label": "Name", "type": "Data", "key": "name", "width": "16rem"}, + {"label": "Last Modified", "type": "Datetime", "key": "modified", "width": "8rem"}, + ] + + if not rows: + rows = ["name"] + + if not custom_view and frappe.db.exists("CRM View Settings", doctype): + list_view_settings = frappe.get_doc("CRM View Settings", doctype) columns = frappe.parse_json(list_view_settings.columns) rows = frappe.parse_json(list_view_settings.rows) is_default = False - else: + elif not custom_view or is_default: list = get_controller(doctype) if hasattr(list, "default_list_data"): @@ -117,14 +133,26 @@ def get_list_data(doctype: str, filters: dict, order_by: str): if field not in fields: fields.append(field) + if not is_default and custom_view_name: + is_default = frappe.db.get_value("CRM View Settings", custom_view_name, "default_columns") + return { "data": data, "columns": columns, "rows": rows, "fields": fields, "is_default": is_default, + "views": get_views(doctype), } +def get_views(doctype): + views = frappe.get_all( + "CRM View Settings", + fields=["*"], + filters={"dt": doctype, "user": frappe.session.user} + ) + return views + def get_doctype_fields(doctype): not_allowed_fieldtypes = [ diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.py b/crm/fcrm/doctype/crm_lead/crm_lead.py index 94f9c824..d21fdaa6 100644 --- a/crm/fcrm/doctype/crm_lead/crm_lead.py +++ b/crm/fcrm/doctype/crm_lead/crm_lead.py @@ -196,6 +196,10 @@ class CRMLead(Document): { "label": 'Mobile no', "value": 'mobile_no' }, ] + @staticmethod + def get_non_filterable_fields(): + return ["converted"] + @staticmethod def default_list_data(): columns = [ diff --git a/crm/fcrm/doctype/crm_list_view_settings/crm_list_view_settings.json b/crm/fcrm/doctype/crm_list_view_settings/crm_list_view_settings.json deleted file mode 100644 index 5806c815..00000000 --- a/crm/fcrm/doctype/crm_list_view_settings/crm_list_view_settings.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "actions": [], - "allow_rename": 1, - "autoname": "prompt", - "creation": "2023-11-27 16:29:10.993403", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "user", - "columns", - "rows" - ], - "fields": [ - { - "fieldname": "columns", - "fieldtype": "Code", - "label": "Columns" - }, - { - "fieldname": "user", - "fieldtype": "Link", - "label": "User", - "options": "User" - }, - { - "fieldname": "rows", - "fieldtype": "Code", - "label": "Rows" - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2023-11-28 00:17:42.675332", - "modified_by": "Administrator", - "module": "FCRM", - "name": "CRM List View Settings", - "naming_rule": "Set by user", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "read_only": 1, - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "track_changes": 1 -} \ No newline at end of file diff --git a/crm/fcrm/doctype/crm_list_view_settings/crm_list_view_settings.py b/crm/fcrm/doctype/crm_list_view_settings/crm_list_view_settings.py deleted file mode 100644 index 66770ce8..00000000 --- a/crm/fcrm/doctype/crm_list_view_settings/crm_list_view_settings.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt -import json -import frappe -from frappe.model.document import Document, get_controller - - -class CRMListViewSettings(Document): - pass - - -@frappe.whitelist() -def update(doctype, columns, rows): - default_rows = sync_default_list_rows(doctype) - - if default_rows: - rows = rows + default_rows - - rows = remove_duplicates(rows) - - if not frappe.db.exists("CRM List View Settings", doctype): - # create new CRM List View Settings - doc = frappe.new_doc("CRM List View Settings") - doc.name = doctype - doc.columns = json.dumps(columns) - doc.rows = json.dumps(rows) - doc.insert() - else: - # update existing CRM List View Settings - doc = frappe.get_doc("CRM List View Settings", doctype) - doc.columns = json.dumps(columns) - doc.rows = json.dumps(rows) - doc.save() - -def remove_duplicates(l): - return list(dict.fromkeys(l)) - -def sync_default_list_rows(doctype): - list = get_controller(doctype) - rows = [] - - if hasattr(list, "default_list_data"): - rows = list.default_list_data().get("rows") - - return rows - -@frappe.whitelist() -def reset_to_default(doctype): - if frappe.db.exists("CRM List View Settings", doctype): - frappe.delete_doc("CRM List View Settings", doctype) \ No newline at end of file diff --git a/crm/fcrm/doctype/crm_list_view_settings/__init__.py b/crm/fcrm/doctype/crm_view_settings/__init__.py similarity index 100% rename from crm/fcrm/doctype/crm_list_view_settings/__init__.py rename to crm/fcrm/doctype/crm_view_settings/__init__.py diff --git a/crm/fcrm/doctype/crm_list_view_settings/crm_list_view_settings.js b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.js similarity index 76% rename from crm/fcrm/doctype/crm_list_view_settings/crm_list_view_settings.js rename to crm/fcrm/doctype/crm_view_settings/crm_view_settings.js index 87850935..df838f2f 100644 --- a/crm/fcrm/doctype/crm_list_view_settings/crm_list_view_settings.js +++ b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.js @@ -1,7 +1,7 @@ // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -// frappe.ui.form.on("CRM List View Settings", { +// frappe.ui.form.on("CRM View Settings", { // refresh(frm) { // }, diff --git a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json new file mode 100644 index 00000000..8b7ae5f0 --- /dev/null +++ b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json @@ -0,0 +1,116 @@ +{ + "actions": [], + "autoname": "autoincrement", + "creation": "2023-11-27 16:29:10.993403", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "label", + "user", + "column_break_zacm", + "dt", + "columns_tab", + "default_columns", + "columns", + "rows", + "filters_tab", + "filters", + "order_by_tab", + "order_by" + ], + "fields": [ + { + "fieldname": "columns", + "fieldtype": "Code", + "label": "Columns" + }, + { + "fieldname": "user", + "fieldtype": "Link", + "label": "User", + "options": "User" + }, + { + "fieldname": "rows", + "fieldtype": "Code", + "label": "Rows" + }, + { + "fieldname": "filters", + "fieldtype": "Code", + "label": "Filters" + }, + { + "fieldname": "columns_tab", + "fieldtype": "Tab Break", + "label": "Columns" + }, + { + "fieldname": "filters_tab", + "fieldtype": "Tab Break", + "label": "Filters" + }, + { + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Label" + }, + { + "fieldname": "column_break_zacm", + "fieldtype": "Column Break" + }, + { + "fieldname": "dt", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "DocType", + "options": "DocType" + }, + { + "fieldname": "order_by_tab", + "fieldtype": "Tab Break", + "label": "Order By" + }, + { + "fieldname": "order_by", + "fieldtype": "Code", + "label": "Order By" + }, + { + "default": "0", + "fieldname": "default_columns", + "fieldtype": "Check", + "label": "Default Columns" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2023-12-30 19:28:02.541487", + "modified_by": "Administrator", + "module": "FCRM", + "name": "CRM View Settings", + "naming_rule": "Autoincrement", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "read_only": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py new file mode 100644 index 00000000..dbb218f6 --- /dev/null +++ b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py @@ -0,0 +1,85 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt +import json +import frappe +from frappe.model.document import Document, get_controller + + +class CRMViewSettings(Document): + pass + +@frappe.whitelist() +def create(view, duplicate=False): + view = frappe._dict(view) + + if duplicate: + view.filters = json.loads(view.filters) + view.columns = json.loads(view.columns) + view.rows = json.loads(view.rows) + + doc = frappe.new_doc("CRM View Settings") + doc.name = view.label + doc.label = view.label + doc.dt = view.doctype + doc.user = frappe.session.user + doc.filters = json.dumps(view.filters) + doc.order_by = view.order_by + doc.default_columns = view.default_columns or False + + if not view.columns: + view.columns = [] + if not view.rows: + view.rows = [] + + default_rows = sync_default_list_rows(view.doctype) + + if default_rows: + view.rows = view.rows + default_rows + + view.rows = remove_duplicates(view.rows) + + doc.columns = json.dumps(view.columns) + doc.rows = json.dumps(view.rows) + doc.insert() + return doc + +@frappe.whitelist() +def update(view): + view = frappe._dict(view) + default_rows = sync_default_list_rows(view.doctype) + columns = view.columns or [] + filters = view.filters + rows = view.rows or [] + default_columns = view.default_columns or False + + if default_rows: + rows = rows + default_rows + + rows = remove_duplicates(rows) + + doc = frappe.get_doc("CRM View Settings", view.name) + doc.label = view.label + doc.default_columns = default_columns + doc.filters = json.dumps(filters) + doc.order_by = view.order_by + doc.columns = json.dumps(columns) + doc.rows = json.dumps(rows) + doc.save() + return doc + +@frappe.whitelist() +def delete(name): + if frappe.db.exists("CRM View Settings", name): + frappe.delete_doc("CRM View Settings", name) + +def remove_duplicates(l): + return list(dict.fromkeys(l)) + +def sync_default_list_rows(doctype): + list = get_controller(doctype) + rows = [] + + if hasattr(list, "default_list_data"): + rows = list.default_list_data().get("rows") + + return rows diff --git a/crm/fcrm/doctype/crm_list_view_settings/test_crm_list_view_settings.py b/crm/fcrm/doctype/crm_view_settings/test_crm_view_settings.py similarity index 77% rename from crm/fcrm/doctype/crm_list_view_settings/test_crm_list_view_settings.py rename to crm/fcrm/doctype/crm_view_settings/test_crm_view_settings.py index d99027bc..cd6df2d7 100644 --- a/crm/fcrm/doctype/crm_list_view_settings/test_crm_list_view_settings.py +++ b/crm/fcrm/doctype/crm_view_settings/test_crm_view_settings.py @@ -5,5 +5,5 @@ from frappe.tests.utils import FrappeTestCase -class TestCRMListViewSettings(FrappeTestCase): +class TestCRMViewSettings(FrappeTestCase): pass diff --git a/frontend/src/components/Controls/Link.vue b/frontend/src/components/Controls/Link.vue index d351db3c..5eef3930 100644 --- a/frontend/src/components/Controls/Link.vue +++ b/frontend/src/components/Controls/Link.vue @@ -88,17 +88,17 @@ watchDebounced( val = val || '' if (text.value === val) return text.value = val - options.update({ - params: { - txt: val, - doctype: props.doctype, - }, - }) - options.reload() + reload(val) }, { debounce: 300, immediate: true } ) +watchDebounced( + () => props.doctype, + () => reload(''), + { debounce: 300, immediate: true } +) + const options = createResource({ url: 'frappe.desk.search.search_link', cache: [props.doctype, text.value], @@ -117,6 +117,16 @@ const options = createResource({ }, }) +function reload(val) { + options.update({ + params: { + txt: val, + doctype: props.doctype, + }, + }) + options.reload() +} + const labelClasses = computed(() => { return [ { diff --git a/frontend/src/components/Filter.vue b/frontend/src/components/Filter.vue index b333a312..53583242 100644 --- a/frontend/src/components/Filter.vue +++ b/frontend/src/components/Filter.vue @@ -3,27 +3,27 @@ - -
-
- - - -
-
- - - -
-
+ getOrderBy(), - (value, old_value) => { - if (!value && !old_value) return - deals.params = getParams() - deals.reload() - }, - { immediate: true } -) - -watch( - storage, - useDebounceFn((value, old_value) => { - if (JSON.stringify([...value]) === JSON.stringify([...old_value])) return - deals.params = getParams() - deals.reload() - }, 300), - { deep: true } -) +// deals data is loaded in the ViewControls component +const deals = ref({}) +// Rows const rows = computed(() => { - if (!deals.data?.data) return [] - return deals.data.data.map((deal) => { + if (!deals.value?.data?.data) return [] + return deals.value.data.data.map((deal) => { let _rows = {} - deals.data.rows.forEach((row) => { + deals.value.data.rows.forEach((row) => { _rows[row] = deal[row] let org = getOrganization(deal.organization) @@ -229,49 +162,7 @@ const rows = computed(() => { }) }) -const viewsDropdownOptions = [ - { - label: 'List', - icon: 'list', - onClick() { - currentView.value = { - label: 'List', - icon: 'list', - } - }, - }, - { - label: 'Table', - icon: 'grid', - onClick() { - currentView.value = { - label: 'Table', - icon: 'grid', - } - }, - }, - { - label: 'Calender', - icon: 'calendar', - onClick() { - currentView.value = { - label: 'Calender', - icon: 'calendar', - } - }, - }, - { - label: 'Board', - icon: 'columns', - onClick() { - currentView.value = { - label: 'Board', - icon: 'columns', - } - }, - }, -] - +// New Deal const showNewDialog = ref(false) let newDeal = reactive({ @@ -294,8 +185,6 @@ const createDeal = createResource({ }, }) -const router = useRouter() - function createNewDeal(close) { createDeal .submit(newDeal, { diff --git a/frontend/src/pages/Leads.vue b/frontend/src/pages/Leads.vue index 2b13adec..2d7f3141 100644 --- a/frontend/src/pages/Leads.vue +++ b/frontend/src/pages/Leads.vue @@ -9,29 +9,11 @@ -
-
- - - -
-
- - - -
-
+ getOrderBy(), - (value, old_value) => { - if (!value && !old_value) return - leads.params = getParams() - leads.reload() - }, - { immediate: true } -) - -watch( - storage, - useDebounceFn((value, old_value) => { - if (JSON.stringify([...value]) === JSON.stringify([...old_value])) return - leads.params = getParams() - leads.reload() - }, 300), - { deep: true } -) +// leads data is loaded in the ViewControls component +const leads = ref({}) +// Rows const rows = computed(() => { - if (!leads.data?.data) return [] - return leads.data.data.map((lead) => { + if (!leads.value?.data?.data) return [] + return leads.value?.data.data.map((lead) => { let _rows = {} - leads.data.rows.forEach((row) => { + leads.value?.data.rows.forEach((row) => { _rows[row] = lead[row] if (row == 'lead_name') { @@ -228,49 +162,7 @@ const rows = computed(() => { }) }) -const viewsDropdownOptions = [ - { - label: 'List', - icon: 'list', - onClick() { - currentView.value = { - label: 'List', - icon: 'list', - } - }, - }, - { - label: 'Table', - icon: 'grid', - onClick() { - currentView.value = { - label: 'Table', - icon: 'grid', - } - }, - }, - { - label: 'Calender', - icon: 'calendar', - onClick() { - currentView.value = { - label: 'Calender', - icon: 'calendar', - } - }, - }, - { - label: 'Board', - icon: 'columns', - onClick() { - currentView.value = { - label: 'Board', - icon: 'columns', - } - }, - }, -] - +// New Lead const showNewDialog = ref(false) let newLead = reactive({ @@ -297,8 +189,6 @@ const createLead = createResource({ }, }) -const router = useRouter() - function createNewLead(close) { createLead .submit(newLead, { diff --git a/frontend/src/pages/Organizations.vue b/frontend/src/pages/Organizations.vue index 09ac6b2f..cc545a37 100644 --- a/frontend/src/pages/Organizations.vue +++ b/frontend/src/pages/Organizations.vue @@ -13,36 +13,16 @@ -
-
- - - -
-
- - - -
-
+ -
+
@@ -60,30 +40,23 @@ import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue' import LayoutHeader from '@/components/LayoutHeader.vue' import OrganizationModal from '@/components/Modals/OrganizationModal.vue' import OrganizationsListView from '@/components/ListViews/OrganizationsListView.vue' -import SortBy from '@/components/SortBy.vue' -import Filter from '@/components/Filter.vue' -import ViewSettings from '@/components/ViewSettings.vue' -import { useOrderBy } from '@/composables/orderby' -import { useFilter } from '@/composables/filter' -import { useDebounceFn } from '@vueuse/core' -import { FeatherIcon, Breadcrumbs, Dropdown, createResource } from 'frappe-ui' +import ViewControls from '@/components/ViewControls.vue' +import { FeatherIcon, Breadcrumbs } from 'frappe-ui' import { dateFormat, dateTooltipFormat, timeAgo, formatNumberIntoCurrency, } from '@/utils' -import { ref, computed, watch } from 'vue' +import { ref, computed } from 'vue' import { useRoute } from 'vue-router' const route = useRoute() -const { get: getOrderBy } = useOrderBy() -const { getArgs, storage } = useFilter() const showOrganizationModal = ref(false) const currentOrganization = computed(() => { - return organizations.data?.data?.find( + return organizations.value?.data?.data?.find( (organization) => organization.name === route.params.organizationId ) }) @@ -101,53 +74,13 @@ const breadcrumbs = computed(() => { return items }) -const currentView = ref({ - label: 'List', - icon: 'list', -}) - -function getParams() { - const filters = getArgs() || {} - const order_by = getOrderBy() || 'modified desc' - - return { - doctype: 'CRM Organization', - filters: filters, - order_by: order_by, - } -} - -const organizations = createResource({ - url: 'crm.api.doc.get_list_data', - params: getParams(), - auto: true, -}) - -watch( - () => getOrderBy(), - (value, old_value) => { - if (!value && !old_value) return - organizations.params = getParams() - organizations.reload() - }, - { immediate: true } -) - -watch( - storage, - useDebounceFn((value, old_value) => { - if (JSON.stringify([...value]) === JSON.stringify([...old_value])) return - organizations.params = getParams() - organizations.reload() - }, 300), - { deep: true } -) +const organizations = ref({}) const rows = computed(() => { - if (!organizations.data?.data) return [] - return organizations.data.data.map((organization) => { + if (!organizations.value?.data?.data) return [] + return organizations.value?.data.data.map((organization) => { let _rows = {} - organizations.data.rows.forEach((row) => { + organizations.value?.data.rows.forEach((row) => { _rows[row] = organization[row] if (row === 'organization_name') { @@ -170,49 +103,6 @@ const rows = computed(() => { }) }) -const viewsDropdownOptions = [ - { - label: 'List', - icon: 'list', - onClick() { - currentView.value = { - label: 'List', - icon: 'list', - } - }, - }, - { - label: 'Table', - icon: 'grid', - onClick() { - currentView.value = { - label: 'Table', - icon: 'grid', - } - }, - }, - { - label: 'Calender', - icon: 'calendar', - onClick() { - currentView.value = { - label: 'Calender', - icon: 'calendar', - } - }, - }, - { - label: 'Board', - icon: 'columns', - onClick() { - currentView.value = { - label: 'Board', - icon: 'columns', - } - }, - }, -] - function website(url) { return url && url.replace(/^(?:https?:\/\/)?(?:www\.)?/i, '') } diff --git a/frontend/src/stores/views.js b/frontend/src/stores/views.js new file mode 100644 index 00000000..64039643 --- /dev/null +++ b/frontend/src/stores/views.js @@ -0,0 +1,34 @@ +import { defineStore } from 'pinia' +import { createListResource } from 'frappe-ui' +import { reactive } from 'vue' + +export const viewsStore = defineStore('crm-views', () => { + let viewsByName = reactive({}) + + const views = createListResource({ + doctype: 'CRM View Settings', + fields: ['*'], + cache: 'crm-views', + initialData: [], + auto: true, + transform(views) { + for (let view of views) { + viewsByName[view.name] = view + } + return views + }, + }) + + function getView(view) { + if (!view) return null + if (!viewsByName[view]) { + views.reload() + } + return viewsByName[view] + } + + return { + views, + getView, + } +})