448 lines
17 KiB
Python
448 lines
17 KiB
Python
# The COPYRIGHT file at the top level of this repository contains the full
|
|
# copyright notices and license terms.
|
|
import difflib
|
|
import html
|
|
import pytz
|
|
|
|
from urllib.parse import urlparse
|
|
from email.mime.text import MIMEText
|
|
from email.header import Header
|
|
from collections import OrderedDict
|
|
|
|
from trytond.model import ModelSQL, ModelView, fields
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.pyson import Eval
|
|
from trytond.sendmail import sendmail_transactional
|
|
from trytond.config import config
|
|
from trytond.transaction import Transaction
|
|
|
|
__all__ = ['WorkParty', 'Work']
|
|
|
|
FROM_ADDR = config.get('email', 'from')
|
|
URL = config.get('project_contact', 'url')
|
|
|
|
class WorkParty(ModelSQL):
|
|
'Work Party'
|
|
__name__ = "project.work-party.party"
|
|
|
|
work = fields.Many2One('project.work', 'Work',
|
|
required=True, ondelete='CASCADE')
|
|
party = fields.Many2One('party.party', 'Party', required=True)
|
|
|
|
|
|
class Work(metaclass=PoolMeta):
|
|
__name__ = "project.work"
|
|
|
|
allowed_contacts = fields.Function(fields.Many2Many('party.party',
|
|
None, None, 'Allowed Contacts',
|
|
context={
|
|
'company': Eval('company', -1),
|
|
},
|
|
depends=['company']),
|
|
'on_change_with_allowed_contacts')
|
|
contacts = fields.Many2Many('project.work-party.party', 'work',
|
|
'party', 'Contacts',
|
|
domain=[
|
|
('id', 'in', Eval('allowed_contacts', [])),
|
|
],
|
|
context={
|
|
'company': Eval('company', -1),
|
|
},
|
|
depends=['allowed_contacts', 'company'])
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Work,cls).__setup__()
|
|
cls._buttons.update({
|
|
'send_summary': {},
|
|
})
|
|
|
|
@staticmethod
|
|
def default_contacts():
|
|
DefaultRule = Pool().get('project.work.default_rule')
|
|
pattern = {
|
|
'project': None,
|
|
}
|
|
contacts = DefaultRule.compute(pattern)
|
|
return [x.id for x in contacts]
|
|
|
|
@fields.depends('_parent_parent.id','parent', methods=['get_default_rule_pattern'])
|
|
def on_change_with_contacts(self, name=None):
|
|
DefaultRule = Pool().get('project.work.default_rule')
|
|
pattern = self.get_default_rule_pattern()
|
|
contacts = DefaultRule.compute(pattern)
|
|
# Removes the list of existing contacts
|
|
return [x.id for x in contacts]
|
|
|
|
def get_default_rule_pattern(self):
|
|
return {
|
|
'project': self.parent.id if self.parent else None,
|
|
}
|
|
|
|
@fields.depends('party', 'company', '_parent_party.id')
|
|
def on_change_with_allowed_contacts(self, name=None):
|
|
pool = Pool()
|
|
Employee = pool.get('company.employee')
|
|
res = [e.party.id for e in Employee.search([])]
|
|
if not self.party:
|
|
return res
|
|
res.extend(r.to.id for r in self.party.relations)
|
|
return res
|
|
|
|
@staticmethod
|
|
def get_mail_fields():
|
|
# Dictionary for One2Many values
|
|
fields = ['name', 'effort_duration', 'comment', 'status']
|
|
res = OrderedDict.fromkeys(fields)
|
|
return res
|
|
|
|
def get_mail(self, one2many_values=None, old_values=None):
|
|
'''
|
|
Return Mail object or None if there are no recipients
|
|
'''
|
|
pool = Pool()
|
|
Employee = pool.get('company.employee')
|
|
|
|
if old_values is None:
|
|
old_values = {}
|
|
for field in self.get_mail_fields():
|
|
old_values.fromkeys(field, None)
|
|
if one2many_values is None:
|
|
one2many_values = {}
|
|
|
|
def get_value(field, value):
|
|
if isinstance(getattr(self.__class__, field), fields.Many2One):
|
|
if value:
|
|
if isinstance(value, int):
|
|
Model = Pool().get(getattr(self.__class__,
|
|
field).model_name)
|
|
value = Model(value)
|
|
return value.rec_name
|
|
return value
|
|
|
|
to_addr = []
|
|
employees = [e.party.id for e in Employee.search([])]
|
|
|
|
uid = self.write_uid or self.create_uid
|
|
if uid:
|
|
discard_employees = [ce.party.id for ce in uid.employees
|
|
if not uid.send_own_changes]
|
|
employees = set(employees) - set(discard_employees)
|
|
|
|
for party in self.contacts:
|
|
if party.id in employees:
|
|
to_addr.append(party.email)
|
|
|
|
if not to_addr:
|
|
return
|
|
|
|
url = '%s/model/project.work/%s' % (URL, getattr(self,'id'))
|
|
name = self.rec_name
|
|
|
|
body = []
|
|
body.append(u'<div style="background: #EEEEEE; padding-left: 10px; '
|
|
'padding-bottom: 10px">'
|
|
'<a href="%(url)s">'
|
|
'<h2 style="margin: 0px 0px 0px 0px; '
|
|
'padding: 0px 0px 0px 0px;">%(name)s</h2>'
|
|
'</a>' % {
|
|
'url':url,
|
|
'name' : name,
|
|
})
|
|
|
|
for field, subfields in self.get_mail_fields().items():
|
|
if old_values.get(field) == getattr(self, field):
|
|
continue
|
|
diff=[]
|
|
if isinstance(getattr(self.__class__, field), fields.Text):
|
|
old = old_values.get(field) or ''
|
|
old = old.splitlines(1)
|
|
new = getattr(self, field) or ''
|
|
new = new.splitlines(1)
|
|
diffs = difflib.unified_diff(old, new, n=3)
|
|
title = ' '.join([x.capitalize() for x in field.split('_')])
|
|
body.append(u'<br><b>{}</b>:'.format(title))
|
|
for diff in diffs:
|
|
diff = html.escape(diff)
|
|
if (diff.startswith('@') or diff.startswith('+++')
|
|
or diff.startswith('---')):
|
|
continue
|
|
if (diff.startswith('-')):
|
|
body.append(u'<font color="red">%s</font>' %
|
|
diff.rstrip('\n'))
|
|
continue
|
|
if (diff.startswith('+')):
|
|
body.append(u'<font color="green">%s</font>' %
|
|
diff.rstrip('\n'))
|
|
continue
|
|
else:
|
|
body.append(diff.rstrip('\n'))
|
|
continue
|
|
elif isinstance(getattr(self.__class__, field), fields.One2Many):
|
|
related_model = Pool().get(getattr(self.__class__,
|
|
field).model_name)
|
|
for one2many in one2many_values.get(field, []):
|
|
for subfield in subfields:
|
|
if isinstance(getattr(related_model, subfield),
|
|
fields.Text):
|
|
texts = one2many.get(subfield) or ''
|
|
texts = texts.splitlines(1)
|
|
title = ' '.join([x.capitalize() for x in subfield.split('_')])
|
|
body.append(u'<b>{}</b>:'.format(title))
|
|
for text in texts:
|
|
text = html.escape(text)
|
|
body.append(text)
|
|
elif isinstance(getattr(related_model, subfield),
|
|
fields.DateTime):
|
|
date = one2many.get(subfield)
|
|
date = date.strftime('%Y-%m-%d %H:%M') if date else '/'
|
|
title = ' '.join([x.capitalize() for x in subfield.split('_')])
|
|
body.append(u'<b>{}</b>: {}'.format(title,date))
|
|
elif isinstance(getattr(related_model, subfield),
|
|
fields.Many2One):
|
|
Model = Pool().get(getattr(related_model,
|
|
subfield).model_name)
|
|
value = Model(one2many.get(subfield))
|
|
title = ' '.join([x.capitalize() for x in subfield.split('_')])
|
|
body.append(u'<b>{}</b>: {}'.format(title,value.rec_name))
|
|
else:
|
|
title = ' '.join([x.capitalize() for x in subfield.split('_')])
|
|
body.append(u'<b>{}</b>: {}'.format(title,one2many.get(subfield)))
|
|
body.append(u'<br><hr style="border-top: 1px dashed;">')
|
|
else:
|
|
title = ' '.join([x.capitalize() for x in field.split('_')])
|
|
body.append(u'<b>{}</b>:'.format(title))
|
|
if field in old_values:
|
|
body.append(u'<font color="red">- {} </font>'.format(
|
|
get_value(field, old_values[field])))
|
|
body.append(u'<font color="green"> + {} </font>'.format(
|
|
get_value(field, getattr(self, field))))
|
|
|
|
Company = pool.get('company.company')
|
|
company_id = Transaction().context.get('company')
|
|
date = self.write_date or self.create_date
|
|
if company_id:
|
|
company = Company(company_id)
|
|
if company.timezone:
|
|
timezone = pytz.timezone(company.timezone)
|
|
date = timezone.localize(date)
|
|
date = date + date.utcoffset()
|
|
|
|
date = date.strftime('%Y-%m-%d %H:%M') if date else '/'
|
|
body.append('<br>'
|
|
'<small>'
|
|
'%(operation)s by %(write_user)s on %(write_date)s'
|
|
'</small>' % {
|
|
'operation': 'Updated' if old_values else 'Created',
|
|
'write_user': uid.name,
|
|
'write_date' : date,
|
|
})
|
|
|
|
body.append(u'</div>')
|
|
body = u'<br/>\n'.join(body)
|
|
body = u'''<html><head>
|
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
|
</head>
|
|
<body style="font-family: courier">%s</body>
|
|
</html>''' % body
|
|
|
|
msg = MIMEText(body, 'html',_charset='utf-8')
|
|
msg['From'] = FROM_ADDR
|
|
msg['To'] = ', '.join(to_addr)
|
|
msg['Subject'] = Header(u"Changes in %s" % self.rec_name, 'utf-8')
|
|
|
|
url = urlparse(url)
|
|
if old_values:
|
|
msg['In-Reply-To'] = "<{}@{}>".format(self.id, url.netloc)
|
|
return msg
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
records = super(Work, cls).create(vlist)
|
|
for record in records:
|
|
for values in vlist:
|
|
record.send_mail(record.get_mail())
|
|
return records
|
|
|
|
@classmethod
|
|
def write(cls, *args):
|
|
pool = Pool()
|
|
ModelData = pool.get('ir.model.data')
|
|
SummaryContacts = pool.get('project.work.summary_contacts')
|
|
|
|
actions = iter(args)
|
|
args = []
|
|
status_done_id = ModelData.get_id('project', 'work_done_status')
|
|
|
|
old_values = {}
|
|
to_addr = []
|
|
ready_to_send_summary = []
|
|
check_in_email_fields = []
|
|
one2many_values = {}
|
|
for records, values in zip(actions, actions):
|
|
if values.get('status') == status_done_id:
|
|
ready_to_send_summary += records
|
|
|
|
if set(values.keys()) & set(cls.get_mail_fields()):
|
|
check_in_email_fields += records
|
|
|
|
for record in records:
|
|
old_values[record.id] = {}
|
|
for field in cls.get_mail_fields():
|
|
old_values[record.id][field] = getattr(record, field)
|
|
|
|
if isinstance(getattr(record.__class__, field),
|
|
fields.One2Many):
|
|
|
|
if not field in values:
|
|
continue
|
|
if values[field][0][0] != 'create':
|
|
continue
|
|
|
|
for one2many_list in values[field][0][1]:
|
|
one2many_values.setdefault(field, []).append(
|
|
one2many_list)
|
|
|
|
for work in records:
|
|
for party in work.contacts:
|
|
to_addr.append(party.email)
|
|
args.extend((records, values))
|
|
|
|
super(Work, cls).write(*args)
|
|
|
|
actions = iter(args)
|
|
for record in check_in_email_fields:
|
|
record.send_mail(record.get_mail(one2many_values,
|
|
old_values[record.id]))
|
|
|
|
for work in ready_to_send_summary:
|
|
to_addr.extend(SummaryContacts.get_mail())
|
|
pattern = work.get_summary_contacts_pattern()
|
|
to_addr = SummaryContacts.compute(pattern)
|
|
work.send_summary_mail(to_addr)
|
|
|
|
def get_summary_contacts_pattern(self):
|
|
return {
|
|
'project': self.parent.id if self.parent else None,
|
|
}
|
|
|
|
def get_summary_mail(self, to_addr):
|
|
'''
|
|
Return Mail object or None if there are no recipients
|
|
'''
|
|
|
|
Employee = Pool().get('company.employee')
|
|
Company = Pool().get('company.company')
|
|
|
|
to_addr = []
|
|
employees = [e.party.id for e in Employee.search([])]
|
|
|
|
uid = self.write_uid or self.create_uid
|
|
if uid:
|
|
discard_employees = [ce.party.id for ce in uid.employees
|
|
if not uid.send_own_changes]
|
|
employees = set(employees) - set(discard_employees)
|
|
|
|
for party in self.contacts:
|
|
if party.id in employees:
|
|
to_addr.append(party.email)
|
|
|
|
if not to_addr:
|
|
return
|
|
|
|
def get_value(field, value):
|
|
if isinstance(getattr(self.__class__, field), fields.Many2One):
|
|
if value:
|
|
if isinstance(value, int):
|
|
Model = Pool().get(getattr(self.__class__,
|
|
field).model_name)
|
|
value = Model(value)
|
|
return value.rec_name
|
|
return value
|
|
|
|
url = '%s/model/project.work/%s' % (URL, getattr(self,'id'))
|
|
name = self.rec_name
|
|
|
|
body = []
|
|
|
|
body.append(u'<div style="background: #EEEEEE; padding-left: 10px; '
|
|
'padding-bottom: 10px">'
|
|
'<a href="%(url)s">'
|
|
'<h2 style="margin: 0px 0px 0px 0px; '
|
|
'padding: 0px 0px 0px 0px;"> %(name)s </h2>'
|
|
'</a>'
|
|
% {
|
|
'url':url,
|
|
'name' : name,
|
|
})
|
|
|
|
for field in self.get_mail_fields():
|
|
if isinstance(getattr(self.__class__, field), fields.One2Many):
|
|
continue
|
|
elif isinstance(getattr(self.__class__, field), fields.Text):
|
|
texts = getattr(self, field) or ''
|
|
texts = texts.splitlines(1)
|
|
title = ' '.join([x.capitalize() for x in field.split('_')])
|
|
body.append(u'<b>{}</b>:'.format(title))
|
|
for text in texts:
|
|
text = html.escape(text)
|
|
body.append(text)
|
|
else:
|
|
title = ' '.join([x.capitalize() for x in field.split('_')])
|
|
body.append(u'<b>{}</b>: {}'.format(title,get_value(field,
|
|
getattr(self, field))))
|
|
|
|
|
|
company_id = Transaction().context.get('company')
|
|
date = self.write_date or self.create_date
|
|
if company_id:
|
|
company = Company(company_id)
|
|
if company.timezone:
|
|
timezone = pytz.timezone(company.timezone)
|
|
date = timezone.localize(date)
|
|
date = date + date.utcoffset()
|
|
|
|
date = date.strftime('%Y-%m-%d %H:%M') if date else '/'
|
|
body.append('<br>'
|
|
'<small>'
|
|
'Closed by %(write_user)s on %(write_date)s'
|
|
'</small>' % {
|
|
'write_user': uid.name,
|
|
'write_date' : date,
|
|
})
|
|
|
|
body.append(u'</div>')
|
|
body = u'<br/>\n'.join(body)
|
|
body = u'''<html><head>
|
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
|
</head>
|
|
<body style="font-family: courier">%s</body>
|
|
</html>''' % body
|
|
|
|
msg = MIMEText(body, 'html',_charset='utf-8')
|
|
msg['From'] = FROM_ADDR
|
|
msg['To'] = ', '.join(to_addr)
|
|
msg['Subject'] = Header(u'Summary of %s' % self.rec_name, 'utf-8')
|
|
|
|
url = urlparse(url)
|
|
msg['In-Reply-To'] = "<{}@{}>".format(self.id, url.netloc)
|
|
return msg
|
|
|
|
def send_summary_mail(self, to_addr):
|
|
msg = self.get_summary_mail(to_addr)
|
|
if msg and msg['To']:
|
|
sendmail_transactional(msg['From'], msg['To'], msg)
|
|
|
|
def send_mail(self, msg):
|
|
if msg and msg['To']:
|
|
sendmail_transactional(msg['From'], msg['To'], msg)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def send_summary(cls, works):
|
|
SummaryContacts = Pool().get('project.work.summary_contacts')
|
|
to_addr = []
|
|
to_addr.extend(SummaryContacts.get_mail())
|
|
for work in works:
|
|
work.send_summary_mail(to_addr)
|