Added activity creation via mail and resource guess

This commit is contained in:
Alberto Rivera 2021-03-23 09:49:03 +01:00
parent 409d7a62a7
commit 67b9a58870
14 changed files with 199 additions and 216 deletions

View File

@ -1,16 +1,17 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from trytond.pool import Pool
from .activity import *
from .imap import *
from .user import *
from . import activity
from . import configuration
from . import user
def register():
Pool.register(
Activity,
IMAPServer,
User,
activity.Activity,
activity.Cron,
user.User,
configuration.Configuration,
module='electronic_mail_activity', type_='model')
Pool.register(
ActivityReplyMail,
activity.ActivityReplyMail,
module='electronic_mail_activity', type_='wizard')

View File

@ -4,6 +4,7 @@ from trytond.pool import Pool, PoolMeta
from trytond.model import fields, ModelView
from trytond.transaction import Transaction
from trytond.wizard import Wizard, StateAction
from trytond.pyson import Eval, Bool
from email.utils import parseaddr, formataddr, formatdate, make_msgid
from email import encoders, message_from_bytes
from email.mime.multipart import MIMEMultipart
@ -26,6 +27,16 @@ except ImportError:
__all__ = ['Activity', 'ActivityReplyMail']
class Cron(metaclass=PoolMeta):
__name__ = 'ir.cron'
@classmethod
def __setup__(cls):
super().__setup__()
cls.method.selection.extend([
('activity.activity|create_activity', "Create Activity")])
class Activity(metaclass=PoolMeta):
__name__ = 'activity.activity'
@ -40,12 +51,17 @@ class Activity(metaclass=PoolMeta):
def __setup__(cls):
super(Activity, cls).__setup__()
cls._buttons.update({
'new': {
'icon': 'tryton-email',
},
'reply': {
'icon': 'tryton-forward'
},
'new': {
'icon': 'tryton-email',
},
'reply': {
'icon': 'tryton-forward',
},
'guess': {
'icon': 'tryton-forward',
'invisible': Bool(Eval('resource', -1)),
'depends': ['resource'],
},
})
@property
@ -58,6 +74,10 @@ class Activity(metaclass=PoolMeta):
and self.related_activity.mail and
self.related_activity.mail.message_id or "")
@classmethod
def _get_origin(cls):
return super()._get_origin() + ['electronic.mail']
@property
def reference(self):
result = ""
@ -95,6 +115,17 @@ class Activity(metaclass=PoolMeta):
def reply(cls, activities):
cls.check_activity_user_info()
@classmethod
@ModelView.button
def guess(cls, activities):
activities = cls.browse(sorted(activities, key=lambda x: x.id))
for activity in activities:
activity = cls(activity)
activity.guess_resource()
# Each activity is saved because in this list there
# could be a resource of another of the same list
activity.save()
@classmethod
def check_activity_user_info(cls):
"Check if user have deffined the a server and a mailbox"
@ -265,158 +296,59 @@ class Activity(metaclass=PoolMeta):
return None
@classmethod
def create_activity(cls, received_mails):
IMAPServer = Pool().get('imap.server')
CompanyEmployee = Pool().get('company.employee')
def create_activity(cls):
pool = Pool()
ModelData = pool.get('ir.model.data')
Employee = pool.get('company.employee')
ElectronicMail = pool.get('electronic.mail')
Mailbox = pool.get('electronic.mail.mailbox')
Activity = pool.get('activity.activity')
ActivityType = pool.get('activity.type')
ActivityConfiguration = pool.get('activity.configuration')
mails = ElectronicMail.search([
('mailbox', '=', ActivityConfiguration(0).pending_mailbox)
], order=[('date', 'ASC'), ('id', 'ASC')])
activity_type = ActivityType(ModelData.get_id('activity',
'incoming_email_type'))
employee = ActivityConfiguration(0).employee
processed_mailbox = ActivityConfiguration(0).processed_mailbox
activities = []
for mail in mails:
activity = Activity()
activity.subject = mail.subject
activity.activity_type = activity_type
activity.employee = employee
activity.dtstart = mail.date
activity.description = html2text(mail.body_plain).replace('\r', '')
activity.mail = mail
activity.state = 'planned'
activity.resource = None
activity.origin = mail
activities.append(activity)
mail.mailbox = processed_mailbox
cls.save(activities)
ElectronicMail.save(mails)
cls.guess(activities)
def get_previous_activity(self):
ElectronicMail = Pool().get('electronic.mail')
Attachment = Pool().get('ir.attachment')
ActivityType = Pool().get('activity.type')
if not isinstance(self.origin, ElectronicMail):
return
parent = self.origin.parent
if not parent:
return
activities = self.search([
('origin', '=', parent)
], limit=1)
if activities:
return activities[0]
values = []
attachs = {}
for server_id, mails in list(received_mails.items()):
servers = IMAPServer.browse([server_id])
server = servers and servers[0] or None
for mail in mails:
# Control if the mail recevied is send by us, searching if
# there are any activity with that mail attached
activity_exist = cls.search([
('mail', '=', mail.id)
])
if activity_exist:
continue
# Take the possible employee, if not the default.
deliveredtos = mail.deliveredto and [mail.deliveredto] or []
deliveredtos.extend([m[1] for m in mail.all_to])
deliveredtos.extend([m[1] for m in mail.all_cc])
deliveredtos = ElectronicMail.validate_emails(deliveredtos)
contact = None
if deliveredtos:
employees = CompanyEmployee.search([])
parties = [p.party.id for p in employees]
contacts = []
for deliveredto in deliveredtos:
cm = cls.get_contact_mechanism(deliveredto, parties)
if cm:
contacts.append(cm)
contact = contacts and contacts[0] or None
employee = None
if contact:
emails_employee = [c.value
for c in contact.party.contact_mechanisms
if c.type == 'email']
employee = CompanyEmployee.search([
('party', '=', contact.party.id)
])
if not employee:
employee = server and server.employee or None
emails_employee = (server and server.employee and
[server.employee.party.email] or [])
else:
employee = employee[0]
# Search for the parties with that mails, to attach in the
# contacts and main contact
mail_from = ElectronicMail.validate_emails(
parseaddr(mail.from_.replace(',', ' '))[1])
contact = cls.get_contact_mechanism(mail_from)
main_contact = contact and contact.party or False
email_to = []
for to in mail.all_to:
if to[1] not in emails_employee:
email_to.append(to[1])
email_cc = email_to + [m[1] for m in mail.all_cc]
emails_cc = ElectronicMail.validate_emails(email_cc)
contacts = []
for email_cc in emails_cc:
contact = cls.get_contact_mechanism(email_cc)
if contact:
contacts.append(contact.party.id)
# Search for the possible activity referenced to add in the
# same resource.
referenced_mail = []
if mail.in_reply_to:
referenced_mail = ElectronicMail.search([
('message_id', '=', mail.in_reply_to)
])
if not referenced_mail and mail.reference:
referenced_mail = ElectronicMail.search([
('message_id', 'in', mail.reference)
])
# Fill the fields, in case the activity don't have enought
# information
resource = None
party = (main_contact and
[r.to for r in main_contact.relations] or [])
party = party and party[0] or None
if referenced_mail:
# Search if the activity have resource to use for activity
# that create now.
referenced_mails = [r.id for r in referenced_mail]
activities = cls.search([
('mail', 'in', referenced_mails)
])
if activities:
resource = activities[0].resource
party = resource and resource.party or party
# TODO: Search for a better default.
# By the moment search the first activity type with the 0 in
# sequence
activity_types = ActivityType.search([
('sequence', '=', 0)
])
activity_type = activity_types and activity_types[0] or None
# Create the activity
base_values = {
'subject': mail.subject or "NONE",
'activity_type': activity_type,
'employee': employee.id,
'dtstart': datetime.datetime.now(),
'description': (mail.body_plain
or html2text(mail.body_html)),
'mail': mail.id,
'state': 'planned',
'resource': None,
}
values = base_values.copy()
if resource:
values['resource'] = str(resource)
if party:
values['party'] = party.id
if main_contact:
values['main_contact'] = main_contact.id
if contacts:
values['contacts'] = [('add', contacts)]
try:
activity = cls.create([values])
except:
activity = cls.create([base_values])
# Add all the possible attachments from the mil to the activity
msg = message_from_bytes(mail.mail_file)
attachs = ElectronicMail.get_attachments(msg)
if attachs:
values = []
for attach in attachs:
values.append({
'name': attach.get('filename', mail.subject),
'type': 'data',
'data': attach.get('data'),
'resource': str(activity[0])
})
try:
Attachment.create(values)
except Exception as e:
logging.getLogger('Activity Mail').info(
'The mail (%s) has attachments but they are not '
'possible to attach to the activity (%s).\n\n%s' %
(mail.id, activity.id, e))
return mails
def guess_resource(self):
previous_activity = self.get_previous_activity()
if previous_activity:
if previous_activity.resource:
self.resource = previous_activity.resource
self.party = previous_activity.resource.party
class ActivityReplyMail(Wizard, metaclass=PoolMeta):

