Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions crm/api/communication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import frappe
from frappe import _

from crm.api.doc import get_assigned_users
from crm.fcrm.doctype.crm_notification.crm_notification import notify_user


def after_insert(doc, method):
"""
Notify assigned users when an email is received on a Lead or Deal.

This hook is triggered after a Communication document is created.
We only process incoming emails that are linked to CRM Lead or CRM Deal.
"""
if not (
doc.communication_medium == "Email"
and doc.sent_or_received == "Received"
):
return

if doc.reference_doctype not in ["CRM Lead", "CRM Deal"]:
return

if not doc.reference_name:
return

notify_email_received(doc)


def notify_email_received(doc):
"""
Create notifications for all users assigned to the Lead/Deal.

If the email is linked to a converted Lead, we notify the Deal assignees instead,
since the Deal is now the active record for this customer.

Args:
doc: Communication document instance
"""
reference_doctype = doc.reference_doctype
reference_name = doc.reference_name
reference_doc = None

if reference_doctype == "CRM Lead":
try:
reference_doc = frappe.get_doc("CRM Lead", reference_name)
if reference_doc.converted:
deal_name = frappe.db.get_value(
"CRM Deal",
{"lead": reference_name},
"name",
order_by="creation desc"
)
if deal_name:
reference_doctype = "CRM Deal"
reference_name = deal_name
reference_doc = frappe.get_doc(reference_doctype, reference_name)
except (frappe.DoesNotExistError, frappe.ValidationError):
return
else:
try:
reference_doc = frappe.get_doc(reference_doctype, reference_name)
except (frappe.DoesNotExistError, frappe.ValidationError):
return

assigned_users = get_assigned_users(reference_doctype, reference_name)
if not assigned_users:
return

doctype = reference_doctype
if doctype and doctype.startswith("CRM "):
doctype = doctype[4:].lower()

if reference_doctype == "CRM Lead":
display_name = reference_doc.lead_name or reference_name
elif reference_doctype == "CRM Deal":
display_name = (
reference_doc.organization
or reference_doc.lead_name
or reference_name
)
else:
display_name = reference_name

sender_name = doc.sender_full_name or doc.sender or _("Someone")

notification_text = f"""
<div class="mb-2 leading-5 text-ink-gray-5">
<span class="font-medium text-ink-gray-9">{sender_name}</span>
<span>{_('sent an email to {0}').format(doctype)}</span>
<span class="font-medium text-ink-gray-9">{display_name}</span>
</div>
"""

message = doc.subject or _("No subject")

for user in assigned_users:
notify_user(
{
"owner": doc.owner,
"assigned_to": user,
"notification_type": "Email",
"message": message,
"notification_text": notification_text,
"reference_doctype": "Communication",
"reference_docname": doc.name,
"redirect_to_doctype": reference_doctype,
"redirect_to_docname": reference_name,
}
)
7 changes: 4 additions & 3 deletions crm/fcrm/doctype/crm_notification/crm_notification.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"fieldtype": "Select",
"in_list_view": 1,
"label": "Type",
"options": "Mention\nTask\nAssignment\nWhatsApp",
"options": "Mention\nTask\nAssignment\nWhatsApp\nEmail",
"reqd": 1
},
{
Expand Down Expand Up @@ -116,7 +116,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-09-23 19:34:08.635305",
"modified": "2025-10-28 16:48:35.523319",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Notification",
Expand Down Expand Up @@ -159,7 +159,8 @@
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}
3 changes: 3 additions & 0 deletions crm/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@
"validate": ["crm.api.whatsapp.validate"],
"on_update": ["crm.api.whatsapp.on_update"],
},
"Communication": {
"after_insert": ["crm.api.communication.after_insert"],
},
"CRM Deal": {
"on_update": [
"crm.fcrm.doctype.erpnext_crm_settings.erpnext_crm_settings.create_customer_in_erpnext"
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/components/Notifications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
:class="[n.read ? 'bg-transparent' : 'bg-surface-gray-7']"
/>
<WhatsAppIcon v-if="n.type == 'WhatsApp'" class="size-7" />
<Email2Icon v-else-if="n.type == 'Email'" class="size-7" />
<UserAvatar v-else :user="n.from_user.name" size="lg" />
</div>
<div>
Expand Down Expand Up @@ -82,6 +83,7 @@
</template>
<script setup>
import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
import Email2Icon from '@/components/Icons/Email2Icon.vue'
import MarkAsDoneIcon from '@/components/Icons/MarkAsDoneIcon.vue'
import NotificationsIcon from '@/components/Icons/NotificationsIcon.vue'
import UserAvatar from '@/components/UserAvatar.vue'
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/pages/MobileNotification.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
:class="[n.read ? 'bg-transparent' : 'bg-surface-gray-7']"
/>
<WhatsAppIcon v-if="n.type == 'WhatsApp'" class="size-7" />
<Email2Icon v-else-if="n.type == 'Email'" class="size-7" />
<UserAvatar v-else :user="n.from_user.name" size="lg" />
</div>
<div>
Expand Down Expand Up @@ -66,6 +67,7 @@
<script setup>
import LayoutHeader from '@/components/LayoutHeader.vue'
import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
import Email2Icon from '@/components/Icons/Email2Icon.vue'
import MarkAsDoneIcon from '@/components/Icons/MarkAsDoneIcon.vue'
import NotificationsIcon from '@/components/Icons/NotificationsIcon.vue'
import UserAvatar from '@/components/UserAvatar.vue'
Expand Down