From d95aa9ee0744b5394f62a53091e2c5258755fd43 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 29 Jan 2024 19:37:41 +0530 Subject: [PATCH 01/21] feat: added notification doctype --- crm/fcrm/doctype/crm_notification/__init__.py | 0 .../crm_notification/crm_notification.js | 8 ++ .../crm_notification/crm_notification.json | 115 ++++++++++++++++++ .../crm_notification/crm_notification.py | 9 ++ .../crm_notification/test_crm_notification.py | 9 ++ 5 files changed, 141 insertions(+) create mode 100644 crm/fcrm/doctype/crm_notification/__init__.py create mode 100644 crm/fcrm/doctype/crm_notification/crm_notification.js create mode 100644 crm/fcrm/doctype/crm_notification/crm_notification.json create mode 100644 crm/fcrm/doctype/crm_notification/crm_notification.py create mode 100644 crm/fcrm/doctype/crm_notification/test_crm_notification.py diff --git a/crm/fcrm/doctype/crm_notification/__init__.py b/crm/fcrm/doctype/crm_notification/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/crm/fcrm/doctype/crm_notification/crm_notification.js b/crm/fcrm/doctype/crm_notification/crm_notification.js new file mode 100644 index 00000000..5df45561 --- /dev/null +++ b/crm/fcrm/doctype/crm_notification/crm_notification.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("CRM Notification", { +// refresh(frm) { + +// }, +// }); diff --git a/crm/fcrm/doctype/crm_notification/crm_notification.json b/crm/fcrm/doctype/crm_notification/crm_notification.json new file mode 100644 index 00000000..9ddb2cff --- /dev/null +++ b/crm/fcrm/doctype/crm_notification/crm_notification.json @@ -0,0 +1,115 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-01-29 19:31:13.613929", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "from_user", + "type", + "column_break_dduu", + "to_user", + "comment", + "read", + "section_break_vpwa", + "message" + ], + "fields": [ + { + "fieldname": "from_user", + "fieldtype": "Link", + "label": "From User", + "options": "User" + }, + { + "fieldname": "type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "options": "Mention", + "reqd": 1 + }, + { + "fieldname": "column_break_dduu", + "fieldtype": "Column Break" + }, + { + "fieldname": "to_user", + "fieldtype": "Link", + "in_list_view": 1, + "label": "To User", + "options": "User", + "reqd": 1 + }, + { + "fieldname": "comment", + "fieldtype": "Link", + "label": "Comment", + "link_filters": "[[{\"fieldname\":\"comment\",\"field_option\":\"Comment\"},\"comment_type\",\"=\",\"Comment\"]]", + "options": "Comment" + }, + { + "default": "0", + "fieldname": "read", + "fieldtype": "Check", + "label": "Read" + }, + { + "fieldname": "section_break_vpwa", + "fieldtype": "Section Break" + }, + { + "fieldname": "message", + "fieldtype": "Text Editor", + "label": "Message" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-01-29 19:36:19.466742", + "modified_by": "Administrator", + "module": "FCRM", + "name": "CRM Notification", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/crm/fcrm/doctype/crm_notification/crm_notification.py b/crm/fcrm/doctype/crm_notification/crm_notification.py new file mode 100644 index 00000000..9470101f --- /dev/null +++ b/crm/fcrm/doctype/crm_notification/crm_notification.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CRMNotification(Document): + pass diff --git a/crm/fcrm/doctype/crm_notification/test_crm_notification.py b/crm/fcrm/doctype/crm_notification/test_crm_notification.py new file mode 100644 index 00000000..e643a91d --- /dev/null +++ b/crm/fcrm/doctype/crm_notification/test_crm_notification.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestCRMNotification(FrappeTestCase): + pass From e5e64c2ea9dcf3077daa79cf249d88de2b2e067d Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 29 Jan 2024 20:05:54 +0530 Subject: [PATCH 02/21] fix: create notification when someone mentions in comment --- crm/api/comment.py | 43 +++++++++++++++++++ .../crm_notification/crm_notification.json | 4 +- crm/hooks.py | 3 ++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 crm/api/comment.py diff --git a/crm/api/comment.py b/crm/api/comment.py new file mode 100644 index 00000000..6797da98 --- /dev/null +++ b/crm/api/comment.py @@ -0,0 +1,43 @@ +import frappe +from bs4 import BeautifulSoup + +def on_update(self, method): + notify_mentions(self) + + +def notify_mentions(doc): + """ + Extract mentions from `content`, and notify. + `content` must have `HTML` content. + """ + content = getattr(doc, "content", None) + if not content: + return + mentions = extract_mentions(content) + for mention in mentions: + values = frappe._dict( + doctype="CRM Notification", + from_user=doc.owner, + to_user=mention.email, + type="Mention", + message=doc.content, + comment=doc.name, + ) + # Why mention oneself? + # if values.from_user == values.to_user: + # continue + if frappe.db.exists("CRM Notification", values): + return + frappe.get_doc(values).insert() + + +def extract_mentions(html): + if not html: + return [] + soup = BeautifulSoup(html, "html.parser") + mentions = [] + for d in soup.find_all("span", attrs={"data-type": "mention"}): + mentions.append( + frappe._dict(full_name=d.get("data-label"), email=d.get("data-id")) + ) + return mentions \ No newline at end of file diff --git a/crm/fcrm/doctype/crm_notification/crm_notification.json b/crm/fcrm/doctype/crm_notification/crm_notification.json index 9ddb2cff..112d52f9 100644 --- a/crm/fcrm/doctype/crm_notification/crm_notification.json +++ b/crm/fcrm/doctype/crm_notification/crm_notification.json @@ -60,13 +60,13 @@ }, { "fieldname": "message", - "fieldtype": "Text Editor", + "fieldtype": "HTML Editor", "label": "Message" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-01-29 19:36:19.466742", + "modified": "2024-01-29 20:03:40.102839", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Notification", diff --git a/crm/hooks.py b/crm/hooks.py index c60beac0..b25039c3 100644 --- a/crm/hooks.py +++ b/crm/hooks.py @@ -133,6 +133,9 @@ doc_events = { "ToDo": { "after_insert": ["crm.api.todo.after_insert"], }, + "Comment": { + "on_update": ["crm.api.comment.on_update"], + } } # Scheduled Tasks From d6b0bee1a6a7685df4d416a15176341c72ff6f51 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 29 Jan 2024 20:20:44 +0530 Subject: [PATCH 03/21] fix: added notifications button in sidebar --- .../components/Icons/NotificationsIcon.vue | 26 +++++++++++++++++++ .../src/components/Layouts/AppSidebar.vue | 10 +++++++ 2 files changed, 36 insertions(+) create mode 100644 frontend/src/components/Icons/NotificationsIcon.vue diff --git a/frontend/src/components/Icons/NotificationsIcon.vue b/frontend/src/components/Icons/NotificationsIcon.vue new file mode 100644 index 00000000..9b15a62c --- /dev/null +++ b/frontend/src/components/Icons/NotificationsIcon.vue @@ -0,0 +1,26 @@ + diff --git a/frontend/src/components/Layouts/AppSidebar.vue b/frontend/src/components/Layouts/AppSidebar.vue index 931eeaa8..c2c12c3d 100644 --- a/frontend/src/components/Layouts/AppSidebar.vue +++ b/frontend/src/components/Layouts/AppSidebar.vue @@ -7,6 +7,15 @@
+
+ +
Date: Mon, 29 Jan 2024 20:35:33 +0530 Subject: [PATCH 04/21] fix: created notifications store to get all notifications --- crm/api/notifications.py | 18 ++++++++++ .../src/components/Layouts/AppSidebar.vue | 4 ++- frontend/src/stores/notifications.js | 36 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 crm/api/notifications.py create mode 100644 frontend/src/stores/notifications.js diff --git a/crm/api/notifications.py b/crm/api/notifications.py new file mode 100644 index 00000000..70abee02 --- /dev/null +++ b/crm/api/notifications.py @@ -0,0 +1,18 @@ +import frappe + + +@frappe.whitelist() +def get_notifications(): + if frappe.session.user == "Guest": + frappe.throw("Authentication failed", exc=frappe.AuthenticationError) + + Notification = frappe.qb.DocType("CRM Notification") + query = ( + frappe.qb.from_(Notification) + .select("*") + .where(Notification.to_user == frappe.session.user) + .where(Notification.read == False) + .orderby("creation") + ) + notifications = query.run(as_dict=True) + return notifications diff --git a/frontend/src/components/Layouts/AppSidebar.vue b/frontend/src/components/Layouts/AppSidebar.vue index c2c12c3d..c7ed5b5b 100644 --- a/frontend/src/components/Layouts/AppSidebar.vue +++ b/frontend/src/components/Layouts/AppSidebar.vue @@ -12,7 +12,7 @@ label="Notifications" :icon="NotificationsIcon" :isCollapsed="isSidebarCollapsed" - @click="() => {}" + @click="() => toggleNotificationPanel()" class="mx-2 my-0.5" />
@@ -93,10 +93,12 @@ import CollapseSidebar from '@/components/Icons/CollapseSidebar.vue' import NotificationsIcon from '@/components/Icons/NotificationsIcon.vue' import SidebarLink from '@/components/SidebarLink.vue' import { viewsStore } from '@/stores/views' +import { notificationsStore } from '@/stores/notifications' import { useStorage } from '@vueuse/core' import { computed } from 'vue' const { getPinnedViews, getPublicViews } = viewsStore() +const { toggle: toggleNotificationPanel } = notificationsStore() const isSidebarCollapsed = useStorage('sidebar_is_collapsed', false) const links = [ diff --git a/frontend/src/stores/notifications.js b/frontend/src/stores/notifications.js new file mode 100644 index 00000000..dbe83977 --- /dev/null +++ b/frontend/src/stores/notifications.js @@ -0,0 +1,36 @@ +import { defineStore } from 'pinia' +import { sessionStore } from '@/stores/session' +import { createResource } from 'frappe-ui' +import { reactive, ref } from 'vue' + +export const notificationsStore = defineStore('crm-notifications', () => { + const { user } = sessionStore() + + let visible = ref(false) + let unreadNotifications = reactive([]) + + const notifications = createResource({ + url: 'crm.api.notifications.get_notifications', + cache: 'crm-notifications', + initialData: [], + auto: true, + transform(data) { + unreadNotifications = data + return data + }, + }) + + function toggle() { + visible.value = !visible.value + } + + function getUnreadNotifications() { + return unreadNotifications || [] + } + + return { + visible, + toggle, + getUnreadNotifications, + } +}) From 8a5b5a6e0f12f05ed205fb24604050906182492e Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 29 Jan 2024 22:50:27 +0530 Subject: [PATCH 05/21] fix: added id on comment in activity --- crm/api/activities.py | 2 ++ frontend/src/components/Activities.vue | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/crm/api/activities.py b/crm/api/activities.py index ef42a929..42611b24 100644 --- a/crm/api/activities.py +++ b/crm/api/activities.py @@ -97,6 +97,7 @@ def get_deal_activities(name): for comment in docinfo.comments: activity = { + "name": comment.name, "activity_type": "comment", "creation": comment.creation, "owner": comment.owner, @@ -203,6 +204,7 @@ def get_lead_activities(name): for comment in docinfo.comments: activity = { + "name": comment.name, "activity_type": "comment", "creation": comment.creation, "owner": comment.owner, diff --git a/frontend/src/components/Activities.vue b/frontend/src/components/Activities.vue index d93285dd..ad1a4148 100644 --- a/frontend/src/components/Activities.vue +++ b/frontend/src/components/Activities.vue @@ -417,7 +417,11 @@
-
+
From f4be8cc2591746a627c092dee56867888a0d2b32 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 29 Jan 2024 22:51:11 +0530 Subject: [PATCH 06/21] fix: allow scroll to comment if hash is present --- frontend/src/components/Activities.vue | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Activities.vue b/frontend/src/components/Activities.vue index ad1a4148..e430113d 100644 --- a/frontend/src/components/Activities.vue +++ b/frontend/src/components/Activities.vue @@ -769,6 +769,7 @@ import { } from 'frappe-ui' import { useElementVisibility } from '@vueuse/core' import { ref, computed, h, defineModel, markRaw, watch, nextTick } from 'vue' +import { useRoute } from 'vue-router' const { makeCall } = globalStore() const { getUser } = usersStore() @@ -1110,11 +1111,14 @@ watch([reload, reload_email], ([reload_value, reload_email_value]) => { } }) -function scroll(el) { +function scroll(hash) { setTimeout(() => { - if (!el) { + let el + if (!hash) { let e = document.getElementsByClassName('activity') el = e[e.length - 1] + } else { + el = document.getElementById(hash) } if (el && !useElementVisibility(el).value) { el.scrollIntoView({ behavior: 'smooth' }) @@ -1125,7 +1129,12 @@ function scroll(el) { defineExpose({ emailBox }) -nextTick(() => scroll()) +const route = useRoute() + +nextTick(() => { + const hash = route.hash.slice(1) || null + scroll(hash) +})