View File

@ -3,6 +3,13 @@
copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.cron" id="cron_create_activity">
<field name="method">activity.activity|create_activity</field>
<field name="active" eval="True"/>
<field name="interval_number" eval="1"/>
<field name="interval_type">days</field>
</record>
<record model="ir.action.wizard" id="wizard_replymail">
<field name="name">Reply Mail</field>
<field name="wiz_name">activity.activity.replymail</field>
@ -42,5 +49,15 @@
<field name="group" ref="electronic_mail.group_email_user"/>
</record>
<record model="ir.model.button" id="electronic_mail_guess_button">
<field name="name">guess</field>
<field name="string">Guess Resource</field>
<field name="model" search="[('model', '=', 'activity.activity')]"/>
</record>
<record model="ir.model.button-res.group"
id="activity_guess_button_group">
<field name="button" ref="electronic_mail_guess_button"/>
<field name="group" ref="electronic_mail.group_email_user"/>
</record>
</data>
</tryton>

16
configuration.py Normal file
View File

@ -0,0 +1,16 @@
from trytond.pool import Pool, PoolMeta
from trytond.model import fields, ModelView
__all__ = ['Configuration']
class Configuration(metaclass=PoolMeta):
'Activity Configuration'
__name__ = 'activity.configuration'
employee = fields.Many2One('company.employee', 'Employee', required=True)
pending_mailbox = fields.Many2One('electronic.mail.mailbox', 'Pending Mailbox',
required=True)
processed_mailbox = fields.Many2One('electronic.mail.mailbox', 'Processed Mailbox',
required=True)

