From a171f817389f7feb04fd629fe754ac326071c0f0 Mon Sep 17 00:00:00 2001 From: Muhammed Raihan P A <99407382+pu-raihan@users.noreply.github.com> Date: Wed, 19 Feb 2025 00:49:59 -0800 Subject: [PATCH 01/21] fix: prevent unnecessary notification reloads for all users - Emit WebSocket event only to the intended recipient instead of broadcasting - Improves performance by reducing unnecessary API calls and WebSocket traffic --- crm/fcrm/doctype/crm_notification/crm_notification.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crm/fcrm/doctype/crm_notification/crm_notification.py b/crm/fcrm/doctype/crm_notification/crm_notification.py index 69aa127d..dbc075a1 100644 --- a/crm/fcrm/doctype/crm_notification/crm_notification.py +++ b/crm/fcrm/doctype/crm_notification/crm_notification.py @@ -8,7 +8,8 @@ from frappe.model.document import Document class CRMNotification(Document): def on_update(self): - frappe.publish_realtime("crm_notification") + if self.to_user: + frappe.publish_realtime("crm_notification", user= self.to_user) def notify_user(args): """ @@ -33,4 +34,4 @@ def notify_user(args): if frappe.db.exists("CRM Notification", values): return - frappe.get_doc(values).insert(ignore_permissions=True) \ No newline at end of file + frappe.get_doc(values).insert(ignore_permissions=True) From cf4393550881bc358e1a92ec87dfa19900303e59 Mon Sep 17 00:00:00 2001 From: Muhammed Raihan P A <99407382+pu-raihan@users.noreply.github.com> Date: Wed, 19 Feb 2025 01:22:54 -0800 Subject: [PATCH 02/21] clear contact details when switching from existing to new contact - Ensure previously selected contact details are removed when toggling - Clear all fields in the 'contact_details_section' when choosing an existing contact - Set 'contact' field to null when switching to a new contact - Prevents unnecessary data from being sent to the API --- frontend/src/components/Modals/DealModal.vue | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frontend/src/components/Modals/DealModal.vue b/frontend/src/components/Modals/DealModal.vue index 9c38f1ef..068feed1 100644 --- a/frontend/src/components/Modals/DealModal.vue +++ b/frontend/src/components/Modals/DealModal.vue @@ -190,6 +190,24 @@ function createDeal() { if (deal.website && !deal.website.startsWith('http')) { deal.website = 'https://' + deal.website } + if (chooseExistingContact.value) { + const contactDetailsFields = new Set( + tabs.data.flatMap((tab) => + tab.sections + .filter((section) => section.name === 'contact_details_section') + .flatMap((section) => + section.columns.flatMap((column) => + column.fields.map((field) => field.fieldname), + ), + ), + ), + ) + + contactDetailsFields.forEach((field) => { + deal[field] = null + }) + } else deal['contact'] = null + createResource({ url: 'crm.fcrm.doctype.crm_deal.crm_deal.create_deal', params: { args: deal }, From 4d3a90cf3c6a0b4149244dcf624bcb354798e115 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 20 Feb 2025 12:51:35 +0530 Subject: [PATCH 03/21] fix: renamed is_default to is_standard --- crm/api/doc.py | 2 +- .../crm_view_settings/crm_view_settings.json | 16 ++++++------ .../crm_view_settings/crm_view_settings.py | 6 ++--- frontend/src/components/ViewControls.vue | 26 +++++++++---------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/crm/api/doc.py b/crm/api/doc.py index eba670c7..a8627584 100644 --- a/crm/api/doc.py +++ b/crm/api/doc.py @@ -276,7 +276,7 @@ def get_data( default_view_filters = { "dt": doctype, "type": view_type or "list", - "is_default": 1, + "is_standard": 1, "user": frappe.session.user, } diff --git a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json index dde0b128..f1081b79 100644 --- a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json +++ b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json @@ -8,7 +8,7 @@ "label", "icon", "user", - "is_default", + "is_standard", "column_break_zacm", "type", "dt", @@ -112,12 +112,6 @@ "fieldtype": "Check", "label": "Public" }, - { - "default": "0", - "fieldname": "is_default", - "fieldtype": "Check", - "label": "Is Default" - }, { "fieldname": "icon", "fieldtype": "Data", @@ -178,11 +172,17 @@ "fieldname": "title_field", "fieldtype": "Data", "label": "Title Field" + }, + { + "default": "0", + "fieldname": "is_standard", + "fieldtype": "Check", + "label": "Is Standard" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-06-25 19:40:12.067788", + "modified": "2025-02-20 12:41:51.574655", "modified_by": "Administrator", "module": "FCRM", "name": "CRM View Settings", diff --git a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py index 13d08686..c62a0f52 100644 --- a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py +++ b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py @@ -153,7 +153,7 @@ def set_as_default(name=None, type=None, doctype=None): @frappe.whitelist() -def create_or_update_default_view(view): +def create_or_update_standard_view(view): view = frappe._dict(view) filters = parse_json(view.filters) or {} @@ -173,7 +173,7 @@ def create_or_update_default_view(view): doc = frappe.db.exists( "CRM View Settings", - {"dt": view.doctype, "type": view.type or "list", "is_default": True, "user": frappe.session.user}, + {"dt": view.doctype, "type": view.type or "list", "is_standard": True, "user": frappe.session.user}, ) if doc: doc = frappe.get_doc("CRM View Settings", doc) @@ -210,5 +210,5 @@ def create_or_update_default_view(view): doc.kanban_fields = json.dumps(kanban_fields) doc.columns = json.dumps(columns) doc.rows = json.dumps(rows) - doc.is_default = True + doc.is_standard = True doc.insert() diff --git a/frontend/src/components/ViewControls.vue b/frontend/src/components/ViewControls.vue index 56a84669..a2fd14db 100644 --- a/frontend/src/components/ViewControls.vue +++ b/frontend/src/components/ViewControls.vue @@ -310,13 +310,13 @@ const currentView = computed(() => { label: _view?.label || props.options?.defaultViewName || getViewType().label, icon: _view?.icon || getViewType().icon, - is_default: !_view || _view.is_default, + is_standard: !_view || _view.is_standard, } }) usePageMeta(() => { let label = currentView.value.label - if (currentView.value.is_default) { + if (currentView.value.is_standard) { let routeName = route.name label = `${routeName} - ${label}` } @@ -558,7 +558,7 @@ const viewsDropdownOptions = computed(() => { }) let publicViews = list.value.data.views.filter((v) => v.public) let savedViews = list.value.data.views.filter( - (v) => !v.pinned && !v.public && !v.is_default, + (v) => !v.pinned && !v.public && !v.is_standard, ) let pinnedViews = list.value.data.views.filter((v) => v.pinned) @@ -662,7 +662,7 @@ function updateFilter(filters) { list.value.reload() if (!route.query.view) { - create_or_update_default_view() + createOrUpdateStandardView() } } @@ -677,7 +677,7 @@ function updateSort(order_by) { list.value.reload() if (!route.query.view) { - create_or_update_default_view() + createOrUpdateStandardView() } } @@ -692,7 +692,7 @@ function updateGroupBy(group_by_field) { list.value.reload() if (!route.query.view) { - create_or_update_default_view() + createOrUpdateStandardView() } } @@ -726,7 +726,7 @@ function updateColumns(obj) { viewUpdated.value = true if (!route.query.view) { - create_or_update_default_view() + createOrUpdateStandardView() } } @@ -768,7 +768,7 @@ async function updateKanbanSettings(data) { list.value.reload() if (!route.query.view) { - create_or_update_default_view() + createOrUpdateStandardView() } else if (!data.column_field) { if (isDirty) { $dialog({ @@ -780,14 +780,14 @@ async function updateKanbanSettings(data) { label: __('Update'), variant: 'solid', onClick: (close) => { - update_custom_view() + updateCustomView() close() }, }, ], }) } else { - update_custom_view() + updateCustomView() } } } @@ -811,11 +811,11 @@ function loadMoreKanban(columnName) { list.value.reload() } -function create_or_update_default_view() { +function createOrUpdateStandardView() { if (route.query.view) return view.value.doctype = props.doctype call( - 'crm.fcrm.doctype.crm_view_settings.crm_view_settings.create_or_update_default_view', + 'crm.fcrm.doctype.crm_view_settings.crm_view_settings.create_or_update_standard_view', { view: view.value, }, @@ -842,7 +842,7 @@ function create_or_update_default_view() { }) } -function update_custom_view() { +function updateCustomView() { viewUpdated.value = false view.value = { doctype: props.doctype, From c0f6dcd7e05092679406886292d2692b3159efc5 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 20 Feb 2025 12:53:03 +0530 Subject: [PATCH 04/21] fix: removed default_view field from fcrm settings --- crm/fcrm/doctype/fcrm_settings/fcrm_settings.json | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json index f2aa7b83..250c8c29 100644 --- a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json +++ b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json @@ -6,8 +6,6 @@ "engine": "InnoDB", "field_order": [ "defaults_tab", - "default_view", - "column_break_jeeh", "restore_defaults", "branding_tab", "brand_name", @@ -58,21 +56,12 @@ "fieldname": "favicon", "fieldtype": "Attach", "label": "Favicon" - }, - { - "fieldname": "default_view", - "fieldtype": "Data", - "label": "Default View" - }, - { - "fieldname": "column_break_jeeh", - "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-02-18 17:05:39.440396", + "modified": "2025-02-20 12:38:38.088477", "modified_by": "Administrator", "module": "FCRM", "name": "FCRM Settings", From d057926f7ab73786813b14787692d45dbdcd01d0 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 20 Feb 2025 12:53:40 +0530 Subject: [PATCH 05/21] fix: added is_default field in crm view settings --- .../doctype/crm_view_settings/crm_view_settings.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json index f1081b79..7beefffb 100644 --- a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json +++ b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json @@ -9,6 +9,7 @@ "icon", "user", "is_standard", + "is_default", "column_break_zacm", "type", "dt", @@ -178,11 +179,17 @@ "fieldname": "is_standard", "fieldtype": "Check", "label": "Is Standard" + }, + { + "default": "0", + "fieldname": "is_default", + "fieldtype": "Check", + "label": "Is Default" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-02-20 12:41:51.574655", + "modified": "2025-02-20 12:52:27.522624", "modified_by": "Administrator", "module": "FCRM", "name": "CRM View Settings", From c0bcd2a34ffd10c40cc67fbdd356bc55c66150fd Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 20 Feb 2025 13:02:55 +0530 Subject: [PATCH 06/21] fix: renamed is_default to is_standard --- frontend/src/components/ViewControls.vue | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/ViewControls.vue b/frontend/src/components/ViewControls.vue index a2fd14db..5d4f6715 100644 --- a/frontend/src/components/ViewControls.vue +++ b/frontend/src/components/ViewControls.vue @@ -480,11 +480,11 @@ async function exportRows() { export_type.value = 'Excel' } -let defaultViews = [] +let standardViews = [] let allowedViews = props.options.allowedViews || ['list'] if (allowedViews.includes('list')) { - defaultViews.push({ + standardViews.push({ name: 'list', label: __(props.options?.defaultViewName) || __('List'), icon: markRaw(ListIcon), @@ -495,7 +495,7 @@ if (allowedViews.includes('list')) { }) } if (allowedViews.includes('kanban')) { - defaultViews.push({ + standardViews.push({ name: 'kanban', label: __(props.options?.defaultViewName) || __('Kanban'), icon: markRaw(KanbanIcon), @@ -506,7 +506,7 @@ if (allowedViews.includes('kanban')) { }) } if (allowedViews.includes('group_by')) { - defaultViews.push({ + standardViews.push({ name: 'group_by', label: __(props.options?.defaultViewName) || __('Group By'), icon: markRaw(GroupByIcon), @@ -531,9 +531,9 @@ function getIcon(icon, type) { const viewsDropdownOptions = computed(() => { let _views = [ { - group: __('Default Views'), + group: __('Standard Views'), hideLabel: true, - items: defaultViews, + items: standardViews, }, ] @@ -904,7 +904,7 @@ const viewActions = (view) => { let actions = [ { - group: __('Default Views'), + group: __('Actions'), hideLabel: true, items: [ { @@ -916,7 +916,7 @@ const viewActions = (view) => { }, ] - if (!isStandardView(_view, isStandard)) { + if (!isDefaultView(_view, isStandard)) { actions[0].items.unshift({ label: __('Set as default'), icon: () => h(CheckIcon, { class: 'h-4 w-4' }), @@ -981,7 +981,7 @@ const viewActions = (view) => { return actions } -function isStandardView(v, isStandard) { +function isDefaultView(v, isStandard) { let defaultView = getDefaultView() if (!defaultView) return false From 1e39385abe97239f2b06b90abb0fd77da46eb821 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 20 Feb 2025 14:10:53 +0530 Subject: [PATCH 07/21] fix: set as default view in view settings per user --- crm/api/views.py | 5 -- .../crm_view_settings/crm_view_settings.py | 28 +++++++++-- frontend/src/components/ViewControls.vue | 9 ++-- frontend/src/router.js | 19 ++++--- frontend/src/stores/views.js | 49 +++---------------- frontend/src/utils/view.js | 6 +-- 6 files changed, 46 insertions(+), 70 deletions(-) diff --git a/crm/api/views.py b/crm/api/views.py index ece602a1..5313753e 100644 --- a/crm/api/views.py +++ b/crm/api/views.py @@ -14,8 +14,3 @@ def get_views(doctype): query = query.where(View.dt == doctype) views = query.run(as_dict=True) return views - - -@frappe.whitelist() -def get_default_view(): - return frappe.db.get_single_value("FCRM Settings", "default_view") or None diff --git a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py index c62a0f52..694882ab 100644 --- a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py +++ b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py @@ -147,9 +147,19 @@ def sync_default_columns(view): @frappe.whitelist() def set_as_default(name=None, type=None, doctype=None): - if not name: - name = type + "_" + doctype - frappe.db.set_single_value("FCRM Settings", "default_view", name) + if name: + frappe.db.set_value("CRM View Settings", name, "is_default", 1) + else: + doc = create_or_update_standard_view({"type": type, "doctype": doctype, "is_default": 1}) + name = doc.name + + # remove default from other views of same user + frappe.db.set_value( + "CRM View Settings", + {"name": ("!=", name), "user": frappe.session.user, "is_default": 1}, + "is_default", + 0, + ) @frappe.whitelist() @@ -190,10 +200,17 @@ def create_or_update_standard_view(view): doc.kanban_fields = json.dumps(kanban_fields) doc.columns = json.dumps(columns) doc.rows = json.dumps(rows) + doc.is_default = view.is_default or False doc.save() else: doc = frappe.new_doc("CRM View Settings") - label = "Group By View" if view.type == "group_by" else "List View" + + label = "List" + if view.type == "group_by": + label = "Group By" + elif view.type == "kanban": + label = "Kanban" + doc.name = view.label or label doc.label = view.label or label doc.type = view.type or "list" @@ -211,4 +228,7 @@ def create_or_update_standard_view(view): doc.columns = json.dumps(columns) doc.rows = json.dumps(rows) doc.is_standard = True + doc.is_default = view.is_default or False doc.insert() + + return doc diff --git a/frontend/src/components/ViewControls.vue b/frontend/src/components/ViewControls.vue index 5d4f6715..ee38ccc0 100644 --- a/frontend/src/components/ViewControls.vue +++ b/frontend/src/components/ViewControls.vue @@ -897,6 +897,7 @@ const viewActions = (view) => { if (!_view) { _view = { + label: view.label, type: view.name, dt: props.doctype, } @@ -984,13 +985,9 @@ const viewActions = (view) => { function isDefaultView(v, isStandard) { let defaultView = getDefaultView() - if (!defaultView) return false + if (!defaultView || (isStandard && !v.name)) return false - if (isStandard && !v.name) { - return defaultView == v.type + '_' + v.dt - } - - return defaultView == v.name + return defaultView.name == v.name } const viewModalObj = ref({}) diff --git a/frontend/src/router.js b/frontend/src/router.js index e6af1562..43ff0e33 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -113,23 +113,22 @@ router.beforeEach(async (to, from, next) => { isLoggedIn && (await userResource.promise) if (to.name === 'Home' && isLoggedIn) { - const { getDefaultView, defaultView } = viewsStore() - await defaultView.promise + const { views, getDefaultView } = viewsStore() + await views.promise - let _defaultView = getDefaultView(true) - - if (!_defaultView) { + let defaultView = getDefaultView() + if (!defaultView) { next({ name: 'Leads' }) return } - let { name, type, view } = _defaultView - name = name || 'Leads' + let { route_name, type, name, is_standard } = defaultView + route_name = route_name || 'Leads' - if (view) { - next({ name, params: { viewType: type }, query: { view } }) + if (name && !is_standard) { + next({ name: route_name, params: { viewType: type }, query: { name } }) } else { - next({ name, params: { viewType: type } }) + next({ name: route_name, params: { viewType: type } }) } } else if (!isLoggedIn) { window.location.href = '/login?redirect-to=/crm' diff --git a/frontend/src/stores/views.js b/frontend/src/stores/views.js index 011a9e38..097d2706 100644 --- a/frontend/src/stores/views.js +++ b/frontend/src/stores/views.js @@ -7,13 +7,7 @@ export const viewsStore = defineStore('crm-views', (doctype) => { let pinnedViews = ref([]) let publicViews = ref([]) let standardViews = ref({}) - - // Default view - const defaultView = createResource({ - url: 'crm.api.views.get_default_view', - cache: 'crm-default-view', - auto: true, - }) + const defaultView = ref(null) // Views const views = createResource({ @@ -34,48 +28,19 @@ export const viewsStore = defineStore('crm-views', (doctype) => { if (view.public) { publicViews.value?.push(view) } - if (view.is_default && view.dt) { + if (view.is_standard && view.dt) { standardViews.value[view.dt + ' ' + view.type] = view } + if (view.is_default) { + defaultView.value = view + } } return views }, }) - function getDefaultView(routeName = false) { - let view = defaultView.data - if (!view) return null - - if (typeof view === 'string' && !isNaN(view)) { - view = parseInt(view) - } - - if (routeName) { - let viewObj = getView(view) || { - type: view.split('_')[0], - dt: view.split('_')[1], - } - - let routeName = viewObj.dt - - if (routeName.startsWith('CRM ')) { - routeName = routeName.slice(4) - } - - if (!routeName.endsWith('s')) { - routeName += 's' - } - - let viewName = viewObj.is_default ? null : viewObj.name - - return { - name: routeName, - type: viewObj.type, - view: viewName, - } - } - - return view + function getDefaultView() { + return defaultView.value } function getView(view, type, doctype = null) { diff --git a/frontend/src/utils/view.js b/frontend/src/utils/view.js index a6db2fb2..d810eb70 100644 --- a/frontend/src/utils/view.js +++ b/frontend/src/utils/view.js @@ -6,7 +6,7 @@ import { markRaw } from 'vue' const { getView: getViewDetails } = viewsStore() -function defaultView(type) { +function standardView(type) { let types = { list: { label: __('List'), @@ -29,7 +29,7 @@ export function getView(view, type, doctype) { let viewType = type || 'list' let viewDetails = getViewDetails(view, viewType, doctype) if (viewDetails && !viewDetails.icon) { - viewDetails.icon = defaultView(viewType).icon + viewDetails.icon = standardView(viewType).icon } - return viewDetails || defaultView(viewType) + return viewDetails || standardView(viewType) } From ab25f26f1447b4a323242397183ee9e8de7060ba Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 20 Feb 2025 15:21:56 +0530 Subject: [PATCH 08/21] fix: set route name if not set --- .../crm_view_settings/crm_view_settings.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py index 694882ab..d340fff7 100644 --- a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py +++ b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py @@ -37,7 +37,7 @@ def create(view): doc.icon = view.icon doc.dt = view.doctype doc.user = frappe.session.user - doc.route_name = view.route_name or "" + doc.route_name = view.route_name or get_route_name(view.doctype) doc.load_default_columns = view.load_default_columns or False doc.filters = json.dumps(view.filters) doc.order_by = view.order_by @@ -70,7 +70,7 @@ def update(view): doc.label = view.label doc.type = view.type or "list" doc.icon = view.icon - doc.route_name = view.route_name or "" + doc.route_name = view.route_name or get_route_name(view.doctype) doc.load_default_columns = view.load_default_columns or False doc.filters = json.dumps(filters) doc.order_by = view.order_by @@ -189,7 +189,7 @@ def create_or_update_standard_view(view): doc = frappe.get_doc("CRM View Settings", doc) doc.label = view.label doc.type = view.type or "list" - doc.route_name = view.route_name or "" + doc.route_name = view.route_name or get_route_name(view.doctype) doc.load_default_columns = view.load_default_columns or False doc.filters = json.dumps(filters) doc.order_by = view.order_by @@ -216,7 +216,7 @@ def create_or_update_standard_view(view): doc.type = view.type or "list" doc.dt = view.doctype doc.user = frappe.session.user - doc.route_name = view.route_name or "" + doc.route_name = view.route_name or get_route_name(view.doctype) doc.load_default_columns = view.load_default_columns or False doc.filters = json.dumps(filters) doc.order_by = view.order_by @@ -232,3 +232,14 @@ def create_or_update_standard_view(view): doc.insert() return doc + + +def get_route_name(doctype): + # Example: "CRM Lead" -> "Leads" + if doctype.startswith("CRM "): + doctype = doctype[4:] + + if doctype[-1] != "s": + doctype += "s" + + return doctype From 19c1501eba293f627868defad09f505c32109e6b Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 20 Feb 2025 15:38:41 +0530 Subject: [PATCH 09/21] fix: set default column_field, order_by & group_by_field if not set --- .../doctype/crm_view_settings/crm_view_settings.json | 3 +-- crm/fcrm/doctype/crm_view_settings/crm_view_settings.py | 9 +++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json index 7beefffb..e8b9d8cc 100644 --- a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json +++ b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.json @@ -169,7 +169,6 @@ "label": "Kanban Fields" }, { - "default": "name", "fieldname": "title_field", "fieldtype": "Data", "label": "Title Field" @@ -189,7 +188,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-02-20 12:52:27.522624", + "modified": "2025-02-20 15:36:55.059065", "modified_by": "Administrator", "module": "FCRM", "name": "CRM View Settings", diff --git a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py index d340fff7..2552b901 100644 --- a/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py +++ b/crm/fcrm/doctype/crm_view_settings/crm_view_settings.py @@ -171,6 +171,7 @@ def create_or_update_standard_view(view): rows = parse_json(view.rows or "[]") kanban_columns = parse_json(view.kanban_columns or "[]") kanban_fields = parse_json(view.kanban_fields or "[]") + view.column_field = view.column_field or "status" default_rows = sync_default_rows(view.doctype, view.type) rows = rows + default_rows if default_rows else rows @@ -192,8 +193,8 @@ def create_or_update_standard_view(view): doc.route_name = view.route_name or get_route_name(view.doctype) doc.load_default_columns = view.load_default_columns or False doc.filters = json.dumps(filters) - doc.order_by = view.order_by - doc.group_by_field = view.group_by_field + doc.order_by = view.order_by or "modified desc" + doc.group_by_field = view.group_by_field or "owner" doc.column_field = view.column_field doc.title_field = view.title_field doc.kanban_columns = json.dumps(kanban_columns) @@ -219,8 +220,8 @@ def create_or_update_standard_view(view): doc.route_name = view.route_name or get_route_name(view.doctype) doc.load_default_columns = view.load_default_columns or False doc.filters = json.dumps(filters) - doc.order_by = view.order_by - doc.group_by_field = view.group_by_field + doc.order_by = view.order_by or "modified desc" + doc.group_by_field = view.group_by_field or "owner" doc.column_field = view.column_field doc.title_field = view.title_field doc.kanban_columns = json.dumps(kanban_columns) From c3ab48ea7409653874980b1f4b559a1491e1976e Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 20 Feb 2025 18:31:26 +0530 Subject: [PATCH 10/21] fix: fix for dark mode --- frontend/src/pages/Lead.vue | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue index 347935eb..e3e61b28 100644 --- a/frontend/src/pages/Lead.vue +++ b/frontend/src/pages/Lead.vue @@ -202,7 +202,7 @@ -
+
{{ __('Choose Existing') }}
@@ -210,7 +210,6 @@
-
+
{{ __('Choose Existing') }}
@@ -237,7 +236,6 @@ Date: Thu, 20 Feb 2025 21:15:09 -0800 Subject: [PATCH 11/21] only clearing first_name, mobile_no, email, last_name while contact exist --- frontend/src/components/Modals/DealModal.vue | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/Modals/DealModal.vue b/frontend/src/components/Modals/DealModal.vue index 068feed1..7bd0b592 100644 --- a/frontend/src/components/Modals/DealModal.vue +++ b/frontend/src/components/Modals/DealModal.vue @@ -191,21 +191,10 @@ function createDeal() { deal.website = 'https://' + deal.website } if (chooseExistingContact.value) { - const contactDetailsFields = new Set( - tabs.data.flatMap((tab) => - tab.sections - .filter((section) => section.name === 'contact_details_section') - .flatMap((section) => - section.columns.flatMap((column) => - column.fields.map((field) => field.fieldname), - ), - ), - ), - ) - - contactDetailsFields.forEach((field) => { - deal[field] = null - }) + deal['first_name'] = null + deal['last_name'] = null + deal['email'] = null + deal['mobile_no'] = null } else deal['contact'] = null createResource({ From 4a1d35cd71040c098888e7572ef11aee466bc88b Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 21 Feb 2025 14:38:01 +0530 Subject: [PATCH 12/21] fix: capture signup from demo account --- frontend/src/components/SignupBanner.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/components/SignupBanner.vue b/frontend/src/components/SignupBanner.vue index 7586f463..1faed676 100644 --- a/frontend/src/components/SignupBanner.vue +++ b/frontend/src/components/SignupBanner.vue @@ -21,6 +21,7 @@ From 235b1346a49e63e06a6441980c6e2ef8b5d3946c Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 21 Feb 2025 14:38:26 +0530 Subject: [PATCH 13/21] build(deps): bump frappeui to 0.1.110 --- frontend/package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 1ac29f78..88fa0c76 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,7 +14,7 @@ "@vueuse/core": "^10.3.0", "@vueuse/integrations": "^10.3.0", "feather-icons": "^4.28.0", - "frappe-ui": "^0.1.105", + "frappe-ui": "^0.1.110", "gemoji": "^8.1.0", "lodash": "^4.17.21", "mime": "^4.0.1", diff --git a/yarn.lock b/yarn.lock index 7b7baaa4..25bbca56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1136,10 +1136,10 @@ dependencies: mini-svg-data-uri "^1.2.3" -"@tailwindcss/typography@^0.5.0": - version "0.5.15" - resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.15.tgz#007ab9870c86082a1c76e5b3feda9392c7c8d648" - integrity sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA== +"@tailwindcss/typography@^0.5.16": + version "0.5.16" + resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.16.tgz#a926c8f44d5c439b2915e231cad80058850047c6" + integrity sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA== dependencies: lodash.castarray "^4.4.0" lodash.isplainobject "^4.0.6" @@ -2388,15 +2388,15 @@ fraction.js@^4.3.7: resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== -frappe-ui@^0.1.105: - version "0.1.105" - resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.105.tgz#3bdf3c458ba27f27ff2f2a28cf7eb6f9ed872367" - integrity sha512-9bZ/hj/HhQ9vp7DxE8aOKS8HqwETZrKT3IhSzjpYOk21efK8QwdbQ9sp0t4m3UII+HaUTSOTHnFzF7y9EhRZxg== +frappe-ui@^0.1.110: + version "0.1.110" + resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.110.tgz#dbe02d294cb0aeb0a4c1b3000682093c309450ae" + integrity sha512-kFah6SoPauULXaeSbljNUq595/82VmY4k4+KA8zi4sXxpn4sXYi12qUl/1I8GOBhsCQQizmoh46DO7e/uU2M1A== dependencies: "@headlessui/vue" "^1.7.14" "@popperjs/core" "^2.11.2" "@tailwindcss/forms" "^0.5.3" - "@tailwindcss/typography" "^0.5.0" + "@tailwindcss/typography" "^0.5.16" "@tiptap/extension-color" "^2.0.3" "@tiptap/extension-highlight" "^2.0.3" "@tiptap/extension-image" "^2.0.3" From 3ed63ebc47b611244b92ecab46d8331bee2d52d7 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 21 Feb 2025 14:40:43 +0530 Subject: [PATCH 14/21] fix: catch error while converting lead to deal --- frontend/src/pages/Lead.vue | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue index e3e61b28..7de430aa 100644 --- a/frontend/src/pages/Lead.vue +++ b/frontend/src/pages/Lead.vue @@ -599,10 +599,15 @@ async function convertToDeal(updated) { } else { let deal = await call( 'crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal', - { - lead: lead.data.name, - }, - ) + { lead: lead.data.name }, + ).catch((err) => { + createToast({ + title: __('Error converting to deal'), + text: __(err.messages?.[0]), + icon: 'x', + iconClasses: 'text-ink-red-4', + }) + }) if (deal) { capture('convert_lead_to_deal') if (updated) { From 55a4b9b3e3be1721ace3ef80086c0a2f50d805ab Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 21 Feb 2025 14:43:37 +0530 Subject: [PATCH 15/21] feat: show required deal fields in convert to deal modal --- frontend/src/pages/Lead.vue | 53 +++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue index 7de430aa..3f02832e 100644 --- a/frontend/src/pages/Lead.vue +++ b/frontend/src/pages/Lead.vue @@ -245,6 +245,15 @@ {{ __("New contact will be created based on the person's details") }}
+ +
+ + { + let statuses = statusOptions('deal') + if (!deal.status) { + deal.status = statuses[0].value + } + return statuses +}) + +const dealTabs = createResource({ + url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout', + cache: ['RequiredFields', 'CRM Deal'], + params: { doctype: 'CRM Deal', type: 'Required Fields' }, + auto: true, + transform: (_tabs) => { + let hasFields = false + let parsedTabs = _tabs.forEach((tab) => { + tab.sections.forEach((section) => { + section.columns.forEach((column) => { + column.fields.forEach((field) => { + hasFields = true + if (field.fieldname == 'status') { + field.fieldtype = 'Select' + field.options = dealStatuses.value + field.prefix = getDealStatus(deal.status).color + } + + if (field.fieldtype === 'Table') { + deal[field.fieldname] = [] + } + }) + }) + }) + }) + return hasFields ? parsedTabs : [] + }, +}) From ec6a1f84eb7962cac52bfab411839595d2632e68 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 21 Feb 2025 14:44:33 +0530 Subject: [PATCH 16/21] fix: do not get default layout if type is Required Fields --- crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py index 271a5da9..e44e3e20 100644 --- a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py +++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py @@ -24,7 +24,7 @@ def get_fields_layout(doctype: str, type: str, parent_doctype: str | None = None if layout and layout.layout: tabs = json.loads(layout.layout) - if not tabs: + if not tabs and type != "Required Fields": tabs = get_default_layout(doctype) has_tabs = tabs[0].get("sections") if tabs and tabs[0] else False From 19bf3a0122530843fad3f039de7065aee5f71902 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 21 Feb 2025 14:44:54 +0530 Subject: [PATCH 17/21] fix: added Required Fields option in type --- crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json index 43019c5c..c61189e2 100644 --- a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json +++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.json @@ -27,7 +27,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Type", - "options": "Quick Entry\nSide Panel\nData Fields\nGrid Row" + "options": "Quick Entry\nSide Panel\nData Fields\nGrid Row\nRequired Fields" }, { "fieldname": "section_break_ttpm", @@ -46,7 +46,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-01-02 22:12:51.663011", + "modified": "2025-02-21 13:09:49.573515", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Fields Layout", From 7294310e84488d73e69ca0ed1ec4f18c75306cb6 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 21 Feb 2025 14:47:28 +0530 Subject: [PATCH 18/21] fix: added quick entry modal editor for required fields --- frontend/src/pages/Lead.vue | 49 +++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue index 3f02832e..5361b41c 100644 --- a/frontend/src/pages/Lead.vue +++ b/frontend/src/pages/Lead.vue @@ -186,7 +186,6 @@ + + From 3c865c37a7296ef481b1c71f4edeaff38f8f055a Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 21 Feb 2025 14:49:39 +0530 Subject: [PATCH 19/21] fix: set type based on onlyRequired flag --- crm/api/doc.py | 5 ++++- frontend/src/components/FieldLayoutEditor.vue | 5 +++++ frontend/src/components/Modals/QuickEntryModal.vue | 13 ++++++++++--- frontend/src/pages/Lead.vue | 1 + 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/crm/api/doc.py b/crm/api/doc.py index a8627584..8caf0d04 100644 --- a/crm/api/doc.py +++ b/crm/api/doc.py @@ -537,7 +537,7 @@ def get_records_based_on_order(doctype, rows, filters, page_length, order): @frappe.whitelist() -def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False): +def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False, only_required=False): not_allowed_fieldtypes = [ "Tab Break", "Section Break", @@ -572,6 +572,9 @@ def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False): if not restricted_fieldtypes or field.get("fieldtype") not in restricted_fieldtypes: fields.append(field) + if only_required: + fields = [field for field in fields if field.get("reqd")] + if as_array: return fields diff --git a/frontend/src/components/FieldLayoutEditor.vue b/frontend/src/components/FieldLayoutEditor.vue index 5dcff8e5..8804b920 100644 --- a/frontend/src/components/FieldLayoutEditor.vue +++ b/frontend/src/components/FieldLayoutEditor.vue @@ -226,6 +226,10 @@ import { ref, computed, watch } from 'vue' const props = defineProps({ tabs: Object, doctype: String, + onlyRequired: { + type: Boolean, + default: false, + }, }) const tabIndex = ref(0) @@ -249,6 +253,7 @@ const params = computed(() => { doctype: props.doctype, restricted_fieldtypes: restrictedFieldTypes, as_array: true, + only_required: props.onlyRequired, } }) diff --git a/frontend/src/components/Modals/QuickEntryModal.vue b/frontend/src/components/Modals/QuickEntryModal.vue index ec237520..7c3af8a8 100644 --- a/frontend/src/components/Modals/QuickEntryModal.vue +++ b/frontend/src/components/Modals/QuickEntryModal.vue @@ -35,6 +35,7 @@ v-if="!preview" :tabs="tabs.data" :doctype="_doctype" + :onlyRequired="onlyRequired" />
@@ -55,6 +56,10 @@ const props = defineProps({ type: String, default: 'CRM Lead', }, + onlyRequired: { + type: Boolean, + default: false, + }, }) const show = defineModel() @@ -64,12 +69,13 @@ const dirty = ref(false) const preview = ref(false) function getParams() { - return { doctype: _doctype.value, type: 'Quick Entry' } + let type = props.onlyRequired ? 'Required Fields' : 'Quick Entry' + return { doctype: _doctype.value, type } } const tabs = createResource({ url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout', - cache: ['QuickEntryModal', _doctype.value], + cache: ['QuickEntryModal', _doctype.value, props.onlyRequired], params: getParams(), onSuccess(data) { tabs.originalData = JSON.parse(JSON.stringify(data)) @@ -106,11 +112,12 @@ function saveChanges() { }) }) loading.value = true + let type = props.onlyRequired ? 'Required Fields' : 'Quick Entry' call( 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.save_fields_layout', { doctype: _doctype.value, - type: 'Quick Entry', + type: type, layout: JSON.stringify(_tabs), }, ).then(() => { diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue index 5361b41c..040b8834 100644 --- a/frontend/src/pages/Lead.vue +++ b/frontend/src/pages/Lead.vue @@ -285,6 +285,7 @@ v-if="showQuickEntryModal" v-model="showQuickEntryModal" doctype="CRM Deal" + :onlyRequired="true" /> Date: Fri, 21 Feb 2025 14:50:16 +0530 Subject: [PATCH 20/21] fix: pass required deal fields value while converting to deal --- crm/fcrm/doctype/crm_lead/crm_lead.py | 31 +++++++++++++++------------ frontend/src/pages/Lead.vue | 8 +++---- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.py b/crm/fcrm/doctype/crm_lead/crm_lead.py index bdf0885f..a7a41d34 100644 --- a/crm/fcrm/doctype/crm_lead/crm_lead.py +++ b/crm/fcrm/doctype/crm_lead/crm_lead.py @@ -197,8 +197,8 @@ class CRMLead(Document): return False - def create_deal(self, contact, organization): - deal = frappe.new_doc("CRM Deal") + def create_deal(self, contact, organization, deal=None): + new_deal = frappe.new_doc("CRM Deal") lead_deal_map = { "lead_owner": "deal_owner", @@ -245,13 +245,13 @@ class CRMLead(Document): if field.fieldname in lead_deal_map: fieldname = lead_deal_map[field.fieldname] - if hasattr(deal, fieldname): + if hasattr(new_deal, fieldname): if fieldname == "organization": - deal.update({fieldname: organization}) + new_deal.update({fieldname: organization}) else: - deal.update({fieldname: self.get(field.fieldname)}) + new_deal.update({fieldname: self.get(field.fieldname)}) - deal.update( + new_deal.update( { "lead": self.name, "contacts": [{"contact": contact}], @@ -259,7 +259,7 @@ class CRMLead(Document): ) if self.first_responded_on: - deal.update( + new_deal.update( { "sla_creation": self.sla_creation, "response_by": self.response_by, @@ -270,8 +270,11 @@ class CRMLead(Document): } ) - deal.insert(ignore_permissions=True) - return deal.name + if deal: + new_deal.update(deal) + + new_deal.insert(ignore_permissions=True) + return new_deal.name def set_sla(self): """ @@ -297,8 +300,8 @@ class CRMLead(Document): if sla: sla.apply(self) - def convert_to_deal(self): - return convert_to_deal(lead=self.name, doc=self) + def convert_to_deal(self, deal=None): + return convert_to_deal(lead=self.name, doc=self, deal=deal) @staticmethod def get_non_filterable_fields(): @@ -380,7 +383,7 @@ class CRMLead(Document): @frappe.whitelist() -def convert_to_deal(lead, doc=None): +def convert_to_deal(lead, doc=None, deal=None): if not (doc and doc.flags.get("ignore_permissions")) and not frappe.has_permission( "CRM Lead", "write", lead ): @@ -394,5 +397,5 @@ def convert_to_deal(lead, doc=None): lead.db_set("communication_status", "Replied") contact = lead.create_contact(False) organization = lead.create_organization() - deal = lead.create_deal(contact, organization) - return deal + _deal = lead.create_deal(contact, organization, deal) + return _deal diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue index 040b8834..1e8d728b 100644 --- a/frontend/src/pages/Lead.vue +++ b/frontend/src/pages/Lead.vue @@ -646,9 +646,9 @@ async function convertToDeal(updated) { ) showConvertToDealModal.value = false } else { - let deal = await call( + let _deal = await call( 'crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal', - { lead: lead.data.name }, + { lead: lead.data.name, deal }, ).catch((err) => { createToast({ title: __('Error converting to deal'), @@ -657,12 +657,12 @@ async function convertToDeal(updated) { iconClasses: 'text-ink-red-4', }) }) - if (deal) { + if (_deal) { capture('convert_lead_to_deal') if (updated) { await contacts.reload() } - router.push({ name: 'Deal', params: { dealId: deal } }) + router.push({ name: 'Deal', params: { dealId: _deal } }) } } } From acf307cc521eb366e50844284283396aa26c9339 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 21 Feb 2025 15:30:52 +0530 Subject: [PATCH 21/21] fix: show title and description in link field --- frontend/src/components/Controls/Link.vue | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Controls/Link.vue b/frontend/src/components/Controls/Link.vue index 6f995f70..e6cb4356 100644 --- a/frontend/src/components/Controls/Link.vue +++ b/frontend/src/components/Controls/Link.vue @@ -25,7 +25,19 @@