snapshot of https://github.com/frappe/crm.git (develop, 2026-09-18; blobs > 131072B omitted)
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
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
This commit is contained in:
128
crm/api/comment.py
Normal file
128
crm/api/comment.py
Normal file
@@ -0,0 +1,128 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user