12
configuration.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!--The COPYRIGHT file at the top level of this repository
contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="activity_configuration_view_form">
<field name="model">activity.configuration</field>
<field name="inherit" ref="activity.activity_configuration_view_form"/>
<field name="name">configuration_form</field>
</record>
</data>
</tryton>

30
imap.py
View File

@ -1,30 +0,0 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from trytond.pool import Pool, PoolMeta
from trytond.model import fields
__all__ = ['IMAPServer']
class IMAPServer(metaclass=PoolMeta):
__name__ = 'imap.server'
employee = fields.Many2One('company.employee', 'Default Employee')
@classmethod
def fetch_mails(cls, servers):
Activity = Pool().get('activity.activity')
activity_servers = []
other_servers = []
for server in servers:
if server.model and server.model.model == 'activity.activity':
activity_servers.append(server)
else:
other_servers.append(server)
mails = {}
if activity_servers:
mails.update(super(IMAPServer, cls).fetch_mails(activity_servers))
Activity.create_activity(mails)
if other_servers:
mails.update(super(IMAPServer, cls).fetch_mails(other_servers))
return mails

View File

@ -1,12 +0,0 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="imap_server_view_form">
<field name="model">imap.server</field>
<field name="inherit" ref="imap.imap_server_view_form"/>
<field name="name">imap_server_form</field>
</record>
</data>
</tryton>

View File

