diff --git a/__init__.py b/__init__.py index 296d16d..0ecc117 100644 --- a/__init__.py +++ b/__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') diff --git a/activity.py b/activity.py index 71dcb68..cf6d678 100644 --- a/activity.py +++ b/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): diff --git a/activity.xml b/activity.xml index b1eb6db..03fe44f 100644 --- a/activity.xml +++ b/activity.xml @@ -3,6 +3,13 @@ copyright notices and license terms. --> + + activity.activity|create_activity + + + days + + Reply Mail activity.activity.replymail @@ -42,5 +49,15 @@ + + guess + Guess Resource + + + + + + diff --git a/configuration.py b/configuration.py new file mode 100644 index 0000000..fd12c08 --- /dev/null +++ b/configuration.py @@ -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) \ No newline at end of file diff --git a/configuration.xml b/configuration.xml new file mode 100644 index 0000000..5ab0e21 --- /dev/null +++ b/configuration.xml @@ -0,0 +1,12 @@ + + + + + + activity.configuration + + configuration_form + + + diff --git a/imap.py b/imap.py deleted file mode 100644 index f583096..0000000 --- a/imap.py +++ /dev/null @@ -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 diff --git a/imap.xml b/imap.xml deleted file mode 100644 index 9678774..0000000 --- a/imap.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - imap.server - - imap_server_form - - - diff --git a/locale/ca.po b/locale/ca.po index 1dabda1..8e66ffc 100644 --- a/locale/ca.po +++ b/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" diff --git a/locale/es.po b/locale/es.po index 4035963..bd8c194 100644 --- a/locale/es.po +++ b/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" diff --git a/tryton.cfg b/tryton.cfg index e17a30a..3398c8d 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -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 diff --git a/view/activity_activity_form.xml b/view/activity_activity_form.xml index 2901033..90da862 100644 --- a/view/activity_activity_form.xml +++ b/view/activity_activity_form.xml @@ -2,6 +2,9 @@ + +