def find_parent_based_on_subject_and_sender(self, communication, email): '''Find parent document based on subject and sender match''' parent = None if self.append_to and self.sender_field: if self.subject_field: # try and match by subject and sender # if sent by same sender with same subject, # append it to old coversation subject = frappe.as_unicode(strip(re.sub(r"(^\s*(fw|fwd|wg)[^:]*:|\s*(re|aw)[^:]*:\s*)*", "", email.subject, 0, flags=re.IGNORECASE))) parent = frappe.db.get_all(self.append_to, filters={ self.sender_field: email.from_email, self.subject_field: ("like", "%{0}%".format(subject)), "creation": (">", (get_datetime() - relativedelta(days=60)).strftime(DATE_FORMAT)) }, fields="name") # match only subject field # when the from_email is of a user in the system # and subject is atleast 10 chars long if not parent and len(subject) > 10 and is_system_user(email.from_email): parent = frappe.db.get_all(self.append_to, filters={ self.subject_field: ("like", "%{0}%".format(subject)), "creation": (">", (get_datetime() - relativedelta(days=60)).strftime(DATE_FORMAT)) }, fields="name") if parent: parent = frappe._dict(doctype=self.append_to, name=parent[0].name) return parent
def match_record_by_subject_and_sender(self, doctype): """Find a record in the given doctype that matches with email subject and sender. Cases: 1. Sometimes record name is part of subject. We can get document by parsing name from subject 2. Find by matching sender and subject 3. Find by matching subject alone (Special case) Ex: when a System User is using Outlook and replies to an email from their own client, it reaches the Email Account with the threading info lost and the (sender + subject match) doesn't work because the sender in the first communication was someone different to whom the system user is replying to via the common email account in Frappe. This fix bypasses the sender match when the sender is a system user and subject is atleast 10 chars long (for additional safety) NOTE: We consider not to match by subject if match record is very old. """ name = self.get_reference_name_from_subject() email_fields = self.get_email_fields(doctype) record = self.get_doc(doctype, name, ignore_error=True) if name else None if not record: subject = self.clean_subject(self.subject) filters = { email_fields.subject_field: ("like", f"%{subject}%"), "creation": (">", self.get_relative_dt(days=-60)) } # Sender check is not needed incase mail is from system user. if not (len(subject) > 10 and is_system_user(self.from_email)): filters[email_fields.sender_field] = self.from_email name = frappe.db.get_value(self.email_account.append_to, filters = filters) record = self.get_doc(doctype, name, ignore_error=True) if name else None return record
def update_first_response_time(parent, communication): if parent.meta.has_field("first_response_time") and not parent.get("first_response_time"): if is_system_user(communication.sender): first_responded_on = communication.creation if parent.meta.has_field("first_responded_on") and communication.sent_or_received == "Sent": parent.db_set("first_responded_on", first_responded_on) parent.db_set("first_response_time", round(time_diff_in_seconds(first_responded_on, parent.creation), 2))
def find_parent_based_on_subject_and_sender(self, communication, email): '''Find parent document based on subject and sender match''' parent = None if self.append_to and self.sender_field: if self.subject_field: # try and match by subject and sender # if sent by same sender with same subject, # append it to old coversation subject = strip(re.sub("(^\s*(Fw|FW|fwd)[^:]*:|\s*(Re|RE)[^:]*:\s*)*", "", email.subject)) parent = frappe.db.get_all(self.append_to, filters={ self.sender_field: email.from_email, self.subject_field: ("like", "%{0}%".format(subject)), "creation": (">", (get_datetime() - relativedelta(days=10)).strftime(DATE_FORMAT)) }, fields="name") # match only subject field # when the from_email is of a user in the system # and subject is atleast 10 chars long if not parent and len(subject) > 10 and is_system_user(email.from_email): parent = frappe.db.get_all(self.append_to, filters={ self.subject_field: ("like", "%{0}%".format(subject)), "creation": (">", (get_datetime() - relativedelta(days=10)).strftime(DATE_FORMAT)) }, fields="name") if parent: parent = frappe.get_doc(self.append_to, parent[0].name) return parent
def find_parent_based_on_subject_and_sender(self, communication, email): '''Find parent document based on subject and sender match''' parent = None if self.append_to and self.sender_field: if self.subject_field: if '#' in email.subject: # try and match if ID is found # document ID is appended to subject # example "Re: Your email (#OPP-2020-2334343)" parent_id = email.subject.rsplit('#', 1)[-1].strip(' ()') if parent_id: parent = frappe.db.get_all(self.append_to, filters = dict(name = parent_id), fields = 'name') if not parent: # try and match by subject and sender # if sent by same sender with same subject, # append it to old coversation subject = frappe.as_unicode(strip(re.sub(r"(^\s*(fw|fwd|wg)[^:]*:|\s*(re|aw)[^:]*:\s*)*", "", email.subject, 0, flags=re.IGNORECASE))) parent = frappe.db.get_all(self.append_to, filters={ self.sender_field: email.from_email, self.subject_field: ("like", "%{0}%".format(subject)), "creation": (">", (get_datetime() - relativedelta(days=60)).strftime(DATE_FORMAT)) }, fields = "name", limit = 1) if not parent and len(subject) > 10 and is_system_user(email.from_email): # match only subject field # when the from_email is of a user in the system # and subject is atleast 10 chars long parent = frappe.db.get_all(self.append_to, filters={ self.subject_field: ("like", "%{0}%".format(subject)), "creation": (">", (get_datetime() - relativedelta(days=60)).strftime(DATE_FORMAT)) }, fields = "name", limit = 1) if parent: parent = frappe._dict(doctype=self.append_to, name=parent[0].name) return parent
def set_thread(self, communication, email): """Appends communication to parent based on thread ID. Will extract parent communication and will link the communication to the reference of that communication. Also set the status of parent transaction to Open or Replied. If no thread id is found and `append_to` is set for the email account, it will create a new parent transaction (e.g. Issue)""" in_reply_to = (email.mail.get("In-Reply-To") or "").strip(" <>") parent = None if self.append_to: # set subject_field and sender_field meta_module = frappe.get_meta_module(self.append_to) meta = frappe.get_meta(self.append_to) subject_field = getattr(meta_module, "subject_field", "subject") if not meta.get_field(subject_field): subject_field = None sender_field = getattr(meta_module, "sender_field", "sender") if not meta.get_field(sender_field): sender_field = None if in_reply_to: if "@{0}".format(frappe.local.site) in in_reply_to: # reply to a communication sent from the system in_reply_to, domain = in_reply_to.split("@", 1) if frappe.db.exists("Communication", in_reply_to): parent = frappe.get_doc("Communication", in_reply_to) # set in_reply_to of current communication communication.in_reply_to = in_reply_to if parent.reference_name: parent = frappe.get_doc(parent.reference_doctype, parent.reference_name) if not parent and self.append_to and sender_field: if subject_field: # try and match by subject and sender # if sent by same sender with same subject, # append it to old coversation subject = strip(re.sub("^\s*(Re|RE)[^:]*:\s*", "", email.subject)) parent = frappe.db.get_all(self.append_to, filters={ sender_field: email.from_email, subject_field: ("like", "%{0}%".format(subject)), "creation": (">", (get_datetime() - relativedelta(days=10)).strftime(DATE_FORMAT)) }, fields="name") # match only subject field # when the from_email is of a user in the system # and subject is atleast 10 chars long if not parent and len(subject) > 10 and is_system_user(email.from_email): parent = frappe.db.get_all(self.append_to, filters={ subject_field: ("like", "%{0}%".format(subject)), "creation": (">", (get_datetime() - relativedelta(days=10)).strftime(DATE_FORMAT)) }, fields="name") if parent: parent = frappe.get_doc(self.append_to, parent[0].name) if not parent and self.append_to and self.append_to!="Communication": # no parent found, but must be tagged # insert parent type doc parent = frappe.new_doc(self.append_to) if subject_field: parent.set(subject_field, email.subject) if sender_field: parent.set(sender_field, email.from_email) parent.flags.ignore_mandatory = True try: parent.insert(ignore_permissions=True) except frappe.DuplicateEntryError: # try and find matching parent parent_name = frappe.db.get_value(self.append_to, {sender_field: email.from_email}) if parent_name: parent.name = parent_name else: parent = None # NOTE if parent isn't found and there's no subject match, it is likely that it is a new conversation thread and hence is_first = True communication.is_first = True if parent: communication.reference_doctype = parent.doctype communication.reference_name = parent.name
def set_thread(self, communication, email): """Appends communication to parent based on thread ID. Will extract parent communication and will link the communication to the reference of that communication. Also set the status of parent transaction to Open or Replied. If no thread id is found and `append_to` is set for the email account, it will create a new parent transaction (e.g. Issue)""" in_reply_to = (email.mail.get("In-Reply-To") or "").strip(" <>") parent = None if self.append_to: # set subject_field and sender_field meta_module = frappe.get_meta_module(self.append_to) meta = frappe.get_meta(self.append_to) subject_field = getattr(meta_module, "subject_field", "subject") if not meta.get_field(subject_field): subject_field = None sender_field = getattr(meta_module, "sender_field", "sender") if not meta.get_field(sender_field): sender_field = None if in_reply_to: if "@{0}".format(frappe.local.site) in in_reply_to: # reply to a communication sent from the system in_reply_to, domain = in_reply_to.split("@", 1) if frappe.db.exists("Communication", in_reply_to): parent = frappe.get_doc("Communication", in_reply_to) # set in_reply_to of current communication communication.in_reply_to = in_reply_to if parent.reference_name: parent = frappe.get_doc(parent.reference_doctype, parent.reference_name) if not parent and self.append_to and sender_field: if subject_field: # try and match by subject and sender # if sent by same sender with same subject, # append it to old coversation subject = strip( re.sub("^\s*(Re|RE)[^:]*:\s*", "", email.subject)) parent = frappe.db.get_all( self.append_to, filters={ sender_field: email.from_email, subject_field: ("like", "%{0}%".format(subject)), "creation": (">", (get_datetime() - relativedelta(days=10)).strftime(DATE_FORMAT)) }, fields="name") # match only subject field # when the from_email is of a user in the system # and subject is atleast 10 chars long if not parent and len(subject) > 10 and is_system_user( email.from_email): parent = frappe.db.get_all( self.append_to, filters={ subject_field: ("like", "%{0}%".format(subject)), "creation": (">", (get_datetime() - relativedelta(days=10)).strftime(DATE_FORMAT)) }, fields="name") if parent: parent = frappe.get_doc(self.append_to, parent[0].name) if not parent and self.append_to and self.append_to != "Communication": # no parent found, but must be tagged # insert parent type doc parent = frappe.new_doc(self.append_to) if subject_field: parent.set(subject_field, email.subject) if sender_field: parent.set(sender_field, email.from_email) parent.flags.ignore_mandatory = True try: parent.insert(ignore_permissions=True) except frappe.DuplicateEntryError: # try and find matching parent parent_name = frappe.db.get_value( self.append_to, {sender_field: email.from_email}) if parent_name: parent.name = parent_name else: parent = None # NOTE if parent isn't found and there's no subject match, it is likely that it is a new conversation thread and hence is_first = True communication.is_first = True if parent: communication.reference_doctype = parent.doctype communication.reference_name = parent.name
def set_thread(self, communication, email): """Appends communication to parent based on thread ID. Will extract parent communication and will link the communication to the reference of that communication. Also set the status of parent transaction to Open or Replied. If no thread id is found and `append_to` is set for the email account, it will create a new parent transaction (e.g. Issue)""" in_reply_to = (email.mail.get("In-Reply-To") or "") parent = None if self.append_to: # set subject_field and sender_field meta_module = frappe.get_meta_module(self.append_to) meta = frappe.get_meta(self.append_to) subject_field = getattr(meta_module, "subject_field", "subject") if not meta.get_field(subject_field): subject_field = None sender_field = getattr(meta_module, "sender_field", "sender") if not meta.get_field(sender_field): sender_field = None matched = False if in_reply_to: # reply to a communication sent from the system reply_found = frappe.db.get_value("Communication", {"message_id": in_reply_to}, ["name", "reference_doctype", "reference_name"], order_by="creation", as_dict=1) if reply_found: # set in_reply_to of current communication communication.in_reply_to = reply_found.name if frappe.db.exists(reply_found.reference_doctype, reply_found.reference_name): communication.reference_doctype = reply_found.reference_doctype communication.reference_name = reply_found.reference_name matched = True if email.message_id: first = frappe.db.get_value("Communication", {"message_id": email.message_id},["name", "reference_doctype", "reference_name"], order_by="creation", as_dict=1) if first: #set timeline hide to parent doc so are linked communication.timeline_hide = first.name if frappe.db.exists(first.reference_doctype, first.reference_name): communication.reference_doctype = first.reference_doctype communication.reference_name = first.reference_name matched = True if not matched: if not parent and self.append_to and sender_field: if subject_field: # try and match by subject and sender # if sent by same sender with same subject, # append it to old coversation subject = strip(re.sub("^\s*(Re|RE)[^:]*:\s*", "", email.subject)) parent = frappe.db.get_all(self.append_to, filters={ sender_field: email.from_email, subject_field: ("like", "%{0}%".format(subject)), "creation": (">", (get_datetime() - relativedelta(days=10)).strftime(DATE_FORMAT)) }, fields="name") # match only subject field # when the from_email is of a user in the system # and subject is atleast 10 chars long if not parent and len(subject) > 10 and is_system_user(email.from_email): parent = frappe.db.get_all(self.append_to, filters={ subject_field: ("like", "%{0}%".format(subject)), "creation": (">", (get_datetime() - relativedelta(days=10)).strftime(DATE_FORMAT)) }, fields="name") if parent: parent = frappe.get_doc(self.append_to, parent[0].name) if not parent: # try match doctype based on subject if ':' in email.subject: try: subject = strip(re.sub("(^\s*(Fw|FW|fwd)[^:]*:|\s*(Re|RE)[^:]*:\s*)*","", email.subject)) if ':' in subject: reference_doctype,reference_name = subject.split(': ',1) parent = frappe.get_doc(reference_doctype,reference_name) except: try: ref = re.search("((?<=New Leave Application: ).*(?= - Employee:))",email.subject).group(0) parent = frappe.get_doc("Leave Application",ref) except: pass if not parent and self.append_to and self.append_to!="Communication": # no parent found, but must be tagged # insert parent type doc parent = frappe.new_doc(self.append_to) if subject_field: parent.set(subject_field, email.subject) if sender_field: parent.set(sender_field, email.from_email) parent.flags.ignore_mandatory = True try: parent.insert(ignore_permissions=True) except frappe.DuplicateEntryError: # try and find matching parent parent_name = frappe.db.get_value(self.append_to, {sender_field: email.from_email}) if parent_name: parent.name = parent_name else: parent = None # NOTE if parent isn't found and there's no subject match, it is likely that it is a new conversation thread and hence is_first = True communication.is_first = True if parent: communication.reference_doctype = parent.doctype communication.reference_name = parent.name # check if message is notification and disable notifications for this message isnotification = email.mail.get("isnotification") if isnotification: if "notification" in isnotification: communication.unread_notification_sent = 1