@ -14,6 +14,14 @@ msgctxt "field:activity.activity,related_activity:"
msgid "Related activity"
msgstr "Activitat relacionada"
msgctxt "field:activity.configuration,pending_mailbox:"
msgid "Pending Mailbox"
msgstr "Bústia de correu Pendents"
msgctxt "field:activity.configuration,processed_mailbox:"
msgid "Processed Mailbox"
msgstr "Bústia de correu Processats"
msgctxt "field:imap.server,employee:"
msgid "Default Employee"
msgstr "Empleat per defecte"
@ -26,6 +34,10 @@ msgctxt "field:res.user,mailbox:"
msgid "Mailbox"
msgstr "Bústia de correu"
msgctxt "field:res.user,signature_html:"
msgid "Signature"
msgstr "Signatura"
msgctxt "field:res.user,smtp_server:"
msgid "SMTP Server"
msgstr "Servidor SMTP"
@ -63,6 +75,10 @@ msgctxt "model:ir.message,text:no_valid_mail"
msgid "The \"%(mail)s\" of the party \"%(party)s\" it is not correct."
msgstr "El \"%(mail)s\" del tercer \"%(party)s\" no es correcte."
msgctxt "model:ir.model.button,string:electronic_mail_guess_button"
msgid "Guess Resource"
msgstr "Troba recurs"
msgctxt "model:ir.model.button,string:electronic_mail_new_button"
msgid "Send Mail"
msgstr "Envia correu"
@ -71,6 +87,10 @@ msgctxt "model:ir.model.button,string:electronic_mail_reply_button"
msgid "Reply Mail"
msgstr "Respon correu"
msgctxt "selection:ir.cron,method:"
msgid "Create Activity"
msgstr "Crear Activitat"
msgctxt "view:activity.activity:"
msgid "Reply Mail"
msgstr "Respon correu"

View File

@ -14,6 +14,14 @@ msgctxt "field:activity.activity,related_activity:"
msgid "Related activity"
msgstr "Actividad relacionada"
msgctxt "field:activity.configuration,pending_mailbox:"
msgid "Pending Mailbox"
msgstr "Buzón Pendientes"
msgctxt "field:activity.configuration,processed_mailbox:"
msgid "Processed Mailbox"
msgstr "Buzón Procesados"
msgctxt "field:imap.server,employee:"
msgid "Default Employee"
msgstr "Empleado por defecto"
@ -26,6 +34,10 @@ msgctxt "field:res.user,mailbox:"
msgid "Mailbox"
msgstr "Buzón de correo"
msgctxt "field:res.user,signature_html:"
msgid "Signature"
msgstr "Firma"
msgctxt "field:res.user,smtp_server:"
msgid "SMTP Server"
msgstr "Servidor SMTP"
@ -65,6 +77,10 @@ msgctxt "model:ir.message,text:no_valid_mail"
msgid "The \"%(mail)s\" of the party \"%(party)s\" it is not correct."
msgstr "El \"%(mail)s\" del tercero \"%(party)s\" no es correcto."
msgctxt "model:ir.model.button,string:electronic_mail_guess_button"
msgid "Guess Resource"
msgstr "Encuentra el Recurso"
msgctxt "model:ir.model.button,string:electronic_mail_new_button"
msgid "Send Mail"
msgstr "Envio correo"
@ -73,6 +89,10 @@ msgctxt "model:ir.model.button,string:electronic_mail_reply_button"
msgid "Reply Mail"
msgstr "Responder correo"
msgctxt "selection:ir.cron,method:"
msgid "Create Activity"
msgstr "Crear Actividad"
msgctxt "view:activity.activity:"
msgid "Reply Mail"
msgstr "Responder correo"

View File

@ -2,11 +2,10 @@
version=5.5.0
depends:
electronic_mail
electronic_mail_model
electronic_mail_wizard
activity_contact
xml:
activity.xml
imap.xml
user.xml
message.xml
configuration.xml

View File

@ -2,6 +2,9 @@
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/form/group[@id='buttons']/button[@name='activity_split']" position="after">
<button name="guess"/>
</xpath>
<xpath expr="/form/label[@name='state']" position="replace" />
<xpath expr="/form/field[@name='state']" position="replace">
<group id="state" colspan="4" col="6">

View File

@ -5,6 +5,7 @@
<xpath expr="/tree/field[@name='state']" position="after">
<button name="new"/>
<button name="reply"/>
<button name="guess"/>
<field name="have_mail" tree_invisible="1"/>
</xpath>
</data>

View File

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!--The COPYRIGHT file at the top level of this repository
contains the full copyright notices and license terms. -->
<data>
<xpath expr="/form" position="inside">
<label name="employee"/>
<field name="employee"/>
<label name="pending_mailbox"/>
<field name="pending_mailbox"/>
<label name="processed_mailbox"/>
<field name="processed_mailbox"/>
</xpath>
</data>

View File

@ -1,9 +0,0 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/form/field[@name='model']" position="after">
<label name="employee"/>
<field name="employee"/>
</xpath>
</data>