diff --git a/crm/api/activities.py b/crm/api/activities.py index ef42a9292..42611b249 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/crm/api/comment.py b/crm/api/comment.py new file mode 100644 index 000000000..8f2df3f29 --- /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, + reference_doctype=doc.reference_doctype, + reference_name=doc.reference_name, + ) + + 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/api/notifications.py b/crm/api/notifications.py new file mode 100644 index 000000000..bdbe91183 --- /dev/null +++ b/crm/api/notifications.py @@ -0,0 +1,59 @@ +import frappe +from frappe.query_builder import Order + + +@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) + .orderby("creation", order=Order.desc) + ) + notifications = query.run(as_dict=True) + + _notifications = [] + for notification in notifications: + _notifications.append( + { + "creation": notification.creation, + "from_user": { + "name": notification.from_user, + "full_name": frappe.get_value( + "User", notification.from_user, "full_name" + ), + }, + "type": notification.type, + "to_user": notification.to_user, + "read": notification.read, + "comment": notification.comment, + "reference_doctype": "deal" + if notification.reference_doctype == "CRM Deal" + else "lead", + "reference_name": notification.reference_name, + "route_name": "Deal" + if notification.reference_doctype == "CRM Deal" + else "Lead", + } + ) + + return _notifications + + +@frappe.whitelist() +def mark_as_read(user=None, comment=None): + if frappe.session.user == "Guest": + frappe.throw("Authentication failed", exc=frappe.AuthenticationError) + + user = user or frappe.session.user + filters = {"to_user": user, "read": False} + if comment: + filters["comment"] = comment + for n in frappe.get_all("CRM Notification", filters=filters): + d = frappe.get_doc("CRM Notification", n.name) + d.read = True + d.save() diff --git a/crm/fcrm/doctype/crm_notification/__init__.py b/crm/fcrm/doctype/crm_notification/__init__.py new file mode 100644 index 000000000..e69de29bb 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 000000000..5df455616 --- /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 000000000..7b9cee2ea --- /dev/null +++ b/crm/fcrm/doctype/crm_notification/crm_notification.json @@ -0,0 +1,129 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-01-29 19:31:13.613929", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "from_user", + "type", + "reference_doctype", + "reference_name", + "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": "HTML Editor", + "label": "Message" + }, + { + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "label": "Reference Doc", + "options": "reference_doctype" + }, + { + "fieldname": "reference_doctype", + "fieldtype": "Link", + "label": "Reference Doctype", + "options": "DocType" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-01-30 01:04:27.946030", + "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 000000000..9470101f1 --- /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 000000000..e643a91d6 --- /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 diff --git a/crm/hooks.py b/crm/hooks.py index c60beac08..b25039c3f 100644 --- a/crm/hooks.py +++ b/crm/hooks.py @@ -133,6 +133,9 @@ "ToDo": { "after_insert": ["crm.api.todo.after_insert"], }, + "Comment": { + "on_update": ["crm.api.comment.on_update"], + } } # Scheduled Tasks diff --git a/crm/install.py b/crm/install.py index 351dd8cee..5e9857b4c 100644 --- a/crm/install.py +++ b/crm/install.py @@ -7,7 +7,9 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def before_install(): - pass + frappe.reload_doc("fcrm", "doctype", "crm_lead_status") + frappe.reload_doc("fcrm", "doctype", "crm_deal_status") + frappe.reload_doc("fcrm", "doctype", "crm_communication_status") def after_install(): add_default_lead_statuses() diff --git a/frontend/src/components/Activities.vue b/frontend/src/components/Activities.vue index d93285dd3..e430113d2 100644 --- a/frontend/src/components/Activities.vue +++ b/frontend/src/components/Activities.vue @@ -417,7 +417,11 @@ -