diff --git a/crm/api/comment.py b/crm/api/comment.py index 0333d174c..7aa2fee52 100644 --- a/crm/api/comment.py +++ b/crm/api/comment.py @@ -3,6 +3,7 @@ import frappe from frappe import _ from bs4 import BeautifulSoup +from crm.fcrm.doctype.crm_notification.crm_notification import notify_user def on_update(self, method): notify_mentions(self) @@ -29,22 +30,17 @@ def notify_mentions(doc): { doc.reference_name } """ - values = frappe._dict( - doctype="CRM Notification", - from_user=doc.owner, - to_user=mention.email, - type="Mention", - message=doc.content, - notification_text=notification_text, - notification_type_doctype="Comment", - notification_type_doc=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() + notify_user({ + "owner": doc.owner, + "assigned_to": mention.email, + "notification_type": "Mention", + "message": doc.content, + "notification_text": notification_text, + "reference_doctype": "Comment", + "reference_docname": doc.name, + "redirect_to_doctype": doc.reference_doctype, + "redirect_to_docname": doc.reference_name, + }) def extract_mentions(html): diff --git a/crm/api/contact.py b/crm/api/contact.py index 5484bb9bc..65bc06609 100644 --- a/crm/api/contact.py +++ b/crm/api/contact.py @@ -7,6 +7,7 @@ def validate(doc, method): set_primary_mobile_no(doc) doc.set_primary_email() doc.set_primary("mobile_no") + update_deals_email_mobile_no(doc) def set_primary_email(doc): @@ -25,6 +26,21 @@ def set_primary_mobile_no(doc): doc.phone_nos[0].is_primary_mobile_no = 1 +def update_deals_email_mobile_no(doc): + linked_deals = frappe.get_all( + "CRM Contacts", + filters={"contact": doc.name, "is_primary": 1}, + fields=["parent"], + ) + + for linked_deal in linked_deals: + deal = frappe.get_cached_doc("CRM Deal", linked_deal.parent) + if deal.email != doc.email_id or deal.mobile_no != doc.mobile_no: + deal.email = doc.email_id + deal.mobile_no = doc.mobile_no + deal.save(ignore_permissions=True) + + @frappe.whitelist() def get_contact(name): Contact = frappe.qb.DocType("Contact") diff --git a/crm/api/todo.py b/crm/api/todo.py index 7792e848f..e4bb50b5b 100644 --- a/crm/api/todo.py +++ b/crm/api/todo.py @@ -1,8 +1,103 @@ import frappe +from frappe import _ +from crm.fcrm.doctype.crm_notification.crm_notification import notify_user def after_insert(doc, method): if doc.reference_type in ["CRM Lead", "CRM Deal"] and doc.reference_name and doc.allocated_to: fieldname = "lead_owner" if doc.reference_type == "CRM Lead" else "deal_owner" lead_owner = frappe.db.get_value(doc.reference_type, doc.reference_name, fieldname) if not lead_owner: - frappe.db.set_value(doc.reference_type, doc.reference_name, fieldname, doc.allocated_to) \ No newline at end of file + frappe.db.set_value(doc.reference_type, doc.reference_name, fieldname, doc.allocated_to) + + if doc.reference_type in ["CRM Lead", "CRM Deal", "CRM Task"] and doc.reference_name and doc.allocated_to: + notify_assigned_user(doc) + +def on_update(doc, method): + if doc.has_value_changed("status") and doc.status == "Cancelled" and doc.reference_type in ["CRM Lead", "CRM Deal", "CRM Task"] and doc.reference_name and doc.allocated_to: + notify_assigned_user(doc, is_cancelled=True) + +def notify_assigned_user(doc, is_cancelled=False): + _doc = frappe.get_doc(doc.reference_type, doc.reference_name) + owner = frappe.get_cached_value("User", frappe.session.user, "full_name") + notification_text = get_notification_text(owner, doc, _doc, is_cancelled) + + message = _("Your assignment on {0} {1} has been removed by {2}").format( + doc.reference_type, + doc.reference_name, + owner + ) if is_cancelled else _("{0} assigned a {1} {2} to you").format( + owner, + doc.reference_type, + doc.reference_name + ) + + redirect_to_doctype, redirect_to_name = get_redirect_to_doc(doc) + + notify_user({ + "owner": frappe.session.user, + "assigned_to": doc.allocated_to, + "notification_type": "Assignment", + "message": message, + "notification_text": notification_text, + "reference_doctype": doc.reference_type, + "reference_docname": doc.reference_name, + "redirect_to_doctype": redirect_to_doctype, + "redirect_to_docname": redirect_to_name, + }) + +def get_notification_text(owner, doc, reference_doc, is_cancelled=False): + name = doc.reference_name + doctype = doc.reference_type + + if doctype.startswith("CRM "): + doctype = doctype[4:].lower() + + if doctype in ["CRM Lead", "CRM Deal"]: + name = reference_doc.lead_name or name if doctype == "CRM Lead" else reference_doc.organization or reference_doc.lead_name or name + + if is_cancelled: + return f""" +
+ { _('Your assignment on {0} {1} has been removed by {2}').format( + doctype, + f'{ name }', + f'{ owner }' + ) } +
+ """ + + return f""" +
+ { owner } + { _('assigned a {0} {1} to you').format( + doctype, + f'{ name }' + ) } +
+ """ + + if doc.reference_type == "CRM Task": + if is_cancelled: + return f""" +
+ { _('Your assignment on task {0} has been removed by {1}').format( + f'{ reference_doc.title }', + f'{ owner }' + ) } +
+ """ + return f""" +
+ { owner } + { _('assigned a new task {0} to you').format( + f'{ reference_doc.title }' + ) } +
+ """ + +def get_redirect_to_doc(doc): + if doc.reference_type == "CRM Task": + reference_doc = frappe.get_doc(doc.reference_type, doc.reference_name) + return reference_doc.reference_doctype, reference_doc.reference_docname + + return doc.reference_type, doc.reference_name diff --git a/crm/api/whatsapp.py b/crm/api/whatsapp.py index 6bbc2424f..1a0f52fd2 100644 --- a/crm/api/whatsapp.py +++ b/crm/api/whatsapp.py @@ -2,6 +2,7 @@ import json from frappe import _ from crm.api.doc import get_assigned_users +from crm.fcrm.doctype.crm_notification.crm_notification import notify_user def validate(doc, method): @@ -29,30 +30,25 @@ def notify_agent(doc): if doctype.startswith("CRM "): doctype = doctype[4:].lower() notification_text = f""" -
- { _('You') } - { _('received a whatsapp message in {0}').format(doctype) } - { doc.reference_name } -
- """ +
+ { _('You') } + { _('received a whatsapp message in {0}').format(doctype) } + { doc.reference_name } +
+ """ assigned_users = get_assigned_users(doc.reference_doctype, doc.reference_name) for user in assigned_users: - values = frappe._dict( - doctype="CRM Notification", - from_user=doc.owner, - to_user=user, - type="WhatsApp", - message=doc.message, - notification_text=notification_text, - notification_type_doctype="WhatsApp Message", - notification_type_doc=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(ignore_permissions=True) + notify_user({ + "owner": doc.owner, + "assigned_to": user, + "notification_type": "WhatsApp", + "message": doc.message, + "notification_text": notification_text, + "reference_doctype": "WhatsApp Message", + "reference_docname": doc.name, + "redirect_to_doctype": doc.reference_doctype, + "redirect_to_docname": doc.reference_name, + }) def get_lead_or_deal_from_number(number): @@ -62,10 +58,10 @@ def find_record(doctype, mobile_no, where=""): mobile_no = parse_mobile_no(mobile_no) query = f""" - SELECT name, mobile_no - FROM `tab{doctype}` - WHERE CONCAT('+', REGEXP_REPLACE(mobile_no, '[^0-9]', '')) = {mobile_no} - """ + SELECT name, mobile_no + FROM `tab{doctype}` + WHERE CONCAT('+', REGEXP_REPLACE(mobile_no, '[^0-9]', '')) = {mobile_no} + """ data = frappe.db.sql(query + where, as_dict=True) return data[0].name if data else None diff --git a/crm/fcrm/doctype/crm_notification/crm_notification.json b/crm/fcrm/doctype/crm_notification/crm_notification.json index be7ac1f69..f6bc0380b 100644 --- a/crm/fcrm/doctype/crm_notification/crm_notification.json +++ b/crm/fcrm/doctype/crm_notification/crm_notification.json @@ -34,7 +34,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Type", - "options": "Mention\nWhatsApp", + "options": "Mention\nTask\nAssignment\nWhatsApp", "reqd": 1 }, { @@ -116,7 +116,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-04-25 16:26:07.484857", + "modified": "2024-09-23 19:34:08.635305", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Notification", diff --git a/crm/fcrm/doctype/crm_notification/crm_notification.py b/crm/fcrm/doctype/crm_notification/crm_notification.py index 28f4ab80f..69aa127d3 100644 --- a/crm/fcrm/doctype/crm_notification/crm_notification.py +++ b/crm/fcrm/doctype/crm_notification/crm_notification.py @@ -2,9 +2,35 @@ # For license information, please see license.txt import frappe +from frappe import _ from frappe.model.document import Document class CRMNotification(Document): - def on_update(self): - frappe.publish_realtime("crm_notification") + def on_update(self): + frappe.publish_realtime("crm_notification") + +def notify_user(args): + """ + Notify the assigned user + """ + args = frappe._dict(args) + if args.owner == args.assigned_to: + return + + values = frappe._dict( + doctype="CRM Notification", + from_user=args.owner, + to_user=args.assigned_to, + type=args.notification_type, + message=args.message, + notification_text=args.notification_text, + notification_type_doctype=args.reference_doctype, + notification_type_doc=args.reference_docname, + reference_doctype=args.redirect_to_doctype, + reference_name=args.redirect_to_docname, + ) + + if frappe.db.exists("CRM Notification", values): + return + frappe.get_doc(values).insert(ignore_permissions=True) \ No newline at end of file diff --git a/crm/fcrm/doctype/crm_task/crm_task.py b/crm/fcrm/doctype/crm_task/crm_task.py index cf1bc963a..06b50c4de 100644 --- a/crm/fcrm/doctype/crm_task/crm_task.py +++ b/crm/fcrm/doctype/crm_task/crm_task.py @@ -1,11 +1,38 @@ # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +import frappe +from frappe import _ from frappe.model.document import Document +from frappe.desk.form.assign_to import add as assign, remove as unassign +from crm.fcrm.doctype.crm_notification.crm_notification import notify_user class CRMTask(Document): + def after_insert(self): + self.assign_to() + + def validate(self): + if self.is_new() or not self.assigned_to: + return + + if self.get_doc_before_save().assigned_to != self.assigned_to: + self.unassign_from_previous_user(self.get_doc_before_save().assigned_to) + self.assign_to() + + def unassign_from_previous_user(self, user): + unassign(self.doctype, self.name, user) + + def assign_to(self): + if self.assigned_to: + assign({ + "assign_to": [self.assigned_to], + "doctype": self.doctype, + "name": self.name, + "description": self.title or self.description, + }) + + @staticmethod def default_list_data(): columns = [ diff --git a/crm/hooks.py b/crm/hooks.py index fa7e606a8..286eee4d4 100644 --- a/crm/hooks.py +++ b/crm/hooks.py @@ -144,6 +144,7 @@ }, "ToDo": { "after_insert": ["crm.api.todo.after_insert"], + "on_update": ["crm.api.todo.on_update"], }, "Comment": { "on_update": ["crm.api.comment.on_update"], @@ -152,8 +153,8 @@ "validate": ["crm.api.whatsapp.validate"], "on_update": ["crm.api.whatsapp.on_update"], }, - "CRM Deal": { - "on_update": ["crm.fcrm.doctype.erpnext_crm_settings.erpnext_crm_settings.create_customer_in_erpnext"], + "CRM Deal": { + "on_update": ["crm.fcrm.doctype.erpnext_crm_settings.erpnext_crm_settings.create_customer_in_erpnext"], }, } diff --git a/frontend/src/components/ListViews/TasksListView.vue b/frontend/src/components/ListViews/TasksListView.vue index 166f280c9..876adc065 100644 --- a/frontend/src/components/ListViews/TasksListView.vue +++ b/frontend/src/components/ListViews/TasksListView.vue @@ -39,9 +39,11 @@ :row="row" >
- +
- +
{{ dateFormat(item, 'D MMM, hh:mm a') }}