Added activity creation via mail and resource guess
This commit is contained in:
parent
409d7a62a7
commit
67b9a58870
15
__init__.py
15
__init__.py
|
@ -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')
|
||||
|
|
244
activity.py
244
activity.py
|
@ -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):
|
||||
|
|
17
activity.xml
17
activity.xml
|
@ -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>
|
||||
|
|
|
@ -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)
|
|
@ -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
30
imap.py
|
@ -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
|
12
imap.xml
12
imap.xml
|
@ -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>
|
20
locale/ca.po
20
locale/ca.po
|
@ -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"
|
||||
|
|
20
locale/es.po
20
locale/es.po
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
Loading…
Reference in New Issue