334 lines
8.2 KiB
Python
334 lines
8.2 KiB
Python
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
# License: MIT. See LICENSE
|
|
|
|
import frappe
|
|
import frappe.utils
|
|
from frappe import _
|
|
from frappe.model import log_types
|
|
from frappe.query_builder import DocType
|
|
from frappe.utils import get_url_to_form
|
|
|
|
|
|
@frappe.whitelist()
|
|
def update_follow(doctype, doc_name, following):
|
|
if following:
|
|
return follow_document(doctype, doc_name, frappe.session.user)
|
|
else:
|
|
return unfollow_document(doctype, doc_name, frappe.session.user)
|
|
|
|
|
|
@frappe.whitelist()
|
|
def follow_document(doctype, doc_name, user):
|
|
"""
|
|
param:
|
|
Doctype name
|
|
doc name
|
|
user email
|
|
|
|
condition:
|
|
avoided for some doctype
|
|
follow only if track changes are set to 1
|
|
"""
|
|
if (
|
|
doctype
|
|
in (
|
|
"Communication",
|
|
"ToDo",
|
|
"Email Unsubscribe",
|
|
"File",
|
|
"Comment",
|
|
"Email Account",
|
|
"Email Domain",
|
|
)
|
|
or doctype in log_types
|
|
):
|
|
return
|
|
|
|
if (not frappe.get_meta(doctype).track_changes) or user == "Administrator":
|
|
return
|
|
|
|
if not frappe.db.get_value("User", user, "document_follow_notify", ignore=True, cache=True):
|
|
return
|
|
|
|
if not is_document_followed(doctype, doc_name, user):
|
|
doc = frappe.new_doc("Document Follow")
|
|
doc.update({"ref_doctype": doctype, "ref_docname": doc_name, "user": user})
|
|
doc.save()
|
|
return doc
|
|
|
|
|
|
@frappe.whitelist()
|
|
def unfollow_document(doctype, doc_name, user):
|
|
doc = frappe.get_all(
|
|
"Document Follow",
|
|
filters={"ref_doctype": doctype, "ref_docname": doc_name, "user": user},
|
|
fields=["name"],
|
|
limit=1,
|
|
)
|
|
if doc:
|
|
frappe.delete_doc("Document Follow", doc[0].name)
|
|
return 1
|
|
return 0
|
|
|
|
|
|
def get_message(doc_name, doctype, frequency, user):
|
|
activity_list = get_version(doctype, doc_name, frequency, user) + get_comments(
|
|
doctype, doc_name, frequency, user
|
|
)
|
|
return sorted(activity_list, key=lambda k: k["time"], reverse=True)
|
|
|
|
|
|
def send_email_alert(receiver, docinfo, timeline):
|
|
if receiver:
|
|
frappe.sendmail(
|
|
subject=_("Document Follow Notification"),
|
|
recipients=[receiver],
|
|
template="document_follow",
|
|
args={
|
|
"docinfo": docinfo,
|
|
"timeline": timeline,
|
|
},
|
|
)
|
|
|
|
|
|
def send_document_follow_mails(frequency):
|
|
"""
|
|
param:
|
|
frequency for sanding mails
|
|
|
|
task:
|
|
set receiver according to frequency
|
|
group document list according to user
|
|
get changes, activity, comments on doctype
|
|
call method to send mail
|
|
"""
|
|
|
|
user_list = get_user_list(frequency)
|
|
|
|
for user in user_list:
|
|
message, valid_document_follows = get_message_for_user(frequency, user)
|
|
if message:
|
|
send_email_alert(user, valid_document_follows, message)
|
|
# send an email if we have already spent resources creating the message
|
|
# nosemgrep
|
|
frappe.db.commit()
|
|
|
|
|
|
def get_user_list(frequency):
|
|
DocumentFollow = DocType("Document Follow")
|
|
User = DocType("User")
|
|
return (
|
|
frappe.qb.from_(DocumentFollow)
|
|
.join(User)
|
|
.on(DocumentFollow.user == User.name)
|
|
.where(User.document_follow_notify == 1)
|
|
.where(User.document_follow_frequency == frequency)
|
|
.select(DocumentFollow.user)
|
|
.groupby(DocumentFollow.user)
|
|
).run(pluck="user")
|
|
|
|
|
|
def get_message_for_user(frequency, user):
|
|
message = []
|
|
latest_document_follows = get_document_followed_by_user(user)
|
|
valid_document_follows = []
|
|
|
|
for document_follow in latest_document_follows:
|
|
content = get_message(document_follow.ref_docname, document_follow.ref_doctype, frequency, user)
|
|
if content:
|
|
message = message + content
|
|
valid_document_follows.append(
|
|
{
|
|
"reference_docname": document_follow.ref_docname,
|
|
"reference_doctype": document_follow.ref_doctype,
|
|
"reference_url": get_url_to_form(document_follow.ref_doctype, document_follow.ref_docname),
|
|
}
|
|
)
|
|
return message, valid_document_follows
|
|
|
|
|
|
def get_document_followed_by_user(user):
|
|
DocumentFollow = DocType("Document Follow")
|
|
# at max 20 documents are sent for each user
|
|
return (
|
|
frappe.qb.from_(DocumentFollow)
|
|
.where(DocumentFollow.user == user)
|
|
.select(DocumentFollow.ref_doctype, DocumentFollow.ref_docname)
|
|
.orderby(DocumentFollow.modified)
|
|
.limit(20)
|
|
).run(as_dict=True)
|
|
|
|
|
|
def get_version(doctype, doc_name, frequency, user):
|
|
timeline = []
|
|
filters = get_filters("docname", doc_name, frequency, user)
|
|
version = frappe.get_all(
|
|
"Version", filters=filters, fields=["ref_doctype", "data", "modified", "modified", "modified_by"]
|
|
)
|
|
if version:
|
|
for v in version:
|
|
change = frappe.parse_json(v.data)
|
|
time = frappe.utils.format_datetime(v.modified, "hh:mm a")
|
|
timeline_items = []
|
|
if change.changed:
|
|
timeline_items = get_field_changed(change.changed, time, doctype, doc_name, v)
|
|
if change.row_changed:
|
|
timeline_items = get_row_changed(change.row_changed, time, doctype, doc_name, v)
|
|
if change.added:
|
|
timeline_items = get_added_row(change.added, time, doctype, doc_name, v)
|
|
|
|
timeline = timeline + timeline_items
|
|
|
|
return timeline
|
|
|
|
|
|
def get_comments(doctype, doc_name, frequency, user):
|
|
from frappe.core.utils import html2text
|
|
|
|
timeline = []
|
|
filters = get_filters("reference_name", doc_name, frequency, user)
|
|
comments = frappe.get_all(
|
|
"Comment", filters=filters, fields=["content", "modified", "modified_by", "comment_type"]
|
|
)
|
|
for comment in comments:
|
|
if comment.comment_type == "Like":
|
|
by = f""" By : <b>{comment.modified_by}<b>"""
|
|
elif comment.comment_type == "Comment":
|
|
by = f"""Commented by : <b>{comment.modified_by}<b>"""
|
|
else:
|
|
by = ""
|
|
|
|
time = frappe.utils.format_datetime(comment.modified, "hh:mm a")
|
|
timeline.append(
|
|
{
|
|
"time": comment.modified,
|
|
"data": {"time": time, "comment": html2text(str(comment.content)), "by": by},
|
|
"doctype": doctype,
|
|
"doc_name": doc_name,
|
|
"type": "comment",
|
|
}
|
|
)
|
|
return timeline
|
|
|
|
|
|
def is_document_followed(doctype, doc_name, user):
|
|
return frappe.db.exists(
|
|
"Document Follow", {"ref_doctype": doctype, "ref_docname": doc_name, "user": user}
|
|
)
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_follow_users(doctype, doc_name):
|
|
return frappe.get_all(
|
|
"Document Follow", filters={"ref_doctype": doctype, "ref_docname": doc_name}, fields=["user"]
|
|
)
|
|
|
|
|
|
def get_row_changed(row_changed, time, doctype, doc_name, v):
|
|
from frappe.core.utils import html2text
|
|
|
|
items = []
|
|
for d in row_changed:
|
|
d[2] = d[2] if d[2] else " "
|
|
d[0] = d[0] if d[0] else " "
|
|
d[3][0][1] = d[3][0][1] if d[3][0][1] else " "
|
|
items.append(
|
|
{
|
|
"time": v.modified,
|
|
"data": {
|
|
"time": time,
|
|
"table_field": d[0],
|
|
"row": str(d[1]),
|
|
"field": d[3][0][0],
|
|
"from": html2text(str(d[3][0][1])),
|
|
"to": html2text(str(d[3][0][2])),
|
|
},
|
|
"doctype": doctype,
|
|
"doc_name": doc_name,
|
|
"type": "row changed",
|
|
"by": v.modified_by,
|
|
}
|
|
)
|
|
return items
|
|
|
|
|
|
def get_added_row(added, time, doctype, doc_name, v):
|
|
items = []
|
|
for d in added:
|
|
items.append(
|
|
{
|
|
"time": v.modified,
|
|
"data": {"to": d[0], "time": time},
|
|
"doctype": doctype,
|
|
"doc_name": doc_name,
|
|
"type": "row added",
|
|
"by": v.modified_by,
|
|
}
|
|
)
|
|
return items
|
|
|
|
|
|
def get_field_changed(changed, time, doctype, doc_name, v):
|
|
from frappe.core.utils import html2text
|
|
|
|
items = []
|
|
for d in changed:
|
|
d[1] = d[1] if d[1] else " "
|
|
d[2] = d[2] if d[2] else " "
|
|
d[0] = d[0] if d[0] else " "
|
|
items.append(
|
|
{
|
|
"time": v.modified,
|
|
"data": {
|
|
"time": time,
|
|
"field": d[0],
|
|
"from": html2text(str(d[1])),
|
|
"to": html2text(str(d[2])),
|
|
},
|
|
"doctype": doctype,
|
|
"doc_name": doc_name,
|
|
"type": "field changed",
|
|
"by": v.modified_by,
|
|
}
|
|
)
|
|
return items
|
|
|
|
|
|
def send_hourly_updates():
|
|
send_document_follow_mails("Hourly")
|
|
|
|
|
|
def send_daily_updates():
|
|
send_document_follow_mails("Daily")
|
|
|
|
|
|
def send_weekly_updates():
|
|
send_document_follow_mails("Weekly")
|
|
|
|
|
|
def get_filters(search_by, name, frequency, user):
|
|
filters = []
|
|
|
|
if frequency == "Weekly":
|
|
filters = [
|
|
[search_by, "=", name],
|
|
["modified", ">", frappe.utils.add_days(frappe.utils.nowdate(), -7)],
|
|
["modified", "<", frappe.utils.nowdate()],
|
|
["modified_by", "!=", user],
|
|
]
|
|
elif frequency == "Daily":
|
|
filters = [
|
|
[search_by, "=", name],
|
|
["modified", ">", frappe.utils.add_days(frappe.utils.nowdate(), -1)],
|
|
["modified", "<", frappe.utils.nowdate()],
|
|
["modified_by", "!=", user],
|
|
]
|
|
elif frequency == "Hourly":
|
|
filters = [
|
|
[search_by, "=", name],
|
|
["modified", ">", frappe.utils.add_to_date(frappe.utils.now_datetime(), hours=-1)],
|
|
["modified", "<", frappe.utils.now_datetime()],
|
|
["modified_by", "!=", user],
|
|
]
|
|
|
|
return filters
|