Files
upstream-frappe-crm/crm/api/comment.py
upstream-mirror db69de1c11
Some checks failed
Frontend / Unit Tests & Coverage (push) Has been skipped
Regenerate POT file (translatable strings) / Regenerate POT file (develop) (push) Failing after 1m13s
Regenerate POT file (translatable strings) / Regenerate POT file (main-hotfix) (push) Failing after 6s
Server / Tests (frappe ${{ matrix.frappe-branch }}) (push) Has been skipped
Server / Resolve frappe matrix (push) Failing after 1s
Frontend / Lint (push) Failing after 1s
Frontend / Production Build (push) Failing after 1s
Server / Coverage Wrap Up (push) Has been skipped
Frontend / Coverage Wrap Up (push) Has been skipped
snapshot of https://github.com/frappe/crm.git (develop, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:36:01 +05:00

129 lines
3.6 KiB
Python

from collections.abc import Iterable
import frappe
from bs4 import BeautifulSoup
from frappe import _
from frappe.desk.form.utils import add_comment as frappe_add_comment
from frappe.utils import get_fullname
from crm.fcrm.doctype.crm_notification.crm_notification import notify_user
def on_update(self, method):
notify_mentions(self)
def notify_mentions(doc):
"""
Extract mentions from `content`, and notify.
`content` must have `HTML` content.
"""
if doc.reference_doctype not in ["CRM Lead", "CRM Deal"]:
return
content = getattr(doc, "content", None)
if not content:
return
mentions = extract_mentions(content)
reference_doc = frappe.get_doc(doc.reference_doctype, doc.reference_name)
for mention in mentions:
owner = frappe.get_cached_value("User", doc.owner, "full_name")
doctype = doc.reference_doctype
if doctype.startswith("CRM "):
doctype = doctype[4:].lower()
name = (
reference_doc.lead_name
if doctype == "lead"
else reference_doc.organization or reference_doc.lead_name
)
notification_text = f"""
<div class="mb-2 leading-5 text-ink-gray-5">
<span class="font-medium text-ink-gray-9">{owner}</span>
<span>{_("mentioned you in {0}").format(doctype)}</span>
<span class="font-medium text-ink-gray-9">{name}</span>
</div>
"""
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):
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
@frappe.whitelist()
def add_comment(reference_doctype: str, reference_name: str, content: str, attachments: list | None = None):
"""Add a comment to the given document
:param reference_doctype: Reference Doctype
:param reference_name: Reference Document Name
:param content: Comment Content (HTML)
:param attachments: List of File names or dicts with keys "fname" and "fcontent"
:return: Comment Document
"""
comment = frappe_add_comment(
reference_doctype,
reference_name,
content,
comment_email=frappe.session.user,
comment_by=get_fullname(frappe.session.user),
)
if attachments and comment.name:
add_attachments(comment.name, attachments)
return comment
def add_attachments(name: str, attachments: Iterable[str | dict]) -> None:
"""Add attachments to the given Comment
:param name: Comment name
:param attachments: File names or dicts with keys "fname" and "fcontent"
"""
# loop through attachments
for a in attachments:
if isinstance(a, str):
attach = frappe.db.get_value("File", {"name": a}, ["file_url", "is_private"], as_dict=1)
file_args = {
"file_url": attach.file_url,
"is_private": attach.is_private,
}
elif isinstance(a, dict) and "fcontent" in a and "fname" in a:
# dict returned by frappe.attach_print()
file_args = {
"file_name": a["fname"],
"content": a["fcontent"],
"is_private": 1,
}
else:
continue
file_args.update(
{
"attached_to_doctype": "Comment",
"attached_to_name": name,
"folder": "Home/Attachments",
}
)
_file = frappe.new_doc("File")
_file.update(file_args)
_file.save(ignore_permissions=True)