303 lines
11 KiB
Python
303 lines
11 KiB
Python
# This file is part of the sale_opportunity_talk module for Tryton.
|
|
# The COPYRIGHT file at the top level of this repository contains
|
|
# the full copyright notices and license terms.
|
|
from trytond.model import Workflow, ModelView, ModelSQL, fields
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.pyson import Eval
|
|
from trytond.transaction import Transaction
|
|
from datetime import datetime
|
|
from email import Utils
|
|
from email.header import Header
|
|
from email.mime.text import MIMEText
|
|
from email.utils import parseaddr
|
|
import logging
|
|
|
|
CHECK_EMAIL = False
|
|
try:
|
|
import emailvalid
|
|
CHECK_EMAIL = True
|
|
except ImportError:
|
|
import logging
|
|
logging.getLogger('Sale Opportunity Talk').warning(
|
|
'Unable to import emailvalid. Email validation disabled.')
|
|
|
|
__all__ = [
|
|
'SaleOpportunityTalk', 'SaleOpportunity']
|
|
__metaclass__ = PoolMeta
|
|
|
|
|
|
class SaleOpportunityTalk(Workflow, ModelSQL, ModelView):
|
|
'Sale Opportunity Talk'
|
|
__name__ = 'sale.opportunity.talk'
|
|
_rec_name = 'display_text'
|
|
date = fields.DateTime('Date', readonly=True)
|
|
email = fields.Char('email')
|
|
opportunity = fields.Many2One('sale.opportunity', 'Talks',
|
|
required=True, ondelete='CASCADE')
|
|
message = fields.Text('Comment')
|
|
display_text = fields.Function(fields.Text('Display Text'),
|
|
'get_display_text')
|
|
unread = fields.Boolean('Unread')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(SaleOpportunityTalk, cls).__setup__()
|
|
cls._order.insert(0, ('id', 'DESC'))
|
|
|
|
@staticmethod
|
|
def truncate_data(data):
|
|
data_list = data and data.split('\n') or []
|
|
if len(data_list) > 6:
|
|
res = '\n\t'.join(data_list[:6]) + '...'
|
|
else:
|
|
res = '\n\t'.join(data_list)
|
|
return res
|
|
|
|
def get_display_text(self, name=None):
|
|
display = ('(' + str(self.date) + '):\n' +
|
|
self.truncate_data(self.message))
|
|
if self.email:
|
|
display = self.email + ' ' + display
|
|
return display
|
|
|
|
|
|
class SaleOpportunity:
|
|
__name__ = "sale.opportunity"
|
|
talks = fields.One2Many('sale.opportunity.talk', 'opportunity', 'Talks')
|
|
message = fields.Text('Comment')
|
|
email_from = fields.Char('Email')
|
|
email_cc = fields.Char('CC')
|
|
message_id = fields.Char('Message ID')
|
|
phone = fields.Char('Phone')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(SaleOpportunity, cls).__setup__()
|
|
cls._order.insert(1, ('id', 'DESC'))
|
|
cls._buttons.update({
|
|
'add_reply': {},
|
|
'talk_note': {},
|
|
'talk_email': {},
|
|
})
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
return ['OR',
|
|
('reference',) + tuple(clause[1:]),
|
|
('description',) + tuple(clause[1:]),
|
|
('party',) + tuple(clause[1:]),
|
|
('email_from',) + tuple(clause[1:]),
|
|
('email_cc',) + tuple(clause[1:]),
|
|
]
|
|
|
|
@classmethod
|
|
def _talk(self, opportunities):
|
|
pool = Pool()
|
|
Talk = pool.get('sale.opportunity.talk')
|
|
User = pool.get('res.user')
|
|
user = User(Transaction().user)
|
|
|
|
reads = []
|
|
for opportunity in opportunities:
|
|
if not opportunity.message:
|
|
self.raise_user_error('no_description')
|
|
talk = Talk()
|
|
talk.date = datetime.now()
|
|
talk.email = user.email or None
|
|
talk.opportunity = opportunity
|
|
talk.message = opportunity.message
|
|
talk.unread = False
|
|
talk.save()
|
|
|
|
for talk in opportunity.talks:
|
|
if talk.unread:
|
|
reads.append(talk)
|
|
|
|
if reads:
|
|
Talk.write(reads, {'unread': False})
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def add_reply(self, opportunities):
|
|
for opportunity in opportunities:
|
|
if opportunity.talks:
|
|
message = opportunity.talks[0].message
|
|
self.write([opportunity], {
|
|
'message': '> ' + message.replace('\n', '\n> '),
|
|
})
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def talk_email(self, opportunities):
|
|
SMTP = Pool().get('smtp.server')
|
|
|
|
for opportunity in opportunities:
|
|
if not opportunity.email_from:
|
|
self.raise_user_error('no_email_from')
|
|
if not opportunity.message:
|
|
self.raise_user_error('no_description')
|
|
|
|
server = SMTP.get_smtp_server_from_model('sale.opportunity')
|
|
self.send_email(opportunities, server) # Send email
|
|
|
|
self._talk(opportunities)
|
|
self.write(opportunities, {'message': None})
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def talk_note(self, opportunities):
|
|
self._talk(opportunities)
|
|
self.write(opportunities, {'message': None})
|
|
|
|
def on_change_party(self):
|
|
pool = Pool()
|
|
Address = pool.get('party.address')
|
|
Contact = pool.get('party.contact_mechanism')
|
|
|
|
changes = super(SaleOpportunity, self).on_change_party()
|
|
if self.party:
|
|
addresses = Address.search([('party', '=', self.party)])
|
|
for address in addresses:
|
|
changes['address'] = address.id
|
|
if address.email:
|
|
changes['email_from'] = address.email
|
|
break
|
|
if not changes.get('email_from'):
|
|
for contact in Contact.search([('party', '=', self.party)]):
|
|
if contact.type == 'email':
|
|
changes['email_from'] = contact.email
|
|
break
|
|
return changes
|
|
|
|
@classmethod
|
|
def send_email(self, opportunities, server):
|
|
pool = Pool()
|
|
SMTP = pool.get('smtp.server')
|
|
User = pool.get('res.user')
|
|
user = User(Transaction().user)
|
|
|
|
from_ = user.email or server.smtp_email
|
|
if server.smtp_use_email:
|
|
from_ = server.smtp_email
|
|
|
|
for opportunity in opportunities:
|
|
if not opportunity.email_from:
|
|
self.raise_user_error('no_email_from')
|
|
if not opportunity.description:
|
|
self.raise_user_error('no_description')
|
|
|
|
recipients = []
|
|
emails = opportunity.email_from.replace(' ', '').replace(',', ';')
|
|
emails = emails.split(';')
|
|
recipients = recipients + emails
|
|
|
|
cc_addresses = []
|
|
if opportunity.email_cc:
|
|
emails = (opportunity.email_cc.replace(' ', '').replace(
|
|
',', ';'))
|
|
emails = emails.split(';')
|
|
cc_addresses = cc_addresses + emails
|
|
|
|
if CHECK_EMAIL:
|
|
for recipient in recipients:
|
|
if not emailvalid.check_email(recipient):
|
|
self.raise_user_error('no_from_valid')
|
|
if cc_addresses:
|
|
for cc_address in cc_addresses:
|
|
if not emailvalid.check_email(cc_address):
|
|
self.raise_user_error('no_recepients_valid')
|
|
|
|
msg = MIMEText(opportunity.message, _charset='utf-8')
|
|
msg['Subject'] = Header(opportunity.description, 'utf-8')
|
|
msg['From'] = from_
|
|
msg['To'] = ', '.join(recipients)
|
|
msg['Cc'] = ', '.join(cc_addresses) if cc_addresses else None
|
|
msg['Reply-to'] = server.smtp_email
|
|
msg['Message-ID'] = Utils.make_msgid()
|
|
|
|
if opportunity.message_id:
|
|
msg["In-Reply-To"] = opportunity.message_id
|
|
|
|
try:
|
|
server = SMTP.get_smtp_server(server)
|
|
server.sendmail(from_, recipients + cc_addresses,
|
|
msg.as_string())
|
|
server.quit()
|
|
except:
|
|
self.raise_user_error('smtp_error')
|
|
|
|
if not opportunity.message_id:
|
|
self.write([opportunity], {
|
|
'message_id': msg.get('Message-ID'),
|
|
})
|
|
|
|
@classmethod
|
|
def getmail(self, messages, attachments=None):
|
|
'''Get messages and load in opportunity talks'''
|
|
pool = Pool()
|
|
GetMail = pool.get('getmail.server')
|
|
SaleOpportunity = pool.get('sale.opportunity')
|
|
SaleOpportunityTalk = pool.get('sale.opportunity.talk')
|
|
Attachment = pool.get('ir.attachment')
|
|
|
|
for (messageid, message) in messages:
|
|
msgeid = message.messageid
|
|
msgfrom = parseaddr(message.from_addr)[1] if message.from_addr else None
|
|
msgcc = message.cc if not message.cc == 'None' else None
|
|
msgreferences = message.references
|
|
msginrepplyto = getattr(message, "inrepplyto", None)
|
|
msgsubject = message.title or 'Not subject'
|
|
msgdate = message.date
|
|
msgbody = message.body
|
|
|
|
logging.getLogger('Sale Opportunity').info('Process email: %s' % (msgeid))
|
|
|
|
opportunity = None
|
|
if msgreferences or msginrepplyto:
|
|
references = msgreferences or msginrepplyto
|
|
if '\r\n' in references:
|
|
references = references.split('\r\n')
|
|
else:
|
|
references = references.split(' ')
|
|
for ref in references:
|
|
ref = ref.strip()
|
|
opportunity = self.search([('message_id', '=', ref)], limit=1)
|
|
if opportunity:
|
|
opportunity = opportunity[0]
|
|
break
|
|
|
|
# Create a new sale opportunity
|
|
if not opportunity:
|
|
party, address = GetMail.get_party_from_email(msgfrom)
|
|
|
|
opportunity = SaleOpportunity()
|
|
opportunity.description = msgsubject
|
|
#~ oppotunity.start_date = GetMail.get_date(msgdate)
|
|
opportunity.email_from = msgfrom
|
|
opportunity.email_cc = msgcc
|
|
opportunity.party = party if party else None
|
|
opportunity.address = address if address else None
|
|
opportunity.message_id = msgeid
|
|
opportunity.save()
|
|
|
|
# Create a new project_opportunity talk
|
|
opportunity_talk = SaleOpportunityTalk()
|
|
opportunity_talk.date = GetMail.get_date(msgdate)
|
|
opportunity_talk.email = msgfrom
|
|
opportunity_talk.opportunity = opportunity
|
|
opportunity_talk.message = msgbody
|
|
opportunity_talk.unread = True
|
|
opportunity_talk.save()
|
|
|
|
# Create a attachments
|
|
if attachments:
|
|
for attachment in message.attachments:
|
|
attach = Attachment()
|
|
attach.name = attachment[0]
|
|
attach.type = 'data'
|
|
attach.data = attachment[1]
|
|
attach.resource = '%s' % (opportunity)
|
|
attach.save()
|
|
|
|
return True
|